Repository navigation
266 lines (243 loc) · 10.1 KB
/
Copy pathrelease.yml
File metadata and controls
266 lines (243 loc) · 10.1 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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
name: Release
# Manually triggered publishing. The operator picks the bump (or an exact version), the
# workflow verifies the tree, versions and tags it, publishes to npm, and files a GitHub
# release whose notes are derived from the commits since the previous tag.
on:
workflow_dispatch:
inputs:
bump:
description: "Semver bump to apply to packages/draftly (ignored when `version` is set)"
type: choice
required: true
default: patch
options:
- patch
- minor
- major
- prepatch
- preminor
- premajor
- prerelease
- none
version:
description: "Exact version to publish, e.g. 3.0.0. Overrides `bump` when set."
type: string
required: false
preid:
description: "Prerelease identifier for the pre* bumps, e.g. next, beta, rc"
type: string
required: false
default: next
npm_tag:
description: "npm dist-tag to publish under. Use something other than `latest` for a prerelease."
type: choice
required: true
default: latest
options:
- latest
- next
- beta
- alpha
notes_from:
description: "Ref the release notes start after. Defaults to the previous v* tag, else the first commit."
type: string
required: false
dry_run:
description: "Verify, version and build, print the notes, but publish and push nothing"
type: boolean
required: true
default: false
permissions:
contents: write # push the release commit and tag, and create the release
id-token: write # authenticates the publish to npm via OIDC, and signs the provenance
# Two releases at once would race on the tag and the version commit.
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
name: Verify and publish
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out the full history
uses: actions/checkout@v7
with:
# The release notes diff against an earlier tag, so a shallow clone will not do.
fetch-depth: 0
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
# npm does the publishing, not Bun: the publish authenticates to npm through OIDC
# (trusted publishing), which Bun does not implement. `registry-url` is what points
# npm at the public registry; no auth token is set, deliberately — see "Publish to npm".
#
# Node 24 is also the floor for trusted publishing, which needs npm >= 11.5.1
# (Node 24.22 ships 11.19.1) and Node >= 22.14.
- name: Set up Node
uses: actions/setup-node@v7
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Lint
run: bun run lint
# turbo.json has no typecheck task yet (tracked as T-007), so the scripts are called
# directly rather than inventing a task name here.
- name: Typecheck the library
run: cd packages/draftly && bun run typecheck
- name: Typecheck the playground
run: cd apps/web && bun run typecheck
# There is no test suite yet (tracked as T-001). This step runs the moment one lands
# and stays quiet until then, rather than failing every release in the meantime.
- name: Test
run: |
if [ -n "$(git ls-files '*.test.ts' '*.test.tsx' '*.spec.ts' '*.spec.tsx')" ]; then
bun test
else
echo "No test files tracked in git — skipping. See artifacts/tasks (T-001)."
fi
# The version is read with `node -p`, not `npm pkg get`: inside a workspace member npm
# resolves the workspace root and answers with a package-keyed JSON object, which is
# multi-line and rejected by $GITHUB_OUTPUT.
- name: Resolve the version
id: version
env:
BUMP: ${{ inputs.bump }}
EXPLICIT: ${{ inputs.version }}
PREID: ${{ inputs.preid }}
run: |
set -euo pipefail
cd packages/draftly
previous="$(node -p "require('./package.json').version")"
if [ -n "$EXPLICIT" ]; then
npm version --no-git-tag-version --no-workspaces --ignore-scripts --allow-same-version "$EXPLICIT" > /dev/null
elif [ "$BUMP" = "none" ]; then
echo "Publishing the version already in package.json."
elif [ "${BUMP#pre}" != "$BUMP" ]; then
npm version --no-git-tag-version --no-workspaces --ignore-scripts --preid "$PREID" "$BUMP" > /dev/null
else
npm version --no-git-tag-version --no-workspaces --ignore-scripts "$BUMP" > /dev/null
fi
version="$(node -p "require('./package.json').version")"
echo "Resolved $previous -> $version"
echo "previous=$previous" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
- name: Refuse to republish an existing version
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
if npm view "draftly@$VERSION" version > /dev/null 2>&1; then
echo "::error::draftly@$VERSION is already on npm. Pick a different bump or version."
exit 1
fi
if git rev-parse -q --verify "refs/tags/v$VERSION" > /dev/null; then
echo "::error::Tag v$VERSION already exists."
exit 1
fi
- name: Build the library
run: bunx turbo run build --filter=draftly
- name: Generate the release notes
id: notes
env:
VERSION: ${{ steps.version.outputs.version }}
NOTES_FROM: ${{ inputs.notes_from }}
run: |
set -euo pipefail
from="$NOTES_FROM"
if [ -z "$from" ]; then
from="$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || true)"
fi
if [ -z "$from" ]; then
from="$(git rev-list --max-parents=0 HEAD | tail -1)"
echo "::notice::No v* tag found; generating notes from the first commit ($from). Pass notes_from to narrow this."
fi
echo "from=$from" >> "$GITHUB_OUTPUT"
bun .github/scripts/release-notes.ts "$VERSION" "$from" HEAD > "$RUNNER_TEMP/release-notes.md"
{
echo "## Release notes for v$VERSION (since $from)"
echo
cat "$RUNNER_TEMP/release-notes.md"
} >> "$GITHUB_STEP_SUMMARY"
- name: Commit and tag
if: ${{ !inputs.dry_run }}
env:
VERSION: ${{ steps.version.outputs.version }}
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git diff --quiet -- packages/draftly/package.json; then
echo "Version unchanged; tagging the current commit."
else
git add packages/draftly/package.json
git commit -m "chore(release): draftly v$VERSION"
fi
git tag -a "$TAG" -m "draftly v$VERSION"
# Authenticated by **trusted publishing**: no npm token exists anywhere in this
# workflow. The job's OIDC identity proves to npm that this is `release.yml` in this
# repository, which must be registered under the package's Settings → Trusted
# publishing (owner, repo and workflow filename, all case-sensitive).
#
# Provenance is attested automatically because both the repository and the package are
# public — passing `--provenance` is unnecessary, and a stored token is what trusted
# publishing exists to remove. A token-based publish on a 2FA-enforced account fails
# with EOTP, and npm revoked the automation tokens that used to be the way around that.
- name: Publish to npm
if: ${{ !inputs.dry_run }}
env:
NPM_TAG: ${{ inputs.npm_tag }}
run: |
set -euo pipefail
# Below 11.5.1 the OIDC exchange is not attempted and npm fails with a misleading
# E404, so assert it rather than leave that to be debugged.
npm_version="$(npm --version)"
if [ "$(printf '%s\n11.5.1\n' "$npm_version" | sort -V | head -1)" != "11.5.1" ]; then
echo "::error::npm $npm_version is too old for trusted publishing; needs >= 11.5.1."
exit 1
fi
cd packages/draftly
npm publish --access public --tag "$NPM_TAG"
# Pushed only once npm has accepted the publish, so a failed publish leaves no
# release commit behind to unpick.
- name: Push the release commit and tag
if: ${{ !inputs.dry_run }}
env:
TAG: ${{ steps.version.outputs.tag }}
run: git push origin "HEAD:${GITHUB_REF_NAME}" "refs/tags/$TAG"
- name: Create the GitHub release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.tag }}
VERSION: ${{ steps.version.outputs.version }}
NPM_TAG: ${{ inputs.npm_tag }}
run: |
set -euo pipefail
args=(--title "draftly v$VERSION" --notes-file "$RUNNER_TEMP/release-notes.md" --verify-tag)
if [ "$NPM_TAG" != "latest" ]; then
args+=(--prerelease)
fi
gh release create "$TAG" "${args[@]}"
- name: Summarise
env:
VERSION: ${{ steps.version.outputs.version }}
PREVIOUS: ${{ steps.version.outputs.previous }}
FROM: ${{ steps.notes.outputs.from }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
{
echo
echo "---"
echo
if [ "$DRY_RUN" = "true" ]; then
echo "**Dry run.** Nothing was published, pushed or tagged."
else
echo "Published \`draftly@$VERSION\` (was \`$PREVIOUS\`), notes from \`$FROM\`."
fi
} >> "$GITHUB_STEP_SUMMARY"