blob: 5b34d660657ed8104f8dcbfd5a29e7b2374d9626 [file]
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
changes:
runs-on: ubuntu-latest
outputs:
code: ${{ steps.plan.outputs.code }}
e2e: ${{ steps.plan.outputs.e2e }}
headless: ${{ steps.plan.outputs.headless }}
runtime_sandbox: ${{ steps.plan.outputs.runtime_sandbox }}
script_mode: ${{ steps.plan.outputs.script_mode }}
storage_stress: ${{ steps.plan.outputs.storage_stress }}
storybook: ${{ steps.plan.outputs.storybook }}
unit: ${{ steps.plan.outputs.unit }}
workspaces: ${{ steps.plan.outputs.workspaces }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: plan
name: Select affected test surfaces
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
EVENT_NAME: ${{ github.event_name }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [[ "$EVENT_NAME" == "push" ]]; then
node scripts/ci-test-plan.mjs --full >> "$GITHUB_OUTPUT"
else
node scripts/ci-test-plan.mjs --base "$BASE_SHA" --head "$HEAD_SHA" >> "$GITHUB_OUTPUT"
fi
typecheck:
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run format:check
- run: npm run build
- run: npm run typecheck
# Generated-artifact governance. astryx-theme/maka.{css,js} are built
# from makaTheme.ts, which is now the renderer's type-scale authority —
# editing the scale without regenerating would leave the authority and
# the shipped ladder disagreeing, and nothing else would notice.
- name: Astryx theme drift
run: npm run astryx:theme -- --check
# Dead-code / dependency governance. Entry points and reasoned ignores
# live in knip.json; both workspaces must stay at zero findings.
- name: Knip (apps/desktop)
run: npx knip --workspace apps/desktop
- name: Knip (packages/ui)
run: npx knip --workspace packages/ui
# Dead-CSS governance. Lived only under `check:release`, so a regression
# stayed invisible until a release was cut; it was red on main when this
# step was added. Same class of source-scanning guard as knip above.
- name: Dead CSS
run: node scripts/check-dead-css.mjs --check
# Storybook fidelity: every Product/* story must name the real user path
# to the state it renders. Presence is mechanical; truth stays with the
# reviewer. See apps/desktop/stories/FIDELITY.md.
- name: Story annotations
run: node scripts/check-story-annotations.mjs
test:
needs: changes
if: needs.changes.outputs.unit == 'true' || needs.changes.outputs.script_mode != 'none'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- uses: astral-sh/setup-uv@v6
if: needs.changes.outputs.headless == 'true'
- name: Install pinned Harbor contract runtime
if: needs.changes.outputs.headless == 'true'
run: |
uv tool install "harbor==0.13.2"
uv tool dir --bin >> "$GITHUB_PATH"
- name: Install Linux runtime dependencies
if: needs.changes.outputs.headless == 'true' || needs.changes.outputs.runtime_sandbox == 'true'
run: sudo apt-get update && sudo apt-get install -y ripgrep bubblewrap
# Ubuntu 24.04 hosted runners gate unprivileged user namespaces through
# AppArmor, which otherwise makes bwrap fail while configuring loopback.
- name: Enable bubblewrap user namespaces
if: needs.changes.outputs.runtime_sandbox == 'true'
run: |
if [[ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]]; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
if [[ -e /proc/sys/kernel/unprivileged_userns_clone ]]; then
sudo sysctl -w kernel.unprivileged_userns_clone=1
fi
- run: npm ci
# build:test skips the renderer bundle (vite); test:dist only consumes
# tsc outputs (dist/main + dist/renderer side-files), never the vite
# bundle. e2e builds its own renderer in a separate job.
- run: npm run build:test
if: needs.changes.outputs.unit == 'true' || needs.changes.outputs.script_mode != 'none'
- name: Linux sandbox smoke
if: needs.changes.outputs.runtime_sandbox == 'true'
env:
MAKA_REQUIRE_LINUX_SANDBOX_SMOKE: '1'
run: npm exec -w @maka/runtime -- node --test dist/__tests__/linux-sandbox-smoke.test.js
- name: Run fast script tests
if: needs.changes.outputs.script_mode == 'fast' || needs.changes.outputs.script_mode == 'full'
run: npm run test:scripts
- name: Run extended script tests
if: needs.changes.outputs.script_mode == 'extended' || needs.changes.outputs.script_mode == 'full'
run: npm run test:scripts:extended
# Workspaces consume the dist built above. Selection includes reverse
# dependencies, while bounded concurrency avoids both the old serial
# critical path and an unbounded process stampede on two-core runners.
- name: Run affected workspace tests
if: needs.changes.outputs.unit == 'true'
env:
HEADLESS_AFFECTED: ${{ needs.changes.outputs.headless }}
STORAGE_STRESS: ${{ needs.changes.outputs.storage_stress }}
WORKSPACES: ${{ needs.changes.outputs.workspaces }}
run: |
if [[ "$HEADLESS_AFFECTED" == "true" ]]; then
export MAKA_REQUIRE_HARBOR_CONTRACT=1
fi
if [[ "$STORAGE_STRESS" == "true" ]]; then
export MAKA_STORAGE_STRESS=1
fi
node scripts/run-workspace-tests-parallel.mjs --concurrency=3 --workspaces="$WORKSPACES"
e2e:
needs: changes
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
# Electron is a GUI app; on a headless Linux runner it needs a virtual
# display. The suite already runs with show:false, but the process still
# requires an X server to start.
- name: Ensure xvfb
run: command -v xvfb-run >/dev/null 2>&1 || { sudo apt-get update && sudo apt-get install -y xvfb; }
- name: E2E
run: xvfb-run -a npm --workspace @maka/desktop run e2e
# Design governance: the CDP alignment auditor walks the e2e-fixture
# fixtures and fails on same-type height mismatches, mixed-type
# centerline drift, or radius-family splits. Reuses the renderer the
# e2e step just built; same xvfb pattern.
- name: Alignment audit
run: xvfb-run -a node scripts/audit-alignment.mjs
# Storybook build + render smoke. Independent of Electron e2e: the smoke
# launches Playwright Chromium, not `_electron.launch`, so it does not
# need xvfb or the desktop cold-start suite. See stories/FIDELITY.md.
storybook:
needs: changes
if: needs.changes.outputs.storybook == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
# Stories import @maka/core / @maka/ui package exports (dist/). The old
# e2e job paid for this via `build:with-deps`; the split job must too.
- name: Build workspace packages
run: npm --workspace @maka/desktop run build:workspace-deps
# The smoke calls `chromium.launch()`; Electron's binary is irrelevant.
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Build Storybook
run: npm --workspace @maka/desktop run build-storybook
- name: Storybook smoke
run: npm --workspace @maka/desktop run smoke:storybook