Release: sync develop → main #395
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |