mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-03-30 22:55:52 +00:00
- Remove build-node and deploy-node jobs (Node staging on port 81) - Rename build-go → build and deploy-go → deploy - Update publish job to depend only on deploy (not deploy-node) - Update README badges to show Go coverage (server/ingestor) instead of Node backend - Remove Node staging references from deployment summary - node-test job remains (frontend tests + Playwright) Pipeline is now: node-test + go-test → build → deploy → publish Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
384 lines
17 KiB
YAML
384 lines
17 KiB
YAML
name: Deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [master]
|
|
paths-ignore:
|
|
- '**.md'
|
|
- 'LICENSE'
|
|
- '.gitignore'
|
|
- 'docs/**'
|
|
|
|
concurrency:
|
|
group: deploy
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
|
|
# Pipeline:
|
|
# node-test (frontend tests) ──┐
|
|
# go-test ├──→ build → deploy → publish
|
|
# └─ (both wait)
|
|
#
|
|
# Proto validation flow:
|
|
# 1. go-test job: verify .proto files compile (syntax check)
|
|
# 2. deploy job: capture fresh fixtures from prod, validate protos match actual API responses
|
|
|
|
jobs:
|
|
# ───────────────────────────────────────────────────────────────
|
|
# 1. Go Build & Test — compiles + tests Go modules, coverage badges
|
|
# ───────────────────────────────────────────────────────────────
|
|
go-test:
|
|
name: "✅ Go Build & Test"
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Go 1.22
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.22'
|
|
cache-dependency-path: |
|
|
cmd/server/go.sum
|
|
cmd/ingestor/go.sum
|
|
|
|
- name: Build and test Go server (with coverage)
|
|
run: |
|
|
set -e -o pipefail
|
|
cd cmd/server
|
|
go build .
|
|
go test -coverprofile=server-coverage.out ./... 2>&1 | tee server-test.log
|
|
echo "--- Go Server Coverage ---"
|
|
go tool cover -func=server-coverage.out | tail -1
|
|
|
|
- name: Build and test Go ingestor (with coverage)
|
|
run: |
|
|
set -e -o pipefail
|
|
cd cmd/ingestor
|
|
go build .
|
|
go test -coverprofile=ingestor-coverage.out ./... 2>&1 | tee ingestor-test.log
|
|
echo "--- Go Ingestor Coverage ---"
|
|
go tool cover -func=ingestor-coverage.out | tail -1
|
|
|
|
- name: Verify proto syntax (all .proto files compile)
|
|
run: |
|
|
set -e
|
|
echo "Installing protoc..."
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y protobuf-compiler
|
|
|
|
echo "Checking proto syntax..."
|
|
for proto in proto/*.proto; do
|
|
echo " ✓ $(basename "$proto")"
|
|
protoc --proto_path=proto --descriptor_set_out=/dev/null "$proto"
|
|
done
|
|
echo "✅ All .proto files are syntactically valid"
|
|
|
|
- name: Generate Go coverage badges
|
|
if: always()
|
|
run: |
|
|
mkdir -p .badges
|
|
|
|
# Parse server coverage
|
|
SERVER_COV="0"
|
|
if [ -f cmd/server/server-coverage.out ]; then
|
|
SERVER_COV=$(cd cmd/server && go tool cover -func=server-coverage.out | tail -1 | grep -oP '[\d.]+(?=%)')
|
|
fi
|
|
SERVER_COLOR="red"
|
|
if [ "$(echo "$SERVER_COV >= 80" | bc -l 2>/dev/null)" = "1" ]; then
|
|
SERVER_COLOR="green"
|
|
elif [ "$(echo "$SERVER_COV >= 60" | bc -l 2>/dev/null)" = "1" ]; then
|
|
SERVER_COLOR="yellow"
|
|
fi
|
|
echo "{\"schemaVersion\":1,\"label\":\"go server coverage\",\"message\":\"${SERVER_COV}%\",\"color\":\"${SERVER_COLOR}\"}" > .badges/go-server-coverage.json
|
|
echo "Go server coverage: ${SERVER_COV}% (${SERVER_COLOR})"
|
|
|
|
# Parse ingestor coverage
|
|
INGESTOR_COV="0"
|
|
if [ -f cmd/ingestor/ingestor-coverage.out ]; then
|
|
INGESTOR_COV=$(cd cmd/ingestor && go tool cover -func=ingestor-coverage.out | tail -1 | grep -oP '[\d.]+(?=%)')
|
|
fi
|
|
INGESTOR_COLOR="red"
|
|
if [ "$(echo "$INGESTOR_COV >= 80" | bc -l 2>/dev/null)" = "1" ]; then
|
|
INGESTOR_COLOR="green"
|
|
elif [ "$(echo "$INGESTOR_COV >= 60" | bc -l 2>/dev/null)" = "1" ]; then
|
|
INGESTOR_COLOR="yellow"
|
|
fi
|
|
echo "{\"schemaVersion\":1,\"label\":\"go ingestor coverage\",\"message\":\"${INGESTOR_COV}%\",\"color\":\"${INGESTOR_COLOR}\"}" > .badges/go-ingestor-coverage.json
|
|
echo "Go ingestor coverage: ${INGESTOR_COV}% (${INGESTOR_COLOR})"
|
|
|
|
echo "## Go Coverage" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Module | Coverage |" >> $GITHUB_STEP_SUMMARY
|
|
echo "|--------|----------|" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Server | ${SERVER_COV}% |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Ingestor | ${INGESTOR_COV}% |" >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: Upload Go coverage badges
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: go-badges
|
|
path: .badges/go-*.json
|
|
retention-days: 1
|
|
if-no-files-found: ignore
|
|
|
|
# ───────────────────────────────────────────────────────────────
|
|
# 2. Node.js Tests — backend unit tests + Playwright E2E, coverage
|
|
# ───────────────────────────────────────────────────────────────
|
|
node-test:
|
|
name: "🧪 Node.js Tests"
|
|
runs-on: self-hosted
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 2
|
|
|
|
- name: Set up Node.js 22
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci --production=false
|
|
|
|
- name: Detect changed files
|
|
id: changes
|
|
run: |
|
|
BACKEND=$(git diff --name-only HEAD~1 | grep -cE '^(server|db|decoder|packet-store|server-helpers|iata-coords)\.js$' || true)
|
|
FRONTEND=$(git diff --name-only HEAD~1 | grep -cE '^public/' || true)
|
|
TESTS=$(git diff --name-only HEAD~1 | grep -cE '^test-|^tools/' || true)
|
|
CI=$(git diff --name-only HEAD~1 | grep -cE '\.github/|package\.json|test-all\.sh|scripts/' || true)
|
|
# If CI/test infra changed, run everything
|
|
if [ "$CI" -gt 0 ]; then BACKEND=1; FRONTEND=1; fi
|
|
# If test files changed, run everything
|
|
if [ "$TESTS" -gt 0 ]; then BACKEND=1; FRONTEND=1; fi
|
|
echo "backend=$([[ $BACKEND -gt 0 ]] && echo true || echo false)" >> $GITHUB_OUTPUT
|
|
echo "frontend=$([[ $FRONTEND -gt 0 ]] && echo true || echo false)" >> $GITHUB_OUTPUT
|
|
echo "Changes: backend=$BACKEND frontend=$FRONTEND tests=$TESTS ci=$CI"
|
|
|
|
- name: Run backend tests with coverage
|
|
if: steps.changes.outputs.backend == 'true'
|
|
run: |
|
|
npx c8 --reporter=text-summary --reporter=text sh test-all.sh 2>&1 | tee test-output.txt
|
|
|
|
TOTAL_PASS=$(grep -oP '\d+(?= passed)' test-output.txt | awk '{s+=$1} END {print s}')
|
|
TOTAL_FAIL=$(grep -oP '\d+(?= failed)' test-output.txt | awk '{s+=$1} END {print s}')
|
|
BE_COVERAGE=$(grep 'Statements' test-output.txt | tail -1 | grep -oP '[\d.]+(?=%)')
|
|
|
|
mkdir -p .badges
|
|
BE_COLOR="red"
|
|
[ "$(echo "$BE_COVERAGE > 60" | bc -l 2>/dev/null)" = "1" ] && BE_COLOR="yellow"
|
|
[ "$(echo "$BE_COVERAGE > 80" | bc -l 2>/dev/null)" = "1" ] && BE_COLOR="brightgreen"
|
|
echo "{\"schemaVersion\":1,\"label\":\"backend tests\",\"message\":\"${TOTAL_PASS} passed\",\"color\":\"brightgreen\"}" > .badges/backend-tests.json
|
|
echo "{\"schemaVersion\":1,\"label\":\"backend coverage\",\"message\":\"${BE_COVERAGE}%\",\"color\":\"${BE_COLOR}\"}" > .badges/backend-coverage.json
|
|
|
|
echo "## Backend: ${TOTAL_PASS} tests, ${BE_COVERAGE}% coverage" >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: Run backend tests (quick, no coverage)
|
|
if: steps.changes.outputs.backend == 'false'
|
|
run: npm run test:unit
|
|
|
|
- name: Install Playwright browser
|
|
if: steps.changes.outputs.frontend == 'true'
|
|
run: npx playwright install chromium --with-deps 2>/dev/null || true
|
|
|
|
- name: Instrument frontend JS for coverage
|
|
if: steps.changes.outputs.frontend == 'true'
|
|
run: sh scripts/instrument-frontend.sh
|
|
|
|
- name: Start instrumented test server on port 13581
|
|
if: steps.changes.outputs.frontend == 'true'
|
|
run: |
|
|
# Kill any stale server on 13581
|
|
fuser -k 13581/tcp 2>/dev/null || true
|
|
sleep 2
|
|
COVERAGE=1 PORT=13581 node server.js &
|
|
echo $! > .server.pid
|
|
echo "Server PID: $(cat .server.pid)"
|
|
# Health-check poll loop (up to 30s)
|
|
for i in $(seq 1 30); do
|
|
if curl -sf http://localhost:13581/api/stats > /dev/null 2>&1; then
|
|
echo "Server ready after ${i}s"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 30 ]; then
|
|
echo "Server failed to start within 30s"
|
|
echo "Last few lines from server logs:"
|
|
ps aux | grep "PORT=13581" || echo "No server process found"
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
- name: Run Playwright E2E tests
|
|
if: steps.changes.outputs.frontend == 'true'
|
|
run: BASE_URL=http://localhost:13581 node test-e2e-playwright.js 2>&1 | tee e2e-output.txt
|
|
|
|
- name: Collect frontend coverage report
|
|
if: always() && steps.changes.outputs.frontend == 'true'
|
|
run: |
|
|
BASE_URL=http://localhost:13581 node scripts/collect-frontend-coverage.js 2>&1 | tee fe-coverage-output.txt
|
|
|
|
E2E_PASS=$(grep -oP '[0-9]+(?=/)' e2e-output.txt | tail -1)
|
|
|
|
mkdir -p .badges
|
|
if [ -f .nyc_output/frontend-coverage.json ]; then
|
|
npx nyc report --reporter=text-summary --reporter=text 2>&1 | tee fe-report.txt
|
|
FE_COVERAGE=$(grep 'Statements' fe-report.txt | head -1 | grep -oP '[\d.]+(?=%)' || echo "0")
|
|
FE_COVERAGE=${FE_COVERAGE:-0}
|
|
FE_COLOR="red"
|
|
[ "$(echo "$FE_COVERAGE > 50" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="yellow"
|
|
[ "$(echo "$FE_COVERAGE > 80" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="brightgreen"
|
|
echo "{\"schemaVersion\":1,\"label\":\"frontend coverage\",\"message\":\"${FE_COVERAGE}%\",\"color\":\"${FE_COLOR}\"}" > .badges/frontend-coverage.json
|
|
echo "## Frontend: ${FE_COVERAGE}% coverage" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
echo "{\"schemaVersion\":1,\"label\":\"frontend tests\",\"message\":\"${E2E_PASS:-0} E2E passed\",\"color\":\"brightgreen\"}" > .badges/frontend-tests.json
|
|
|
|
- name: Stop test server
|
|
if: always() && steps.changes.outputs.frontend == 'true'
|
|
run: |
|
|
if [ -f .server.pid ]; then
|
|
kill $(cat .server.pid) 2>/dev/null || true
|
|
rm -f .server.pid
|
|
echo "Server stopped"
|
|
fi
|
|
|
|
- name: Run frontend E2E (quick, no coverage)
|
|
if: steps.changes.outputs.frontend == 'false'
|
|
run: |
|
|
fuser -k 13581/tcp 2>/dev/null || true
|
|
PORT=13581 node server.js &
|
|
SERVER_PID=$!
|
|
sleep 5
|
|
BASE_URL=http://localhost:13581 node test-e2e-playwright.js || true
|
|
kill $SERVER_PID 2>/dev/null || true
|
|
|
|
- name: Upload Node.js test badges
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: node-badges
|
|
path: .badges/
|
|
retention-days: 1
|
|
if-no-files-found: ignore
|
|
|
|
# ───────────────────────────────────────────────────────────────
|
|
# 3. Build Docker Image
|
|
# ───────────────────────────────────────────────────────────────
|
|
build:
|
|
name: "🏗️ Build Docker Image"
|
|
needs: [go-test]
|
|
runs-on: self-hosted
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Node.js 22
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
- name: Build Go Docker image
|
|
run: |
|
|
echo "${GITHUB_SHA::7}" > .git-commit
|
|
APP_VERSION=$(node -p "require('./package.json').version") \
|
|
GIT_COMMIT="${GITHUB_SHA::7}" \
|
|
docker compose --profile staging-go build staging-go
|
|
echo "Built Go staging image"
|
|
|
|
# ───────────────────────────────────────────────────────────────
|
|
# 4. Deploy Staging — start on port 82, healthcheck, smoke test
|
|
# ───────────────────────────────────────────────────────────────
|
|
deploy:
|
|
name: "🚀 Deploy Staging"
|
|
needs: [build]
|
|
runs-on: self-hosted
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Start staging on port 82
|
|
run: |
|
|
# Force remove stale containers
|
|
docker rm -f meshcore-staging-go 2>/dev/null || true
|
|
# Clean up stale ports
|
|
fuser -k 82/tcp 2>/dev/null || true
|
|
docker compose --profile staging-go up -d staging-go
|
|
|
|
- name: Healthcheck staging container
|
|
run: |
|
|
for i in $(seq 1 120); do
|
|
HEALTH=$(docker inspect meshcore-staging-go --format '{{.State.Health.Status}}' 2>/dev/null || echo "starting")
|
|
if [ "$HEALTH" = "healthy" ]; then
|
|
echo "Staging healthy after ${i}s"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 120 ]; then
|
|
echo "Staging failed health check after 120s"
|
|
docker logs meshcore-staging-go --tail 50
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
- name: Smoke test staging API
|
|
run: |
|
|
if curl -sf http://localhost:82/api/stats | grep -q engine; then
|
|
echo "Staging verified — engine field present ✅"
|
|
else
|
|
echo "Staging /api/stats did not return engine field"
|
|
exit 1
|
|
fi
|
|
|
|
# ───────────────────────────────────────────────────────────────
|
|
# 5. Publish Badges & Summary
|
|
# ───────────────────────────────────────────────────────────────
|
|
publish:
|
|
name: "📝 Publish Badges & Summary"
|
|
needs: [deploy]
|
|
runs-on: self-hosted
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Download Go coverage badges
|
|
continue-on-error: true
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: go-badges
|
|
path: .badges/
|
|
|
|
- name: Download Node.js test badges
|
|
continue-on-error: true
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: node-badges
|
|
path: .badges/
|
|
|
|
- name: Publish coverage badges to repo
|
|
continue-on-error: true
|
|
run: |
|
|
git config user.name "github-actions"
|
|
git config user.email "actions@github.com"
|
|
git remote set-url origin https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git
|
|
git add .badges/ -f
|
|
git diff --cached --quiet || (git commit -m "ci: update test badges [skip ci]" && git push) || echo "Badge push failed"
|
|
|
|
- name: Post deployment summary
|
|
run: |
|
|
echo "## Staging Deployed ✓" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "**Commit:** \`$(git rev-parse --short HEAD)\` — $(git log -1 --format=%s)" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "**Staging:** http://<VM_HOST>:82" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "To promote to production:" >> $GITHUB_STEP_SUMMARY
|
|
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
|
|
echo "ssh deploy@\$VM_HOST" >> $GITHUB_STEP_SUMMARY
|
|
echo "cd /opt/meshcore-deploy" >> $GITHUB_STEP_SUMMARY
|
|
echo "./manage.sh promote" >> $GITHUB_STEP_SUMMARY
|
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|