From 405a2b5913faeb8260c22ef7519f45aef82e2204 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 10:09:55 +0100 Subject: [PATCH 01/14] ci(publish)!: migrate NPM publication to OIDC trusted publishing (#104) NPM now requires trusted publishing (OIDC), which binds a package to one exact workflow filename. Alpha (ci_reusable.yaml) and latest (publish-latest.yaml) were published from two different workflows with a long-lived NPM_TOKEN, so both paths are now broken. Consolidate both publish paths into a single tokenless workflow: - Add .github/workflows/publish.yaml: push to main -> alpha (npm publish --tag alpha), workflow_dispatch -> latest via semantic-release (semantic_version 25, which bundles @semantic-release/npm >= 13.1 with OIDC support) plus a GitHub release. Uses permissions: id-token: write and no NPM_TOKEN. - Extract the shared setup-node + npm upgrade + ci + build + test steps into a local composite action (.github/actions/build-test) reused by both the publish workflow and PR CI, so build/test is defined once and runs once per runner. Bumps Node to 22 and npm to >= 11.5.1 as required by trusted publishing. - Delete publish-latest.yaml (superseded). - Reduce ci_reusable.yaml to build/test only (drops NPM_TOKEN, NODE_AUTH_TOKEN, PUBLISH_NPM and all publish steps) and ci.yaml to a PR-only trigger. Requires a one-time manual step: configure the GitHub Actions trusted publisher for @bosonprotocol/chat-sdk on npmjs.com pointing at publish.yaml. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/build-test/action.yml | 32 ++++++++++++++ .github/workflows/ci.yaml | 17 +------- .github/workflows/ci_reusable.yaml | 56 +++---------------------- .github/workflows/publish-latest.yaml | 59 -------------------------- .github/workflows/publish.yaml | 60 +++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 124 deletions(-) create mode 100644 .github/actions/build-test/action.yml delete mode 100644 .github/workflows/publish-latest.yaml create mode 100644 .github/workflows/publish.yaml diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml new file mode 100644 index 0000000..b255cde --- /dev/null +++ b/.github/actions/build-test/action.yml @@ -0,0 +1,32 @@ +name: "Build and Test" +description: "Install deps, build and test the Chat SDK (shared by CI and publish workflows)" +inputs: + node-version: + description: "Node.js version" + required: false + default: "22" # OIDC requires Node >= 22.14; PR CI uses the same version we publish from +runs: + using: "composite" + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + registry-url: "https://registry.npmjs.org" # writes .npmrc; harmless on PR, used by OIDC on publish + cache: "npm" + - name: Upgrade npm (trusted publishing needs >= 11.5.1) + shell: bash + run: npm install -g npm@latest + - uses: actions/cache@v4 + with: + path: ~/.npm + key: npm-${{ hashFiles('package-lock.json') }} + restore-keys: npm- + - name: Install dependencies + shell: bash + run: npm ci + - name: Build + shell: bash + run: npm run build + - name: Test + shell: bash + run: npm run test diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 09b36f5..0b7856f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,23 +1,10 @@ -name: Call reusable workflow - Chat SDK +name: CI - Chat SDK on: - push: - branches: - - main pull_request: branches: - main jobs: - call-reusable-workflow-PR: - uses: ./.github/workflows/ci_reusable.yaml - if: github.event_name == 'pull_request' - with: - PUBLISH_NPM: false - secrets: inherit - call-reusable-workflow-testing: + build-test: uses: ./.github/workflows/ci_reusable.yaml - if: github.event_name == 'push' - with: - PUBLISH_NPM: true - secrets: inherit diff --git a/.github/workflows/ci_reusable.yaml b/.github/workflows/ci_reusable.yaml index 6654dc4..31871e5 100644 --- a/.github/workflows/ci_reusable.yaml +++ b/.github/workflows/ci_reusable.yaml @@ -1,60 +1,16 @@ -name: Build, Test and Publish - Chat SDK +name: Build and Test - Chat SDK on: workflow_call: - inputs: - PUBLISH_NPM: - required: true - type: boolean - secrets: - NPM_TOKEN: - required: true jobs: - build-test-publish: - name: Build, Test and Publish + build-test: + name: Build and Test runs-on: ubuntu-latest - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - PUBLISH_NPM: ${{ inputs.PUBLISH_NPM }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/setup-node@v3 - with: - node-version: "20" - registry-url: "https://registry.npmjs.org" - cache: "npm" - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ~/.npm - key: npm-${{ hashFiles('package-lock.json') }} - restore-keys: npm- - - run: npm ci - - run: npm run prettier + - uses: ./.github/actions/build-test # same shared composite as publish.yaml + - run: npm run prettier # PR-only quality checks (mutate tree; not run on publish) - run: npm run lint:fix - - run: npm run build - - run: npm run test - - name: Set github bot - run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - - name: "Update to alpha version" - if: inputs.PUBLISH_NPM - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - git reset --hard - npm version prerelease --preid alpha - OLD_MSG=$(git log --format=%B -n1) - git commit --amend -m "$OLD_MSG" -m "[skip ci]" - git push - git push --tags - - name: "Publish to npm" - if: inputs.PUBLISH_NPM - run: | - npm publish --tag alpha - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-latest.yaml b/.github/workflows/publish-latest.yaml deleted file mode 100644 index ff92517..0000000 --- a/.github/workflows/publish-latest.yaml +++ /dev/null @@ -1,59 +0,0 @@ -name: Publish latest - -on: - workflow_dispatch: - -permissions: - contents: write # for GitHub releases - issues: write # for release comments - pull-requests: write # for PR comments - id-token: write # for npm provenance if needed - -jobs: - release: - name: Build and release latest - if: ${{ github.ref == 'refs/heads/main' }} - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - steps: - # 1. Checkout latest main - - uses: actions/checkout@v3 - with: - persist-credentials: false - fetch-depth: 0 - - - run: | - git fetch origin main - git checkout main - git reset --hard origin/main - - # 2. Setup Node and npm auth - - uses: actions/setup-node@v3 - with: - node-version: "20" - registry-url: "https://registry.npmjs.org" - cache: "npm" - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - # 3. Install dependencies and build - - run: npm ci - - run: npm run build - - - name: Semantic Release Publish - uses: cycjimmy/semantic-release-action@v4 - with: - semantic_version: 24 - extra_plugins: | - @semantic-release/changelog@6 - @semantic-release/git@10 - branches: | - [ - 'main' - ] - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..715b158 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,60 @@ +name: Publish - Chat SDK + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write # push alpha version commit/tag + create GitHub release + id-token: write # OIDC trusted publishing + provenance + issues: write # semantic-release release comments + pull-requests: write # semantic-release release comments + +jobs: + publish: + name: Build, Test and Publish + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # semantic-release needs full history + # persist-credentials defaults true -> alpha `git push` works + - uses: ./.github/actions/build-test # shared composite: setup-node + npm upgrade + ci + build + test + + # ---- alpha: every push to main ---- + - name: Configure git author + if: github.event_name == 'push' + run: | + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + - name: Publish alpha to npm + if: github.event_name == 'push' + run: | + git reset --hard + npm version prerelease --preid alpha + OLD_MSG=$(git log --format=%B -n1) + git commit --amend -m "$OLD_MSG" -m "[skip ci]" + git push + git push --tags + npm publish --tag alpha # tokenless OIDC, provenance automatic + + # ---- latest: on demand (workflow_dispatch) ---- + - name: Publish latest + GitHub release (semantic-release) + if: github.event_name == 'workflow_dispatch' + uses: cycjimmy/semantic-release-action@v4 + with: + semantic_version: 25 # bundles @semantic-release/npm >= 13.1 (OIDC) + extra_plugins: | + @semantic-release/changelog@6 + @semantic-release/git@10 + branches: | + [ + 'main' + ] + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # no NPM_TOKEN — OIDC handles npm auth From 7c0dc30acb8e02a22c18baa8b92e27ace5e42f44 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 10:41:17 +0100 Subject: [PATCH 02/14] fix(ci): make alpha publish check out main and stop amending the bump commit actions/checkout leaves a detached HEAD on push events, so the bare `git push` in the alpha step would fail ("not currently on a branch"). And `git commit --amend` rewrote the version-bump commit without moving the tag npm had just created, so the pushed tag pointed at the pre-amend (orphaned) commit. Check out `main` explicitly via the checkout `ref`, and bake `[skip ci]` into the `npm version` commit message (`-m "%s [skip ci]"`) so the commit and tag are created together in one step. Push both with `git push --follow-tags`. Addresses PR #105 review comment r3354854034. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 715b158..7de3c19 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -22,8 +22,9 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: main # check out the main branch (not a detached HEAD) so the alpha `git push` works fetch-depth: 0 # semantic-release needs full history - # persist-credentials defaults true -> alpha `git push` works + # persist-credentials defaults true -> alpha `git push` uses GITHUB_TOKEN - uses: ./.github/actions/build-test # shared composite: setup-node + npm upgrade + ci + build + test # ---- alpha: every push to main ---- @@ -36,11 +37,11 @@ jobs: if: github.event_name == 'push' run: | git reset --hard - npm version prerelease --preid alpha - OLD_MSG=$(git log --format=%B -n1) - git commit --amend -m "$OLD_MSG" -m "[skip ci]" - git push - git push --tags + # Bake [skip ci] into the commit message so the version bump commit+tag are created in + # one step (no --amend, which would leave the tag pointing at the pre-amend commit) and + # the pushed commit to main does not re-trigger this workflow. + npm version prerelease --preid alpha -m "%s [skip ci]" + git push --follow-tags npm publish --tag alpha # tokenless OIDC, provenance automatic # ---- latest: on demand (workflow_dispatch) ---- From b91d2d0f3b543082bf8019432334d29589ebc361 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 10:42:14 +0100 Subject: [PATCH 03/14] fix(ci): pin npm to 11.5.1 instead of @latest Installing npm@latest makes CI and publishing non-deterministic: a new npm release could break builds or publishing with no change in the repo. Pin to a known-good version that satisfies the trusted-publishing requirement (>= 11.5.1) and bump it intentionally when needed. Addresses PR #105 review comment r3354854090. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/build-test/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml index b255cde..e0ad3c0 100644 --- a/.github/actions/build-test/action.yml +++ b/.github/actions/build-test/action.yml @@ -15,7 +15,8 @@ runs: cache: "npm" - name: Upgrade npm (trusted publishing needs >= 11.5.1) shell: bash - run: npm install -g npm@latest + # Pinned for deterministic builds; bump intentionally (must stay >= 11.5.1 for OIDC). + run: npm install -g npm@11.5.1 - uses: actions/cache@v4 with: path: ~/.npm From 056a303a6e78efe0fa3590251c7278a2bea3462c Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 10:43:22 +0100 Subject: [PATCH 04/14] fix(ci): drop redundant actions/cache step actions/setup-node already caches the npm download cache when cache: "npm" is set, so the separate actions/cache step for ~/.npm duplicated that work and only added restore/save overhead and confusion. Rely on setup-node's built-in caching. Addresses PR #105 review comment r3354854115. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/build-test/action.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml index e0ad3c0..a56c358 100644 --- a/.github/actions/build-test/action.yml +++ b/.github/actions/build-test/action.yml @@ -17,11 +17,6 @@ runs: shell: bash # Pinned for deterministic builds; bump intentionally (must stay >= 11.5.1 for OIDC). run: npm install -g npm@11.5.1 - - uses: actions/cache@v4 - with: - path: ~/.npm - key: npm-${{ hashFiles('package-lock.json') }} - restore-keys: npm- - name: Install dependencies shell: bash run: npm ci From add68affe28a4cbcfab56981373cd6de635c8114 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 12:19:02 +0100 Subject: [PATCH 05/14] fix(ci): serialize publish runs with a concurrency group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The publish job mutates and pushes main (version bump + tags) and publishes to npm, so overlapping runs — two pushes to main close together, or a manual workflow_dispatch while a push publish is still running — can race and cause non-fast-forward push failures or inconsistent prerelease sequencing. Add a workflow-level concurrency group keyed on the ref with cancel-in-progress: false so publish runs are serialized and a run is never cancelled mid-publish. Addresses PR #105 review comment r3355066601. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 7de3c19..48deb84 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -6,6 +6,13 @@ on: - main workflow_dispatch: +# Serialize publish runs: this job bumps + pushes main and publishes to npm, so overlapping runs +# (back-to-back pushes, or a manual dispatch during a push publish) would race on the main push and +# the prerelease sequence. Never cancel a run mid-publish. +concurrency: + group: publish-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + permissions: contents: write # push alpha version commit/tag + create GitHub release id-token: write # OIDC trusted publishing + provenance From 31440db6453120ad4d8f677d41fcf9f8d722425b Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 12:32:26 +0100 Subject: [PATCH 06/14] fix(ci): tie publish run to the triggering commit instead of latest main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checking out `ref: main` meant a run triggered by a specific push could build, test, bump and publish a *newer* main if other pushes landed before the job started — making publishes non-reproducible and inviting push conflicts. Check out `${{ github.sha }}` and recreate a local `main` branch at that commit (`git checkout -B main`), which keeps the run pinned to the triggering commit while still avoiding the detached HEAD that would break `git push`. Push explicitly to `origin main` since the recreated branch has no upstream. Addresses PR #105 review comment r3355066656. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 48deb84..dcd8ed7 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -29,9 +29,11 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: main # check out the main branch (not a detached HEAD) so the alpha `git push` works + ref: ${{ github.sha }} # tie the run to the triggering commit, not the latest main fetch-depth: 0 # semantic-release needs full history # persist-credentials defaults true -> alpha `git push` uses GITHUB_TOKEN + - name: Create local main branch at the triggering commit + run: git checkout -B main # non-detached HEAD (needed for the alpha git push) tied to github.sha - uses: ./.github/actions/build-test # shared composite: setup-node + npm upgrade + ci + build + test # ---- alpha: every push to main ---- @@ -48,7 +50,7 @@ jobs: # one step (no --amend, which would leave the tag pointing at the pre-amend commit) and # the pushed commit to main does not re-trigger this workflow. npm version prerelease --preid alpha -m "%s [skip ci]" - git push --follow-tags + git push --follow-tags origin main # explicit target: local main has no upstream after checkout -B npm publish --tag alpha # tokenless OIDC, provenance automatic # ---- latest: on demand (workflow_dispatch) ---- From 59e77777993f7905494e9aa1f97299355f789f87 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 12:33:30 +0100 Subject: [PATCH 07/14] fix(ci): pin default node-version to 22.14.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The major-only selector "22" does not guarantee the >= 22.14 patch level that OIDC trusted publishing requires — on a fresh runner or a stale toolcache it can resolve to an earlier 22.x, which would make publishing fail unexpectedly. Pin the default to 22.14.0 (still overridable via the node-version input). Addresses PR #105 review comment r3355066675. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/build-test/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml index a56c358..128acb2 100644 --- a/.github/actions/build-test/action.yml +++ b/.github/actions/build-test/action.yml @@ -4,7 +4,7 @@ inputs: node-version: description: "Node.js version" required: false - default: "22" # OIDC requires Node >= 22.14; PR CI uses the same version we publish from + default: "22.14.0" # pinned >= the OIDC minimum (Node 22.14); major-only "22" can resolve below it runs: using: "composite" steps: From ecd69016193129679d99b238cfe206ee2e7157d3 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 13:36:15 +0100 Subject: [PATCH 08/14] fix(ci): set upstream on the recreated main branch `git checkout -B main` creates the branch without an upstream. The alpha path compensates with an explicit `origin main` push, but semantic-release performs its own `git push` on the workflow_dispatch (latest) path and can fail with "no upstream branch". Configure branch.main.remote/merge so the branch tracks origin/main, making both publish paths robust. Addresses PR #105 review comment r3355705468. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index dcd8ed7..d500f35 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -33,9 +33,13 @@ jobs: fetch-depth: 0 # semantic-release needs full history # persist-credentials defaults true -> alpha `git push` uses GITHUB_TOKEN - name: Create local main branch at the triggering commit - run: git checkout -B main # non-detached HEAD (needed for the alpha git push) tied to github.sha + run: | + git checkout -B main # non-detached HEAD (needed for the alpha git push) tied to github.sha + # -B leaves the branch untracked; set its upstream to origin/main so semantic-release's + # own `git push` (workflow_dispatch / latest path) and any bare `git push` resolve a target. + git config branch.main.remote origin + git config branch.main.merge refs/heads/main - uses: ./.github/actions/build-test # shared composite: setup-node + npm upgrade + ci + build + test - # ---- alpha: every push to main ---- - name: Configure git author if: github.event_name == 'push' From c25b6495bb5361d2ffa77f8943d36da4732bc2e7 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 13:37:13 +0100 Subject: [PATCH 09/14] fix(ci): configure git author for both publish paths The git author identity was only set on push events, but the workflow_dispatch (latest) path runs semantic-release with @semantic-release/git, which creates changelog/version commits and fails without a configured author. Move the author config out of the alpha-only section so it runs unconditionally. Addresses PR #105 review comment r3355705521. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index d500f35..e2a6fc1 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -40,12 +40,13 @@ jobs: git config branch.main.remote origin git config branch.main.merge refs/heads/main - uses: ./.github/actions/build-test # shared composite: setup-node + npm upgrade + ci + build + test - # ---- alpha: every push to main ---- + # Needed by both paths: the alpha bump commit and semantic-release's @semantic-release/git commits. - name: Configure git author - if: github.event_name == 'push' run: | git config user.name 'github-actions[bot]' git config user.email 'github-actions[bot]@users.noreply.github.com' + + # ---- alpha: every push to main ---- - name: Publish alpha to npm if: github.event_name == 'push' run: | From 873cb2e1e728ae368b7ebd347e14ad497db4280f Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 13:41:39 +0100 Subject: [PATCH 10/14] fix(ci): lint as a non-mutating gate before build/test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously PR CI ran `prettier --write` and `eslint --fix` *after* the composite's build+test, so build/test exercised the pre-fix code and the auto-fixers (which always exit 0) gated nothing — reducing confidence in CI. Add a `run-checks` input to the build-test composite that runs `npm run lint` (non-mutating, no --fix) after `npm ci` but before build+test, and enable it from ci_reusable.yaml (PR CI). Lint is now a real gate and build/test run on the actual committed code. Publish runs leave run-checks at its default (off). The mutating `prettier --write` step is dropped from CI: it never gated anything and whitespace formatting does not affect build/test. A real `prettier --check` gate is deferred — it currently fails on pre-existing unrelated files (incl. the semantic-release-managed CHANGELOG.md) and is a separate cleanup. Addresses PR #105 review comment r3355705568. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/build-test/action.yml | 10 ++++++++++ .github/workflows/ci_reusable.yaml | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml index 128acb2..403a208 100644 --- a/.github/actions/build-test/action.yml +++ b/.github/actions/build-test/action.yml @@ -5,6 +5,10 @@ inputs: description: "Node.js version" required: false default: "22.14.0" # pinned >= the OIDC minimum (Node 22.14); major-only "22" can resolve below it + run-checks: + description: "Run lint before build+test (enable for PR CI)" + required: false + default: "false" runs: using: "composite" steps: @@ -20,6 +24,12 @@ runs: - name: Install dependencies shell: bash run: npm ci + # Run lint as a non-mutating gate BEFORE build/test, so build/test exercise the actual committed + # code rather than code an `eslint --fix` may have silently rewritten afterwards. + - name: Lint + if: ${{ inputs.run-checks == 'true' }} + shell: bash + run: npm run lint - name: Build shell: bash run: npm run build diff --git a/.github/workflows/ci_reusable.yaml b/.github/workflows/ci_reusable.yaml index 31871e5..9b85264 100644 --- a/.github/workflows/ci_reusable.yaml +++ b/.github/workflows/ci_reusable.yaml @@ -12,5 +12,5 @@ jobs: with: ref: ${{ github.event.pull_request.head.ref }} - uses: ./.github/actions/build-test # same shared composite as publish.yaml - - run: npm run prettier # PR-only quality checks (mutate tree; not run on publish) - - run: npm run lint:fix + with: + run-checks: "true" # lint runs (non-mutating) before build+test on PRs; skipped on publish From 7c5c3bcfd08a4829e262ba180adc682f67448227 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 13:45:27 +0100 Subject: [PATCH 11/14] fix(ci): add scoped prettier --check gate before build/test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to r3355705568: add a real (non-mutating) formatting gate alongside lint. New `prettier:check` script runs `prettier --check` scoped to first-party `src/` and `tests/` (both currently clean), wired into the build-test composite so it runs before build/test when run-checks is enabled (PR CI). Kept scoped rather than repo-wide because `prettier --check .` fails on pre-existing unrelated files (CHANGELOG.md — managed by semantic-release — plus docs/ and scripts/); formatting those is a separate cleanup. Addresses PR #105 review comment r3355705568. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/build-test/action.yml | 14 +++++++++----- package.json | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml index 403a208..9b9bf0b 100644 --- a/.github/actions/build-test/action.yml +++ b/.github/actions/build-test/action.yml @@ -6,7 +6,7 @@ inputs: required: false default: "22.14.0" # pinned >= the OIDC minimum (Node 22.14); major-only "22" can resolve below it run-checks: - description: "Run lint before build+test (enable for PR CI)" + description: "Run lint + format checks before build+test (enable for PR CI)" required: false default: "false" runs: @@ -24,12 +24,16 @@ runs: - name: Install dependencies shell: bash run: npm ci - # Run lint as a non-mutating gate BEFORE build/test, so build/test exercise the actual committed - # code rather than code an `eslint --fix` may have silently rewritten afterwards. - - name: Lint + # Run lint + format as non-mutating gates BEFORE build/test, so build/test exercise the actual + # committed code rather than code an `eslint --fix` / `prettier --write` may have rewritten + # afterwards. prettier:check is scoped to first-party src/tests (a repo-wide check fails on + # pre-existing unrelated files such as the semantic-release-managed CHANGELOG.md). + - name: Lint and check formatting if: ${{ inputs.run-checks == 'true' }} shell: bash - run: npm run lint + run: | + npm run lint + npm run prettier:check - name: Build shell: bash run: npm run build diff --git a/package.json b/package.json index 9d7d690..16fa0f8 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "lint": "eslint --ignore-path .gitignore --ext .js,.ts . --config .eslintrc.tests.cjs", "lint:fix": "npm run lint -- --fix", "prettier": "prettier --write .", + "prettier:check": "prettier --check \"src/**/*\" \"tests/**/*\"", "test": "run-p test:browser test:node", "test:browser": "vitest --config vite.config.mts", "test:node": "vitest --config vite.config.node.mts", From c66c166dff7942d16794d2fda789f87f1b30fccb Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 14:14:50 +0100 Subject: [PATCH 12/14] fix(ci): check out github.sha in the reusable workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ci_reusable.yaml runs via workflow_call, and `ref: github.event.pull_request. head.ref` is the wrong revision to check out: it targets the PR head branch tip rather than the merge commit, and for fork PRs the head ref doesn't exist in this repo so checkout fails. Drop the explicit ref so actions/checkout uses its default (github.sha) — the PR merge commit in the caller context, which also works for fork PRs. Addresses PR #105 review comment r3356088977. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci_reusable.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci_reusable.yaml b/.github/workflows/ci_reusable.yaml index 9b85264..522d221 100644 --- a/.github/workflows/ci_reusable.yaml +++ b/.github/workflows/ci_reusable.yaml @@ -8,9 +8,7 @@ jobs: name: Build and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.ref }} + - uses: actions/checkout@v4 # defaults to github.sha (the PR merge commit); works in workflow_call and for fork PRs - uses: ./.github/actions/build-test # same shared composite as publish.yaml with: run-checks: "true" # lint runs (non-mutating) before build+test on PRs; skipped on publish From b2ab539e666e982742bc9edd0b5a5f68266858d8 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 14:15:40 +0100 Subject: [PATCH 13/14] docs(ci): correct stale rationale on the alpha push comment The inline comment claimed the explicit `origin main` target was needed because the branch had no upstream after `checkout -B`, but the upstream is now set (branch.main.remote/merge) a few steps earlier. Update the comment to reflect that the explicit target is just being explicit; behavior is unchanged. Addresses PR #105 review comment r3356089027. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index e2a6fc1..122f9a9 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -55,7 +55,7 @@ jobs: # one step (no --amend, which would leave the tag pointing at the pre-amend commit) and # the pushed commit to main does not re-trigger this workflow. npm version prerelease --preid alpha -m "%s [skip ci]" - git push --follow-tags origin main # explicit target: local main has no upstream after checkout -B + git push --follow-tags origin main # push the bump commit + its tag explicitly (upstream is set above) npm publish --tag alpha # tokenless OIDC, provenance automatic # ---- latest: on demand (workflow_dispatch) ---- From 36e18b9898a1ab8cfe2df80206e06fb44b95e159 Mon Sep 17 00:00:00 2001 From: Ludovic Levalleux Date: Thu, 4 Jun 2026 14:27:39 +0100 Subject: [PATCH 14/14] fix(toolchain): align Volta pins with CI/publish toolchain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build-test composite runs Node 22.14.0 / npm 11.5.1 (the latter required by OIDC trusted publishing), but the Volta pins still selected Node 20.19.0 / npm 8.11.0, so contributors using Volta ran a different runtime than CI and publishing — a "works locally, fails in CI" gap. Bump the Volta pins to match, so local dev, PR CI and publish all use the same toolchain. Addresses PR #105 review comment r3356297523. Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 16fa0f8..d9360ed 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "vitest": "^3.1.1" }, "volta": { - "node": "20.19.0", - "npm": "8.11.0" + "node": "22.14.0", + "npm": "11.5.1" } }