name: Release & Publish on: workflow_run: workflows: [CI] types: [completed] branches: [main] permissions: contents: write id-token: write jobs: check: name: Check for releasable commits runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' outputs: bump: ${{ steps.bump.outputs.bump }} 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: fetch-depth: 0 - name: Determine version bump from conventional commits id: bump 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 (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 "$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 "$SUBJECTS" | grep -v "^chore(release):" || true) if [ -z "$NON_RELEASE_COMMITS" ]; then echo "Only release commits found, skipping" echo "should_release=false" >> "$GITHUB_OUTPUT" exit 0 fi echo "Commits since $LATEST_TAG:" echo "$SUBJECTS" # Determine bump type from conventional commit prefixes and footers BUMP="patch" if echo "$SUBJECTS" | grep -qiE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:"; then BUMP="major" elif echo "$FULL_LOG" | grep -qiE "^BREAKING[ -]CHANGE:"; then BUMP="major" elif echo "$SUBJECTS" | grep -qiE "^(feat|feature)(\(.+\))?:"; then BUMP="minor" elif echo "$SUBJECTS" | grep -qiE "^✨"; then BUMP="minor" fi # Read current version and compute new one CURRENT=$(node -p "require('./package.json').version") IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" case "$BUMP" in major) NEW_VERSION="$((MAJOR+1)).0.0" ;; minor) NEW_VERSION="$MAJOR.$((MINOR+1)).0" ;; patch) NEW_VERSION="$MAJOR.$MINOR.$((PATCH+1))" ;; esac echo "Current: $CURRENT → New: $NEW_VERSION ($BUMP)" # Build changelog from conventional commits CHANGELOG="" 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) add_section() { local header="$1" local items="$2" if [ -n "$items" ]; then CHANGELOG="$(printf '%s\n\n%s\n%s' "$CHANGELOG" "$header" "$(echo "$items" | sed 's/^/- /')")" fi } add_section "### ⚠️ Breaking Changes" "$BREAKING" add_section "### ✨ Features" "$FEATURES" add_section "### 🐛 Bug Fixes" "$FIXES" add_section "### 📦 Other Changes" "$OTHERS" 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 { echo "changelog<> "$GITHUB_OUTPUT" release: name: Version bump & release needs: check if: needs.check.outputs.should_release == 'true' runs-on: ubuntu-latest 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 "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: | npm version ${{ needs.check.outputs.new_version }} --no-git-tag-version git add package.json package-lock.json git commit -m "chore(release): v${{ needs.check.outputs.new_version }}" git tag "v${{ needs.check.outputs.new_version }}" - name: Push version commit and tag run: | git push origin main git push origin "v${{ needs.check.outputs.new_version }}" - 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/${{ needs.check.outputs.prev_tag && needs.check.outputs.prev_tag || 'initial' }}...v${{ needs.check.outputs.new_version }} --- ``` npm install @sanjibdevnath/mcp-excalidraw-local@${{ needs.check.outputs.new_version }} ``` draft: false prerelease: false publish-npm: name: Publish to NPM needs: release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: main - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Check if version already on NPM id: check-npm run: | VERSION=$(node -p "require('./package.json').version") if npm view @sanjibdevnath/mcp-excalidraw-local@$VERSION version 2>/dev/null; then echo "exists=true" >> "$GITHUB_OUTPUT" else echo "exists=false" >> "$GITHUB_OUTPUT" fi - name: Publish to NPM if: steps.check-npm.outputs.exists == 'false' run: npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Published if: steps.check-npm.outputs.exists == 'false' run: echo "✅ Published v$(node -p "require('./package.json').version") to NPM" - name: Skipped (already exists) if: steps.check-npm.outputs.exists == 'true' run: echo "⚠️ Version already on NPM, skipping" publish-docker: name: Build & push Docker images needs: release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: v${{ needs.release.outputs.version }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push MCP Server image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true tags: | sanjibdevnath/mcp-excalidraw-local:latest sanjibdevnath/mcp-excalidraw-local:v${{ needs.release.outputs.version }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 - name: Build and push Canvas Server image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.canvas push: true tags: | sanjibdevnath/mcp-excalidraw-local-canvas:latest sanjibdevnath/mcp-excalidraw-local-canvas:v${{ needs.release.outputs.version }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64