Repository navigation
371 lines (331 loc) · 17.7 KB
/
Copy pathrelease.yml
File metadata and controls
371 lines (331 loc) · 17.7 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
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
name: Release
run-name: Release ${{ inputs.release_type }}${{ inputs.version && format(' v{0}', inputs.version) || '' }}${{ inputs.dry_run && ' (dry run)' || '' }}
# Publishes a single release (pre-release or stable) to the VS Code Marketplace
# and Open VSX Registry, tags the commit, and (for stable releases) creates a
# GitHub Release with the VSIX.
#
# Documentation (in Dart-Code/Dart-Code, keep in sync when modifying this workflow):
# - https://github.com/Dart-Code/Dart-Code/blob/master/docs/releasing.md: Step-by-step instructions for performing a release.
# - https://github.com/Dart-Code/Dart-Code/blob/master/docs/release-process.md: Technical details on how this release workflow works.
#
# Note: When publishing a '.0' stable release (e.g. 3.148.0), the workflow opens a PR
# to bump package.json to the next '-dev' version (e.g. 3.150.0-dev). To publish the
# first pre-release for the next cycle (e.g. 3.149.YYYYMMDD), either merge that PR
# first (so the version is auto-calculated) or pass the version explicitly.
on:
workflow_dispatch:
inputs:
release_type:
description: "Release type (pre-release or stable)"
required: true
type: choice
options:
- pre-release
- stable
default: pre-release
version:
description: "Version to publish (X.Y.Z). Required for stable; leave blank for pre-release to auto-calculate."
required: false
type: string
default: ""
skip_ci_check:
description: "Skip verifying that CI checks on the target commit passed"
required: false
type: boolean
default: false
dry_run:
description: "Dry run (skip publishing to VS Code Marketplace and Open VSX, but still commit/tag/release)"
required: false
type: boolean
default: true
permissions:
contents: read
jobs:
release:
name: Release Extension
runs-on: ubuntu-latest
timeout-minutes: 15
environment: release
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
id-token: write
# actions: write is required to read CI runs (gh run list) and to dispatch the
# standalone post-release test workflow run (gh workflow run).
actions: write
steps:
# By default (when 'ref' is omitted), actions/checkout checks out the exact commit SHA
# (github.sha) that was at the tip of the selected branch when the workflow was triggered
# (whereas passing a branch name via 'ref' would resolve the branch head dynamically at
# checkout time). Leaving 'ref' unset guarantees that re-running a failed job always
# packages and tags the exact same commit.
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Set up Node v22
uses: actions/setup-node@v7
with:
node-version: 22
# Compute and validate the release version:
# - Stable releases require an explicit X.Y.Z version input with an EVEN minor version.
# - Pre-releases use an explicit version input or derive X.(Y-1).YYYYMMDD from X.Y.0-dev
# in package.json, and require an ODD minor version.
# - Verify the release tag (vX.Y.Z) does not already exist.
# - For '.0' stable releases, also compute the next '-dev' version (X.(Y+2).0-dev).
- name: Compute and validate version
id: version
env:
RELEASE_TYPE: ${{ inputs.release_type }}
INPUT_VERSION: ${{ inputs.version }}
run: |
PKG_PUBLISHER=$(node -p "require('./package.json').publisher")
PKG_NAME=$(node -p "require('./package.json').name")
PKG_VERSION=$(node -p "require('./package.json').version")
if [ -z "$INPUT_VERSION" ]; then
if [ "$RELEASE_TYPE" = "stable" ]; then
echo "::error::An explicit version (X.Y.Z) is required for stable releases. Re-run the workflow and provide the target version (e.g. 3.148.0)."
exit 1
fi
if [[ ! "$PKG_VERSION" =~ ^([0-9]+)\.([0-9]+)\.0-dev$ ]]; then
echo "::error::Cannot auto-calculate pre-release version because package.json version is '$PKG_VERSION' (expected 'X.Y.0-dev'). Update package.json or provide an explicit version input."
exit 1
fi
# Subtract 1 from the even '-dev' minor version in package.json to get the odd pre-release
# minor version, and use today's UTC date (YYYYMMDD) as the patch number.
VERSION="${BASH_REMATCH[1]}.$(( ${BASH_REMATCH[2]} - 1 )).$(date -u +%Y%m%d)"
else
VERSION="$INPUT_VERSION"
fi
if [[ ! "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "::error::Version '$VERSION' must match 'X.Y.Z' (three numeric components, e.g. 3.148.0)."
exit 1
fi
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
PATCH="${BASH_REMATCH[3]}"
if [ "$RELEASE_TYPE" = "stable" ] && [ $((MINOR % 2)) -ne 0 ]; then
echo "::error::Stable version '$VERSION' must have an EVEN minor version (found $MINOR). Check the version number or switch 'release_type' to 'pre-release'."
exit 1
fi
if [ "$RELEASE_TYPE" = "pre-release" ] && [ $((MINOR % 2)) -ne 1 ]; then
echo "::error::Pre-release version '$VERSION' must have an ODD minor version (found $MINOR). Check the version number or switch 'release_type' to 'stable'."
exit 1
fi
# Fail if the tag already exists rather than attempting in-place recovery after git push.
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "::error::Git tag 'v$VERSION' already exists. Check that the version number is correct, or if you are retrying a failed release, delete the existing 'v$VERSION' tag (and any partial GitHub Release) or create the GitHub Release manually from the pushed tag and uploaded VSIX artifact."
exit 1
fi
# After a '.0' stable minor release (e.g. 3.148.0), advance package.json to the next even
# minor '-dev' version (Y + 2, e.g. 3.150.0-dev). Patch releases (PATCH > 0) do not advance '-dev'.
NEXT_DEV_VERSION=""
if [ "$RELEASE_TYPE" = "stable" ] && [ "$PATCH" -eq 0 ]; then
NEXT_DEV_VERSION="${MAJOR}.$((MINOR + 2)).0-dev"
fi
VSIX="${PKG_NAME}-${VERSION}.vsix"
{
echo "version=$VERSION"
echo "vsix=$VSIX"
echo "pkg_publisher=$PKG_PUBLISHER"
echo "pkg_name=$PKG_NAME"
echo "next_dev_version=$NEXT_DEV_VERSION"
} >> "$GITHUB_OUTPUT"
echo "Prepared $RELEASE_TYPE v$VERSION ($VSIX)"
# Check that no pull requests are currently queued in the target branch's Merge Queue
# so a release isn't cut while changes are mid-merge.
- name: Verify Merge Queue is empty
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
TOTAL_COUNT=$(gh api graphql \
-f query='query($o:String!,$r:String!,$b:String!){repository(owner:$o,name:$r){mergeQueue(branch:$b){entries{totalCount}}}}' \
-f o="$REPO_OWNER" -f r="$REPO_NAME" -f b="$GITHUB_REF_NAME" \
--jq '.data.repository.mergeQueue.entries.totalCount // 0')
if [ "$TOTAL_COUNT" -gt 0 ]; then
echo "::error::Merge queue on '$GITHUB_REF_NAME' is not empty ($TOTAL_COUNT PR(s) currently queued). Wait for queued PRs to finish merging or remove them from the queue before releasing."
exit 1
fi
# Unless bypassed via skip_ci_check, verify that the test workflow (build-and-test.yml)
# has completed and passed on the target commit.
- name: Verify CI passed on target commit
if: inputs.skip_ci_check != true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TARGET_SHA=$(git rev-parse HEAD)
RUN_JSON=$(gh run list --workflow=build-and-test.yml --commit="$TARGET_SHA" --json event,status,conclusion,url \
--jq '[.[] | select(.event != "merge_group")][0]')
if [ -z "$RUN_JSON" ] || [ "$RUN_JSON" = "null" ]; then
echo "::error::No 'Run Tests' (build-and-test.yml) run found for commit $TARGET_SHA. Wait for CI to start and finish, or re-run the Release workflow with 'skip_ci_check' enabled to override."
exit 1
fi
RUN_STATUS=$(echo "$RUN_JSON" | jq -r '.status')
RUN_CONCLUSION=$(echo "$RUN_JSON" | jq -r '.conclusion')
RUN_URL=$(echo "$RUN_JSON" | jq -r '.url')
if [ "$RUN_STATUS" != "completed" ]; then
echo "::error::'Run Tests' on commit $TARGET_SHA is still in progress (status: $RUN_STATUS, $RUN_URL). Wait for it to complete and click 'Re-run failed jobs' on this release, or trigger a new Release run with 'skip_ci_check' enabled to override."
exit 1
fi
if [ "$RUN_CONCLUSION" != "success" ]; then
echo "::error::'Run Tests' on commit $TARGET_SHA did not pass (conclusion: $RUN_CONCLUSION, $RUN_URL). Fix or re-run the failed CI jobs, or trigger a new Release run with 'skip_ci_check' enabled to override."
exit 1
fi
echo "Verified 'Run Tests' succeeded on commit $TARGET_SHA ($RUN_URL)."
# Install dependencies, run the linter, update package.json/package-lock.json to the
# release version, and package the .vsix (flagged as pre-release when appropriate).
- name: npm ci
run: npm ci
- name: npm run lint
run: npm run lint
- name: Package extension (.vsix)
env:
RELEASE_TYPE: ${{ inputs.release_type }}
VERSION: ${{ steps.version.outputs.version }}
VSIX: ${{ steps.version.outputs.vsix }}
run: |
npm version "$VERSION" --no-git-tag-version
if [ "$RELEASE_TYPE" = "pre-release" ]; then
npx @vscode/vsce package --pre-release -o "$VSIX"
else
npx @vscode/vsce package -o "$VSIX"
fi
# Upload the packaged .vsix as a workflow artifact so it is preserved for inspection
# or manual recovery if a later step fails.
- name: Upload VSIX artifact
uses: actions/upload-artifact@v7
with:
name: ${{ steps.version.outputs.vsix }}
path: ${{ steps.version.outputs.vsix }}
overwrite: true
if-no-files-found: error
# Publish the packaged .vsix to the VS Code Marketplace using OIDC Trusted Publishing
# (--oidc). Passing --skip-duplicate ignores duplicate version errors so the job can be
# safely re-run if a subsequent step fails after Marketplace publication succeeds.
- name: Publish to VS Code Marketplace
id: publish_vscode
env:
DRY_RUN: ${{ inputs.dry_run }}
RELEASE_TYPE: ${{ inputs.release_type }}
VSIX: ${{ steps.version.outputs.vsix }}
run: |
PRE_FLAG=""
if [ "$RELEASE_TYPE" = "pre-release" ]; then
PRE_FLAG="--pre-release"
fi
if [ "$DRY_RUN" = "true" ]; then
if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then
echo "::error::OIDC token environment variables are missing (check id-token: write permission)."
exit 1
fi
echo "[Dry Run] Would execute: npx @vscode/vsce publish --oidc --skip-duplicate $PRE_FLAG --packagePath $VSIX"
else
npx @vscode/vsce publish --oidc --skip-duplicate $PRE_FLAG --packagePath "$VSIX"
fi
# Publish the packaged .vsix to the Open VSX Registry using OIDC Trusted Publishing
# (--trusted-publishing). Passing --skip-duplicate ignores duplicate version errors so the job
# can be safely re-run if a subsequent step fails after Open VSX publication succeeds.
# Note: --pre-release is not passed here because ovsx reads the PreRelease flag embedded in the
# .vsix manifest by 'vsce package' (passing --pre-release with a prepackaged .vsix logs a warning).
- name: Publish to Open VSX
id: publish_openvsx
env:
DRY_RUN: ${{ inputs.dry_run }}
VSIX: ${{ steps.version.outputs.vsix }}
run: |
if [ "$DRY_RUN" = "true" ]; then
if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then
echo "::error::OIDC token environment variables are missing (check id-token: write permission)."
exit 1
fi
echo "[Dry Run] Would execute: npx ovsx publish --trusted-publishing --skip-duplicate --packagePath $VSIX"
else
npx ovsx publish --trusted-publishing --skip-duplicate --packagePath "$VSIX"
fi
# Commit the updated package.json/package-lock.json and push the release tag (refs/tags/vX.Y.Z).
# Because the default branch is protected by a Merge Queue, we push only the tag rather than
# pushing directly to the branch; the tag permanently retains the release commit in the repo.
- name: Commit and push tag
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
# Use the standard GitHub Actions bot identity (user ID 41898282) so commits are
# attributed to github-actions[bot] on GitHub.
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -am "Publish v$VERSION"
git tag "v$VERSION"
git push origin "refs/tags/v$VERSION"
# Create a GitHub Release for stable releases with the .vsix attached and release notes
# linking to the VS Code Marketplace and dartcode.org.
- name: Create GitHub Release (stable only)
if: inputs.release_type == 'stable'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
VSIX: ${{ steps.version.outputs.vsix }}
PKG_PUBLISHER: ${{ steps.version.outputs.pkg_publisher }}
PKG_NAME: ${{ steps.version.outputs.pkg_name }}
run: |
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2 | tr '.' '-')
if [ "$PKG_NAME" = "dart-code" ]; then
RELEASE_TITLE="Dart Code v$VERSION"
INSTALL_TEXT="Dart Code"
elif [ "$PKG_NAME" = "flutter" ]; then
RELEASE_TITLE="v$VERSION"
INSTALL_TEXT="The Flutter extension"
else
RELEASE_TITLE="v$VERSION"
INSTALL_TEXT="The $PKG_NAME extension"
fi
sed 's/^ //' <<EOF | gh release create "v$VERSION" "$VSIX" --title "$RELEASE_TITLE" --notes-file -
## Installation
$INSTALL_TEXT can be [installed from the Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=$PKG_PUBLISHER.$PKG_NAME).
Release notes are available at [dartcode.org/releases/v$MAJOR_MINOR/](https://dartcode.org/releases/v$MAJOR_MINOR/)
EOF
# Because tags pushed by GITHUB_TOKEN do not trigger 'on: push' workflows automatically,
# explicitly dispatch a run of build-and-test.yml against the new release tag to run the
# full test matrix across all OSes and SDK channels.
- name: Trigger full test suite on release tag (stable only)
if: inputs.release_type == 'stable'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
run: gh workflow run build-and-test.yml --ref "v$VERSION"
# For '.0' stable releases, branch from the commit prior to the 'Publish vX.Y.Z' commit
# (HEAD~1) and open a pull request to advance package.json to the next '-dev' version
# (since Merge Queue branch protection prevents pushing directly to the base branch).
- name: Open PR for next -dev version (stable .0 releases only)
if: steps.version.outputs.next_dev_version != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
NEXT_DEV_VERSION: ${{ steps.version.outputs.next_dev_version }}
run: |
BRANCH_NAME="version-bump/$NEXT_DEV_VERSION"
git checkout -B "$BRANCH_NAME" HEAD~1
npm version "$NEXT_DEV_VERSION" --no-git-tag-version
git commit -am "Set version to $NEXT_DEV_VERSION"
git push --force origin "HEAD:refs/heads/$BRANCH_NAME"
if ! gh pr view "$BRANCH_NAME" >/dev/null 2>&1; then
gh pr create \
--base "$GITHUB_REF_NAME" \
--head "$BRANCH_NAME" \
--title "Set version to $NEXT_DEV_VERSION" \
--body "Automated version bump after releasing v$VERSION."
fi
# If any step fails after the extension has already been published to the VS Code Marketplace
# or Open VSX, log recovery instructions reminding the maintainer that the job can be safely
# re-run because both publish steps ignore duplicate versions.
- name: Print retry instructions on failure
if: failure() && (steps.publish_vscode.outcome == 'success' || steps.publish_openvsx.outcome == 'success')
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
echo "::warning::A step failed after v$VERSION was published to the VS Code Marketplace and/or Open VSX. Because 'vsce publish' and 'ovsx publish' both use '--skip-duplicate', it is safe to re-run this job (if the tag 'v$VERSION' or GitHub Release was already created, delete it first with 'gh release delete v$VERSION --yes' / 'git push --delete origin v$VERSION')."