mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-09 11:51:41 +00:00
chore: normalize all files to LF line endings
This commit is contained in:
+378
-378
@@ -1,378 +1,378 @@
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
STAGING_COMPOSE_FILE: docker-compose.staging.yml
|
||||
STAGING_SERVICE: staging-go
|
||||
STAGING_CONTAINER: corescope-staging-go
|
||||
|
||||
# Pipeline (sequential, fail-fast):
|
||||
# go-test → e2e-test → build → deploy → publish
|
||||
# PRs stop after build. Master continues to deploy + publish.
|
||||
|
||||
jobs:
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 1. Go Build & Test
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
go-test:
|
||||
name: "✅ Go Build & Test"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Clean Go module cache
|
||||
run: rm -rf ~/go/pkg/mod 2>/dev/null || true
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v6
|
||||
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
|
||||
run: |
|
||||
set -e
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y protobuf-compiler
|
||||
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: success()
|
||||
run: |
|
||||
mkdir -p .badges
|
||||
|
||||
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
|
||||
|
||||
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 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: success()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: go-badges
|
||||
path: .badges/go-*.json
|
||||
retention-days: 1
|
||||
if-no-files-found: ignore
|
||||
include-hidden-files: true
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 2. Playwright E2E Tests (against Go server with fixture DB)
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
e2e-test:
|
||||
name: "🎭 Playwright E2E Tests"
|
||||
needs: [go-test]
|
||||
runs-on: [self-hosted, Linux]
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js 22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Clean Go module cache
|
||||
run: rm -rf ~/go/pkg/mod 2>/dev/null || true
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache-dependency-path: cmd/server/go.sum
|
||||
|
||||
- name: Build Go server
|
||||
run: |
|
||||
cd cmd/server
|
||||
go build -o ../../corescope-server .
|
||||
echo "Go server built successfully"
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci --production=false
|
||||
|
||||
- name: Install Playwright browser
|
||||
run: |
|
||||
npx playwright install chromium 2>/dev/null || true
|
||||
npx playwright install-deps chromium 2>/dev/null || true
|
||||
|
||||
- name: Instrument frontend JS for coverage
|
||||
run: sh scripts/instrument-frontend.sh
|
||||
|
||||
- name: Start Go server with fixture DB
|
||||
run: |
|
||||
fuser -k 13581/tcp 2>/dev/null || true
|
||||
sleep 1
|
||||
./corescope-server -port 13581 -db test-fixtures/e2e-fixture.db -public public-instrumented &
|
||||
echo $! > .server.pid
|
||||
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"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run Playwright E2E tests (fail-fast)
|
||||
run: |
|
||||
BASE_URL=http://localhost:13581 node test-e2e-playwright.js 2>&1 | tee e2e-output.txt
|
||||
|
||||
- name: Collect frontend coverage (parallel)
|
||||
if: success() && github.event_name == 'push'
|
||||
run: |
|
||||
BASE_URL=http://localhost:13581 node scripts/collect-frontend-coverage.js 2>&1 | tee fe-coverage-output.txt || true
|
||||
|
||||
- name: Generate frontend coverage badges
|
||||
if: success()
|
||||
run: |
|
||||
E2E_PASS=$(grep -oP '[0-9]+(?=/)' e2e-output.txt | tail -1 || echo "0")
|
||||
|
||||
mkdir -p .badges
|
||||
if [ -f .nyc_output/frontend-coverage.json ] || [ -f .nyc_output/e2e-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\":\"e2e tests\",\"message\":\"${E2E_PASS:-0} passed\",\"color\":\"brightgreen\"}" > .badges/e2e-tests.json
|
||||
|
||||
- name: Stop test server
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f .server.pid ]; then
|
||||
kill $(cat .server.pid) 2>/dev/null || true
|
||||
rm -f .server.pid
|
||||
fi
|
||||
|
||||
- name: Upload E2E badges
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: e2e-badges
|
||||
path: .badges/
|
||||
retention-days: 1
|
||||
if-no-files-found: ignore
|
||||
include-hidden-files: true
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 3. Build Docker Image
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
build:
|
||||
name: "🏗️ Build Docker Image"
|
||||
needs: [e2e-test]
|
||||
runs-on: [self-hosted, Linux]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Node.js 22
|
||||
uses: actions/setup-node@v5
|
||||
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}" \
|
||||
APP_VERSION=$(grep -oP 'APP_VERSION:-\K[^}]+' docker-compose.yml | head -1 || echo "3.0.0")
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD)
|
||||
BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
export APP_VERSION GIT_COMMIT BUILD_TIME
|
||||
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging build "$STAGING_SERVICE"
|
||||
echo "Built Go staging image ✅"
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 4. Deploy Staging (master only)
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
deploy:
|
||||
name: "🚀 Deploy Staging"
|
||||
if: github.event_name == 'push'
|
||||
needs: [build]
|
||||
runs-on: [self-hosted, Linux]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Deploy staging
|
||||
run: |
|
||||
# Stop old container and release memory
|
||||
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging down --timeout 30 2>/dev/null || true
|
||||
|
||||
# Wait for container to be fully gone and OS to reclaim memory (3GB limit)
|
||||
for i in $(seq 1 15); do
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q 'corescope-staging-go'; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
sleep 5 # extra pause for OS memory reclaim
|
||||
|
||||
# Ensure staging data dir exists (config.json lives here, no separate file mount)
|
||||
STAGING_DATA="${STAGING_DATA_DIR:-$HOME/meshcore-staging-data}"
|
||||
mkdir -p "$STAGING_DATA"
|
||||
|
||||
# If no config exists, copy the example (CI doesn't have a real prod config)
|
||||
if [ ! -f "$STAGING_DATA/config.json" ]; then
|
||||
echo "Staging config missing — copying config.example.json"
|
||||
cp config.example.json "$STAGING_DATA/config.json" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging up -d staging-go
|
||||
|
||||
- name: Healthcheck staging container
|
||||
run: |
|
||||
for i in $(seq 1 120); do
|
||||
HEALTH=$(docker inspect corescope-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 corescope-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 (master only)
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
publish:
|
||||
name: "📝 Publish Badges & Summary"
|
||||
if: github.event_name == 'push'
|
||||
needs: [deploy]
|
||||
runs-on: [self-hosted, Linux]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download Go coverage badges
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: go-badges
|
||||
path: .badges/
|
||||
|
||||
- name: Download E2E badges
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: e2e-badges
|
||||
path: .badges/
|
||||
|
||||
- name: Publish coverage badges to repo
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }}
|
||||
run: |
|
||||
# GITHUB_TOKEN cannot push to protected branches (required status checks).
|
||||
# Use admin PAT (BADGE_PUSH_TOKEN) via GitHub Contents API instead.
|
||||
for badge in .badges/*.json; do
|
||||
FILENAME=$(basename "$badge")
|
||||
FILEPATH=".badges/$FILENAME"
|
||||
CONTENT=$(base64 -w0 "$badge")
|
||||
CURRENT_SHA=$(gh api "repos/${{ github.repository }}/contents/$FILEPATH" --jq '.sha' 2>/dev/null || echo "")
|
||||
if [ -n "$CURRENT_SHA" ]; then
|
||||
gh api "repos/${{ github.repository }}/contents/$FILEPATH" \
|
||||
-X PUT \
|
||||
-f message="ci: update $FILENAME [skip ci]" \
|
||||
-f content="$CONTENT" \
|
||||
-f sha="$CURRENT_SHA" \
|
||||
-f branch="master" \
|
||||
--silent 2>&1 || echo "Failed to update $FILENAME"
|
||||
else
|
||||
gh api "repos/${{ github.repository }}/contents/$FILEPATH" \
|
||||
-X PUT \
|
||||
-f message="ci: update $FILENAME [skip ci]" \
|
||||
-f content="$CONTENT" \
|
||||
-f branch="master" \
|
||||
--silent 2>&1 || echo "Failed to create $FILENAME"
|
||||
fi
|
||||
done
|
||||
echo "Badge publish complete"
|
||||
|
||||
- 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
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
STAGING_COMPOSE_FILE: docker-compose.staging.yml
|
||||
STAGING_SERVICE: staging-go
|
||||
STAGING_CONTAINER: corescope-staging-go
|
||||
|
||||
# Pipeline (sequential, fail-fast):
|
||||
# go-test → e2e-test → build → deploy → publish
|
||||
# PRs stop after build. Master continues to deploy + publish.
|
||||
|
||||
jobs:
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 1. Go Build & Test
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
go-test:
|
||||
name: "✅ Go Build & Test"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Clean Go module cache
|
||||
run: rm -rf ~/go/pkg/mod 2>/dev/null || true
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v6
|
||||
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
|
||||
run: |
|
||||
set -e
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y protobuf-compiler
|
||||
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: success()
|
||||
run: |
|
||||
mkdir -p .badges
|
||||
|
||||
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
|
||||
|
||||
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 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: success()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: go-badges
|
||||
path: .badges/go-*.json
|
||||
retention-days: 1
|
||||
if-no-files-found: ignore
|
||||
include-hidden-files: true
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 2. Playwright E2E Tests (against Go server with fixture DB)
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
e2e-test:
|
||||
name: "🎭 Playwright E2E Tests"
|
||||
needs: [go-test]
|
||||
runs-on: [self-hosted, Linux]
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js 22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Clean Go module cache
|
||||
run: rm -rf ~/go/pkg/mod 2>/dev/null || true
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache-dependency-path: cmd/server/go.sum
|
||||
|
||||
- name: Build Go server
|
||||
run: |
|
||||
cd cmd/server
|
||||
go build -o ../../corescope-server .
|
||||
echo "Go server built successfully"
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci --production=false
|
||||
|
||||
- name: Install Playwright browser
|
||||
run: |
|
||||
npx playwright install chromium 2>/dev/null || true
|
||||
npx playwright install-deps chromium 2>/dev/null || true
|
||||
|
||||
- name: Instrument frontend JS for coverage
|
||||
run: sh scripts/instrument-frontend.sh
|
||||
|
||||
- name: Start Go server with fixture DB
|
||||
run: |
|
||||
fuser -k 13581/tcp 2>/dev/null || true
|
||||
sleep 1
|
||||
./corescope-server -port 13581 -db test-fixtures/e2e-fixture.db -public public-instrumented &
|
||||
echo $! > .server.pid
|
||||
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"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run Playwright E2E tests (fail-fast)
|
||||
run: |
|
||||
BASE_URL=http://localhost:13581 node test-e2e-playwright.js 2>&1 | tee e2e-output.txt
|
||||
|
||||
- name: Collect frontend coverage (parallel)
|
||||
if: success() && github.event_name == 'push'
|
||||
run: |
|
||||
BASE_URL=http://localhost:13581 node scripts/collect-frontend-coverage.js 2>&1 | tee fe-coverage-output.txt || true
|
||||
|
||||
- name: Generate frontend coverage badges
|
||||
if: success()
|
||||
run: |
|
||||
E2E_PASS=$(grep -oP '[0-9]+(?=/)' e2e-output.txt | tail -1 || echo "0")
|
||||
|
||||
mkdir -p .badges
|
||||
if [ -f .nyc_output/frontend-coverage.json ] || [ -f .nyc_output/e2e-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\":\"e2e tests\",\"message\":\"${E2E_PASS:-0} passed\",\"color\":\"brightgreen\"}" > .badges/e2e-tests.json
|
||||
|
||||
- name: Stop test server
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f .server.pid ]; then
|
||||
kill $(cat .server.pid) 2>/dev/null || true
|
||||
rm -f .server.pid
|
||||
fi
|
||||
|
||||
- name: Upload E2E badges
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: e2e-badges
|
||||
path: .badges/
|
||||
retention-days: 1
|
||||
if-no-files-found: ignore
|
||||
include-hidden-files: true
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 3. Build Docker Image
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
build:
|
||||
name: "🏗️ Build Docker Image"
|
||||
needs: [e2e-test]
|
||||
runs-on: [self-hosted, Linux]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Node.js 22
|
||||
uses: actions/setup-node@v5
|
||||
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}" \
|
||||
APP_VERSION=$(grep -oP 'APP_VERSION:-\K[^}]+' docker-compose.yml | head -1 || echo "3.0.0")
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD)
|
||||
BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
export APP_VERSION GIT_COMMIT BUILD_TIME
|
||||
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging build "$STAGING_SERVICE"
|
||||
echo "Built Go staging image ✅"
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 4. Deploy Staging (master only)
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
deploy:
|
||||
name: "🚀 Deploy Staging"
|
||||
if: github.event_name == 'push'
|
||||
needs: [build]
|
||||
runs-on: [self-hosted, Linux]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Deploy staging
|
||||
run: |
|
||||
# Stop old container and release memory
|
||||
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging down --timeout 30 2>/dev/null || true
|
||||
|
||||
# Wait for container to be fully gone and OS to reclaim memory (3GB limit)
|
||||
for i in $(seq 1 15); do
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q 'corescope-staging-go'; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
sleep 5 # extra pause for OS memory reclaim
|
||||
|
||||
# Ensure staging data dir exists (config.json lives here, no separate file mount)
|
||||
STAGING_DATA="${STAGING_DATA_DIR:-$HOME/meshcore-staging-data}"
|
||||
mkdir -p "$STAGING_DATA"
|
||||
|
||||
# If no config exists, copy the example (CI doesn't have a real prod config)
|
||||
if [ ! -f "$STAGING_DATA/config.json" ]; then
|
||||
echo "Staging config missing — copying config.example.json"
|
||||
cp config.example.json "$STAGING_DATA/config.json" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging up -d staging-go
|
||||
|
||||
- name: Healthcheck staging container
|
||||
run: |
|
||||
for i in $(seq 1 120); do
|
||||
HEALTH=$(docker inspect corescope-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 corescope-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 (master only)
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
publish:
|
||||
name: "📝 Publish Badges & Summary"
|
||||
if: github.event_name == 'push'
|
||||
needs: [deploy]
|
||||
runs-on: [self-hosted, Linux]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download Go coverage badges
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: go-badges
|
||||
path: .badges/
|
||||
|
||||
- name: Download E2E badges
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: e2e-badges
|
||||
path: .badges/
|
||||
|
||||
- name: Publish coverage badges to repo
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }}
|
||||
run: |
|
||||
# GITHUB_TOKEN cannot push to protected branches (required status checks).
|
||||
# Use admin PAT (BADGE_PUSH_TOKEN) via GitHub Contents API instead.
|
||||
for badge in .badges/*.json; do
|
||||
FILENAME=$(basename "$badge")
|
||||
FILEPATH=".badges/$FILENAME"
|
||||
CONTENT=$(base64 -w0 "$badge")
|
||||
CURRENT_SHA=$(gh api "repos/${{ github.repository }}/contents/$FILEPATH" --jq '.sha' 2>/dev/null || echo "")
|
||||
if [ -n "$CURRENT_SHA" ]; then
|
||||
gh api "repos/${{ github.repository }}/contents/$FILEPATH" \
|
||||
-X PUT \
|
||||
-f message="ci: update $FILENAME [skip ci]" \
|
||||
-f content="$CONTENT" \
|
||||
-f sha="$CURRENT_SHA" \
|
||||
-f branch="master" \
|
||||
--silent 2>&1 || echo "Failed to update $FILENAME"
|
||||
else
|
||||
gh api "repos/${{ github.repository }}/contents/$FILEPATH" \
|
||||
-X PUT \
|
||||
-f message="ci: update $FILENAME [skip ci]" \
|
||||
-f content="$CONTENT" \
|
||||
-f branch="master" \
|
||||
--silent 2>&1 || echo "Failed to create $FILENAME"
|
||||
fi
|
||||
done
|
||||
echo "Badge publish complete"
|
||||
|
||||
- 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
|
||||
|
||||
Reference in New Issue
Block a user