diff --git a/.github/workflows/bump-and-tag.yml b/.github/workflows/bump-and-tag.yml index 9ed73656..f2d4eb1d 100644 --- a/.github/workflows/bump-and-tag.yml +++ b/.github/workflows/bump-and-tag.yml @@ -1,7 +1,14 @@ -# When a PR is merged into master, this workflow handles versioning: -# - If code files changed but version wasn't bumped: auto-increments patch version +# Creates a new git tag when a PR is merged +# +# Here's the flow: +# - Triggered whenever a PR is merged, if that PR made code changes +# - If version wasn't bumped in PR, increment patch version and update package.json +# - Otherwise (if the PR did bump version) we use the new version from package.json # - Creates and pushes a git tag for the new version -# - The tag then triggers Docker publishing and release drafting +# - That git tag then triggers Docker publishing and release drafting in other CI +# - Add tags to issues from newly relesaed features/fixes (if applicable) +# - Trigger fresh deploy of docs site, so changelog remains up-to-date +# - Finally, shows summary of actions taken and new tag published name: 🔖 Auto Version & Tag on: @@ -16,6 +23,7 @@ concurrency: permissions: contents: write pull-requests: read + issues: write jobs: version-and-tag: @@ -25,7 +33,7 @@ jobs: steps: - name: Check PR for code changes and version bump 📂 id: check_pr - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const { owner, repo } = context.repo; @@ -77,7 +85,7 @@ jobs: - name: Checkout repository đŸ›Žī¸ if: steps.check_pr.outputs.needs_tag == 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -87,6 +95,23 @@ jobs: git config user.name "Liss-Bot" git config user.email "liss-bot@d0h.co" + - name: Extract referenced issues 🔍 + id: issues + if: steps.check_pr.outputs.needs_tag == 'true' + uses: actions/github-script@v8 + with: + script: | + const body = context.payload.pull_request.body || ''; + const matches = body.match(/#(\d+)/g); + if (!matches) { + core.info('No issue references found in PR body'); + core.setOutput('numbers', ''); + return; + } + const unique = [...new Set(matches.map(m => m.replace('#', '')))]; + core.info(`Found issue references: ${unique.join(', ')}`); + core.setOutput('numbers', unique.join(',')); + - name: Bump patch version âŦ†ī¸ if: steps.check_pr.outputs.needs_bump == 'true' run: | @@ -96,7 +121,13 @@ jobs: git push - name: Create and push tag đŸˇī¸ + id: tag if: steps.check_pr.outputs.needs_tag == 'true' + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + ISSUES: ${{ steps.issues.outputs.numbers }} run: | VERSION=$(node -p "require('./package.json').version") git fetch --tags --force @@ -111,5 +142,121 @@ jobs: "$PR_TITLE" \ "$MERGE_SHA" > tag-message.txt + if [ -n "$ISSUES" ]; then + printf 'Resolves: %s\n' "$(echo "$ISSUES" | sed 's/,/, #/g; s/^/#/')" >> tag-message.txt + fi + git tag -a "$VERSION" -F tag-message.txt git push origin "$VERSION" + + - name: Label referenced issues đŸ›Šī¸ + id: label + if: steps.check_pr.outputs.needs_tag == 'true' && steps.issues.outputs.numbers != '' + continue-on-error: true + uses: actions/github-script@v8 + env: + ISSUES: ${{ steps.issues.outputs.numbers }} + with: + github-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { owner, repo } = context.repo; + const { version } = JSON.parse(require('fs').readFileSync('package.json', 'utf8')); + const labelName = `đŸ›Šī¸ Released ${version}`; + const issues = process.env.ISSUES.split(',').filter(Boolean); + + try { + await github.rest.issues.createLabel({ + owner, repo, + name: labelName, + color: 'EDEDED', + description: `Included in release v${version}`, + }); + core.info(`Created label: ${labelName}`); + } catch (e) { + if (e.status === 422) { + core.info(`Label already exists: ${labelName}`); + } else { + core.warning(`Failed to create label: ${e.message}`); + } + } + + for (const num of issues) { + try { + await github.rest.issues.addLabels({ + owner, repo, + issue_number: parseInt(num, 10), + labels: [labelName], + }); + core.info(`Labeled issue #${num}`); + } catch (e) { + core.warning(`Failed to label issue #${num}: ${e.message}`); + } + } + + - name: Trigger docs site rebuild 📝 + id: docs + if: steps.tag.outcome == 'success' + continue-on-error: true + env: + HOOK_URL: ${{ secrets.DOCS_SITE_REBUILD_HOOK }} + run: | + if [ -z "$HOOK_URL" ]; then + echo "::warning::DOCS_SITE_REBUILD_HOOK secret is not set, skipping" + exit 1 + fi + VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") + curl -sf -X POST -d '{}' "${HOOK_URL}?trigger_title=v${VERSION}+released" \ + --max-time 15 --retry 2 --retry-max-time 30 + echo "Triggered docs rebuild for v${VERSION}" + + - name: Job summary 📋 + if: always() + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + NEEDS_BUMP: ${{ steps.check_pr.outputs.needs_bump }} + NEEDS_TAG: ${{ steps.check_pr.outputs.needs_tag }} + ISSUES: ${{ steps.issues.outputs.numbers }} + TAG_OUTCOME: ${{ steps.tag.outcome }} + LABEL_OUTCOME: ${{ steps.label.outcome }} + DOCS_OUTCOME: ${{ steps.docs.outcome }} + run: | + VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") + + { + echo "## 🔖 Auto Version & Tag" + echo "" + echo "| Step | Result |" + echo "|------|--------|" + echo "| PR | [#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}) — ${PR_TITLE} |" + + if [ "$NEEDS_BUMP" = "true" ]; then + echo "| Version bump | ✅ \`${VERSION}\` |" + else + echo "| Version bump | â­ī¸ Skipped |" + fi + + if [ "$NEEDS_TAG" = "true" ] && [ "$TAG_OUTCOME" = "success" ]; then + echo "| Tag | ✅ [\`${VERSION}\`](${REPO_URL}/releases/tag/${VERSION}) |" + elif [ "$NEEDS_TAG" = "true" ]; then + echo "| Tag | ❌ Failed |" + else + echo "| Tag | â­ī¸ Skipped |" + fi + + if [ "$DOCS_OUTCOME" = "success" ]; then + echo "| Docs rebuild | ✅ Triggered |" + elif [ "$DOCS_OUTCOME" = "failure" ]; then + echo "| Docs rebuild | âš ī¸ Failed |" + fi + + if [ -n "$ISSUES" ]; then + ISSUE_LINKS=$(echo "$ISSUES" | tr ',' '\n' | sed "s|.*|[#&](${REPO_URL}/issues/&)|" | paste -sd ' ' -) + if [ "$LABEL_OUTCOME" = "success" ]; then + echo "| Issues labeled | ✅ ${ISSUE_LINKS} |" + else + echo "| Issues labeled | âš ī¸ ${ISSUE_LINKS} |" + fi + fi + } >> "$GITHUB_STEP_SUMMARY"