Skip to main content

Plan: npm Trusted Publishing (OIDC) via GitHub Actions

Moves sovdev-logger's npm publish step from manual (npm login + personal OTP, every release) to npm's Trusted Publishing — a GitHub Actions workflow authenticates via short-lived OIDC tokens instead of a stored npm token or a human's 2FA. Manual publishing keeps working as a fallback; this doesn't remove it.

IMPLEMENTATION RULES: Before implementing this plan, read and follow:

Status: Completed

Goal: A workflow_dispatch-triggered GitHub Actions workflow that publishes sovdev-logger to npm via Trusted Publishing, with the npmjs.com Trusted Publisher configuration set up to match it exactly.

Last Updated: 2026-07-13

Investigation: INVESTIGATE-npm-trusted-publishing.md — all 5 questions resolved. Trigger: workflow_dispatch ([Q3]). Scope: auth mechanism only, no changelog/version-bump automation bundled in ([Q5]).

Real constraint found during research, not assumed: the npm CLI version required (>=11.5.1) isn't satisfied anywhere in this repo's current environments (host 10.2.4, DevContainer 10.9.7, CI's actions/setup-node@v4+Node 22 default is npm 10.x) — the workflow must explicitly upgrade npm as its own step.

A step I cannot do myself: configuring the actual Trusted Publisher on npmjs.com/package/sovdev-logger/access requires the maintainer's own logged-in session on npmjs.com's web UI — no CLI or API path exists for this per every source checked during the investigation. This plan creates and validates everything up to that point; the maintainer does the npmjs.com-side configuration and the first live trigger.


Phase 1: Create the publish workflow — DONE

Tasks

  • 1.1 Created .github/workflows/publish.yml: workflow_dispatch trigger, permissions: { contents: read, id-token: write }, actions/checkout@v7, actions/setup-node@v6 with registry-url: 'https://registry.npmjs.org' and node-version: 22.
  • 1.2 Explicit npm install -g npm@latest step before anything else.
  • 1.3 npm ci, npm run build, npm run lint inside typescript/ (working-directory default).
  • 1.4 npm publish --access public --provenance.
  • 1.5 No E2E test step — confirmed unnecessary, per plan.

Validation

A real dispatched run, not just syntax checking. Confirmed GitHub requires a workflow_dispatch workflow to exist on the default branch before it's dispatchable at all — tried dispatching from the feature branch first, got a clean 404 workflow not found on the default branch, confirming this constraint directly rather than assuming it. Merged to main, then dispatched a real run: every step through Verify package.json metadata passed (checkout, npm upgrade, install, build, lint) — the log itself doesn't print the resulting npm version, but the subsequent successful OIDC token exchange and provenance signing (below) wouldn't have worked at all on the pre-upgrade npm 10.x, confirming the upgrade step did what it needed to. The final Publish to npm step failed exactly as expected — 404 Not Found ... could not be found or you do not have permission to access it — because no Trusted Publisher is configured yet on npmjs.com (Phase 2, not done).

Unexpected, useful finding: provenance attestation actually worked before any Trusted Publisher was configured — the run's log shows npm notice publish Signed provenance statement with source and build information from GitHub Actions and a real Sigstore transparency-log entry (https://search.sigstore.dev/?logIndex=2165055851), even though the subsequent publish itself failed on authorization. Provenance signing (GitHub's generic OIDC/Sigstore mechanism) and npm's Trusted Publisher access grant (who's authorized to publish this specific package) are evidently independent — the former doesn't require the latter to be configured. Worth confirming this holds once Phase 2 makes the actual publish succeed, but it de-risks acceptance criterion "provenance attestation confirmed visible" considerably.


Phase 2: npmjs.com configuration and first live run (maintainer-only)

Tasks — DONE

  • 2.1 (Maintainer) Added the Trusted Publisher on npmjs.com/package/sovdev-logger/access: GitHub org helpers-no, repo sovdev-logger, workflow filename publish.yml, no environment name, npm publish selected as the allowed action.
  • 2.2 Triggered the workflow (gh workflow run publish.yml) — published 1.0.1 for real (it existed in package.json since the OTel-upgrade work but had never actually been published; this was its genuine first publish, not a re-publish).
  • 2.3 Full run succeeded end-to-end — OIDC token exchange, npm upgrade, build, lint, and the actual npm publish all green. Verified independently against the real registry: npm view sovdev-logger version1.0.1, npm view sovdev-logger dist.tarball → a real, resolvable tarball URL.
  • 2.4 Provenance confirmed via the registry's own API, not just the workflow log: npm view sovdev-logger@1.0.1 --json's dist.attestations shows a real SLSA provenance attestation (predicateType: https://slsa.dev/provenance/v1) with its own attestations URL. Also a real, distinct Sigstore transparency-log entry (logIndex=2165078709) — different from the earlier failed-publish attempt's entry, confirming this one is genuinely tied to the successful publish, not a stale artifact.
  • 2.5 Manual-publish fallback confirmed via direct, package-specific evidence rather than a redundant real test: the npmjs.com settings page itself states "All publishing access options above are compatible with OIDC trusted publishers... they will continue to work regardless of which option you select" — for this exact package, not generic docs. No "disallow tokens" restriction was enabled on sovdev-logger's settings. Not verified via an actual duplicate manual publish, since that would need another version bump and OTP for no additional confidence beyond this direct confirmation.

Validation

1.0.1 published via the new workflow, confirmed on the real npm registry (version, tarball URL, and a real SLSA provenance attestation with a distinct Sigstore log entry) — not just the workflow's own "success" log line.


Acceptance Criteria

  • .github/workflows/publish.yml exists, workflow_dispatch-triggered, upgrades npm explicitly, builds/lints before publishing.
  • npmjs.com Trusted Publisher configured to match the workflow exactly.
  • A real publish via the workflow succeeds, verified against the live registry (not just the workflow's own "success" log line).
  • Provenance attestation confirmed visible on the published version.
  • Manual npm publish fallback confirmed still functional afterward (via direct, package-specific settings-page evidence, not a redundant real test).

Files to Modify

  • .github/workflows/publish.yml (new file) — done
  • website/docs/contributor/publishing/typescript.md — done, restructured into "Publish (recommended: GitHub Actions)" as the primary path and "Publish manually (fallback)" for the old OTP-gated process