Skip to content

CI

CI #239

Workflow file for this run

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 }}