Repository navigation
182 lines (168 loc) · 9.11 KB
/
Copy pathnpm-release.yml
File metadata and controls
182 lines (168 loc) · 9.11 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# Publish to npm when a GitHub release is promoted from prerelease to full release.
# Downloads the npm package tarball from the release (no rebuild).
#
# AUTH: npm OIDC trusted publishing (no long-lived NPM_TOKEN). One-time setup on
# npmjs.com — needed SEPARATELY for `@spacedevin/deck`, `@spacedevin/deck-synths` AND
# `@spacedevin/deck-player`, since trusted publishers are per package. Settings > Trusted Publisher > GitHub Actions:
# Organization or user: spacedevin
# Repository: deck
# Workflow filename: npm-release.yml
# Environment: (leave blank)
# Requires npm >= 11.5.1 (upgraded below) and the id-token: write permission.
#
# All three packages ship from one release at one version, published in dependency order —
# language, then synths, then player — because each one's dependency is pinned to that exact version.
name: NPM release
on:
release:
types: [published, edited]
# Re-dispatch to publish a release the event never reached — promoting a prerelease does not
# always fire `edited`, and without this the only recovery is editing the release again and hoping.
# Publishing is idempotent: a version already on npm is skipped rather than failing.
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish, e.g. v1.2.6"
required: true
type: string
jobs:
publish:
name: Publish to npm
if: ${{ github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false }}
runs-on: ubuntu-latest
permissions:
contents: write # required to update release body via API
id-token: write # required for npm OIDC trusted publishing
env:
# No NODE_AUTH_TOKEN — auth is via OIDC.
# Provenance disabled: packages publish from pre-built tarballs (built in CI, not this run).
NPM_CONFIG_PROVENANCE: "false"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: refs/tags/${{ github.event.release.tag_name || inputs.tag }}
fetch-depth: 0
- name: Resolve the release
id: rel
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.event.release.tag_name || inputs.tag }}"
# `github.event.release.id` is empty on a workflow_dispatch, so look it up by tag — which
# also works for the release event and keeps one code path.
ID=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
if [ -z "$ID" ] || [ "$ID" = "null" ]; then
echo "No release found for tag $TAG"
exit 1
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "id=$ID" >> "$GITHUB_OUTPUT"
- name: Download npm package tarball from release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Look the asset up through the API, NOT `github.event.release.assets`. That payload is a
# snapshot frozen when the release event fired, and ci.yml creates the release BEFORE it
# finishes uploading the tarball — so this races, and a re-run replays the SAME frozen
# payload and fails identically forever. (tishlang/tish#593 — it cost v3.2.1 its npm publish.)
for attempt in 1 2 3 4 5 6 7 8 9 10; do
DECK_URL=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.rel.outputs.id }}/assets?per_page=100" \
| jq -r '.[] | select(.name == "spacedevin-deck-npm-package.tgz" and .state == "uploaded") | .url')
[ -n "$DECK_URL" ] && [ "$DECK_URL" != "null" ] && break
echo "tarball not uploaded yet (attempt $attempt/10) — waiting 30s"
sleep 30
done
if [ -z "$DECK_URL" ] || [ "$DECK_URL" = "null" ]; then
echo "Release has no uploaded asset spacedevin-deck-npm-package.tgz after 5 minutes"
exit 1
fi
curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/octet-stream" "$DECK_URL" -o spacedevin-deck-npm-package.tgz
# The synths catalog ships in lockstep from the same release. Same retry loop, same reason,
# and the same tolerance: a release predating the package has no such asset, so its absence
# is not fatal.
for attempt in 1 2 3 4 5; do
SYNTHS_URL=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.rel.outputs.id }}/assets?per_page=100" \
| jq -r '.[] | select(.name == "spacedevin-deck-synths-npm-package.tgz" and .state == "uploaded") | .url')
[ -n "$SYNTHS_URL" ] && [ "$SYNTHS_URL" != "null" ] && break
echo "synths tarball not uploaded yet (attempt $attempt/5) — waiting 30s"
sleep 30
done
if [ -n "$SYNTHS_URL" ] && [ "$SYNTHS_URL" != "null" ]; then
curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/octet-stream" "$SYNTHS_URL" -o spacedevin-deck-synths-npm-package.tgz
else
echo "No spacedevin-deck-synths-npm-package.tgz on this release — skipping the synths publish."
fi
# The player ships in lockstep from the same release. Same retry loop, same reason. A
# release predating the player has no such asset, so its absence is not fatal — it just
# means there is nothing to publish.
for attempt in 1 2 3 4 5; do
PLAYER_URL=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.rel.outputs.id }}/assets?per_page=100" \
| jq -r '.[] | select(.name == "spacedevin-deck-player-npm-package.tgz" and .state == "uploaded") | .url')
[ -n "$PLAYER_URL" ] && [ "$PLAYER_URL" != "null" ] && break
echo "player tarball not uploaded yet (attempt $attempt/5) — waiting 30s"
sleep 30
done
if [ -n "$PLAYER_URL" ] && [ "$PLAYER_URL" != "null" ]; then
curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/octet-stream" "$PLAYER_URL" -o spacedevin-deck-player-npm-package.tgz
else
echo "No spacedevin-deck-player-npm-package.tgz on this release — skipping the player publish."
fi
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing (needs >= 11.5.1)
run: |
# Pinned to npm 11.x (not 12.x) — same rationale as tishlang/lattish npm-release.yml.
npm install -g "npm@^11"
npm --version
- name: Publish @spacedevin/deck to npm
run: npm publish spacedevin-deck-npm-package.tgz --access public
# After the language, for the same reason: the synths package's dependency was pinned to this
# exact version at pack time, so it is uninstallable until the language is on the registry.
- name: Publish @spacedevin/deck-synths to npm
run: |
if [ -f spacedevin-deck-synths-npm-package.tgz ]; then
npm publish spacedevin-deck-synths-npm-package.tgz --access public
else
echo "No synths tarball — nothing to publish."
fi
# Second, and only after the language package is live: the player's dependency was pinned to
# this exact version at pack time, so publishing it first would put an uninstallable package on
# the registry for as long as the other step takes.
- name: Publish @spacedevin/deck-player to npm
run: |
if [ -f spacedevin-deck-player-npm-package.tgz ]; then
npm publish spacedevin-deck-player-npm-package.tgz --access public
else
echo "No player tarball — nothing to publish."
fi
- name: Update release description with npm URL
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.rel.outputs.tag }}"
VERSION="${TAG#v}"
NPM_URL="https://www.npmjs.com/package/@spacedevin/deck/v/${VERSION}"
RELEASE_ID="${{ steps.rel.outputs.id }}"
REPO="${{ github.repository }}"
CURRENT_BODY=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/${REPO}/releases/${RELEASE_ID}" | jq -r '.body // ""')
NEW_BODY="${CURRENT_BODY}
---
Published to npm: ${NPM_URL}"
curl -s -X PATCH \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${REPO}/releases/${RELEASE_ID}" \
-d "$(printf '%s' "$NEW_BODY" | jq -Rs '{body: .}')"