Maintainer guide for publishing a new version of the @pharn-dev/pharn npm package.
Releases are automated: cut a GitHub Release and
.github/workflows/publish.yml publishes to
npm. Authentication is npm Trusted Publishing (OIDC) — there are no npm
tokens anywhere in this repository or in its GitHub Actions secrets.
The canonical npm package is @pharn-dev/pharn (org-scoped) —
https://www.npmjs.com/package/@pharn-dev/pharn. It exposes a single pharn bin.
The unscoped name
pharnis not publishable: npm rejects it with E403 as too similar to existing packages (yarn,charm,sharp), and a scoped name sidesteps that similarity check — so@pharn-dev/pharnis canonical. An earlier@pharn-dev/pharn@0.2.0was published then unpublished on 2026-07-22, so0.2.0is permanently burned on this name; releases resumed at0.3.0. The installed binary stayspharn.
publish.yml uses npm Trusted Publishing (OIDC): at publish time the
workflow exchanges a short-lived GitHub OIDC id-token with npm — no long-lived
credential is stored or passed.
- No secrets, no tokens — not in the repo, not in Actions secrets, not in
the workflow.
permissions: id-token: writeis what enables the OIDC exchange (and the--provenanceattestation); it is not a secret. - The Trusted Publisher is configured on npmjs.com for package
@pharn-dev/pharn, pinned to workflowpublish.ymland the GitHub deployment environmentnpm-publish. - If a publish ever fails asking for a token, the Trusted Publisher config is broken — fix it on npmjs.com. Never add an npm token secret.
-
Bump the version. Update
"version"inpackage.json(SemVer). -
Update
CHANGELOG.md. Move the[Unreleased]entries under a new## [X.Y.Z] — YYYY-MM-DDheading and refresh the compare links at the bottom. -
Merge to
mainvia PR. All nine required checks must pass: the sixci.ymlgates plusfloor,gitleaksandAnalyze (javascript-typescript). -
Cut a GitHub Release. Tag it
vX.Y.Z, whereX.Y.Zexactly matchespackage.jsonversion. The tag must be plainvX.Y.Z— a prerelease tag (v1.2.0-rc.1) or a tag without thevis refused — and it must point at a commit that is onmain. -
Publishing the Release triggers
publish.yml, in two jobs:buildholds no publish rights (id-tokenis not granted to it). Before installing anything it checks the tag format, that the tag equalspackage.jsonversion, and that the tagged commit is contained inmain. It then runsnpm ci,npm run checkandnpm run test:coverage, packs the tarball (npm pack, whoseprepackbuilds it), installs that tarball into a scratch directory and runspharn --version, and uploads it as an artifact.publishis the only job withid-token: writeand thenpm-publishenvironment. It checks out nothing and installs nothing: on node 24, whose bundled npm already satisfies the npm >= 11.5.1 Trusted Publishing needs (an Assert npm floor step enforces that), it downloads the tarball and runsnpm publish <tarball> --provenance --access public --ignore-scripts. The--provenanceflag overridespublishConfig.provenance: false, so the release carries a signed provenance attestation.
Why the split: every step of a job that holds
id-token: writecan request the token npm trades for publish rights. Keeping the dev toolchain (install scripts, linters, tests, bundler) out of that job means a compromised dev dependency can no longer publish on its own. It can still influence the tarball's content inbuild— the residual this does not close.Note that
pharnVersion(this package) andskillsVersion(upstream'sSKILLS_VERSION, which an install records separately) are independent numbers. Releasing the CLI neither reads nor moves the content version. If a release requires newer content, coordinate upstream'sMIN_CLIfile in the same window — that is the handshake that refuses an under-versioned CLI.
- GitHub Actions — the
publishworkflow run for the Release is green. - npmjs.com — https://www.npmjs.com/package/@pharn-dev/pharn shows the new version, and that version shows a provenance badge linking back to the GitHub Actions run.
- Smoke-test —
npx @pharn-dev/pharn@latest --versionprints the new version.
npm only lets you configure a Trusted Publisher on a package that already
exists. So the very first publish of a brand-new package name is done
manually and locally by a maintainer who is logged in (npm login):
npm publish # provenance stays off (publishConfig.provenance: false) — no OIDC available yetThen configure the Trusted Publisher on npmjs.com (workflow publish.yml,
environment npm-publish), and every subsequent release goes through
publish.yml as described above. This exception applies only to introducing
a new package name — never to a normal version bump of @pharn-dev/pharn.