Some checks failed
morphit-release / Build + publish release tarball (push) Has been cancelled
329 lines
15 KiB
YAML
329 lines
15 KiB
YAML
# Morphit CI — runs on every push and pull request.
|
||
#
|
||
# Four gates, in order of cheapness:
|
||
# 1. typecheck-sweep — fast, catches type errors across all
|
||
# backend + library workspaces (apps/web excluded — it
|
||
# extends the generated .svelte-kit/tsconfig.json, which
|
||
# this sweep doesn't materialize)
|
||
# 2. web-check — fast, runs `svelte-kit sync` then
|
||
# `svelte-check` against apps/web; catches Svelte-template
|
||
# + .svelte-aware TypeScript errors the workspace sweep
|
||
# can't see
|
||
# 3. ansible-lint — fast, catches playbook quality drift
|
||
# 4. integration — the indexer integration suite (vitest)
|
||
# against a REAL Postgres 16, so cross-table SQL + migration
|
||
# regressions surface in CI instead of only in manual runs.
|
||
# (Added cp426: the suite was green but CI-invisible, so it
|
||
# silently drifted — see the two stale tests fixed at cp423.)
|
||
# 5. run-smokes — the full smoke suite (~4,400+ scenarios
|
||
# across ~190 runners, triple-pulsed for flakes)
|
||
#
|
||
# If any gate fails, the run fails. Gates 1–4 run in parallel;
|
||
# the smokes gate is the longest-running and effectively serial.
|
||
# The web-check job intentionally runs `svelte-kit sync &&
|
||
# svelte-check` directly (not via run-smokes →
|
||
# workspace-typecheck-smoke) so the protection is legible in
|
||
# the CI surface and a sandbox-broken smoke runner can't hide
|
||
# behind a SKIP exit code.
|
||
#
|
||
# Forgejo Actions has GitHub-Actions-compatible syntax; this
|
||
# workflow runs unchanged on either as long as the runner is a
|
||
# Debian/Ubuntu host.
|
||
#
|
||
# AUDIT-CI-2 (cp18 deep-deep): third-party actions are SHA-pinned,
|
||
# not tag-pinned. Tags like `@v4` can be re-pointed at attacker-
|
||
# controlled commits if an action repo is compromised; SHAs are
|
||
# immutable. The version comment after each SHA (`# v4.2.2`) lets
|
||
# Dependabot/Renovate auto-suggest updates. When bumping, ALSO
|
||
# audit the diff from the previous SHA before merging.
|
||
|
||
name: morphit-ci
|
||
|
||
on:
|
||
push:
|
||
branches:
|
||
- main
|
||
pull_request:
|
||
|
||
# Newer pushes to the same branch cancel any still-running CI on
|
||
# that branch. Saves CI minutes on rapid-fire commit + amend.
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
typecheck:
|
||
name: TypeScript typecheck (sweep all workspaces)
|
||
runs-on: ubuntu-24.04
|
||
# cp145 — every job gets an explicit wall-clock ceiling.
|
||
# cp143 put a per-smoke timeout inside scripts/run-smokes.sh;
|
||
# this job-level timeout is the complement for the other CI
|
||
# steps (npm ci, tsc invocations) that no smoke wraps.
|
||
# Current runtime is <2 minutes; 10× headroom for slow runners.
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
|
||
- name: Install Node.js 22 LTS
|
||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
|
||
- name: Install workspace dependencies
|
||
# --ignore-scripts skips native-build steps (better-sqlite3
|
||
# needs nodejs.org headers which may be restricted). The
|
||
# typecheck doesn't actually run any binaries so this is
|
||
# fine here — but the run-smokes job needs a full install.
|
||
run: npm ci --ignore-scripts --no-audit --no-fund
|
||
|
||
- name: Run typecheck-sweep
|
||
run: bash scripts/typecheck-sweep.sh
|
||
|
||
web-check:
|
||
name: apps/web svelte-check (svelte-kit sync + svelte-aware tsc)
|
||
runs-on: ubuntu-24.04
|
||
# cp145 — current runtime is ~3 minutes; 10 minutes is roomy.
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
|
||
- name: Install Node.js 22 LTS
|
||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
|
||
- name: Install workspace dependencies
|
||
# --ignore-scripts is safe here — svelte-check doesn't
|
||
# need any native postinstall steps. Workspace links
|
||
# to @morphit/* must materialize, which they do under
|
||
# the standard `npm ci` workspace install.
|
||
run: npm ci --ignore-scripts --no-audit --no-fund
|
||
|
||
- name: svelte-kit sync (generate .svelte-kit/tsconfig.json)
|
||
working-directory: apps/web
|
||
# The web tsconfig extends `.svelte-kit/tsconfig.json`,
|
||
# generated by svelte-kit sync from the project config.
|
||
# Without this step, svelte-check would run against a
|
||
# stale or missing extended tsconfig.
|
||
run: npx svelte-kit sync
|
||
|
||
- name: svelte-check (strict, errors fail the build)
|
||
working-directory: apps/web
|
||
# `--threshold error` excludes warnings from the fail
|
||
# signal; `--fail-on-warnings false` is the documented
|
||
# default but stated explicitly here for clarity.
|
||
# The script in apps/web/package.json (`npm run check`)
|
||
# does the same thing; invoking the binary directly
|
||
# keeps the CI step legible without an indirection.
|
||
run: npx svelte-check --tsconfig ./tsconfig.json --threshold error
|
||
|
||
integration:
|
||
name: Integration tests (real Postgres 16)
|
||
runs-on: ubuntu-24.04
|
||
# cp145 — explicit wall-clock ceiling. The indexer integration
|
||
# suite is ~10 files / ~92 tests and runs in well under a minute
|
||
# against a warm DB; 15 minutes is generous headroom for the
|
||
# postgres service to become healthy + npm ci on a cold cache.
|
||
timeout-minutes: 15
|
||
# ── Why an explicit `docker run` instead of a `services:` block (cp426) ──
|
||
# This project's Forgejo runner uses act_runner's HOST executor: jobs run
|
||
# directly on the host, not in an ephemeral container (see `hostexecutor`
|
||
# in the run logs). On a host executor a GitHub-style `services:` block does
|
||
# NOT hand the job an isolated Postgres on localhost:5432 with our creds —
|
||
# the first attempt (task 895) reached *a* Postgres on localhost:5432 that
|
||
# had no `morphit_test` role and failed with 28P01 (either the host's own
|
||
# Postgres, or a service container that never received our env). So we start
|
||
# our OWN throwaway container explicitly, pass credentials directly as -e,
|
||
# and bind it to a NON-standard loopback port (55432) so it can never
|
||
# collide with a Postgres already running on the host's 5432.
|
||
#
|
||
# Requires the runner's job steps to be able to invoke `docker` (this host
|
||
# already runs its Docker stack, so the CLI is present). If a future run
|
||
# fails at the "Start throwaway Postgres" step with a docker permission /
|
||
# not-found error, add the runner's user to the `docker` group on the host
|
||
# (a one-time host change, not a repo change) — the tests themselves are
|
||
# unaffected.
|
||
#
|
||
# The harness (apps/indexer/test/integration/harness.ts) CREATEs a random
|
||
# schema per suite, runs the PRODUCTION migration path (runMigrations), and
|
||
# DROP SCHEMA … CASCADE on teardown — so an EMPTY database is all we need.
|
||
# Tests SKIP when TEST_DATABASE_URL is unset (INTEGRATION_ENABLED); setting
|
||
# it in the run step is what turns them on.
|
||
steps:
|
||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
|
||
- name: Install Node.js 22 LTS
|
||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
|
||
- name: Start throwaway Postgres 16 (own container, loopback port 55432)
|
||
run: |
|
||
# Remove any leftover from a previous run on this persistent host.
|
||
docker rm -f morphit-ci-pg >/dev/null 2>&1 || true
|
||
docker run -d --name morphit-ci-pg \
|
||
-e POSTGRES_USER=morphit_test \
|
||
-e POSTGRES_PASSWORD=test \
|
||
-e POSTGRES_DB=morphit_test \
|
||
-p 127.0.0.1:55432:5432 \
|
||
postgres:16
|
||
# Wait until it accepts connections (pg_isready INSIDE the container,
|
||
# so the host needs no psql client). Fail loudly if it never comes up
|
||
# rather than letting the tests fail later with a vague error.
|
||
#
|
||
# `-h 127.0.0.1` forces a TCP check. During its first boot the
|
||
# postgres image runs a TEMPORARY socket-only server (listen_addresses='')
|
||
# to execute initdb, then stops it and starts the real server. A
|
||
# socket pg_isready (no -h) gives a false "ready" against that temp
|
||
# server, and the very next check lands in the stop/restart window
|
||
# with "no response" (task 1505). TCP is only ever answered by the
|
||
# real server, so it can't false-positive on the initdb phase.
|
||
for i in $(seq 1 30); do
|
||
if docker exec morphit-ci-pg pg_isready -h 127.0.0.1 -p 5432 -U morphit_test -d morphit_test >/dev/null 2>&1; then
|
||
echo "postgres ready after ${i}s"
|
||
break
|
||
fi
|
||
echo "waiting for postgres (${i}/30)..."
|
||
sleep 1
|
||
done
|
||
docker exec morphit-ci-pg pg_isready -h 127.0.0.1 -p 5432 -U morphit_test -d morphit_test
|
||
|
||
- name: Install workspace dependencies
|
||
# --ignore-scripts is SAFE here: the indexer integration tests use
|
||
# `pg`, not better-sqlite3, so no native build is required — and
|
||
# skipping postinstall dodges the better-sqlite3 native build (which
|
||
# the smokes job needs build-essential for) entirely. The @morphit/*
|
||
# workspace links still materialize under the standard workspace ci.
|
||
run: npm ci --ignore-scripts --no-audit --no-fund
|
||
|
||
- name: Run indexer integration tests
|
||
working-directory: apps/indexer
|
||
env:
|
||
# 127.0.0.1:55432 → our own container above (NOT the host's 5432).
|
||
TEST_DATABASE_URL: postgres://morphit_test:test@127.0.0.1:55432/morphit_test
|
||
run: npm run test:integration
|
||
|
||
- name: Tear down Postgres
|
||
if: always()
|
||
run: docker rm -f morphit-ci-pg >/dev/null 2>&1 || true
|
||
|
||
ansible-lint:
|
||
name: ansible-lint (playbook quality gate)
|
||
runs-on: ubuntu-24.04
|
||
# cp145 — current runtime is <1 minute; 5 minutes is roomy.
|
||
timeout-minutes: 5
|
||
steps:
|
||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
|
||
- name: Install Python 3.12 + ansible-lint
|
||
run: |
|
||
# cp190 — update ONLY the base Ubuntu repos, not the
|
||
# third-party repos in /etc/apt/sources.list.d/ that the
|
||
# runner base image ships (e.g. repo.zabbix.com). We
|
||
# install nothing from those repos, and a transient
|
||
# Hash-Sum-mismatch on one of them (CI runs 523/524 died
|
||
# exactly this way) must not fail an unrelated apt install.
|
||
# `Dir::Etc::sourceparts=-` makes apt ignore sources.list.d;
|
||
# the retry loop rides out a brief base-mirror hiccup.
|
||
for i in 1 2 3; do
|
||
sudo apt-get update -qq \
|
||
-o Dir::Etc::sourceparts=- \
|
||
-o APT::Get::List-Cleanup=0 && break
|
||
echo "apt-get update attempt $i failed; retrying in 5s..."
|
||
sleep 5
|
||
done
|
||
sudo apt-get install -y --no-install-recommends \
|
||
python3 python3-pip python3-venv
|
||
# Use --break-system-packages because Ubuntu 24.04 marks
|
||
# the system Python as externally managed. This is a
|
||
# CI runner; the host is ephemeral.
|
||
pip3 install --break-system-packages --quiet \
|
||
ansible ansible-lint
|
||
|
||
- name: Install required ansible collections
|
||
run: |
|
||
ansible-galaxy collection install -r \
|
||
ops/ansible/collections/requirements.yml
|
||
|
||
- name: Run ansible-lint
|
||
working-directory: ops/ansible
|
||
run: ansible-lint --offline --strict playbook.yml
|
||
|
||
smokes:
|
||
name: Smoke suite (run-smokes.sh, triple-pulse)
|
||
runs-on: ubuntu-24.04
|
||
# cp145 — triple-pulse currently takes ~18 minutes (6 minutes
|
||
# per pulse × 3). cp143's per-smoke 240s timeout caps any
|
||
# single hung smoke; this job-level ceiling caps the whole
|
||
# battery in case a non-smoke step (npm ci, build, ansible-lint
|
||
# install, ansible-galaxy collection install) hangs, or in
|
||
# case the outer `for i in 1 2 3` loop itself wedges.
|
||
# 45 minutes gives 2.5× headroom over the current observed
|
||
# runtime.
|
||
timeout-minutes: 45
|
||
steps:
|
||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
|
||
- name: Install Node.js 22 LTS
|
||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
|
||
- name: Install build tools (for better-sqlite3 native build)
|
||
run: |
|
||
# cp190 — see the ansible-lint job: scope apt update to the
|
||
# base Ubuntu repos so a flaky third-party repo in the runner
|
||
# image (e.g. repo.zabbix.com) can't fail this install.
|
||
for i in 1 2 3; do
|
||
sudo apt-get update -qq \
|
||
-o Dir::Etc::sourceparts=- \
|
||
-o APT::Get::List-Cleanup=0 && break
|
||
echo "apt-get update attempt $i failed; retrying in 5s..."
|
||
sleep 5
|
||
done
|
||
sudo apt-get install -y --no-install-recommends \
|
||
build-essential python3
|
||
|
||
- name: Install workspace dependencies (with native build)
|
||
# The smoke suite includes a matrix-bot deps-pin-check
|
||
# which needs node_modules populated with REAL packages.
|
||
# Don't pass --ignore-scripts here.
|
||
run: npm ci --no-audit --no-fund
|
||
|
||
- name: Install ansible-lint (for ansible-lint-smoke)
|
||
run: |
|
||
sudo apt-get install -y --no-install-recommends \
|
||
python3-pip
|
||
pip3 install --break-system-packages --quiet \
|
||
ansible ansible-lint
|
||
ansible-galaxy collection install -r \
|
||
ops/ansible/collections/requirements.yml
|
||
|
||
- name: Build workspaces that ship compiled artifacts
|
||
# `apps/mcp-server` is the only workspace whose `bin`
|
||
# points into `dist/` and therefore needs `tsc -p
|
||
# tsconfig.build.json` to run before its smoke spawns
|
||
# `node dist/main.js`. The smoke is self-healing as of
|
||
# cp142 (will lazy-build if missing), but doing the
|
||
# build here makes the failure mode legible — a build
|
||
# break surfaces as this step failing, not as a
|
||
# mystery hang inside the smoke runner.
|
||
#
|
||
# If a future workspace adds a similar dist-bin, add
|
||
# it here and the matching `spawn-dist-prebuild-coverage-smoke`
|
||
# (cp142) will keep this list and the smokes in sync.
|
||
run: npm run build -w apps/mcp-server
|
||
|
||
- name: Run smokes (triple-pulse)
|
||
# We run the suite three times to catch flakes. Each
|
||
# invocation is ~30s in CI; the parallel typecheck +
|
||
# ansible-lint jobs finish well before this anyway.
|
||
run: |
|
||
for i in 1 2 3; do
|
||
echo "=== Pulse $i ==="
|
||
bash scripts/run-smokes.sh
|
||
done
|