inspector/.github/workflows/main.yml
cliffhall ccbcd3d719 ci(publish): harden the release job per review
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
2026-07-11 00:13:51 -04:00

145 lines
6.5 KiB
YAML

name: Run install, format, lint, build, and test on every push
on:
push:
# A published GitHub release triggers the `publish` job below. The release's
# target commit determines which workflow runs — so this only publishes when a
# release is cut from a commit that carries this (v2) workflow.
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22.x'
cache: 'npm'
- name: Install dependencies (root + all clients)
# The root postinstall (scripts/install-clients.mjs) cascades
# `npm install` into clients/web, clients/cli, clients/tui, and
# clients/launcher, so this single step sets up every client.
run: npm install
- name: Validate (format, lint, build, fast tests)
# Each client self-validates: format:check + lint + build + test (no
# coverage instrumentation — fast). This also builds every client bundle
# (web dist, cli/tui/launcher) that the smokes below need. The heavier
# per-file coverage gate runs in its own step below. Unit tests run in
# both steps (fast here, instrumented under coverage); the double unit
# run is an accepted trade-off for keeping this fast build step intact.
# A future optimization could split coverage into per-client parallel
# jobs, but that's a larger restructure and deliberately out of scope.
run: npm run validate
- name: Enforce per-file coverage gate (≥90% on all four dimensions)
# CI-ENFORCED coverage gate (#1550): runs `npm run coverage`, which
# chains every client's `test:coverage` (v8-instrumented) and fails the
# job if ANY file drops below 90% on lines, statements, functions, or
# branches. This is the whole point of the step — a PR that regresses
# coverage below the threshold now blocks merge instead of relying on a
# contributor remembering to run the gate locally.
#
# This also covers the web integration project: web's `test:coverage`
# runs `--project=unit --project=integration --coverage`, so the former
# standalone `Run web integration tests` step was removed to avoid
# running the integration suite twice.
run: npm run coverage
- name: Run cross-client smokes
# End-to-end smokes through the built launcher (--help dispatch + prod
# CLI/web). Not part of any client's `validate`: it needs the
# cli/tui/launcher bundles, which `validate` above already built
# (smoke:web builds clients/web/dist on demand — #1486). smoke:tui
# self-skips here — the Ink TUI needs a real TTY (raw mode) that headless
# CI lacks, so its boot/render check is local-only.
run: npm run smoke
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('clients/web/package-lock.json') }}
- name: Install Playwright browsers
working-directory: ./clients/web
run: npx playwright install --with-deps chromium
- name: Run Storybook play-function tests
working-directory: ./clients/web
run: npm run test:storybook
# Publish the single `@modelcontextprotocol/inspector` package to npm on a
# published GitHub release. v2 is not an npm workspace, so there is no
# `publish-all` / `--workspaces` (v1) — just one `npm publish`, whose `prepack`
# (`npm run build`) builds every client bundle into the `files` allowlist.
# `pack:verify` runs first as the pre-publish gate: it builds, packs the real
# tarball, installs it into a clean throwaway consumer, and drives the
# installed `mcp-inspector` bin (web/cli/tui) end to end — so a broken package
# is caught before it reaches npm rather than after.
publish:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: release
needs: build
# Serialize publishes so two releases cut in quick succession can't run
# overlapping `npm publish`es. Never cancel an in-flight publish.
concurrency:
group: publish-npm
cancel-in-progress: false
permissions:
contents: read
# Required for npm provenance (`--provenance` mints a signed attestation
# via GitHub's OIDC token). The repo is public, so provenance is available.
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22.x'
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- name: Assert release tag matches package version
# `npm publish` ships whatever `version` is in the root package.json,
# regardless of the release's tag. Fail fast (before the heavy install /
# pack:verify) if they disagree — e.g. a release drafted without running
# `npm version`, or cut from the wrong commit — since publishing is
# irreversible. Tolerates the conventional leading `v` (npm version tags
# as `vX.Y.Z`).
run: |
PKG="$(node -p "require('./package.json').version")"
TAG="${{ github.event.release.tag_name }}"
if [ "${TAG#v}" != "$PKG" ]; then
echo "Release tag '$TAG' does not match package.json version '$PKG'"
exit 1
fi
- name: Install dependencies (root + all clients)
run: npm install
- name: Verify the publishable tarball end to end
# Builds, `npm pack`s, installs the tarball into a clean consumer, and
# drives the installed bin. Needs registry access to pull the tarball's
# runtime deps — available here. `smoke:tui` inside it self-skips on CI.
run: npm run pack:verify
- name: Publish to npm (single package, with provenance)
# `prepack` (`npm run build`) rebuilds the client bundles into the tarball.
# The build runs three times on this path (build job → pack:verify →
# prepack); the redundancy is intentional — each is a clean-tree rebuild
# and the `prepack` one is what actually populates the published tarball,
# so don't "optimize" it away.
run: npm publish --access public --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}