-
Notifications
You must be signed in to change notification settings - Fork 10
151 lines (138 loc) · 6.08 KB
/
Copy pathpublish.yml
File metadata and controls
151 lines (138 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# Publish the release to npm.
#
# moshcode's own install path is `curl … install.sh | sh`, and npm is a second
# channel rather than the primary one — which is exactly why it needs to be
# automatic. A channel that only updates when someone remembers is a channel
# that silently serves an old version forever.
#
# Runs on a published GitHub release, so the release itself is the trigger and
# there is no separate step to forget. `workflow_dispatch` is for re-running one
# that failed on something transient; it is safe because a version already on
# the registry is skipped rather than attempted.
#
# Authenticates with a stored npm automation token, in the repository secret
# `NPM_TOKEN`. Trusted publishing (OIDC) would avoid the stored credential and
# was tried twice; both attempts failed with npm reporting no credential at all,
# and the registration on npmjs.com — the web-UI half of it, pinned to this
# file's name — is what remains unconfirmed. See #305, #309, #311.
#
# If you go back to OIDC, the trap is below: `registry-url` is *required* here
# and *fatal* there. setup-node writes
#
# //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
#
# which is how the token reaches npm — and under OIDC, with no token to fill it,
# resolves to an empty credential that stops npm attempting the exchange.
# Removing it changed the error from E404 to ENEEDAUTH but did not publish.
# actions/setup-node#1551, npm/cli#9088.
#
# Since then setup-node v7 stopped exporting a dummy NODE_AUTH_TOKEN when no
# token is set (actions/setup-node#1558), which is the half of that trap that
# corrupted .npmrc under OIDC. It does not affect the token path — we set
# NODE_AUTH_TOKEN explicitly — but it means trusted publishing is worth a third
# attempt before the 2FA-bypass deprecation below forces one.
#
# Worth knowing when this is next revisited: npm is restricting tokens that
# bypass 2FA for direct publishing, so the token path has a horizon.
# https://gh.io/npm-gat-bypass2fa-deprecation
name: publish
on:
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
# Still needed with token auth: provenance is signed with a short-lived OIDC
# token even though the publish itself authenticates with NPM_TOKEN. Without
# it `--provenance` fails.
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
# pnpm version is read from the "packageManager" field in package.json.
# Do not pin a version here — it conflicts with packageManager and fails
# with ERR_PNPM_BAD_PM_VERSION.
- uses: pnpm/action-setup@v6
# `registry-url` is what makes setup-node write the .npmrc line that feeds
# NODE_AUTH_TOKEN to npm. Required for token auth — and the thing to delete
# first if this ever moves back to OIDC.
- uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm
registry-url: https://registry.npmjs.org
- run: pnpm install --frozen-lockfile
# Publishing is the one action here that cannot be taken back — npm will
# not let a version be replaced — so the tests run first, on the exact
# tree about to be packed.
- run: pnpm run --if-present test
env:
CI: true
- name: Read the version being published
id: version
run: |
VERSION="$(node -p "require('./package.json').version")"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "moshcode@$VERSION"
# A release tagged v0.25.0 carrying package.json 0.24.0 would publish the
# wrong tree under a version nobody can reuse. Cheap to check, impossible
# to undo.
- name: Check the tag matches package.json
if: github.event_name == 'release'
env:
TAG: ${{ github.event.release.tag_name }}
VERSION: ${{ steps.version.outputs.version }}
run: |
if [ "$TAG" != "v$VERSION" ]; then
echo "::error::release tag $TAG does not match package.json version $VERSION"
exit 1
fi
# Makes a re-run harmless. Without it, dispatching the workflow twice
# fails the second time on an E403 that reads like something broke.
- name: Skip if this version is already on npm
id: published
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
if npm view "moshcode@$VERSION" version >/dev/null 2>&1; then
echo "already=true" >> "$GITHUB_OUTPUT"
echo "moshcode@$VERSION is already published — nothing to do"
else
echo "already=false" >> "$GITHUB_OUTPUT"
fi
# Said plainly here, rather than as the E404/no-permission npm otherwise
# returns partway through a release — an error that reads as "the package
# does not exist" rather than "there is no credential".
- name: Require an npm token
if: steps.published.outputs.already == 'false'
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -z "$NPM_TOKEN" ]; then
echo "::error::NPM_TOKEN is not set — add an npm automation token as a repository secret named NPM_TOKEN"
exit 1
fi
- name: Publish
if: steps.published.outputs.already == 'false'
run: npm publish --access public --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Confirm the registry has it
if: steps.published.outputs.already == 'false'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
# The registry is read-through cached, so a fresh publish can 404 for
# a moment. Retry rather than report a good publish as a failure.
for _ in 1 2 3 4 5; do
if [ "$(npm view "moshcode@$VERSION" version 2>/dev/null)" = "$VERSION" ]; then
echo "moshcode@$VERSION is on the registry"
exit 0
fi
sleep 5
done
echo "::error::published, but the registry does not report moshcode@$VERSION yet"
exit 1