Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 29 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ When a PR is merged (and the release label requirement is met, if enabled):
2. Analyzes all commits since that tag using conventional commit prefixes
3. Determines the appropriate semver bump (major, minor, or patch)
4. Creates a `release/<prefix><version>` branch
5. Optionally generates/updates `CHANGELOG.md`
6. Opens a release PR with a summary of what's included
5. Bumps `package.json` version if npm publishing is enabled
6. Optionally generates/updates `CHANGELOG.md`
7. Opens a release PR with a summary of what's included

### Phase 2: Finalize the release

When the release PR (detected by the `release/` branch prefix) is merged:

1. Creates an annotated git tag
2. Optionally creates a GitHub release with auto-generated release notes
3. Optionally publishes to an npm registry

## Usage

Expand Down Expand Up @@ -76,6 +78,9 @@ jobs:
| `create-release` | Create a GitHub release when the release PR is merged | No | `true` |
| `draft-release` | Create the GitHub release as a draft (unpublished) | No | `true` |
| `changelog` | Generate or update `CHANGELOG.md` in the release PR | No | `true` |
| `publish-npm` | Publish the package to an npm registry when the release PR is merged | No | `false` |
| `npm-token` | Auth token for the npm registry (required when `publish-npm` is `true`) | No | — |
| `npm-registry` | npm registry URL (set to `https://npm.pkg.github.com` for GitHub Packages) | No | `https://registry.npmjs.org` |

## Outputs

Expand Down Expand Up @@ -163,6 +168,27 @@ Commits that can't be linked to a PR will reference the commit SHA instead.
version-prefix: ''
```

### Publish to npm

```yaml
- uses: offload-project/release-champion@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
publish-npm: true
npm-token: ${{ secrets.NPM_TOKEN }}
```

### Publish to GitHub Packages

```yaml
- uses: offload-project/release-champion@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
publish-npm: true
npm-token: ${{ secrets.GITHUB_TOKEN }}
npm-registry: 'https://npm.pkg.github.com'
```

### Tag only, no GitHub release

```yaml
Expand Down Expand Up @@ -190,8 +216,7 @@ steps:

## Permissions

The workflow needs `contents: write` (for tags and releases) and `pull-requests: write` (for creating PRs). If using the
default `GITHUB_TOKEN`, set these under the job's `permissions` key.
The workflow needs `contents: write` (for tags and releases) and `pull-requests: write` (for creating PRs). If publishing to GitHub Packages, also add `packages: write`. If using the default `GITHUB_TOKEN`, set these under the job's `permissions` key.

## Tests

Expand Down
14 changes: 14 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ inputs:
description: 'Generate or update CHANGELOG.md in the release PR'
required: false
default: 'true'
publish-npm:
description: 'Publish the package to an npm registry when the release PR is merged'
required: false
default: 'false'
npm-token:
description: 'Auth token for the npm registry (required when publish-npm is true)'
required: false
npm-registry:
description: 'npm registry URL (e.g., https://npm.pkg.github.com for GitHub Packages)'
required: false
default: 'https://registry.npmjs.org'

outputs:
version:
Expand Down Expand Up @@ -66,6 +77,9 @@ runs:
INPUT_CREATE_RELEASE: ${{ inputs.create-release }}
INPUT_DRAFT_RELEASE: ${{ inputs.draft-release }}
INPUT_CHANGELOG: ${{ inputs.changelog }}
INPUT_PUBLISH_NPM: ${{ inputs.publish-npm }}
INPUT_NPM_TOKEN: ${{ inputs.npm-token }}
INPUT_NPM_REGISTRY: ${{ inputs.npm-registry }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_MERGED: ${{ github.event.pull_request.merged }}
Expand Down
23 changes: 23 additions & 0 deletions scripts/create-release-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ main() {

git checkout -b "$branch_name"

# Bump package.json version if npm publishing is enabled
if [[ "${INPUT_PUBLISH_NPM:-false}" == "true" ]]; then
if [[ ! -f package.json ]]; then
echo "::error::publish-npm is enabled but no package.json found"
exit 1
fi
echo "Bumping package.json to ${new_version}"
npm version "$new_version" --no-git-tag-version
git add package.json
if [[ -f package-lock.json ]] && ! git diff --staged --quiet package-lock.json 2>/dev/null; then
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package-lock.json staging condition is checking the staged diff before the file is staged, so it will never be added even if npm version modified it. This can leave package-lock.json updated in the working tree but not committed/pushed on the release branch. Check for an unstaged change (or just git add package-lock.json when the file exists) before committing.

Suggested change
if [[ -f package-lock.json ]] && ! git diff --staged --quiet package-lock.json 2>/dev/null; then
if [[ -f package-lock.json ]]; then

Copilot uses AI. Check for mistakes.
git add package-lock.json
fi
git commit -m "chore: bump package version to ${new_version}"
fi

# Generate changelog if enabled
if [[ "$INPUT_CHANGELOG" == "true" ]]; then
echo "Generating changelog..."
Expand Down Expand Up @@ -124,6 +139,14 @@ generate_pr_body() {
fi
fi

if [[ "${INPUT_PUBLISH_NPM:-false}" == "true" ]]; then
if [[ "$INPUT_NPM_REGISTRY" == *"npm.pkg.github.com"* ]]; then
body+="*This release will be published to GitHub Packages.*"$'\n'
else
body+="*This release will be published to npm.*"$'\n'
fi
fi

echo "$body"
}

Expand Down
31 changes: 31 additions & 0 deletions scripts/finalize-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,37 @@ main() {
echo "release_url=${release_url}" >> "$GITHUB_OUTPUT"
fi

# Publish to npm
if [[ "${INPUT_PUBLISH_NPM:-false}" == "true" ]]; then
echo "::group::Publishing to npm"

if ! command -v npm &>/dev/null; then
echo "::error::npm is not installed. Add a setup-node step before release-champion."
exit 1
fi

if [[ -z "${INPUT_NPM_TOKEN:-}" ]]; then
echo "::error::npm-token is required when publish-npm is true"
exit 1
fi

local registry="${INPUT_NPM_REGISTRY:-https://registry.npmjs.org}"
local registry_host
registry_host=$(echo "$registry" | sed 's|https:||')

echo "${registry_host}:_authToken=${INPUT_NPM_TOKEN}" > .npmrc
Comment on lines +77 to +79
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated auth line in .npmrc is likely malformed for the default registry (and GitHub Packages). registry_host becomes //registry.npmjs.org and the script writes //registry.npmjs.org:_authToken=..., but npm expects //registry.npmjs.org/:_authToken=... (note the trailing /). Consider normalizing the registry URL/host and ensuring the auth line includes //<host>/:_authToken=... so publishing actually authenticates.

Suggested change
registry_host=$(echo "$registry" | sed 's|https:||')
echo "${registry_host}:_authToken=${INPUT_NPM_TOKEN}" > .npmrc
registry_host="${registry#http://}"
registry_host="${registry_host#https://}"
registry_host="${registry_host%%/*}"
echo "//${registry_host}/:_authToken=${INPUT_NPM_TOKEN}" > .npmrc

Copilot uses AI. Check for mistakes.
echo "registry=${registry}" >> .npmrc

npm publish

rm -f .npmrc
Comment on lines +79 to +84
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This writes credentials to a workspace .npmrc and then deletes it, but (1) rm -f .npmrc won’t run if npm publish fails (leaving the token behind for subsequent steps), and (2) using a fixed .npmrc path can clobber a repo’s existing .npmrc (common in npm projects). Use a temp userconfig (e.g., via NPM_CONFIG_USERCONFIG / npm --userconfig) and a trap to guarantee cleanup, without overwriting/removing any pre-existing .npmrc.

Suggested change
echo "${registry_host}:_authToken=${INPUT_NPM_TOKEN}" > .npmrc
echo "registry=${registry}" >> .npmrc
npm publish
rm -f .npmrc
# Use a temporary npm userconfig to avoid clobbering any existing .npmrc
local npm_userconfig
npm_userconfig="$(mktemp)"
cleanup_npm_userconfig() {
if [[ -n "${npm_userconfig:-}" && -f "$npm_userconfig" ]]; then
rm -f "$npm_userconfig"
fi
}
trap cleanup_npm_userconfig EXIT
{
echo "${registry_host}:_authToken=${INPUT_NPM_TOKEN}"
echo "registry=${registry}"
} > "$npm_userconfig"
NPM_CONFIG_USERCONFIG="$npm_userconfig" npm publish

Copilot uses AI. Check for mistakes.

local pkg_name
pkg_name=$(node -p "require('./package.json').name")
echo "Published ${pkg_name}@${version}"
echo "::endgroup::"
fi

echo "version=${version}" >> "$GITHUB_OUTPUT"
}

Expand Down
Loading