CI #239
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] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main] | |
| # GIT-83: run the venue gate by hand (Actions → CI → Run workflow) before | |
| # tagging. Publishing still happens only on a v* tag. | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| node-version: [18, 20, 22] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Use Node.js ${{ matrix.node-version }} | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci --legacy-peer-deps | |
| - name: Type check | |
| run: npm run typecheck | |
| - name: Build | |
| run: npm run build | |
| - name: Unit tests | |
| run: npm run test:unit | |
| - name: Smoke test (free tier) | |
| run: npm run test:smoke:free | |
| # GIT-89: session identity across a real MCP server restart. Runs the | |
| # single e2e file rather than `npm run test:e2e`, because the Pro suites in | |
| # that config gate on Docker/Supabase and hang rather than skip when | |
| # neither is present (GIT-90). This file spawns the built server directly | |
| # on the free tier — no Docker, no credentials — so it is safe in CI, and | |
| # the publish job gates on this one passing. | |
| - name: E2E — session identity survives MCP restart | |
| run: npx vitest run --config vitest.e2e.config.ts tests/e2e/git-89-session-identity.test.ts | |
| # GIT-120: install-hooks / uninstall-hooks keep foreign hooks. Runs the | |
| # real CLI in a scratch repo; no server, Docker or credentials. | |
| - name: E2E — hooks install/uninstall keep foreign hooks | |
| run: npx vitest run --config vitest.e2e.config.ts tests/e2e/git-120-hooks-merge.test.ts | |
| # GIT-115: a fresh install in a clean HOME — init, the SessionStart hook, | |
| # the built server — plus the CLI/wizard suite. Both run the real CLI in | |
| # scratch directories with a scratch HOME; no Docker or credentials. | |
| - name: E2E — fresh install reads what init wrote; CLI and wizard | |
| run: npx vitest run --config vitest.e2e.config.ts tests/e2e/git-115-fresh-install.test.ts tests/e2e/cli-fresh-install.test.ts | |
| - name: Dependency audit (no unused deps) | |
| run: npx depcheck --ignores="@types/*" --json | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); const unused=d.dependencies||[]; if(unused.length){console.error('Unused dependencies:',unused);process.exit(1)}" | |
| # GIT-83 / GIT-112: the hook suite on a runner WITHOUT jq (or ps). The hooks | |
| # fall back to node and bash builtins there; on 1.10.0 that fallback left | |
| # Stop enforcement inactive and nothing noticed, because every runner and | |
| # dev machine the suite had run on had jq. | |
| hooks-without-jq: | |
| runs-on: ubuntu-latest | |
| container: node:20-bookworm-slim | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Runner has no jq and no ps | |
| run: | | |
| if command -v jq >/dev/null; then echo "::error::jq is installed; this job must run without it"; exit 1; fi | |
| if command -v ps >/dev/null; then echo "::error::ps is installed; this job must run without it"; exit 1; fi | |
| bash --version | head -1; node --version | |
| - name: Hook tests | |
| run: bash hooks/tests/test-hooks.sh | |
| # GIT-116: the same suite on stock macOS. The runner has Homebrew (and may | |
| # have coreutils' timeout), so the suite runs with the system directories | |
| # plus a directory holding only node — what a Mac without Homebrew has. | |
| hooks-macos: | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Hook tests on a stock macOS PATH (no Homebrew, no timeout) | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/nodeonly" | |
| ln -s "$(command -v node)" "$RUNNER_TEMP/nodeonly/node" | |
| STOCK="/usr/bin:/bin:/usr/sbin:/sbin:$RUNNER_TEMP/nodeonly" | |
| for t in timeout gtimeout; do | |
| if PATH="$STOCK" command -v "$t" >/dev/null; then echo "::error::$t is on the stock PATH"; exit 1; fi | |
| done | |
| # macOS 15+ ships /usr/bin/jq; older macOS does not. The jq-less path | |
| # is covered by hooks-without-jq (Linux); report which this runner is. | |
| PATH="$STOCK" command -v jq >/dev/null && echo "stock jq present: $(PATH="$STOCK" jq --version)" || echo "stock jq absent" | |
| env -i HOME="$HOME" PATH="$STOCK" /bin/bash hooks/tests/test-hooks.sh | |
| # GIT-83: the release gate. Customers never run SQL after setup (standing | |
| # rule, 2026-09-21), so a release must work on a store still on the v1.8.0 | |
| # setup.sql — the venue is put on exactly that schema first, then the | |
| # current one. Runs on v* tags (publish depends on it) and on demand. | |
| # | |
| # Needs repository secrets, all for the DISPOSABLE venue (never production; | |
| # scripts/venue-schema.sh and the driver both refuse it): | |
| # VENUE_REF project ref | |
| # VENUE_SUPABASE_URL https://<VENUE_REF>.supabase.co | |
| # VENUE_SERVICE_ROLE_KEY service role key | |
| # VENUE_DB_URL postgres:// URL (session pooler) for schema resets | |
| venue-gate: | |
| if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # One venue: two runs would wipe each other's data. | |
| concurrency: | |
| group: gitmem-venue | |
| cancel-in-progress: false | |
| env: | |
| VENUE_REF: ${{ secrets.VENUE_REF }} | |
| VENUE_DB_URL: ${{ secrets.VENUE_DB_URL }} | |
| VENUE_SUPABASE_URL: ${{ secrets.VENUE_SUPABASE_URL }} | |
| VENUE_SERVICE_ROLE_KEY: ${{ secrets.VENUE_SERVICE_ROLE_KEY }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # the v1.8.0 tag supplies the floor schema | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - run: npm ci --legacy-peer-deps | |
| - run: npm run build | |
| # Missing secrets fail the gate — a skipped gate would let publish through. | |
| - name: Venue configured | |
| run: | | |
| missing="" | |
| for v in VENUE_REF VENUE_DB_URL VENUE_SUPABASE_URL VENUE_SERVICE_ROLE_KEY; do | |
| [ -n "${!v}" ] || missing="$missing $v" | |
| done | |
| if [ -n "$missing" ]; then echo "::error::venue gate cannot run, missing secrets:$missing"; exit 1; fi | |
| umask 077 | |
| printf 'SUPABASE_URL=%s\nSUPABASE_SERVICE_ROLE_KEY=%s\nVENUE_REF=%s\n' \ | |
| "$VENUE_SUPABASE_URL" "$VENUE_SERVICE_ROLE_KEY" "$VENUE_REF" > "$RUNNER_TEMP/venue.env" | |
| echo "VENUE_ENV=$RUNNER_TEMP/venue.env" >> "$GITHUB_ENV" | |
| echo "RUNS=$RUNNER_TEMP/venue-runs" >> "$GITHUB_ENV" | |
| mkdir -p "$RUNNER_TEMP/venue-runs" | |
| git show v1.8.0:schema/setup.sql > "$RUNNER_TEMP/setup-v1.8.0.sql" | |
| - name: Venue on the v1.8.0 setup.sql | |
| run: scripts/venue-schema.sh reset-to "$RUNNER_TEMP/setup-v1.8.0.sql" | |
| - name: Driver on v1.8.0 — flow | |
| run: node tests/e2e/blank-supabase.mjs flow --label v180-flow --out "$RUNS" | |
| - name: Driver on v1.8.0 — egress | |
| run: node tests/e2e/blank-supabase.mjs egress --label v180-egress --out "$RUNS" --rows 250 --usage 5 | |
| - name: Driver on v1.8.0 — projects (when the driver has it) | |
| run: | | |
| # The usage line lists the modes; a bare "projects" string also appears in a path. | |
| if grep -q 'flow|egress|projects' tests/e2e/blank-supabase.mjs; then | |
| node tests/e2e/blank-supabase.mjs projects --label v180-projects --out "$RUNS" | |
| else | |
| echo "driver has no projects mode in this revision" | |
| fi | |
| # The current setup.sql must apply on top of v1.8.0, and apply again. | |
| - name: Venue on the current setup.sql (applied twice) | |
| run: | | |
| scripts/venue-schema.sh apply schema/setup.sql | |
| scripts/venue-schema.sh apply schema/setup.sql | |
| - name: Driver on the current schema — flow | |
| run: node tests/e2e/blank-supabase.mjs flow --label current-flow --out "$RUNS" | |
| - name: Driver reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: venue-gate-${{ github.run_id }} | |
| # netlogs record method, path, status and body size only — no bodies or headers. | |
| path: ${{ runner.temp }}/venue-runs | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| publish: | |
| needs: [build, hooks-without-jq, hooks-macos, venue-gate] | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| registry-url: https://registry.npmjs.org | |
| cache: npm | |
| - run: npm ci --legacy-peer-deps | |
| - run: npm run build | |
| - run: npm publish | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |