Skip to content

feat: publish with npm provenance from CI and move Nx framework deps to peers#216

Merged
NachoVazquez merged 3 commits into
mainfrom
apnea/npm-provenance-ci-publish
Jul 21, 2026
Merged

feat: publish with npm provenance from CI and move Nx framework deps to peers#216
NachoVazquez merged 3 commits into
mainfrom
apnea/npm-provenance-ci-publish

Conversation

@NachoVazquez

@NachoVazquez NachoVazquez commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Merge strategy: rebase-merge preferred so the three commits land as-is. Squash is fine for this PR now that nothing is a major-breaking ! commit — but keeping the two package-scoped feats separate still produces cleaner per-package changelogs.

npmx flagged both published packages for supply-chain trust issues. This PR fixes the root causes and documents the one-time maintainer actions that code cannot perform.

Problems addressed

  1. Missing provenance on production releases. Versions 1.x–3.0.0 of @naxodev/gonx (and 5.x–6.x of @naxodev/nx-cloudflare) had npm provenance attestations; every release since (gonx 4.x, nx-cloudflare 7.0.0) has none, because production publishing moved to a maintainer laptop with an OTP. Provenance can only be emitted from cloud CI.
  2. Dependency-bloat "vulnerability" noise. nx/@nx/devkit (gonx) and @nx/devkit/@nx/js (nx-cloudflare) were runtime dependencies, adding ~73MB per install and attributing the Nx framework's transitive advisories to these plugins. Both plugins only ever run inside an Nx workspace, which already provides these packages.
  3. Stale dist-tags. gonx@next points at 2.0.0-beta.7 (latest is 4.0.2); nx-cloudflare@next points at 5.0.0-rc.0 (latest is 7.0.0).

What changed

ci: publish production releases via OIDC with provenance

  • New unified .github/workflows/publish.yml replaces publish-pr.yml (npm allows one trusted-publisher workflow filename per package, so preview and production must share a file):
    • publish-preview — the existing label-gated PR-preview job, unchanged except job-level concurrency (publish-preview-<pr>) and an npm pin (npm@11).
    • publish-production — fires on <project>@vX.Y.Z tag pushes (the tags nx release --skip-publish already creates) or workflow_dispatch with a tag input. Parses and validates the tag, checks out the tagged commit, asserts source and built dist/ manifest versions match the tag, skips idempotently if the version is already live, and publishes dist/packages/<project> tokenlessly via npm OIDC trusted publishing — provenance is automatic. Prereleases (version containing -) auto-tag next; stable versions tag latest. Gated by the production GitHub environment.
    • No tokens anywhere: static tokens conflict with the account's 2FA-on-writes; OIDC is the path that already works for previews. Publishing bypasses bun (nx release publish delegates to bun, whose OIDC exchange doesn't engage).
  • CONTRIBUTING.md Releasing section rewritten: version/changelog/tag locally with bunx nx release --projects=<project> --skip-publish, CI publishes on the pushed tag. Includes dry-run, re-run, prerelease handling, back-patch latest warning, trusted-publisher setup, dist-tag hygiene, provenance verification, and a no-provenance local fallback (last resort). CLAUDE.md updated to match.

feat(gonx): move nx and @nx/devkit to peerDependencies
feat(nx-cloudflare): move @nx/devkit and @nx/js to peerDependencies

  • Framework packages become peerDependencies at ^23.0.0 (same range that was already declared as runtime deps). Only true runtime deps remain in dependencies. Runtime imports are unchanged — peers resolve identically.
  • Shipped as minors, not majors: every consumer is an Nx workspace that already has these packages, and modern package managers (npm ≥ 7, pnpm ≥ 8, bun, yarn berry) auto-install peers. No public API change; no supported version range removed.
  • Optional packageJsonUpdates migrations still ship so nx migrate inserts the peers for anyone who relied on the previous transitive install: keyed at gonx 4.1.0 / nx-cloudflare 7.1.0 with alwaysAddToPackageJson: true.
  • repository.url normalized to git+https://github.com/naxodev/oss.git in both manifests (provenance hygiene).

Not changed: chalk@^4.1.2 stays. OSV MAL-2025-46969 affects only chalk@5.6.1; scanners flagging gonx for it are ignoring affected-version ranges — false positive, no action.

Test plan

  • bunx nx run gonx:lint / nx-cloudflare:lint green — includes @nx/nx-plugin-checks.
  • bunx nx run-many -t build test --projects=gonx,nx-cloudflare green; bunx nx format:check green.
  • Both e2e suites green (serial, shared Verdaccio) — real tarball install into a fresh Nx workspace.
  • Doc-consistency greps verified; the only remaining publish-pr.yml reference is the intentional historical one in CONTRIBUTING.

Post-merge checklist (human, in order)

  1. Immediately after merge — on npmjs.com, for each of @naxodev/gonx and @naxodev/nx-cloudflare: Package Settings → Trusted Publisher → change the GitHub Actions workflow filename from publish-pr.yml to publish.yml (repository naxodev/oss, environment field left blank). Until this is done, preview and production publishes fail loudly with an OIDC error.
  2. GitHub → repo Settings → Environments → create production and add yourself as required reviewer.
  3. One-shot dist-tag cleanup (needs OTP): npm dist-tag rm @naxodev/gonx next (stale at 2.0.0-beta.7) and npm dist-tag rm @naxodev/nx-cloudflare next (stale at 5.0.0-rc.0).
  4. First provenance releases: bunx nx release --projects=gonx --skip-publish, then the same for nx-cloudflare — these produce the 4.1.0 / 7.1.0 minors carrying the peer-dependency move and its migrations. Approve the environment gate, then verify npm view @naxodev/<pkg>@<version> --json | jq .dist.attestations is non-null and the npmjs package page shows the provenance badge.
  5. Release-notes note: chalk remains at ^4.1.2 and is unaffected by MAL-2025-46969 (scanner false positive on 5.6.1 only).

Residual risk

  • Trusted-publisher rename window (checklist item 1): between merge and the npm config update, all publishes fail. Loud, expected, and confined to previews unless a release is cut in that window.
  • Consumers on npm < 7 (no automatic peer installation) must add the peers manually if they skip nx migrate — rare in Nx 23 workspaces.
  • Back-patching an older major moves latest backwards (CI maps every non-prerelease to latest); CONTRIBUTING documents the manual npm dist-tag add retag.

@nx-cloud

nx-cloud Bot commented Jul 21, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 466a1a8

Command Status Duration Result
nx affected -t e2e --parallel=1 ✅ Succeeded 4m 18s View ↗
nx affected -t build --yes ✅ Succeeded 1s View ↗
nx affected -t lint test typecheck ✅ Succeeded 2s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-21 14:13:30 UTC

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gonx-docs 466a1a8 Commit Preview URL

Branch Preview URL
Jul 21 2026, 02:07 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
nx-cloudflare 466a1a8 Commit Preview URL

Branch Preview URL
Jul 21 2026, 02:07 PM

Stop shipping the Nx framework as nested runtime dependencies. `nx` and
`@nx/devkit` are now peerDependencies at ^23.0.0 (same range as before).

Every consumer is an Nx workspace that already provides these packages, and
modern package managers auto-install peers, so this is a minor — not a major.

`packageJsonUpdates` at 4.1.0 uses alwaysAddToPackageJson: true so
`nx migrate @naxodev/gonx` still inserts the peers for consumers that relied
on the previous transitive install.
Stop shipping the Nx framework as nested runtime dependencies. `@nx/devkit`
and `@nx/js` are now peerDependencies at ^23.0.0 (same range as before);
`wrangler` was already a peer.

Every consumer is an Nx workspace that already provides these packages, and
modern package managers auto-install peers, so this is a minor — not a major.

`packageJsonUpdates` at 7.1.0 uses alwaysAddToPackageJson: true so
`nx migrate @naxodev/nx-cloudflare` still inserts the peers for consumers that
relied on the previous transitive install.
@NachoVazquez NachoVazquez changed the title feat!: publish with npm provenance from CI and move Nx framework deps to peers feat: publish with npm provenance from CI and move Nx framework deps to peers Jul 21, 2026
@NachoVazquez
NachoVazquez force-pushed the apnea/npm-provenance-ci-publish branch from c336ecc to 466a1a8 Compare July 21, 2026 14:06
@NachoVazquez
NachoVazquez merged commit 0f65415 into main Jul 21, 2026
10 checks passed
@NachoVazquez
NachoVazquez deleted the apnea/npm-provenance-ci-publish branch July 21, 2026 14:24
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