Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
72804f7
chore: trigger renovate
nirooxx Mar 27, 2025
b0421f1
Merge branch 'main' of https://github.com/open-resource-discovery/git…
nirooxx May 16, 2025
14b17c9
Merge branch 'main' of https://github.com/open-resource-discovery/git…
nirooxx May 27, 2025
ef99f80
Merge branch 'main' of https://github.com/open-resource-discovery/git…
nirooxx May 28, 2025
71f081a
Merge branch 'main' of https://github.com/open-resource-discovery/git…
nirooxx Jun 3, 2025
7e2b836
Merge branch 'main' of https://github.com/open-resource-discovery/git…
nirooxx Aug 6, 2025
9acf467
Merge branch 'main' of https://github.com/open-resource-discovery/git…
nirooxx Nov 27, 2025
f21630c
fix: configure git to use GITHUB_TOKEN for GHE fetch operations
nirooxx Nov 27, 2025
9acc674
fix(tsconfig): enable isolatedModules for NodeNext/ts-jest compatibility
nirooxx Nov 27, 2025
af4869e
fix: deduplicate contributors by GitHub login instead of email
nirooxx Mar 13, 2026
9ebef55
Merge branch 'main' into fix/contributor-deduplication
nirooxx Mar 13, 2026
f6834cb
Merge branch 'main' into fix/contributor-deduplication
nirooxx Mar 26, 2026
4b7387c
Update scripts/collect-commits.sh
nirooxx Mar 26, 2026
a299689
test: add proper unit tests for release creation
nirooxx Mar 26, 2026
63da9df
Merge branch 'fix/contributor-deduplication' of https://github.com/op…
nirooxx Mar 26, 2026
3758c14
Merge branch 'main' into fix/contributor-deduplication
nirooxx Mar 31, 2026
421d02f
refactor: make env checks more explicit in release creation
nirooxx Mar 31, 2026
75a8224
Merge branch 'fix/contributor-deduplication' of https://github.com/op…
nirooxx Mar 31, 2026
6100ea2
Solving merge conflicts
nirooxx Mar 31, 2026
64faec5
Clean up
nirooxx Mar 31, 2026
34a8755
feat: modernize release contributors layout
nirooxx May 15, 2026
6a93429
chore: merge main into branch and resolve conflicts
Copilot May 15, 2026
cfa6f8b
test: cover release scripts and GitHub release flow
nirooxx May 15, 2026
222150e
Merge branch 'improve/release-contributors-layout' of https://github.…
nirooxx May 15, 2026
2b35fcf
test: cover release scripts and GitHub release flow
nirooxx May 15, 2026
207c828
Merge branch 'main' into improve/release-contributors-layout
nirooxx May 26, 2026
80bc746
Merge branch 'main' into improve/release-contributors-layout
nirooxx Jun 10, 2026
c6b4101
test: update release body expectations for modern layout
nirooxx Jun 10, 2026
8bd9804
feat: improve release notes layout and PR link rendering
nirooxx Jun 10, 2026
dbc7c9a
Merge branch 'main' into improve/release-contributors-layout
nirooxx Jun 13, 2026
3773653
Dispatch CI workflows for generated changelog PRs (#177)
nirooxx Jul 3, 2026
2fe0244
Merge remote-tracking branch 'origin/main' into improve/release-contr…
Copilot Jul 3, 2026
0aaff74
fix: configure git identity in cloned repo before committing in test
Copilot Jul 3, 2026
11bb084
refactor: switch Docker action runtime to TypeScript
nirooxx Jul 3, 2026
a09ca93
Merge branch 'main' into improve/release-contributors-layout
nirooxx Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"permissions": {
"allow": [
"Bash(pwd)",
"Bash(ls*)",
"Bash(dir*)",
"Bash(Get-ChildItem*)",
"Bash(Get-Content*)",
"Bash(Select-String*)",
"Bash(Test-Path*)",
"Bash(npm install*)",
"Bash(npm ci*)",
"Bash(npm run *)",
"Bash(npm test*)",
"Bash(npx *)",
"Bash(node *)",
"Bash(git status*)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(git branch*)",
"Bash(Start-Sleep -Seconds 5)",
"Bash(powershell -Command \"docker ps\")"
],
"deny": [
"Bash(git commit*)",
"Bash(git push*)",
"Bash(git tag*)",
"Bash(git reset --hard*)",
"Bash(rm -rf*)",
"Bash(Remove-Item * -Recurse*)",
"Bash(del *)",
"Bash(rmdir *)"
],
"ask": [
"Bash(git add*)",
"Bash(git restore*)",
"Bash(git checkout*)",
"Bash(git stash*)",
"Bash(mkdir*)",
"Bash(New-Item*)",
"Bash(Copy-Item*)",
"Bash(Move-Item*)"
],
"defaultMode": "default"
}
}
16 changes: 16 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,19 @@ jobs:
uses: ./
with:
dry-run: true

docker-smoke:
name: Docker Smoke Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build Docker image
run: docker build --no-cache -t github-release-action:test .

- name: Run smoke test
run: |
output="$(docker run --rm github-release-action:test --smoke-test)"
echo "$output"
echo "$output" | grep -qF "GITHUB_RELEASE_ACTION_RUNTIME=typescript-v1"
echo "$output" | grep -qF "TypeScript Docker runtime smoke test passed."
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ yarn-error.log

# GitHub Actions cache
.github/workflows/node_modules/

# Claude Code local state (worktrees, shell snapshots, plans, session data)
.claude/worktrees/
.claude/shell-snapshots/
.claude/plans/
.claude/projects/
.claude/todos/
.claude/history/
.claude/*.local.json
.claude/settings.local.json
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
## [unreleased]

### Added

- Added automated GitHub release creation.
- Added changelog-based release notes.
- Added configurable tag templates and release metadata.
- Added contributor detection and GitHub-native mentions.
- Added optional CI workflow dispatch for generated changelog PRs.
10 changes: 3 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ COPY . .

# Compile TypeScript files
RUN npm run build
RUN test -f /app/dist/src/main.js

# Remove unnecessary files after build
RUN npm prune --production
Expand All @@ -32,11 +33,6 @@ WORKDIR /app
COPY --from=build /app /app

# Install packages
RUN apk add --no-cache git jq curl
RUN apk add --no-cache git

# Ensure scripts and compiled TypeScript files are executable
RUN chmod +x /app/scripts/*.sh
RUN chmod +x /app/dist/src/release.js

# Set the entrypoint script
ENTRYPOINT ["/bin/sh", "/app/scripts/entrypoint.sh"]
ENTRYPOINT ["node", "/app/dist/src/main.js"]
14 changes: 14 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ inputs:
description: "Explicit version for releasing (overwrites package.json)"
required: false
default: ""
ci-workflows:
description: >
Comma-separated workflow file names to dispatch after creating a changelog PR.
Use 'auto' (default) to dispatch all workflow_dispatch-enabled workflows except the release workflow itself.
Use 'none' or 'false' to disable dispatch entirely.
Dispatched CI job results are mirrored as GitHub Check Runs on the changelog PR head SHA,
so required checks are satisfied automatically.
Requires: permissions: actions: write, checks: write (plus contents: write, pull-requests: write).
required: false
default: "auto"

outputs:
release-url:
description: "URL of the created GitHub release."

runs:
using: "docker"
Expand Down
189 changes: 101 additions & 88 deletions scripts/collect-commits.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,23 @@ else
fi
fi

# Prepare full changelog link for GitHub release notes.
if [ -n "$prev_semver" ]; then
full_changelog_url="$BASE_URL/$REPO/compare/$prev_semver...$TAG"
printf '**Full Changelog**: [%s...%s](%s)\n' "$prev_semver" "$TAG" "$full_changelog_url" > full_changelog.txt
else
: > full_changelog.txt
fi

# Check if commit range is valid
if [ -z "$commit_range" ]; then
echo "No commit range defined. Skipping commit collection."
return 0
fi

# Collect commit log and contributors
commit_data=$(git log "$commit_range" --max-count=30 --pretty=format:"%H|%an|%ae") || { echo "::error:: commit data failed"; return 0; }
field_sep=$(printf '\037')
commit_data=$(git log "$commit_range" --max-count=30 --pretty=format:"%H${field_sep}%h${field_sep}%an${field_sep}%ae${field_sep}%s") || { echo "::error:: commit data failed"; return 0; }
if [ -z "$commit_data" ]; then
echo "No commits found in the specified range."
commit_log="* No changes since last release."
Expand All @@ -97,118 +106,122 @@ if [ -z "$commit_data" ]; then
echo "Dry-Run: Skipping writing 'commit_log.txt' and 'contributors.txt'."
else
echo "$commit_log" > commit_log.txt
echo "<table><tr><td>No contributors found</td></tr></table>" > contributors.txt
: > contributors.txt
fi
return 0
fi

# Collect commit log
commit_log=$(git log "$commit_range" --max-count=30 --pretty=format:"* [%h]($BASE_URL/$REPO/commit/%H) %s (%an)")
log_status=$?

if [ $log_status -ne 0 ]; then
echo "::error:: git log failed with exit code $log_status"
echo "::error:: commit_range was '$commit_range'"
return 0
fi

# Extract unique contributor emails and commit hashes
commit_emails=$(echo "$commit_data" | awk -F"|" '{print $3}' | sort | uniq)

# Save commit log to a file
if [ "$DRY_RUN" = "true" ]; then
echo "Dry-Run: Skipping writing 'commit_log.txt'."
else
echo "$commit_log" > commit_log.txt
fi
# Build GitHub-native release notes with @mentions and PR links.
commit_log_file=$(mktemp)
contributors_mentions_file=$(mktemp)
seen_logins_file=$(mktemp)
seen_prs_file=$(mktemp)

printf '%s\n' "$commit_data" | while IFS="$field_sep" read -r commit_sha short_sha author_name author_email subject; do
[ -z "$commit_sha" ] && continue

login=""
pr_number=""
pr_title=""
pr_url=""
pr_user=""
commit_url="$BASE_URL/$REPO/commit/$commit_sha"

commit_response=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"$BASE_API_URL/repos/$REPO/commits/$commit_sha")

if printf '%s\n' "$commit_response" | jq empty > /dev/null 2>&1; then
login=$(printf '%s\n' "$commit_response" | jq -r '.author.login // empty')
fi

# Skip contributor collection in dry-run mode
if [ "$DRY_RUN" = "true" ]; then
echo "Dry-Run: Skipping contributor collection."
contributor_details="<table><tr><td>Dry-Run: Contributor collection skipped</td></tr></table>"
else
pr_response_file=$(mktemp)
pr_http_code=$(curl -sS -o "$pr_response_file" -w "%{http_code}" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"$BASE_API_URL/repos/$REPO/commits/$commit_sha/pulls?per_page=1" || echo "000")

pr_response=$(cat "$pr_response_file")
rm -f "$pr_response_file"

if [ "$pr_http_code" = "200" ] && \
printf '%s\n' "$pr_response" | jq -e 'type == "array" and length > 0' > /dev/null 2>&1; then
pr_number=$(printf '%s\n' "$pr_response" | jq -r '.[0].number // empty')
pr_title=$(printf '%s\n' "$pr_response" | jq -r '.[0].title // empty')
pr_url=$(printf '%s\n' "$pr_response" | jq -r '.[0].html_url // empty')
pr_user=$(printf '%s\n' "$pr_response" | jq -r '.[0].user.login // empty')
else
pr_number=$(printf '%s\n' "$subject" | sed -n 's/.*[Mm]erge pull request #\([0-9][0-9]*\).*/\1/p')

# Prepare contributors list with profile pictures
contributor_details="<table><tr>"
seen_logins=""
for email in $commit_emails; do
login=""
full_name=""
profile_url=""
avatar_url=""

if echo "$email" | grep -q '\[bot\]'; then
echo "Skipping bot user: $email"
continue
if [ -z "$pr_number" ]; then
pr_number=$(printf '%s\n' "$subject" | sed -n 's/.*(#\([0-9][0-9]*\)).*/\1/p')
fi

commit_sha=$(echo "$commit_data" | awk -F"|" -v email="$email" '$3 == email { print $1; exit }')

if [ -n "$commit_sha" ]; then
if [ -n "$pr_number" ]; then
pr_url="$BASE_URL/$REPO/pull/$pr_number"

commit_response=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"$BASE_API_URL/repos/$REPO/commits/$commit_sha")

if echo "$commit_response" | jq empty > /dev/null 2>&1; then
login=$(echo "$commit_response" | jq -r '.author.login // empty')
if printf '%s\n' "$subject" | grep -qi '^Merge pull request #[0-9]'; then
pr_title="Pull request #$pr_number"
else
pr_title="$subject"
fi
fi
else
echo "::warning:: No commit SHA found for email $email. Skipping commit lookup."
fi

# Step 3: Final check
if [ -z "$login" ] || [ "$login" = "empty" ]; then
echo "::warning:: No valid GitHub user found for email $email or commit lookup. Skipping..."
continue
login="$pr_user"
fi

case " $seen_logins " in
*" $login "*)
echo "Skipping duplicate contributor login: $login"
continue
;;
esac
seen_logins="$seen_logins $login"

# Fetch GitHub user details
user_response=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"$BASE_API_URL/users/$login")


if echo "$user_response" | jq empty > /dev/null 2>&1; then
full_name=$(echo "$user_response" | jq -r '.name // empty')
profile_url=$(echo "$user_response" | jq -r '.html_url // empty')
avatar_url=$(echo "$user_response" | jq -r '.avatar_url // empty')
is_bot=false
if printf '%s\n' "$author_email" | grep -q '\[bot\]' || \
printf '%s\n' "$login" | grep -q '\[bot\]'; then
is_bot=true
fi

# If no full name is found, use the login name
if [ -z "$full_name" ] || [ "$full_name" = "empty" ]; then
full_name="$login"
if [ -n "$login" ] && [ "$login" != "empty" ] && [ "$is_bot" = "false" ]; then
if ! grep -Fxq -- "$login" "$seen_logins_file"; then
printf '%s\n' "$login" >> "$seen_logins_file"
printf '@%s\n' "$login" >> "$contributors_mentions_file"
fi
fi

if [ -z "$profile_url" ] || [ -z "$avatar_url" ] || [ "$profile_url" = "empty" ] || [ "$avatar_url" = "empty" ]; then
echo "::warning:: No valid GitHub profile for $email. Skipping..."
if [ -n "$pr_number" ] && [ "$pr_number" != "empty" ] && \
[ -n "$pr_url" ] && [ "$pr_url" != "empty" ]; then
if grep -Fxq -- "$pr_number" "$seen_prs_file"; then
continue
fi

# Build contributor HTML
contributor_details="$contributor_details<td align='center'>
<a href='$profile_url'>
<img src='$avatar_url' alt='$full_name' width='50' height='50' style='border-radius: 50%;'><br>
<span>$full_name</span>
</a>
</td>"
printf '%s\n' "$pr_number" >> "$seen_prs_file"

if [ -z "$pr_title" ] || [ "$pr_title" = "empty" ]; then
pr_title="$subject"
fi

if [ -n "$login" ] && [ "$login" != "empty" ] && [ "$is_bot" = "false" ]; then
printf '* %s by @%s in [#%s](%s)\n' "$pr_title" "$login" "$pr_number" "$pr_url" >> "$commit_log_file"
else
printf '* %s in [#%s](%s)\n' "$pr_title" "$pr_number" "$pr_url" >> "$commit_log_file"
fi

continue
fi

if [ -n "$login" ] && [ "$login" != "empty" ] && [ "$is_bot" = "false" ]; then
printf '* %s by @%s in [%s](%s)\n' "$subject" "$login" "$short_sha" "$commit_url" >> "$commit_log_file"
else
Comment thread
nirooxx marked this conversation as resolved.
printf '* %s by %s in [%s](%s)\n' "$subject" "$author_name" "$short_sha" "$commit_url" >> "$commit_log_file"
fi
done

contributor_details="$contributor_details</tr></table>"
fi
commit_log=$(cat "$commit_log_file")
contributors_mentions=$(paste -sd' ' "$contributors_mentions_file")

rm -f "$commit_log_file" "$contributors_mentions_file" "$seen_logins_file" "$seen_prs_file"

# Save contributors to a file
if [ "$DRY_RUN" = "true" ]; then
echo "Dry-Run: Skipping writing 'contributors.txt'."
echo "$contributor_details" > contributors.txt
echo "Dry-Run: Skipping writing 'commit_log.txt' and 'contributors.txt'."
else
echo "$contributor_details" > contributors.txt
fi
echo "$commit_log" > commit_log.txt
printf '%s\n' "$contributors_mentions" > contributors.txt
fi
Loading
Loading