Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
314 changes: 15 additions & 299 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,110 +6,37 @@ on:
pull_request:

concurrency:
# PRs group by number so a newer push supersedes and cancels the stale
# run. Pushes to main group by commit SHA, giving every commit its own
# group: a constant branch-wide key would let a newer push evict the
# queued run of a commit still waiting behind an in-flight one, and
# that commit would never be validated. cancel-in-progress therefore
# only governs PR supersession.
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# The four jobs below were one serial `checks` job. Splitting them lets
# GitHub run them concurrently, so a PR's wall-clock is the slowest job
# rather than the sum of all of them, and a lint or structural failure
# reports in about a minute instead of waiting behind the build.
lint:
# Warms the bun install cache once so the parallel jobs below mostly
# restore rather than reinstall from the network.
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-workbench
- uses: actions/cache@v4
with:
path: |
.eslintcache
node_modules/.cache/prettier
key: lint-${{ runner.os }}-${{ hashFiles('bun.lock', 'eslint.config.ts', '.prettierrc.json', '.bun-version') }}
- run: bun run lint

typecheck:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/setup-workbench
- uses: actions/cache@v4
with:
# Restores each package's incremental tsbuildinfo (composite build
# and combined --noEmit check now write separate files, so caching
# both means the next run's `tsc` calls only recheck what actually
# changed since the cached commit, instead of every file every PR.
# Paths are the workspace roots spelled out rather than `**/dist`:
# a recursive glob walks node_modules and follows every workspace
# symlink in it, and the save step ran for over 40 minutes.
path: |
apps/*/dist
packages/*/dist
tools/*/dist
workflows/*/dist
vendor/intx/*/dist
apps/*/tsconfig.tsbuildinfo
packages/*/tsconfig.tsbuildinfo
tools/*/tsconfig.tsbuildinfo
workflows/*/tsconfig.tsbuildinfo
vendor/intx/*/tsconfig.tsbuildinfo
tsconfig.tsbuildinfo
test/tsconfig.tsbuildinfo
test/isolation/tsconfig.tsbuildinfo
key: typecheck-${{ runner.os }}-${{ hashFiles('bun.lock', '.bun-version', 'tsconfig.base.json', 'scripts/generate-tsconfig-references.ts') }}-${{ github.sha }}
restore-keys: |
typecheck-${{ runner.os }}-${{ hashFiles('bun.lock', '.bun-version', 'tsconfig.base.json', 'scripts/generate-tsconfig-references.ts') }}-
- run: bun run typecheck

# A hosted runner's 4 vCPUs cap how much a single job's package fan-out
# (scripts/run-all.ts's own concurrency) can cut wall-clock: the total
# CPU-seconds across every package's test script divided by 4 was still
# over 90s. Splitting the same total work across 3 runners, each with
# its own 4 vCPUs, is what actually buys more parallelism. --shard i/3
# (scripts/run-all.ts) round-robins the workspace's stably name-sorted
# package list across shards, so a run's set of packages-per-shard is
# reproducible and every package lands in exactly one shard.
build-test-shard:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
lint:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/setup-workbench
- run: bun run build
# The root-level scripts/*.test.ts suite (scripts/run-all.test.ts
# among them) isn't itself sharded — it only needs to run once, so
# it rides along on shard 1.
- if: matrix.shard == 1
run: bun test ./scripts/*.test.ts
- run: bun run scripts/run-all.ts test --shard ${{ matrix.shard }}/3

# The required check every branch protection rule and PR template
# names is "build-test" (predating the shard split above); this job
# keeps that name live by depending on every shard and failing if any
# of them did.
build-test:
needs: build-test-shard
if: always()
runs-on: ubuntu-latest
steps:
- if: contains(needs.build-test-shard.result, 'failure') || contains(needs.build-test-shard.result, 'cancelled')
run: |
echo "at least one build-test shard failed" >&2
exit 1
- run: bun run lint
- run: bun run fmt:check

structural:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -118,225 +45,14 @@ jobs:
- uses: ./.github/actions/setup-workbench
- run: bun run check:structural

e2e-plan:
runs-on: ubuntu-latest
outputs:
suites: ${{ steps.list-suites.outputs.suites }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-workbench
- name: List e2e suites
id: list-suites
run: echo "suites=$(bun run scripts/e2e/list-suites.ts)" >> "$GITHUB_OUTPUT"

# One job per e2e suite so a PR's e2e wall-clock is the slowest suite
# rather than the sum of all of them, and a failing suite names itself
# instead of hiding behind "e2e failed". fail-fast is off so one flaky
# suite doesn't cancel every other suite's run.
e2e-suite:
needs: e2e-plan
# A single fast pass over every workspace's tests. DB-gated suites
# (scripts/e2e/* included) skip themselves when DATABASE_URL is unset —
# see scripts/e2e/db-gate.ts — so this job needs no Postgres service.
unit:
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
suite: ${{ fromJson(needs.e2e-plan.outputs.suites) }}

services:
postgres:
# pgvector-enabled Postgres 17, matching the local development
# database (brew postgresql@17 + pgvector).
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-workbench

# The committed template ships a placeholder secret and a
# credential-less local DATABASE_URL; point the URL at the service
# container's superuser and mint a real session secret in place.
- name: Write env file
run: |
cp .env.example .env
secret=$(openssl rand -hex 32)
sed -i "s|^SESSION_SECRET=.*|SESSION_SECRET=${secret}|" .env
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench|" .env

# The e2e suite skips itself when DATABASE_URL is absent, so a
# wiring mistake here could otherwise drop the whole suite while
# CI stays green. Fail loudly instead: the env file must exist
# with a usable DATABASE_URL and SESSION_SECRET, Postgres must be
# reachable, and git (the workflow-asset publication path) must be
# present. CI=true turns any remaining skip into a hard failure.
- name: Assert the e2e test env is wired
run: |
test -f .env
grep -q '^DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench$' .env
grep -Eq '^SESSION_SECRET=.{32,}$' .env
pg_isready -h localhost -p 5432
git --version

- name: Run the suite
run: |
if [ "${{ matrix.suite }}" = "unit" ]; then
bun test scripts/e2e/harness.test.ts scripts/e2e/db-gate.test.ts scripts/e2e/db-setup.test.ts scripts/e2e/list-suites.test.ts --max-concurrency=1
else
bun test "scripts/e2e/${{ matrix.suite }}.test.ts" --max-concurrency=1
fi
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench
E2E_LOG_DIR: e2e-logs

- name: Upload e2e logs
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-logs-${{ matrix.suite }}
path: e2e-logs
if-no-files-found: ignore

# Branch protection's required check is named "e2e"; this summary job
# keeps that name while the actual work runs as per-suite e2e-suite jobs.
e2e:
needs: e2e-suite
if: always()
runs-on: ubuntu-latest
steps:
- name: Check e2e-suite matrix result
run: |
if [ "${{ needs.e2e-suite.result }}" != "success" ]; then
echo "one or more e2e suites failed"
exit 1
fi

isolation:
runs-on: ubuntu-latest
timeout-minutes: 15

services:
postgres:
# pgvector-enabled Postgres 17, matching the local development
# database (brew postgresql@17 + pgvector).
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-workbench

- name: Write env file
run: |
cp .env.example .env
secret=$(openssl rand -hex 32)
sed -i "s|^SESSION_SECRET=.*|SESSION_SECRET=${secret}|" .env
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench|" .env

- name: Assert the e2e test env is wired
run: |
test -f .env
grep -q '^DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench$' .env
grep -Eq '^SESSION_SECRET=.{32,}$' .env
pg_isready -h localhost -p 5432
git --version

- name: Run the two-org isolation suite
run: bun test test/isolation
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench

db-suites:
runs-on: ubuntu-latest
timeout-minutes: 20

services:
postgres:
# pgvector-enabled Postgres 17, matching the local development
# database (brew postgresql@17 + pgvector).
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-workbench

- name: Write env file
run: |
cp .env.example .env
secret=$(openssl rand -hex 32)
sed -i "s|^SESSION_SECRET=.*|SESSION_SECRET=${secret}|" .env
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench|" .env

- name: Assert the e2e test env is wired
run: |
test -f .env
grep -q '^DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench$' .env
grep -Eq '^SESSION_SECRET=.{32,}$' .env
pg_isready -h localhost -p 5432
git --version

# apps/hub/test and most of the package suites below connect
# straight to DATABASE_URL (or its `_e2e`-suffixed sibling from
# `e2eDatabaseUrl()`) rather than booting through the harness, so
# — unlike e2e and isolation, which provision their own schema —
# this job must create and migrate both databases itself before
# those suites run.
- name: Set up the databases
run: |
DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench bun scripts/db-setup.ts
DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench_e2e bun scripts/db-setup.ts

# apps/hub's own DB-backed suites (e.g. the sign-up rate-limit
# proof) never run under the plain `checks` job, which has no
# Postgres. CI=true turns a would-be skip here into a hard
# failure, so a misconfigured invocation can't pass vacuously.
- name: Run the hub's database-backed suites
run: |
set -a
. ./.env
set +a
bun test apps/hub/test
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench

# Every DB-gated suite under packages/* and apps/hub/src gates on
# DATABASE_URL and describe.skip's without one, so build-test (no
# Postgres) never runs them. Find them by the same signal they
# gate on rather than hardcoding a file list, so a newly added
# suite gets picked up automatically. CI=true turns a would-be
# skip into a hard failure.
- name: Run the database-backed package suites
run: |
set -a
. ./.env
set +a
bun --env-file="${GITHUB_WORKSPACE}/.env" test $(grep -rl DATABASE_URL --include='*.test.ts' packages apps | grep -v apps/hub/test)
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench
- run: bun test apps packages workflows scripts
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,13 @@ tmp/
# Local runtime state (hub and sidecar data dirs)
.data/

# Local agent scratch (not product source; breaks prettier --check .)
# Local agent scratch (not product source)
.mb-scratch/
.corbits/

# Browser walkthrough harness screenshots + run summary (CL-6072)
scripts/e2e/browser/shots/

# Lint caches — local only; CI installs fresh and rebuilds them.
.eslintcache

# `bun pm pack` / `bun run release` tarballs — built on demand, never
# committed (CL-8167).
*.tgz
File renamed without changes.
Loading
Loading