SDK
stackctl's SDK is what it exports for your own code: a test suite's global setup, a seed or migration script, a launcher. It does from a script what the CLI does from a shell, with the same workspace, modes and environment. Everything is a named export of stackctl:
import {
defineConfig,
exec,
logging,
resolveEnv,
run,
startStack,
} from 'stackctl';stackctl is ESM only. It has no CommonJS entry, so require('stackctl') fails.
| Export | What it does |
|---|---|
defineConfig | Defines the config. |
startStack | Starts a healthy stack for a test suite. |
exec, run | Run a command or script with a service's environment. |
resolveEnv | Returns a service's environment without running anything. |
logging | Log formatter helpers. |
Like the CLI, each function finds the workspace from the current directory, or takes a workspaceDir. startStack, exec, run and resolveEnv can't be called from inside stackctl.config.ts or its callbacks: they would load the config again.
defineConfig
function defineConfig(options): Config;Takes the options in the config file and returns the resolved config, which is the file's default export.
startStack
function startStack(options): Promise<Stack>;Starts a headless session as a child of the calling process and resolves once the infra and every service asked for are healthy. See Tests and CI.
const stack = await startStack({ mode: 'test', services: ['web'] });
try {
await fetch(`${stack.origins.api}/health`);
} finally {
await stack.stop();
}| Option | Default | What it is |
|---|---|---|
services | required | The services to bring up. Their deps and the mode's infra come too. |
mode | as the CLI resolves it | The mode. |
workspaceDir | nearest config | The directory holding stackctl.config.ts. |
waitTimeout | 300 | Seconds to wait for everything to be healthy. |
killOthers | false | Quit a session already running in the mode, as --kill-others does. |
logDir | the mode's log dir | Where the session log is written. |
env | stackEnvironment() | The environment the session starts with. |
It resolves with:
| Field | What it is |
|---|---|
origins | Each configured service's public origin, by name. |
domain | The reverse proxy's canonical domain, or null without one. |
services | What the session runs: the services asked for and their deps. |
control | A control API client for the session. |
stop() | Quits the session, resolving once it has exited. |
pid, port | The session's process and its control-API port. |
mode, modeSource, logFile | The session's mode, where it came from, and its log. |
- The session ends with the calling process, however that ends.
- Healthy means running and passing its healthcheck. A service with no healthcheck must stay up for a second.
- A service with
autostart: falsestays stopped and isn't waited on. Start it withstack.control.command.service.start(name). - It rejects when a session of the mode is already running (unless
killOthers), when the session exits or a service stops while starting, or whenwaitTimeoutpasses (not healthy after 300s: worker). The session is stopped first, and the error has the log's path.
stackEnvironment
function stackEnvironment(env = process.env): NodeJS.ProcessEnv;Returns env without what a package manager and a test runner add to it, which would otherwise leak into every service: npm_*, INIT_CWD, NODE, NODE_PATH, PNPM_SCRIPT_SRC_DIR, and under Vitest or Jest their own variables and NODE_ENV. It is startStack's default env. To add a variable:
await startStack({
services: ['web'],
env: { ...stackEnvironment(), FEATURE_X: '1' },
});exec and run
function exec(options): Promise<{ exitCode, signal, stdout?, stderr? }>;
function run(options): Promise<{ exitCode, signal, stdout?, stderr? }>;What stackctl exec and stackctl run do, from a script: one command in a target's directory, with its environment.
await exec({ target: 'api', command: ['node', 'src/migrate.ts'] });
const { exitCode, stdout } = await run({
target: 'api',
script: 'seed',
args: ['--rows=100'],
mode: 'test',
stdio: 'pipe',
});| Option | Default | What it is |
|---|---|---|
command | required (exec) | The program and its arguments, run with no shell. |
script | required (run) | The script or Makefile target. |
args | none | run only: arguments forwarded to the script. |
target | the current directory | A service's name, or a directory. |
mode | as the CLI resolves it | The mode. |
workspaceDir | nearest config | The directory holding stackctl.config.ts. |
stdio | 'inherit' | 'inherit' streams the output. 'pipe' collects it into stdout and stderr. |
- A non-zero exit resolves, with its
exitCodeor thesignalthat ended it. The caller judges the result. - It rejects only when the command can't start: no config, an unknown target, a missing script.
- The calling process is untouched: its
process.envand working directory don't change, and neither function callsprocess.exit. - Signals are forwarded to the command.
resolveEnv
function resolveEnv(options?): { env, files, candidates };Returns the environment stackctl would give a target, without starting or running anything. For a script stackctl doesn't start, such as a launcher that needs a port before anything is up. It is synchronous.
const { env } = resolveEnv({ mode: 'test', target: 'api' });
const databaseUrl = env.DATABASE_URL;| Option | Default | What it is |
|---|---|---|
target | the current directory | A service's name, or a directory. |
mode | as the CLI resolves it | The mode whose files to read. |
workspaceDir | nearest config | The directory holding stackctl.config.ts. |
| Field | What it is |
|---|---|
env | What stackctl adds: the files' variables, env.vars, and for a service its PORT and PUBLIC_ORIGIN. |
files | The .env files that existed and were read. |
candidates | Every path looked for. Check it when a variable is missing. |
env doesn't include the rest of process.env. Merge them yourself when you need both: { ...process.env, ...env }.
logging
Builders for a service's logs.formatter:
logging.formatter(config?);
logging.nodejs.pino.formatter(config?);
logging.nodejs.winston.formatter(config?);z, from zod, is re-exported for a hand-written formatter.
Types
The types used by the config are exported for splitting it across files: ServiceInput, DockerComposeServiceInput, DockerComposeServicesInput, DockerComposeDestroyContext, DockerComposeStartContext, DockerComposeReadyContext, DockerComposeCallbackContext, ReverseProxyRoute, ReverseProxyInput and DockerComposeInfraInput. With typed service names, ServiceName, InfraServiceName and ServiceConfig are your own. ControlClient, SessionStatus and SessionEvent describe the control API client, and EventLine, EventEntry, EventEnvelope and EventBy the event log's lines.