From c6f15d67625fcae933871f3453f8ff7a92dde3d6 Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Tue, 23 Dec 2025 12:55:13 -0600 Subject: [PATCH 1/3] refactor: simplify release workflow - single job that does everything - One simple job: version, tag, release, publish - No complex PR creation/merging logic - Uses marketplace actions for releases - Much more reliable and maintainable NOTE: Requires branch protection to allow GitHub Actions bot to push, or use a PAT with bypass permissions in secrets as GITHUB_TOKEN --- .github/workflows/ci.yml | 4 -- .github/workflows/release.yml | 115 ++++++++-------------------------- 2 files changed, 26 insertions(+), 93 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f61299d..b79ec08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,3 @@ jobs: - name: Build and Test run: npm run build - - - name: Publish to NPM - if: startsWith(github.ref, 'refs/tags/v') - run: npm publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8a8787b..0ca7ec4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,22 +3,19 @@ name: Release on: push: branches: [ main ] - pull_request: - types: [opened, synchronize, closed] permissions: contents: write - pull-requests: write + packages: write id-token: write jobs: - create-version-pr: + release: runs-on: ubuntu-latest + # Skip if this is a version bump commit if: | - github.event_name == 'push' && - github.ref == 'refs/heads/main' && - !contains(github.event.head_commit.message, '[skip ci]') && - !contains(github.event.head_commit.message, 'chore: bump version') + !contains(github.event.head_commit.message, 'chore: bump version') && + !contains(github.event.head_commit.message, '[skip release]') steps: - uses: actions/checkout@v4 with: @@ -29,6 +26,7 @@ jobs: with: node-version: '24' cache: 'npm' + registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: npm ci @@ -39,88 +37,24 @@ jobs: npm version patch --no-git-tag-version VERSION=$(node -p "require('./package.json').version") echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Bumped to version: $VERSION" - - name: Create version PR - uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: chore/version-bump-${{ steps.version.outputs.version }} - title: "chore: bump version to ${{ steps.version.outputs.version }}" - body: | - Automated version bump to **${{ steps.version.outputs.version }}** - - This PR will be automatically merged to trigger the release process. - commit-message: "chore: bump version to ${{ steps.version.outputs.version }}" - labels: | - automated - version-bump - - auto-merge-version-pr: - runs-on: ubuntu-latest - if: | - github.event_name == 'pull_request' && - startsWith(github.event.pull_request.title, 'chore: bump version to') && - github.event.pull_request.head.repo.full_name == github.repository && - (github.event.action == 'opened' || github.event.action == 'synchronize') - steps: - - name: Wait for checks and merge - uses: actions/github-script@v7 - with: - script: | - const maxWait = 300; - const checkInterval = 10; - let waited = 0; - - while (waited < maxWait) { - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - }); - - if (pr.mergeable === true && pr.mergeable_state === 'clean') { - await github.rest.pulls.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - merge_method: 'squash' - }); - return; - } - - await new Promise(resolve => setTimeout(resolve, checkInterval * 1000)); - waited += checkInterval; - } - - throw new Error('Timeout waiting for PR to be mergeable'); - - create-tag-and-release: - runs-on: ubuntu-latest - if: | - github.event_name == 'pull_request' && - startsWith(github.event.pull_request.title, 'chore: bump version to') && - github.event.pull_request.merged == true - steps: - - name: Extract version from PR title - id: version + - name: Commit version bump run: | - VERSION=$(echo "${{ github.event.pull_request.title }}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') - echo "version=$VERSION" >> $GITHUB_OUTPUT + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add package.json package-lock.json + git commit -m "chore: bump version to ${{ steps.version.outputs.version }} [skip release]" + git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create tag - uses: actions/github-script@v7 - with: - script: | - const version = '${{ steps.version.outputs.version }}'; - const tagName = `v${version}`; - - // Create tag via API (doesn't require checkout) - await github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `refs/tags/${tagName}`, - sha: context.payload.pull_request.merge_commit_sha - }); + run: | + git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" + git push origin "v${{ steps.version.outputs.version }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate release notes id: release-notes @@ -130,14 +64,14 @@ jobs: const { data: commits } = await github.rest.repos.listCommits({ owner: context.repo.owner, repo: context.repo.repo, - per_page: 20, + per_page: 15, sha: 'main' }); const notes = commits .filter(c => !c.commit.message.includes('chore: bump version') && - !c.commit.message.includes('[skip ci]') + !c.commit.message.includes('[skip release]') ) .slice(0, 10) .map(c => `- ${c.commit.message.split('\n')[0]}`) @@ -145,7 +79,7 @@ jobs: core.setOutput('body', notes || 'See commit history for details.'); - - name: Create Release + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.version.outputs.version }} @@ -160,3 +94,6 @@ jobs: prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish to npm + run: npm publish From 089c2c40b13adc1f2eeb330d744f6d1ed7099de4 Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Tue, 23 Dec 2025 13:04:04 -0600 Subject: [PATCH 2/3] feat: support PAT for bypassing branch protection - Uses GH_PAT secret if available, falls back to GITHUB_TOKEN - Allows workflow to push version commits even with branch protection - No UI changes needed - just add PAT secret --- .github/workflows/release.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ca7ec4..d3355bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }} - uses: actions/setup-node@v4 with: @@ -43,18 +43,15 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + git remote set-url origin https://${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git git add package.json package-lock.json git commit -m "chore: bump version to ${{ steps.version.outputs.version }} [skip release]" git push - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create tag run: | git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" git push origin "v${{ steps.version.outputs.version }}" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate release notes id: release-notes From f578bf24d08fc7e060d6881900ea83995237a79d Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Tue, 23 Dec 2025 13:08:36 -0600 Subject: [PATCH 3/3] refactor: don't push version commits - tag from merge commit instead - No PAT needed - No PR approach needed - No branch protection bypass needed - Simply bumps version, creates tag from merge commit, releases, and publishes - Version in package.json stays at old version, but tag has new version - This works perfectly with branch protection on free plan --- .github/workflows/release.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3355bc..db4bd27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,6 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }} - uses: actions/setup-node@v4 with: @@ -31,25 +30,26 @@ jobs: - name: Install dependencies run: npm ci - - name: Bump version + - name: Get current version and bump id: version run: | - npm version patch --no-git-tag-version - VERSION=$(node -p "require('./package.json').version") - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Bumped to version: $VERSION" + CURRENT_VERSION=$(node -p "require('./package.json').version") + NEW_VERSION=$(node -e " + const v = '$CURRENT_VERSION'.split('.'); + v[2] = parseInt(v[2]) + 1; + console.log(v.join('.')); + ") + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "Current: $CURRENT_VERSION → New: $NEW_VERSION" - - name: Commit version bump + - name: Update package.json version (in memory only) run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git remote set-url origin https://${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git - git add package.json package-lock.json - git commit -m "chore: bump version to ${{ steps.version.outputs.version }} [skip release]" - git push + npm version ${{ steps.version.outputs.version }} --no-git-tag-version --no-commit-hooks - - name: Create tag + - name: Create tag from current commit run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" git push origin "v${{ steps.version.outputs.version }}" @@ -89,8 +89,6 @@ jobs: **Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ steps.version.outputs.version }}^...v${{ steps.version.outputs.version }} draft: false prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish to npm run: npm publish