Releases currently ship the packaged tarball as a release asset, which installs fine:
npm install -g https://github.com/coderdojo-linz/locker-cli/releases/latest/download/coderdojo-linz-locker-0.1.0.tgz
What it does not give us is npx @coderdojo-linz/locker — the short, install-free way in, which for a tool aimed at kids is most of the point. That needs the package on the npm registry.
Why there is no token to just add
npm permanently revoked every classic token on 9 December 2025. They cannot be recreated or recovered. Granular tokens still exist, but they enforce 2FA and are capped at a 90-day lifetime, so an unattended pipeline built on one is a secret that silently stops working four times a year.
The supported answer is trusted publishing: GitHub mints a short-lived OIDC token for the workflow, npm verifies it against a trust relationship configured on npm's side, and nothing long-lived is stored anywhere. Provenance is generated automatically as a result, so the package page shows which commit and which workflow built it.
What is already done
.github/workflows/publish.yml requests id-token: write and runs on a GitHub-hosted runner. Self-hosted runners are not accepted for trusted publishing, which is why publishing lives here rather than on the Forgejo runners with the rest of the pipeline.
package.json has the repository field npm requires to match exactly, and a publishConfig asking for public access and provenance — so publishing by hand from a laptop fails rather than quietly producing an unattested package.
- The npm publish step exists but is switched off, so releases do not fail while this is outstanding.
What is left
1. Own the scope. @coderdojo-linz/locker needs an npm organisation named coderdojo-linz. Create it at npmjs.com if it does not exist. (If the org has to be called something else, the scope in package.json changes with it — it is one word.)
2. Add an organisation-level trusted publisher.
npmjs.com → your organisation → Settings → Automation → Trusted Publishers → Add.
| Field |
Value |
| Provider |
GitHub Actions |
| Organization or user |
coderdojo-linz |
| Repository |
locker-cli |
| Workflow filename |
publish.yml |
| Environment |
(leave empty) |
| Allowed actions |
npm publish |
It has to be on the organisation, not on the package. A package-level trusted publisher can only be added to a package that already exists, and this one does not — the usual chicken-and-egg, and the reason the first publish would otherwise still need a token. An organisation-level publisher lets the workflow create new packages in the scope, which sidesteps it entirely.
If the Automation section is not visible, it is an organisation-level setting rather than a personal-account one — a personal account's settings page does not have it. Check you are looking at the org, and that you are an owner of it.
3. Flip the switch. In this repository: Settings → Secrets and variables → Actions → Variables → new repository variable:
NPM_TRUSTED_PUBLISHING = true
The workflow reads it and the next release publishes to npm as well as attaching the tarball. No code change needed, and setting it back to anything else turns it off again.
4. Release.
gh release create v0.1.0 --repo coderdojo-linz/locker-cli --title v0.1.0 --generate-notes
The workflow refuses if the tag and package.json disagree.
Then
Update the install instructions in README.md — both here and in the private server repo, where the CLI is developed — to lead with npx instead of the release tarball.
References
Releases currently ship the packaged tarball as a release asset, which installs fine:
What it does not give us is
npx @coderdojo-linz/locker— the short, install-free way in, which for a tool aimed at kids is most of the point. That needs the package on the npm registry.Why there is no token to just add
npm permanently revoked every classic token on 9 December 2025. They cannot be recreated or recovered. Granular tokens still exist, but they enforce 2FA and are capped at a 90-day lifetime, so an unattended pipeline built on one is a secret that silently stops working four times a year.
The supported answer is trusted publishing: GitHub mints a short-lived OIDC token for the workflow, npm verifies it against a trust relationship configured on npm's side, and nothing long-lived is stored anywhere. Provenance is generated automatically as a result, so the package page shows which commit and which workflow built it.
What is already done
.github/workflows/publish.ymlrequestsid-token: writeand runs on a GitHub-hosted runner. Self-hosted runners are not accepted for trusted publishing, which is why publishing lives here rather than on the Forgejo runners with the rest of the pipeline.package.jsonhas therepositoryfield npm requires to match exactly, and apublishConfigasking for public access and provenance — so publishing by hand from a laptop fails rather than quietly producing an unattested package.What is left
1. Own the scope.
@coderdojo-linz/lockerneeds an npm organisation namedcoderdojo-linz. Create it at npmjs.com if it does not exist. (If the org has to be called something else, the scope inpackage.jsonchanges with it — it is one word.)2. Add an organisation-level trusted publisher.
npmjs.com → your organisation → Settings → Automation → Trusted Publishers → Add.
coderdojo-linzlocker-clipublish.ymlnpm publishIt has to be on the organisation, not on the package. A package-level trusted publisher can only be added to a package that already exists, and this one does not — the usual chicken-and-egg, and the reason the first publish would otherwise still need a token. An organisation-level publisher lets the workflow create new packages in the scope, which sidesteps it entirely.
If the Automation section is not visible, it is an organisation-level setting rather than a personal-account one — a personal account's settings page does not have it. Check you are looking at the org, and that you are an owner of it.
3. Flip the switch. In this repository: Settings → Secrets and variables → Actions → Variables → new repository variable:
The workflow reads it and the next release publishes to npm as well as attaching the tarball. No code change needed, and setting it back to anything else turns it off again.
4. Release.
The workflow refuses if the tag and
package.jsondisagree.Then
Update the install instructions in
README.md— both here and in the private server repo, where the CLI is developed — to lead withnpxinstead of the release tarball.References