🔧 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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,28 +86,28 @@ 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
|
||||||
@@ -110,6 +115,7 @@ jobs:
|
|||||||
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 }}
|
||||||
|
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user