name: CI on: push: branches: [master, main] pull_request: jobs: ci: name: Typecheck & Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 22 cache: 'npm' - name: Install dependencies run: npm ci - name: Check package-lock.json version sync run: npm run check:lockfile - name: Type check run: npm run typecheck - name: Lint run: npm run lint - name: Frontend JS syntax check run: npm run check:frontend-syntax - name: Format check run: npm run format:check # install.sh reaches users through `curl | bash` with nothing between it and # them, and until now nothing in this repo checked it at all: no shellcheck, # no bats, and the vitest gate is Node-only. - name: install.sh syntax run: bash -n install.sh # macOS ships bash 3.2 and this runner has bash 5, so the constructs that # actually break a Mac install are invisible here without a container. This # step is what catches them — in particular expanding an EMPTY array under # `set -u`, which bash 3.2 treats as an unbound variable and `bash -n` # cannot see because it is a runtime error, not a syntax one. - name: install.sh runs on bash 3.2 (macOS's version) run: | set -euo pipefail docker run --rm -v "$PWD":/w -w /w bash:3.2 bash -n /w/install.sh docker run --rm -v "$PWD":/w -w /w -e CODEMAN_INSTALL_SH_LIB=1 bash:3.2 bash -c ' set -euo pipefail . /w/install.sh detect_all_clis # `shell` declares no binaries, so its offset/length window is length 0. # Iterating it is the empty-array case; reaching here means it did not abort. echo "bash $BASH_VERSION: ${#CLI_IDS[@]} CLIs, $CLI_FOUND_COUNT found" cli_catalog_names >/dev/null cli_catalog_print_install_hints >/dev/null ' - name: CLI catalogue artifacts are in sync with stock.ts run: npm run generate:cli-catalog -- --check - name: Server boot smoke test run: | set -u if ! command -v tmux >/dev/null; then sudo apt-get update -qq sudo apt-get install -y tmux fi npx tsx src/index.ts web --port 3151 > /tmp/boot.log 2>&1 & SERVER_PID=$! trap "kill $SERVER_PID 2>/dev/null || true" EXIT for i in $(seq 1 30); do if curl -fsS http://localhost:3151/api/status -o /dev/null; then echo "Server booted in ${i}s" exit 0 fi if ! kill -0 $SERVER_PID 2>/dev/null; then echo "Server exited before becoming ready. Logs:" cat /tmp/boot.log exit 1 fi sleep 1 done echo "Server did not respond on /api/status within 30s. Logs:" cat /tmp/boot.log exit 1 test: name: Unit & integration tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 22 cache: 'npm' - name: Install dependencies run: npm ci - name: Install tmux run: | if ! command -v tmux >/dev/null; then sudo apt-get update -qq sudo apt-get install -y tmux fi - name: Run unit & integration tests # Excludes the suites that need chromium, per-machine PNG baselines or a # quiet machine — see config/test-suites.ts for the list and the reason # behind each entry. Identical to what `npm test` runs locally. # Safe in CI: TmuxManager no-ops all shell commands under VITEST (test/setup.ts). run: npm run test:ci - name: Run xterm-zerolag-input package tests # Layers 1-3 of the predictive-echo suites (unit laws, fixture replay, # seeded fuzz): deterministic, no browser, no live server. Depends on # the ROOT `npm ci` above — workspaces hoist the package's vitest into # the root node_modules; do not add a separate install here. run: npx vitest run working-directory: packages/xterm-zerolag-input # Note: three suites are excluded from CI, each with its own local runner: # npm run test:browser Playwright + chromium (+ a live server, and a real # codex binary for codex-predictive-echo) # npm run test:mobile the above plus environment-specific PNG baselines # npm run test:perf wall-clock benchmarks; need an otherwise idle machine # config/test-suites.ts holds the globs; the configs derive from it so the # exclusions here and those runners cannot drift apart. Everything else runs in # the `test` job above, which is the same thing `npm test` runs.