Skip to content

ci: add hardened npm publish pipeline (OIDC, provenance, pkg.pr.new) - #2

Merged
ushironoko merged 3 commits into
mainfrom
npm-publish-supplychain
Jun 3, 2026
Merged

ci: add hardened npm publish pipeline (OIDC, provenance, pkg.pr.new)#2
ushironoko merged 3 commits into
mainfrom
npm-publish-supplychain

Conversation

@ushironoko

Copy link
Copy Markdown
Owner

Summary

CI-only, tokenless npm publishing for the packages, following current npm supply-chain best practices (researched + adversarially verified, then plan-reviewed).

Why

First-time publish setup. Given recent npm supply-chain attacks, the aim is to keep no long-lived publish token in CI and to attest every release with provenance.

What's in here

Publishing

  • OIDC Trusted Publishing via the npm CLI with automatic Sigstore provenance (bun publish supports neither yet).
  • release.yml split into two jobs: version (Changesets PR, no id-token) and publish (gated by a protected release Environment, with id-token: write scoped to that job only).
  • scripts/publish.ts: ordered publish (compiler → tskm → vite), rewrites workspace:* to a concrete range before packing and restores it, is fail-closed on registry errors, and asserts the protocol never leaks into the tarball. Unit-tested (scripts/publish.test.ts).
  • scripts/smoke.ts: clean-room gate — packs each package, installs them with their real peers outside the workspace, and exercises the public entry points, the bin, and the spawned worker file.
  • Continuous preview releases via pkg.pr.new (preview.yml, no npm token).

Hardening

  • All GitHub Actions pinned to commit SHAs; Dependabot keeps them current.
  • --ignore-scripts installs plus a lockfile-integrity guard in CI.
  • repository metadata and a LICENSE added per package (provenance requires repository); publint/attw gates; initial version set.

Verification

lint, typecheck, build, the full test suite, the publish unit tests, publint, attw, the publish dry-run, the clean-room smoke test, and actionlint all pass locally.

Follow-up (human, before first release)

One-time setup is documented in docs/RELEASING.md: npm scope, the bootstrap publish, Trusted Publisher config (workflow release.yml), the pkg.pr.new GitHub App, branch protection on main, and the release Environment with required reviewers.

🤖 Generated with Claude Code

ushironoko and others added 2 commits June 3, 2026 10:54
Set up CI-only, tokenless publishing for the packages, following current
npm supply-chain best practices:

- OIDC Trusted Publishing via the npm CLI (bun publish supports neither
  OIDC nor provenance), with automatic Sigstore provenance. release.yml
  splits the Changesets version PR (no id-token) from publish, which is
  gated by a protected Environment and is the only job granted id-token.
- An ordered publish script that rewrites the workspace: protocol to a
  concrete range before packing, is fail-closed on registry errors, and
  asserts the protocol never leaks into the tarball. Unit tested.
- A clean-room install+run smoke gate: packs each package, installs them
  with their real peers outside the workspace, and exercises the public
  entry points, the bin, and the spawned worker file to catch breakage
  that only appears after a real install.
- Continuous preview releases via pkg.pr.new (no npm token).
- Actions pinned to commit SHAs with automated update PRs; installs run
  with --ignore-scripts plus a lockfile-integrity guard.
- Add repository metadata and a LICENSE to each package (required for
  provenance), set the initial version, and add publint/attw gates.

See docs/RELEASING.md for the one-time human setup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the test:type / typecheck scripts from tsc to tsgo
(@typescript/native-preview), the native TypeScript compiler, for much
faster type-checking. Added as a root devDependency so the binary
resolves at the workspace root; the platform binary ships as an
os/cpu-constrained optionalDependency, so it resolves in CI under
--ignore-scripts. tsc is kept for .d.ts generation via rolldown-plugin-dts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@ushironoko ushironoko left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[AI Rally - Reviewee]

Removed the fragile overrides: { "@tskm/compiler": "file:..." } entry from scripts/smoke.ts. npm validates override targets as semver/package specs, so a file: tarball override can fail (e.g. Invalid comparator) and block the pre-publish smoke gate. The override was unnecessary: packPackage rewrites @tskm/vite's @tskm/compiler: workspace:* to ^<compiler version>, and the top-level @tskm/compiler file: tarball installs that exact version, which satisfies the ^ range — npm dedupes to it without reaching the registry, so transitive resolution already works. Added a comment explaining why no override is needed. Verified with bun run typecheck (tsgo, clean) and bun run lint (biome, clean). Committed locally; not pushed.

Files modified:

  • scripts/smoke.ts

@ushironoko ushironoko left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[AI Rally - Reviewer]

The prior blocking issue is addressed. Removing the overrides entry avoids npm's override-target validation path, and the smoke test now correctly relies on the packed @tskm/vite dependency rewrite plus the top-level @tskm/compiler file tarball satisfying the resulting range. I did not find any new blocking issues in the updated diff. The PR is ready to merge, assuming the documented one-time npm/GitHub setup is completed before relying on the automated release path.

The smoke consumer carried an `overrides` entry pinning @tskm/compiler to
the local file: tarball. It is redundant: the packed @tskm/vite has
`@tskm/compiler: workspace:*` rewritten to a caret range, and because the
three packages are released in lockstep at the same version, the top-level
@tskm/compiler file: tarball already satisfies that range — npm dedupes to
it without reaching the registry. Removing the override keeps the consumer
minimal; verified the clean-room smoke still packs, installs, imports, runs
the bin, and finds the worker file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ushironoko
ushironoko merged commit 51d3c75 into main Jun 3, 2026
1 of 3 checks passed
@ushironoko
ushironoko deleted the npm-publish-supplychain branch June 3, 2026 04:08
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.

1 participant