Skip to content

Publish Extension to VS Code Marketplace and Open VSX Registry #17

Publish Extension to VS Code Marketplace and Open VSX Registry

Publish Extension to VS Code Marketplace and Open VSX Registry #17

# The auto package.json version update code is adapted from: https://github.com/valeryan/vscode-phpsab/blob/v0.0.21/.github/workflows/publish.yml
on:
release:
types: [prereleased, released]
env:
TAG_NAME: ${{ github.ref_name }}
TARGET_BRANCH: ${{ github.event.release.target_commitish }}
permissions:
contents: write
pull-requests: write
actions: read
name: Publish Extension to VS Code Marketplace and Open VSX Registry
jobs:
validate-release-version:
name: Validate Release Version
runs-on: ubuntu-latest
if: github.event_name == 'release'
outputs:
version: ${{ steps.parse-version.outputs.version }}
is-valid: ${{ steps.parse-version.outputs.is-valid }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Parse and Validate Version
id: parse-version
run: |
TAG_NAME="${{ env.TAG_NAME }}"
# Remove 'v' prefix if present and validate semver format
if [[ $TAG_NAME =~ ^v?([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?)$ ]]; then
VERSION=${BASH_REMATCH[1]}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is-valid=true" >> $GITHUB_OUTPUT
echo "✅ Valid version format: $VERSION"
else
echo "is-valid=false" >> $GITHUB_OUTPUT
echo "❌ Invalid version format: $TAG_NAME"
echo "Version must follow semver format (e.g., v1.0.0, 1.0.0, v1.0.0-beta.1)"
exit 1
fi
update-version:
name: Update Version
needs: validate-release-version
runs-on: ubuntu-latest
if: needs.validate-release-version.outputs.is-valid == 'true'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: "npm"
- name: Install Dependencies
run: npm install
- name: Update Package.json Version
run: |
VERSION="${{ needs.validate-release-version.outputs.version }}"
# If this is a prerelease, remove any prerelease tags to get base version
if [ "${{ github.event.action }}" = "prereleased" ]; then
BASE_VERSION=$(echo "$VERSION" | sed 's/-.*$//')
echo "🔄 Prerelease detected: using base version $BASE_VERSION instead of $VERSION"
VERSION="$BASE_VERSION"
fi
CURRENT_VERSION=$(node -p "require('./package.json').version")
if [ "$VERSION" = "$CURRENT_VERSION" ]; then
echo "ℹ️ Package.json already has version $VERSION, skipping update"
else
echo "📦 Updating package.json from $CURRENT_VERSION to $VERSION"
# Update version via "npm version" command without creating a git tag.
# This ensures package-lock.json is also updated as well as package.json.
npm version $VERSION --no-git-tag-version
echo "✅ Successfully updated package version to $VERSION"
fi
- name: Check for Changes
id: git-check
run: |
# Check for any modified or untracked files
if [ -n "$(git status --porcelain)" ]; then
echo "changes=true" >> $GITHUB_OUTPUT
echo "📝 Changes detected:"
git status --porcelain
else
echo "changes=false" >> $GITHUB_OUTPUT
echo "ℹ️ No changes to commit"
fi
- name: Create New Branch
if: steps.git-check.outputs.changes == 'true'
id: create-branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e # Exit on any error
BRANCH_NAME="release/${{ env.TAG_NAME }}"
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
# Check if branch already exists remotely
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
echo "❌ Cannot create $BRANCH_NAME branch as it already exists. This may indicate:"
echo " - A previous workflow run failed partway through"
echo " - The branch was manually created"
echo "Please manually delete the branch or check for an existing PR and resolve manually."
exit 1
fi
# Create and checkout new branch
git checkout -b "$BRANCH_NAME" || {
echo "❌ Failed to create branch $BRANCH_NAME"
exit 1
}
echo "✅ Created $BRANCH_NAME branch"
- name: Commit Changes
if: steps.git-check.outputs.changes == 'true'
id: commit-changes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e # Exit on any error
# Stage and commit changes
git add package.json package-lock.json || {
echo "❌ Failed to stage files"
exit 1
}
# Create commit message
COMMIT_MESSAGE="chore: version bump
git commit -m "$COMMIT_MESSAGE" || {
echo "❌ Failed to commit changes"
exit 1
}
echo "✅ Committed changes"
# Push to remote
BRANCH_NAME="${{ steps.create-branch.outputs.branch-name }}"
git push origin "$BRANCH_NAME" || {
echo "❌ Failed to push changes to remote"
exit 1
}
echo "✅ Pushed changes to remote on $BRANCH_NAME branch"
- name: Create Pull Request
if: steps.git-check.outputs.changes == 'true'
id: create-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e # Exit on any error
BRANCH_NAME="${{ steps.create-branch.outputs.branch-name }}"
echo "Creating pull request on $BRANCH_NAME branch into ${{ env.TARGET_BRANCH }}"
# Set PR title and body based on release type
RELEASE_TYPE="release"
if [ "${{ github.event.action }}" = "prereleased" ]; then
RELEASE_TYPE="pre-release"
fi
# Define PR details
# ^ capitalizes the first letter
PR_TITLE="${RELEASE_TYPE^} ${{ env.TAG_NAME }}"
PR_BODY="Automated version bump for $RELEASE_TYPE ${{ env.TAG_NAME }}."
PR_LABELS="auto version bump,release"
# Create pull request
PR_URL=$(gh pr create \
--base "${{ env.TARGET_BRANCH }}" \
--head "$BRANCH_NAME" \
--title "$PR_TITLE" \
--body "$PR_BODY" \
--label "$PR_LABELS") || {
echo "❌ Failed to create pull request"
exit 1
}
echo "✅ Pull request created successfully: $PR_URL"
# Extract PR number from URL and store as output
PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$')
echo "pr-number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr-url=$PR_URL" >> $GITHUB_OUTPUT
# Enable GitHub PR auto-merge
gh pr merge "$PR_URL" --auto --squash || {
echo "❌ Failed to enable auto-merge"
echo "This may be due to:"
echo " - Auto-merge not being enabled in repository settings"
echo " - Required status checks not configured"
echo " - Insufficient permissions"
exit 1
}
echo "✅ Enabled auto-merge for pull request"
- name: Wait for PR Merge
if: steps.git-check.outputs.changes == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ steps.create-pr.outputs.pr-number }}"
PR_URL="${{ steps.create-pr.outputs.pr-url }}"
echo "⏳ Waiting for pull request #$PR_NUMBER to be merged..."
# Wait for PR to be merged (timeout after 5 minutes)
TIMEOUT=300
ELAPSED=0
INTERVAL=5
while [ $ELAPSED -lt $TIMEOUT ]; do
# Get PR state and merge status in one call
PR_DATA=$(gh pr view "$PR_NUMBER" --json state,mergedAt 2>/dev/null || echo '{"state":"UNKNOWN","mergedAt":null}')
PR_STATE=$(echo "$PR_DATA" | jq -r '.state')
PR_MERGED_AT=$(echo "$PR_DATA" | jq -r '.mergedAt')
if [ "$PR_STATE" = "MERGED" ] || [ "$PR_MERGED_AT" != "null" ]; then
echo "✅ Pull request successfully merged"
break
fi
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
echo "⏳ Still waiting... (${ELAPSED}s elapsed)"
done
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "❌ Timeout waiting for pull request to merge"
exit 1
fi
- name: Update Tag Reference
if: steps.git-check.outputs.changes == 'true'
run: |
# Fetch the latest changes from target branch
git fetch origin ${{ env.TARGET_BRANCH }}
git checkout ${{ env.TARGET_BRANCH }}
git pull origin ${{ env.TARGET_BRANCH }}
# Move the existing tag to point to the new commit (preserves GitHub release relationship)
git tag ${{ env.TAG_NAME }} -f
git push origin ${{ env.TAG_NAME }} -f
echo "✅ Updated tag ${{ env.TAG_NAME }} to point to release commit"
deploy:
name: Publish Extension
needs: [validate-release-version, update-version]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.TARGET_BRANCH }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Publish to VS Code Marketplace
uses: HaaLeo/publish-vscode-extension@v2
id: publishToVsCodeMarketplace
with:
pat: ${{ secrets.VS_CODE_MARKETPLACE_TOKEN }}
# If the release is a prerelease, mark the extension as a prerelease in the
# VS Code marketplace.
preRelease: ${{ github.event.action == 'prereleased' }}
registryUrl: https://marketplace.visualstudio.com
- name: Publish to Open VSX Registry
uses: HaaLeo/publish-vscode-extension@v2
with:
pat: ${{ secrets.OPEN_VSX_REGISTRY_TOKEN }}
# If the release is a prerelease, mark the extension as a prerelease in the
# Open VSX Registry.
preRelease: ${{ github.event.action == 'prereleased' }}
extensionFile: ${{ steps.publishToVsCodeMarketplace.outputs.vsixPath }}
- name: Report Success
run: |
echo "✅ Successfully published to both marketplaces"