From f7279e2d8e0fc9c605b38d894eec8420b967320d Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Tue, 9 Dec 2025 03:47:06 +0000 Subject: [PATCH 1/2] feat: add automatic package.json version bump based on GitHub releases. - Added automatic package.json version bump to the publish extension workflow. It gets the release tag, validates that it's the correct format, removes any prerelease tags it may have (because vscode doesn't like prerelease tags in the extension versions), bump the version in package.json and package.lock with the version from the release tag, commits the changes, force pushes the tag reference to point to the new commit, and then publish the extension to the marketplaces. --- .github/workflows/publish-extension.yml | 126 +++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index 66b6101..a5293d3 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -1,17 +1,141 @@ +# 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 + 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" + 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: Commit and Push All Changes + if: steps.git-check.outputs.changes == 'true' + run: | + git add package.json package-lock.json + git commit -m "chore: version bump ${{ env.TAG_NAME }} + + - Update version to ${{ needs.validate-release-version.outputs.version }}" + git push origin HEAD:${{ env.TARGET_BRANCH }} + + - name: Update Tag Reference + if: steps.git-check.outputs.changes == 'true' + run: | + # 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 From 501d793b48b1ca5ecc53f3a145644d9655125a2b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 8 Jan 2026 00:07:40 +0000 Subject: [PATCH 2/2] feat: enhance version update workflow with branch and PR creation - Added a new workflow step to create a new branch that the package.json version bump will be committed to. The branch will be named in the form of "release/[version]". - Refactored the Commit Changes step to include error handling and push the new branch to remote. - Added a new workflow step to create a new PR from the newly created branch. It uses the GitHub's (gh) CLI commands to create the PR and merge it automatically onto the default branch (master) via the auto-merge strategy. By committing the version bump to a branch and using a PR to merge the changes onto the master branch, we prevent code being committed straight to master. And by using GitHub's auto-merge strategy, we ensure that the commits and PRs adhere to branch protection rules, if there are any. A new step after the creation will wait for any PR checks to be completed and the PR to be merged. It checks the state of the PR using gh commands to ensure the PR is merged. If the PR is merged within the allotted time (around 5 mins), the step succeeds and continues to the next, otherwise it will error out. - Refactored the Update Tag References step to pull the latest changes from the branch to ensure it is using the up to date commit. - Added new step to echo a success message after the extension was published to both marketplaces. --- .github/workflows/publish-extension.yml | 159 +++++++++++++++++++++++- 1 file changed, 154 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index a5293d3..2990ff2 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -10,6 +10,7 @@ env: permissions: contents: write + pull-requests: write actions: read name: Publish Extension to VS Code Marketplace and Open VSX Registry @@ -89,7 +90,11 @@ jobs: 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 @@ -106,18 +111,158 @@ jobs: echo "â„šī¸ No changes to commit" fi - - name: Commit and Push All Changes + - 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: | - git add package.json package-lock.json - git commit -m "chore: version bump ${{ env.TAG_NAME }} + set -e # Exit on any error + BRANCH_NAME="${{ steps.create-branch.outputs.branch-name }}" - - Update version to ${{ needs.validate-release-version.outputs.version }}" - git push origin HEAD:${{ env.TARGET_BRANCH }} + 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 @@ -155,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"