Skip to content
Merged
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
275 changes: 274 additions & 1 deletion .github/workflows/publish-extension.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,286 @@
# 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 ci

- 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 install
- run: npm ci

- name: Publish to VS Code Marketplace
uses: HaaLeo/publish-vscode-extension@v2
Expand All @@ -31,3 +300,7 @@ jobs:
# Open VSX Registry.
preRelease: ${{ github.event.action == 'prereleased' }}
extensionFile: ${{ steps.publishToVsCodeMarketplace.outputs.vsixPath }}

- name: Report Success
run: |
echo "✅ Successfully published to both marketplaces"