name: Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., v1.0.0)'
required: true
type: string
jobs:
test-before-release:
name: Pre-release Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run full test suite
run: |
npm run lint
npm run typecheck
npm test -- --coverage
npm run build
- name: Test package integrity
run: |
npm pack
tar -tzf *.tgz | head -20
build-and-release:
name: Build and Release
runs-on: ubuntu-latest
needs: test-before-release
permissions:
contents: write
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Extract version from tag
id: extract_version
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION=${GITHUB_REF#refs/tags/}
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "Version: ${VERSION}"
- name: Update package version
run: |
VERSION="${{ steps.extract_version.outputs.version }}"
VERSION_NO_V=${VERSION#v}
npm version ${VERSION_NO_V} --no-git-tag-version
- name: Generate changelog
id: changelog
run: |
if [ -f CHANGELOG.md ]; then
# Extract changelog for this version
awk '/^##/ {if(p) exit; if($2 ~ /^v?[0-9]/) p=1} p' CHANGELOG.md > release_notes.md
else
echo "# Release Notes" > release_notes.md
echo "" >> release_notes.md
echo "## What's Changed" >> release_notes.md
echo "- See commit history for detailed changes" >> release_notes.md
fi
echo "changelog<<EOF" >> $GITHUB_OUTPUT
cat release_notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create release package
run: |
# Create distribution package
npm pack
# Create additional release artifacts
tar -czf mcp-console-automation-${{ steps.extract_version.outputs.version }}-src.tar.gz \
--exclude=node_modules \
--exclude=.git \
--exclude=dist \
--exclude=coverage \
--exclude=*.tgz \
.
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.extract_version.outputs.version }}
release_name: Release ${{ steps.extract_version.outputs.version }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: ${{ contains(steps.extract_version.outputs.version, '-') }}
- name: Upload release assets
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./mcp-console-automation-*.tgz
asset_name: mcp-console-automation-${{ steps.extract_version.outputs.version }}.tgz
asset_content_type: application/gzip
- name: Upload source archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./mcp-console-automation-${{ steps.extract_version.outputs.version }}-src.tar.gz
asset_name: mcp-console-automation-${{ steps.extract_version.outputs.version }}-src.tar.gz
asset_content_type: application/gzip
- name: Publish to NPM
if: ${{ !contains(steps.extract_version.outputs.version, '-') }}
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish pre-release to NPM
if: ${{ contains(steps.extract_version.outputs.version, '-') }}
run: npm publish --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
docker-release:
name: Docker Release
runs-on: ubuntu-latest
needs: test-before-release
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
mcp/console-automation
ghcr.io/${{ github.repository }}
tags: |
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
labels: |
org.opencontainers.image.title=MCP Console Automation
org.opencontainers.image.description=MCP server for AI-driven console application automation
- name: Create Dockerfile
run: |
cat > Dockerfile << 'EOF'
FROM node:20-alpine AS builder
WORKDIR /app
# Install build dependencies
RUN apk add --no-cache python3 make g++
# Copy package files
COPY package*.json ./
# Install all dependencies (including dev)
RUN npm ci
# Copy source code
COPY . .
# Build application
RUN npm run build
# Production stage
FROM node:20-alpine AS runtime
# Install runtime dependencies
RUN apk add --no-cache \
openssh-client \
bash \
curl \
git \
dumb-init \
ca-certificates
# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install production dependencies only
RUN npm ci --only=production && \
npm cache clean --force
# Copy built application from builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/src ./src
# Set ownership
RUN chown -R appuser:appgroup /app
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "console.log('Health check OK')" || exit 1
EXPOSE 3000
# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
CMD ["npm", "start"]
EOF
- name: Build and push Docker images
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
update-documentation:
name: Update Documentation
runs-on: ubuntu-latest
needs: build-and-release
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Generate API documentation
run: |
# Generate TypeScript documentation if available
if command -v typedoc &> /dev/null; then
npx typedoc src --out docs/api
fi
# Update README with latest version info
VERSION="${{ needs.build-and-release.outputs.version || github.ref_name }}"
sed -i "s/version-[0-9]\+\.[0-9]\+\.[0-9]\+/version-${VERSION#v}/g" README.md || true
- name: Commit documentation updates
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add -A
git diff --staged --quiet || git commit -m "docs: update documentation for release ${{ needs.build-and-release.outputs.version || github.ref_name }}"
git push || true
notify-release:
name: Notify Release
runs-on: ubuntu-latest
if: always()
needs: [build-and-release, docker-release]
steps:
- name: Notify successful release
if: needs.build-and-release.result == 'success'
run: |
echo "🎉 Release successful!"
echo "Version: ${{ needs.build-and-release.outputs.version || github.ref_name }}"
echo "- NPM package: ✅"
echo "- GitHub release: ✅"
echo "- Docker images: ${{ needs.docker-release.result == 'success' && '✅' || '⚠️' }}"
- name: Notify failed release
if: needs.build-and-release.result == 'failure'
run: |
echo "❌ Release failed!"
echo "Check the build logs for details."
exit 1
post-release-tests:
name: Post-release Tests
runs-on: ubuntu-latest
needs: [build-and-release, docker-release]
if: needs.build-and-release.result == 'success'
steps:
- name: Test NPM package installation
run: |
# Test installing the published package
npm install -g @mcp/console-automation@latest
mcp-console --version || echo "CLI test completed"
- name: Test Docker image
if: needs.docker-release.result == 'success'
run: |
# Pull and test the published Docker image
docker pull mcp/console-automation:latest
docker run --rm mcp/console-automation:latest node --version
# Test container health
CONTAINER_ID=$(docker run -d mcp/console-automation:latest)
sleep 10
docker inspect $CONTAINER_ID --format='{{.State.Health.Status}}' || echo "Health check not available"
docker stop $CONTAINER_ID