@iloom/cli 0.13.0-beta.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +3 -18
- package/dist/{ClaudeContextManager-RRGREEZQ.js → ClaudeContextManager-ZH6LEA5I.js} +5 -5
- package/dist/{ClaudeService-LEPW6QAC.js → ClaudeService-YR66WXZN.js} +4 -4
- package/dist/{IssueTrackerFactory-KE2BDCLC.js → IssueTrackerFactory-O2ZBA666.js} +3 -3
- package/dist/{LoomLauncher-GKQMR5E6.js → LoomLauncher-V54ENBEF.js} +5 -5
- package/dist/{MetadataManager-V4LSJ2PB.js → MetadataManager-HHE6LQF2.js} +2 -2
- package/dist/{PromptTemplateManager-I75WKXM4.js → PromptTemplateManager-4RFELNYY.js} +2 -2
- package/dist/README.md +3 -18
- package/dist/{SettingsManager-KQU7OX7G.js → SettingsManager-SLSYEYDZ.js} +4 -4
- package/dist/agents/iloom-artifact-reviewer.md +1 -0
- package/dist/agents/iloom-code-reviewer.md +21 -0
- package/dist/agents/iloom-issue-analyze-and-plan.md +30 -12
- package/dist/agents/iloom-issue-analyzer.md +32 -7
- package/dist/agents/iloom-issue-complexity-evaluator.md +32 -12
- package/dist/agents/iloom-issue-implementer.md +31 -12
- package/dist/agents/iloom-issue-planner.md +30 -12
- package/dist/agents/iloom-wave-verifier.md +177 -4
- package/dist/{build-V3KADFMO.js → build-ZTGWDHWU.js} +8 -8
- package/dist/{chunk-VVQQIG64.js → chunk-55NTREIU.js} +33 -30
- package/dist/chunk-55NTREIU.js.map +1 -0
- package/dist/{chunk-AYLC633W.js → chunk-7TN5VW4I.js} +65 -7
- package/dist/chunk-7TN5VW4I.js.map +1 -0
- package/dist/{chunk-RFCAPHL5.js → chunk-C2BVNJW5.js} +2 -2
- package/dist/{chunk-3XEXT35Z.js → chunk-E5OM25WK.js} +3 -3
- package/dist/{chunk-ZUIFO7B4.js → chunk-ERMEYFT6.js} +7 -2
- package/dist/chunk-ERMEYFT6.js.map +1 -0
- package/dist/{chunk-WGUGB54H.js → chunk-F5NKWLMQ.js} +21 -24
- package/dist/chunk-F5NKWLMQ.js.map +1 -0
- package/dist/{chunk-TN2D2RX7.js → chunk-G2DGDCDP.js} +33 -224
- package/dist/chunk-G2DGDCDP.js.map +1 -0
- package/dist/{chunk-NUUFP53X.js → chunk-GPBX2BY2.js} +2 -2
- package/dist/{chunk-SN4S5CWL.js → chunk-GQDVH6FA.js} +2 -2
- package/dist/{chunk-YUOVWWJX.js → chunk-HKEXRZMU.js} +5 -310
- package/dist/chunk-HKEXRZMU.js.map +1 -0
- package/dist/{chunk-TAEVA4QR.js → chunk-HWDQRW3O.js} +3 -3
- package/dist/chunk-HWDQRW3O.js.map +1 -0
- package/dist/{chunk-KQSV7FOG.js → chunk-J5JOJPK3.js} +2 -2
- package/dist/{chunk-PD75ZCFT.js → chunk-KCAWSZUO.js} +18 -17
- package/dist/chunk-KCAWSZUO.js.map +1 -0
- package/dist/{chunk-QNPJXO53.js → chunk-KGOBNC5A.js} +4 -4
- package/dist/{chunk-H3T3EPF3.js → chunk-LNY2Y32V.js} +2 -2
- package/dist/{chunk-7RCUWU3I.js → chunk-MRPIDNZU.js} +1 -1
- package/dist/chunk-MRPIDNZU.js.map +1 -0
- package/dist/{chunk-QQULYI2S.js → chunk-N6DY47YN.js} +80 -45
- package/dist/chunk-N6DY47YN.js.map +1 -0
- package/dist/{chunk-VIQOQ463.js → chunk-OLJ54WGW.js} +15 -10
- package/dist/chunk-OLJ54WGW.js.map +1 -0
- package/dist/{chunk-4VQXMEEP.js → chunk-PPQ5LV7U.js} +3 -3
- package/dist/{chunk-4VQXMEEP.js.map → chunk-PPQ5LV7U.js.map} +1 -1
- package/dist/{chunk-QED2WB2D.js → chunk-PS6K2AOV.js} +5 -5
- package/dist/{chunk-JD3K2344.js → chunk-QNRXRSKC.js} +36 -3
- package/dist/chunk-QNRXRSKC.js.map +1 -0
- package/dist/{chunk-Q7VXHJP6.js → chunk-SM3BCHYB.js} +5 -5
- package/dist/{chunk-SA446KA2.js → chunk-T4KFKKEB.js} +7 -7
- package/dist/{chunk-XCP2WDYA.js → chunk-T4NESGYB.js} +3 -3
- package/dist/{chunk-QXGM32TO.js → chunk-TJDKGKQV.js} +2 -2
- package/dist/{chunk-X5DRLONY.js → chunk-UXBVDD7U.js} +6 -6
- package/dist/{chunk-JDN4SPV3.js → chunk-WYDLOQYO.js} +2 -2
- package/dist/{chunk-4JZEQBWV.js → chunk-XIVLGWUX.js} +3 -1
- package/dist/chunk-XIVLGWUX.js.map +1 -0
- package/dist/{chunk-NTDY5AMO.js → chunk-ZEFTWM5Z.js} +2 -2
- package/dist/{cleanup-RJKLI47I.js → cleanup-BCVY7PEF.js} +22 -22
- package/dist/cleanup-BCVY7PEF.js.map +1 -0
- package/dist/cli.js +126 -105
- package/dist/cli.js.map +1 -1
- package/dist/{commit-SUHRUMDE.js → commit-L5JNBU4U.js} +8 -8
- package/dist/{compile-2MD346PO.js → compile-GPJOHXH4.js} +8 -8
- package/dist/{contribute-P4BMRY7C.js → contribute-QEGCI4PS.js} +4 -4
- package/dist/{dev-server-ZNTLWOL5.js → dev-server-67NPVWUN.js} +247 -31
- package/dist/dev-server-67NPVWUN.js.map +1 -0
- package/dist/{feedback-Q6WG2WX4.js → feedback-2LWXKLQZ.js} +4 -4
- package/dist/{git-TX2IEMB3.js → git-IS7AV3ED.js} +4 -4
- package/dist/hooks/iloom-hook.js +40 -2
- package/dist/{ignite-P644W2PK.js → ignite-VQDJQ37S.js} +12 -14
- package/dist/index.d.ts +73 -75
- package/dist/index.js +32 -32
- package/dist/index.js.map +1 -1
- package/dist/{init-5HFY7JG6.js → init-7SDJUAEZ.js} +8 -8
- package/dist/{install-deps-J4ALTM27.js → install-deps-NGSFDNUW.js} +8 -8
- package/dist/{issues-LZMIF22U.js → issues-4HQKEUP7.js} +5 -5
- package/dist/{lint-XIXKU22H.js → lint-C5FOVRXY.js} +8 -8
- package/dist/mcp/issue-management-server.js +19 -22
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/neon-helpers-LCZAN4U4.js +11 -0
- package/dist/{open-KUO35JIJ.js → open-WUTLRI6S.js} +19 -15
- package/dist/open-WUTLRI6S.js.map +1 -0
- package/dist/{plan-7CF56OIR.js → plan-GC3HF73T.js} +86 -66
- package/dist/plan-GC3HF73T.js.map +1 -0
- package/dist/{projects-L5AHUBGA.js → projects-3F6T3KZL.js} +2 -2
- package/dist/prompts/init-prompt.txt +40 -36
- package/dist/prompts/issue-prompt.txt +4 -1
- package/dist/prompts/plan-prompt.txt +97 -16
- package/dist/prompts/regular-prompt.txt +1 -1
- package/dist/prompts/swarm-orchestrator-prompt.txt +25 -12
- package/dist/{rebase-MAMWPA2L.js → rebase-CSGQICAP.js} +7 -7
- package/dist/{recap-IDBO3KM5.js → recap-CKGKFDJL.js} +7 -7
- package/dist/{run-RGZHCQ6M.js → run-3YL2IXXI.js} +19 -15
- package/dist/run-3YL2IXXI.js.map +1 -0
- package/dist/schema/settings.schema.json +35 -31
- package/dist/{shell-7ADCDFIV.js → shell-M2YYPNGV.js} +6 -6
- package/dist/{summary-7J2HORFD.js → summary-XR4CBJEG.js} +9 -9
- package/dist/{test-SRB7EWU6.js → test-ESDAHEVE.js} +8 -8
- package/dist/{test-git-G7ATVIXG.js → test-git-KWPLHYSI.js} +4 -4
- package/dist/{test-jira-Q2HPA522.js → test-jira-6NK7UHSV.js} +3 -3
- package/dist/{test-prefix-JMDGXR5A.js → test-prefix-VVODGHXP.js} +4 -4
- package/dist/{test-webserver-GZFVXBGD.js → test-webserver-AHXKC6H4.js} +6 -6
- package/dist/{vscode-3I7ISHUU.js → vscode-OY7HOVRO.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-4JZEQBWV.js.map +0 -1
- package/dist/chunk-7RCUWU3I.js.map +0 -1
- package/dist/chunk-AYLC633W.js.map +0 -1
- package/dist/chunk-JD3K2344.js.map +0 -1
- package/dist/chunk-PD75ZCFT.js.map +0 -1
- package/dist/chunk-QQULYI2S.js.map +0 -1
- package/dist/chunk-TAEVA4QR.js.map +0 -1
- package/dist/chunk-TN2D2RX7.js.map +0 -1
- package/dist/chunk-VIQOQ463.js.map +0 -1
- package/dist/chunk-VVQQIG64.js.map +0 -1
- package/dist/chunk-WGUGB54H.js.map +0 -1
- package/dist/chunk-YUOVWWJX.js.map +0 -1
- package/dist/chunk-ZUIFO7B4.js.map +0 -1
- package/dist/cleanup-RJKLI47I.js.map +0 -1
- package/dist/database-helpers-PRDFNDRO.js +0 -11
- package/dist/dev-server-ZNTLWOL5.js.map +0 -1
- package/dist/open-KUO35JIJ.js.map +0 -1
- package/dist/plan-7CF56OIR.js.map +0 -1
- package/dist/run-RGZHCQ6M.js.map +0 -1
- /package/dist/{ClaudeContextManager-RRGREEZQ.js.map → ClaudeContextManager-ZH6LEA5I.js.map} +0 -0
- /package/dist/{ClaudeService-LEPW6QAC.js.map → ClaudeService-YR66WXZN.js.map} +0 -0
- /package/dist/{IssueTrackerFactory-KE2BDCLC.js.map → IssueTrackerFactory-O2ZBA666.js.map} +0 -0
- /package/dist/{LoomLauncher-GKQMR5E6.js.map → LoomLauncher-V54ENBEF.js.map} +0 -0
- /package/dist/{MetadataManager-V4LSJ2PB.js.map → MetadataManager-HHE6LQF2.js.map} +0 -0
- /package/dist/{PromptTemplateManager-I75WKXM4.js.map → PromptTemplateManager-4RFELNYY.js.map} +0 -0
- /package/dist/{SettingsManager-KQU7OX7G.js.map → SettingsManager-SLSYEYDZ.js.map} +0 -0
- /package/dist/{build-V3KADFMO.js.map → build-ZTGWDHWU.js.map} +0 -0
- /package/dist/{chunk-RFCAPHL5.js.map → chunk-C2BVNJW5.js.map} +0 -0
- /package/dist/{chunk-3XEXT35Z.js.map → chunk-E5OM25WK.js.map} +0 -0
- /package/dist/{chunk-NUUFP53X.js.map → chunk-GPBX2BY2.js.map} +0 -0
- /package/dist/{chunk-SN4S5CWL.js.map → chunk-GQDVH6FA.js.map} +0 -0
- /package/dist/{chunk-KQSV7FOG.js.map → chunk-J5JOJPK3.js.map} +0 -0
- /package/dist/{chunk-QNPJXO53.js.map → chunk-KGOBNC5A.js.map} +0 -0
- /package/dist/{chunk-H3T3EPF3.js.map → chunk-LNY2Y32V.js.map} +0 -0
- /package/dist/{chunk-QED2WB2D.js.map → chunk-PS6K2AOV.js.map} +0 -0
- /package/dist/{chunk-Q7VXHJP6.js.map → chunk-SM3BCHYB.js.map} +0 -0
- /package/dist/{chunk-SA446KA2.js.map → chunk-T4KFKKEB.js.map} +0 -0
- /package/dist/{chunk-XCP2WDYA.js.map → chunk-T4NESGYB.js.map} +0 -0
- /package/dist/{chunk-QXGM32TO.js.map → chunk-TJDKGKQV.js.map} +0 -0
- /package/dist/{chunk-X5DRLONY.js.map → chunk-UXBVDD7U.js.map} +0 -0
- /package/dist/{chunk-JDN4SPV3.js.map → chunk-WYDLOQYO.js.map} +0 -0
- /package/dist/{chunk-NTDY5AMO.js.map → chunk-ZEFTWM5Z.js.map} +0 -0
- /package/dist/{commit-SUHRUMDE.js.map → commit-L5JNBU4U.js.map} +0 -0
- /package/dist/{compile-2MD346PO.js.map → compile-GPJOHXH4.js.map} +0 -0
- /package/dist/{contribute-P4BMRY7C.js.map → contribute-QEGCI4PS.js.map} +0 -0
- /package/dist/{feedback-Q6WG2WX4.js.map → feedback-2LWXKLQZ.js.map} +0 -0
- /package/dist/{database-helpers-PRDFNDRO.js.map → git-IS7AV3ED.js.map} +0 -0
- /package/dist/{git-TX2IEMB3.js.map → ignite-VQDJQ37S.js.map} +0 -0
- /package/dist/{init-5HFY7JG6.js.map → init-7SDJUAEZ.js.map} +0 -0
- /package/dist/{install-deps-J4ALTM27.js.map → install-deps-NGSFDNUW.js.map} +0 -0
- /package/dist/{issues-LZMIF22U.js.map → issues-4HQKEUP7.js.map} +0 -0
- /package/dist/{lint-XIXKU22H.js.map → lint-C5FOVRXY.js.map} +0 -0
- /package/dist/{ignite-P644W2PK.js.map → neon-helpers-LCZAN4U4.js.map} +0 -0
- /package/dist/{projects-L5AHUBGA.js.map → projects-3F6T3KZL.js.map} +0 -0
- /package/dist/{rebase-MAMWPA2L.js.map → rebase-CSGQICAP.js.map} +0 -0
- /package/dist/{recap-IDBO3KM5.js.map → recap-CKGKFDJL.js.map} +0 -0
- /package/dist/{shell-7ADCDFIV.js.map → shell-M2YYPNGV.js.map} +0 -0
- /package/dist/{summary-7J2HORFD.js.map → summary-XR4CBJEG.js.map} +0 -0
- /package/dist/{test-SRB7EWU6.js.map → test-ESDAHEVE.js.map} +0 -0
- /package/dist/{test-git-G7ATVIXG.js.map → test-git-KWPLHYSI.js.map} +0 -0
- /package/dist/{test-jira-Q2HPA522.js.map → test-jira-6NK7UHSV.js.map} +0 -0
- /package/dist/{test-prefix-JMDGXR5A.js.map → test-prefix-VVODGHXP.js.map} +0 -0
- /package/dist/{test-webserver-GZFVXBGD.js.map → test-webserver-AHXKC6H4.js.map} +0 -0
- /package/dist/{vscode-3I7ISHUU.js.map → vscode-OY7HOVRO.js.map} +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
DockerManager
|
|
4
|
-
|
|
3
|
+
DockerManager,
|
|
4
|
+
expandAndValidateSecretPaths
|
|
5
|
+
} from "./chunk-7TN5VW4I.js";
|
|
5
6
|
import {
|
|
6
7
|
ProcessManager
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LNY2Y32V.js";
|
|
8
9
|
import {
|
|
9
10
|
detectPackageManager,
|
|
10
11
|
runScript
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OLJ54WGW.js";
|
|
12
13
|
import {
|
|
13
14
|
getPackageScripts
|
|
14
15
|
} from "./chunk-K3QGG4O2.js";
|
|
@@ -17,6 +18,31 @@ import {
|
|
|
17
18
|
restoreTerminalState
|
|
18
19
|
} from "./chunk-VRPPI6GU.js";
|
|
19
20
|
|
|
21
|
+
// src/utils/dev-server.ts
|
|
22
|
+
function buildDevServerUrl(port, protocol = "http") {
|
|
23
|
+
return `${protocol}://localhost:${port}`;
|
|
24
|
+
}
|
|
25
|
+
async function buildDevServerCommand(workspacePath) {
|
|
26
|
+
const packageManager = await detectPackageManager(workspacePath);
|
|
27
|
+
let devCommand;
|
|
28
|
+
switch (packageManager) {
|
|
29
|
+
case "pnpm":
|
|
30
|
+
devCommand = "pnpm dev";
|
|
31
|
+
break;
|
|
32
|
+
case "npm":
|
|
33
|
+
devCommand = "npm run dev";
|
|
34
|
+
break;
|
|
35
|
+
case "yarn":
|
|
36
|
+
devCommand = "yarn dev";
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
logger.warn(`Unknown or unsupported package manager: ${packageManager}, defaulting to npm`);
|
|
40
|
+
devCommand = "npm run dev";
|
|
41
|
+
}
|
|
42
|
+
logger.debug(`Dev server command: ${devCommand}`);
|
|
43
|
+
return devCommand;
|
|
44
|
+
}
|
|
45
|
+
|
|
20
46
|
// src/lib/DevServerManager.ts
|
|
21
47
|
import path from "path";
|
|
22
48
|
|
|
@@ -86,13 +112,21 @@ var DockerDevServerStrategy = class {
|
|
|
86
112
|
args.push("--build-arg", `${key}=${value}`);
|
|
87
113
|
}
|
|
88
114
|
}
|
|
115
|
+
const expandedSecrets = expandAndValidateSecretPaths(config.buildSecrets, worktreePath);
|
|
116
|
+
for (const [id, srcPath] of Object.entries(expandedSecrets)) {
|
|
117
|
+
args.push("--secret", `id=${id},src=${srcPath}`);
|
|
118
|
+
}
|
|
89
119
|
args.push(".");
|
|
90
120
|
logger.info(`Building Docker image "${imageName}" from ${dockerfilePath}...`);
|
|
121
|
+
const execaOptions = {
|
|
122
|
+
cwd: worktreePath,
|
|
123
|
+
stdio: "inherit"
|
|
124
|
+
};
|
|
125
|
+
if (Object.keys(expandedSecrets).length > 0) {
|
|
126
|
+
execaOptions.env = { ...process.env, DOCKER_BUILDKIT: "1" };
|
|
127
|
+
}
|
|
91
128
|
try {
|
|
92
|
-
await execa("docker", args,
|
|
93
|
-
cwd: worktreePath,
|
|
94
|
-
stdio: "inherit"
|
|
95
|
-
});
|
|
129
|
+
await execa("docker", args, execaOptions);
|
|
96
130
|
} catch (error) {
|
|
97
131
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
98
132
|
throw new Error(`Docker build failed for image "${imageName}": ${message}`);
|
|
@@ -144,7 +178,8 @@ var DockerDevServerStrategy = class {
|
|
|
144
178
|
args.push(...config.runArgs);
|
|
145
179
|
}
|
|
146
180
|
args.push(imageName);
|
|
147
|
-
|
|
181
|
+
const displayProtocol = config.protocol ?? "http";
|
|
182
|
+
logger.info(`Starting Docker container "${containerName}" in background (${displayProtocol}://localhost:${hostPort} \u2192 container:${containerPort})...`);
|
|
148
183
|
try {
|
|
149
184
|
await execa("docker", args);
|
|
150
185
|
} catch (error) {
|
|
@@ -167,10 +202,11 @@ var DockerDevServerStrategy = class {
|
|
|
167
202
|
* @returns Object with optional pid (Docker containers don't expose host PID)
|
|
168
203
|
*/
|
|
169
204
|
async runContainerForeground(worktreePath, hostPort, containerPort, config, opts = {}) {
|
|
205
|
+
var _a, _b;
|
|
170
206
|
const nameId = config.identifier ?? worktreePath;
|
|
171
207
|
const imageName = this.utils.buildImageName(nameId);
|
|
172
208
|
const containerName = this.utils.buildContainerName(nameId);
|
|
173
|
-
const { redirectToStderr, onProcessStarted, envOverrides } = opts;
|
|
209
|
+
const { redirectToStderr, onProcessStarted, envOverrides, onOutput } = opts;
|
|
174
210
|
await execa("docker", ["rm", "-f", containerName], { reject: false });
|
|
175
211
|
const args = [
|
|
176
212
|
"run",
|
|
@@ -199,8 +235,9 @@ var DockerDevServerStrategy = class {
|
|
|
199
235
|
args.push(...config.runArgs);
|
|
200
236
|
}
|
|
201
237
|
args.push(imageName);
|
|
202
|
-
|
|
203
|
-
|
|
238
|
+
const displayProtocol = config.protocol ?? "http";
|
|
239
|
+
logger.info(`Running Docker container "${containerName}" in foreground (${displayProtocol}://localhost:${hostPort} \u2192 container:${containerPort})...`);
|
|
240
|
+
const stdio = onOutput ? ["ignore", "pipe", "pipe"] : redirectToStderr ? [process.stdin, process.stderr, process.stderr] : "inherit";
|
|
204
241
|
const forwardSignal = () => {
|
|
205
242
|
logger.debug(`Stopping container "${containerName}"`);
|
|
206
243
|
void execa("docker", ["stop", containerName], { reject: false });
|
|
@@ -213,7 +250,18 @@ var DockerDevServerStrategy = class {
|
|
|
213
250
|
onProcessStarted(void 0);
|
|
214
251
|
}
|
|
215
252
|
try {
|
|
216
|
-
|
|
253
|
+
const dockerProcess = execa("docker", args, { stdio });
|
|
254
|
+
if (onOutput) {
|
|
255
|
+
(_a = dockerProcess.stdout) == null ? void 0 : _a.on("data", onOutput);
|
|
256
|
+
(_b = dockerProcess.stderr) == null ? void 0 : _b.on("data", onOutput);
|
|
257
|
+
}
|
|
258
|
+
await dockerProcess;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const execaError = error;
|
|
261
|
+
const isExpectedShutdown = execaError.exitCode === 143 || execaError.exitCode === 130 || execaError.signal === "SIGTERM" || execaError.signal === "SIGINT";
|
|
262
|
+
if (!isExpectedShutdown) {
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
217
265
|
} finally {
|
|
218
266
|
process.removeListener("SIGINT", onSigint);
|
|
219
267
|
process.removeListener("SIGTERM", onSigterm);
|
|
@@ -293,30 +341,6 @@ var DockerDevServerStrategy = class {
|
|
|
293
341
|
// src/lib/NativeDevServerStrategy.ts
|
|
294
342
|
import { execa as execa2 } from "execa";
|
|
295
343
|
import { setTimeout } from "timers/promises";
|
|
296
|
-
|
|
297
|
-
// src/utils/dev-server.ts
|
|
298
|
-
async function buildDevServerCommand(workspacePath) {
|
|
299
|
-
const packageManager = await detectPackageManager(workspacePath);
|
|
300
|
-
let devCommand;
|
|
301
|
-
switch (packageManager) {
|
|
302
|
-
case "pnpm":
|
|
303
|
-
devCommand = "pnpm dev";
|
|
304
|
-
break;
|
|
305
|
-
case "npm":
|
|
306
|
-
devCommand = "npm run dev";
|
|
307
|
-
break;
|
|
308
|
-
case "yarn":
|
|
309
|
-
devCommand = "yarn dev";
|
|
310
|
-
break;
|
|
311
|
-
default:
|
|
312
|
-
logger.warn(`Unknown or unsupported package manager: ${packageManager}, defaulting to npm`);
|
|
313
|
-
devCommand = "npm run dev";
|
|
314
|
-
}
|
|
315
|
-
logger.debug(`Dev server command: ${devCommand}`);
|
|
316
|
-
return devCommand;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// src/lib/NativeDevServerStrategy.ts
|
|
320
344
|
var NativeDevServerStrategy = class {
|
|
321
345
|
constructor(processManager, startupTimeout, checkInterval) {
|
|
322
346
|
this.runningServers = /* @__PURE__ */ new Map();
|
|
@@ -363,11 +387,13 @@ var NativeDevServerStrategy = class {
|
|
|
363
387
|
logger.success(`Dev server started successfully on port ${port}`);
|
|
364
388
|
}
|
|
365
389
|
async startForeground(worktreePath, port, opts) {
|
|
366
|
-
|
|
390
|
+
var _a, _b;
|
|
391
|
+
const { redirectToStderr = false, onProcessStarted, envOverrides, onOutput } = opts;
|
|
367
392
|
logger.debug(`Starting dev server in foreground on port ${port}`);
|
|
368
|
-
if (redirectToStderr) {
|
|
393
|
+
if (redirectToStderr || onOutput) {
|
|
369
394
|
const devCommand = await buildDevServerCommand(worktreePath);
|
|
370
395
|
logger.debug(`Starting dev server with command: ${devCommand}`);
|
|
396
|
+
const stdio = onOutput ? ["ignore", "pipe", "pipe"] : [process.stdin, process.stderr, process.stderr];
|
|
371
397
|
const serverProcess = execa2("sh", ["-c", devCommand], {
|
|
372
398
|
cwd: worktreePath,
|
|
373
399
|
env: {
|
|
@@ -375,8 +401,12 @@ var NativeDevServerStrategy = class {
|
|
|
375
401
|
...envOverrides,
|
|
376
402
|
PORT: port.toString()
|
|
377
403
|
},
|
|
378
|
-
stdio
|
|
404
|
+
stdio
|
|
379
405
|
});
|
|
406
|
+
if (onOutput) {
|
|
407
|
+
(_a = serverProcess.stdout) == null ? void 0 : _a.on("data", onOutput);
|
|
408
|
+
(_b = serverProcess.stderr) == null ? void 0 : _b.on("data", onOutput);
|
|
409
|
+
}
|
|
380
410
|
const processInfo = serverProcess.pid !== void 0 ? { pid: serverProcess.pid } : {};
|
|
381
411
|
if (onProcessStarted) {
|
|
382
412
|
onProcessStarted(processInfo.pid);
|
|
@@ -404,6 +434,7 @@ var NativeDevServerStrategy = class {
|
|
|
404
434
|
},
|
|
405
435
|
foreground: true,
|
|
406
436
|
...onProcessStarted && { onStart: onProcessStarted },
|
|
437
|
+
...onOutput !== void 0 ? { onOutput } : {},
|
|
407
438
|
noCi: true
|
|
408
439
|
// Dev servers should not have CI=true
|
|
409
440
|
});
|
|
@@ -495,8 +526,10 @@ function toStrategyConfig(config) {
|
|
|
495
526
|
dockerFile: config.dockerFile,
|
|
496
527
|
containerPort: config.containerPort,
|
|
497
528
|
buildArgs: config.dockerBuildArgs,
|
|
529
|
+
buildSecrets: config.dockerBuildSecrets,
|
|
498
530
|
runArgs: config.dockerRunArgs,
|
|
499
|
-
identifier: config.identifier
|
|
531
|
+
identifier: config.identifier,
|
|
532
|
+
protocol: config.protocol
|
|
500
533
|
};
|
|
501
534
|
}
|
|
502
535
|
var DevServerManager = class {
|
|
@@ -632,7 +665,7 @@ var DevServerManager = class {
|
|
|
632
665
|
* @param onProcessStarted - Callback called immediately after process starts with PID
|
|
633
666
|
* @returns Process information including PID
|
|
634
667
|
*/
|
|
635
|
-
async runServerForeground(worktreePath, port, redirectToStderr = false, onProcessStarted, envOverrides, dockerConfig) {
|
|
668
|
+
async runServerForeground(worktreePath, port, redirectToStderr = false, onProcessStarted, envOverrides, dockerConfig, onOutput) {
|
|
636
669
|
if (dockerConfig) {
|
|
637
670
|
logger.debug(`Starting Docker dev server in foreground on port ${port}`);
|
|
638
671
|
const strategy = this.createDockerStrategy(dockerConfig);
|
|
@@ -656,7 +689,7 @@ var DevServerManager = class {
|
|
|
656
689
|
port,
|
|
657
690
|
containerPort,
|
|
658
691
|
strategyConfig,
|
|
659
|
-
{ redirectToStderr, envOverrides }
|
|
692
|
+
{ redirectToStderr, envOverrides, onOutput }
|
|
660
693
|
);
|
|
661
694
|
} finally {
|
|
662
695
|
this.runningDockerContainers.delete(port);
|
|
@@ -666,7 +699,8 @@ var DevServerManager = class {
|
|
|
666
699
|
return this.nativeStrategy.startForeground(worktreePath, port, {
|
|
667
700
|
redirectToStderr,
|
|
668
701
|
...onProcessStarted !== void 0 && { onProcessStarted },
|
|
669
|
-
...envOverrides !== void 0 && { envOverrides }
|
|
702
|
+
...envOverrides !== void 0 && { envOverrides },
|
|
703
|
+
...onOutput !== void 0 && { onOutput }
|
|
670
704
|
});
|
|
671
705
|
}
|
|
672
706
|
/**
|
|
@@ -691,6 +725,7 @@ var DevServerManager = class {
|
|
|
691
725
|
};
|
|
692
726
|
|
|
693
727
|
export {
|
|
728
|
+
buildDevServerUrl,
|
|
694
729
|
DevServerManager
|
|
695
730
|
};
|
|
696
|
-
//# sourceMappingURL=chunk-
|
|
731
|
+
//# sourceMappingURL=chunk-N6DY47YN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/dev-server.ts","../src/lib/DevServerManager.ts","../src/lib/DockerDevServerStrategy.ts","../src/lib/NativeDevServerStrategy.ts"],"sourcesContent":["import { detectPackageManager } from './package-manager.js'\nimport { logger } from './logger.js'\nimport type { Capability } from '../types/loom.js'\n\n/**\n * Build the dev server URL from port and protocol\n */\nexport function buildDevServerUrl(port: number, protocol: 'http' | 'https' = 'http'): string {\n\treturn `${protocol}://localhost:${port}`\n}\n\n/**\n * Build dev server command for workspace\n * Detects package manager and constructs appropriate command\n */\nexport async function buildDevServerCommand(\n\tworkspacePath: string\n): Promise<string> {\n\tconst packageManager = await detectPackageManager(workspacePath)\n\n\tlet devCommand: string\n\n\tswitch (packageManager) {\n\t\tcase 'pnpm':\n\t\t\tdevCommand = 'pnpm dev'\n\t\t\tbreak\n\t\tcase 'npm':\n\t\t\tdevCommand = 'npm run dev'\n\t\t\tbreak\n\t\tcase 'yarn':\n\t\t\tdevCommand = 'yarn dev'\n\t\t\tbreak\n\t\tdefault:\n\t\t\t// Fallback to npm (handles bun and other package managers)\n\t\t\tlogger.warn(`Unknown or unsupported package manager: ${packageManager}, defaulting to npm`)\n\t\t\tdevCommand = 'npm run dev'\n\t}\n\n\tlogger.debug(`Dev server command: ${devCommand}`)\n\treturn devCommand\n}\n\n/**\n * Build complete dev server launch command for terminal\n * Includes VSCode launch, echo message (only for web projects), and dev server start\n */\nexport async function getDevServerLaunchCommand(\n\tworkspacePath: string,\n\tport?: number,\n\tcapabilities: Capability[] = []\n): Promise<string> {\n\tconst devCommand = await buildDevServerCommand(workspacePath)\n\n\tconst commands: string[] = []\n\n\t// // Open VSCode\n\t// commands.push('code .')\n\n\t// Echo message (only for web projects)\n\tif (capabilities.includes('web')) {\n\t\tif (port !== undefined) {\n\t\t\tcommands.push(`echo 'Starting dev server on PORT=${port}...'`)\n\t\t} else {\n\t\t\tcommands.push(`echo 'Starting dev server...'`)\n\t\t}\n\t}\n\n\t// Start dev server\n\tcommands.push(devCommand)\n\n\treturn commands.join(' && ')\n}\n","import path from 'path'\nimport { ProcessManager } from './process/ProcessManager.js'\nimport { DockerManager, type DockerConfig } from './DockerManager.js'\nimport { DockerDevServerStrategy, type DockerConfig as StrategyDockerConfig, type DockerUtils } from './DockerDevServerStrategy.js'\nimport { NativeDevServerStrategy } from './NativeDevServerStrategy.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Default startup timeout in milliseconds (180 seconds)\n * Can be overridden via ILOOM_DEV_SERVER_TIMEOUT environment variable\n */\nconst DEFAULT_STARTUP_TIMEOUT = 180000\n\n/**\n * Bridge DockerManager static methods to the DockerUtils interface\n * expected by DockerDevServerStrategy.\n */\nconst dockerUtils: DockerUtils = {\n\tparseDockerfileExpose: (filePath: string) => DockerManager.parseExposeFromDockerfile(filePath),\n\tinspectImagePorts: (imageName: string) => DockerManager.inspectImagePorts(imageName),\n\tbuildContainerName: (id: string | number) => DockerManager.buildContainerName(id),\n\tbuildImageName: (id: string | number) => DockerManager.buildImageName(id),\n\tassertDockerAvailable: () => DockerManager.assertAvailable(),\n}\n\nfunction getStartupTimeout(): number {\n\tconst envTimeout = process.env.ILOOM_DEV_SERVER_TIMEOUT\n\tif (envTimeout) {\n\t\tconst parsed = parseInt(envTimeout, 10)\n\t\tif (!isNaN(parsed) && parsed > 0) {\n\t\t\treturn parsed\n\t\t}\n\t}\n\treturn DEFAULT_STARTUP_TIMEOUT\n}\n\nexport interface DevServerManagerOptions {\n\t/**\n\t * Maximum time to wait for server to start (in milliseconds)\n\t * Default: 180000 (180 seconds)\n\t * Can be overridden via ILOOM_DEV_SERVER_TIMEOUT environment variable\n\t */\n\tstartupTimeout?: number\n\n\t/**\n\t * Interval between port checks (in milliseconds)\n\t * Default: 1000 (1 second)\n\t */\n\tcheckInterval?: number\n}\n\n// Re-export DockerConfig from DockerManager for backward compatibility\nexport type { DockerConfig } from './DockerManager.js'\n\n/**\n * Convert a DockerConfig (from DockerManager) to a StrategyDockerConfig\n * (for DockerDevServerStrategy).\n */\nfunction toStrategyConfig(config: DockerConfig): StrategyDockerConfig {\n\treturn {\n\t\tdockerFile: config.dockerFile,\n\t\tcontainerPort: config.containerPort,\n\t\tbuildArgs: config.dockerBuildArgs,\n\t\tbuildSecrets: config.dockerBuildSecrets,\n\t\trunArgs: config.dockerRunArgs,\n\t\tidentifier: config.identifier,\n\t\tprotocol: config.protocol,\n\t}\n}\n\n/**\n * DevServerManager handles auto-starting and monitoring dev servers.\n * Used by open/run commands to ensure dev server is running before opening browser.\n *\n * When devServer config is absent OR mode is not 'docker', behavior is identical\n * to the native process-based implementation via NativeDevServerStrategy.\n * When Docker mode is configured, all operations delegate to DockerDevServerStrategy.\n */\nexport class DevServerManager {\n\tprivate readonly processManager: ProcessManager\n\tprivate readonly options: Required<DevServerManagerOptions>\n\tprivate readonly nativeStrategy: NativeDevServerStrategy\n\tprivate runningDockerContainers: Map<number, string> = new Map()\n\n\tconstructor(\n\t\tprocessManager?: ProcessManager,\n\t\toptions: DevServerManagerOptions = {}\n\t) {\n\t\tthis.processManager = processManager ?? new ProcessManager()\n\t\tthis.options = {\n\t\t\tstartupTimeout: options.startupTimeout ?? getStartupTimeout(),\n\t\t\tcheckInterval: options.checkInterval ?? 1000,\n\t\t}\n\t\tthis.nativeStrategy = new NativeDevServerStrategy(\n\t\t\tthis.processManager,\n\t\t\tthis.options.startupTimeout,\n\t\t\tthis.options.checkInterval\n\t\t)\n\t}\n\n\t/**\n\t * Create a DockerDevServerStrategy for the given Docker config.\n\t * The strategy encapsulates all Docker container lifecycle operations.\n\t */\n\tprivate createDockerStrategy(dockerConfig: DockerConfig): DockerDevServerStrategy {\n\t\treturn new DockerDevServerStrategy(toStrategyConfig(dockerConfig), dockerUtils)\n\t}\n\n\t/**\n\t * Ensure dev server is running on the specified port.\n\t * If not running, start it and wait for it to be ready.\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param port - Port the server should run on\n\t * @param dockerConfig - Optional Docker configuration for container-based server\n\t * @returns true if server is ready, false if startup failed/timed out\n\t */\n\tasync ensureServerRunning(worktreePath: string, port: number, dockerConfig?: DockerConfig): Promise<boolean> {\n\t\tlogger.debug(`Checking if dev server is running on port ${port}...`)\n\n\t\t// Docker mode: check if container is already running\n\t\tif (dockerConfig) {\n\t\t\tconst strategy = this.createDockerStrategy(dockerConfig)\n\t\t\tconst containerName = dockerUtils.buildContainerName(dockerConfig.identifier)\n\t\t\tconst isRunning = await strategy.isContainerRunning(containerName)\n\t\t\tif (isRunning) {\n\t\t\t\tlogger.debug(`Docker container \"${containerName}\" already running on port ${port}`)\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tlogger.info(`Docker dev server not running on port ${port}, starting...`)\n\t\t\ttry {\n\t\t\t\tawait this.startDockerServer(worktreePath, port, dockerConfig, strategy)\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`Failed to start Docker dev server: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\t// Native mode: check if a process is listening on the port\n\t\tconst existingProcess = await this.processManager.detectDevServer(port)\n\t\tif (existingProcess) {\n\t\t\tlogger.debug(\n\t\t\t\t`Dev server already running on port ${port} (PID: ${existingProcess.pid})`\n\t\t\t)\n\t\t\treturn true\n\t\t}\n\n\t\t// Not running - start it\n\t\tlogger.info(`Dev server not running on port ${port}, starting...`)\n\n\t\ttry {\n\t\t\tawait this.nativeStrategy.startBackground(worktreePath, port)\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tlogger.error(\n\t\t\t\t`Failed to start dev server: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Start dev server in Docker container (background) and wait for it to be ready.\n\t * Builds the image, resolves the container port, starts the container detached,\n\t * and polls the host port for readiness.\n\t */\n\tprivate async startDockerServer(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\tdockerConfig: DockerConfig,\n\t\tstrategy: DockerDevServerStrategy\n\t): Promise<void> {\n\t\tconst strategyConfig = toStrategyConfig(dockerConfig)\n\t\tconst imageName = dockerUtils.buildImageName(dockerConfig.identifier)\n\t\tconst dockerfilePath = path.resolve(worktreePath, dockerConfig.dockerFile)\n\n\t\t// Build image\n\t\tawait strategy.buildImage(worktreePath, strategyConfig)\n\n\t\t// Resolve container port (config > image inspect > Dockerfile EXPOSE)\n\t\tconst containerPort = await strategy.resolveContainerPort(\n\t\t\tstrategyConfig,\n\t\t\timageName,\n\t\t\tdockerfilePath\n\t\t)\n\n\t\t// Run container detached\n\t\tconst containerName = await strategy.runContainerDetached(\n\t\t\tworktreePath,\n\t\t\tport,\n\t\t\tcontainerPort,\n\t\t\tstrategyConfig\n\t\t)\n\n\t\t// Track for cleanup\n\t\tthis.runningDockerContainers.set(port, containerName)\n\n\t\t// Wait for server to be ready via TCP probe (Docker proxy listens on host port)\n\t\t// Pass container name for early crash detection\n\t\tlogger.info(`Waiting for Docker dev server to start on port ${port}...`)\n\t\tconst ready = await strategy.waitForReady(\n\t\t\tport,\n\t\t\tthis.options.startupTimeout,\n\t\t\tthis.options.checkInterval,\n\t\t\tcontainerName\n\t\t)\n\n\t\tif (!ready) {\n\t\t\t// Clean up the container if startup failed\n\t\t\tawait strategy.stopContainer(containerName)\n\t\t\tthis.runningDockerContainers.delete(port)\n\t\t\tthrow new Error(\n\t\t\t\t`Docker dev server failed to start within ${this.options.startupTimeout}ms timeout`\n\t\t\t)\n\t\t}\n\n\t\tlogger.success(`Docker dev server started successfully on port ${port}`)\n\t}\n\n\t/**\n\t * Check if a dev server is running on the specified port\n\t *\n\t * @param port - Port to check\n\t * @param dockerConfig - Optional Docker configuration; when provided, checks container status\n\t * @returns true if server is running, false otherwise\n\t */\n\tasync isServerRunning(port: number, dockerConfig?: DockerConfig): Promise<boolean> {\n\t\tif (dockerConfig) {\n\t\t\tconst strategy = this.createDockerStrategy(dockerConfig)\n\t\t\tconst containerName = dockerUtils.buildContainerName(dockerConfig.identifier)\n\t\t\treturn strategy.isContainerRunning(containerName)\n\t\t}\n\t\tconst existingProcess = await this.processManager.detectDevServer(port)\n\t\treturn existingProcess !== null\n\t}\n\n\t/**\n\t * Run dev server in foreground mode (blocking).\n\t * This method blocks until the server is stopped (e.g., via Ctrl+C).\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param port - Port the server should run on\n\t * @param redirectToStderr - If true, redirect stdout/stderr to stderr (useful for JSON output)\n\t * @param onProcessStarted - Callback called immediately after process starts with PID\n\t * @returns Process information including PID\n\t */\n\tasync runServerForeground(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\tredirectToStderr = false,\n\t\tonProcessStarted?: (pid?: number) => void,\n\t\tenvOverrides?: Record<string, string>,\n\t\tdockerConfig?: DockerConfig,\n\t\tonOutput?: (data: Buffer) => void\n\t): Promise<{ pid?: number }> {\n\t\t// Docker mode: build image and run container in foreground\n\t\tif (dockerConfig) {\n\t\t\tlogger.debug(`Starting Docker dev server in foreground on port ${port}`)\n\n\t\t\tconst strategy = this.createDockerStrategy(dockerConfig)\n\t\t\tconst strategyConfig = toStrategyConfig(dockerConfig)\n\t\t\tconst imageName = dockerUtils.buildImageName(dockerConfig.identifier)\n\t\t\tconst containerName = dockerUtils.buildContainerName(dockerConfig.identifier)\n\t\t\tconst dockerfilePath = path.resolve(worktreePath, dockerConfig.dockerFile)\n\n\t\t\t// Build image\n\t\t\tawait strategy.buildImage(worktreePath, strategyConfig)\n\n\t\t\t// Resolve container port\n\t\t\tconst containerPort = await strategy.resolveContainerPort(\n\t\t\t\tstrategyConfig,\n\t\t\t\timageName,\n\t\t\t\tdockerfilePath\n\t\t\t)\n\n\t\t\tif (onProcessStarted) {\n\t\t\t\tonProcessStarted(undefined)\n\t\t\t}\n\n\t\t\t// Track container for cleanup\n\t\t\tthis.runningDockerContainers.set(port, containerName)\n\t\t\ttry {\n\t\t\t\t// Run container in foreground (blocks until stopped)\n\t\t\t\t// DockerDevServerStrategy.runContainerForeground handles signal forwarding internally\n\t\t\t\tawait strategy.runContainerForeground(\n\t\t\t\t\tworktreePath,\n\t\t\t\t\tport,\n\t\t\t\t\tcontainerPort,\n\t\t\t\t\tstrategyConfig,\n\t\t\t\t\t{ redirectToStderr, envOverrides, onOutput }\n\t\t\t\t)\n\t\t\t} finally {\n\t\t\t\tthis.runningDockerContainers.delete(port)\n\t\t\t}\n\n\t\t\treturn {}\n\t\t}\n\n\t\t// Native mode: delegate to NativeDevServerStrategy\n\t\treturn this.nativeStrategy.startForeground(worktreePath, port, {\n\t\t\tredirectToStderr,\n\t\t\t...(onProcessStarted !== undefined && { onProcessStarted }),\n\t\t\t...(envOverrides !== undefined && { envOverrides }),\n\t\t\t...(onOutput !== undefined && { onOutput }),\n\t\t})\n\t}\n\n\t/**\n\t * Clean up all running server processes and Docker containers.\n\t * This should be called when the manager is being disposed.\n\t */\n\tasync cleanup(): Promise<void> {\n\t\t// Clean up native process-based servers\n\t\tawait this.nativeStrategy.stopAll()\n\n\t\t// Clean up Docker containers using DockerDevServerStrategy\n\t\tfor (const [port, containerName] of this.runningDockerContainers.entries()) {\n\t\t\ttry {\n\t\t\t\tlogger.debug(`Cleaning up Docker container \"${containerName}\" on port ${port}`)\n\t\t\t\t// Create a minimal strategy just for stopContainer\n\t\t\t\tconst strategy = new DockerDevServerStrategy({}, dockerUtils)\n\t\t\t\tawait strategy.stopContainer(containerName)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to stop Docker container \"${containerName}\" on port ${port}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tthis.runningDockerContainers.clear()\n\t}\n}\n","import { execa, type ExecaError } from 'execa'\nimport net from 'net'\nimport { logger } from '../utils/logger.js'\nimport { restoreTerminalState } from '../utils/terminal.js'\nimport { expandAndValidateSecretPaths } from '../utils/docker.js'\n\n/**\n * Docker configuration shape consumed by DockerDevServerStrategy.\n * Matches the DevServerSettings['docker'] shape from the settings schema.\n */\nexport interface DockerConfig {\n\t/** Path to Dockerfile (relative to worktree) */\n\tdockerFile?: string | undefined\n\t/** Port inside the container (auto-detected from image inspect or Dockerfile EXPOSE if not set) */\n\tcontainerPort?: number | undefined\n\t/** Build arguments passed as --build-arg to docker build */\n\tbuildArgs?: Record<string, string> | undefined\n\t/** Secret files to mount during docker build via BuildKit --secret flag */\n\tbuildSecrets?: Record<string, string> | undefined\n\t/** Additional docker run flags */\n\trunArgs?: string[] | undefined\n\t/** Identifier for naming containers/images (issue number, branch name). Falls back to worktreePath if not set. */\n\tidentifier?: string | undefined\n\t/** Protocol for displayed URLs (http or https, default http) */\n\tprotocol?: 'http' | 'https' | undefined\n}\n\n/**\n * Options for runContainerForeground.\n */\nexport interface RunForegroundOptions {\n\t/** If true, redirect stdout/stderr to process.stderr */\n\tredirectToStderr?: boolean | undefined\n\t/** Called immediately after the container starts */\n\tonProcessStarted?: ((pid?: number) => void) | undefined\n\t/** Additional environment variables to forward into the container */\n\tenvOverrides?: Record<string, string> | undefined\n\t/** Callback for server output when using pipe mode (for TUI). When provided, stdio is piped instead of inherited. */\n\tonOutput?: ((data: Buffer) => void) | undefined\n}\n\n/**\n * Utility function contracts from the docker-utils sibling issue.\n * Coded against these shapes so this class compiles without waiting for the sibling merge.\n */\ntype ParseDockerfileExposeFn = (path: string) => Promise<number | null>\ntype InspectImagePortsFn = (name: string) => Promise<number | null>\ntype BuildContainerNameFn = (id: string | number) => string\ntype BuildImageNameFn = (id: string | number) => string\ntype AssertDockerAvailableFn = () => Promise<void>\n\n/**\n * Injected docker utility functions.\n * Default implementations are imported from DockerManager for backward compatibility\n * until the dedicated docker-utils module is merged.\n */\nexport interface DockerUtils {\n\tparseDockerfileExpose: ParseDockerfileExposeFn\n\tinspectImagePorts: InspectImagePortsFn\n\tbuildContainerName: BuildContainerNameFn\n\tbuildImageName: BuildImageNameFn\n\tassertDockerAvailable: AssertDockerAvailableFn\n}\n\n/**\n * Attempt a single TCP connection to localhost:port.\n * Resolves true if the connection succeeds, false otherwise.\n */\nfunction tcpProbe(port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst socket = net.createConnection({ port, host: '127.0.0.1' })\n\t\tsocket.once('connect', () => {\n\t\t\tsocket.destroy()\n\t\t\tresolve(true)\n\t\t})\n\t\tsocket.once('error', () => {\n\t\t\tsocket.destroy()\n\t\t\tresolve(false)\n\t\t})\n\t})\n}\n\n/**\n * DockerDevServerStrategy handles the full Docker container lifecycle for a dev server:\n * - Image building\n * - Container running (detached and foreground)\n * - Container stopping\n * - Readiness detection via TCP probe\n * - Port resolution (3-tier: config > image inspect > Dockerfile EXPOSE)\n *\n * This class is the core Docker logic delegated to by DevServerManager.\n * It does NOT modify DevServerManager, ResourceCleanup, or CLI commands.\n */\nexport class DockerDevServerStrategy {\n\tprivate readonly utils: DockerUtils\n\n\tconstructor(_config: DockerConfig, utils: DockerUtils) {\n\t\tthis.utils = utils\n\t}\n\n\t/**\n\t * Resolve the container port using 3-tier fallback:\n\t * 1. config.containerPort (explicit)\n\t * 2. inspectImagePorts(imageName) (from built image)\n\t * 3. parseDockerfileExpose(dockerfilePath) (from Dockerfile)\n\t *\n\t * Throws a clear error if all three return null.\n\t *\n\t * @param config - Docker config (may override the constructor config)\n\t * @param imageName - Name of the built Docker image\n\t * @param dockerfilePath - Absolute path to the Dockerfile\n\t */\n\tasync resolveContainerPort(\n\t\tconfig: DockerConfig,\n\t\timageName: string,\n\t\tdockerfilePath: string\n\t): Promise<number> {\n\t\tif (config.containerPort !== undefined) {\n\t\t\treturn config.containerPort\n\t\t}\n\n\t\tconst inspectedPort = await this.utils.inspectImagePorts(imageName)\n\t\tif (inspectedPort !== null) {\n\t\t\tlogger.debug(`Auto-detected container port ${inspectedPort} from Docker image inspect`)\n\t\t\treturn inspectedPort\n\t\t}\n\n\t\tconst exposedPort = await this.utils.parseDockerfileExpose(dockerfilePath)\n\t\tif (exposedPort !== null) {\n\t\t\tlogger.debug(`Auto-detected container port ${exposedPort} from Dockerfile EXPOSE directive`)\n\t\t\treturn exposedPort\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t'Cannot determine container port. Set `devServer.docker.containerPort` in settings or add an `EXPOSE` directive to your Dockerfile.'\n\t\t)\n\t}\n\n\t/**\n\t * Build a Docker image for the worktree.\n\t * Build context is always the worktree root directory.\n\t *\n\t * @param worktreePath - Absolute path to the worktree (build context)\n\t * @param config - Docker config with Dockerfile path and build args\n\t */\n\tasync buildImage(worktreePath: string, config: DockerConfig): Promise<void> {\n\t\tconst imageName = this.utils.buildImageName(config.identifier ?? worktreePath)\n\t\tconst dockerfilePath = config.dockerFile ?? './Dockerfile'\n\n\t\tconst args = ['build', '-t', imageName, '-f', dockerfilePath]\n\n\t\tif (config.buildArgs) {\n\t\t\tfor (const [key, value] of Object.entries(config.buildArgs)) {\n\t\t\t\targs.push('--build-arg', `${key}=${value}`)\n\t\t\t}\n\t\t}\n\n\t\t// Mount secret files via BuildKit --secret flags\n\t\tconst expandedSecrets = expandAndValidateSecretPaths(config.buildSecrets, worktreePath)\n\t\tfor (const [id, srcPath] of Object.entries(expandedSecrets)) {\n\t\t\targs.push('--secret', `id=${id},src=${srcPath}`)\n\t\t}\n\n\t\t// Context is always the worktree root\n\t\targs.push('.')\n\n\t\tlogger.info(`Building Docker image \"${imageName}\" from ${dockerfilePath}...`)\n\n\t\tconst execaOptions: { cwd: string; stdio: 'inherit'; env?: Record<string, string> } = {\n\t\t\tcwd: worktreePath,\n\t\t\tstdio: 'inherit',\n\t\t}\n\n\t\t// Enable BuildKit when secrets are being used (required for --secret flag on older Docker versions)\n\t\tif (Object.keys(expandedSecrets).length > 0) {\n\t\t\texecaOptions.env = { ...process.env, DOCKER_BUILDKIT: '1' }\n\t\t}\n\n\t\ttry {\n\t\t\tawait execa('docker', args, execaOptions)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tthrow new Error(`Docker build failed for image \"${imageName}\": ${message}`)\n\t\t}\n\n\t\tlogger.success(`Docker image \"${imageName}\" built successfully`)\n\t}\n\n\t/**\n\t * Run a container in detached (background) mode.\n\t * Force-removes any existing container with the same name first.\n\t * Mounts the worktree at /app and adds an anonymous volume for node_modules.\n\t * Forwards PORT and any envOverrides into the container.\n\t *\n\t * @param worktreePath - Absolute path to the worktree (mounted at /app)\n\t * @param hostPort - Port on the host to map\n\t * @param containerPort - Port inside the container\n\t * @param config - Docker config with run args\n\t * @param envOverrides - Additional environment variables to set in the container\n\t * @returns The container name\n\t */\n\tasync runContainerDetached(\n\t\tworktreePath: string,\n\t\thostPort: number,\n\t\tcontainerPort: number,\n\t\tconfig: DockerConfig,\n\t\tenvOverrides?: Record<string, string>\n\t): Promise<string> {\n\t\tconst nameId = config.identifier ?? worktreePath\n\t\tconst imageName = this.utils.buildImageName(nameId)\n\t\tconst containerName = this.utils.buildContainerName(nameId)\n\n\t\t// Force-remove any existing container with same name\n\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\n\t\tconst args = [\n\t\t\t'run', '-d',\n\t\t\t'--name', containerName,\n\t\t\t'-p', `${hostPort}:${containerPort}`,\n\t\t\t// Mount worktree at /app\n\t\t\t'-v', `${worktreePath}:/app`,\n\t\t\t// Anonymous volume for node_modules to prevent host/container conflicts\n\t\t\t'-v', '/app/node_modules',\n\t\t\t// Forward PORT as the container port so the app listens where Docker expects.\n\t\t\t// The -p mapping handles host-to-container translation.\n\t\t\t'-e', `PORT=${containerPort}`,\n\t\t]\n\n\t\t// Forward additional environment variables\n\t\tif (envOverrides) {\n\t\t\tfor (const [key, value] of Object.entries(envOverrides)) {\n\t\t\t\targs.push('-e', `${key}=${value}`)\n\t\t\t}\n\t\t}\n\n\t\t// Additional run flags from config\n\t\tif (config.runArgs) {\n\t\t\targs.push(...config.runArgs)\n\t\t}\n\n\t\targs.push(imageName)\n\n\t\tconst displayProtocol = config.protocol ?? 'http'\n\t\tlogger.info(`Starting Docker container \"${containerName}\" in background (${displayProtocol}://localhost:${hostPort} → container:${containerPort})...`)\n\n\t\ttry {\n\t\t\tawait execa('docker', args)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tthrow new Error(`Failed to start Docker container \"${containerName}\": ${message}`)\n\t\t}\n\n\t\tlogger.success(`Docker container \"${containerName}\" started on port ${hostPort}`)\n\t\treturn containerName\n\t}\n\n\t/**\n\t * Run a container in foreground (blocking) mode.\n\t * The container is automatically removed on exit (--rm flag).\n\t * Traps SIGINT and SIGTERM and forwards them to the container via docker stop.\n\t *\n\t * @param worktreePath - Absolute path to the worktree (mounted at /app)\n\t * @param hostPort - Port on the host to map\n\t * @param containerPort - Port inside the container\n\t * @param config - Docker config with run args\n\t * @param opts - Additional options (redirectToStderr, onProcessStarted, envOverrides)\n\t * @returns Object with optional pid (Docker containers don't expose host PID)\n\t */\n\tasync runContainerForeground(\n\t\tworktreePath: string,\n\t\thostPort: number,\n\t\tcontainerPort: number,\n\t\tconfig: DockerConfig,\n\t\topts: RunForegroundOptions = {}\n\t): Promise<{ pid?: number }> {\n\t\tconst nameId = config.identifier ?? worktreePath\n\t\tconst imageName = this.utils.buildImageName(nameId)\n\t\tconst containerName = this.utils.buildContainerName(nameId)\n\t\tconst { redirectToStderr, onProcessStarted, envOverrides, onOutput } = opts\n\n\t\t// Force-remove any existing container with same name (stale from previous ungraceful exit)\n\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\n\t\tconst args = [\n\t\t\t'run', '--rm',\n\t\t\t'--name', containerName,\n\t\t\t'-p', `${hostPort}:${containerPort}`,\n\t\t\t// Mount worktree at /app\n\t\t\t'-v', `${worktreePath}:/app`,\n\t\t\t// Anonymous volume for node_modules to prevent host/container conflicts\n\t\t\t'-v', '/app/node_modules',\n\t\t\t// Forward PORT as the container port so the app listens where Docker expects.\n\t\t\t// The -p mapping handles host-to-container translation.\n\t\t\t'-e', `PORT=${containerPort}`,\n\t\t]\n\n\t\t// Forward additional environment variables\n\t\tif (envOverrides) {\n\t\t\tfor (const [key, value] of Object.entries(envOverrides)) {\n\t\t\t\targs.push('-e', `${key}=${value}`)\n\t\t\t}\n\t\t}\n\n\t\t// Additional run flags from config\n\t\tif (config.runArgs) {\n\t\t\targs.push(...config.runArgs)\n\t\t}\n\n\t\targs.push(imageName)\n\n\t\tconst displayProtocol = config.protocol ?? 'http'\n\t\tlogger.info(`Running Docker container \"${containerName}\" in foreground (${displayProtocol}://localhost:${hostPort} → container:${containerPort})...`)\n\n\t\t// Determine stdio based on mode:\n\t\t// - onOutput (TUI pipe mode): stdin ignored (TUI handles it), stdout/stderr piped to callback\n\t\t// - redirectToStderr: stdout/stderr -> process.stderr, stdin inherited\n\t\t// - default: inherit all stdio\n\t\tconst stdio = onOutput\n\t\t\t? (['ignore', 'pipe', 'pipe'] as const)\n\t\t\t: redirectToStderr\n\t\t\t\t? ([process.stdin, process.stderr, process.stderr] as const)\n\t\t\t\t: ('inherit' as const)\n\n\t\t// Signal forwarding: trap SIGINT/SIGTERM and forward to container\n\t\tconst forwardSignal = (): void => {\n\t\t\tlogger.debug(`Stopping container \"${containerName}\"`)\n\t\t\tvoid execa('docker', ['stop', containerName], { reject: false })\n\t\t}\n\n\t\tconst onSigint = (): void => forwardSignal()\n\t\tconst onSigterm = (): void => forwardSignal()\n\n\t\tprocess.on('SIGINT', onSigint)\n\t\tprocess.on('SIGTERM', onSigterm)\n\n\t\tif (onProcessStarted) {\n\t\t\tonProcessStarted(undefined)\n\t\t}\n\n\t\ttry {\n\t\t\tconst dockerProcess = execa('docker', args, { stdio })\n\n\t\t\t// When onOutput is provided, pipe stdout/stderr to the callback\n\t\t\tif (onOutput) {\n\t\t\t\tdockerProcess.stdout?.on('data', onOutput)\n\t\t\t\tdockerProcess.stderr?.on('data', onOutput)\n\t\t\t}\n\n\t\t\tawait dockerProcess\n\t\t} catch (error) {\n\t\t\tconst execaError = error as ExecaError\n\t\t\t// When the user presses Ctrl+C, the signal handler calls `docker stop`,\n\t\t\t// which causes `docker run` to exit with code 143 (128+SIGTERM) or 130\n\t\t\t// (128+SIGINT). Execa may also report the signal name directly. These\n\t\t\t// are all expected shutdown paths and should not surface as errors.\n\t\t\tconst isExpectedShutdown =\n\t\t\t\texecaError.exitCode === 143 ||\n\t\t\t\texecaError.exitCode === 130 ||\n\t\t\t\texecaError.signal === 'SIGTERM' ||\n\t\t\t\texecaError.signal === 'SIGINT'\n\t\t\tif (!isExpectedShutdown) {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t} finally {\n\t\t\t// Clean up signal handlers to avoid leaks\n\t\t\tprocess.removeListener('SIGINT', onSigint)\n\t\t\tprocess.removeListener('SIGTERM', onSigterm)\n\t\t\trestoreTerminalState()\n\t\t}\n\n\t\treturn {}\n\t}\n\n\t/**\n\t * Stop and remove a container by name.\n\t * Uses docker rm -f which handles both running and stopped containers atomically.\n\t * Handles already-stopped containers gracefully (no error thrown).\n\t *\n\t * @param containerName - Name of the container to stop and remove\n\t */\n\tasync stopContainer(containerName: string): Promise<void> {\n\t\tlogger.debug(`Stopping and removing container \"${containerName}\"...`)\n\t\tawait execa('docker', ['rm', '-f', containerName], { reject: false })\n\t\tlogger.debug(`Container \"${containerName}\" stopped and removed`)\n\t}\n\n\t/**\n\t * Check if a named container is currently running.\n\t * Uses exact name matching with anchored regex to avoid partial name matches.\n\t *\n\t * @param containerName - Name of the container to check\n\t * @returns true if the container is running, false otherwise\n\t */\n\tasync isContainerRunning(containerName: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst result = await execa('docker', [\n\t\t\t\t'ps',\n\t\t\t\t'--filter', `name=^${containerName}$`,\n\t\t\t\t'--format', '{{.Names}}',\n\t\t\t], { reject: false })\n\n\t\t\treturn result.exitCode === 0 && result.stdout.trim() === containerName\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Wait for the dev server to be ready by probing the TCP port.\n\t * Uses net.createConnection instead of lsof-based detection since Docker port\n\t * forwarding shows com.docker.backend as the listening process (not the dev server).\n\t * Exits early if the container has stopped (crash detection).\n\t *\n\t * @param port - Host port to probe\n\t * @param timeout - Maximum time to wait in milliseconds\n\t * @param interval - Interval between probes in milliseconds\n\t * @param containerName - Optional container name to monitor for early exit\n\t * @returns true if the port accepts connections within the timeout, false otherwise\n\t */\n\tasync waitForReady(port: number, timeout: number, interval: number, containerName?: string): Promise<boolean> {\n\t\tconst startTime = Date.now()\n\t\tlet attempts = 0\n\n\t\twhile (Date.now() - startTime < timeout) {\n\t\t\tattempts++\n\n\t\t\t// Early exit: if the container has stopped, stop polling\n\t\t\tif (containerName && attempts % 3 === 0) {\n\t\t\t\tconst stillRunning = await this.isContainerRunning(containerName)\n\t\t\t\tif (!stillRunning) {\n\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t`Docker container \"${containerName}\" exited before becoming ready (after ${attempts} attempts, ${Date.now() - startTime}ms)`\n\t\t\t\t\t)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst isReady = await tcpProbe(port)\n\t\t\tif (isReady) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tawait new Promise<void>((resolve) => globalThis.setTimeout(resolve, interval))\n\t\t}\n\n\t\treturn false\n\t}\n}\n","import { execa, type ExecaChildProcess, type ExecaError } from 'execa'\nimport { setTimeout } from 'timers/promises'\nimport { ProcessManager } from './process/ProcessManager.js'\nimport { buildDevServerCommand } from '../utils/dev-server.js'\nimport { runScript } from '../utils/package-manager.js'\nimport { getPackageScripts } from '../utils/package-json.js'\nimport { logger } from '../utils/logger.js'\nimport { restoreTerminalState } from '../utils/terminal.js'\nimport type { DevServerStrategy, ForegroundOpts } from './DevServerStrategy.js'\n\n/**\n * NativeDevServerStrategy implements DevServerStrategy for process-based dev servers.\n * This is the default mode — the dev server runs directly on the host as a child process.\n */\nexport class NativeDevServerStrategy implements DevServerStrategy {\n\tprivate readonly processManager: ProcessManager\n\tprivate readonly startupTimeout: number\n\tprivate readonly checkInterval: number\n\tprivate runningServers: Map<number, ExecaChildProcess> = new Map()\n\n\tconstructor(\n\t\tprocessManager: ProcessManager,\n\t\tstartupTimeout: number,\n\t\tcheckInterval: number\n\t) {\n\t\tthis.processManager = processManager\n\t\tthis.startupTimeout = startupTimeout\n\t\tthis.checkInterval = checkInterval\n\t}\n\n\tasync isRunning(port: number): Promise<boolean> {\n\t\tconst process = await this.processManager.detectDevServer(port)\n\t\treturn process !== null\n\t}\n\n\tasync startBackground(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\tenvOverrides?: Record<string, string>\n\t): Promise<void> {\n\t\t// Guard: Check if a dev script exists in package.json or package.iloom.json\n\t\tconst scripts = await getPackageScripts(worktreePath)\n\t\tif (!scripts['dev']) {\n\t\t\tlogger.warn('Skipping auto-start: no \"dev\" script found in package.json or package.iloom.json')\n\t\t\treturn\n\t\t}\n\n\t\t// Build dev server command\n\t\tconst devCommand = await buildDevServerCommand(worktreePath)\n\t\tlogger.debug(`Starting dev server with command: ${devCommand}`)\n\n\t\t// Start server in background\n\t\tconst serverProcess = execa('sh', ['-c', devCommand], {\n\t\t\tcwd: worktreePath,\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\t...envOverrides,\n\t\t\t\tPORT: port.toString(),\n\t\t\t},\n\t\t\t// Important: Don't inherit stdio - server runs in background\n\t\t\tstdio: 'ignore',\n\t\t\t// Detach from parent process so it continues running\n\t\t\tdetached: true,\n\t\t})\n\n\t\t// Store reference to prevent cleanup\n\t\tthis.runningServers.set(port, serverProcess)\n\n\t\t// Remove from map when process exits naturally or crashes\n\t\tserverProcess.on('exit', () => {\n\t\t\tthis.runningServers.delete(port)\n\t\t})\n\n\t\t// Unref so parent can exit\n\t\tserverProcess.unref()\n\n\t\t// Wait for server to be ready (pass process ref for early crash detection)\n\t\tlogger.info(`Waiting for dev server to start on port ${port}...`)\n\t\tconst ready = await this.waitForReady(port, serverProcess)\n\n\t\tif (!ready) {\n\t\t\tthrow new Error(\n\t\t\t\t`Dev server failed to start within ${this.startupTimeout}ms timeout`\n\t\t\t)\n\t\t}\n\n\t\tlogger.success(`Dev server started successfully on port ${port}`)\n\t}\n\n\tasync startForeground(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\topts: ForegroundOpts\n\t): Promise<{ pid?: number }> {\n\t\tconst { redirectToStderr = false, onProcessStarted, envOverrides, onOutput } = opts\n\n\t\tlogger.debug(`Starting dev server in foreground on port ${port}`)\n\n\t\tif (redirectToStderr || onOutput) {\n\t\t\t// For redirectToStderr or onOutput (TUI pipe mode), we need direct execa control for custom stdio\n\t\t\tconst devCommand = await buildDevServerCommand(worktreePath)\n\t\t\tlogger.debug(`Starting dev server with command: ${devCommand}`)\n\n\t\t\t// Determine stdio based on mode:\n\t\t\t// - redirectToStderr: stdout/stderr -> process.stderr, stdin inherited\n\t\t\t// - onOutput (TUI mode): stdin ignored (TUI handles it), stdout/stderr piped to callback\n\t\t\tconst stdio = onOutput\n\t\t\t\t? (['ignore', 'pipe', 'pipe'] as const)\n\t\t\t\t: ([process.stdin, process.stderr, process.stderr] as const)\n\n\t\t\tconst serverProcess = execa('sh', ['-c', devCommand], {\n\t\t\t\tcwd: worktreePath,\n\t\t\t\tenv: {\n\t\t\t\t\t...process.env,\n\t\t\t\t\t...envOverrides,\n\t\t\t\t\tPORT: port.toString(),\n\t\t\t\t},\n\t\t\t\tstdio,\n\t\t\t})\n\n\t\t\t// When onOutput is provided, pipe stdout/stderr to the callback\n\t\t\tif (onOutput) {\n\t\t\t\tserverProcess.stdout?.on('data', onOutput)\n\t\t\t\tserverProcess.stderr?.on('data', onOutput)\n\t\t\t}\n\n\t\t\tconst processInfo: { pid?: number } =\n\t\t\t\tserverProcess.pid !== undefined ? { pid: serverProcess.pid } : {}\n\n\t\t\tif (onProcessStarted) {\n\t\t\t\tonProcessStarted(processInfo.pid)\n\t\t\t}\n\n\t\t\t// Register no-op SIGINT handler to prevent signal-exit from re-raising SIGINT\n\t\t\t// before finally blocks can run, ensuring terminal state is restored on Ctrl+C.\n\t\t\tconst onSigint = (): void => {}\n\t\t\tprocess.on('SIGINT', onSigint)\n\n\t\t\ttry {\n\t\t\t\tawait serverProcess\n\t\t\t} catch (error) {\n\t\t\t\tconst execaError = error as ExecaError\n\t\t\t\t// If killed by SIGINT, the user intentionally cancelled — return silently\n\t\t\t\tif (execaError.signal !== 'SIGINT') {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tprocess.removeListener('SIGINT', onSigint)\n\t\t\t\trestoreTerminalState()\n\t\t\t}\n\n\t\t\treturn processInfo\n\t\t}\n\n\t\t// Use runScript for standard foreground mode\n\t\treturn await runScript('dev', worktreePath, [], {\n\t\t\tenv: {\n\t\t\t\t...envOverrides,\n\t\t\t\tPORT: port.toString(),\n\t\t\t},\n\t\t\tforeground: true,\n\t\t\t...(onProcessStarted && { onStart: onProcessStarted }),\n\t\t\t...(onOutput !== undefined ? { onOutput } : {}),\n\t\t\tnoCi: true, // Dev servers should not have CI=true\n\t\t})\n\t}\n\n\tasync stop(port: number): Promise<boolean> {\n\t\tconst serverProcess = this.runningServers.get(port)\n\t\tif (!serverProcess) {\n\t\t\treturn false\n\t\t}\n\n\t\ttry {\n\t\t\t// Kill the entire process group (negative PID) since the server is\n\t\t\t// spawned with detached:true via `sh -c`. Without this, only the\n\t\t\t// shell process receives the signal and the actual dev server\n\t\t\t// (node/vite/next) remains running as an orphan.\n\t\t\tif (serverProcess.pid) {\n\t\t\t\tprocess.kill(-serverProcess.pid, 'SIGTERM')\n\t\t\t} else {\n\t\t\t\tserverProcess.kill()\n\t\t\t}\n\t\t\tthis.runningServers.delete(port)\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tlogger.warn(\n\t\t\t\t`Failed to kill server process on port ${port}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Stop all tracked server processes. Called during cleanup.\n\t */\n\tasync stopAll(): Promise<void> {\n\t\tfor (const [port] of this.runningServers.entries()) {\n\t\t\tawait this.stop(port)\n\t\t}\n\t}\n\n\t/**\n\t * Wait for server to be ready by polling the port.\n\t * Exits early if the spawned process has already exited (crash detection).\n\t * Public so DevServerManager can reuse it for Docker mode readiness checks.\n\t *\n\t * @param port - Port to poll\n\t * @param processRef - Optional spawned process to monitor for early exit\n\t */\n\tasync waitForReady(port: number, processRef?: ExecaChildProcess): Promise<boolean> {\n\t\tconst startTime = Date.now()\n\t\tlet attempts = 0\n\n\t\twhile (Date.now() - startTime < this.startupTimeout) {\n\t\t\tattempts++\n\n\t\t\t// Early exit: if the spawned process has already exited, stop polling\n\t\t\t// Check both null and undefined since exitCode is undefined before the process exits\n\t\t\tif (processRef && processRef.exitCode != null) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Dev server process exited with code ${processRef.exitCode} before becoming ready (after ${attempts} attempts, ${Date.now() - startTime}ms)`\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst processInfo = await this.processManager.detectDevServer(port)\n\n\t\t\tif (processInfo) {\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`Server detected on port ${port} after ${attempts} attempts (${Date.now() - startTime}ms)`\n\t\t\t\t)\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tawait setTimeout(this.checkInterval)\n\t\t}\n\n\t\tlogger.warn(\n\t\t\t`Server did not start on port ${port} after ${this.startupTimeout}ms (${attempts} attempts)`\n\t\t)\n\t\treturn false\n\t}\n}\n"],"mappings":"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","names":["execa","process","execa"]}
|
|
@@ -105,6 +105,7 @@ async function installDependencies(cwd, frozen = true, quiet = false) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
async function runScript(scriptName, cwd, args = [], options = {}) {
|
|
108
|
+
var _a, _b;
|
|
108
109
|
const scripts = await getPackageScripts(cwd);
|
|
109
110
|
const scriptConfig = scripts[scriptName];
|
|
110
111
|
const isDebugMode = getLogger().isDebugEnabled();
|
|
@@ -118,10 +119,10 @@ async function runScript(scriptName, cwd, args = [], options = {}) {
|
|
|
118
119
|
if (!options.noCi) {
|
|
119
120
|
env.CI = "true";
|
|
120
121
|
}
|
|
121
|
-
const stdio = options.foreground ? "inherit" : options.quiet ? "pipe" : "inherit";
|
|
122
|
+
const stdio = options.onOutput ? ["ignore", "pipe", "pipe"] : options.foreground ? "inherit" : options.quiet ? "pipe" : "inherit";
|
|
122
123
|
const onSigint = () => {
|
|
123
124
|
};
|
|
124
|
-
if (options.foreground) {
|
|
125
|
+
if (options.foreground || options.onOutput) {
|
|
125
126
|
process.on("SIGINT", onSigint);
|
|
126
127
|
}
|
|
127
128
|
try {
|
|
@@ -131,8 +132,8 @@ async function runScript(scriptName, cwd, args = [], options = {}) {
|
|
|
131
132
|
execaProcess = execa("sh", ["-c", `${scriptConfig.command} "$@"`, "--", ...args], {
|
|
132
133
|
cwd,
|
|
133
134
|
stdio,
|
|
134
|
-
...!options.foreground && { timeout: 6e5 },
|
|
135
|
-
// No timeout for foreground mode
|
|
135
|
+
...!options.foreground && !options.onOutput && { timeout: 6e5 },
|
|
136
|
+
// No timeout for foreground/TUI mode
|
|
136
137
|
env,
|
|
137
138
|
verbose: isDebugMode
|
|
138
139
|
});
|
|
@@ -142,14 +143,18 @@ async function runScript(scriptName, cwd, args = [], options = {}) {
|
|
|
142
143
|
execaProcess = execa(packageManager, [...command, ...args], {
|
|
143
144
|
cwd,
|
|
144
145
|
stdio,
|
|
145
|
-
...!options.foreground && { timeout: 6e5 },
|
|
146
|
-
// No timeout for foreground mode
|
|
146
|
+
...!options.foreground && !options.onOutput && { timeout: 6e5 },
|
|
147
|
+
// No timeout for foreground/TUI mode
|
|
147
148
|
env,
|
|
148
149
|
verbose: isDebugMode
|
|
149
150
|
});
|
|
150
151
|
}
|
|
152
|
+
if (options.onOutput) {
|
|
153
|
+
(_a = execaProcess.stdout) == null ? void 0 : _a.on("data", options.onOutput);
|
|
154
|
+
(_b = execaProcess.stderr) == null ? void 0 : _b.on("data", options.onOutput);
|
|
155
|
+
}
|
|
151
156
|
const result = {};
|
|
152
|
-
if (options.foreground && execaProcess.pid !== void 0) {
|
|
157
|
+
if ((options.foreground || options.onOutput) && execaProcess.pid !== void 0) {
|
|
153
158
|
result.pid = execaProcess.pid;
|
|
154
159
|
}
|
|
155
160
|
if (options.onStart) {
|
|
@@ -159,13 +164,13 @@ async function runScript(scriptName, cwd, args = [], options = {}) {
|
|
|
159
164
|
return result;
|
|
160
165
|
} catch (error) {
|
|
161
166
|
const execaError = error;
|
|
162
|
-
if (options.foreground && execaError.signal === "SIGINT") {
|
|
167
|
+
if ((options.foreground || options.onOutput) && execaError.signal === "SIGINT") {
|
|
163
168
|
return {};
|
|
164
169
|
}
|
|
165
170
|
const stderr = execaError.stderr ?? execaError.message ?? "Unknown error";
|
|
166
171
|
throw new Error(`Failed to run script '${scriptName}': ${stderr}`);
|
|
167
172
|
} finally {
|
|
168
|
-
if (options.foreground) {
|
|
173
|
+
if (options.foreground || options.onOutput) {
|
|
169
174
|
process.removeListener("SIGINT", onSigint);
|
|
170
175
|
restoreTerminalState();
|
|
171
176
|
}
|
|
@@ -177,4 +182,4 @@ export {
|
|
|
177
182
|
installDependencies,
|
|
178
183
|
runScript
|
|
179
184
|
};
|
|
180
|
-
//# sourceMappingURL=chunk-
|
|
185
|
+
//# sourceMappingURL=chunk-OLJ54WGW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/package-manager.ts"],"sourcesContent":["import { execa, type ExecaError } from 'execa'\nimport { getLogger } from './logger-context.js'\nimport { getPackageScripts } from './package-json.js'\nimport { restoreTerminalState } from './terminal.js'\nimport fs from 'fs-extra'\nimport path from 'path'\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn'\n\n/**\n * Validate if a string is a supported package manager\n */\nfunction isValidPackageManager(manager: string): manager is PackageManager {\n return manager === 'pnpm' || manager === 'npm' || manager === 'yarn'\n}\n\n/**\n * Detect which package manager to use for a project\n * Checks in order:\n * 1. packageManager field in package.json (Node.js standard)\n * 2. Lock files (pnpm-lock.yaml, package-lock.json, yarn.lock)\n * 3. Installed package managers (system-wide check)\n * 4. Defaults to npm if all detection fails\n *\n * @param cwd Working directory to detect package manager in (defaults to process.cwd())\n * @returns The detected package manager, or 'npm' as default\n */\nexport async function detectPackageManager(cwd: string = process.cwd()): Promise<PackageManager> {\n // 1. Check packageManager field in package.json\n try {\n const packageJsonPath = path.join(cwd, 'package.json')\n if (await fs.pathExists(packageJsonPath)) {\n const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8')\n const packageJson = JSON.parse(packageJsonContent)\n\n if (packageJson.packageManager) {\n // Parse \"pnpm@8.15.0\" or \"pnpm@10.16.1+sha512...\" -> \"pnpm\"\n const manager = packageJson.packageManager.split('@')[0]\n if (isValidPackageManager(manager)) {\n getLogger().debug(`Detected package manager from package.json: ${manager}`)\n return manager\n }\n }\n }\n } catch (error) {\n // If package.json doesn't exist, is malformed, or unreadable, continue to next detection method\n getLogger().debug(`Could not read packageManager from package.json: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n\n // 2. Check lock files (priority: pnpm > npm > yarn)\n const lockFiles: Array<{ file: string; manager: PackageManager }> = [\n { file: 'pnpm-lock.yaml', manager: 'pnpm' },\n { file: 'package-lock.json', manager: 'npm' },\n { file: 'yarn.lock', manager: 'yarn' },\n ]\n\n for (const { file, manager } of lockFiles) {\n if (await fs.pathExists(path.join(cwd, file))) {\n getLogger().debug(`Detected package manager from lock file ${file}: ${manager}`)\n return manager\n }\n }\n\n // 3. Check installed package managers (original behavior)\n const managers: PackageManager[] = ['pnpm', 'npm', 'yarn']\n for (const manager of managers) {\n try {\n await execa(manager, ['--version'])\n getLogger().debug(`Detected installed package manager: ${manager}`)\n return manager\n } catch {\n // Continue to next manager\n }\n }\n\n // 4. Default to npm (always available in Node.js environments)\n getLogger().debug('No package manager detected, defaulting to npm')\n return 'npm'\n}\n\n/**\n * Install dependencies using the detected package manager\n * @param cwd Working directory to run install in\n * @param frozen Whether to use frozen lockfile (for production installs)\n * @param quiet Whether to suppress command output (default: false)\n * @returns true if installation succeeded, throws Error on failure\n */\nexport async function installDependencies(\n cwd: string,\n frozen: boolean = true,\n quiet: boolean = false\n): Promise<void> {\n // Check if working directory is provided\n if (!cwd) {\n getLogger().debug('Skipping dependency installation - no working directory provided')\n return\n }\n\n // Check for install script in package.iloom.json or package.json\n const scripts = await getPackageScripts(cwd)\n if (scripts.install) {\n getLogger().info('Installing dependencies with install script...')\n // runScript handles both iloom-config (shell execution) and package-manager (npm/pnpm/yarn) sources\n await runScript('install', cwd, [], { quiet })\n getLogger().success('Dependencies installed successfully')\n return\n }\n\n // Fall back to Node.js package manager detection for projects without install script\n const pkgPath = path.join(cwd, 'package.json')\n if (!(await fs.pathExists(pkgPath))) {\n getLogger().debug('Skipping dependency installation - no package.json found and no install script')\n return\n }\n\n const packageManager = await detectPackageManager(cwd)\n\n getLogger().info(`Installing dependencies with ${packageManager}...`)\n\n const args: string[] = ['install']\n\n // Add frozen lockfile flag based on package manager\n if (frozen) {\n switch (packageManager) {\n case 'pnpm':\n args.push('--frozen-lockfile')\n break\n case 'yarn':\n args.push('--frozen-lockfile')\n break\n case 'npm':\n args.shift() // Remove 'install'\n args.push('ci') // npm ci is equivalent to frozen lockfile\n break\n }\n }\n\n try {\n await execa(packageManager, args, {\n cwd,\n stdio: quiet ? 'pipe' : 'inherit',\n timeout: 300000, // 5 minute timeout for install\n })\n\n getLogger().success('Dependencies installed successfully')\n } catch (error) {\n const execaError = error as ExecaError\n const stderr = execaError.stderr ?? execaError.message ?? 'Unknown error'\n throw new Error(`Failed to install dependencies: ${stderr}`)\n }\n}\n\n/**\n * Options for running a script\n */\nexport interface RunScriptOptions {\n /** Suppress command output (default: false) */\n quiet?: boolean\n /** Custom environment variables merged with process.env */\n env?: Record<string, string>\n /** Use inherited stdio, return process info (default: false) */\n foreground?: boolean\n /** Callback when process starts, receives PID */\n onStart?: (pid?: number) => void\n /** Don't set CI=true (for dev servers, default: false) */\n noCi?: boolean\n /** Callback for server output when using pipe mode (for TUI). When provided, stdio is piped instead of inherited. */\n onOutput?: (data: Buffer) => void\n}\n\n/**\n * Run a package.json or iloom config script\n * Automatically detects whether to use package manager or direct shell execution\n * based on the script source.\n *\n * @param scriptName The script name from package.json or package.iloom.json\n * @param cwd Working directory\n * @param args Additional arguments to pass to the script\n * @param options Execution options\n * @returns Object with pid when foreground mode is enabled\n */\nexport async function runScript(\n scriptName: string,\n cwd: string,\n args: string[] = [],\n options: RunScriptOptions = {}\n): Promise<{ pid?: number }> {\n // Get scripts with source metadata\n const scripts = await getPackageScripts(cwd)\n const scriptConfig = scripts[scriptName]\n\n const isDebugMode = getLogger().isDebugEnabled()\n\n if (!scriptConfig) {\n throw new Error(`Script '${scriptName}' not found`)\n }\n\n // Build environment variables\n const env: Record<string, string> = {\n ...process.env as Record<string, string>,\n ...options.env,\n }\n\n // Add CI=true unless noCi is set\n if (!options.noCi) {\n env.CI = 'true'\n }\n\n // Determine stdio mode:\n // - onOutput (TUI pipe mode): stdin ignored, stdout/stderr piped to callback\n // - foreground: inherit all stdio\n // - quiet: pipe all stdio (suppress output)\n // - default: inherit\n const stdio = options.onOutput\n ? (['ignore', 'pipe', 'pipe'] as const)\n : options.foreground ? 'inherit' : (options.quiet ? 'pipe' : 'inherit')\n\n // For foreground mode, register a no-op SIGINT handler to prevent signal-exit\n // (used internally by execa) from re-raising SIGINT and killing the process\n // before finally blocks can run. This ensures terminal state is restored on Ctrl+C.\n const onSigint = (): void => {}\n if (options.foreground || options.onOutput) {\n process.on('SIGINT', onSigint)\n }\n\n try {\n let execaProcess\n\n if (scriptConfig.source === 'iloom-config') {\n // Execute directly as shell command (for non-Node.js projects)\n // Use \"$@\" pattern to properly handle argument escaping via the shell\n getLogger().debug(`Executing shell command: ${scriptConfig.command} with args: ${args.join(' ')}`)\n\n execaProcess = execa('sh', ['-c', `${scriptConfig.command} \"$@\"`, '--', ...args], {\n cwd,\n stdio,\n ...(!options.foreground && !options.onOutput && { timeout: 600000 }), // No timeout for foreground/TUI mode\n env,\n verbose: isDebugMode,\n })\n } else {\n // Execute via package manager (for Node.js projects)\n const packageManager = await detectPackageManager(cwd)\n const command = packageManager === 'npm' ? ['run', scriptName] : [scriptName]\n\n execaProcess = execa(packageManager, [...command, ...args], {\n cwd,\n stdio,\n ...(!options.foreground && !options.onOutput && { timeout: 600000 }), // No timeout for foreground/TUI mode\n env,\n verbose: isDebugMode,\n })\n }\n\n // When onOutput is provided, pipe stdout/stderr to the callback\n if (options.onOutput) {\n execaProcess.stdout?.on('data', options.onOutput)\n execaProcess.stderr?.on('data', options.onOutput)\n }\n\n // For foreground mode, get PID and call onStart callback immediately\n const result: { pid?: number } = {}\n if ((options.foreground || options.onOutput) && execaProcess.pid !== undefined) {\n result.pid = execaProcess.pid\n }\n\n // Call onStart callback if provided\n if (options.onStart) {\n options.onStart(result.pid)\n }\n\n // Wait for process to complete\n await execaProcess\n\n return result\n } catch (error) {\n const execaError = error as ExecaError\n // If the process was killed by SIGINT, the user intentionally cancelled — return silently\n if ((options.foreground || options.onOutput) && execaError.signal === 'SIGINT') {\n return {}\n }\n const stderr = execaError.stderr ?? execaError.message ?? 'Unknown error'\n throw new Error(`Failed to run script '${scriptName}': ${stderr}`)\n } finally {\n if (options.foreground || options.onOutput) {\n process.removeListener('SIGINT', onSigint)\n restoreTerminalState()\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isPRBranch,
|
|
12
12
|
isValidGitRepo,
|
|
13
13
|
parseWorktreeList
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KGOBNC5A.js";
|
|
15
15
|
import {
|
|
16
16
|
getLogger
|
|
17
17
|
} from "./chunk-FTYWGQFM.js";
|
|
@@ -323,7 +323,7 @@ var GitWorktreeManager = class {
|
|
|
323
323
|
*/
|
|
324
324
|
async findWorktreeForIssue(issueNumber) {
|
|
325
325
|
const worktrees = await this.listWorktrees({ porcelain: true });
|
|
326
|
-
const pattern = new RegExp(`(?:^|[/_-])issue
|
|
326
|
+
const pattern = new RegExp(`(?:^|[/_-])issue[-/]${issueNumber}(?:-|__|$)`, "i");
|
|
327
327
|
return worktrees.find((wt) => pattern.test(wt.branch)) ?? null;
|
|
328
328
|
}
|
|
329
329
|
/**
|
|
@@ -388,4 +388,4 @@ var GitWorktreeManager = class {
|
|
|
388
388
|
export {
|
|
389
389
|
GitWorktreeManager
|
|
390
390
|
};
|
|
391
|
-
//# sourceMappingURL=chunk-
|
|
391
|
+
//# sourceMappingURL=chunk-PPQ5LV7U.js.map
|