Skip to content

Commit 85c6b3a

Browse files
[release] use changesets/action for canary release (vercel#79038)
This PR uses `changesets/action` to handle the canary release. Since the action creates a Version Packages PR by default, I added a GH Action that checks if the PR is released from the correct release workflow. It triple-verifies via the user login (`vercel-release-bot`), PR title (`Version Packages (canary)`), and a label (`created-by: CI`) to ensure the opened PR is not trying to spoof. After the validation, the PR bypasses required checks, is merged into the canary branch, and proceeds with the rest of the release process. ### Testing Plan Try running `ci:version` and `ci:publish` locally, and handle the release type via env `RELEASE_TYPE`. Confirmed the CI force merge on devjiwonchoi/lab-releases#13 --------- Co-authored-by: Sebastian "Sebbie" Silbermann <sebastian.silbermann@vercel.com>
1 parent f3f6b6c commit 85c6b3a

7 files changed

+105
-7
lines changed

.github/workflows/build_and_deploy.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ env:
2020
# See https://doc.rust-lang.org/rustc/platform-support/apple-darwin.html#os-version for more details
2121
MACOSX_DEPLOYMENT_TARGET: 11.0
2222
# This will become "true" if the latest commit is
23-
# "Version Packages", set from check-is-release.js
23+
# "Version Packages" or "Version Pacakges (canary)"
24+
# set from scripts/check-is-release.js
2425
__NEW_RELEASE: 'false'
2526

2627
jobs:
@@ -49,7 +50,7 @@ jobs:
4950
run: |
5051
# TODO: Remove the new release check once the new release workflow is fully replaced.
5152
RELEASE_CHECK=$(node ./scripts/check-is-release.js 2> /dev/null || :)
52-
if [[ $RELEASE_CHECK =~ ^Version\ Packages$ ]];
53+
if [[ $RELEASE_CHECK =~ ^Version\ Packages(\ \(canary\))?$ ]];
5354
then
5455
echo "__NEW_RELEASE=true" >> $GITHUB_ENV
5556
elif [[ $RELEASE_CHECK = v* ]];
@@ -610,6 +611,14 @@ jobs:
610611
env:
611612
GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
612613
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
614+
RELEASE_TYPE: ${{ github.event.inputs.releaseType }}
615+
616+
# Add label to verify the PR is created from this workflow.
617+
- name: Add label to PR
618+
if: steps.changesets.outputs.pullRequestNumber
619+
run: 'gh pr edit ${{ steps.changesets.outputs.pullRequestNumber }} --add-label "created-by: CI"'
620+
env:
621+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
613622

614623
- name: Upload npm log artifact
615624
uses: actions/upload-artifact@v4
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Force Merge Canary Release PR
2+
3+
on: pull_request
4+
5+
permissions:
6+
# To bypass and merge PR
7+
pull-requests: write
8+
9+
jobs:
10+
force-merge-canary-release-pr:
11+
runs-on: ubuntu-latest
12+
# Validate the login, PR title, and the label to ensure the PR is
13+
# from the release PR and prevent spoofing.
14+
if: |
15+
github.event.pull_request.user.login == 'vercel-release-bot' &&
16+
github.event.pull_request.title == 'Version Packages (canary)' &&
17+
contains(github.event.pull_request.labels.*.name, 'created-by: CI')
18+
steps:
19+
- name: Bypass required status checks and merge PR
20+
run: gh pr merge --admin --squash "$PR_URL"
21+
env:
22+
PR_URL: ${{github.event.pull_request.html_url}}
23+
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}

.github/workflows/trigger_release_new.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
name: Trigger Release (New)
22

33
on:
4+
# Run every day at 23:15 UTC
5+
# TODO: Disabled cron for now, but uncomment
6+
# once replaced the old release workflow.
7+
# schedule:
8+
# - cron: '15 23 * * *'
9+
# Run manually
410
workflow_dispatch:
511
inputs:
612
releaseType:
713
description: Release Type
814
required: true
915
type: choice
16+
# Cron job will run canary release
17+
default: canary
1018
options:
11-
# TODO: Follow up enable the case.
12-
# - canary
19+
- canary
1320
- stable
1421
# TODO: Follow up enable the case.
1522
# - release-candidate
@@ -26,6 +33,10 @@ env:
2633
TURBO_VERSION: 2.3.3
2734
NODE_LTS_VERSION: 20
2835

36+
permissions:
37+
# To create PR
38+
pull-requests: write
39+
2940
jobs:
3041
start:
3142
if: github.repository_owner == 'vercel'

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"lerna": "lerna",
1414
"dev": "turbo run dev --parallel",
1515
"pack-next": "tsx scripts/pack-next.ts",
16-
"ci:version": "changeset version && pnpm install --no-frozen-lockfile",
17-
"ci:publish": "changeset publish",
16+
"ci:version": "tsx ./scripts/release/version-packages.ts",
17+
"ci:publish": "tsx ./scripts/release/publish-npm.ts",
1818
"test-types": "tsc",
1919
"test-unit": "jest test/unit/ packages/next/ packages/font",
2020
"test-dev": "cross-env NEXT_TEST_MODE=dev pnpm testheadless",

scripts/check-is-release.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const checkIsRelease = async () => {
1313

1414
const versionString = commitMsg.split(' ').pop().trim()
1515
const publishMsgRegex = /^v\d{1,}\.\d{1,}\.\d{1,}(-\w{1,}\.\d{1,})?$/
16-
const newPublishMsgRegex = /^Version Packages$/
16+
const newPublishMsgRegex = /^Version Packages( \(canary\))?$/
1717

1818
if (publishMsgRegex.test(versionString)) {
1919
console.log(versionString)

scripts/release/publish-npm.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import execa from 'execa'
2+
3+
async function publishNpm() {
4+
const releaseType = process.env.RELEASE_TYPE
5+
const tag =
6+
releaseType === 'canary'
7+
? 'canary'
8+
: releaseType === 'release-candidate'
9+
? 'rc'
10+
: 'latest'
11+
12+
await execa('pnpm', ['changeset', 'publish', '--tag', tag], {
13+
stdio: 'inherit',
14+
})
15+
}
16+
17+
publishNpm()

scripts/release/version-packages.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import execa from 'execa'
2+
import { existsSync } from 'node:fs'
3+
import { join } from 'node:path'
4+
5+
async function versionPackages() {
6+
const preConfigPath = join(process.cwd(), '.changeset', 'pre.json')
7+
8+
// Exit previous pre mode to prepare for the next release.
9+
if (existsSync(preConfigPath)) {
10+
if (require(preConfigPath).mode !== 'exit') {
11+
// Since current repository is in pre mode, need
12+
// to exit before versioning the packages.
13+
await execa('pnpm', ['changeset', 'pre', 'exit'], {
14+
stdio: 'inherit',
15+
})
16+
}
17+
}
18+
19+
const releaseType = process.env.RELEASE_TYPE
20+
21+
if (releaseType === 'canary') {
22+
// Enter pre mode as "canary" tag.
23+
await execa('pnpm', ['changeset', 'pre', 'enter', 'canary'], {
24+
stdio: 'inherit',
25+
})
26+
// TODO: Create empty changeset for `next` to bump canary version
27+
// even if there is no changeset.
28+
}
29+
30+
await execa('pnpm', ['changeset', 'version'], {
31+
stdio: 'inherit',
32+
})
33+
await execa('pnpm', ['install', '--no-frozen-lockfile'], {
34+
stdio: 'inherit',
35+
})
36+
}
37+
38+
versionPackages()

0 commit comments

Comments
 (0)