mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-22 18:57:13 +00:00
stage the npm release rather than publishing it
npm is withdrawing direct publish from every token that bypasses 2FA: management operations went in July, publishing goes in January, after which such a token may only read private packages and stage a publish. Its trusted publishing is OIDC against GitHub Actions and GitLab, which this forge is neither, so there is no credential-free path here either. So the runner does the part a token is still allowed to do. It stages the tarball and stops; nothing is on the registry until a maintainer approves it with a code, from `npm stage approve` or the Staged Packages tab. The forge ends up holding a credential that can propose a release and cannot make one, which is a better arrangement than the one being taken away. The secret it wants is therefore an ordinary granular token scoped to this package, explicitly without 2FA bypass. Node comes from nodesource now: `npm stage` wants npm 11.15 on Node 22.14, and bookworm ships 18.
This commit is contained in:
@@ -61,7 +61,11 @@ jobs:
|
||||
|
||||
- name: Build the npm package
|
||||
run: |
|
||||
apt-get install -y --no-install-recommends nodejs npm
|
||||
# Node from nodesource rather than apt. `npm stage` below wants npm
|
||||
# 11.15 and Node 22.14, and bookworm ships Node 18.
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y --no-install-recommends nodejs
|
||||
npm install --global npm@^11.15
|
||||
rustup target add wasm32-unknown-unknown
|
||||
version=$(awk '/^name = "wasm-bindgen"$/{getline; gsub(/[",]/,""); print $3; exit}' Cargo.lock)
|
||||
curl -sSfL "https://github.com/rustwasm/wasm-bindgen/releases/download/$version/wasm-bindgen-$version-x86_64-unknown-linux-musl.tar.gz" \
|
||||
@@ -91,22 +95,28 @@ jobs:
|
||||
# so a half-published release would otherwise burn it.
|
||||
/tmp/venv/bin/twine upload --non-interactive --skip-existing dist/*
|
||||
|
||||
- name: Publish to npm
|
||||
# Staged, not published. npm is withdrawing direct publish from any token
|
||||
# that bypasses 2FA, and its trusted publishing is OIDC against GitHub
|
||||
# Actions and GitLab, which this forge is neither. So the runner does the
|
||||
# part a token may do and stages the tarball, and a maintainer approves it
|
||||
# with 2FA: `npm stage list`, then `npm stage approve <id>`, or the Staged
|
||||
# Packages tab on npmjs.com. Nothing reaches the registry until then.
|
||||
- name: Stage the npm release
|
||||
env:
|
||||
# Scoped to this package, for the same reason the PyPI token is
|
||||
# scoped to this project: npm has trusted publishing too, and it does
|
||||
# not know this forge either.
|
||||
# Granular, write on typikon alone, and it does not need to bypass
|
||||
# 2FA, because staging is exactly what a token that cannot bypass it
|
||||
# is still allowed to do.
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$NPM_TOKEN" ]; then
|
||||
echo "::warning::NPM_TOKEN is not set; skipping the npm publish"
|
||||
echo "::warning::NPM_TOKEN is not set; nothing staged on npm"
|
||||
exit 0
|
||||
fi
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
||||
# A version cannot be reused on npm either, so a re-run after a
|
||||
# partial release must not die on the version already being there.
|
||||
npm publish --access public npm/dist || {
|
||||
npm view "typikon@${GITHUB_REF_NAME#v}" version >/dev/null 2>&1 \
|
||||
&& echo "::notice::already published" \
|
||||
|| exit 1
|
||||
}
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
if npm view "typikon@$version" version >/dev/null 2>&1; then
|
||||
echo "::notice::typikon@$version is already published"
|
||||
exit 0
|
||||
fi
|
||||
cd npm/dist && npm stage publish --access public
|
||||
echo "::notice::staged typikon@$version; approve it with 2FA to publish"
|
||||
|
||||
+33
-4
@@ -97,10 +97,20 @@ Also under **funkpower**, also scoped to the one package, added as the
|
||||
repository secret `NPM_TOKEN`. Make it a **granular access token** with write
|
||||
on `typikon` alone; a classic automation token is account-wide.
|
||||
|
||||
npm has trusted publishing too, and it does not know this forge either, so the
|
||||
same tradeoff applies. Without the secret the release still succeeds and the
|
||||
npm step warns and skips, so the first release of a version after the token
|
||||
lapses is a PyPI release and nothing else. Check the run.
|
||||
It does **not** need "bypass 2FA", and should not have it. npm is withdrawing
|
||||
direct publish from every token that bypasses 2FA: management operations went
|
||||
in July 2026, publishing goes in January 2027, after which such a token may
|
||||
only read private packages and stage a publish. Staging is what this workflow
|
||||
does, so an ordinary granular token is both sufficient and the one that
|
||||
survives.
|
||||
|
||||
npm has trusted publishing too, and like PyPI's it is OIDC against GitHub
|
||||
Actions and GitLab, which this forge is neither. So the forge holds a
|
||||
long-lived credential and the scoping is the whole of the mitigation.
|
||||
|
||||
Without the secret the release still succeeds and the npm step warns and
|
||||
skips, so the first release after the token lapses is a PyPI release and
|
||||
nothing else. Check the run.
|
||||
|
||||
## What gets published
|
||||
|
||||
@@ -119,6 +129,25 @@ cannot compile WebAssembly synchronously on the main thread and so has to be
|
||||
awaited. The exports map picks between them. There are no per-platform
|
||||
artefacts, because WebAssembly has no platforms.
|
||||
|
||||
### The npm half needs a hand
|
||||
|
||||
The tag does not finish the npm release. The runner **stages** the tarball; it
|
||||
is not on the registry and `npm install typikon` will not see it until someone
|
||||
approves it with 2FA:
|
||||
|
||||
```console
|
||||
$ npm stage list
|
||||
$ npm stage view <stage-id> # what is in it
|
||||
$ npm stage approve <stage-id> # asks for the code
|
||||
```
|
||||
|
||||
Or the **Staged Packages** tab on npmjs.com. Either way a code is asked for,
|
||||
which is the point: the forge holds a credential that can propose a release and
|
||||
cannot make one.
|
||||
|
||||
`npm stage` wants npm 11.15 or newer on Node 22.14 or newer, on the approving
|
||||
machine as well as the runner.
|
||||
|
||||
**No macOS or Windows wheels.** Forgejo has no hosted runners, so those need a
|
||||
Mac and a Windows box registered as runners. Until then both fall back to the
|
||||
sdist, which needs a Rust toolchain on the installing machine — fine for
|
||||
|
||||
Reference in New Issue
Block a user