Files
meshcore-analyzer/.github/workflows/deploy.yml
T
Kpa-clawbot 494d3022f9 Partial fix for #1128: close audit gaps (z-scale, css-var lint, multi-viewport E2E, Bug 1+5 polish) (#1133)
## Partial fix for #1128 — closes the gaps PR #1131 left behind

PR #1131 was a partial fix for the packets-page layout chaos
(merged 2026-05-06 ~01:55 UTC, then the issue was reopened by the
maintainer). #1131 shipped Bug 4 (`--surface` definition), the
`.path-popover` flip + lower z-index, the debounced re-measure for
Bug 1, the `.filter-bar` row-gap + `.multi-select-trigger`
truncation for Bug 3, the new z-index TOKENS, and a single-viewport
E2E with five individual-component assertions.

This PR closes everything else the issue body and the
`specs/packets-layout-audit.md` audit asked for.

### What changed (per gap)

**Gap A — apply the z-index scale (audit Section 2)**
#1131 added `--z-dropdown` / `--z-popover` / `--z-modal` /
`--z-tooltip` but explicitly left existing literal values in place.
This PR renumbers the 7 dropdowns/popovers the audit named:

| Selector | Before | After |
|---|---:|---:|
| `.col-toggle-menu` | 50 | `var(--z-dropdown)` (100) |
| `.multi-select-menu` | 90 | `var(--z-dropdown)` |
| `.region-dropdown-menu` | 90 | `var(--z-dropdown)` |
| `.node-filter-dropdown` | 100 | `var(--z-dropdown)` |
| `.fux-saved-menu` | `var(--z-tooltip)` (9200) | `var(--z-dropdown)` |
| `.fux-ac-dropdown` | `var(--z-tooltip)` | `var(--z-dropdown)` |
| `.hop-conflict-popover` | `var(--z-tooltip)` | `var(--z-popover)`
(300) |

`.fux-ctx-menu` deliberately retains the tooltip band — context
menus must float above all toolbar UI. `.region-filter-options-menu`
no longer exists in the source (was renamed
`.region-dropdown-menu`).

The `style.css` doc-block at the top is rewritten to record the
applied scale and to point operators at the new lint.

**Gap B — CSS-var lint (audit Section 5 #1, "single highest-value
addition")**
Adds `scripts/check-css-vars.js` (~70 lines). Walks
`public/*.css`, extracts every `var(--name)` reference WITHOUT a
fallback, asserts the name is defined in some `public/*.css`.
References WITH a fallback are tolerated. Wired into CI in the
`go-test` job before the JS unit tests.

The red commit (`608d81f`) shipped this lint exiting 1 against the
master tree — three undefined vars that bypassed earlier review:

```
public/style.css:2628  var(--text-primary)
public/style.css:2675  var(--bg-hover)
public/style.css:2924  var(--primary)
```

The green commit (`1369d1e`) defines those three as aliases in the
:root block (`--text-primary` → `--text`, `--bg-hover` →
`--hover-bg`, `--primary` → `--accent`). Light + dark themes
inherit through the existing tokens.

**Gap C — multi-viewport E2E (issue acceptance criterion)**
Adds `test-issue-1128-multi-viewport-e2e.js` — sister of the
existing single-viewport test. At each of three viewports
(1280×900, 1080×800, 768×1024):

  - takes a screenshot to `e2e-screenshots/issue-1128-<viewport>.png`
  - asserts no two `.filter-group` siblings vertically overlap
  - on desktop+laptop, opens the Saved menu and the Types
    multi-select and asserts the dropdown does not vertically
    overlap any `.filter-group` below it

Plus three viewport-agnostic assertions:

  - dropdown selectors compute z-index in `[100,199]`
    (`.col-toggle-menu`, `.multi-select-menu`,
    `.region-filter-options-menu`, `.fux-saved-menu`,
    `.fux-ac-dropdown`)
  - `.path-hops .hop / .hop-named / .arrow` compute
    `line-height ≤ 18px`
  - `.col-path` computes `height ≤ 28px`

Wired into the e2e-test job after the existing #1128 test.

**Gap D — Bug 5 polish (toolbar reorder)**
Audit Section 3 Bug 5: swaps `filter-group-dropdowns` and
`filter-group-toggles` in `public/packets.js` so time range +
Group by Hash + ★ My Nodes sit next to the search input. Pure
markup reorder. No CSS / no JS-handler changes.

**Gap E — Bug 1 belt-and-suspenders**
Audit Section 3 Bug 1 sub-bullets:

  - locks `.path-hops .hop / .hop-named / .arrow` to
    `line-height: 18px` so a chip with mixed font metrics cannot
    overflow the 22px host vertically and bleed into the row above
  - converts `.col-path { max-height: 28px }` → `height: 28px`
    because browsers widely ignore `max-height` on `<td>`s; the
    earlier rule was a no-op

### TDD discipline (red → green)

```
$ git log --oneline origin/master..HEAD
68b0426 fix(#1128): Bug 5 — toolbar group reorder (toggles before dropdowns)
6d16e6f fix(#1128): apply z-index scale to dropdowns + Bug 1 chip line-height lock
b9850c9 fix(check-css-vars): strip /* ... */ comments before scanning
1369d1e fix(#1128): define --text-primary, --bg-hover, --primary aliases (lint green)
0d4660f test(#1128): multi-viewport E2E + wire CSS-var lint into CI (red commit)
608d81f test(#1128): add scripts/check-css-vars.js — fails on 3 undefined vars (red commit)
```

Both red commits (`608d81f`, `0d4660f`) were verified to fail
locally before the green commits landed:

  - `608d81f` runs the lint and exits 1 on the three undefined vars
    listed above (proven against master).
  - `0d4660f` introduces the multi-viewport E2E and wires the lint
    into CI — the lint then fails the build on master, and the E2E
    z-scale assertion fails because pre-fix `.col-toggle-menu` is
    50, the multi-selects are 90, etc.

### Acceptance criteria status

From the original issue body:
  -  Bug 4 root cause fixed (#1131 + this PR's lint guard)
  -  Bug 1 chip-spill (debounced re-measure from #1131 +
       line-height lock + col-path height fix from this PR)
  -  Bug 2 +N popover positioning (#1131)
  -  Bug 3 toolbar overlap (#1131 + #1131 row-gap)
  -  Bug 5 group reorder (this PR)
  -  Z-index scale documented + applied (this PR)
  -  E2E screenshots at multiple viewports (this PR)
  -  Bounding-rect collision detection on visible interactive
       elements (this PR — `.filter-group` siblings + dropdown vs.
       toolbar)
  -  CSS-var lint in CI (this PR)

### Why this is "Partial fix for #1128", not "Fixes #1128"

Per `AGENTS.md` rule 34, automated closure is reserved for the
operator after they verify on staging. The acceptance criteria
above appear satisfied in code, but the user should confirm the
visual outcome on staging before closing.

### Files changed

- `scripts/check-css-vars.js` (new — ~70 lines)
- `test-issue-1128-multi-viewport-e2e.js` (new)
- `.github/workflows/deploy.yml` (lint step + e2e step wiring)
- `public/style.css` (z-renumber, doc-block, Bug 1 polish, alias defs)
- `public/packets.js` (Bug 5 reorder)

Refs #1128, follows #1131

---------

Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local>
Co-authored-by: openclaw-bot <bot@openclaw.local>
2026-05-07 03:41:12 +00:00

544 lines
22 KiB
YAML

name: CI/CD Pipeline
on:
push:
branches: [master]
tags: ['v*']
pull_request:
branches: [master]
workflow_dispatch:
permissions:
contents: read
packages: write
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-and-publish → deploy → publish-badges
# PRs stop after build-and-publish (no GHCR push). Master continues to deploy + badges.
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: Build and test channel library + decrypt CLI
run: |
set -e -o pipefail
cd internal/channel
go test ./...
echo "--- Channel library tests passed ---"
cd ../../cmd/decrypt
CGO_ENABLED=0 go build -ldflags="-s -w" -o corescope-decrypt .
go test ./...
echo "--- Decrypt CLI tests passed ---"
- name: Lint CSS variables (issue #1128)
run: |
set -e
node scripts/check-css-vars.js
node scripts/test-check-css-vars.js
- name: Run JS unit tests (packet-filter)
run: |
set -e
node test-packet-filter.js
node test-packet-filter-time.js
node test-channel-decrypt-insecure-context.js
node test-live-region-filter.js
node test-channel-qr.js
node test-channel-qr-wiring.js
node test-channel-modal-ux.js
node test-channel-issue-1087.js
node test-channel-issue-1101.js
node test-pull-to-reconnect-1091.js
node test-channel-fluid-layout.js
- 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: ubuntu-latest
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: Freshen fixture timestamps
run: bash tools/freshen-fixture.sh test-fixtures/e2e-fixture.db
- 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/healthz > /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
BASE_URL=http://localhost:13581 node test-filter-ux-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-channel-issue-1087-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-channel-issue-1111-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-map-modal-fluid-e2e.js 2>&1 | tee -a e2e-output.txt
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-nav-fluid-1055-e2e.js 2>&1 | tee -a e2e-output.txt
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-nav-priority-1102-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-channel-fluid-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-table-fluid-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-issue-1122-packets-filter-ux-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-issue-1128-packets-layout-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-issue-1128-multi-viewport-e2e.js 2>&1 | tee -a e2e-output.txt
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-logo-rebrand-e2e.js 2>&1 | tee -a e2e-output.txt
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-logo-theme-e2e.js 2>&1 | tee -a 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 & Publish Docker Image
# ───────────────────────────────────────────────────────────────
build-and-publish:
name: "🏗️ Build & Publish Docker Image"
needs: [e2e-test]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Compute build metadata
id: meta
run: |
BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
GIT_COMMIT="${GITHUB_SHA::7}"
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
APP_VERSION="${GITHUB_REF#refs/tags/}"
else
APP_VERSION="edge"
fi
echo "build_time=$BUILD_TIME" >> "$GITHUB_OUTPUT"
echo "git_commit=$GIT_COMMIT" >> "$GITHUB_OUTPUT"
echo "app_version=$APP_VERSION" >> "$GITHUB_OUTPUT"
echo "Build: version=$APP_VERSION commit=$GIT_COMMIT time=$BUILD_TIME"
- name: Build Go Docker image (local staging)
run: |
GIT_COMMIT="${{ steps.meta.outputs.git_commit }}" \
APP_VERSION="${{ steps.meta.outputs.app_version }}" \
BUILD_TIME="${{ steps.meta.outputs.build_time }}" \
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging build "$STAGING_SERVICE"
echo "Built Go staging image ✅"
- name: Set up Docker Buildx
if: github.event_name == 'push'
uses: docker/setup-buildx-action@v3
- name: Set up QEMU (arm64 runtime stage)
if: github.event_name == 'push'
uses: docker/setup-qemu-action@v3
- name: Log in to GHCR
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
if: github.event_name == 'push'
id: docker-meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/kpa-clawbot/corescope
tags: |
type=semver,pattern=v{{version}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=edge,branch=master
- name: Build and push to GHCR
if: github.event_name == 'push'
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.docker-meta.outputs.tags }}
labels: ${{ steps.docker-meta.outputs.labels }}
build-args: |
APP_VERSION=${{ steps.meta.outputs.app_version }}
GIT_COMMIT=${{ steps.meta.outputs.git_commit }}
BUILD_TIME=${{ steps.meta.outputs.build_time }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ───────────────────────────────────────────────────────────────
# 4. Release Artifacts (tags only)
# ───────────────────────────────────────────────────────────────
release-artifacts:
name: "📦 Release Artifacts"
if: startsWith(github.ref, 'refs/tags/v')
needs: [go-test]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Go 1.22
uses: actions/setup-go@v6
with:
go-version: '1.22'
- name: Build corescope-decrypt (static, linux/amd64)
run: |
cd cmd/decrypt
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=${{ github.ref_name }}" -o ../../corescope-decrypt-linux-amd64 .
- name: Build corescope-decrypt (static, linux/arm64)
run: |
cd cmd/decrypt
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.version=${{ github.ref_name }}" -o ../../corescope-decrypt-linux-arm64 .
- name: Upload release assets
uses: softprops/action-gh-release@v2
with:
files: |
corescope-decrypt-linux-amd64
corescope-decrypt-linux-arm64
# ───────────────────────────────────────────────────────────────
# 4b. Deploy Staging (master only)
# ───────────────────────────────────────────────────────────────
deploy:
name: "🚀 Deploy Staging"
if: github.event_name == 'push'
needs: [build-and-publish]
runs-on: [self-hosted, meshcore-runner-2]
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Pull latest image from GHCR
run: |
# Try to pull the edge image from GHCR and tag for docker-compose compatibility
if docker pull ghcr.io/kpa-clawbot/corescope:edge; then
docker tag ghcr.io/kpa-clawbot/corescope:edge corescope-go:latest
echo "Pulled and tagged GHCR edge image ✅"
else
echo "⚠️ GHCR pull failed — falling back to locally built image"
fi
- name: Deploy staging
run: |
# Force-remove the staging container regardless of how it was created
# (compose-managed OR manually created via docker run)
docker stop corescope-staging-go 2>/dev/null || true
docker rm -f corescope-staging-go 2>/dev/null || true
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: |
PORT="${STAGING_GO_HTTP_PORT:-80}"
if curl -sf "http://localhost:${PORT}/api/stats" | grep -q engine; then
echo "Staging verified — engine field present ✅"
else
echo "Staging /api/stats did not return engine field (port ${PORT})"
exit 1
fi
- name: Clean up old Docker images
if: always()
run: |
# Remove dangling images and images older than 24h (keeps current build)
echo "--- Docker disk usage before cleanup ---"
docker system df
docker image prune -af --filter "until=24h" 2>/dev/null || true
docker builder prune -f --keep-storage=1GB 2>/dev/null || true
echo "--- Docker disk usage after cleanup ---"
docker system df
# ───────────────────────────────────────────────────────────────
# 5. Publish Badges & Summary (master only)
# ───────────────────────────────────────────────────────────────
publish:
name: "📝 Publish Badges & Summary"
if: github.event_name == 'push'
needs: [deploy]
runs-on: ubuntu-latest
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