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.
119 lines
3.1 KiB
TypeScript
119 lines
3.1 KiB
TypeScript
import { sh, fatal, log } from "./shell.ts";
|
|
import {
|
|
NEON_PROJECT_ID,
|
|
NEON_TEMPLATE_BRANCH,
|
|
NEON_PARENT_BRANCH,
|
|
BRANCH_NAME_RE,
|
|
} from "../constants.ts";
|
|
import type { NeonBranch } from "../types.ts";
|
|
|
|
function neon(args: string[]): { stdout: string; stderr: string; code: number } {
|
|
return sh("neon", args);
|
|
}
|
|
|
|
export function listBranches(): NeonBranch[] {
|
|
const res = neon([
|
|
"branches",
|
|
"list",
|
|
"--project-id",
|
|
NEON_PROJECT_ID,
|
|
"--output",
|
|
"json",
|
|
]);
|
|
if (res.code !== 0) {
|
|
fatal(`neon branches list failed: ${res.stderr || res.stdout}`);
|
|
}
|
|
try {
|
|
return JSON.parse(res.stdout) as NeonBranch[];
|
|
} catch {
|
|
fatal(`could not parse neon branches list output:\n${res.stdout}`);
|
|
}
|
|
}
|
|
|
|
export function findBranchByName(name: string): NeonBranch | undefined {
|
|
return listBranches().find((b) => b.name === name);
|
|
}
|
|
|
|
export function createBranch(name: string, parent: string): NeonBranch {
|
|
if (!BRANCH_NAME_RE.test(name) && name !== NEON_TEMPLATE_BRANCH) {
|
|
fatal(`refusing to create branch with unexpected name: ${name}`);
|
|
}
|
|
log(`creating neon branch ${name} (parent: ${parent})`);
|
|
const res = neon([
|
|
"branches",
|
|
"create",
|
|
"--project-id",
|
|
NEON_PROJECT_ID,
|
|
"--name",
|
|
name,
|
|
"--parent",
|
|
parent,
|
|
"--output",
|
|
"json",
|
|
]);
|
|
if (res.code !== 0) {
|
|
fatal(`neon branches create failed: ${res.stderr || res.stdout}`);
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(res.stdout) as { branch?: NeonBranch };
|
|
const branch = parsed.branch ?? (parsed as unknown as NeonBranch);
|
|
if (!branch?.id) fatal(`unexpected neon create output:\n${res.stdout}`);
|
|
return branch;
|
|
} catch {
|
|
fatal(`could not parse neon create output:\n${res.stdout}`);
|
|
}
|
|
}
|
|
|
|
export function deleteBranch(idOrName: string): void {
|
|
const res = neon([
|
|
"branches",
|
|
"delete",
|
|
idOrName,
|
|
"--project-id",
|
|
NEON_PROJECT_ID,
|
|
]);
|
|
if (res.code !== 0) {
|
|
console.error(
|
|
`[dw] neon branches delete ${idOrName} failed: ${res.stderr || res.stdout}`,
|
|
);
|
|
} else {
|
|
log(`deleted neon branch ${idOrName}`);
|
|
}
|
|
}
|
|
|
|
export function connectionString(
|
|
branchName: string,
|
|
opts: { pooled?: boolean } = {},
|
|
): string {
|
|
const args = [
|
|
"connection-string",
|
|
branchName,
|
|
"--project-id",
|
|
NEON_PROJECT_ID,
|
|
];
|
|
if (opts.pooled) args.push("--pooled");
|
|
const res = neon(args);
|
|
if (res.code !== 0) {
|
|
fatal(
|
|
`neon connection-string for ${branchName} failed: ${res.stderr || res.stdout}`,
|
|
);
|
|
}
|
|
return res.stdout.trim();
|
|
}
|
|
|
|
export function ensureTemplateBranch(): void {
|
|
const branch = findBranchByName(NEON_TEMPLATE_BRANCH);
|
|
if (branch) return;
|
|
log(`bootstrap: ${NEON_TEMPLATE_BRANCH} missing, creating empty parent`);
|
|
createBranch(NEON_TEMPLATE_BRANCH, NEON_PARENT_BRANCH);
|
|
// Wipe the inherited schema so children start truly empty.
|
|
const url = connectionString(NEON_TEMPLATE_BRANCH);
|
|
const reset = sh("psql", [url, "-v", "ON_ERROR_STOP=1"], {
|
|
stdin: `DROP SCHEMA IF EXISTS public CASCADE;\nCREATE SCHEMA public;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\n`,
|
|
});
|
|
if (reset.code !== 0) {
|
|
fatal(`failed to reset ${NEON_TEMPLATE_BRANCH}:\n${reset.stderr}`);
|
|
}
|
|
log(`${NEON_TEMPLATE_BRANCH} ready (empty + pg_trgm)`);
|
|
}
|