Run CI on your machine
Write your app's CI as a TypeScript script, run it with sdxc ci before you push, and let GitHub Actions skip its own run for a commit you already signed off.
Last updated
After reading this guide, you will know
- How to write a CI script with steps, parallel groups and a matrix over workspaces
- How
sdxc ci --pushtests, pushes and signs a commit off in one go - How the GitHub workflow skips its run for a commit a contributor signed off
sdxc create starts every app with .config/sdxc/ci.ts, a CI script that runs on your machine
and on GitHub Actions alike. Run it before you push: when it passes, it marks the commit green
on GitHub, and the workflow skips its own run.
Run it
sdxc cisdxc ci installs dependencies with a frozen lockfile, then runs each step and prints one line
per step as it finishes. A failed step shows its output under it. The run exits 0 when every
step passed and 1 otherwise.
While you work, narrow the run to the steps you care about:
sdxc ci --only TestA run with --only, --skip or --scope never signs off, so a narrowed run can't mark a
commit green.
Write steps
The script is a function that receives ci. await runs steps one after another, and
ci.parallel runs a group at once:
import { defineCI } from "@sdxc/ci";
export default defineCI(async (ci) => {
await ci.step("Install browsers", "bunx playwright install chromium", {
halt: true,
});
await ci.parallel("Checks", [
() => ci.step("Doctor", "bunx sdxc doctor"),
() => ci.step("Format, lint and types", "bun run check"),
]);
await ci.step("Test", "bun run test", { cache: true });
if (ci.status === "passing") await ci.signoff("All systems go.");
else
ci.failure(
"CI failed. Do not push.",
"Fix the steps above and run `sdxc ci` again.",
);
});A failed step leaves the run going, so you see every failure in one pass. Add
halt: truewhen nothing after the step can succeed without it.cache: trueskips a step whose inputs haven't changed since it last passed. The step's output from that earlier pass is kept.ci.runneris"remote"on GitHub Actions and"local"on your machine. Use it for steps that belong in only one place, such as copying.env.exampleto.dev.varson CI.
Test every workspace
In a monorepo, ci.matrix runs one entry per workspace, each with its own steps and status:
let tested = (await ci.workspaces()).filter((ws) => ws.has("**/*.test.ts"));
await ci.matrix("Workspaces", tested, async (ws) => {
await ci.step(`Tests for ${ws.name}`, "bun run test", {
cwd: ws.path,
cache: true,
});
});Run sdxc ci from inside a workspace and only that workspace's entry runs, plus every
workspace that depends on it.
Sign off and push
sdxc ci --pushWhen every step passes, --push pushes exactly the tested commit to its upstream branch, then
posts an sdxc/ci commit status on GitHub. It signs off only when the run:
ran every step,
started and ended with no uncommitted or untracked files,
tested the same commit from start to end.
If someone pushed first, the push is rejected; pull, then run sdxc ci --push again.
The token comes from GITHUB_TOKEN, or from gh auth token when you're signed in with the
GitHub CLI.
Skip the hosted run
The workflow sdxc create writes, .github/workflows/ci.yml, starts with a gate job:
bunx sdxc ci verify "$SHA" --wait 60sIt exits 0 when someone with write access to the repository signed the commit off, and the
workflow then skips its CI job. Every other commit runs the full script on GitHub Actions with
sdxc ci --runner remote.
Where to go next
Test your app — the tests
bun run testruns in the script's Test step.Check a project — what the Doctor step checks.
Deploy — ship the commit you just signed off.