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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions .github/actions/checks-lane/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: effect-microvm checks lane
description: effect-microvm's steps for each phase of systemfsoftware's reusable-checks.yml
inputs:
lane:
description: plan, static, affected, test, test-after, timings, gate or docs
required: true
job:
description: The test job from the plan, as JSON
required: false
default: "{}"
profile:
description: Conformance profile, `local` (pull request) or `per-change` (main and the merge queue)
required: false
default: per-change
runs:
using: composite
steps:
- if: inputs.lane == 'plan' || inputs.lane == 'timings'
uses: systemfsoftware/systemfsoftware/.github/actions/sfs-tools@21dfc32e9927c87a31a37d19e5643445a4e398c3
with:
repository: systemfsoftware/systemfsoftware
ref: 21dfc32e9927c87a31a37d19e5643445a4e398c3
- if: inputs.lane == 'plan'
shell: bash -e {0}
run: corepack enable

# Before the merge: the tests this change affects or touches, including any microVM integration file it touches.
- name: Affected lane environment
if: inputs.lane == 'affected'
shell: bash -e {0}
run: printf 'CI=true\nCONFORMANCE_PROFILE=local\nTURBO_LOG_ORDER=stream\n' >> "$GITHUB_ENV"
- uses: systemfsoftware/systemfsoftware/.github/actions/install-deps@21dfc32e9927c87a31a37d19e5643445a4e398c3
if: inputs.lane == 'affected'
- uses: systemfsoftware/systemfsoftware/.github/actions/grant-kvm@21dfc32e9927c87a31a37d19e5643445a4e398c3
if: inputs.lane == 'affected'
- name: Turbo cache
if: inputs.lane == 'affected'
uses: actions/cache@v6
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-affected-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-affected-
turbo-${{ runner.os }}-checks-
turbo-${{ runner.os }}-
- name: The tests this change affects or touches
id: affected
if: inputs.lane == 'affected'
shell: bash -e {0}
run: node .github/ci/pr-tests.mjs plan
- name: Run them
if: inputs.lane == 'affected'
shell: bash -e {0}
env:
BASE: ${{ steps.affected.outputs.base }}
run: node .github/ci/pr-tests.mjs run

- name: Static lane environment
if: inputs.lane == 'static'
shell: bash -e {0}
run: printf 'CI=true\nOXLINT_FORMAT=github\nTSGO_FORMAT=github-actions\nTURBO_LOG_ORDER=stream\n' >> "$GITHUB_ENV"
- uses: systemfsoftware/systemfsoftware/.github/actions/install-deps@21dfc32e9927c87a31a37d19e5643445a4e398c3
if: inputs.lane == 'static'
- uses: cachix/install-nix-action@v31
if: inputs.lane == 'static' || inputs.lane == 'docs'
- name: dprint from the flake
if: inputs.lane == 'static' || inputs.lane == 'docs'
shell: bash -e {0}
run: echo "$(nix build --no-link --print-out-paths .#dprint)/bin" >> "$GITHUB_PATH"
- name: Turbo cache
if: inputs.lane == 'static'
uses: actions/cache@v6
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-checks-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-checks-
turbo-${{ runner.os }}-

- name: Test lane environment
if: inputs.lane == 'test'
shell: bash -e {0}
run: printf 'CI=true\n' >> "$GITHUB_ENV"
- name: Local conformance profile
if: inputs.lane == 'test' && inputs.profile == 'local'
shell: bash -e {0}
run: printf 'CONFORMANCE_PROFILE=local\n' >> "$GITHUB_ENV"
- uses: systemfsoftware/systemfsoftware/.github/actions/install-deps@21dfc32e9927c87a31a37d19e5643445a4e398c3
if: inputs.lane == 'test'
- uses: systemfsoftware/systemfsoftware/.github/actions/grant-kvm@21dfc32e9927c87a31a37d19e5643445a4e398c3
if: inputs.lane == 'test'
- name: Turbo cache
if: inputs.lane == 'test'
uses: actions/cache@v6
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-test-${{ fromJSON(inputs.job).id }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-test-${{ fromJSON(inputs.job).id }}-
turbo-${{ runner.os }}-checks-
turbo-${{ runner.os }}-
- name: Build
if: inputs.lane == 'test'
shell: bash -e {0}
env:
FILTERS: ${{ fromJSON(inputs.job).filters }}
run: corepack pnpm exec turbo run build --output-logs=errors-only --log-order=stream $FILTERS

# Heavy: the whole real-microVM integration suites run on main, never before the merge.
- name: microVM integration suites
if: inputs.lane == 'test-after' && inputs.profile != 'local' && (fromJSON(inputs.job).shard == null || fromJSON(inputs.job).shard.index == 1)
shell: bash -e {0}
env:
FILTERS: ${{ fromJSON(inputs.job).filters }}
run: corepack pnpm exec turbo run test:integration --concurrency=1 --continue --output-logs=new-only --log-order=stream $FILTERS
4 changes: 4 additions & 0 deletions .github/ci/no-tests-before-merge.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { appendFile } from 'node:fs/promises'

const output = process.env.GITHUB_OUTPUT
if (process.argv[2] === 'plan' && output) await appendFile(output, 'jobs=[]\n')
130 changes: 130 additions & 0 deletions .github/ci/pr-tests.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { execFile, spawn } from 'node:child_process'
import { appendFile, readFile } from 'node:fs/promises'
import { promisify } from 'node:util'

const exec = promisify(execFile)

const command = async (cmd, args, env = {}) => {
const { stdout } = await exec(cmd, args, { env: { ...process.env, ...env }, maxBuffer: 64 * 1024 * 1024 })
return stdout
}

const git = async (...args) => (await command('git', args)).trim()

const turboLs = async (env, affected) => {
const text = await command('corepack', [
'pnpm',
'exec',
'turbo',
'ls',
...(affected ? ['--affected'] : []),
'--output=json',
], env)
return JSON.parse(text.slice(text.indexOf('{'))).packages.items
}

const manifest = async (pkg) => JSON.parse(await readFile(`${pkg.path}/package.json`, 'utf8'))

const owner = (packages, file) =>
packages
.filter((pkg) => file.startsWith(`${pkg.path}/`))
.reduce((best, pkg) => (best === undefined || pkg.path.length > best.path.length ? pkg : best), undefined)

const otherTestTasks = ['test:integration']

const plan = async (base) => {
const head = await git('rev-parse', 'HEAD')
const scm = { TURBO_SCM_BASE: base, TURBO_SCM_HEAD: head }
const changed = (await git('diff', '--name-only', '--diff-filter=AMR', '-z', base, head)).split('\0').filter(Boolean)
const [all, affected] = await Promise.all([turboLs(scm, false), turboLs(scm, true)])
const manifests = new Map(await Promise.all(all.map(async (pkg) => [pkg.name, await manifest(pkg)])))

const filesByPackage = new Map()
for (const file of changed) {
const pkg = owner(all, file)
if (pkg === undefined) continue
filesByPackage.set(pkg.name, [...(filesByPackage.get(pkg.name) ?? []), file.slice(pkg.path.length + 1)])
}

const commands = [[
'corepack',
'pnpm',
'exec',
'turbo',
'run',
'test',
'--affected',
'--continue',
'--concurrency=50%',
'--output-logs=new-only',
'--log-order=stream',
'--',
`--changed=${base}`,
'--passWithNoTests',
]]
for (const [name, files] of filesByPackage) {
for (const task of otherTestTasks) {
if (manifests.get(name)?.scripts?.[task] === undefined) continue
commands.push([
'corepack',
'pnpm',
'exec',
'turbo',
'run',
task,
`--filter=${name}`,
'--output-logs=new-only',
'--',
'--passWithNoTests',
...files,
])
}
}

const browserPackages = affected.filter((pkg) => {
const json = manifests.get(pkg.name)
return json?.scripts?.test !== undefined && json.devDependencies?.playwright !== undefined
})
return { head, scm, changed, affected, commands, browserPackages }
}

const status = (cmd, args, env) =>
new Promise((resolve) => {
const child = spawn(cmd, args, { env: { ...process.env, ...env }, stdio: 'inherit' })
child.on('close', (code) => resolve(code ?? 1))
child.on('error', () => resolve(1))
})

const mode = process.argv[2]
if (mode !== 'plan' && mode !== 'run') {
console.error('usage: pr-tests.mjs plan|run')
process.exit(2)
}
const base = process.env.BASE || (await git('rev-parse', 'HEAD^1'))
const { head, scm, changed, affected, commands, browserPackages } = await plan(base)

if (mode === 'plan') {
console.log(`base ${base}, head ${head}: ${changed.length} changed files, ${affected.length} affected packages`)
for (const pkg of affected) console.log(` affected ${pkg.name}`)
for (const cmd of commands) console.log(cmd.join(' '))
const output = process.env.GITHUB_OUTPUT
if (output) {
const browserPackage = browserPackages[0]?.name ?? ''
await appendFile(
output,
`base=${base}\nbrowsers=${browserPackages.length > 0}\nbrowser-package=${browserPackage}\n`,
)
}
} else {
let failed = false
for (const [cmd, ...args] of commands) {
console.log(`::group::${[cmd, ...args].join(' ')}`)
const code = await status(cmd, args, scm)
console.log('::endgroup::')
if (code !== 0) {
console.log(`::error title=pr-tests::${[cmd, ...args].join(' ')} exited ${code}`)
failed = true
}
}
process.exit(failed ? 1 : 0)
}
71 changes: 71 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: CI

# Fast checks before the merge, heavy suites after it (owner, 2026-10-29: "They need to be fast with the heavier
# checks on main"), the shape of core's #881. A pull request and a merge-queue draft run the static lane and the
# `affected` lane (.github/ci/pr-tests.mjs), and their plan schedules no packed test job
# (.github/ci/no-tests-before-merge.mjs). Main runs every package's whole suite in the packed test jobs, the microVM
# integration suites and the smoke journeys, with KVM granted.
on:
push:
branches: [main]
pull_request:
branches: [main]
merge_group:
workflow_dispatch:
# Every commit on main gets its own run and verdict; on any other ref a newer push cancels the superseded run.
concurrency:
group: ci-${{ github.ref == 'refs/heads/main' && github.sha || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

permissions:
contents: read

jobs:
conflicts:
name: no merge conflicts
uses: systemfsoftware/systemfsoftware/.github/workflows/conflict-check.yml@21dfc32e9927c87a31a37d19e5643445a4e398c3
with:
base_ref: ${{ github.event.pull_request.base.ref }}
head_sha: ${{ github.event.pull_request.head.sha }}
checks:
name: build · lint · typecheck · test
needs: conflicts
permissions:
contents: read
actions: read
uses: systemfsoftware/systemfsoftware/.github/workflows/reusable-checks.yml@21dfc32e9927c87a31a37d19e5643445a4e398c3
with:
profile: ${{ github.ref == 'refs/heads/main' && 'per-change' || 'local' }}
record: test-timings-${{ github.ref == 'refs/heads/main' && 'per-change' || 'local' }}
timings: ${{ github.ref == 'refs/heads/main' && 'test-timings' || 'node .github/ci/no-tests-before-merge.mjs' }}
node-options: --max-old-space-size=4096
pnpm: corepack pnpm
test-flags: --output-logs=new-only --log-order=stream --log-prefix=none
gate-name: the gate (pnpm check:ci)
lanes: >-
${{ github.ref == 'refs/heads/main'
&& '[{"id": "static", "name": "static checks (pnpm check:static)", "timeout-minutes": 45, "node-options": "--max-old-space-size=4096", "run": "corepack pnpm check:static", "docs-run": "./bin/dprint check"}]'
|| '[{"id": "static", "name": "static checks (pnpm check:static)", "timeout-minutes": 45, "node-options": "--max-old-space-size=4096", "run": "corepack pnpm check:static", "docs-run": "./bin/dprint check"}, {"id": "affected", "name": "affected tests (turbo --affected, vitest --changed)", "timeout-minutes": 30, "fetch-depth": 2, "node-options": "--max-old-space-size=4096"}]' }}
smoke:
# Heavy: the real-microVM journeys run on main and on demand, never before the merge.
name: smoke · effect-microsandbox journeys (real microVM)
needs: conflicts
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 30
env:
CI: "true"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: cachix/install-nix-action@v31
- name: Core tarballs
run: |
mkdir -p .sfs-deps
cp -L --no-preserve=mode "$(nix build --no-link --print-out-paths .#sfs-deps)"/*.tgz .sfs-deps/
- uses: systemfsoftware/systemfsoftware/.github/actions/install-deps@21dfc32e9927c87a31a37d19e5643445a4e398c3
- uses: systemfsoftware/systemfsoftware/.github/actions/grant-kvm@21dfc32e9927c87a31a37d19e5643445a4e398c3
- name: Smoke journeys
run: |
set -o pipefail
corepack pnpm --filter @systemfsoftware/effect-microsandbox smoke 2>&1 | tee /tmp/smoke.log
grep -q 'all journeys green' /tmp/smoke.log
13 changes: 13 additions & 0 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Commitlint

on:
pull_request:
branches: [main]

permissions:
contents: read

jobs:
commitlint:
name: commitlint
uses: systemfsoftware/systemfsoftware/.github/workflows/reusable-commitlint.yml@21dfc32e9927c87a31a37d19e5643445a4e398c3
21 changes: 21 additions & 0 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Nix

# Heavy: builds every flake output that changed. Runs on main and on demand, never on a pull request.
on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.sha || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
nix:
name: nix
uses: systemfsoftware/systemfsoftware/.github/workflows/reusable-nix.yml@21dfc32e9927c87a31a37d19e5643445a4e398c3
with:
systems: '[{"system": "x86_64-linux", "runner": "ubuntu-latest"}]'
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
node_modules/
dist/
.turbo/
coverage/
.vitest/
reports/
__tmp__/
.stryker-tmp/
temp/
*.tsbuildinfo
.DS_Store
.npmrc
.direnv/
/result
/result-*
/deno.lock
.timings/
.sfs-tools/
# Core tarballs, built by `nix build .#sfs-deps` (CI: the sfs-deps job); never committed
.sfs-deps/
13 changes: 13 additions & 0 deletions .mergify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Mergify: the policy lives in systemfsoftware/.github (.mergify.yml). This file only names this repository's checks.
extends: .github

queue_rules:
- name: default
queue_conditions:
- check-success = no merge conflicts
- check-success = build · lint · typecheck · test / static checks (pnpm check:static)
- or:
- check-success = commitlint / lint PR commits
- check-skipped = commitlint / lint PR commits
merge_conditions:
- check-success = build · lint · typecheck · test / the gate (pnpm check:ci)
Loading
Loading