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
131 changes: 131 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Release

# Publishes the package to npm when a version tag such as 2.5.2 is pushed.
# npm trusts this workflow (trusted publishing with OIDC), so there is no npm token and no 2FA prompt.
# Run the workflow by hand on the default branch to check the trusted publisher setup.
# That run publishes nothing.
#
# Only the publish and check jobs may request an OIDC token. The verify job runs the
# repository scripts (install, lint, tests) without that permission.

on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:

permissions: {}

jobs:
verify:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Check the tag
if: github.event_name == 'push'
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
version=$(jq -r '.version' package.json)
if [ "$version" != "$GITHUB_REF_NAME" ]; then
echo "::error::The tag $GITHUB_REF_NAME does not match the version $version in package.json."
exit 1
fi
if ! git merge-base --is-ancestor "$GITHUB_SHA" "origin/$DEFAULT_BRANCH"; then
echo "::error::The tag $GITHUB_REF_NAME is not on $DEFAULT_BRANCH."
exit 1
fi

- uses: actions/setup-node@v7
with:
node-version: 24
package-manager-cache: false

- run: npm ci
- run: npm run lint
- run: npm test
- run: npm pack --dry-run

publish:
needs: verify
if: github.event_name == 'push'
runs-on: ubuntu-latest
environment: npm-publish
permissions:
contents: read
id-token: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

# Again here, before any repository code runs with the OIDC permission
- name: Check the tag
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
version=$(jq -r '.version' package.json)
if [ "$version" != "$GITHUB_REF_NAME" ]; then
echo "::error::The tag $GITHUB_REF_NAME does not match the version $version in package.json."
exit 1
fi
if ! git merge-base --is-ancestor "$GITHUB_SHA" "origin/$DEFAULT_BRANCH"; then
echo "::error::The tag $GITHUB_REF_NAME is not on $DEFAULT_BRANCH."
exit 1
fi

- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Check the npm version
# Trusted publishing needs npm 11.5.1 or newer
run: |
npm --version
node -e "const [a, b, c] = process.argv[1].split('.').map(Number); process.exit(a > 11 || (a === 11 && (b > 5 || (b === 5 && c >= 1))) ? 0 : 1)" "$(npm --version)"

# No install scripts of dependencies while this job may request an OIDC token
- run: npm ci --ignore-scripts

- name: Publish
run: npm publish

check-trusted-publisher:
if: github.event_name == 'workflow_dispatch' && github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest
environment: npm-publish
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7

# Asks npm for a publish token the same way npm publish does, and only reports the result.
# A success shows that owner, repository, workflow and environment match. It does not show
# that direct publishing with npm publish is allowed. That is a separate setting on npmjs.com.
- name: Check the trusted publisher without publishing
run: |
name=$(jq -r '.name | sub("/"; "%2f")' package.json)
id_token=$(curl -sSf -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=npm:registry.npmjs.org" | jq -r '.value')
code=$(curl -sS -o "$RUNNER_TEMP/exchange.json" -w '%{http_code}' -X POST \
-H "Authorization: Bearer $id_token" \
"https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/$name")
if [ "$code" -ge 200 ] && [ "$code" -lt 300 ] && jq -e '.token' "$RUNNER_TEMP/exchange.json" > /dev/null; then
echo "Trusted publisher works: npm accepted this workflow for $name."
else
echo "::error::npm refused the token exchange (HTTP $code): $(jq -r '.message // .error // "no message"' "$RUNNER_TEMP/exchange.json")"
rm -f "$RUNNER_TEMP/exchange.json"
exit 1
fi
rm -f "$RUNNER_TEMP/exchange.json"
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ npm run build # builds dist/ and the demo

`npm pack` and `npm publish` build `dist/` first.

### Releases

1. Set the new version in `package.json` and add it to `CHANGELOG.md`.
2. Merge the change into `master`.
3. Push a tag with the version number, for example `git tag 2.5.2 && git push origin 2.5.2`.

The `Release` workflow then runs the lint and the tests, and publishes the package to npm. It uses npm trusted publishing, so it needs no npm token and no 2FA prompt. The tag must match the version in `package.json` and must be on `master`. Run the workflow by hand to check the setup. That run publishes nothing.

On npmjs.com, the trusted publisher of the package points to this repository, the workflow `release.yml` and the environment `npm-publish`. Under "Allowed actions", it must allow `npm publish`. A new trusted publisher expires if it does not publish within 2 days, so create it right before a release.

## Security

If you discover any security problems, please, don't email me. (I'm a bit scared 😱) avidofood@protonmail.com
Expand Down
Loading