diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index 66b6101..2990ff2 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -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 @@ -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"