Skip to content

Commit ffec413

Browse files
authored
chore: add release.yml with trusted publishing for stable releases (typescript-eslint#11566)
1 parent 0daf303 commit ffec413

File tree

2 files changed

+253
-37
lines changed

2 files changed

+253
-37
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# NOTE: The name of this workflow is significant - it is used as the identifier for the workflow_run trigger in the release workflow
12
name: CI
23

34
on:
@@ -289,40 +290,3 @@ jobs:
289290
files: coverage/**/lcov.info
290291
flags: unittest
291292
name: codecov
292-
293-
publish_canary_version:
294-
name: Publish the latest code as a canary version
295-
environment: ${{ (github.repository == 'typescript-eslint/typescript-eslint' && github.ref == 'refs/heads/main') && 'main' || '' }} # Have to specify per job
296-
runs-on: ubuntu-latest
297-
permissions:
298-
id-token: write
299-
needs: [integration_tests, lint_with_build, lint_without_build, unit_tests]
300-
if: github.repository == 'typescript-eslint/typescript-eslint' && github.ref == 'refs/heads/main'
301-
steps:
302-
- name: Checkout
303-
uses: actions/checkout@v4
304-
with:
305-
fetch-depth: 0 # we need the tags to be available
306-
307-
- name: Install
308-
uses: ./.github/actions/prepare-install
309-
with:
310-
node-version: ${{ env.PRIMARY_NODE_VERSION }}
311-
registry-url: 'https://registry.npmjs.org'
312-
313-
# 11.5.2 and later required for trusted publishing
314-
- name: Use npm 11.5.2
315-
run: npm install -g npm@11.5.2
316-
317-
- name: Build
318-
uses: ./.github/actions/prepare-build
319-
320-
- name: Figure out and apply the next canary version
321-
run: npx tsx tools/release/apply-canary-version.mts
322-
323-
- name: Publish all packages to npm with the canary tag
324-
# NOTE: this needs to be npx, rather than yarn, to make sure the authenticated npm registry is used
325-
run: npx nx release publish --tag canary --verbose
326-
env:
327-
NX_CLOUD_DISTRIBUTED_EXECUTION: false
328-
NPM_CONFIG_PROVENANCE: true

.github/workflows/release.yml

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
name: Release
2+
3+
on:
4+
# Triggered by completed CI runs (we check for successful status in the validate job) on main branch for canary releases
5+
workflow_run:
6+
workflows: ['CI']
7+
types: [completed]
8+
branches: [main]
9+
10+
schedule:
11+
# Github actions do not currently support specifying a timezone.
12+
# Run on Mondays at 5pm UTC (1pm Eastern (Summer) Time)
13+
- cron: '0 17 * * 1'
14+
15+
# Manual trigger for out of band releases and next major version prereleases
16+
workflow_dispatch:
17+
inputs:
18+
release_type:
19+
description: 'Type of release to perform (stable requires core team approval)'
20+
required: true
21+
type: choice
22+
options:
23+
- canary
24+
- stable
25+
default: 'canary'
26+
override_major_version:
27+
description: 'Override major version for canary releases'
28+
required: false
29+
type: string
30+
dry_run:
31+
description: 'Perform a dry run (stable releases only)'
32+
required: false
33+
type: boolean
34+
default: true
35+
force-release-without-changes:
36+
description: 'Whether to do a release regardless of if there have been changes'
37+
required: false
38+
type: boolean
39+
default: false
40+
41+
# Ensure only one release workflow runs at a time
42+
concurrency:
43+
group: release
44+
cancel-in-progress: false
45+
46+
env:
47+
PRIMARY_NODE_VERSION: 20
48+
49+
# Minimal permissions by default
50+
permissions:
51+
contents: read
52+
53+
jobs:
54+
# Validation job to ensure secure inputs and determine release type
55+
validate:
56+
name: Validate Release Parameters
57+
runs-on: ubuntu-latest
58+
# Only run on the official repository to avoid wasted compute and unnecessary errors on forks (also an initial albeit weak first layer of protection against unauthorized releases)
59+
if: github.repository == 'typescript-eslint/typescript-eslint'
60+
outputs:
61+
should_release: ${{ steps.validate.outputs.should_release }}
62+
release_type: ${{ steps.validate.outputs.release_type }}
63+
is_canary: ${{ steps.validate.outputs.is_canary }}
64+
is_stable: ${{ steps.validate.outputs.is_stable }}
65+
dry_run: ${{ steps.validate.outputs.dry_run }}
66+
force_release_without_changes: ${{ steps.validate.outputs.force_release_without_changes }}
67+
override_major_version: ${{ steps.validate.outputs.override_major_version }}
68+
steps:
69+
- name: Validate inputs and determine release type
70+
id: validate
71+
env:
72+
# Ensure user input is treated as data by passing them as environment variables
73+
INPUT_RELEASE_TYPE: ${{ inputs.release_type }}
74+
INPUT_OVERRIDE_MAJOR: ${{ inputs.override_major_version }}
75+
INPUT_DRY_RUN: ${{ inputs.dry_run }}
76+
INPUT_FORCE_RELEASE: ${{ inputs.force_release_without_changes }}
77+
run: |
78+
SHOULD_RELEASE="false"
79+
80+
# Determine release type based on trigger
81+
if [[ "${{ github.event_name }}" == "schedule" ]]; then
82+
RELEASE_TYPE="stable"
83+
SHOULD_RELEASE="true"
84+
elif [[ "${{ github.event_name }}" == "workflow_run" ]]; then
85+
# Only release canary if the CI workflow succeeded
86+
if [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]]; then
87+
RELEASE_TYPE="canary"
88+
SHOULD_RELEASE="true"
89+
else
90+
echo "CI workflow did not succeed, skipping canary release"
91+
RELEASE_TYPE="canary"
92+
SHOULD_RELEASE="false"
93+
fi
94+
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
95+
RELEASE_TYPE="$INPUT_RELEASE_TYPE"
96+
SHOULD_RELEASE="true"
97+
else
98+
echo "::error::Unsupported trigger event: ${{ github.event_name }}"
99+
exit 1
100+
fi
101+
102+
# Validate release type
103+
if [[ "$RELEASE_TYPE" != "canary" && "$RELEASE_TYPE" != "stable" ]]; then
104+
echo "::error::Invalid release type: $RELEASE_TYPE. Must be 'canary' or 'stable'"
105+
exit 1
106+
fi
107+
108+
# Set outputs
109+
echo "should_release=$SHOULD_RELEASE" >> $GITHUB_OUTPUT
110+
echo "release_type=$RELEASE_TYPE" >> $GITHUB_OUTPUT
111+
echo "is_canary=$([[ "$RELEASE_TYPE" == "canary" ]] && echo "true" || echo "false")" >> $GITHUB_OUTPUT
112+
echo "is_stable=$([[ "$RELEASE_TYPE" == "stable" ]] && echo "true" || echo "false")" >> $GITHUB_OUTPUT
113+
114+
# Handle dry run for stable releases
115+
if [[ "$RELEASE_TYPE" == "stable" ]]; then
116+
if [[ "${{ github.event_name }}" == "schedule" ]]; then
117+
# Scheduled releases are never dry runs
118+
echo "dry_run=false" >> $GITHUB_OUTPUT
119+
else
120+
# Manual stable releases default to dry run unless explicitly disabled
121+
echo "dry_run=${INPUT_DRY_RUN:-true}" >> $GITHUB_OUTPUT
122+
fi
123+
else
124+
echo "dry_run=false" >> $GITHUB_OUTPUT
125+
fi
126+
127+
# Handle force release without changes for stable releases
128+
if [[ "$RELEASE_TYPE" == "stable" && "${{ github.event_name }}" == "workflow_dispatch" ]]; then
129+
echo "force_release_without_changes=${INPUT_FORCE_RELEASE:-false}" >> $GITHUB_OUTPUT
130+
else
131+
echo "force_release_without_changes=false" >> $GITHUB_OUTPUT
132+
fi
133+
134+
# Validate and handle override major version for canary releases
135+
if [[ "$RELEASE_TYPE" == "canary" && "${{ github.event_name }}" == "workflow_dispatch" && -n "$INPUT_OVERRIDE_MAJOR" ]]; then
136+
if [[ ! "$INPUT_OVERRIDE_MAJOR" =~ ^[0-9]+$ ]]; then
137+
echo "::error::Invalid override major version format: $INPUT_OVERRIDE_MAJOR. Must be a positive integer."
138+
exit 1
139+
fi
140+
echo "override_major_version=$INPUT_OVERRIDE_MAJOR" >> $GITHUB_OUTPUT
141+
else
142+
echo "override_major_version=" >> $GITHUB_OUTPUT
143+
fi
144+
145+
echo "Validated release configuration:"
146+
echo "- Should release: $SHOULD_RELEASE"
147+
echo "- Release type: $RELEASE_TYPE"
148+
echo "- Is canary: $([[ "$RELEASE_TYPE" == "canary" ]] && echo "true" || echo "false")"
149+
echo "- Is stable: $([[ "$RELEASE_TYPE" == "stable" ]] && echo "true" || echo "false")"
150+
if [[ "$RELEASE_TYPE" == "stable" ]]; then
151+
echo "- Dry run: ${INPUT_DRY_RUN:-true}"
152+
echo "- Force release without changes: ${INPUT_FORCE_RELEASE:-false}"
153+
fi
154+
if [[ "$RELEASE_TYPE" == "canary" && -n "$INPUT_OVERRIDE_MAJOR" ]]; then
155+
echo "- Override major version: $INPUT_OVERRIDE_MAJOR"
156+
fi
157+
158+
# Do not require npm-registry environment (and therefore manual approvals) for canary releases
159+
# npm trusted publishing should already go a long way to protecting against unauthorized releases
160+
canary_release:
161+
name: Publish Canary Release
162+
runs-on: ubuntu-latest
163+
needs: [validate]
164+
# Only run on the official repository to avoid wasted compute and unnecessary errors on forks (also an initial albeit weak first layer of protection against unauthorized releases)
165+
# Also ensure validation passed and we're releasing a canary version
166+
if: github.repository == 'typescript-eslint/typescript-eslint' && needs.validate.outputs.should_release == 'true' && needs.validate.outputs.is_canary == 'true'
167+
permissions:
168+
contents: read # No need to write to the repository for canary releases
169+
id-token: write # Required for trusted publishing
170+
steps:
171+
- name: Checkout
172+
uses: actions/checkout@v4
173+
with:
174+
# We need the full history for version calculation
175+
fetch-depth: 0
176+
177+
- name: Install dependencies
178+
uses: ./.github/actions/prepare-install
179+
with:
180+
node-version: ${{ env.PRIMARY_NODE_VERSION }}
181+
registry-url: 'https://registry.npmjs.org'
182+
183+
# Use specific npm version required for trusted publishing
184+
- name: Use npm 11.5.2
185+
run: npm install -g npm@11.5.2
186+
187+
- name: Build packages
188+
uses: ./.github/actions/prepare-build
189+
190+
- name: Calculate and apply canary version
191+
run: npx tsx tools/release/apply-canary-version.mts
192+
env:
193+
# Use the validated override major version from the validate job, if set
194+
OVERRIDE_MAJOR_VERSION: ${{ needs.validate.outputs.override_major_version }}
195+
196+
- name: Publish canary packages
197+
# NOTE: this needs to be npx, rather than yarn, to make sure the authenticated npm registry is used
198+
run: npx nx release publish --tag canary --verbose
199+
env:
200+
# Enable npm provenance
201+
NPM_CONFIG_PROVENANCE: true
202+
# Disable distributed execution here for predictability
203+
NX_CLOUD_DISTRIBUTED_EXECUTION: false
204+
205+
stable_release:
206+
name: Publish Stable Release
207+
runs-on: ubuntu-latest
208+
environment: npm-registry # Require core team approvals for stable releases as an ultimate layer of protection against unauthorized releases
209+
needs: [validate]
210+
# Only run on the official repository to avoid wasted compute and unnecessary errors on forks (also an initial albeit weak first layer of protection against unauthorized releases)
211+
# Also ensure validation passed and we're releasing a stable version
212+
if: github.repository == 'typescript-eslint/typescript-eslint' && needs.validate.outputs.should_release == 'true' && needs.validate.outputs.is_stable == 'true'
213+
permissions:
214+
contents: write # Need to create releases and push tags
215+
id-token: write # Required for trusted publishing
216+
steps:
217+
- name: Checkout
218+
uses: actions/checkout@v4
219+
with:
220+
# Need full history for changelog generation
221+
fetch-depth: 0
222+
223+
- name: Install dependencies
224+
uses: ./.github/actions/prepare-install
225+
with:
226+
node-version: ${{ env.PRIMARY_NODE_VERSION }}
227+
registry-url: 'https://registry.npmjs.org'
228+
229+
# Use specific npm version required for trusted publishing
230+
- name: Use npm 11.5.2
231+
run: npm install -g npm@11.5.2
232+
233+
- name: Build packages
234+
uses: ./.github/actions/prepare-build
235+
236+
- name: Configure git user for automated commits
237+
run: |
238+
git config --global user.email "typescript-eslint[bot]@users.noreply.github.com"
239+
git config --global user.name "typescript-eslint[bot]"
240+
241+
- name: Run stable release
242+
run: npx tsx tools/release/release.mts --dry-run=${{ needs.validate.outputs.dry_run }} --force-release-without-changes=${{ needs.validate.outputs.force_release_without_changes }} --verbose
243+
env:
244+
# Enable npm provenance
245+
NPM_CONFIG_PROVENANCE: true
246+
# Disable distributed execution here for predictability
247+
NX_CLOUD_DISTRIBUTED_EXECUTION: false
248+
249+
- name: Force update the website branch to match the latest release
250+
run: |
251+
git branch -f website
252+
git push -f origin website

0 commit comments

Comments
 (0)