Skip to content

ci(release): publish @workspacejson/cli from tagged releases (META-236) - #13

Merged
qmarcelle merged 1 commit into
mainfrom
feat/cli-npm-publish-pipeline
Jul 26, 2026
Merged

ci(release): publish @workspacejson/cli from tagged releases (META-236)#13
qmarcelle merged 1 commit into
mainfrom
feat/cli-npm-publish-pipeline

Conversation

@qmarcelle

@qmarcelle qmarcelle commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Gives @workspacejson/cli a tag-triggered npm publisher, modelled on the flow proven in workspace-json/codex-mcp. Releasing becomes:

pnpm changeset version          # bumps packages/cli/package.json + CHANGELOG
git commit && git push
git tag cli-v0.1.0 && git push --tags

The tag is both trigger and source of truth. cli-v0.1.0 is read from an env var (never inlined into a shell), validated as clean semver, and asserted equal to packages/cli/package.json before anything reaches the registry — a tag that disagrees with the manifest fails the run rather than publishing a version nobody named.

Authority stays split. agents-audit remains published by workspace-json/agents-audit until META-243. The disjoint tag namespaces mean no single workflow can publish both, so this needs no authority cutover: OWNERSHIP.md recorded @workspacejson/cli's prior authority as "none yet", and the name is unregistered on npm.

Why npm rather than pnpm for the publish step

pnpm 9 exposes no --provenance flag, and pnpm publish refuses to run on a detached tag checkout without disabling git checks wholesale. The substitution is safe only because @workspacejson/cli declares no workspace: dependencies, so there is nothing for pnpm to rewrite at pack time — and verify-package-tarball.mjs asserts that against the packed manifest. The invariant holds by gate, not by assumption.

The workflow calls the existing verify-package-tarball.mjs rather than reimplementing the reference repo's checks: ours is stronger already, installing the tarball into a clean project and running workspacejson generate end to end versus the reference's --help.

Three claims that had drifted from reality

  • .changeset/config.json was invalid. It ignored @workspacejson/cli while agents-audit depends on it, which changesets rejects outright — pnpm changeset status failed before this PR. CI never ran changesets, so nothing caught it.
  • verify-published.mjs, release.yml and verify-package-tarball.mjs each stated that @workspacejson/cli is private: true and kept off the registry by the architecture guard. The manifest carries no private field, sets publishConfig.access: public, OWNERSHIP.md said "public, not yet published", and the guard contains no such assertion.
  • verify-published.mjs would have verified the wrong package. It knew only agents-audit and derived the version from that manifest, so a cli-v* release could have gone green on a sibling. It now takes the package name as an argument, with guards against unknown names and against pinning one version across several packages.

OWNERSHIP.md is updated to match, since it is the authoritative record of publish authority and leaving it would recreate exactly this class of drift.

Verification

  • pnpm -r typecheck
  • pnpm -r test — 100 tests, 14 files
  • pnpm -r build
  • node packages/agents-audit-compat/dist/cli.js scan . --fail-on error — 0 errors, 0 warnings
  • bash migration/parity-agents-audit-runtime.shran in CI, passed (Compatibility parity vs frozen source, 1m3s). I had judged it not applicable, since nothing agents-audit exposes changed — the edits are release tooling (verify-published.mjs selection logic, one comment in verify-package-tarball.mjs) and its command surface, exit codes, output and exports are untouched. CI runs the harness unconditionally, so the claim is now measured rather than argued.

Also run:

  • pnpm run check:architecture — 63 files, 3 manifests, no violations
  • pnpm run release:verify-packs — both packages release-safe
  • pnpm changeset status — now succeeds
  • npm publish --dry-run from packages/cli — 12 files, 9.3 kB, no source or test files
  • node scripts/verify-published.mjs agents-audit — end to end against the live registry
  • All three workflow YAMLs parse with the expected triggers

Notes

Could not be tested locally: --provenance requires CI OIDC. Prerequisites are met (public repo, repository.url matches the remote, id-token: write granted). A provenance failure occurs before upload, so the risk is a red run, not a partial publish.

Before tagging: confirm NPM_TOKEN grants publish on @workspacejson/cli. This is a new package name, so a token scoped only to existing packages will not cover the first publish — that needs broader scope or org-level publish rights.

Follow-up (META-243): the same workflow shape drops in for agents-audit on its own tag namespace, but that one needs the ordered cutover — disable the old workflow, revoke its token, then grant here. Two publishers racing on a live package is the failure mode to avoid.

Adds `.github/workflows/publish-cli.yml`, a tag-triggered publisher for
`@workspacejson/cli` modelled on the flow proven in workspace-json/codex-mcp.

The tag is both trigger and source of truth: `cli-v0.1.0` is read from an env
var (never inlined into a shell), validated as clean semver, and asserted equal
to `packages/cli/package.json` before anything reaches the registry. A tag that
disagrees with the manifest fails the run instead of publishing a version
nobody named.

Publishing uses npm rather than pnpm for two reasons: pnpm 9 exposes no
`--provenance` flag, and `pnpm publish` refuses to run on a detached tag
checkout without disabling git checks wholesale. The substitution is safe only
because `@workspacejson/cli` declares no `workspace:` dependencies, and
verify-package-tarball.mjs asserts that against the packed manifest — so it
holds by gate, not by assumption.

Authority is deliberately split. `agents-audit` stays with
workspace-json/agents-audit until META-243; the disjoint tag namespaces mean no
one workflow can publish both.

Also fixes three claims that had drifted from reality:

* `.changeset/config.json` ignored `@workspacejson/cli` while `agents-audit`
  depended on it, which is an invalid configuration — `changeset status` failed
  outright. CI never ran changesets, so nothing caught it.
* verify-published.mjs, release.yml and verify-package-tarball.mjs each stated
  that `@workspacejson/cli` is `private: true` and kept off the registry by the
  architecture guard. The manifest carries no `private` field, sets
  `publishConfig.access: public`, and the guard contains no such assertion.
* verify-published.mjs verified only `agents-audit` and took its version from
  that package's manifest, so a CLI release would have checked the wrong
  package. It now takes the package as an argument.

Verified: architecture guard, typecheck, 100 tests, CLI tarball verification
(packs, installs into a clean project, runs `workspacejson generate`),
`npm publish --dry-run`, and verify-published.mjs end to end against the
published agents-audit@0.4.4.
Copilot AI review requested due to automatic review settings July 26, 2026 22:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@qmarcelle
qmarcelle merged commit 3cca591 into main Jul 26, 2026
3 checks passed
@qmarcelle
qmarcelle deleted the feat/cli-npm-publish-pipeline branch July 26, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants