Workflows
Pipelines are TypeScript files in .plain/workflows, type-checked before they run.
CI on Plain is a program, not a YAML document. A workflow is a TypeScript file in your repository: it type-checks with the rest of your code, runs on your machine with the local runner, and expresses control flow as control flow.
Your first workflow
import { defineWorkflow, on, pm, sh } from "@plainalpha/ci";
defineWorkflow("ci", {
on: [on.push({ branches: ["main"] }), on.pullRequest({ branches: ["main"] })],
run: async ({ ctx }) => {
await pm.install(); // detects pnpm/npm/yarn/bun from the lockfile
await sh("pnpm test");
await sh("pnpm tsc --noEmit");
await ctx.artifact.write("ci.txt", `checked ${ctx.git.sha} on ${ctx.git.branch}`);
},
});Install the API as a dev dependency (pnpm add -D @plainalpha/ci), commit the file, push, and the run starts.
The run body
run is ordinary async TypeScript. Conditionals, loops, retries, and early exits are just JavaScript; there is no expression mini-language to learn and no if: syntax to get subtly wrong. A failing sh command fails the run with the command's output in the log, unless you opt out:
const result = await sh("./flaky-check.sh", { allowFailure: true });
if (result.exitCode !== 0) {
await ctx.comments.create({ body: "flaky-check failed, continuing anyway" });
}
That snippet also shows the other thing YAML cannot do: the run body can write to the platform. ctx exposes issues, comments, docs, labels, and relations, so a workflow can file the bug it just found. See the Workflow API.
Where workflows live
Workflows are .ts files under .plain/workflows/. A workflow's identity is its name argument, not its filename, and one file can define several:
import { defineWorkflow, on, pm, sh } from "@plainalpha/ci";
defineWorkflow("nightly", {
on: [on.schedule("0 3 * * *")],
run: async () => {
await pm.install();
await sh("pnpm test");
},
});Concurrency and timeouts
Workflows accept concurrency (group runs and cancel in-progress ones when a newer commit arrives) and timeoutMs (kill runs that hang). Network access from a run is restricted to package registries unless the workflow declares an allowlist; see Secrets and environment.