Add release-gated npm publish job for the single v2 package - #1647
Conversation
…age (#1640) Wire the v2 publish pipeline into main.yml. On a published GitHub release, a new `publish` job runs `npm run pack:verify` (the pre-publish gate — builds, packs the real tarball, installs it into a clean consumer, and drives the bin) then `npm publish --access public --provenance`. This is a single `npm publish` (v2 is not an npm workspace, so no v1-style publish-all/--workspaces), with a signed provenance attestation via GitHub OIDC (id-token: write, environment: release, NPM_TOKEN). Add the `release: types: [published]` trigger; the existing `build` job is unchanged and the publish job is gated on the release event. Document the single-version release/tag flow in the root README (one `npm version` at the root; no per-client sync / check-version since only the root package.json carries a version). Docker/GHCR is deferred: v2 has no Dockerfile (the v1 client/server/cli layout is gone), and a correct image needs a net-new, docker-build-tested Dockerfile. Split into #1646. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
The clients/web README was the default "React + TypeScript + Vite" template (plus a proxy note). Replace it with a web-client-specific guide: the src/ (browser) vs server/ (Node) split and what each server/ file does, the build pipeline (tsc -b → vite build → build:runner, producing dist/ + build/), the component layers (elements/groups/screens/views) and theme system, the three Vitest projects (unit happy-dom / integration node / storybook Chromium) and the ≥90% coverage gate, Storybook (dev + play-function browser tests), and the auth-token flow. Preserve the existing HTTP-proxy section. Points at the root README/AGENTS.md for the repo-wide picture rather than duplicating it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Also bundled into this PR (per request): replaced |
|
@claude review |
|
Claude finished @cliffhall's task in 1m 44s —— View job PR Review — Release-gated npm publish job
Overall this is a clean, well-designed publish job. Release-gated, 👍 What's right
Suggestions (non-blocking)1. (medium) No guard that the release tag matches the published version. - name: Assert release tag matches package version
run: |
PKG=$(node -p "require('./package.json').version")
TAG="${{ github.event.release.tag_name }}"
# strip a leading v (npm version tags as vX.Y.Z)
[ "${TAG#v}" = "$PKG" ] || {
echo "Release tag '$TAG' != package.json version '$PKG'"; exit 1; }This is exactly the kind of check the removed v1 2. (low) The build runs three times on the release path. 3. (low) No 4. (nit)
|
Address PR #1647 review: - Assert the release tag matches the root package.json version before publishing (fail fast, before install/pack:verify) — publishing is irreversible, so a release drafted without `npm version` or cut from the wrong commit shouldn't silently ship a mismatched version. Tolerates a leading `v`. - Add a `concurrency: { group: publish-npm, cancel-in-progress: false }` guard so two releases in quick succession can't run overlapping publishes. - Note in-comment that the triple build (build job → pack:verify → prepack) is intentional, so a future reader doesn't "optimize" prepack away and break the tarball. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Thanks for the review! Applied all three non-blocking suggestions: 1. Tag ↔ version guard — added an 2. Intentional triple build — added an in-comment note on the publish step that the build-job → pack:verify → 3. Concurrency guard — added 4. (No worries on the workflow-file edit limitation — applied on my end.) YAML re-validated. Re-requesting review. @claude review |
|
Claude finished @cliffhall's task in 1m 8s —— View job Re-review — hardening appliedWent through the updated
✅ Verified
Also re-checked the provenance prerequisites still hold: One new nit (low, optional)The guard interpolates the release tag straight into the shell script body: run: |
PKG="$(node -p "require('./package.json').version")"
TAG="${{ github.event.release.tag_name }}"Templating - name: Assert release tag matches package version
env:
TAG: ${{ github.event.release.tag_name }}
run: |
PKG="$(node -p "require('./package.json').version")"
if [ "${TAG#v}" != "$PKG" ]; then
echo "Release tag '$TAG' does not match package.json version '$PKG'"
exit 1
fiPurely defensive and doesn't change behavior — take it or leave it. Apply →
Verdict: LGTM. The three fixes are correct and the job is in good shape to merge; the injection nit is optional hardening, not a blocker. |
Re-review nit: interpolating `${{ github.event.release.tag_name }}` directly
into the `run:` block is a script-injection surface (a crafted tag with shell
metacharacters would splice into the script). Pass it through `env: TAG` so it
arrives as a plain shell variable instead. Purely defensive; no behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Applied the injection-hardening nit — the release tag now arrives via @claude review |
|
Claude finished @cliffhall's task in 26s —— View job Re-review — injection hardening applied
✅ VerifiedInjection hardening (the requested change) is correct. The Everything from the prior rounds still holds:
Verdict: LGTM ✅The injection nit is resolved, no behavior change, and no new issues introduced. The publish job is in good shape to merge. Reminder that per AGENTS.md, since this targets |
Closes #1640
Wires the v2 single-package publish/release job into
.github/workflows/main.yml(parent tracking: #1636).What's added
release: types: [published]trigger + a release-gatedpublishjob:npm run pack:verify— the pre-publish gate (builds,npm packs the real tarball, installs it into a clean throwaway consumer, drives the installedmcp-inspectorbin end to end).npm publish --access public --provenance— a singlenpm publish(v2 isn't an npm workspace, so no v1-stylepublish-all/--workspaces), with a signed provenance attestation via GitHub OIDC (id-token: write,environment: release,NPM_TOKEN). The v1--provenanceTODO is resolved now the repo is public.buildjob is unchanged;publishis gated ongithub.event_name == 'release'andneeds: build, so normal push/PR CI is unaffected.npm versionat the root → tag → GitHub Release triggerspublish. No per-client version sync /check-version(only the rootpackage.jsoncarries a version).Deferred (split out)
client/server/clilayout is gone). A correct v2 image needs a net-new,docker build-tested Dockerfile — cleanly separable from the npm publish (independent job). README notes this.Notes
buildjob runs as before. The publish path itself can't be exercised without a real release (publishing is irreversible);pack:verifyin the job is the automated pre-publish safety net.v2/main(non-default branch), soCloses #1640is a cross-reference — close Wire the v2 single-package publish/release job into main.yml (at the v2→main cutover) #1640 + move its board card to Done manually on merge, per AGENTS.md.🤖 Generated with Claude Code