Publish Extension to VS Code Marketplace and Open VSX Registry #17
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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" |