|
10 | 10 | # that failed on something transient; it is safe because a version already on |
11 | 11 | # the registry is skipped rather than attempted. |
12 | 12 | # |
13 | | -# Authenticates by trusted publishing (OIDC) rather than a stored token: npm |
14 | | -# trades the short-lived token GitHub mints for this specific workflow run for |
15 | | -# permission to publish, so there is no long-lived credential in the repository |
16 | | -# to leak, rotate or forget. |
| 13 | +# Authenticates with a stored npm automation token, in the repository secret |
| 14 | +# `NPM_TOKEN`. Trusted publishing (OIDC) would avoid the stored credential, but |
| 15 | +# it needs a trusted publisher registered against this workflow's filename on |
| 16 | +# npmjs.com, which can only be done through the web UI — and until that exists |
| 17 | +# npm rejects the publish as E404/no-permission, which is how v0.24.1 failed. |
17 | 18 | # |
18 | | -# The other half of that trust lives on npmjs.com, under the package's Trusted |
19 | | -# Publisher settings, and it is pinned to the *filename* of this workflow. |
20 | | -# Renaming this file silently breaks publishing — npm will refuse the exchange |
21 | | -# because the run no longer matches what was configured. |
| 19 | +# Worth knowing when this is next revisited: npm is restricting tokens that |
| 20 | +# bypass 2FA for direct publishing, so the token path has a horizon. |
| 21 | +# https://gh.io/npm-gat-bypass2fa-deprecation |
22 | 22 | name: publish |
23 | 23 |
|
24 | 24 | on: |
|
28 | 28 |
|
29 | 29 | permissions: |
30 | 30 | contents: read |
31 | | - # The whole basis of the exchange: this is what lets the run mint the OIDC |
32 | | - # token npm authenticates against. Without it there is no credential at all |
33 | | - # and publishing fails outright. |
| 31 | + # Still needed with token auth: provenance is signed with a short-lived OIDC |
| 32 | + # token even though the publish itself authenticates with NPM_TOKEN. Without |
| 33 | + # it `--provenance` fails. |
34 | 34 | id-token: write |
35 | 35 |
|
36 | 36 | jobs: |
|
51 | 51 | cache: pnpm |
52 | 52 | registry-url: https://registry.npmjs.org |
53 | 53 |
|
54 | | - # Node 22 bundles npm 10, which predates trusted publishing and would fall |
55 | | - # back to looking for a token that no longer exists — an auth failure that |
56 | | - # reads as a credential problem rather than a version one. 11.5.1 is the |
57 | | - # floor; the check below says so plainly if that ever regresses. |
58 | | - - name: Install an npm that understands trusted publishing |
59 | | - run: | |
60 | | - npm install -g npm@latest |
61 | | - VERSION="$(npm --version)" |
62 | | - MINIMUM=11.5.1 |
63 | | - echo "npm $VERSION" |
64 | | - # Lowest of the two must be the minimum, or this npm is older than it. |
65 | | - if [ "$(printf '%s\n%s\n' "$MINIMUM" "$VERSION" | sort -V | head -n1)" != "$MINIMUM" ]; then |
66 | | - echo "::error::npm $VERSION cannot use trusted publishing — $MINIMUM or later is required" |
67 | | - exit 1 |
68 | | - fi |
69 | | -
|
70 | 54 | - run: pnpm install --frozen-lockfile |
71 | 55 |
|
72 | 56 | # Publishing is the one action here that cannot be taken back — npm will |
@@ -111,11 +95,24 @@ jobs: |
111 | 95 | echo "already=false" >> "$GITHUB_OUTPUT" |
112 | 96 | fi |
113 | 97 |
|
114 | | - # No token, and no `--provenance` either: publishing through trusted |
115 | | - # publishing generates and attaches the attestation on its own. |
| 98 | + # Said plainly here, rather than as the E404/no-permission npm otherwise |
| 99 | + # returns partway through a release — an error that reads as "the package |
| 100 | + # does not exist" rather than "there is no credential". |
| 101 | + - name: Require an npm token |
| 102 | + if: steps.published.outputs.already == 'false' |
| 103 | + env: |
| 104 | + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} |
| 105 | + run: | |
| 106 | + if [ -z "$NPM_TOKEN" ]; then |
| 107 | + echo "::error::NPM_TOKEN is not set — add an npm automation token as a repository secret named NPM_TOKEN" |
| 108 | + exit 1 |
| 109 | + fi |
| 110 | +
|
116 | 111 | - name: Publish |
117 | 112 | if: steps.published.outputs.already == 'false' |
118 | | - run: npm publish --access public |
| 113 | + run: npm publish --access public --provenance |
| 114 | + env: |
| 115 | + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
119 | 116 |
|
120 | 117 | - name: Confirm the registry has it |
121 | 118 | if: steps.published.outputs.already == 'false' |
|
0 commit comments