refactor: simplify worktree script spawning

This commit is contained in:
Mohamed Boudra
2026-04-21 20:46:54 +07:00
parent 79bf5ac3c7
commit 39db3ea5ff
5 changed files with 62 additions and 86 deletions
@@ -7,7 +7,7 @@ import { scheduler } from "node:timers/promises";
import { afterEach, describe, expect, it, vi } from "vitest";
import { findFreePort, ScriptRouteStore } from "./script-proxy.js";
import { ScriptHealthMonitor, type ScriptHealthEntry } from "./script-health-monitor.js";
import { spawnWorktreeScripts } from "./worktree-bootstrap.js";
import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
type TcpServerHandle = {
@@ -433,15 +433,19 @@ describe("ScriptHealthMonitor", () => {
[];
try {
await spawnWorktreeScripts({
repoRoot: workspace.repoDir,
workspaceId: workspace.repoDir,
branchName: null,
daemonPort: null,
routeStore,
runtimeStore,
terminalManager: createStubTerminalManager(createTerminalCalls) as any,
});
for (const scriptName of ["typecheck", "api"]) {
await spawnWorkspaceScript({
repoRoot: workspace.repoDir,
workspaceId: workspace.repoDir,
projectSlug: "repo",
branchName: null,
scriptName,
daemonPort: null,
routeStore,
runtimeStore,
terminalManager: createStubTerminalManager(createTerminalCalls) as any,
});
}
expect(createTerminalCalls).toHaveLength(2);
expect(routeStore.listRoutes()).toEqual([
@@ -5,11 +5,7 @@ import { join } from "path";
import { tmpdir } from "os";
import type { AgentTimelineItem } from "./agent/agent-sdk-types.js";
import {
runAsyncWorktreeBootstrap,
spawnWorkspaceScript,
spawnWorktreeScripts,
} from "./worktree-bootstrap.js";
import { runAsyncWorktreeBootstrap, spawnWorkspaceScript } from "./worktree-bootstrap.js";
import { ensureWorkspaceServicePortPlan } from "./workspace-service-port-registry.js";
import { ScriptRouteStore } from "./script-proxy.js";
import { createBranchChangeRouteHandler } from "./script-route-branch-handler.js";
@@ -717,17 +713,19 @@ describe("runAsyncWorktreeBootstrap", () => {
const createTerminalCalls: CreateTerminalCall[] = [];
const terminalRecords: StubTerminalRecord[] = [];
const results = await spawnWorktreeScripts({
const result = await spawnWorkspaceScript({
repoRoot: repoDir,
workspaceId: repoDir,
projectSlug: "repo",
branchName: "feature-socket-service",
scriptName: "web",
daemonPort: null,
routeStore,
runtimeStore,
terminalManager: createStubTerminalManager(createTerminalCalls, terminalRecords),
});
expect(results).toHaveLength(1);
expect(result).toBeDefined();
expect(routeStore.listRoutes()).toEqual([]);
expect(createTerminalCalls).toHaveLength(1);
expect(createTerminalCalls[0]?.cwd).toBe(repoDir);
@@ -1344,15 +1342,19 @@ describe("runAsyncWorktreeBootstrap", () => {
const terminalManager = createTerminalManager();
realTerminalManagers.push(terminalManager);
await spawnWorktreeScripts({
repoRoot: repoDir,
workspaceId: repoDir,
branchName: "feature-peer-env",
daemonPort: 6767,
routeStore,
runtimeStore,
terminalManager,
});
for (const scriptName of ["api", "web"]) {
await spawnWorkspaceScript({
repoRoot: repoDir,
workspaceId: repoDir,
projectSlug: "repo",
branchName: "feature-peer-env",
scriptName,
daemonPort: 6767,
routeStore,
runtimeStore,
terminalManager,
});
}
const apiEnvPath = join(repoDir, "api-env.json");
const webEnvPath = join(repoDir, "web-env.json");
@@ -1422,10 +1424,12 @@ describe("runAsyncWorktreeBootstrap", () => {
const runtimeStore = new WorkspaceScriptRuntimeStore();
const createTerminalCalls: CreateTerminalCall[] = [];
await spawnWorktreeScripts({
await spawnWorkspaceScript({
repoRoot: repoDir,
workspaceId: repoDir,
projectSlug: "repo",
branchName: "feature-remote-service",
scriptName: "web",
daemonPort: 6767,
daemonListenHost: "100.64.0.20",
routeStore,
@@ -3,7 +3,6 @@ import type { Logger } from "pino";
import type { TerminalManager } from "../terminal/terminal-manager.js";
import type { TerminalSession } from "../terminal/terminal.js";
import { buildScriptHostname } from "../utils/script-hostname.js";
import { deriveProjectSlug } from "./workspace-git-metadata.js";
import {
getScriptConfigs,
getWorktreeTerminalSpecs,
@@ -922,39 +921,6 @@ export async function spawnWorkspaceScript(
}
}
export async function spawnWorktreeScripts(options: {
repoRoot: string;
workspaceId: string;
branchName: string | null;
daemonPort?: number | null;
daemonListenHost?: string | null;
routeStore: ScriptRouteStore;
runtimeStore: WorkspaceScriptRuntimeStore;
terminalManager: TerminalManager;
logger?: Logger;
onLifecycleChanged?: () => void;
}): Promise<WorktreeScriptResult[]> {
const { repoRoot } = options;
const projectSlug = deriveProjectSlug(repoRoot);
const scriptConfigs = getScriptConfigs(repoRoot);
if (scriptConfigs.size === 0) {
return [];
}
const results: WorktreeScriptResult[] = [];
for (const scriptName of scriptConfigs.keys()) {
results.push(
await spawnWorkspaceScript({
...options,
projectSlug,
scriptName,
}),
);
}
return results;
}
export function teardownWorktreeScripts(options: {
hostnames: string[];
routeStore: ScriptRouteStore;
@@ -20,7 +20,6 @@ import {
buildWorktreeSetupDetail,
createWorktreeSetupProgressAccumulator,
getWorktreeSetupProgressResults,
spawnWorktreeScripts,
} from "./worktree-bootstrap.js";
import type { TerminalManager } from "../terminal/terminal-manager.js";
import type { ScriptRouteStore } from "./script-proxy.js";
@@ -738,28 +737,6 @@ export async function runWorktreeSetupInBackground(
emitSetupProgress("completed", null);
}
}
if (
options.shouldBootstrap &&
dependencies.terminalManager &&
dependencies.scriptRouteStore &&
dependencies.scriptRuntimeStore
) {
await spawnWorktreeScripts({
repoRoot: worktree.worktreePath,
workspaceId: options.workspaceId,
branchName: worktree.branchName,
daemonPort: dependencies.getDaemonTcpPort?.() ?? null,
daemonListenHost: dependencies.getDaemonTcpHost?.() ?? null,
routeStore: dependencies.scriptRouteStore,
runtimeStore: dependencies.scriptRuntimeStore,
terminalManager: dependencies.terminalManager,
logger: dependencies.sessionLogger,
onLifecycleChanged: () => {
dependencies.onScriptsChanged?.(options.workspaceId, worktree.worktreePath);
},
});
}
} catch (error) {
if (error instanceof WorktreeSetupError) {
setupResults = error.results;