Fix trusted-publishing release job: npm@latest dropped node 20 support #6
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| # Publishing is gated on a version tag so a release is always a deliberate act | |
| # with a commit to point at. workflow_dispatch exists to rehearse the whole | |
| # pipeline (dry_run defaults to true) without touching the registry. | |
| on: | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Run every check and pack the tarball, but do not publish" | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: write # create the GitHub Release and upload the tarball | |
| id-token: write # OIDC token for npm trusted publishing + provenance | |
| # Two releases racing each other would fight over the same version number. | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| name: publish etherfind to npm | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| # node 20's bundled npm predates trusted publishing (needs npm | |
| # >= 11.5.1). node 22 is enough to pull a compatible npm; CI's | |
| # matrix already proves the package builds fine under it. | |
| node-version: 22 | |
| cache: npm | |
| # Writes an .npmrc pointing at the public registry. Needed so `npm | |
| # publish` knows where to send the OIDC token exchange even though | |
| # no long-lived auth token is configured below. | |
| registry-url: https://registry.npmjs.org | |
| # Pinned to the 11.x line rather than "latest": npm 12 dropped node 20 | |
| # support entirely and broke this step outright (EBADENGINE) the first | |
| # time this ran. 11.x is the line that introduced trusted publishing. | |
| - run: npm install -g npm@11 | |
| - run: npm --version | |
| - run: npm ci | |
| # Re-run the full CI gate here rather than trusting an earlier green run: | |
| # a tag can point at any commit, including one CI never saw. | |
| - run: npm run lint | |
| - run: npm run build | |
| - run: npm test | |
| - name: Check the tag matches the package version | |
| if: startsWith(github.ref, 'refs/tags/') | |
| run: | | |
| tag="${GITHUB_REF_NAME#v}" | |
| pkg="$(node -p "require('./packages/cli/package.json').version")" | |
| if [ "$tag" != "$pkg" ]; then | |
| echo "::error::tag $GITHUB_REF_NAME implies version $tag, but packages/cli/package.json says $pkg" | |
| exit 1 | |
| fi | |
| echo "releasing etherfind@$pkg" | |
| # npm rejects a duplicate version anyway, but it does so *after* the | |
| # publish step has already been entered. Failing here keeps the reason | |
| # obvious: bump the version, don't retry the same tag. | |
| - name: Refuse to republish an existing version | |
| run: | | |
| pkg="$(node -p "require('./packages/cli/package.json').version")" | |
| if npm view "etherfind@$pkg" version >/dev/null 2>&1; then | |
| echo "::error::etherfind@$pkg is already on the registry; bump the version first" | |
| exit 1 | |
| fi | |
| # Same smoke test as the CI packaging job: a missing "files" entry or a | |
| # leaked runtime dependency must fail before the publish, not after it, | |
| # because a published version can never be replaced. | |
| - name: Pack and verify the tarball | |
| run: | | |
| mkdir -p /tmp/tarballs | |
| npm pack --workspace etherfind --pack-destination /tmp/tarballs | |
| mkdir -p /tmp/smoke && cd /tmp/smoke | |
| npm init -y > /dev/null | |
| npm install /tmp/tarballs/etherfind-*.tgz --no-audit --no-fund | |
| ./node_modules/.bin/etherfind --version | |
| ./node_modules/.bin/etherfind --help | |
| node -e "const p=require('/tmp/smoke/node_modules/etherfind/package.json'); if (Object.keys(p.dependencies||{}).length) { console.error('published package must have no runtime dependencies'); process.exit(1); }" | |
| ./node_modules/.bin/etherfind --simulate --json | tee run.ndjson | |
| grep -q '"event":"final"' run.ndjson | |
| grep -q '"ok":true' run.ndjson | |
| # --version, --help and --simulate --json above never load ink, so they | |
| # missed a real bug: the published 0.1.0 crashed with "Dynamic require | |
| # of assert is not supported" the moment a real terminal reached | |
| # `await import("ink")`. This forces that import path against the | |
| # tarball's own dist/cli.js. | |
| - name: Check the interactive TUI import path | |
| run: node packages/cli/scripts/verify-tui-import.mjs /tmp/smoke/node_modules/etherfind/dist/cli.js | |
| # On a tag push the `inputs` context is empty, so `!inputs.dry_run` is | |
| # true and the publish runs. On workflow_dispatch it honours the checkbox. | |
| # | |
| # Trusted publishing: no NPM_TOKEN. npm exchanges this job's OIDC token | |
| # (id-token: write, above) for a short-lived publish token, verified | |
| # against the trusted publisher linked to this repo + workflow file on | |
| # npmjs.com. --provenance attaches a signed build attestation; both | |
| # require a public repository, which this one now is. | |
| - name: Publish to npm | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| pkg="$(node -p "require('./packages/cli/package.json').version")" | |
| # A prerelease version must not take the "latest" dist-tag, or every | |
| # plain `npx etherfind` would immediately start serving the beta. | |
| case "$pkg" in | |
| *-*) tag=next ;; | |
| *) tag=latest ;; | |
| esac | |
| echo "publishing etherfind@$pkg under dist-tag $tag" | |
| npm publish --workspace etherfind --access public --provenance --tag "$tag" | |
| - name: Create the GitHub release | |
| if: ${{ startsWith(github.ref, 'refs/tags/') && !inputs.dry_run }} | |
| run: | | |
| pkg="$(node -p "require('./packages/cli/package.json').version")" | |
| case "$pkg" in | |
| *-*) prerelease=--prerelease ;; | |
| *) prerelease= ;; | |
| esac | |
| gh release create "$GITHUB_REF_NAME" --generate-notes --verify-tag $prerelease /tmp/tarballs/etherfind-*.tgz | |
| env: | |
| GH_TOKEN: ${{ github.token }} |