-
Notifications
You must be signed in to change notification settings - Fork 90
356 lines (314 loc) · 14.6 KB
/
Copy pathrelease.yml
File metadata and controls
356 lines (314 loc) · 14.6 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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
name: Release
on:
workflow_dispatch:
inputs:
type:
description: 'Release type'
required: true
type: choice
options:
- pre-release
- release
version:
description: 'Version bump'
required: false
type: choice
default: patch
options:
- patch
- minor
- major
skip-windows-e2e:
description: 'Skip Windows E2E tests (they take a long time)'
required: false
type: boolean
default: false
skip-macos-e2e:
description: 'Skip macOS E2E tests (they take a long time)'
required: false
type: boolean
default: false
permissions:
contents: write
id-token: write
concurrency:
group: release
cancel-in-progress: false
jobs:
e2e-windows:
if: ${{ !inputs.skip-windows-e2e }}
uses: ./.github/workflows/e2e-windows.yml
e2e-macos:
if: ${{ !inputs.skip-macos-e2e }}
uses: ./.github/workflows/e2e-macos.yml
e2e-linux:
# Re-run the full Linux E2E matrix on the exact commit being released.
# ci.yml only runs on push/PR, so a manually-dispatched release (especially
# a pre-release from a feature branch) is not otherwise guaranteed to have
# had Linux E2E executed against this SHA.
uses: ./.github/workflows/e2e-linux.yml
conformance:
# Gate releases on MCP spec conformance so we never publish a version
# that regresses against the reference conformance suite.
uses: ./.github/workflows/conformance.yml
release:
name: Publish ${{ inputs.type }}
runs-on: ubuntu-latest
needs: [e2e-windows, e2e-macos, e2e-linux, conformance]
# Release must be from main; pre-release can be from any branch.
# Allow the e2e-windows and e2e-macos dependencies to be either successful
# or skipped (when the user opts out via skip-windows-e2e / skip-macos-e2e).
# Linux E2E and conformance must pass.
if: |
always() &&
(needs.e2e-windows.result == 'success' || needs.e2e-windows.result == 'skipped') &&
(needs.e2e-macos.result == 'success' || needs.e2e-macos.result == 'skipped') &&
needs.e2e-linux.result == 'success' &&
needs.conformance.result == 'success' &&
(inputs.type == 'pre-release' || github.ref == 'refs/heads/main')
steps:
- name: Validate inputs
run: |
if [[ "${{ inputs.type }}" == "release" && "${{ github.ref }}" != "refs/heads/main" ]]; then
echo "::error::Releases must be published from the main branch."
exit 1
fi
- uses: actions/checkout@v6
with:
fetch-depth: 0
# Use a PAT so the release job can push the version-bump
# commit and tag directly to main, bypassing branch-protection
# rulesets that block the default GITHUB_TOKEN.
token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Install pnpm and dependencies
uses: apify/actions/pnpm-install@v1.1.2
- name: Check dependency age
# Supply-chain gate: pnpm's `minimumReleaseAge` only applies when resolving new
# versions, and pnpm 10 does not re-check an existing lockfile on a
# --frozen-lockfile install, so without this nothing enforces the quarantine at
# release time. Runs before the version bump, tag, push and publish steps, so a
# failure costs nothing and leaves no partial release behind.
run: pnpm run check:deps-age
- name: Lint
run: pnpm run lint
- name: Build
run: pnpm run build
- name: Unit tests
run: pnpm run test:unit
- name: Update README
if: inputs.type == 'release'
run: pnpm run build:readme
- name: Update REFERENCE.md
if: inputs.type == 'release'
run: pnpm run build:reference
- name: Report install size
id: install-size
# Measures the tarball/unpacked/full-install size of what will be
# published and compares it with the latest version on npm: prints a
# table to the logs and run summary, warns if this release is much
# bigger, and exposes the report as an output that the GitHub release
# steps embed in the release body.
run: node scripts/report-install-size.mjs
# ── Pre-release ────────────────────────────────────────────────
- name: Bump pre-release version
if: inputs.type == 'pre-release'
id: prerelease
run: |
PKG_NAME=$(node -p "require('./package.json').name")
CURRENT=$(node -p "require('./package.json').version")
BUMP="${{ inputs.version }}"
# Compute the next base version according to the selected bump type
# e.g. 0.1.10 + minor -> 0.2.0, 0.1.10 + major -> 1.0.0, 0.1.10 + patch -> 0.1.11
# If already a beta, strip the pre-release suffix first
NEXT_BASE=$(node -p "
const base = '${CURRENT}'.replace(/-beta\..*/, '');
const v = base.split('.').map(Number);
if ('${BUMP}' === 'major') { v[0]++; v[1] = 0; v[2] = 0; }
else if ('${BUMP}' === 'minor') { v[1]++; v[2] = 0; }
else { v[2]++; }
v.join('.')
")
# If current version is already a beta for this base, don't re-bump
CURRENT_BASE=$(node -p "'${CURRENT}'.replace(/-beta\..*/, '')")
if [[ "${CURRENT}" == *"-beta."* ]]; then
# Already on a beta — check if it matches the target base
# e.g. current=0.2.0-beta.1, bump=minor from 0.1.10 -> NEXT_BASE=0.2.0 -> matches, keep it
# e.g. current=0.1.11-beta.1, bump=minor from 0.1.10 -> NEXT_BASE=0.2.0 -> mismatch, use NEXT_BASE
if [[ "$CURRENT_BASE" == "$NEXT_BASE" ]]; then
NEXT_BASE="$CURRENT_BASE"
fi
fi
echo "Base version for beta: $NEXT_BASE"
# Query npm for existing betas of this base version
LATEST_BETA=$(npm view "${PKG_NAME}" versions --json 2>/dev/null \
| node -p "
const all = JSON.parse(require('fs').readFileSync(0,'utf8'));
const betas = (Array.isArray(all) ? all : [all])
.filter(v => v.startsWith('${NEXT_BASE}-beta.'))
.sort((a,b) => {
const na = Number(a.split('-beta.')[1]);
const nb = Number(b.split('-beta.')[1]);
return na - nb;
});
betas.length ? betas[betas.length-1] : ''
" \
) || true
if [[ -n "$LATEST_BETA" && "$LATEST_BETA" == ${NEXT_BASE}-beta.* ]]; then
# Existing beta found — increment from it
echo "Found existing beta on npm: $LATEST_BETA — incrementing"
pnpm version "$LATEST_BETA" --no-git-tag-version --allow-same-version
pnpm version prerelease --preid=beta --no-git-tag-version
else
# No beta yet — start at beta.0
echo "No existing beta for ${NEXT_BASE} — creating ${NEXT_BASE}-beta.0"
pnpm version "${NEXT_BASE}-beta.0" --no-git-tag-version --allow-same-version
fi
NEW_VERSION=$(node -p "require('./package.json').version")
PKG_NAME=$(node -p "require('./package.json').name")
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "package-name=$PKG_NAME" >> "$GITHUB_OUTPUT"
# Print install instructions to the job logs AND the run summary so
# they are visible both while the run is in progress and on the run
# summary tab afterwards.
{
echo "### Pre-release version: \`$NEW_VERSION\`"
echo ""
echo "#### Install"
echo "\`\`\`bash"
echo "npm install -g ${PKG_NAME}@${NEW_VERSION}"
echo "# or"
echo "bun install -g ${PKG_NAME}@${NEW_VERSION}"
echo "\`\`\`"
} | tee -a "$GITHUB_STEP_SUMMARY"
- name: Publish pre-release to npm
if: inputs.type == 'pre-release'
run: MCPC_RELEASE=1 pnpm publish --access public --tag beta --provenance --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub pre-release
if: inputs.type == 'pre-release'
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.prerelease.outputs.version }}
name: v${{ steps.prerelease.outputs.version }}
prerelease: true
generate_release_notes: true
# Append install instructions to the auto-generated "What's Changed"
# so the release page shows users how to install this version.
append_body: true
body: |
## Install
```bash
npm install -g ${{ steps.prerelease.outputs.package-name }}@${{ steps.prerelease.outputs.version }}
# or
bun install -g ${{ steps.prerelease.outputs.package-name }}@${{ steps.prerelease.outputs.version }}
```
${{ steps.install-size.outputs.report }}
target_commitish: ${{ github.sha }}
env:
GITHUB_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
# ── Release ────────────────────────────────────────────────────
- name: Bump release version
if: inputs.type == 'release'
id: release
run: |
CURRENT=$(node -p "require('./package.json').version")
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
pnpm version "${{ inputs.version }}" --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
PKG_NAME=$(node -p "require('./package.json').name")
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "package-name=$PKG_NAME" >> "$GITHUB_OUTPUT"
# Print install instructions to the job logs AND the run summary so
# they are visible both while the run is in progress and on the run
# summary tab afterwards.
{
echo "### Release version: \`$NEW_VERSION\`"
echo ""
echo "#### Install"
echo "\`\`\`bash"
echo "npm install -g ${PKG_NAME}@${NEW_VERSION}"
echo "# or"
echo "bun install -g ${PKG_NAME}@${NEW_VERSION}"
echo "\`\`\`"
} | tee -a "$GITHUB_STEP_SUMMARY"
- name: Update CHANGELOG.md
if: inputs.type == 'release'
run: |
NEW_VERSION="${{ steps.release.outputs.version }}"
CURRENT="${{ steps.release.outputs.current }}"
TODAY=$(date +%Y-%m-%d)
# Replace [Unreleased] heading with new version, keep an empty [Unreleased] section
sed -i "s/^## \[Unreleased\]/## [Unreleased]\n\n## [$NEW_VERSION] - $TODAY/" CHANGELOG.md
# Update comparison links at the bottom
if grep -q "^\[Unreleased\]:" CHANGELOG.md; then
sed -i "s|\[Unreleased\]: \(.*\)/compare/v[0-9.]*\.\.\.HEAD|[Unreleased]: \1/compare/v$NEW_VERSION...HEAD\n[$NEW_VERSION]: \1/compare/v$CURRENT...v$NEW_VERSION|" CHANGELOG.md
fi
- name: Commit version bump and changelog
if: inputs.type == 'release'
run: |
NEW_VERSION="${{ steps.release.outputs.version }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add package.json pnpm-lock.yaml CHANGELOG.md README.md docs/REFERENCE.md docs/images/related-work.svg
git commit -m "v${NEW_VERSION}"
git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}"
git push origin main
git push origin "v${NEW_VERSION}"
- name: Publish release to npm
if: inputs.type == 'release'
run: MCPC_RELEASE=1 pnpm publish --access public --provenance --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub release
if: inputs.type == 'release'
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.release.outputs.version }}
name: v${{ steps.release.outputs.version }}
generate_release_notes: true
# Append install instructions to the auto-generated "What's Changed"
# so the release page shows users how to install this version.
append_body: true
body: |
## Install
```bash
npm install -g ${{ steps.release.outputs.package-name }}@${{ steps.release.outputs.version }}
# or
bun install -g ${{ steps.release.outputs.package-name }}@${{ steps.release.outputs.version }}
```
${{ steps.install-size.outputs.report }}
env:
GITHUB_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
# ── Homebrew tap ───────────────────────────────────────────────
- name: Homebrew formula update instructions
if: inputs.type == 'release'
# The formula bump in apify/homebrew-tap is started by hand for now, so
# this step only prints the command — it deliberately does not dispatch
# anything. To automate it later, run the printed command in this step
# (with GH_TOKEN: secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN) and keep it
# last and continue-on-error, so a Homebrew hiccup can never fail a
# release that is already tagged, published to npm and announced.
#
# The dispatched workflow points Formula/mcpc.rb at the new npm tarball,
# installs and `brew test`s it on Linux and macOS, then merges the bump.
# It polls the registry itself, so it tolerates publish propagation.
continue-on-error: true
env:
VERSION: ${{ steps.release.outputs.version }}
run: |
{
echo "### Homebrew"
echo ""
echo "The formula bump is manual for now. To ship \`${VERSION}\` to \`brew\`, run:"
echo ""
echo '```bash'
echo "gh workflow run update_formula.yaml --repo apify/homebrew-tap \\"
echo " --field package=mcpc --field npm_package=@apify/mcpc --field version=${VERSION}"
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY"