Files
cfw-autumn/scripts/dw/helpers/compose.ts
amianthus 25e920bbd3 feat(dw): 🎸 parallel agent worktrees with isolated infra + test harness
bun dw spins up a fully isolated per-worktree dev stack: Neon DB branch,
Dragonfly + ElasticMQ via docker compose, portless aliases, tmux
session, and emulate.dev Google OAuth. Workers, dev server, vite,
checkout, and stripe-listen run as concurrently siblings. bun setup-test
auto-runs on first dw and seeds unit-test-org via
clearOrg+setupOrg+ensureDefaultStripeAccount with 13 features + team
invites for all 5 useautumn.com members. Hardcoded localhost URLs in
test scenarios now read AUTUMN_TEST_BASE_URL/AUTUMN_TEST_VITE_URL.
preload-env.ts loads .env.local so bun t/cm/setup-test route to the
worktree server. 1265-line scripts/dw.ts split into scripts/dw/ module
(index + commands + helpers). server/src/db/initDrizzle.ts reverted to
upstream (search_path concerns solved by per-branch Neon isolation).
Workers enabled in agent worktrees now that SQS is per-worktree via
ElasticMQ.
2026-05-16 02:02:43 +01:00

99 lines
2.3 KiB
TypeScript

import { join } from "node:path";
import { sh, log } from "./shell.ts";
import { composeProjectName, dragonflyPortFor, elasticMqPortFor } from "./ports.ts";
import { SCRIPT_DIR } from "../constants.ts";
const composeFilePath = join(SCRIPT_DIR, "../setup/dw.compose.yml");
export function dockerComposeAvailable(): boolean {
const res = sh("docker", ["compose", "version"]);
return res.code === 0;
}
export function ensureComposeStack(worktreeNum: number): void {
if (worktreeNum === 1) return;
if (!dockerComposeAvailable()) {
log("docker compose not available; skipping infra stack");
return;
}
const project = composeProjectName(worktreeNum);
const dragonflyPort = String(dragonflyPortFor(worktreeNum));
const elasticMqPort = String(elasticMqPortFor(worktreeNum));
const env = {
...(process.env as Record<string, string>),
COMPOSE_PROJECT_NAME: project,
DRAGONFLY_PORT: dragonflyPort,
ELASTICMQ_PORT: elasticMqPort,
};
const up = sh(
"docker",
["compose", "-f", composeFilePath, "-p", project, "up", "-d"],
{ env },
);
if (up.code === 0) {
log(
`compose stack ${project} up (dragonfly :${dragonflyPort}, elasticmq :${elasticMqPort})`,
);
} else {
console.error(
`[dw] failed to start compose stack ${project}: ${up.stderr}`,
);
}
}
export function removeComposeStack(worktreeNum: number): void {
if (worktreeNum === 1) return;
const project = composeProjectName(worktreeNum);
const down = sh("docker", [
"compose",
"-f",
composeFilePath,
"-p",
project,
"down",
"-v",
]);
if (down.code === 0) {
log(`removed compose stack ${project}`);
}
}
export function removeAllAutumnComposeStacks(): void {
if (!dockerComposeAvailable()) return;
const ls = sh("docker", [
"compose",
"ls",
"--filter",
"name=autumn-wt-",
"--format",
"json",
]);
if (ls.code !== 0 || !ls.stdout) return;
try {
const projects = JSON.parse(ls.stdout) as { Name: string }[];
for (const p of projects) {
const down = sh("docker", [
"compose",
"-f",
composeFilePath,
"-p",
p.Name,
"down",
"-v",
]);
if (down.code === 0) {
log(`removed compose stack ${p.Name}`);
} else {
console.error(
`[dw] failed to remove compose stack ${p.Name}: ${down.stderr}`,
);
}
}
} catch {
/* JSON parse failed, ignore */
}
}