diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3350c54..0b32987 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,35 @@ concurrency: cancel-in-progress: true 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: name: Build & Test (Node ${{ matrix.node-version }}) + needs: check-changes + if: needs.check-changes.outputs.should_test == 'true' runs-on: ubuntu-latest strategy: matrix: @@ -55,6 +82,8 @@ jobs: lint-check: name: Lint Check + needs: check-changes + if: needs.check-changes.outputs.should_test == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -73,8 +102,9 @@ jobs: e2e: name: E2E Tests + needs: [check-changes, build-and-test] + if: needs.check-changes.outputs.should_test == 'true' runs-on: ubuntu-latest - needs: build-and-test steps: - uses: actions/checkout@v4 @@ -110,7 +140,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: false name: CI Status Check - needs: [build-and-test, lint-check, e2e] + needs: [check-changes, build-and-test, lint-check, e2e] if: always() permissions: statuses: write diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1c36689..8836df2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,11 +4,6 @@ name: Docker Build on: pull_request: branches: [main] - paths: - - 'Dockerfile*' - - 'src/**' - - 'frontend/**' - - 'package.json' workflow_dispatch: inputs: push: @@ -22,8 +17,35 @@ env: IMAGE_NAME_CANVAS: sanjibdevnath/mcp-excalidraw-local-canvas 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: name: Build MCP Server image + needs: check-changes + if: needs.check-changes.outputs.should_build == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -60,6 +82,8 @@ jobs: build-canvas: name: Build Canvas Server image + needs: check-changes + if: needs.check-changes.outputs.should_build == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -97,6 +121,7 @@ jobs: test-images: name: Test Docker images needs: [build-mcp, build-canvas] + if: needs.build-mcp.result == 'success' && needs.build-canvas.result == 'success' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -128,7 +153,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: false name: Docker Build Status Check - needs: [build-mcp, build-canvas, test-images] + needs: [check-changes, build-mcp, build-canvas, test-images] if: always() permissions: statuses: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea25028..66dcb17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,7 @@ jobs: new_version: ${{ steps.bump.outputs.new_version }} changelog: ${{ steps.bump.outputs.changelog }} should_release: ${{ steps.bump.outputs.should_release }} + prev_tag: ${{ steps.bump.outputs.prev_tag }} steps: - uses: actions/checkout@v4 with: @@ -32,23 +33,25 @@ jobs: run: | # Find the latest semver tag LATEST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -n1) + PREV_TAG="${LATEST_TAG:-}" if [ -z "$LATEST_TAG" ]; then LATEST_TAG=$(git rev-list --max-parents=0 HEAD) echo "No tags found, using first commit" fi echo "Latest tag: $LATEST_TAG" - # Get commit messages since last tag - COMMITS=$(git log "$LATEST_TAG"..HEAD --pretty=format:"%s" 2>/dev/null || git log --pretty=format:"%s") + # Get commit messages since last tag (subject + body for BREAKING CHANGE footers) + 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 "should_release=false" >> "$GITHUB_OUTPUT" exit 0 fi # 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 echo "Only release commits found, skipping" echo "should_release=false" >> "$GITHUB_OUTPUT" @@ -56,15 +59,17 @@ jobs: fi 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" - if echo "$COMMITS" | grep -qiE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:"; then + if echo "$SUBJECTS" | grep -qiE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:"; then 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" - elif echo "$COMMITS" | grep -qiE "^✨"; then + elif echo "$SUBJECTS" | grep -qiE "^✨"; then BUMP="minor" fi @@ -81,35 +86,36 @@ jobs: # Build changelog from conventional commits CHANGELOG="" - BREAKING=$(echo "$COMMITS" | grep -iE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:" || true) - FEATURES=$(echo "$COMMITS" | grep -iE "^(feat|feature|✨)" || true) - FIXES=$(echo "$COMMITS" | grep -iE "^(fix|🐛)" || true) - OTHERS=$(echo "$COMMITS" | grep -viE "^(feat|feature|✨|fix|🐛|chore\(release\))" | grep -viE "BREAKING" || true) + BREAKING=$(echo "$SUBJECTS" | grep -iE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:" || true) + FEATURES=$(echo "$SUBJECTS" | grep -iE "^(feat|feature|✨)" || true) + FIXES=$(echo "$SUBJECTS" | grep -iE "^(fix|🐛)" || true) + OTHERS=$(echo "$SUBJECTS" | grep -viE "^(feat|feature|✨|fix|🐛|chore\(release\))" | grep -viE "BREAKING" || true) if [ -n "$BREAKING" ]; then - CHANGELOG="$CHANGELOG - ### ⚠️ Breaking Changes - $(echo "$BREAKING" | sed 's/^/- /')" + CHANGELOG="${CHANGELOG} +### ⚠️ Breaking Changes +$(echo "$BREAKING" | sed 's/^/- /')" fi if [ -n "$FEATURES" ]; then - CHANGELOG="$CHANGELOG - ### ✨ Features - $(echo "$FEATURES" | sed 's/^/- /')" + CHANGELOG="${CHANGELOG} +### ✨ Features +$(echo "$FEATURES" | sed 's/^/- /')" fi if [ -n "$FIXES" ]; then - CHANGELOG="$CHANGELOG - ### 🐛 Bug Fixes - $(echo "$FIXES" | sed 's/^/- /')" + CHANGELOG="${CHANGELOG} +### 🐛 Bug Fixes +$(echo "$FIXES" | sed 's/^/- /')" fi if [ -n "$OTHERS" ]; then - CHANGELOG="$CHANGELOG - ### 📦 Other Changes - $(echo "$OTHERS" | sed 's/^/- /')" + CHANGELOG="${CHANGELOG} +### 📦 Other Changes +$(echo "$OTHERS" | sed 's/^/- /')" fi echo "bump=$BUMP" >> "$GITHUB_OUTPUT" echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT" echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT" # Multi-line output for changelog { @@ -152,14 +158,22 @@ jobs: outputs: version: ${{ needs.check.outputs.new_version }} 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 with: fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} - name: Configure git run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "sanjibdevnathlabs-release-bot[bot]" + git config user.email "${{ secrets.APP_ID }}+sanjibdevnathlabs-release-bot[bot]@users.noreply.github.com" - name: Bump version in package.json run: | @@ -176,13 +190,15 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: + token: ${{ steps.app-token.outputs.token }} tag_name: v${{ needs.check.outputs.new_version }} name: v${{ needs.check.outputs.new_version }} + generate_release_notes: true body: | ## What's Changed in v${{ needs.check.outputs.new_version }} ${{ 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 }} --- ```