Refs & state
Capture exec output and thread values between steps using ExecResult and the workflow builder's vars.
There's no special Ref<T> type. Output from one step is just a TypeScript variable — you capture it from the ExecResult and pass it to the next step however you like.
Capturing stdout
await sb.exec(cmd) returns { stdout, stderr, exitCode }. Use stdout directly in subsequent commands:
const sb = await client.sandbox({
image: "node:20-slim",
resources: { cpu: "500m", memory: "256Mi" },
});
try {
const { stdout: nodeVersion } = await sb.exec('node -e "process.stdout.write(process.version)"');
// Use the captured value in the next exec
await sb.exec(`echo "Running on Node ${nodeVersion.trim()}"`).pipe(process.stdout);
// Or write it to a file
await sb.writeFile(
"/tmp/info.json",
JSON.stringify({ node: nodeVersion.trim(), capturedAt: new Date().toISOString() }),
);
} finally {
await sb.close();
}Reading files as state
sb.readFile(path) returns the file content as a string:
await sb.exec("node -e \"require('fs').writeFileSync('/tmp/version.txt', process.version)\"");
const version = await sb.readFile("/tmp/version.txt");
console.log(version.trim()); // "v20.x.x"State in the workflow builder
When using @alineo-labs/workflow, the SandboxBuilder doesn't return values from exec() — steps are queued, not executed yet. Use readFile(path, as) to capture a file into the vars map, then read from vars after the workflow resolves:
import { workflow } from "@alineo-labs/workflow";
const { vars } = await workflow(client)
.sandbox({ image: "node:22", resources: { cpu: "500m", memory: "256Mi" } }, (sb) => {
sb.exec("node --version > /tmp/version.txt");
sb.readFile("/tmp/version.txt", "nodeVersion"); // captured into vars["nodeVersion"]
})
.result();
console.log(vars.nodeVersion); // "v22.x.x\n"The when predicate also receives runtime state:
sb.exec("test -f /etc/hostname", { strict: false });
sb.when(
(ctx) => ctx.exitCode === 0, // ctx.exitCode, ctx.stdout, ctx.vars
(sb) => sb.exec("echo exists"),
(sb) => sb.exec("echo missing"),
);ctx.stdout is the accumulated stdout from all execs in the sandbox so far. ctx.vars holds anything captured via readFile(..., as).
Passing state between sandboxes
In .sequence(), each stage receives the previous stage's WorkflowResult:
await workflow(client)
.sequence([
{
image: "node:22",
name: "build",
resources: { cpu: "1", memory: "512Mi" },
run: (sb) => {
sb.exec("npm run build 2>&1 | tee /tmp/build.log");
sb.readFile("/tmp/build.log", "buildLog");
},
},
{
image: "ubuntu:22.04",
name: "deploy",
resources: { cpu: "500m", memory: "256Mi" },
run: (sb, prev) => {
// prev.vars.buildLog from the first sandbox
sb.exec(`echo "Build output lines: $(echo '${prev?.vars.buildLog}' | wc -l)"`);
},
},
])
.pipe(process.stdout);