Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 69 additions & 20 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
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,
# tags the commit, and (for stable releases) creates a GitHub Release with the VSIX.
# 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
Expand Down Expand Up @@ -31,7 +36,7 @@ on:
type: boolean
default: false
dry_run:
description: "Dry run (skip publishing to VS Code Marketplace, but still commit/tag/release)"
description: "Dry run (skip publishing to VS Code Marketplace and Open VSX, but still commit/tag/release)"
required: false
type: boolean
default: true
Expand Down Expand Up @@ -60,6 +65,11 @@ jobs:

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:
Expand All @@ -71,18 +81,18 @@ jobs:
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: |
# Versioning convention:
# - Stable releases always use EVEN minor versions (e.g. 3.148.0, or patch 3.146.1).
# - Pre-releases always use ODD minor versions with the UTC date as patch (e.g. 3.147.20261105),
# because the VS Code Marketplace does not support semver pre-release suffixes like '-dev'.
# - Between releases, package.json on the default branch sits at the next EVEN minor version
# with a '-dev' suffix (e.g. 3.148.0-dev).
PKG_PUBLISHER=$(node -p "require('./package.json').publisher")
PKG_NAME=$(node -p "require('./package.json').name")
PKG_VERSION=$(node -p "require('./package.json').version")
Expand Down Expand Up @@ -120,10 +130,7 @@ jobs:
exit 1
fi

# Intentionally fail if the tag already exists rather than attempting complex in-place
# retry recovery after git push. If 'gh release create' fails after the tag is pushed,
# the release can be created manually from the pushed tag and uploaded VSIX workflow
# artifact (or the tag/commit can be deleted before re-running).
# 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
Expand All @@ -146,6 +153,8 @@ jobs:
} >> "$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 }}
Expand All @@ -161,6 +170,8 @@ jobs:
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:
Expand Down Expand Up @@ -191,6 +202,8 @@ jobs:

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

Expand All @@ -210,6 +223,8 @@ jobs:
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:
Expand All @@ -218,8 +233,11 @@ jobs:
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
id: publish_vscode
env:
DRY_RUN: ${{ inputs.dry_run }}
RELEASE_TYPE: ${{ inputs.release_type }}
Expand All @@ -240,6 +258,30 @@ jobs:
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 }}
Expand All @@ -253,6 +295,8 @@ jobs:
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:
Expand Down Expand Up @@ -282,17 +326,19 @@ jobs:
Release notes are available at [dartcode.org/releases/v$MAJOR_MINOR/](https://dartcode.org/releases/v$MAJOR_MINOR/)
EOF

# Tags pushed with GITHUB_TOKEN do not trigger on: push workflows automatically,
# so explicitly dispatch a standalone Run Tests workflow run for the new release tag.
# 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"

# Protected branches with a Merge Queue reject direct pushes from GITHUB_TOKEN,
# so open a PR to advance package.json to the next '-dev' 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:
Expand All @@ -314,9 +360,12 @@ jobs:
--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.outcome == 'success'
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. Because 'vsce publish' uses '--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')."
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')."
Loading
Loading