npm-publish.ymlβ’19.8 kB
name: Build and Publish NPM Package
on:
pull_request:
branches: [publish-npm]
release:
types: [published]
# Manual trigger for emergency releases
workflow_dispatch:
inputs:
version_type:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
skip_tests:
description: 'Skip tests (emergency only)'
required: false
default: false
type: boolean
force_version:
description: 'Force specific version (optional)'
required: false
type: string
# Make this workflow reusable
workflow_call:
inputs:
version_type:
description: 'Version bump type'
required: false
default: 'patch'
type: string
skip_tests:
description: 'Skip tests (emergency only)'
required: false
default: false
type: boolean
force_version:
description: 'Force specific version (optional)'
required: false
type: string
env:
NPM_REGISTRY: https://registry.npmjs.org/
PACKAGE_NAME: '@hive-academy/anubis'
DATABASE_URL: 'file:./prisma/.anubis/workflow.db'
jobs:
validate-and-publish:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# Need full history for version bumping
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
registry-url: ${{ env.NPM_REGISTRY }}
- name: Install dependencies
run: npm ci
- name: Run tests
if: ${{ !inputs.skip_tests }}
run: npm test
# ===================================================================
# π€ INTELLIGENT VERSION MANAGEMENT
# ===================================================================
- name: Intelligent version resolution
id: version_resolution
run: |
echo "π€ Starting intelligent version resolution..."
# Get current versions
CURRENT_NPM_VERSION=$(npm view ${{ env.PACKAGE_NAME }} version 2>/dev/null || echo "0.0.0")
CURRENT_LOCAL_VERSION=$(node -p "require('./package.json').version")
echo "π¦ Current NPM version: $CURRENT_NPM_VERSION"
echo "π¦ Current local version: $CURRENT_LOCAL_VERSION"
# Store original versions
echo "npm_version=$CURRENT_NPM_VERSION" >> $GITHUB_OUTPUT
echo "local_version=$CURRENT_LOCAL_VERSION" >> $GITHUB_OUTPUT
# Determine target version
TARGET_VERSION="$CURRENT_LOCAL_VERSION"
VERSION_STRATEGY="use-local"
# Handle forced version
if [ -n "${{ inputs.force_version }}" ]; then
TARGET_VERSION="${{ inputs.force_version }}"
VERSION_STRATEGY="forced"
echo "π― Using forced version: $TARGET_VERSION"
# Handle workflow dispatch (manual bump)
elif [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.version_type }}" ]; then
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
# Bump version
npm version ${{ inputs.version_type }} --no-git-tag-version
TARGET_VERSION=$(node -p "require('./package.json').version")
VERSION_STRATEGY="manual-bump"
echo "π Version bumped (${{ inputs.version_type }}): $TARGET_VERSION"
fi
# Check for version conflicts
echo "π Checking version conflicts..."
if npm view ${{ env.PACKAGE_NAME }}@$TARGET_VERSION version >/dev/null 2>&1; then
echo "β οΈ Version conflict detected: $TARGET_VERSION already exists on NPM"
# Auto-resolve conflict by finding next available version
echo "π€ Auto-resolving version conflict..."
# Parse version components
IFS='.' read -r MAJOR MINOR PATCH <<< "$TARGET_VERSION"
# Try incrementing patch version until we find an available one
ATTEMPT_COUNT=0
MAX_ATTEMPTS=50
while [ $ATTEMPT_COUNT -lt $MAX_ATTEMPTS ]; do
NEW_PATCH=$((PATCH + ATTEMPT_COUNT + 1))
CANDIDATE_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
echo "π Testing version: $CANDIDATE_VERSION"
if ! npm view ${{ env.PACKAGE_NAME }}@$CANDIDATE_VERSION version >/dev/null 2>&1; then
TARGET_VERSION="$CANDIDATE_VERSION"
VERSION_STRATEGY="auto-resolved"
echo "β
Found available version: $TARGET_VERSION"
break
fi
ATTEMPT_COUNT=$((ATTEMPT_COUNT + 1))
done
if [ $ATTEMPT_COUNT -eq $MAX_ATTEMPTS ]; then
echo "β Could not find available version after $MAX_ATTEMPTS attempts"
echo "π‘ Consider using a manual version bump or different strategy"
exit 1
fi
# Update package.json with resolved version
npm version $TARGET_VERSION --no-git-tag-version
echo "π Updated package.json to version: $TARGET_VERSION"
else
echo "β
No version conflict - proceeding with: $TARGET_VERSION"
fi
# Store final version info
echo "target_version=$TARGET_VERSION" >> $GITHUB_OUTPUT
echo "version_strategy=$VERSION_STRATEGY" >> $GITHUB_OUTPUT
echo "conflict_resolved=$([[ $VERSION_STRATEGY == "auto-resolved" ]] && echo "true" || echo "false")" >> $GITHUB_OUTPUT
echo "π― Final target version: $TARGET_VERSION"
echo "π Version strategy: $VERSION_STRATEGY"
- name: Commit version changes (if needed)
if: steps.version_resolution.outputs.conflict_resolved == 'true' || github.event_name == 'workflow_dispatch'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
if [ -n "$(git diff --name-only)" ]; then
git add package.json package-lock.json
git commit -m "π€ Auto-resolve version conflict: ${{ steps.version_resolution.outputs.target_version }}
- Strategy: ${{ steps.version_resolution.outputs.version_strategy }}
- Previous NPM: ${{ steps.version_resolution.outputs.npm_version }}
- Previous Local: ${{ steps.version_resolution.outputs.local_version }}
- Resolved: ${{ steps.version_resolution.outputs.target_version }}"
echo "π Committed version resolution changes"
fi
# ===================================================================
# π PRE-PUBLISHING CHECKLIST AUTOMATION
# ===================================================================
- name: Database template verification
run: |
echo "π Verifying pre-seeded database template..."
if [ -f "prisma/.anubis/workflow.db" ]; then
DB_SIZE=$(ls -lh prisma/.anubis/workflow.db | awk '{print $5}')
echo "β
Pre-seeded database found: $DB_SIZE"
# Verify it's approximately the expected size (~589kB)
DB_SIZE_BYTES=$(stat -f%z prisma/.anubis/workflow.db 2>/dev/null || stat -c%s prisma/.anubis/workflow.db)
if [ $DB_SIZE_BYTES -gt 500000 ] && [ $DB_SIZE_BYTES -lt 700000 ]; then
echo "β
Database size is within expected range (500kB-700kB)"
else
echo "β οΈ Database size ($DB_SIZE_BYTES bytes) is outside expected range"
exit 1
fi
else
echo "β Pre-seeded database not found at prisma/.anubis/workflow.db"
echo "π§ Regenerating database template..."
mkdir -p prisma/data
echo "π§ Setting up database URL: $DATABASE_URL"
echo "ποΈ Creating database schema..."
npx prisma db push --accept-data-loss
echo "π± Seeding database..."
npm run db:seed
if [ -f "prisma/.anubis/workflow.db" ]; then
echo "β
Database template regenerated successfully"
else
echo "β Failed to generate database template"
exit 1
fi
fi
- name: Package size verification
run: |
echo "π¦ Verifying package size..."
# Dry run to check what will be included
echo "π Package contents (dry run):"
npm pack --dry-run
# Create actual package and check size
PACKAGE_FILE=$(npm pack)
PACKAGE_SIZE=$(ls -lh $PACKAGE_FILE | awk '{print $5}')
PACKAGE_SIZE_BYTES=$(stat -f%z $PACKAGE_FILE 2>/dev/null || stat -c%s $PACKAGE_FILE)
echo "π¦ Package file: $PACKAGE_FILE"
echo "π¦ Package size: $PACKAGE_SIZE ($PACKAGE_SIZE_BYTES bytes)"
# Verify size is reasonable (~465kB, definitely under 10MB)
if [ $PACKAGE_SIZE_BYTES -gt 10485760 ]; then # 10MB
echo "β Package too large! Size: $PACKAGE_SIZE"
echo "π‘ Check if 'generated/**/*' is properly excluded in package.json"
exit 1
elif [ $PACKAGE_SIZE_BYTES -gt 1048576 ]; then # 1MB
echo "β οΈ Package larger than expected: $PACKAGE_SIZE"
echo "π‘ Expected size: ~465kB. Current size may include unnecessary files."
else
echo "β
Package size is reasonable: $PACKAGE_SIZE"
fi
# Clean up test package
rm $PACKAGE_FILE
- name: Build verification
run: |
echo "π§ Running clean build..."
# This runs prepublishOnly automatically, but let's be explicit
npm run prepublishOnly
# Verify dist directory exists and has content
if [ -d "dist" ] && [ "$(ls -A dist)" ]; then
echo "β
Build successful - dist directory populated"
echo "π Dist contents:"
ls -la dist/
else
echo "β Build failed - dist directory missing or empty"
exit 1
fi
# Verify CLI entry point exists
if [ -f "dist/cli.js" ]; then
echo "β
CLI entry point exists: dist/cli.js"
else
echo "β CLI entry point missing: dist/cli.js"
exit 1
fi
- name: Prisma validation
run: |
echo "π Validating Prisma schema..."
npx prisma validate
echo "β
Prisma schema validation passed"
# Verify generated folder is excluded
if [ -d "generated" ]; then
echo "β οΈ Generated folder exists - verifying it's excluded from package"
if grep -q '"generated/\*\*/\*"' package.json; then
echo "β Generated folder not properly excluded in package.json files array"
exit 1
fi
fi
# ===================================================================
# π PUBLISHING PROCESS
# ===================================================================
- name: Publish to NPM (dry run for PRs)
if: github.event_name == 'pull_request'
run: |
echo "π§ͺ NPM Publish Dry Run (PR)"
npm publish --dry-run
echo "β
Dry run successful - package would publish correctly"
- name: Publish to NPM
if: github.event_name != 'pull_request'
run: |
echo "π Publishing to NPM..."
TARGET_VERSION="${{ steps.version_resolution.outputs.target_version }}"
# Final conflict check before publish (double safety)
if npm view ${{ env.PACKAGE_NAME }}@$TARGET_VERSION version >/dev/null 2>&1; then
echo "β CRITICAL: Version $TARGET_VERSION still conflicts!"
echo "This should not happen after version resolution."
exit 1
fi
npm publish --provenance
echo "β
Successfully published ${{ env.PACKAGE_NAME }}@$TARGET_VERSION"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# ===================================================================
# π POST-PUBLISH VERIFICATION (Simplified)
# ===================================================================
- name: Post-publish verification
if: github.event_name != 'pull_request'
run: |
echo "π Verifying NPM publication..."
# Wait for NPM registry propagation
echo "β³ Waiting for NPM registry propagation (30 seconds)..."
sleep 30
# Simple verification - just check if the package exists
TARGET_VERSION="${{ steps.version_resolution.outputs.target_version }}"
# Try to fetch package info (this is more reliable than version comparison)
if npm view ${{ env.PACKAGE_NAME }}@$TARGET_VERSION version >/dev/null 2>&1; then
echo "β
Package ${{ env.PACKAGE_NAME }}@$TARGET_VERSION is available on NPM"
else
echo "β οΈ Package may still be propagating across NPM CDN"
echo "β
Publication completed successfully (registry propagation may take a few minutes)"
fi
- name: Test published package
if: github.event_name != 'pull_request'
run: |
echo "π§ͺ Testing published package..."
# Create a temporary directory for testing
mkdir -p /tmp/npm-test
cd /tmp/npm-test
# Test package installation and basic functionality
echo "π¦ Testing package installation..."
TARGET_VERSION="${{ steps.version_resolution.outputs.target_version }}"
npx ${{ env.PACKAGE_NAME }}@$TARGET_VERSION --version
echo "β
Published package test successful"
- name: Generate package health report
if: github.event_name != 'pull_request'
run: |
echo "π Generating package health report..."
# Get package info
NPM_INFO=$(npm view ${{ env.PACKAGE_NAME }} --json)
VERSION=$(echo $NPM_INFO | jq -r '.version')
SIZE=$(echo $NPM_INFO | jq -r '.dist.unpackedSize // "unknown"')
TARBALL_SIZE=$(echo $NPM_INFO | jq -r '.dist.fileCount // "unknown"')
echo "## π¦ NPM Package Health Report" >> $GITHUB_STEP_SUMMARY
echo "- **Package:** \`${{ env.PACKAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** \`$VERSION\`" >> $GITHUB_STEP_SUMMARY
echo "- **Version Strategy:** ${{ steps.version_resolution.outputs.version_strategy }}" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.version_resolution.outputs.conflict_resolved }}" = "true" ]; then
echo "- **Conflict Resolution:** β
Auto-resolved from \`${{ steps.version_resolution.outputs.local_version }}\` to \`$VERSION\`" >> $GITHUB_STEP_SUMMARY
fi
echo "- **Unpacked Size:** $SIZE bytes" >> $GITHUB_STEP_SUMMARY
echo "- **Registry:** NPM" >> $GITHUB_STEP_SUMMARY
echo "- **Provenance:** β
Enabled" >> $GITHUB_STEP_SUMMARY
echo "- **Database Template:** β
Pre-seeded (~589kB)" >> $GITHUB_STEP_SUMMARY
echo "- **Runtime:** Prisma generation + database copying" >> $GITHUB_STEP_SUMMARY
- name: Comment PR with package details
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const output = `## π¦ NPM Package Build Results
The NPM package has been built and validated successfully for this PR!
**Package Details:**
- **Name:** \`${{ env.PACKAGE_NAME }}\`
- **Current NPM Version:** \`${{ steps.version_resolution.outputs.npm_version }}\`
- **Local Version:** \`${{ steps.version_resolution.outputs.local_version }}\`
- **Target Version:** \`${{ steps.version_resolution.outputs.target_version }}\`
- **Version Strategy:** ${{ steps.version_resolution.outputs.version_strategy }}
- **Size:** Optimized (~465kB, 99.5% reduction from 86MB)
- **Database:** Pre-seeded workflow.db included
**Validation Results:**
- β
Database template verified (~589kB)
- β
Package size within limits
- β
Build artifacts generated
- β
Prisma schema valid
- β
Dry run publish successful
${steps.version_resolution.outputs.conflict_resolved === 'true' ? '- β
Version conflict auto-resolved' : ''}
**Test Installation (after merge):**
\`\`\`bash
# Global installation
npm install -g ${{ env.PACKAGE_NAME }}@${{ steps.version_resolution.outputs.target_version }}
# Or run directly
npx ${{ env.PACKAGE_NAME }}@${{ steps.version_resolution.outputs.target_version }}
# MCP Server usage
npx ${{ env.PACKAGE_NAME }}@${{ steps.version_resolution.outputs.target_version }}
\`\`\`
**Claude Desktop MCP Configuration:**
\`\`\`json
{
"mcpServers": {
"anubis": {
"command": "npx",
"args": ["${{ env.PACKAGE_NAME }}@${{ steps.version_resolution.outputs.target_version }}"]
}
}
}
\`\`\``;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
- name: Push version changes to repository
if: steps.version_resolution.outputs.conflict_resolved == 'true' || github.event_name == 'workflow_dispatch'
run: |
# Push the version changes back to the repository
git push origin HEAD:${{ github.ref_name }}
echo "π€ Pushed version changes to repository"
- name: Create GitHub release (if tag)
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
## π Release Notes
### π¦ NPM Package
- **Package:** `${{ env.PACKAGE_NAME }}`
- **Version:** `${{ steps.version_resolution.outputs.target_version }}`
- **Version Strategy:** ${{ steps.version_resolution.outputs.version_strategy }}
- **Size:** ~465kB (optimized)
- **Database:** Pre-seeded workflow.db
- **Runtime:** Prisma generation + database copying
### π§ Installation
```bash
npm install -g ${{ env.PACKAGE_NAME }}@${{ steps.version_resolution.outputs.target_version }}
# or
npx ${{ env.PACKAGE_NAME }}@${{ steps.version_resolution.outputs.target_version }}
```
### π³ Docker
```bash
docker pull hiveacademy/anubis:latest
```
See CHANGELOG.md for detailed changes.
draft: false
prerelease: false