Skip to content

Release: sync develop → main #395

Release: sync develop → main

Release: sync develop → main #395

Workflow file for this run

name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
- cron: '0 3 * * *' # nightly full prod-stack smoke
workflow_dispatch:
# Prevent duplicate runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: '20'
jobs:
# ── Static quality: lint (0 errors) + full TypeScript type check ───────────
quality:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
# `npm install` (not `npm ci`) dodges the npm optional-deps bug that
# leaves the platform-specific @rollup/rollup-linux-* binary missing
# (npm/cli#4828). This is why CI used to "disable" Vitest — it never
# needed disabling, just a real install.
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: ESLint (fails only on errors)
run: npm run lint
- name: TypeScript type check (all packages)
run: npm run typecheck
# ── The real unit suites, one job per package, run in parallel ─────────────
unit-tests:
name: Unit Tests (${{ matrix.package }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package: [core, web, server, mcp-server]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Run @graphdone/${{ matrix.package }} tests
run: npm run test --workspace=@graphdone/${{ matrix.package }}
# ── MCP server against a REAL Neo4j (catches Cypher/DB drift) ──────────────
# The unit suites use a mock driver; this runs the MCP GraphService's own
# Cypher against a live Neo4j (node/edge lifecycle, browse). It already
# caught a real getNodeDetails crash the mock hid.
mcp-contract:
name: MCP Real-Neo4j Contract
runs-on: ubuntu-latest
services:
neo4j:
image: neo4j:5.26.12
env:
NEO4J_AUTH: neo4j/graphdone_password
NEO4J_PLUGINS: '["graph-data-science", "apoc"]'
NEO4J_dbms_security_procedures_unrestricted: "gds.*,apoc.*"
NEO4J_dbms_security_procedures_allowlist: "gds.*,apoc.*"
options: >-
--health-cmd "cypher-shell -u neo4j -p graphdone_password 'RETURN 1'"
--health-interval 10s --health-timeout 5s --health-retries 30
ports:
- 7474:7474
- 7687:7687
env:
RUN_DB_CONTRACT: '1'
NEO4J_URI: bolt://localhost:7687
NEO4J_USER: neo4j
NEO4J_PASSWORD: graphdone_password
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Run real-Neo4j contract test
run: npx vitest --run tests/neo4j-contract.test.ts
working-directory: packages/mcp-server
# ── Production build must actually build ────────────────────────────────────
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Build all packages
run: npm run build
- name: Bundle size budget (ADAPT-8)
run: npm run perf:bundle
# ── Security scan (advisory) ───────────────────────────────────────────────
security-scan:
name: Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: npm audit (advisory — does not block)
run: npm audit --audit-level moderate
continue-on-error: true
# ── THE GATE: the app works from a user's point of view ────────────────────
# Fast feedback on every PR: boots the dev stack against a Neo4j service
# container and runs the user-smoke suite (login → nodes AND edges render →
# no GraphQL errors → no orphan edges → grow + undo work). ~3-4 min vs the
# ~45 min cold prod-image build, so it's a gate people actually keep green.
smoke-gate:
name: Smoke Gate
runs-on: ubuntu-latest
needs: [quality, unit-tests, build]
services:
neo4j:
image: neo4j:5.26.12
env:
NEO4J_AUTH: neo4j/graphdone_password
NEO4J_PLUGINS: '["graph-data-science", "apoc"]'
NEO4J_dbms_security_procedures_unrestricted: "gds.*,apoc.*"
NEO4J_dbms_security_procedures_allowlist: "gds.*,apoc.*"
options: >-
--health-cmd "cypher-shell -u neo4j -p graphdone_password 'RETURN 1'"
--health-interval 10s --health-timeout 5s --health-retries 30
ports:
- 7474:7474
- 7687:7687
env:
NEO4J_URI: bolt://localhost:7687
NEO4J_USER: neo4j
NEO4J_PASSWORD: graphdone_password
JWT_SECRET: ci-smoke-jwt-secret
SESSION_SECRET: ci-smoke-session-secret
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Build (dev runtime imports built @graphdone/core)
run: npm run build
- name: Install Playwright (chromium)
run: npx playwright install --with-deps chromium
- name: Start GraphDone (dev stack)
run: |
npm run dev > /tmp/dev.log 2>&1 &
echo "Waiting for web (3127) and API (4127) to come up..."
timeout 240 bash -c 'until curl -sf http://localhost:3127 >/dev/null 2>&1 && curl -sf http://localhost:4127/health >/dev/null 2>&1; do sleep 3; done'
echo "Dev stack healthy."
- name: Seed demo data
run: npm run db:seed
- name: THE GATE — user smoke suite
run: npm run test:smoke
env:
TEST_URL: http://localhost:3127
CI: true
- name: Living-graph effects render (LIVE-*)
run: npx playwright test tests/e2e/living-graph.spec.ts --project="GraphDone-Core/dev-neo4j/chromium" --reporter=line
env:
TEST_URL: http://localhost:3127
CI: true
- name: Performance budgets (ADAPT-8)
run: npm run test:perf
env:
TEST_URL: http://localhost:3127
CI: true
- name: Dump dev log on failure
if: failure()
run: tail -100 /tmp/dev.log || true
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: smoke-results-${{ github.sha }}
path: |
test-results/
test-artifacts/
retention-days: 7
# ── Showcase report: .webm video + screenshots of every mode, all sizes ────
# Documentation, not a gate (not in ci-success). Runs on every PR per the
# team's choice; uploads a single self-contained gallery as an artifact.
showcase-report:
name: Showcase Report (video + screenshots)
runs-on: ubuntu-latest
needs: [quality, unit-tests, build]
services:
neo4j:
image: neo4j:5.26.12
env:
NEO4J_AUTH: neo4j/graphdone_password
NEO4J_PLUGINS: '["graph-data-science", "apoc"]'
NEO4J_dbms_security_procedures_unrestricted: "gds.*,apoc.*"
NEO4J_dbms_security_procedures_allowlist: "gds.*,apoc.*"
options: >-
--health-cmd "cypher-shell -u neo4j -p graphdone_password 'RETURN 1'"
--health-interval 10s --health-timeout 5s --health-retries 30
ports:
- 7474:7474
- 7687:7687
env:
NEO4J_URI: bolt://localhost:7687
NEO4J_USER: neo4j
NEO4J_PASSWORD: graphdone_password
JWT_SECRET: ci-showcase-jwt-secret
SESSION_SECRET: ci-showcase-session-secret
TEST_URL: http://localhost:3127
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Build
run: npm run build
- name: Install Playwright (chromium)
run: npx playwright install --with-deps chromium
- name: Start GraphDone (dev stack)
run: |
npm run dev > /tmp/dev.log 2>&1 &
timeout 240 bash -c 'until curl -sf http://localhost:3127 >/dev/null 2>&1 && curl -sf http://localhost:4127/health >/dev/null 2>&1; do sleep 3; done'
- name: Seed demo data
run: npm run db:seed || true
- name: Capture showcase + build gallery
run: npm run report:showcase
continue-on-error: true
- name: Upload showcase gallery
if: always()
uses: actions/upload-artifact@v4
with:
name: showcase-report-${{ github.sha }}
path: test-artifacts/showcase/
retention-days: 14
# ── Full production-stack validation (nginx/TLS/Docker) ─────────────────────
# Heavy (~45 min cold build), so it runs only on main pushes, the nightly
# schedule, and manual dispatch — not on every PR. Same user-smoke suite,
# plus the broader PR suite as advisory signal, against the real prod stack.
prod-smoke:
name: Prod Stack Smoke (main / nightly)
runs-on: ubuntu-latest
needs: [quality, unit-tests, build]
if: github.ref == 'refs/heads/main' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Install Playwright (chromium)
run: npx playwright install --with-deps chromium
- name: Generate development certificates
run: ./scripts/generate-dev-certs.sh
- name: Start GraphDone (production stack)
run: |
npm run docker:prod &
echo "Waiting for the stack to be healthy (cold image build can take minutes)..."
timeout 1200 bash -c 'until curl -k https://localhost:3128/health 2>/dev/null; do sleep 5; done'
echo "Stack healthy."
- name: Seed demo data
run: npm run db:seed || echo "seed skipped/failed — smoke still validates the stack"
continue-on-error: true
- name: THE GATE — user smoke suite (prod stack)
run: npm run test:smoke
env:
TEST_URL: https://localhost:3128
CI: true
- name: PR critical suite (advisory)
run: npm run test:pr || echo "::warning::PR critical suite has known failures — tracked in docs/TESTING_AND_REFINEMENT_PLAN.md"
continue-on-error: true
env:
TEST_URL: https://localhost:3128
TEST_ENV: production
CI: true
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: prod-smoke-results-${{ github.sha }}
path: |
test-results/
test-artifacts/
retention-days: 7
# ── Overall status ─────────────────────────────────────────────────────────
ci-success:
name: CI Success
runs-on: ubuntu-latest
needs: [quality, unit-tests, mcp-contract, build, smoke-gate]
if: always()
steps:
- name: Verify required jobs passed
run: |
echo "Quality: ${{ needs.quality.result }}"
echo "Unit tests: ${{ needs.unit-tests.result }}"
echo "Build: ${{ needs.build.result }}"
echo "Smoke gate: ${{ needs.smoke-gate.result }}"
if [[ "${{ needs.quality.result }}" == "success" \
&& "${{ needs.unit-tests.result }}" == "success" \
&& "${{ needs.mcp-contract.result }}" == "success" \
&& "${{ needs.build.result }}" == "success" \
&& "${{ needs.smoke-gate.result }}" == "success" ]]; then
echo "✅ CI green — lint, types, every unit suite, the build, and the user smoke gate all passed."
else
echo "❌ CI failed — see the failing job above."
exit 1
fi