Skip to content

CI

CI #138

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
# Nightly full matrix: no path gating, no cache restore — the cold
# safety net that catches anything the scoped runs wrongly skipped.
schedule:
- cron: '0 3 * * *'
permissions:
contents: read
# A newer push to the same ref supersedes the running sweep — never
# stack two full runs.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# Computes which slices of the tree a push touched, so the non-turbo
# jobs (the extension shards) can skip when nothing they cover
# changed. Turbo-driven jobs skip work through their cache instead.
changes:
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
extension: ${{ steps.filter.outputs.extension }}
steps:
# Only pushes have a diff base to gate on — every other trigger
# runs the full matrix and never reads this job's output.
- name: Checkout code
if: github.event_name == 'push'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Detect changed paths
if: github.event_name == 'push'
id: filter
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
filters: |
extension:
- 'apps/extension/**'
- 'packages/**'
- 'pnpm-lock.yaml'
- 'package.json'
- 'turbo.json'
- 'tsconfig.base.json'
- '.github/workflows/ci.yml'
# Fast tripwires: lint (warn-only until the lint-debt session),
# typecheck, and the i18n gates. Fails in minutes when a push is
# structurally broken, while the test matrix grinds in parallel.
gates:
runs-on: ubuntu-latest
# Job timeouts sit at ~2x each job's observed wall time so a wedged
# infra step (dead apt mirror, hung install) releases the concurrency
# slot in minutes instead of holding it for the 6h default.
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.x
cache: pnpm
- name: Cache Electron
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/electron
key: electron-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: electron-${{ runner.os }}-
# Content-addressed replay of unchanged tasks across runs; the
# nightly scheduled run stays cold on purpose.
- name: Cache turbo
if: github.event_name != 'schedule'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .turbo/cache
key: turbo-gates-${{ runner.os }}-${{ github.sha }}
restore-keys: turbo-gates-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm biome check . || echo "::warning::Lint issues found — see output above"
- name: Typecheck all packages
run: pnpm turbo typecheck
- name: i18n hardcoded-string scan
run: pnpm turbo scan:i18n
- name: i18n locale-catalog lint
run: pnpm turbo lint:i18n-locales
# Plain-node vitest packages, serialized through turbo so suites that
# bind ports (daemon, web) never contend on one VM.
test-packages:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.x
cache: pnpm
- name: Cache Electron
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/electron
key: electron-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: electron-${{ runner.os }}-
- name: Cache turbo
if: github.event_name != 'schedule'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .turbo/cache
key: turbo-test-packages-${{ runner.os }}-${{ github.sha }}
restore-keys: turbo-test-packages-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test node packages
run: >
pnpm turbo test --concurrency=1
--filter=@openheaders/core
--filter=@openheaders/i18n
--filter=@openheaders/oracle
--filter=@openheaders/oracle-host-browser
--filter=@openheaders/nm-host
--filter=@openheaders/cli
--filter=@openheaders/daemon
--filter=@openheaders/web
# The two electron-as-node suites, isolated on their own VM and
# serialized — Electron suites flake under same-machine contention.
test-electron-hosts:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.x
cache: pnpm
- name: Cache Electron
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/electron
key: electron-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: electron-${{ runner.os }}-
# The runner's regional apt mirror (azure.archive) blackholes often
# enough to wedge apt past its own timeouts — route straight to the
# canonical archive, and keep the short acquire timeouts as the
# backstop for any remaining slow leg.
- name: Install Electron system dependencies
run: >
sudo sed -i 's|http://azure.archive.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|' /etc/apt/apt-mirrors.txt &&
sudo apt-get -o Acquire::Retries=1 -o Acquire::http::Timeout=15 -o Acquire::https::Timeout=15 update &&
sudo apt-get -o Acquire::Retries=1 -o Acquire::http::Timeout=15 -o Acquire::https::Timeout=15 install -y
libgtk-3-0 libnotify4 libnss3 libxss1 libasound2t64 libgbm1
- name: Cache turbo
if: github.event_name != 'schedule'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .turbo/cache
key: turbo-electron-hosts-${{ runner.os }}-${{ github.sha }}
restore-keys: turbo-electron-hosts-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test electron hosts
run: >
pnpm turbo test --concurrency=1
--filter=@openheaders/oracle-host-node
--filter=@openheaders/desktop
# The extension suite's cost is per-file overhead across ~1,400 test
# files, so it shards near-linearly. Path-gated: only pushes that
# touch the extension or its inputs pay the three runners; every
# non-push trigger (PR, dispatch, nightly) runs unconditionally.
test-extension:
name: test-extension (shard ${{ matrix.shard }}/3)
runs-on: ubuntu-latest
timeout-minutes: 20
needs: changes
if: github.event_name != 'push' || needs.changes.outputs.extension == 'true'
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.x
cache: pnpm
- name: Cache Electron
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/electron
key: electron-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: electron-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Typecheck stays off here: the shard runners never build workspace
# deps, so @openheaders/ui's dist-pointed types resolve to any and
# tsc storms. Test-file types ride the gates job's turbo typecheck
# (extension typecheck script runs tsconfig.test.json too).
- name: Test extension (shard ${{ matrix.shard }}/3)
run: >
pnpm --filter @openheaders/extension exec vitest run
--shard=${{ matrix.shard }}/3
--typecheck.enabled=false
--reporter=default --reporter=github-actions
build-e2e:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.x
cache: pnpm
- name: Cache Electron
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/electron
key: electron-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: electron-${{ runner.os }}-
# The runner's regional apt mirror (azure.archive) blackholes often
# enough to wedge apt past its own timeouts — route straight to the
# canonical archive, and keep the short acquire timeouts as the
# backstop for any remaining slow leg.
- name: Install Electron system dependencies
run: >
sudo sed -i 's|http://azure.archive.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|' /etc/apt/apt-mirrors.txt &&
sudo apt-get -o Acquire::Retries=1 -o Acquire::http::Timeout=15 -o Acquire::https::Timeout=15 update &&
sudo apt-get -o Acquire::Retries=1 -o Acquire::http::Timeout=15 -o Acquire::https::Timeout=15 install -y
libgtk-3-0 libnotify4 libnss3 libxss1 libasound2t64 libgbm1
- name: Cache turbo
if: github.event_name != 'schedule'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .turbo/cache
key: turbo-build-e2e-${{ runner.os }}-${{ github.sha }}
restore-keys: turbo-build-e2e-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
# The web, extension, and desktop builds each peak ~4GB, so on the
# public 4-core/16GB runner two app builds share the box safely but
# three would not — and the heap cap stays lifted past Node's
# default so the big builds can reach their real working sets.
# The extension builds its chrome target only: one leg proves the
# pipeline, the e2e never loads it, and the four-browser matrix
# belongs to the release lanes.
- name: Build all packages
run: pnpm turbo build --concurrency=2 --filter=!@openheaders/extension
env:
NODE_OPTIONS: --max-old-space-size=6144
- name: Build extension (chrome only)
run: pnpm turbo build:chrome --filter=@openheaders/extension
env:
NODE_OPTIONS: --max-old-space-size=6144
# Ubuntu 24.04 confines unprivileged user namespaces behind AppArmor,
# which breaks Electron's sandbox under xvfb.
- name: Allow unprivileged user namespaces for the Electron sandbox
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
# The desktop's TUI gate probes PATH for `oh` — without it every
# provisioning state collapses to the install dialog.
- name: Put the built oh CLI on PATH
run: |
printf '#!/bin/sh\nexec node "%s/apps/cli/dist/cli.js" "$@"\n' "$PWD" | sudo tee /usr/local/bin/oh >/dev/null
sudo chmod +x /usr/local/bin/oh
command -v oh
- name: E2E — CLI provisioning (desktop + oh + pty)
run: xvfb-run --auto-servernum pnpm --filter @openheaders/desktop exec playwright test --trace retain-on-failure tests/e2e/cli-provision.spec.ts
- name: Upload e2e traces on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cli-provision-e2e-results
path: apps/desktop/tests/e2e/test-results
if-no-files-found: ignore
retention-days: 7