From 24d6664fb38d726f7d1a5e2d6854038437e28252 Mon Sep 17 00:00:00 2001 From: DJJ Date: Mon, 24 Aug 2026 08:14:20 -0700 Subject: [PATCH 1/2] ci: add PR test workflow; harden npm publish (idempotent, provenance, auth) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo had no CI on pull requests at all — only the convert workflow (instruction-file paths) and the tag publish. Adds a Node 20/22/24 matrix running build + tests + pack dry-run on every PR and main push. Publish hardening: skip when the version already exists on the registry (safe re-runs), enable --provenance (id-token permission was already declared but unused), wire NODE_AUTH_TOKEN explicitly, add npm cache, concurrency group, and a timeout. --- .github/workflows/ci.yml | 36 +++++++++++++++++++++++++++++++ .github/workflows/publish-npm.yml | 22 ++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d96fad0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Node ${{ matrix.node }} + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + node: [20, 22, 24] + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node }} + cache: npm + + - run: npm ci + - run: npm run build + - run: npm test + - run: npm pack --dry-run diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index bdb15ed..9772eb6 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -9,9 +9,14 @@ permissions: contents: read id-token: write +concurrency: + group: publish-${{ github.ref }} + cancel-in-progress: false + jobs: publish: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v6 @@ -19,6 +24,7 @@ jobs: with: node-version: "24" registry-url: "https://registry.npmjs.org" + cache: npm - name: Verify tag matches package version run: | @@ -32,4 +38,18 @@ jobs: - run: npm run build - run: npm test - run: npm pack --dry-run - - run: npm publish + + - name: Publish (idempotent, with provenance) + run: | + set -euo pipefail + name=$(node -p "require('./package.json').name") + version=$(node -p "require('./package.json').version") + # Re-running the workflow for an already-published tag should be a + # no-op, not a 409 failure. + if npm view "${name}@${version}" version >/dev/null 2>&1; then + echo "${name}@${version} already published — skipping" + else + npm publish --provenance + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 88bdba1c5019efa4b0e284c5195b2202543fee10 Mon Sep 17 00:00:00 2001 From: DJJ Date: Mon, 24 Aug 2026 08:20:02 -0700 Subject: [PATCH 2/2] ci: switch npm publish to trusted publishing (OIDC), drop NPM_TOKEN Configure publish-npm.yml as the package's trusted publisher on npmjs.com; the job's OIDC identity (id-token: write) replaces any token secret, and provenance is attached automatically. Node 24 already bundles npm 11 which supports this. --- .github/workflows/publish-npm.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 9772eb6..6222898 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -39,7 +39,11 @@ jobs: - run: npm test - run: npm pack --dry-run - - name: Publish (idempotent, with provenance) + - name: Publish (trusted publishing, idempotent) + # Tokenless: configure this workflow as the package's trusted publisher + # on npmjs.com; npm exchanges the job's OIDC identity (id-token: write) + # for short-lived credentials. Provenance is attached automatically. + # Requires npm >= 11.5.1 (Node 24 bundles npm 11). run: | set -euo pipefail name=$(node -p "require('./package.json').name") @@ -49,7 +53,5 @@ jobs: if npm view "${name}@${version}" version >/dev/null 2>&1; then echo "${name}@${version} already published — skipping" else - npm publish --provenance + npm publish fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}