Quickstart
FlowWright runs your pipeline on your machine. No account, no server, no YAML — a
pipeline.ts file and one command.
Requirements
Node.js 24 or newer. That's the whole list. Docker is optional and only needed if a stage declares a container.
Install
Run it without installing anything:
npx @flowwright/cli init
Or add it to the project, which is what most teams do — it pins the version in your lockfile alongside everything else:
npm install --save-dev @flowwright/cli
Either way the binary is called flow.
Scaffold a pipeline
From the root of an existing project:
flow init
✓ created pipeline.ts
detected: npm · lint, typecheck, test, build
next: flow run
flow init reads your project before writing anything. It picks the package manager
from your lockfile and looks for the scripts it knows how to wire up — lint,
typecheck, test, build — so the pipeline it writes matches the project you
already have.
For a project with all four scripts and a package-lock.json, that's:
import { pipeline, stage, sh } from "@flowwright/core";
export default pipeline({
name: "acme-api",
stages: [
stage("Install", async () => {
await sh(["npm", "ci"]);
}),
stage("Lint", {
needs: ["install"],
run: async () => {
await sh(["npm", "run", "lint"]);
},
}),
stage("Typecheck", {
needs: ["install"],
run: async () => {
await sh(["npm", "run", "typecheck"]);
},
}),
stage("Test", {
needs: ["install"],
run: async () => {
await sh(["npm", "run", "test"]);
},
}),
stage("Build", {
needs: ["lint", "typecheck", "test"],
run: async () => {
await sh(["npm", "run", "build"]);
},
}),
],
});
Reading it top to bottom:
pipeline({ name, stages })is the whole file. It's a default export, so the CLI can load it.stage("Install", async () => { … })is the short form: a name and a body.stage("Lint", { needs: ["install"], run })is the options form.needsis what builds the graph — Lint waits for Install.- Stage IDs are derived from names.
"Typecheck"becomestypecheck, which is whatneedsrefers to and what shows up in logs and reports. sh(["npm", "run", "lint"])takes an argv array, so there's no shell to quote against and nothing to escape.- Build needs all three checks, so it only starts once they've all passed.
Nothing here is FlowWright-specific configuration — it's a TypeScript file. Your editor autocompletes it and typechecks it like any other. Every option is covered in Stages.
Install the DSL package so the imports resolve:
npm install --save-dev @flowwright/core
Run it
flow run
FlowWright · acme-api
▶ Install
$ npm ci
[07:10:53] added 1 package, and audited 2 packages in 353ms
[07:10:53] found 0 vulnerabilities
✓ install 403ms
▶ Lint
$ npm run lint
[07:10:53] lint: 0 problems
✓ lint 103ms
▶ Typecheck
$ npm run typecheck
[07:10:53] typecheck: 0 errors
✓ typecheck 71ms
▶ Test
$ npm run test
[07:10:53] test: 12 passed
✓ test 67ms
▶ Build
$ npm run build
[07:10:53] build: dist/ written
✓ build 68ms
✔ Run succeeded in 717ms
Every command is echoed before it runs, output streams through live, and each stage
reports its own duration. If a stage fails, the run stops and flow exits with that
command's exit code — so flow run drops into a CI job or a git hook unchanged.
See the plan without running it
flow explain
Pipeline: acme-api
install
$ npm ci
lint ← install
$ npm run lint
typecheck ← install
$ npm run typecheck
test ← install
$ npm run test
build ← lint, typecheck, test
$ npm run build
The ← arrows are the dependency graph. explain never executes anything, which
makes it the fastest way to check a change to pipeline.ts did what you meant.
Notice that Lint, Typecheck and Test all depend only on Install — they're independent of each other, so they can run at the same time:
flow run -j 3
Where the run went
flow run created a .flowwright/ directory next to your pipeline.ts:
.flowwright/
├── state.db run history — what ran, when, and whether it passed
├── runs/<run-id>/
│ ├── run.json the run's summary
│ └── logs/<stage>.log full output, one file per stage
├── cache/ content-addressed cache
└── stash/ scratch space, scoped to a run
It's local, it's yours, and it's disposable — add it to .gitignore. Every past run
stays queryable with flow list and flow logs.
Next
- The CLI — every command and flag
- Running pipelines — parallelism, watch mode, containers
- Authoring pipelines — stages, commands, dependencies, containers
- Plugins — ready-made stages for Node, Python, Docker and git
- Reporters — output formats for CI
- Running in CI — GitHub Actions, GitLab CI and Jenkins