✨ feat: add test suite, CI/CD pipeline, setup wizard, and upstream feature ports (#6)
Establish comprehensive quality infrastructure for a project that previously had zero tests, enabling confident refactoring and community contributions with automated guardrails. Port upstream enhancements for font normalization, image element support, and arrow binding preservation. 🏗️ Testing infrastructure: - Unit tests for SQLite persistence layer and element validation helpers - Integration tests for REST API, WebSocket broadcast, and arrow binding - E2E tests with Playwright for canvas rendering and real-time sync - Vitest + Playwright configuration with proper isolation 👷 CI/CD pipeline: - Auto-versioning from conventional commits on push to main - Auto-publish to NPM and Docker Hub on GitHub release - Matrix testing across Node 18/20/22 with pinned dependencies - Docker health check with diagnostic logging on failure - Preserve rollup status checks for branch protection gates 📦 Developer experience: - Interactive setup wizard for first-time configuration - Canvas clear confirmation and scene description tools - Frontend helpers extracted for testability 🔧 Upstream feature ports: - Font family normalization (string names to numeric IDs) - Image element support with file management API - Arrow binding preservation through server round-trips - Vite config fix for font subsetting worker chunk names - Idempotent database initialization for standalone Docker mode 🐛 Docker fixes: - Set EXCALIDRAW_DB_PATH in both Dockerfiles to writable /app/data/ - Make initDb() idempotent and closeDb() reset-safe for test isolation 🎯 Provides the safety net needed for rapid iteration — every PR is validated across 120 test cases before merge, and releases are fully automated from commit to published package. Co-authored-by: sanjibdevnathlabs <devnath.sanjib@gmail.com>
This commit is contained in:
co-authored by
sanjibdevnathlabs
parent
4c50472ee4
commit
63209f9d5a
+54
-19
@@ -2,22 +2,24 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
branches: [main, develop]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: Build and Type Check
|
||||
name: Build & Test (Node ${{ matrix.node-version }})
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x, 22.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
@@ -28,36 +30,34 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run TypeScript type check
|
||||
- name: Type check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Build project
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Unit & integration tests
|
||||
run: npm test
|
||||
|
||||
- name: Check build artifacts
|
||||
run: |
|
||||
echo "Checking if build artifacts exist..."
|
||||
test -f dist/index.js || (echo "dist/index.js not found" && exit 1)
|
||||
test -f dist/server.js || (echo "dist/server.js not found" && exit 1)
|
||||
test -d dist/frontend || (echo "dist/frontend not found" && exit 1)
|
||||
echo "All build artifacts present!"
|
||||
test -f dist/index.js
|
||||
test -f dist/server.js
|
||||
test -d dist/frontend
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: matrix.node-version == '20.x'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: |
|
||||
dist/
|
||||
path: dist/
|
||||
retention-days: 7
|
||||
|
||||
lint-check:
|
||||
name: Lint Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -71,11 +71,46 @@ jobs:
|
||||
- name: Check for TypeScript errors
|
||||
run: npm run type-check
|
||||
|
||||
e2e:
|
||||
name: E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: ./node_modules/.bin/playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npm run test:e2e
|
||||
env:
|
||||
EXCALIDRAW_DB_PATH: /tmp/excalidraw-e2e-ci.db
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
ci-status:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
name: CI Status Check
|
||||
needs: [build-and-test, lint-check]
|
||||
needs: [build-and-test, lint-check, e2e]
|
||||
if: always()
|
||||
permissions:
|
||||
statuses: write
|
||||
|
||||
@@ -1,144 +1,134 @@
|
||||
name: Docker Build & Push
|
||||
name: Docker Build
|
||||
|
||||
# PR validation and manual builds. Production pushes are handled by release.yml.
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'Dockerfile*'
|
||||
- 'src/**'
|
||||
- 'frontend/**'
|
||||
- 'package.json'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push:
|
||||
description: 'Push images to Docker Hub'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME_MCP: sanjibdevnath/mcp-excalidraw-local
|
||||
IMAGE_NAME_CANVAS: sanjibdevnath/mcp-excalidraw-local-canvas
|
||||
|
||||
jobs:
|
||||
build-and-push-mcp:
|
||||
name: Build and Push MCP Server Image
|
||||
build-mcp:
|
||||
name: Build MCP Server image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for MCP Server
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MCP }}
|
||||
images: ${{ env.IMAGE_NAME_MCP }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha,prefix=sha-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push MCP Server image
|
||||
- name: Build MCP Server image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
push: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
|
||||
|
||||
build-and-push-canvas:
|
||||
name: Build and Push Canvas Server Image
|
||||
build-canvas:
|
||||
name: Build Canvas Server image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Canvas Server
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_CANVAS }}
|
||||
images: ${{ env.IMAGE_NAME_CANVAS }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha,prefix=sha-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Canvas Server image
|
||||
- name: Build Canvas Server image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.canvas
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
push: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
|
||||
|
||||
test-docker-images:
|
||||
name: Test Docker Images
|
||||
needs: [build-and-push-mcp, build-and-push-canvas]
|
||||
if: github.event_name != 'pull_request'
|
||||
test-images:
|
||||
name: Test Docker images
|
||||
needs: [build-mcp, build-canvas]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Test Canvas Server image
|
||||
- name: Build and test Canvas image locally
|
||||
run: |
|
||||
docker pull ${{ env.IMAGE_NAME_CANVAS }}:latest
|
||||
docker run -d -p 3000:3000 --name test-canvas ${{ env.IMAGE_NAME_CANVAS }}:latest
|
||||
sleep 10
|
||||
curl -f http://localhost:3000/health || exit 1
|
||||
docker logs test-canvas
|
||||
docker build -f Dockerfile.canvas -t canvas-test .
|
||||
docker run -d -p 3000:3000 --name test-canvas canvas-test
|
||||
HEALTHY=false
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:3000/health > /dev/null 2>&1; then
|
||||
echo "Health check passed on attempt $i"
|
||||
HEALTHY=true
|
||||
break
|
||||
fi
|
||||
echo "Waiting for server... ($i/30)"
|
||||
sleep 1
|
||||
done
|
||||
if [ "$HEALTHY" != "true" ]; then
|
||||
echo "::error::Health check failed after 30 attempts"
|
||||
docker logs test-canvas
|
||||
docker stop test-canvas || true
|
||||
exit 1
|
||||
fi
|
||||
curl -sf http://localhost:3000/health | jq .
|
||||
docker stop test-canvas
|
||||
|
||||
- name: Test MCP Server image
|
||||
run: |
|
||||
docker pull ${{ env.IMAGE_NAME_MCP }}:latest
|
||||
echo "MCP Server image pulled successfully"
|
||||
|
||||
docker-status:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
name: Docker Build Status Check
|
||||
needs: [build-and-push-mcp, build-and-push-canvas]
|
||||
needs: [build-mcp, build-canvas, test-images]
|
||||
if: always()
|
||||
permissions:
|
||||
statuses: write
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
name: Publish to NPM
|
||||
name: Publish to NPM (manual)
|
||||
|
||||
# Fallback for manual publishing. The primary publish path is release.yml.
|
||||
# Use this for publishing beta/next tags or re-publishing a failed release.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to publish (e.g., latest, beta, next)'
|
||||
description: 'NPM dist-tag (latest, beta, next)'
|
||||
required: true
|
||||
default: 'latest'
|
||||
type: choice
|
||||
options:
|
||||
- latest
|
||||
- beta
|
||||
- next
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish to NPM Registry
|
||||
name: Publish to NPM (${{ github.event.inputs.tag }})
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -32,75 +36,43 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run type check
|
||||
- name: Type check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Build project
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Unit & integration tests
|
||||
run: npm test
|
||||
|
||||
- name: Verify build artifacts
|
||||
run: |
|
||||
echo "Verifying build artifacts..."
|
||||
test -f dist/index.js || (echo "ERROR: dist/index.js not found" && exit 1)
|
||||
test -f dist/server.js || (echo "ERROR: dist/server.js not found" && exit 1)
|
||||
test -d dist/frontend || (echo "ERROR: dist/frontend not found" && exit 1)
|
||||
echo "All required artifacts present!"
|
||||
test -f dist/index.js
|
||||
test -f dist/server.js
|
||||
test -d dist/frontend
|
||||
|
||||
- name: Get package version
|
||||
id: package-version
|
||||
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check if version exists on NPM
|
||||
id: check-version
|
||||
id: check
|
||||
run: |
|
||||
if npm view @sanjibdevnath/mcp-excalidraw-local@${{ steps.package-version.outputs.version }} version 2>/dev/null; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
echo "Version ${{ steps.package-version.outputs.version }} already exists on NPM"
|
||||
if npm view @sanjibdevnath/mcp-excalidraw-local@${{ steps.version.outputs.version }} version 2>/dev/null; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "Version ${{ steps.package-version.outputs.version }} does not exist on NPM"
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Publish to NPM (Release)
|
||||
if: github.event_name == 'release' && steps.check-version.outputs.exists == 'false'
|
||||
run: npm publish --provenance --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish to NPM (Manual)
|
||||
if: github.event_name == 'workflow_dispatch' && steps.check-version.outputs.exists == 'false'
|
||||
- name: Publish to NPM
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
run: npm publish --tag ${{ github.event.inputs.tag }} --provenance --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Skip publishing (version exists)
|
||||
if: steps.check-version.outputs.exists == 'true'
|
||||
- name: Skip (version exists)
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
run: |
|
||||
echo "⚠️ Skipping publish - version ${{ steps.package-version.outputs.version }} already exists on NPM"
|
||||
echo "Please bump the version in package.json before publishing"
|
||||
|
||||
- name: Create GitHub Release Assets
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
tar -czf mcp-excalidraw-local-${{ steps.package-version.outputs.version }}.tar.gz dist/
|
||||
|
||||
- name: Upload Release Assets
|
||||
if: github.event_name == 'release'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
mcp-excalidraw-local-${{ steps.package-version.outputs.version }}.tar.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
notify:
|
||||
name: Publish Notification
|
||||
needs: publish
|
||||
runs-on: ubuntu-latest
|
||||
if: success()
|
||||
|
||||
steps:
|
||||
- name: Success notification
|
||||
run: |
|
||||
echo "✅ Package successfully published to NPM!"
|
||||
echo "View at: https://www.npmjs.com/package/@sanjibdevnath/mcp-excalidraw-local"
|
||||
echo "⚠️ Version ${{ steps.version.outputs.version }} already on NPM"
|
||||
echo "Bump the version in package.json first"
|
||||
exit 1
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
name: Release & Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'docs/**'
|
||||
- '.github/workflows/ci.yml'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
# Gate: only release if CI passed and there are releasable commits
|
||||
check:
|
||||
name: Check for releasable commits
|
||||
runs-on: ubuntu-latest
|
||||
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 }}
|
||||
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)
|
||||
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")
|
||||
|
||||
if [ -z "$COMMITS" ]; 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)
|
||||
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 "$COMMITS"
|
||||
|
||||
# Determine bump type from conventional commit prefixes
|
||||
BUMP="patch"
|
||||
if echo "$COMMITS" | grep -qiE "^.*(BREAKING[ -]CHANGE|!)(\(.+\))?:"; then
|
||||
BUMP="major"
|
||||
elif echo "$COMMITS" | grep -qiE "^(feat|feature)(\(.+\))?:"; then
|
||||
BUMP="minor"
|
||||
elif echo "$COMMITS" | 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 "$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)
|
||||
|
||||
if [ -n "$BREAKING" ]; then
|
||||
CHANGELOG="$CHANGELOG
|
||||
### ⚠️ Breaking Changes
|
||||
$(echo "$BREAKING" | sed 's/^/- /')"
|
||||
fi
|
||||
if [ -n "$FEATURES" ]; then
|
||||
CHANGELOG="$CHANGELOG
|
||||
### ✨ Features
|
||||
$(echo "$FEATURES" | sed 's/^/- /')"
|
||||
fi
|
||||
if [ -n "$FIXES" ]; then
|
||||
CHANGELOG="$CHANGELOG
|
||||
### 🐛 Bug Fixes
|
||||
$(echo "$FIXES" | sed 's/^/- /')"
|
||||
fi
|
||||
if [ -n "$OTHERS" ]; then
|
||||
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"
|
||||
|
||||
# Multi-line output for changelog
|
||||
{
|
||||
echo "changelog<<CHANGELOG_EOF"
|
||||
echo "$CHANGELOG"
|
||||
echo "CHANGELOG_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
test:
|
||||
name: Pre-release tests
|
||||
needs: check
|
||||
if: needs.check.outputs.should_release == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Type check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Unit & integration tests
|
||||
run: npm test
|
||||
|
||||
release:
|
||||
name: Version bump & release
|
||||
needs: [check, test]
|
||||
if: needs.check.outputs.should_release == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ needs.check.outputs.new_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[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:
|
||||
tag_name: v${{ needs.check.outputs.new_version }}
|
||||
name: v${{ needs.check.outputs.new_version }}
|
||||
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 }}
|
||||
|
||||
---
|
||||
```
|
||||
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
|
||||
Reference in New Issue
Block a user