Skip to content

Commit b263cdf

Browse files
authored
Disable automatic canary releases (#1300)
1 parent 5358eb7 commit b263cdf

4 files changed

Lines changed: 110 additions & 10 deletions

File tree

.github/workflows/deploy_docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
# SHA for security hardening. Points at 2.1.1 release
1919
uses: primer/.github/.github/workflows/deploy.yml@2a60f4cc62889bd218f3019ce4a090ff89b71cca
2020
with:
21-
node_version: 24.14.1
21+
node_version: 24
2222
install: npm ci
2323
build: |
2424
export NEXT_PUBLIC_SITE_TITLE='Primer'

.github/workflows/deploy_docs_preview.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# SHA for security hardening. Points at 2.2.0 release
1818
uses: primer/.github/.github/workflows/deploy_preview.yml@91fde6cc969a502562fdacdac48e35859b3a40b4
1919
with:
20-
node_version: 24.14.1
20+
node_version: 24
2121
install: npm ci
2222
build: |
2323
export NEXT_PUBLIC_SITE_TITLE='Primer'

.github/workflows/deploy_docs_preview_forks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# SHA for security hardening. Points at 2.2.0 release
1818
uses: primer/.github/.github/workflows/deploy_preview.yml@91fde6cc969a502562fdacdac48e35859b3a40b4
1919
with:
20-
node_version: 24.14.1
20+
node_version: 24
2121
install: npm ci
2222
build: npm run build
2323
output_dir: apps/next-docs/out/

.github/workflows/release.yml

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ name: Release
22

33
on:
44
push:
5-
# Ignore merge queue branches (matches primer/react configuration)
6-
branches-ignore:
7-
- 'gh-readonly-queue/**'
5+
# Release branches only.
6+
branches:
7+
- 'main'
8+
- 'next-minor'
9+
- 'changeset-release/main'
10+
- 'changeset-release/next-minor'
811
tags-ignore:
912
- '**'
13+
pull_request:
14+
# Canary releases are opt-in via the "release canary" label.
15+
types: [labeled]
1016
workflow_dispatch:
1117

1218
concurrency:
@@ -200,55 +206,149 @@ jobs:
200206
})
201207
202208
release-canary:
203-
if: github.repository == 'primer/brand' && github.ref_name != 'main' && github.ref_name != 'next-minor' && github.ref_name != 'changeset-release/main' && github.ref_name != 'changeset-release/next-minor'
204-
209+
# Runs only when all of the following are true:
210+
# - Event is a pull request label event for "release canary"
211+
# - PR is currently open
212+
# - PR branch comes from this repository (not a fork)
213+
# - PR head branch is not one of the release branches
214+
if: >-
215+
github.event_name == 'pull_request' &&
216+
github.repository == 'primer/brand' &&
217+
github.event.action == 'labeled' &&
218+
github.event.label.name == 'release canary' &&
219+
github.event.pull_request.state == 'open' &&
220+
github.event.pull_request.head.repo.full_name == github.repository &&
221+
github.head_ref != 'main' &&
222+
github.head_ref != 'next-minor' &&
223+
github.head_ref != 'changeset-release/main' &&
224+
github.head_ref != 'changeset-release/next-minor'
205225
name: Canary
206226
runs-on: ubuntu-latest
227+
permissions:
228+
id-token: write
229+
contents: write
230+
issues: write
231+
pull-requests: write
232+
checks: write
233+
statuses: write
207234
steps:
235+
# Re-check the live PR state and labels to prevent stale event payloads
236+
# or workflow re-runs.
237+
- name: Verify canary publish is valid
238+
id: canary-gate
239+
uses: actions/github-script@v4.0.2
240+
with:
241+
script: |
242+
const labelName = 'release canary'
243+
const {owner, repo} = context.repo
244+
const pull_number = context.payload.pull_request.number
245+
246+
const {data: pullRequest} = await github.pulls.get({
247+
owner,
248+
repo,
249+
pull_number
250+
})
251+
252+
const hasLabel = pullRequest.labels.some((label) => label.name === labelName)
253+
const isOpen = pullRequest.state === 'open'
254+
const shouldPublish = hasLabel && isOpen
255+
256+
core.setOutput('should_publish', shouldPublish ? 'true' : 'false')
257+
258+
if (shouldPublish) {
259+
core.info(`Canary publish is authorized for PR #${pull_number}`)
260+
return
261+
}
262+
263+
core.info(`Skipping canary publish because PR #${pull_number} is ${pullRequest.state} or missing label "${labelName}"`)
264+
208265
- name: Checkout repository
266+
if: steps.canary-gate.outputs.should_publish == 'true'
209267
uses: actions/checkout@v4
210268
with:
211-
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
269+
# Pin to the labeled-event head SHA so publish and status refer to the same commit.
270+
repository: ${{ github.event.pull_request.head.repo.full_name }}
271+
ref: ${{ github.event.pull_request.head.sha }}
212272
fetch-depth: 0
213273
persist-credentials: false
214274

215275
- name: Set up Node
276+
if: steps.canary-gate.outputs.should_publish == 'true'
216277
uses: actions/setup-node@v6
217278
with:
218279
node-version: 24.14.1
219280

220281
- name: Install dependencies
282+
if: steps.canary-gate.outputs.should_publish == 'true'
221283
run: npm ci
222284

223285
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42
286+
if: steps.canary-gate.outputs.should_publish == 'true'
224287
id: app-token
225288
with:
226289
app-id: ${{ vars.PRIMER_APP_ID_SHARED }}
227290
private-key: ${{ secrets.PRIMER_APP_PRIVATE_KEY_SHARED }}
228291

229292
- name: Build
293+
if: steps.canary-gate.outputs.should_publish == 'true'
230294
run: npm run build:lib
231295

232296
- name: Publish canary release
297+
id: publish-canary
298+
if: steps.canary-gate.outputs.should_publish == 'true'
233299
run: |
234300
npm exec --workspaces -- ../../packages/repo-configs/scripts/prepare-canary
235301
npx changeset publish --tag canary
236302
env:
237303
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
238304

239305
- name: Output canary version
306+
if: steps.canary-gate.outputs.should_publish == 'true'
240307
uses: actions/github-script@v4.0.2
241308
with:
242309
script: |
310+
const sha = context.payload.pull_request.head.sha
243311
const paths = ['react', 'design-tokens', 'css'].forEach((path) => {
244312
const package = require(`${process.env.GITHUB_WORKSPACE}/packages/${path}/package.json`)
245313
github.repos.createCommitStatus({
246314
owner: context.repo.owner,
247315
repo: context.repo.repo,
248-
sha: context.sha,
316+
sha,
249317
state: 'success',
250318
context: `Published ${package.name}`,
251319
description: package.version,
252320
target_url: `https://unpkg.com/${package.name}@${package.version}/`
253321
})
254322
})
323+
324+
- name: Remove canary label on success
325+
# Treat the label as a one-time publish request. Maintainers can
326+
# re-add it later when they explicitly want a fresh canary.
327+
if: always() && steps.canary-gate.outputs.should_publish == 'true' && steps.publish-canary.outcome == 'success'
328+
uses: actions/github-script@v4.0.2
329+
with:
330+
script: |
331+
const labelName = 'release canary'
332+
const {owner, repo} = context.repo
333+
const issue_number = context.payload.pull_request.number
334+
335+
try {
336+
await github.issues.removeLabel({
337+
owner,
338+
repo,
339+
issue_number,
340+
name: labelName
341+
})
342+
core.info(`Removed label "${labelName}" from PR #${issue_number}`)
343+
} catch (error) {
344+
if (error.status === 404) {
345+
core.info(`Label "${labelName}" was already removed from PR #${issue_number}`)
346+
return
347+
}
348+
349+
throw error
350+
}
351+
352+
- name: Skip canary publish
353+
if: steps.canary-gate.outputs.should_publish != 'true'
354+
run: echo 'Skipping canary because the PR is closed or no longer has the "release canary" label.'

0 commit comments

Comments
 (0)