Guides

Configuration

The common things a stack needs, one at a time. Every option is listed, with its type and default, in the config options reference.

The config file

braid reads braid.config.ts from the directory you run it in, or braid.config.mts if that's what's there (use it in a project whose package.json isn't "type": "module"), or the file given to --config. It default-exports an object whose processes lists everything to run:

braid.config.ts
import { defineConfig } from "@aip-tech/braid";

export default defineConfig({
	processes: [
		{
			name: "api",
			command: "pnpm",
			args: ["--filter", "./api", "dev"],
			cwd: ".",
			env: { NODE_ENV: "development" },
			color: "blue",
		},
	],
});

Each process needs a unique name and a command. Its env is visible to that process only.

Restarting on file changes

Give a process watch paths and braid restarts it when a matching file changes. Leave it out for anything with its own hot reload.

{
	name: "api",
	command: "pnpm",
	args: ["--filter", "./api", "dev"],
	watch: ["api/src"],
	ext: "ts,graphql,json",
	exclude: ["api/src/__generated__"],
}

ext filters by extension (default ts,js,json). exclude leaves a path and everything under it out of the watch, which stops generated files from triggering their own restart. Run braid start --no-watch to switch watching off for one run.

Processes that depend on each other

Five options cover the ways one process can rely on another. Each is independent, so combine only the ones you need:

OptionWhat it does
readyPatternDefines when a process counts as ready: the first output line matching this regex.
startAfterHolds a process's first start until the processes it names are ready.
dependsOnRestarts a process whenever a process it depends on restarts, optionally running a command in between.
onRestartRuns a command after a process restarts itself.
beforeRestartRuns a command after a watched process stops and before it starts again.

A typical pairing: a web app that waits for its API on first start, and regenerates its API client whenever the API restarts.

{
	name: "api",
	command: "pnpm",
	args: ["--filter", "./api", "dev"],
	watch: ["api/src"],
	readyPattern: "^Server listening",
},
{
	name: "web",
	command: "pnpm",
	args: ["--filter", "./web", "dev"],
	startAfter: { processes: ["api"] },
	dependsOn: {
		processes: ["api"],
		run: { command: "pnpm", args: ["--filter", "./web", "codegen"] },
	},
}

When api restarts, web waits until api prints its ready line, stops, runs the codegen command, then starts again. Hook commands are retried (5 times, 1 second apart by default), since the process they rely on may still be warming up.

readyPattern is matched against each line exactly as the app printed it, so anchors like ^ work. If it never matches within readyTimeoutMs (10 seconds by default), braid notes it in the process's log and carries on.

Circular dependsOn or startAfter chains, and names that don't exist, are rejected when the stack starts.

When a process crashes

By default, any process exiting with a non-zero code stops the whole stack, so you're never debugging half of it. To restart just that process instead, turn on autoRestart:

{
	name: "worker",
	command: "node",
	args: ["worker.js"],
	autoRestart: true,
	maxRestarts: 10,
	restartDelayMs: 1000,
}

Restarts back off exponentially (doubling, up to 10 seconds). After maxRestarts crashes in a row, braid gives up and stops the stack. A command that can't be started at all, such as a typo, stops the stack straight away rather than retrying.

Stopping cleanly

braid stops a process with SIGTERM, then SIGKILL if it's still running after stopTimeoutMs (5 seconds by default). Raise it for an app with slow cleanup:

{ name: "db", command: "./start-db.sh", stopTimeoutMs: 15000 }

Everything an app prints while shutting down still reaches its log.

A process started with pnpm --filter ./api run dev may print ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL or ELIFECYCLE Command failed whenever braid stops it. pnpm reports its script ending on SIGTERM as a failure. braid knows it stopped the process, so it's harmless. To avoid it, run the tool with pnpm exec from the package's folder:

{
	name: "api",
	command: "pnpm",
	args: ["exec", "tsx", "index.ts"],
	cwd: "api",
	watch: ["src"],
}

watch paths are relative to cwd.

Processes you start yourself

autoStart: false keeps a process out of braid start. It's still listed in braid status, and you start it when you need it:

{
	name: "report",
	command: "pnpm",
	args: ["--filter", "./jobs", "nightly-report"],
	autoStart: false,
}
npx braid start report

Logs

Each process writes to .braid/logs/<name>.log, with one rotated backup. Logs rotate on every start, on watch and crash restarts, and once a file passes logs.maxSizeBytes (5 MB by default):

export default defineConfig({
	processes: [/* ... */],
	logs: { dir: ".braid/logs", maxSizeBytes: 5_242_880, timestamps: true },
});

timestamps prefixes every line with the time. braid's own messages, such as plugin errors, go to .braid/daemon.log. Read logs with braid logs.

Foreground or background

braid start runs the stack in a background daemon. Set foreground: true, or pass --foreground, to keep it attached to the terminal instead, streaming every process's output until Ctrl+C. --daemon forces the background mode whatever the config says.

Resource usage

braid status and the dashboard show each process's CPU and memory, covering the app and everything it starts (the real server under a pnpm dev, a bundler's workers). Figures refresh every statsPollIntervalMs (2 seconds by default). CPU appears from the second sample.

On macOS, memory is the physical footprint Activity Monitor shows; on Linux it's RSS. Windows gets no figures.