🔧 fix(ci): use GitHub App bot for release and auto-pass Docker check (#7)

The release workflow pushes version bump commits directly to main, but
branch protection rules block the default GITHUB_TOKEN from bypassing
required status checks and PR requirements. Using a dedicated GitHub
App (sanjibdevnathlabs-release-bot) generates installation tokens that
are permitted through the ruleset bypass list, and keeps the bot
identity on release commits instead of a personal account.

The Docker Build workflow previously used a paths filter, causing it to
not trigger at all for non-Docker PRs — leaving the required
github/docker-build-check status permanently pending. Now the workflow
always triggers but checks for Docker-related file changes first,
skipping builds when unnecessary while still reporting the status check
as passed.
This commit is contained in:
Sanjib Devnath
2026-03-13 14:29:36 +05:30
committed by GitHub
parent 63209f9d5a
commit f1e1f4cbab
3 changed files with 107 additions and 36 deletions
+32 -2
View File
@@ -11,8 +11,35 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
check-changes:
name: Check for app changes
runs-on: ubuntu-latest
outputs:
should_test: ${{ steps.filter.outputs.should_test }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check changed files
id: filter
run: |
if [ "${{ github.event_name }}" = "push" ]; then
echo "should_test=true" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED=$(git diff --name-only origin/main...HEAD || true)
if echo "$CHANGED" | grep -qE '^(src/|frontend/|tests/|package\.json|package-lock\.json|tsconfig\.json|vite\.config|vitest\.config|playwright\.config)'; then
echo "should_test=true" >> "$GITHUB_OUTPUT"
else
echo "No application code changed, skipping tests"
echo "should_test=false" >> "$GITHUB_OUTPUT"
fi
build-and-test: build-and-test:
name: Build & Test (Node ${{ matrix.node-version }}) name: Build & Test (Node ${{ matrix.node-version }})
needs: check-changes
if: needs.check-changes.outputs.should_test == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -55,6 +82,8 @@ jobs:
lint-check: lint-check:
name: Lint Check name: Lint Check
needs: check-changes
if: needs.check-changes.outputs.should_test == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -73,8 +102,9 @@ jobs:
e2e: e2e:
name: E2E Tests name: E2E Tests
needs: [check-changes, build-and-test]
if: needs.check-changes.outputs.should_test == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build-and-test
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -110,7 +140,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: false continue-on-error: false
name: CI Status Check name: CI Status Check
needs: [build-and-test, lint-check, e2e] needs: [check-changes, build-and-test, lint-check, e2e]
if: always() if: always()
permissions: permissions:
statuses: write statuses: write
+31 -6
View File
@@ -4,11 +4,6 @@ name: Docker Build
on: on:
pull_request: pull_request:
branches: [main] branches: [main]
paths:
- 'Dockerfile*'
- 'src/**'
- 'frontend/**'
- 'package.json'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
push: push:
@@ -22,8 +17,35 @@ env:
IMAGE_NAME_CANVAS: sanjibdevnath/mcp-excalidraw-local-canvas IMAGE_NAME_CANVAS: sanjibdevnath/mcp-excalidraw-local-canvas
jobs: jobs:
check-changes:
name: Check for Docker-related changes
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.filter.outputs.should_build }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check changed files
id: filter
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED=$(git diff --name-only origin/main...HEAD || true)
if echo "$CHANGED" | grep -qE '^(Dockerfile|src/|frontend/|package\.json)'; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
else
echo "No Docker-related files changed, skipping builds"
echo "should_build=false" >> "$GITHUB_OUTPUT"
fi
build-mcp: build-mcp:
name: Build MCP Server image name: Build MCP Server image
needs: check-changes
if: needs.check-changes.outputs.should_build == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -60,6 +82,8 @@ jobs:
build-canvas: build-canvas:
name: Build Canvas Server image name: Build Canvas Server image
needs: check-changes
if: needs.check-changes.outputs.should_build == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -97,6 +121,7 @@ jobs:
test-images: test-images:
name: Test Docker images name: Test Docker images
needs: [build-mcp, build-canvas] needs: [build-mcp, build-canvas]
if: needs.build-mcp.result == 'success' && needs.build-canvas.result == 'success'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -128,7 +153,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: false continue-on-error: false
name: Docker Build Status Check name: Docker Build Status Check
needs: [build-mcp, build-canvas, test-images] needs: [check-changes, build-mcp, build-canvas, test-images]
if: always() if: always()
permissions: permissions:
statuses: write statuses: write
+44 -28
View File
@@ -22,6 +22,7 @@ jobs:
new_version: ${{ steps.bump.outputs.new_version }} new_version: ${{ steps.bump.outputs.new_version }}
changelog: ${{ steps.bump.outputs.changelog }} changelog: ${{ steps.bump.outputs.changelog }}
should_release: ${{ steps.bump.outputs.should_release }} should_release: ${{ steps.bump.outputs.should_release }}
prev_tag: ${{ steps.bump.outputs.prev_tag }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -32,23 +33,25 @@ jobs:
run: | run: |
# Find the latest semver tag # Find the latest semver tag
LATEST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -n1) LATEST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -n1)
PREV_TAG="${LATEST_TAG:-}"
if [ -z "$LATEST_TAG" ]; then if [ -z "$LATEST_TAG" ]; then
LATEST_TAG=$(git rev-list --max-parents=0 HEAD) LATEST_TAG=$(git rev-list --max-parents=0 HEAD)
echo "No tags found, using first commit" echo "No tags found, using first commit"
fi fi
echo "Latest tag: $LATEST_TAG" echo "Latest tag: $LATEST_TAG"
# Get commit messages since last tag # Get commit messages since last tag (subject + body for BREAKING CHANGE footers)
COMMITS=$(git log "$LATEST_TAG"..HEAD --pretty=format:"%s" 2>/dev/null || git log --pretty=format:"%s") SUBJECTS=$(git log "$LATEST_TAG"..HEAD --pretty=format:"%s" 2>/dev/null || git log --pretty=format:"%s")
FULL_LOG=$(git log "$LATEST_TAG"..HEAD --pretty=format:"%B---END---" 2>/dev/null || git log --pretty=format:"%B---END---")
if [ -z "$COMMITS" ]; then if [ -z "$SUBJECTS" ]; then
echo "No new commits since $LATEST_TAG" echo "No new commits since $LATEST_TAG"
echo "should_release=false" >> "$GITHUB_OUTPUT" echo "should_release=false" >> "$GITHUB_OUTPUT"
exit 0 exit 0
fi fi
# Skip if the only commit is a version bump # Skip if the only commit is a version bump
NON_RELEASE_COMMITS=$(echo "$COMMITS" | grep -v "^chore(release):" || true) NON_RELEASE_COMMITS=$(echo "$SUBJECTS" | grep -v "^chore(release):" || true)
if [ -z "$NON_RELEASE_COMMITS" ]; then if [ -z "$NON_RELEASE_COMMITS" ]; then
echo "Only release commits found, skipping" echo "Only release commits found, skipping"
echo "should_release=false" >> "$GITHUB_OUTPUT" echo "should_release=false" >> "$GITHUB_OUTPUT"
@@ -56,15 +59,17 @@ jobs:
fi fi
echo "Commits since $LATEST_TAG:" echo "Commits since $LATEST_TAG:"
echo "$COMMITS" echo "$SUBJECTS"
# Determine bump type from conventional commit prefixes # Determine bump type from conventional commit prefixes and footers
BUMP="patch" BUMP="patch"
if echo "$COMMITS" | grep -qiE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:"; then if echo "$SUBJECTS" | grep -qiE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:"; then
BUMP="major" BUMP="major"
elif echo "$COMMITS" | grep -qiE "^(feat|feature)(\(.+\))?:"; then elif echo "$FULL_LOG" | grep -qiE "^BREAKING[ -]CHANGE:"; then
BUMP="major"
elif echo "$SUBJECTS" | grep -qiE "^(feat|feature)(\(.+\))?:"; then
BUMP="minor" BUMP="minor"
elif echo "$COMMITS" | grep -qiE "^✨"; then elif echo "$SUBJECTS" | grep -qiE "^✨"; then
BUMP="minor" BUMP="minor"
fi fi
@@ -81,35 +86,36 @@ jobs:
# Build changelog from conventional commits # Build changelog from conventional commits
CHANGELOG="" CHANGELOG=""
BREAKING=$(echo "$COMMITS" | grep -iE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:" || true) BREAKING=$(echo "$SUBJECTS" | grep -iE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:" || true)
FEATURES=$(echo "$COMMITS" | grep -iE "^(feat|feature|✨)" || true) FEATURES=$(echo "$SUBJECTS" | grep -iE "^(feat|feature|✨)" || true)
FIXES=$(echo "$COMMITS" | grep -iE "^(fix|🐛)" || true) FIXES=$(echo "$SUBJECTS" | grep -iE "^(fix|🐛)" || true)
OTHERS=$(echo "$COMMITS" | grep -viE "^(feat|feature|✨|fix|🐛|chore\(release\))" | grep -viE "BREAKING" || true) OTHERS=$(echo "$SUBJECTS" | grep -viE "^(feat|feature|✨|fix|🐛|chore\(release\))" | grep -viE "BREAKING" || true)
if [ -n "$BREAKING" ]; then if [ -n "$BREAKING" ]; then
CHANGELOG="$CHANGELOG CHANGELOG="${CHANGELOG}
### ⚠️ Breaking Changes ### ⚠️ Breaking Changes
$(echo "$BREAKING" | sed 's/^/- /')" $(echo "$BREAKING" | sed 's/^/- /')"
fi fi
if [ -n "$FEATURES" ]; then if [ -n "$FEATURES" ]; then
CHANGELOG="$CHANGELOG CHANGELOG="${CHANGELOG}
### ✨ Features ### ✨ Features
$(echo "$FEATURES" | sed 's/^/- /')" $(echo "$FEATURES" | sed 's/^/- /')"
fi fi
if [ -n "$FIXES" ]; then if [ -n "$FIXES" ]; then
CHANGELOG="$CHANGELOG CHANGELOG="${CHANGELOG}
### 🐛 Bug Fixes ### 🐛 Bug Fixes
$(echo "$FIXES" | sed 's/^/- /')" $(echo "$FIXES" | sed 's/^/- /')"
fi fi
if [ -n "$OTHERS" ]; then if [ -n "$OTHERS" ]; then
CHANGELOG="$CHANGELOG CHANGELOG="${CHANGELOG}
### 📦 Other Changes ### 📦 Other Changes
$(echo "$OTHERS" | sed 's/^/- /')" $(echo "$OTHERS" | sed 's/^/- /')"
fi fi
echo "bump=$BUMP" >> "$GITHUB_OUTPUT" echo "bump=$BUMP" >> "$GITHUB_OUTPUT"
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT" echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "should_release=true" >> "$GITHUB_OUTPUT" echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT"
# Multi-line output for changelog # Multi-line output for changelog
{ {
@@ -152,14 +158,22 @@ jobs:
outputs: outputs:
version: ${{ needs.check.outputs.new_version }} version: ${{ needs.check.outputs.new_version }}
steps: steps:
- name: Generate release bot token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Configure git - name: Configure git
run: | run: |
git config user.name "github-actions[bot]" git config user.name "sanjibdevnathlabs-release-bot[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "${{ secrets.APP_ID }}+sanjibdevnathlabs-release-bot[bot]@users.noreply.github.com"
- name: Bump version in package.json - name: Bump version in package.json
run: | run: |
@@ -176,13 +190,15 @@ jobs:
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
token: ${{ steps.app-token.outputs.token }}
tag_name: v${{ needs.check.outputs.new_version }} tag_name: v${{ needs.check.outputs.new_version }}
name: v${{ needs.check.outputs.new_version }} name: v${{ needs.check.outputs.new_version }}
generate_release_notes: true
body: | body: |
## What's Changed in v${{ needs.check.outputs.new_version }} ## What's Changed in v${{ needs.check.outputs.new_version }}
${{ needs.check.outputs.changelog }} ${{ needs.check.outputs.changelog }}
**Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ needs.check.outputs.new_version }}...v${{ needs.check.outputs.new_version }} **Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ needs.check.outputs.prev_tag && needs.check.outputs.prev_tag || 'initial' }}...v${{ needs.check.outputs.new_version }}
--- ---
``` ```