Skip to content

Fix trusted-publishing release job: npm@latest dropped node 20 support #6

Fix trusted-publishing release job: npm@latest dropped node 20 support

Fix trusted-publishing release job: npm@latest dropped node 20 support #6

Workflow file for this run

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 }}