etherpad-lite/doc/npm-trusted-publishing.md
John McLear b8d1c8a192
ci(docs): build on PRs and pin Node 22 (Qodo follow-up to #7640) (#7645)
* ci(docs): build on PRs and pin Node 22 (Qodo follow-up to #7640)

Qodo flagged two reliability gaps on the oxc-minify fix that landed in
#7640:

  1. The Deploy Docs to GitHub Pages workflow only ran on push to
     develop, so a PR that broke `pnpm run docs:build` was not caught
     until after merge — exactly how the dead-link regression in #7546
     escaped. Add a pull_request trigger that runs the same build but
     skips the deploy/upload steps via `if: github.event_name ==
     'push'`. Also include the workflow file itself in the path filter
     so changes to it are exercised on PR.
  2. oxc-minify@0.128.0 requires Node ^20.19.0 || >=22.12.0, but the
     workflow did not pin Node and the repo declared engines.node
     >=22.0.0 with engineStrict: true — a runner image (or local dev)
     on Node 22.0–22.11 would refuse to install. Pin Node 22 in the
     docs workflow with actions/setup-node@v6 (matching the rest of
     CI), and bump engines.node to >=22.12.0 so the project's
     engineStrict gate matches the actual minimum.

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

* ci(docs): split build and deploy so PR runs do not hit pages env protection

The previous attempt put `if: github.event_name == 'push'` on individual
deploy steps but kept the single job's `environment: github-pages`
binding. Environment protection rules reject any non-develop ref
(including `refs/pull/N/merge`), so the runner failed the entire job
at creation time before any step could execute:

    Branch "refs/pull/7645/merge" is not allowed to deploy to
    github-pages due to environment protection rules.

Split into two jobs: `build` runs on every trigger (PR + push) and
uploads the artifact only on push, `deploy` depends on `build`,
runs only on push, and is the only job bound to the github-pages
environment. Standard GHA pages-deploy pattern; PR builds never
attempt to enter the protected environment.

* docs: align Node minimum references with bumped engines.node (Qodo round 2 on #7645)

Qodo flagged that engines.node moved from >=22.0.0 to >=22.12.0 in
this PR but documentation still claimed the old requirement. Sync the
three places that pinned a specific minimum:

  - README.md installation requirements (>= 22 → >= 22.12)
  - doc/npm-trusted-publishing.md publish prerequisites
    (>=22.0.0 → >=22.12.0, with oxc-minify cited as the driver)
  - CHANGELOG.md 2.7.3 breaking-changes entry (22 → 22.12, with the
    same oxc-minify justification)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:12:23 +01:00

5.5 KiB

npm Trusted Publishing (OIDC)

Etherpad and every ether/ep_* plugin publish to npm using npm Trusted Publishing over OpenID Connect. This eliminates the need to store, rotate, or accidentally leak long-lived NPM_TOKEN secrets — each publish is authenticated against the GitHub Actions runner with a short-lived OIDC token instead.

How it works

  1. The publish workflow declares permissions: id-token: write.
  2. GitHub Actions issues a signed OIDC token to the runner.
  3. The npm CLI (>= 11.5.1) trades that OIDC token for a short-lived publish credential against npmjs.com.
  4. npmjs.com checks the OIDC claims (org, repo, workflow file, branch / environment) against the package's configured trusted publisher and, if they match, accepts the publish. Provenance attestations are recorded automatically.

No NPM_TOKEN secret is needed in any plugin or in core.

One-time setup per package

Trusted publishing has to be enabled once per package. Use the bundled script to do every package in one go via the npm trust CLI (npm >= 11.5.1):

# 1. Make sure npm CLI is recent enough
npm install -g npm@latest

# 2. Log in to npmjs.com as a maintainer
npm login

# 3. Bulk-configure every ether/ep_* plugin + ep_etherpad
bin/setup-trusted-publishers.sh

# Or preview without changing anything
bin/setup-trusted-publishers.sh --dry-run

# Or target a specific subset
bin/setup-trusted-publishers.sh --packages ep_align,ep_webrtc

# Or ignore packages that are already configured (the registry only allows
# one trust relationship per package today)
bin/setup-trusted-publishers.sh --skip-existing

# Supply a 2FA OTP up front (required if your npm account has 2FA enabled —
# it should). The same OTP is reused for every package call inside the same
# minute, so for large batches you may need to chunk via --packages.
bin/setup-trusted-publishers.sh --otp 123456

2FA / OTP note. npm trust github requires an OTP whenever the account has 2FA enabled. Without --otp, npm will prompt interactively per package, which is unworkable in bulk. Pass --otp <code> once and the script will forward it to every npm trust github call. TOTP codes typically expire every 30 seconds, so for >30s runs split the work with --packages ep_a,ep_b,... and re-run with a fresh code.

The script discovers all non-archived ether/ep_* repos via gh repo list and runs npm trust github <pkg> --repository <org>/<repo> --file <workflow> --yes for each one. ep_etherpad is mapped to the etherpad-lite repo and the releaseEtherpad.yml workflow; everything else is mapped to its same-named repo and test-and-release.yml.

If you'd rather click through the npmjs.com UI for a single package: open https://www.npmjs.com/package/<name>/accessTrusted PublisherAdd trusted publisher → Publisher: GitHub Actions, Organization: ether, Repository: as above, Workflow filename: as above, Environment: blank.

Once added, the next push to main/master will publish via OIDC with no token at all.

Migrating an existing package

If a package previously had an NPM_TOKEN secret in CI:

  1. Add the trusted publisher on npmjs.com (steps above).
  2. Bump the workflow to the OIDC version — done in bin/plugins/lib/npmpublish.yml (which is propagated to every plugin by the update-plugins workflow).
  3. Remove the now-unused NPM_TOKEN secret from the GitHub repo settings.

Requirements

  • Node.js: >= 22.12 on the runner. npm 11 requires >=22.9.0 and oxc-minify (a vitepress peer for the docs build) requires >=22.12.0, both of which setup-node@v6 with version: 22 satisfies (resolves to the latest 22.x). The project's engines.node requires >=22.12.0.

  • npm CLI: >= 11.5.1. The publish workflow runs npm install -g npm@latest before publishing so the bundled npm version doesn't matter.

  • Runner: must be a GitHub-hosted (cloud) runner. Self-hosted runners are not yet supported by npm trusted publishing.

  • package.json: must declare a repository field pointing at the GitHub repo so npm can verify the OIDC claim. Example:

    {
      "repository": {
        "type": "git",
        "url": "https://github.com/ether/ep_align.git"
      }
    }
    

Why call npm publish directly?

The publish workflows run npm publish --provenance --access public rather than pnpm publish or gnpm publish. Both wrappers shell out to whichever npm is on PATH, but they obscure version requirements: trusted publishing requires npm >= 11.5.1, and going through the wrapper makes it easy to end up with the wrong CLI version. Invoking npm directly removes that ambiguity.

pnpm is still used for everything else (install, build, version bump) — only the final publish step calls npm directly.

Troubleshooting

npm error 404 Not Found - PUT https://registry.npmjs.org/<pkg>

The trusted publisher hasn't been configured on npmjs.com for that package, or the repository / workflow filename in the trusted publisher config doesn't match the running workflow. Double-check the workflow filename — it must be the basename of the workflow YAML, not the job name.

npm error code E_OIDC_NO_TOKEN

The workflow is missing permissions: id-token: write. Add it to the job (or to the top-level permissions: block).

npm error need: 11.5.1

The runner is using an older bundled npm. The workflow runs npm install -g npm@latest to fix this — make sure that step ran before the publish step.