npm publishing setup
This document describes the one-time, maintainer-facing setup required to publish
@jeap/jeap-jwe-client to the public npm registry. It covers everything that must
be configured outside the repository (on npmjs.com and in the GitHub repository
settings).
The goal is the state required by the project: the package is published publicly
under the npm @jeap organization, releases run
through GitHub Actions using npm Trusted Publishing (OIDC), and no long-lived
npm token is stored in CI.
How releases work
There is a single workflow, .github/workflows/build-and-release.yml. On a push to main
it runs the checks (lint, licenses, compatibility, packaging) and then a release
job:
- Release on merge to
main. When the version inprojects/jeap-jwe-client/package.jsonhas not been released yet (no matchingv<version>tag), thereleasejob publishes it. A merge that does not bump the version is a no-op (the tag already exists, so the job skips). - The job runs in the protected
releaseenvironment (see below). - The publish step uses OIDC trusted publishing in the steady state (no token, provenance generated automatically).
- After a successful publish it pushes a
vX.Y.Ztag as a record of the release, using the defaultGITHUB_TOKEN. The tag is a marker and idempotency guard — it is not a release trigger — so no PAT (CI_PUSH_TOKEN) is needed. - Requirements (already satisfied by the workflow): the job has
permissions: id-token: write(andcontents: writeto push the tag), runs on a GitHub-hosted runner (ubuntu-latest), and upgrades npm to a version that supports trusted publishing (npm install -g npm@latest; trusted publishing needs npm ≥ 11.5.1 and Node ≥ 22.14.0).
One-time prerequisites (npmjs.com)
- The npm
jeaporganization must exist (https://www.npmjs.com/org/jeap), and the publishing maintainer must be a member with rights to publish to the@jeapscope. - The package is scoped and published publicly. This is already declared in
projects/jeap-jwe-client/package.jsonvia"publishConfig": { "access": "public" }, and the workflow also passes--access public.
No PAT is required. The release job pushes its record tag with the default
GITHUB_TOKEN, and OIDC handles publishing.
Why a one-time bootstrap is needed
npm cannot configure a trusted publisher for a package that does not exist yet — the Trusted Publishing settings only appear on an already-published package. So the very first version must be published with a token. After that, trusted publishing takes over and the token is removed.
The workflow handles this automatically: while the NPM_TOKEN secret exists
(scoped to the release environment), it publishes with that token; once the
secret is deleted, it publishes via OIDC. No workflow edit is needed to switch over.
The release environment
The release job runs in a GitHub Actions environment named release
(environment: release in build-and-release.yml). It is used to:
- restrict publishing to
main, - scope the bootstrap
NPM_TOKENsecret to the publish job only, - bind the npm trusted publisher to the same environment name once OIDC is enabled.
There is no manual approval gate; releases publish automatically on merge to main.
Step 1 — Create the release environment
- In the GitHub repository: Settings → Environments → New environment, name it
release. - Under Deployment branches and tags, choose Selected branches and tags
and add a branch rule:
main. - (No required reviewers — this environment publishes without manual approval.)
Step 2 — Bootstrap the first release with a temporary token
- On npmjs.com, create an Automation access token scoped to publish for the
@jeaporganization (Account → Access Tokens → Generate New Token → Automation). - Add it as an environment secret named
NPM_TOKENon thereleaseenvironment (Settings → Environments →release→ Add environment secret). - Make sure
projects/jeap-jwe-client/package.jsonis at the version you want to release (1.0.0for the initial release) and thatCHANGELOG.mdandpubliccode.ymlmatch. - Get that version onto
main(merge the version bump). Thereleasejob sees there is nov1.0.0tag yet, publishes@jeap/jeap-jwe-client@1.0.0to npm (public, with provenance) using the token, and pushes thev1.0.0record tag.
Set up the
releaseenvironment and theNPM_TOKENsecret before the1.0.0version reachesmain, otherwise the release runs before the token exists and fails. A failed release is harmless — fix the setup and re-run the workflow (Run workflowonmain); the guard re-publishes because nov1.0.0tag was pushed yet.
Step 3 — Configure the trusted publisher on npm
Once the package exists on npm:
- Go to https://www.npmjs.com/package/@jeap/jeap-jwe-client → Settings → Trusted Publishing.
- Add a GitHub Actions trusted publisher:
- Organization or user:
jeap-admin-ch - Repository:
jeap-jwe-client - Workflow filename:
build-and-release.yml(filename only, not a path) - Environment name:
release(must match the workflow'senvironment:) - Allowed actions:
npm publish
- Organization or user:
- Save.
The GitHub environment name and npm's Environment name must match. If you set one on npm but not in the workflow (or vice versa), OIDC publishing fails.
Step 4 — Remove the long-lived token from CI
- Delete the
NPM_TOKENenvironment secret (Settings → Environments →release→ remove the secret).
From now on the workflow's OIDC publish step runs and no long-lived npm token is stored in CI, satisfying the project's security requirement.
Steady-state releases
For every subsequent release:
- Bump the version in
projects/jeap-jwe-client/package.jsonand updatepubliccode.yml(softwareVersion/releaseDate) to match. - Update
CHANGELOG.md. - Merge the version bump to
main. - The
releasejob builds, verifies, publishes via OIDC trusted publishing, and pushes thevX.Y.Zrecord tag.
Notes and caveats
- Runner support: trusted publishing requires GitHub-hosted runners. Self-hosted runners are not yet supported.
- Provenance is generated automatically under OIDC; the
--provenanceflag is not required (it is only passed on the bootstrap/token path so the first release is also attested). - Provenance requires a public source repository — the repository is public, so this is satisfied.
- Each package can have only one trusted publisher configured at a time.