@iloom/cli 0.1.17 → 0.1.19
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/README.md +51 -6
- package/dist/ClaudeContextManager-JKR4WGNU.js +13 -0
- package/dist/ClaudeService-55DQGB7T.js +12 -0
- package/dist/{GitHubService-F7Z3XJOS.js → GitHubService-LWP4GKGH.js} +3 -3
- package/dist/{LoomLauncher-MODG2SEM.js → LoomLauncher-UMMLPIZO.js} +7 -7
- package/dist/{PromptTemplateManager-7FINLRDE.js → PromptTemplateManager-WII75TKH.js} +2 -2
- package/dist/README.md +755 -0
- package/dist/{SettingsManager-VAZF26S2.js → SettingsManager-SKLUVE3K.js} +6 -2
- package/dist/{add-issue-22JBNOML.js → add-issue-X56V3XPB.js} +23 -8
- package/dist/add-issue-X56V3XPB.js.map +1 -0
- package/dist/{chunk-Y7SAGNUT.js → chunk-DEPYQRRB.js} +2 -2
- package/dist/{chunk-WKEWRSDB.js → chunk-ELFT36PV.js} +3 -3
- package/dist/chunk-FXV24OYZ.js +83 -0
- package/dist/chunk-FXV24OYZ.js.map +1 -0
- package/dist/{chunk-HPJJSYNS.js → chunk-H5LDRGVK.js} +6 -8
- package/dist/{chunk-HPJJSYNS.js.map → chunk-H5LDRGVK.js.map} +1 -1
- package/dist/{chunk-QEPVTTHD.js → chunk-IO4WFTL2.js} +17 -11
- package/dist/chunk-IO4WFTL2.js.map +1 -0
- package/dist/{chunk-KQDEK2ZW.js → chunk-JXQXSC45.js} +41 -9
- package/dist/chunk-JXQXSC45.js.map +1 -0
- package/dist/{chunk-JQ7VOSTC.js → chunk-KOCQAD2E.js} +3 -3
- package/dist/{chunk-YYSKGAZT.js → chunk-LAPY6NAE.js} +17 -8
- package/dist/chunk-LAPY6NAE.js.map +1 -0
- package/dist/{chunk-O2QWO64Z.js → chunk-PV3GAXQO.js} +56 -3
- package/dist/chunk-PV3GAXQO.js.map +1 -0
- package/dist/chunk-PVAVNJKS.js +188 -0
- package/dist/chunk-PVAVNJKS.js.map +1 -0
- package/dist/{chunk-CP2NU2JC.js → chunk-Q2KYPAH2.js} +7 -7
- package/dist/{chunk-CP2NU2JC.js.map → chunk-Q2KYPAH2.js.map} +1 -1
- package/dist/{chunk-W3DQTW63.js → chunk-USVVV3FP.js} +4 -4
- package/dist/{chunk-SSR5AVRJ.js → chunk-VCMMAFXQ.js} +21 -8
- package/dist/chunk-VCMMAFXQ.js.map +1 -0
- package/dist/chunk-VVH3ANF2.js +307 -0
- package/dist/chunk-VVH3ANF2.js.map +1 -0
- package/dist/{chunk-JBH2ZYYZ.js → chunk-VYQLLHZ7.js} +22 -3
- package/dist/chunk-VYQLLHZ7.js.map +1 -0
- package/dist/{chunk-SJUQ2NDR.js → chunk-ZMNQBJUI.js} +24 -19
- package/dist/chunk-ZMNQBJUI.js.map +1 -0
- package/dist/{chunk-T7QPXANZ.js → chunk-ZWXJBSUW.js} +17 -17
- package/dist/chunk-ZWXJBSUW.js.map +1 -0
- package/dist/{cleanup-3LUWPSM7.js → cleanup-ZHROIBSQ.js} +12 -16
- package/dist/cleanup-ZHROIBSQ.js.map +1 -0
- package/dist/cli.js +107 -49
- package/dist/cli.js.map +1 -1
- package/dist/contribute-3MQJ3XAQ.js +256 -0
- package/dist/contribute-3MQJ3XAQ.js.map +1 -0
- package/dist/{enhance-XJIQHVPD.js → enhance-VGWUX474.js} +18 -8
- package/dist/enhance-VGWUX474.js.map +1 -0
- package/dist/{feedback-23CLXKFT.js → feedback-ZOUCCHN4.js} +8 -8
- package/dist/{finish-3CQZIULO.js → finish-QJSK6Z7J.js} +36 -313
- package/dist/finish-QJSK6Z7J.js.map +1 -0
- package/dist/{git-LVRZ57GJ.js → git-OUYMVYJX.js} +2 -2
- package/dist/{ignite-WXEF2ID5.js → ignite-HICLZEYU.js} +124 -9
- package/dist/ignite-HICLZEYU.js.map +1 -0
- package/dist/index.d.ts +794 -712
- package/dist/index.js +169 -36
- package/dist/index.js.map +1 -1
- package/dist/init-UMKNHNV5.js +339 -0
- package/dist/init-UMKNHNV5.js.map +1 -0
- package/dist/mcp/github-comment-server.js +12 -9
- package/dist/mcp/github-comment-server.js.map +1 -1
- package/dist/neon-helpers-ZVIRPKCI.js +10 -0
- package/dist/{open-X6BTENPV.js → open-ETZUFSE4.js} +15 -17
- package/dist/{open-X6BTENPV.js.map → open-ETZUFSE4.js.map} +1 -1
- package/dist/prompts/init-prompt.txt +748 -0
- package/dist/prompts/issue-prompt.txt +141 -9
- package/dist/rebase-KBWFDZCN.js +95 -0
- package/dist/rebase-KBWFDZCN.js.map +1 -0
- package/dist/remote-GJEZWRCC.js +14 -0
- package/dist/{run-2JCPQAX3.js → run-4SVQ3WEU.js} +15 -17
- package/dist/{run-2JCPQAX3.js.map → run-4SVQ3WEU.js.map} +1 -1
- package/dist/schema/settings.schema.json +51 -1
- package/dist/{start-LWVRBJ6S.js → start-CT2ZEFP2.js} +54 -53
- package/dist/{start-LWVRBJ6S.js.map → start-CT2ZEFP2.js.map} +1 -1
- package/dist/{test-git-XPF4SZXJ.js → test-git-MKZATGZN.js} +3 -3
- package/dist/{test-prefix-XGFXFAYN.js → test-prefix-ZNLWDI3K.js} +3 -3
- package/dist/{update-DN3FSNKY.js → update-4TDDUR5K.js} +10 -4
- package/dist/{update-DN3FSNKY.js.map → update-4TDDUR5K.js.map} +1 -1
- package/package.json +3 -2
- package/dist/ClaudeContextManager-XOSXQ67R.js +0 -13
- package/dist/ClaudeService-YSZ6EXWP.js +0 -12
- package/dist/NeonProvider-PAGPUH7F.js +0 -12
- package/dist/add-issue-22JBNOML.js.map +0 -1
- package/dist/chunk-37DYYFVK.js +0 -29
- package/dist/chunk-37DYYFVK.js.map +0 -1
- package/dist/chunk-F3XBU2R7.js +0 -110
- package/dist/chunk-F3XBU2R7.js.map +0 -1
- package/dist/chunk-JBH2ZYYZ.js.map +0 -1
- package/dist/chunk-KQDEK2ZW.js.map +0 -1
- package/dist/chunk-O2QWO64Z.js.map +0 -1
- package/dist/chunk-QEPVTTHD.js.map +0 -1
- package/dist/chunk-SJUQ2NDR.js.map +0 -1
- package/dist/chunk-SSR5AVRJ.js.map +0 -1
- package/dist/chunk-T7QPXANZ.js.map +0 -1
- package/dist/chunk-YYSKGAZT.js.map +0 -1
- package/dist/cleanup-3LUWPSM7.js.map +0 -1
- package/dist/enhance-XJIQHVPD.js.map +0 -1
- package/dist/env-MDFL4ZXL.js +0 -23
- package/dist/finish-3CQZIULO.js.map +0 -1
- package/dist/ignite-WXEF2ID5.js.map +0 -1
- package/dist/init-RHACUR4E.js +0 -123
- package/dist/init-RHACUR4E.js.map +0 -1
- /package/dist/{ClaudeContextManager-XOSXQ67R.js.map → ClaudeContextManager-JKR4WGNU.js.map} +0 -0
- /package/dist/{ClaudeService-YSZ6EXWP.js.map → ClaudeService-55DQGB7T.js.map} +0 -0
- /package/dist/{GitHubService-F7Z3XJOS.js.map → GitHubService-LWP4GKGH.js.map} +0 -0
- /package/dist/{LoomLauncher-MODG2SEM.js.map → LoomLauncher-UMMLPIZO.js.map} +0 -0
- /package/dist/{NeonProvider-PAGPUH7F.js.map → PromptTemplateManager-WII75TKH.js.map} +0 -0
- /package/dist/{PromptTemplateManager-7FINLRDE.js.map → SettingsManager-SKLUVE3K.js.map} +0 -0
- /package/dist/{chunk-Y7SAGNUT.js.map → chunk-DEPYQRRB.js.map} +0 -0
- /package/dist/{chunk-WKEWRSDB.js.map → chunk-ELFT36PV.js.map} +0 -0
- /package/dist/{chunk-JQ7VOSTC.js.map → chunk-KOCQAD2E.js.map} +0 -0
- /package/dist/{chunk-W3DQTW63.js.map → chunk-USVVV3FP.js.map} +0 -0
- /package/dist/{feedback-23CLXKFT.js.map → feedback-ZOUCCHN4.js.map} +0 -0
- /package/dist/{SettingsManager-VAZF26S2.js.map → git-OUYMVYJX.js.map} +0 -0
- /package/dist/{env-MDFL4ZXL.js.map → neon-helpers-ZVIRPKCI.js.map} +0 -0
- /package/dist/{git-LVRZ57GJ.js.map → remote-GJEZWRCC.js.map} +0 -0
- /package/dist/{test-git-XPF4SZXJ.js.map → test-git-MKZATGZN.js.map} +0 -0
- /package/dist/{test-prefix-XGFXFAYN.js.map → test-prefix-ZNLWDI3K.js.map} +0 -0
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
import {
|
|
3
3
|
AgentSettingsSchema,
|
|
4
4
|
CapabilitiesSettingsSchema,
|
|
5
|
+
DatabaseProvidersSettingsSchema,
|
|
5
6
|
IloomSettingsSchema,
|
|
7
|
+
NeonSettingsSchema,
|
|
6
8
|
SettingsManager,
|
|
7
9
|
WorkflowPermissionSchema,
|
|
8
10
|
WorkflowsSettingsSchema
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
10
12
|
import "./chunk-GEHQXLEI.js";
|
|
11
13
|
export {
|
|
12
14
|
AgentSettingsSchema,
|
|
13
15
|
CapabilitiesSettingsSchema,
|
|
16
|
+
DatabaseProvidersSettingsSchema,
|
|
14
17
|
IloomSettingsSchema,
|
|
18
|
+
NeonSettingsSchema,
|
|
15
19
|
SettingsManager,
|
|
16
20
|
WorkflowPermissionSchema,
|
|
17
21
|
WorkflowsSettingsSchema
|
|
18
22
|
};
|
|
19
|
-
//# sourceMappingURL=SettingsManager-
|
|
23
|
+
//# sourceMappingURL=SettingsManager-SKLUVE3K.js.map
|
|
@@ -6,24 +6,31 @@ import {
|
|
|
6
6
|
AgentManager
|
|
7
7
|
} from "./chunk-OC4H6HJD.js";
|
|
8
8
|
import "./chunk-YETJNRQM.js";
|
|
9
|
+
import {
|
|
10
|
+
getConfiguredRepoFromSettings,
|
|
11
|
+
hasMultipleRemotes
|
|
12
|
+
} from "./chunk-FXV24OYZ.js";
|
|
9
13
|
import {
|
|
10
14
|
GitHubService
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-ZWXJBSUW.js";
|
|
16
|
+
import "./chunk-JXQXSC45.js";
|
|
13
17
|
import "./chunk-PXZBAC2M.js";
|
|
14
18
|
import {
|
|
15
19
|
SettingsManager
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
17
21
|
import "./chunk-JNKJ7NJV.js";
|
|
18
|
-
import
|
|
22
|
+
import {
|
|
23
|
+
logger
|
|
24
|
+
} from "./chunk-GEHQXLEI.js";
|
|
19
25
|
|
|
20
26
|
// src/commands/add-issue.ts
|
|
21
27
|
var AddIssueCommand = class {
|
|
22
|
-
constructor(enhancementService) {
|
|
28
|
+
constructor(enhancementService, settingsManager) {
|
|
29
|
+
this.settingsManager = settingsManager ?? new SettingsManager();
|
|
23
30
|
this.enhancementService = enhancementService ?? new IssueEnhancementService(
|
|
24
31
|
new GitHubService(),
|
|
25
32
|
new AgentManager(),
|
|
26
|
-
|
|
33
|
+
this.settingsManager
|
|
27
34
|
);
|
|
28
35
|
}
|
|
29
36
|
/**
|
|
@@ -36,13 +43,21 @@ var AddIssueCommand = class {
|
|
|
36
43
|
*/
|
|
37
44
|
async execute(input) {
|
|
38
45
|
const { description } = input;
|
|
46
|
+
const settings = await this.settingsManager.loadSettings();
|
|
47
|
+
let repo;
|
|
48
|
+
const multipleRemotes = await hasMultipleRemotes();
|
|
49
|
+
if (multipleRemotes) {
|
|
50
|
+
repo = await getConfiguredRepoFromSettings(settings);
|
|
51
|
+
logger.info(`Using GitHub repository: ${repo}`);
|
|
52
|
+
}
|
|
39
53
|
if (!description || !this.enhancementService.validateDescription(description)) {
|
|
40
54
|
throw new Error("Description is required and must be more than 30 characters with at least 3 words");
|
|
41
55
|
}
|
|
42
56
|
const enhancedDescription = await this.enhancementService.enhanceDescription(description);
|
|
43
57
|
const result = await this.enhancementService.createEnhancedIssue(
|
|
44
58
|
description,
|
|
45
|
-
enhancedDescription
|
|
59
|
+
enhancedDescription,
|
|
60
|
+
repo
|
|
46
61
|
);
|
|
47
62
|
await this.enhancementService.waitForReviewAndOpen(result.number);
|
|
48
63
|
return result.number;
|
|
@@ -51,4 +66,4 @@ var AddIssueCommand = class {
|
|
|
51
66
|
export {
|
|
52
67
|
AddIssueCommand
|
|
53
68
|
};
|
|
54
|
-
//# sourceMappingURL=add-issue-
|
|
69
|
+
//# sourceMappingURL=add-issue-X56V3XPB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/add-issue.ts"],"sourcesContent":["import type { AddIssueOptions } from '../types/index.js'\nimport { IssueEnhancementService } from '../lib/IssueEnhancementService.js'\nimport { GitHubService } from '../lib/GitHubService.js'\nimport { AgentManager } from '../lib/AgentManager.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { getConfiguredRepoFromSettings, hasMultipleRemotes } from '../utils/remote.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Input structure for AddIssueCommand\n */\nexport interface AddIssueCommandInput {\n\tdescription: string\n\toptions: AddIssueOptions\n}\n\n/**\n * Command to create and enhance GitHub issues without creating workspaces.\n * This separates the \"document the work\" step from the \"start the work\" step.\n */\nexport class AddIssueCommand {\n\tprivate enhancementService: IssueEnhancementService\n\tprivate settingsManager: SettingsManager\n\n\tconstructor(enhancementService?: IssueEnhancementService, settingsManager?: SettingsManager) {\n\t\t// Use provided service or create default\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.enhancementService = enhancementService ?? new IssueEnhancementService(\n\t\t\tnew GitHubService(),\n\t\t\tnew AgentManager(),\n\t\t\tthis.settingsManager\n\t\t)\n\t}\n\n\t/**\n\t * Execute the add-issue command workflow:\n\t * 1. Validate description format\n\t * 2. Enhance description with Claude AI\n\t * 3. Create GitHub issue\n\t * 4. Wait for keypress and open browser for review\n\t * 5. Return issue number\n\t */\n\tpublic async execute(input: AddIssueCommandInput): Promise<number> {\n\t\tconst { description } = input\n\n\t\t// Step 0: Load settings and get configured repo for GitHub operations\n\t\tconst settings = await this.settingsManager.loadSettings()\n\t\tlet repo: string | undefined\n\n\t\tconst multipleRemotes = await hasMultipleRemotes()\n\t\tif (multipleRemotes) {\n\t\t\trepo = await getConfiguredRepoFromSettings(settings)\n\t\t\tlogger.info(`Using GitHub repository: ${repo}`)\n\t\t}\n\n\t\t// Step 1: Validate description format\n\t\tif (!description || !this.enhancementService.validateDescription(description)) {\n\t\t\tthrow new Error('Description is required and must be more than 30 characters with at least 3 words')\n\t\t}\n\n\t\t// Step 2: Enhance description using Claude AI\n\t\tconst enhancedDescription = await this.enhancementService.enhanceDescription(description)\n\n\t\t// Step 3: Create GitHub issue with original as title, enhanced as body\n\t\tconst result = await this.enhancementService.createEnhancedIssue(\n\t\t\tdescription,\n\t\t\tenhancedDescription,\n\t\t\trepo\n\t\t)\n\n\t\t// Step 4: Wait for keypress and open issue in browser for review\n\t\tawait this.enhancementService.waitForReviewAndOpen(result.number)\n\n\t\t// Step 5: Return issue number for reference\n\t\treturn result.number\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoBO,IAAM,kBAAN,MAAsB;AAAA,EAI5B,YAAY,oBAA8C,iBAAmC;AAE5F,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAC9D,SAAK,qBAAqB,sBAAsB,IAAI;AAAA,MACnD,IAAI,cAAc;AAAA,MAClB,IAAI,aAAa;AAAA,MACjB,KAAK;AAAA,IACN;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,QAAQ,OAA8C;AAClE,UAAM,EAAE,YAAY,IAAI;AAGxB,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa;AACzD,QAAI;AAEJ,UAAM,kBAAkB,MAAM,mBAAmB;AACjD,QAAI,iBAAiB;AACpB,aAAO,MAAM,8BAA8B,QAAQ;AACnD,aAAO,KAAK,4BAA4B,IAAI,EAAE;AAAA,IAC/C;AAGA,QAAI,CAAC,eAAe,CAAC,KAAK,mBAAmB,oBAAoB,WAAW,GAAG;AAC9E,YAAM,IAAI,MAAM,mFAAmF;AAAA,IACpG;AAGA,UAAM,sBAAsB,MAAM,KAAK,mBAAmB,mBAAmB,WAAW;AAGxF,UAAM,SAAS,MAAM,KAAK,mBAAmB;AAAA,MAC5C;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAGA,UAAM,KAAK,mBAAmB,qBAAqB,OAAO,MAAM;AAGhE,WAAO,OAAO;AAAA,EACf;AACD;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ClaudeService
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ELFT36PV.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
7
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -63,4 +63,4 @@ var ClaudeContextManager = class {
|
|
|
63
63
|
export {
|
|
64
64
|
ClaudeContextManager
|
|
65
65
|
};
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-DEPYQRRB.js.map
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
} from "./chunk-PXZBAC2M.js";
|
|
8
8
|
import {
|
|
9
9
|
PromptTemplateManager
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PVAVNJKS.js";
|
|
11
11
|
import {
|
|
12
12
|
SettingsManager
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
14
14
|
import {
|
|
15
15
|
logger
|
|
16
16
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -148,4 +148,4 @@ var ClaudeService = class {
|
|
|
148
148
|
export {
|
|
149
149
|
ClaudeService
|
|
150
150
|
};
|
|
151
|
-
//# sourceMappingURL=chunk-
|
|
151
|
+
//# sourceMappingURL=chunk-ELFT36PV.js.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/remote.ts
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
async function parseGitRemotes(cwd) {
|
|
6
|
+
const result = await execa("git", ["remote", "-v"], {
|
|
7
|
+
cwd: cwd ?? process.cwd(),
|
|
8
|
+
encoding: "utf8"
|
|
9
|
+
});
|
|
10
|
+
const lines = result.stdout.trim().split("\n");
|
|
11
|
+
const remoteMap = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+\((fetch|push)\)/);
|
|
14
|
+
if (!match) continue;
|
|
15
|
+
const name = match[1];
|
|
16
|
+
const url = match[2];
|
|
17
|
+
if (!name || !url) continue;
|
|
18
|
+
if (remoteMap.has(name)) continue;
|
|
19
|
+
const ownerRepo = extractOwnerRepoFromUrl(url);
|
|
20
|
+
if (!ownerRepo) continue;
|
|
21
|
+
remoteMap.set(name, {
|
|
22
|
+
name,
|
|
23
|
+
url,
|
|
24
|
+
owner: ownerRepo.owner,
|
|
25
|
+
repo: ownerRepo.repo
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return Array.from(remoteMap.values());
|
|
29
|
+
}
|
|
30
|
+
function extractOwnerRepoFromUrl(url) {
|
|
31
|
+
const cleanUrl = url.replace(/\.git$/, "");
|
|
32
|
+
const httpsMatch = cleanUrl.match(/https?:\/\/github\.com\/([^/]+)\/([^/]+)/);
|
|
33
|
+
if ((httpsMatch == null ? void 0 : httpsMatch[1]) && (httpsMatch == null ? void 0 : httpsMatch[2])) {
|
|
34
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
35
|
+
}
|
|
36
|
+
const sshMatch = cleanUrl.match(/git@github\.com:([^/]+)\/(.+)/);
|
|
37
|
+
if ((sshMatch == null ? void 0 : sshMatch[1]) && (sshMatch == null ? void 0 : sshMatch[2])) {
|
|
38
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
async function hasMultipleRemotes(cwd) {
|
|
43
|
+
const remotes = await parseGitRemotes(cwd);
|
|
44
|
+
return remotes.length > 1;
|
|
45
|
+
}
|
|
46
|
+
async function getConfiguredRepoFromSettings(settings, cwd) {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const remoteName = (_b = (_a = settings.issueManagement) == null ? void 0 : _a.github) == null ? void 0 : _b.remote;
|
|
49
|
+
if (!remoteName) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
'GitHub remote not configured. Run "il init" to configure which repository to use for GitHub operations.'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
await validateConfiguredRemote(remoteName, cwd);
|
|
55
|
+
const remotes = await parseGitRemotes(cwd);
|
|
56
|
+
const remote = remotes.find((r) => r.name === remoteName);
|
|
57
|
+
if (!remote) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Configured remote "${remoteName}" not found in git remotes. Run "il init" to reconfigure.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return `${remote.owner}/${remote.repo}`;
|
|
63
|
+
}
|
|
64
|
+
async function validateConfiguredRemote(remoteName, cwd) {
|
|
65
|
+
try {
|
|
66
|
+
await execa("git", ["remote", "get-url", remoteName], {
|
|
67
|
+
cwd: cwd ?? process.cwd(),
|
|
68
|
+
encoding: "utf8"
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Remote "${remoteName}" does not exist in git configuration. Run "il init" to reconfigure.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
parseGitRemotes,
|
|
79
|
+
hasMultipleRemotes,
|
|
80
|
+
getConfiguredRepoFromSettings,
|
|
81
|
+
validateConfiguredRemote
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=chunk-FXV24OYZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/remote.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Represents a parsed git remote\n */\nexport interface GitRemote {\n\tname: string\n\turl: string\n\towner: string\n\trepo: string\n}\n\n/**\n * Parse git remotes from `git remote -v` output\n * Deduplicates fetch/push entries and extracts owner/repo from URLs\n */\nexport async function parseGitRemotes(cwd?: string): Promise<GitRemote[]> {\n\tconst result = await execa('git', ['remote', '-v'], {\n\t\tcwd: cwd ?? process.cwd(),\n\t\tencoding: 'utf8',\n\t})\n\n\tconst lines = result.stdout.trim().split('\\n')\n\tconst remoteMap = new Map<string, GitRemote>()\n\n\tfor (const line of lines) {\n\t\t// Format: \"origin git@github.com:owner/repo.git (fetch)\"\n\t\t// Format: \"origin https://github.com/owner/repo.git (fetch)\"\n\t\tconst match = line.match(/^(\\S+)\\s+(\\S+)\\s+\\((fetch|push)\\)/)\n\t\tif (!match) continue\n\n\t\tconst name = match[1]\n\t\tconst url = match[2]\n\t\tif (!name || !url) continue\n\n\t\t// Skip if we already processed this remote\n\t\tif (remoteMap.has(name)) continue\n\n\t\t// Extract owner/repo from URL\n\t\tconst ownerRepo = extractOwnerRepoFromUrl(url)\n\t\tif (!ownerRepo) continue\n\n\t\tremoteMap.set(name, {\n\t\t\tname,\n\t\t\turl,\n\t\t\towner: ownerRepo.owner,\n\t\t\trepo: ownerRepo.repo,\n\t\t})\n\t}\n\n\treturn Array.from(remoteMap.values())\n}\n\n/**\n * Extract owner and repo from GitHub URL\n * Supports both HTTPS and SSH formats\n */\nfunction extractOwnerRepoFromUrl(url: string): { owner: string; repo: string } | null {\n\t// Remove .git suffix if present\n\tconst cleanUrl = url.replace(/\\.git$/, '')\n\n\t// HTTPS format: https://github.com/owner/repo\n\tconst httpsMatch = cleanUrl.match(/https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)/)\n\tif (httpsMatch?.[1] && httpsMatch?.[2]) {\n\t\treturn { owner: httpsMatch[1], repo: httpsMatch[2] }\n\t}\n\n\t// SSH format: git@github.com:owner/repo\n\tconst sshMatch = cleanUrl.match(/git@github\\.com:([^/]+)\\/(.+)/)\n\tif (sshMatch?.[1] && sshMatch?.[2]) {\n\t\treturn { owner: sshMatch[1], repo: sshMatch[2] }\n\t}\n\n\treturn null\n}\n\n/**\n * Check if repository has multiple remotes\n */\nexport async function hasMultipleRemotes(cwd?: string): Promise<boolean> {\n\tconst remotes = await parseGitRemotes(cwd)\n\treturn remotes.length > 1\n}\n\n/**\n * Get configured repository string from settings\n * Returns \"owner/repo\" format for use with gh CLI --repo flag\n * Throws if configured remote not found\n */\nexport async function getConfiguredRepoFromSettings(\n\tsettings: IloomSettings,\n\tcwd?: string,\n): Promise<string> {\n\tconst remoteName = settings.issueManagement?.github?.remote\n\n\tif (!remoteName) {\n\t\tthrow new Error(\n\t\t\t'GitHub remote not configured. Run \"il init\" to configure which repository to use for GitHub operations.',\n\t\t)\n\t}\n\n\t// Validate configured remote exists\n\tawait validateConfiguredRemote(remoteName, cwd)\n\n\t// Parse remotes and find the configured one\n\tconst remotes = await parseGitRemotes(cwd)\n\tconst remote = remotes.find((r) => r.name === remoteName)\n\n\tif (!remote) {\n\t\tthrow new Error(\n\t\t\t`Configured remote \"${remoteName}\" not found in git remotes. Run \"il init\" to reconfigure.`,\n\t\t)\n\t}\n\n\treturn `${remote.owner}/${remote.repo}`\n}\n\n/**\n * Validate that a remote exists in git config\n * Throws if remote doesn't exist\n */\nexport async function validateConfiguredRemote(remoteName: string, cwd?: string): Promise<void> {\n\ttry {\n\t\tawait execa('git', ['remote', 'get-url', remoteName], {\n\t\t\tcwd: cwd ?? process.cwd(),\n\t\t\tencoding: 'utf8',\n\t\t})\n\t} catch {\n\t\tthrow new Error(\n\t\t\t`Remote \"${remoteName}\" does not exist in git configuration. Run \"il init\" to reconfigure.`,\n\t\t)\n\t}\n}\n"],"mappings":";;;AAAA,SAAS,aAAa;AAiBtB,eAAsB,gBAAgB,KAAoC;AACzE,QAAM,SAAS,MAAM,MAAM,OAAO,CAAC,UAAU,IAAI,GAAG;AAAA,IACnD,KAAK,OAAO,QAAQ,IAAI;AAAA,IACxB,UAAU;AAAA,EACX,CAAC;AAED,QAAM,QAAQ,OAAO,OAAO,KAAK,EAAE,MAAM,IAAI;AAC7C,QAAM,YAAY,oBAAI,IAAuB;AAE7C,aAAW,QAAQ,OAAO;AAGzB,UAAM,QAAQ,KAAK,MAAM,mCAAmC;AAC5D,QAAI,CAAC,MAAO;AAEZ,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,MAAM,MAAM,CAAC;AACnB,QAAI,CAAC,QAAQ,CAAC,IAAK;AAGnB,QAAI,UAAU,IAAI,IAAI,EAAG;AAGzB,UAAM,YAAY,wBAAwB,GAAG;AAC7C,QAAI,CAAC,UAAW;AAEhB,cAAU,IAAI,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA,OAAO,UAAU;AAAA,MACjB,MAAM,UAAU;AAAA,IACjB,CAAC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,UAAU,OAAO,CAAC;AACrC;AAMA,SAAS,wBAAwB,KAAqD;AAErF,QAAM,WAAW,IAAI,QAAQ,UAAU,EAAE;AAGzC,QAAM,aAAa,SAAS,MAAM,0CAA0C;AAC5E,OAAI,yCAAa,QAAM,yCAAa,KAAI;AACvC,WAAO,EAAE,OAAO,WAAW,CAAC,GAAG,MAAM,WAAW,CAAC,EAAE;AAAA,EACpD;AAGA,QAAM,WAAW,SAAS,MAAM,+BAA+B;AAC/D,OAAI,qCAAW,QAAM,qCAAW,KAAI;AACnC,WAAO,EAAE,OAAO,SAAS,CAAC,GAAG,MAAM,SAAS,CAAC,EAAE;AAAA,EAChD;AAEA,SAAO;AACR;AAKA,eAAsB,mBAAmB,KAAgC;AACxE,QAAM,UAAU,MAAM,gBAAgB,GAAG;AACzC,SAAO,QAAQ,SAAS;AACzB;AAOA,eAAsB,8BACrB,UACA,KACkB;AA7FnB;AA8FC,QAAM,cAAa,oBAAS,oBAAT,mBAA0B,WAA1B,mBAAkC;AAErD,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAGA,QAAM,yBAAyB,YAAY,GAAG;AAG9C,QAAM,UAAU,MAAM,gBAAgB,GAAG;AACzC,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU;AAExD,MAAI,CAAC,QAAQ;AACZ,UAAM,IAAI;AAAA,MACT,sBAAsB,UAAU;AAAA,IACjC;AAAA,EACD;AAEA,SAAO,GAAG,OAAO,KAAK,IAAI,OAAO,IAAI;AACtC;AAMA,eAAsB,yBAAyB,YAAoB,KAA6B;AAC/F,MAAI;AACH,UAAM,MAAM,OAAO,CAAC,UAAU,WAAW,UAAU,GAAG;AAAA,MACrD,KAAK,OAAO,QAAQ,IAAI;AAAA,MACxB,UAAU;AAAA,IACX,CAAC;AAAA,EACF,QAAQ;AACP,UAAM,IAAI;AAAA,MACT,WAAW,UAAU;AAAA,IACtB;AAAA,EACD;AACD;","names":[]}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
calculatePortForBranch
|
|
4
|
-
|
|
3
|
+
calculatePortForBranch,
|
|
4
|
+
formatEnvLine,
|
|
5
|
+
parseEnvFile,
|
|
6
|
+
validateEnvVariable
|
|
7
|
+
} from "./chunk-ZMNQBJUI.js";
|
|
5
8
|
import {
|
|
6
9
|
runScript
|
|
7
10
|
} from "./chunk-BLCTGFZN.js";
|
|
@@ -9,11 +12,6 @@ import {
|
|
|
9
12
|
hasScript,
|
|
10
13
|
readPackageJson
|
|
11
14
|
} from "./chunk-2ZPFJQ3B.js";
|
|
12
|
-
import {
|
|
13
|
-
formatEnvLine,
|
|
14
|
-
parseEnvFile,
|
|
15
|
-
validateEnvVariable
|
|
16
|
-
} from "./chunk-SJUQ2NDR.js";
|
|
17
15
|
import {
|
|
18
16
|
createLogger,
|
|
19
17
|
logger
|
|
@@ -641,4 +639,4 @@ export {
|
|
|
641
639
|
CLIIsolationManager,
|
|
642
640
|
DatabaseManager
|
|
643
641
|
};
|
|
644
|
-
//# sourceMappingURL=chunk-
|
|
642
|
+
//# sourceMappingURL=chunk-H5LDRGVK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/EnvironmentManager.ts","../src/lib/CLIIsolationManager.ts","../src/lib/DatabaseManager.ts"],"sourcesContent":["import fs from 'fs-extra'\nimport { createLogger } from '../utils/logger.js'\nimport type {\n PortAssignmentOptions,\n} from '../types/environment.js'\nimport {\n parseEnvFile,\n formatEnvLine,\n validateEnvVariable,\n} from '../utils/env.js'\nimport { calculatePortForBranch } from '../utils/port.js'\n\nconst logger = createLogger({ prefix: '📝' })\n\nexport class EnvironmentManager {\n private readonly backupSuffix: string = '.backup'\n\n /**\n * Set or update an environment variable in a .env file\n * Ports functionality from bash/utils/env-utils.sh:setEnvVar()\n * @returns The backup path if a backup was created\n */\n async setEnvVar(\n filePath: string,\n key: string,\n value: string,\n backup: boolean = false\n ): Promise<string | void> {\n // Validate variable name\n const validation = validateEnvVariable(key, value)\n if (!validation.valid) {\n throw new Error(validation.error ?? 'Invalid variable name')\n }\n\n const fileExists = await fs.pathExists(filePath)\n\n if (!fileExists) {\n // File doesn't exist, create it\n logger.info(`Creating ${filePath} with ${key}...`)\n const content = formatEnvLine(key, value)\n await fs.writeFile(filePath, content, 'utf8')\n logger.success(`${filePath} created with ${key}`)\n return\n }\n\n // File exists, read and parse it\n const existingContent = await fs.readFile(filePath, 'utf8')\n const envMap = parseEnvFile(existingContent)\n\n // Create backup if requested\n let backupPath: string | undefined\n if (backup) {\n backupPath = await this.createBackup(filePath)\n }\n\n // Update or add the variable\n envMap.set(key, value)\n\n // Rebuild the file content, preserving comments and empty lines\n const lines = existingContent.split('\\n')\n const newLines: string[] = []\n let variableUpdated = false\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n // Preserve comments and empty lines\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n newLines.push(line)\n continue\n }\n\n // Remove 'export ' prefix if present\n const cleanLine = trimmedLine.startsWith('export ')\n ? trimmedLine.substring(7)\n : trimmedLine\n\n // Check if this line contains our variable\n const equalsIndex = cleanLine.indexOf('=')\n if (equalsIndex !== -1) {\n const lineKey = cleanLine.substring(0, equalsIndex).trim()\n if (lineKey === key) {\n // Replace this line with the new value\n newLines.push(formatEnvLine(key, value))\n variableUpdated = true\n continue\n }\n }\n\n // Keep other lines as-is\n newLines.push(line)\n }\n\n // If variable wasn't in the file, add it at the end\n if (!variableUpdated) {\n logger.info(`Adding ${key} to ${filePath}...`)\n newLines.push(formatEnvLine(key, value))\n logger.success(`${key} added successfully`)\n } else {\n logger.info(`Updating ${key} in ${filePath}...`)\n logger.success(`${key} updated successfully`)\n }\n\n // Write the updated content\n const newContent = newLines.join('\\n')\n await fs.writeFile(filePath, newContent, 'utf8')\n\n return backupPath\n }\n\n /**\n * Read and parse a .env file\n */\n async readEnvFile(filePath: string): Promise<Map<string, string>> {\n try {\n const content = await fs.readFile(filePath, 'utf8')\n return parseEnvFile(content)\n } catch (error) {\n // If file doesn't exist or can't be read, return empty map\n logger.debug(\n `Could not read env file ${filePath}: ${error instanceof Error ? error.message : String(error)}`\n )\n return new Map()\n }\n }\n\n /**\n * Generic file copy helper that only copies if source exists\n * Does not throw if source file doesn't exist - just logs and returns\n * @private\n */\n async copyIfExists(\n source: string,\n destination: string\n ): Promise<void> {\n const sourceExists = await fs.pathExists(source)\n if (!sourceExists) {\n logger.debug(`Source file ${source} does not exist, skipping copy`)\n return\n }\n\n await fs.copy(source, destination, { overwrite: false })\n logger.success(`Copied ${source} to ${destination}`)\n }\n\n /**\n * Calculate unique port for workspace\n * Implements:\n * - Issue/PR: 3000 + issue/PR number\n * - Branch: 3000 + deterministic hash offset (1-999)\n */\n calculatePort(options: PortAssignmentOptions): number {\n const basePort = options.basePort ?? 3000\n\n // Priority: issueNumber > prNumber > branchName > basePort only\n if (options.issueNumber !== undefined) {\n const port = basePort + options.issueNumber\n // Validate port range\n if (port > 65535) {\n throw new Error(\n `Calculated port ${port} exceeds maximum (65535). Use a lower base port or issue number.`\n )\n }\n return port\n }\n\n if (options.prNumber !== undefined) {\n const port = basePort + options.prNumber\n // Validate port range\n if (port > 65535) {\n throw new Error(\n `Calculated port ${port} exceeds maximum (65535). Use a lower base port or PR number.`\n )\n }\n return port\n }\n\n if (options.branchName !== undefined) {\n // Use deterministic hash for branch-based workspaces\n return calculatePortForBranch(options.branchName, basePort)\n }\n\n // Fallback: basePort only (no offset)\n return basePort\n }\n\n /**\n * Set port environment variable for workspace\n */\n async setPortForWorkspace(\n envFilePath: string,\n issueNumber?: number,\n prNumber?: number,\n branchName?: string\n ): Promise<number> {\n const options: PortAssignmentOptions = {}\n if (issueNumber !== undefined) {\n options.issueNumber = issueNumber\n }\n if (prNumber !== undefined) {\n options.prNumber = prNumber\n }\n if (branchName !== undefined) {\n options.branchName = branchName\n }\n const port = this.calculatePort(options)\n await this.setEnvVar(envFilePath, 'PORT', String(port))\n return port\n }\n\n /**\n * Validate environment configuration\n */\n async validateEnvFile(\n filePath: string\n ): Promise<{ valid: boolean; errors: string[] }> {\n try {\n const content = await fs.readFile(filePath, 'utf8')\n const envMap = parseEnvFile(content)\n const errors: string[] = []\n\n // Validate each variable name\n for (const [key, value] of envMap.entries()) {\n const validation = validateEnvVariable(key, value)\n if (!validation.valid) {\n errors.push(`${key}: ${validation.error}`)\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n } catch (error) {\n return {\n valid: false,\n errors: [\n `Failed to read or parse file: ${error instanceof Error ? error.message : String(error)}`,\n ],\n }\n }\n }\n\n /**\n * Create backup of existing file\n */\n private async createBackup(filePath: string): Promise<string> {\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-')\n const backupPath = `${filePath}${this.backupSuffix}-${timestamp}`\n await fs.copy(filePath, backupPath)\n logger.debug(`Created backup at ${backupPath}`)\n return backupPath\n }\n}\n","import fs from 'fs-extra'\nimport path from 'path'\nimport os from 'os'\nimport { runScript } from '../utils/package-manager.js'\nimport { readPackageJson, hasScript } from '../utils/package-json.js'\nimport { logger } from '../utils/logger.js'\n\nexport class CLIIsolationManager {\n private readonly iloomBinDir: string\n\n constructor() {\n this.iloomBinDir = path.join(os.homedir(), '.iloom', 'bin')\n }\n\n /**\n * Setup CLI isolation for a worktree\n * - Build the project\n * - Create versioned symlinks\n * - Check PATH configuration\n * @param worktreePath Path to the worktree\n * @param identifier Issue/PR number or branch identifier\n * @param binEntries Bin entries from package.json\n * @returns Array of created symlink names\n */\n async setupCLIIsolation(\n worktreePath: string,\n identifier: string | number,\n binEntries: Record<string, string>\n ): Promise<string[]> {\n // 1. Build the project\n await this.buildProject(worktreePath)\n\n // 2. Verify bin targets exist and are executable\n await this.verifyBinTargets(worktreePath, binEntries)\n\n // 3. Create ~/.iloom/bin if needed\n await fs.ensureDir(this.iloomBinDir)\n\n // 4. Create versioned symlinks\n const symlinkNames = await this.createVersionedSymlinks(\n worktreePath,\n identifier,\n binEntries\n )\n\n // 5. Check PATH and provide instructions if needed\n await this.ensureIloomBinInPath()\n\n return symlinkNames\n }\n\n /**\n * Build the project using package.json build script\n * @param worktreePath Path to the worktree\n */\n private async buildProject(worktreePath: string): Promise<void> {\n const pkgJson = await readPackageJson(worktreePath)\n\n if (!hasScript(pkgJson, 'build')) {\n logger.warn('No build script found in package.json - skipping build')\n return\n }\n\n logger.info('Building CLI tool...')\n await runScript('build', worktreePath)\n logger.success('Build completed')\n }\n\n /**\n * Verify bin targets exist and are executable\n * @param worktreePath Path to the worktree\n * @param binEntries Bin entries from package.json\n */\n private async verifyBinTargets(\n worktreePath: string,\n binEntries: Record<string, string>\n ): Promise<void> {\n for (const binPath of Object.values(binEntries)) {\n const targetPath = path.resolve(worktreePath, binPath)\n\n // Check if file exists\n const exists = await fs.pathExists(targetPath)\n if (!exists) {\n throw new Error(`Bin target does not exist: ${targetPath}`)\n }\n\n // Check if file is executable\n try {\n await fs.access(targetPath, fs.constants.X_OK)\n } catch {\n // File is not executable, but that's okay - symlink will work anyway\n // The shebang in the file will determine how it's executed\n }\n }\n }\n\n /**\n * Create versioned symlinks in ~/.iloom/bin\n * @param worktreePath Path to the worktree\n * @param identifier Issue/PR number or branch identifier\n * @param binEntries Bin entries from package.json\n * @returns Array of created symlink names\n */\n private async createVersionedSymlinks(\n worktreePath: string,\n identifier: string | number,\n binEntries: Record<string, string>\n ): Promise<string[]> {\n const symlinkNames: string[] = []\n\n for (const [binName, binPath] of Object.entries(binEntries)) {\n const versionedName = `${binName}-${identifier}`\n const targetPath = path.resolve(worktreePath, binPath)\n const symlinkPath = path.join(this.iloomBinDir, versionedName)\n\n // Create symlink\n await fs.symlink(targetPath, symlinkPath)\n\n logger.success(`CLI available: ${versionedName}`)\n symlinkNames.push(versionedName)\n }\n\n return symlinkNames\n }\n\n /**\n * Check if ~/.iloom/bin is in PATH and provide setup instructions\n */\n private async ensureIloomBinInPath(): Promise<void> {\n const currentPath = process.env.PATH ?? ''\n if (currentPath.includes('.iloom/bin')) {\n return // Already configured\n }\n\n // Detect shell and RC file\n const shell = this.detectShell()\n const rcFile = this.getShellRcFile(shell)\n\n // Print setup instructions\n logger.warn('\\n⚠️ One-time PATH setup required:')\n logger.warn(` Add to ${rcFile}:`)\n logger.warn(` export PATH=\"$HOME/.iloom/bin:$PATH\"`)\n logger.warn(` Then run: source ${rcFile}\\n`)\n }\n\n /**\n * Detect current shell\n * @returns Shell name (zsh, bash, fish, etc.)\n */\n private detectShell(): string {\n const shell = process.env.SHELL ?? ''\n return shell.split('/').pop() ?? 'bash'\n }\n\n /**\n * Get RC file path for shell\n * @param shell Shell name\n * @returns RC file path\n */\n private getShellRcFile(shell: string): string {\n const rcFiles: Record<string, string> = {\n zsh: '~/.zshrc',\n bash: '~/.bashrc',\n fish: '~/.config/fish/config.fish'\n }\n return rcFiles[shell] ?? '~/.bashrc'\n }\n\n /**\n * Cleanup versioned CLI executables for a specific identifier\n * Removes all symlinks matching the pattern: {binName}-{identifier}\n *\n * @param identifier - Issue/PR number or branch identifier\n * @returns Array of removed symlink names\n */\n async cleanupVersionedExecutables(identifier: string | number): Promise<string[]> {\n const removed: string[] = []\n\n try {\n const files = await fs.readdir(this.iloomBinDir)\n\n for (const file of files) {\n if (this.matchesIdentifier(file, identifier)) {\n const symlinkPath = path.join(this.iloomBinDir, file)\n\n try {\n await fs.unlink(symlinkPath)\n removed.push(file)\n } catch (error) {\n // Silently skip if symlink already gone (ENOENT)\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n removed.push(file)\n continue\n }\n\n // Log warning for other errors but continue cleanup\n logger.warn(\n `Failed to remove symlink ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n }\n } catch (error) {\n // Handle missing bin directory gracefully\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n logger.warn('No CLI executables directory found - nothing to cleanup')\n return []\n }\n\n // Re-throw unexpected errors\n throw error\n }\n\n if (removed.length > 0) {\n logger.success(`Removed CLI executables: ${removed.join(', ')}`)\n }\n\n return removed\n }\n\n /**\n * Find orphaned symlinks in ~/.iloom/bin\n * Returns symlinks that point to non-existent targets\n *\n * @returns Array of orphaned symlink information\n */\n async findOrphanedSymlinks(): Promise<Array<{ name: string; path: string; brokenTarget: string }>> {\n const orphaned: Array<{ name: string; path: string; brokenTarget: string }> = []\n\n try {\n const files = await fs.readdir(this.iloomBinDir)\n\n for (const file of files) {\n const symlinkPath = path.join(this.iloomBinDir, file)\n\n try {\n const stats = await fs.lstat(symlinkPath)\n\n if (stats.isSymbolicLink()) {\n const target = await fs.readlink(symlinkPath)\n\n // Check if target exists\n try {\n await fs.access(target)\n } catch {\n // Target doesn't exist - this is an orphaned symlink\n orphaned.push({\n name: file,\n path: symlinkPath,\n brokenTarget: target\n })\n }\n }\n } catch (error) {\n // Skip files we can't read\n logger.warn(\n `Failed to check symlink ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n } catch (error) {\n // Handle missing bin directory\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n return []\n }\n\n // Re-throw unexpected errors\n throw error\n }\n\n return orphaned\n }\n\n /**\n * Cleanup all orphaned symlinks\n * Removes symlinks that point to non-existent targets\n *\n * @returns Number of symlinks removed\n */\n async cleanupOrphanedSymlinks(): Promise<number> {\n const orphaned = await this.findOrphanedSymlinks()\n let removedCount = 0\n\n for (const symlink of orphaned) {\n try {\n await fs.unlink(symlink.path)\n removedCount++\n logger.success(`Removed orphaned symlink: ${symlink.name}`)\n } catch (error) {\n logger.warn(\n `Failed to remove orphaned symlink ${symlink.name}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n\n return removedCount\n }\n\n /**\n * Check if a filename matches the versioned pattern for an identifier\n * Pattern: {binName}-{identifier}\n *\n * @param fileName - Name of the file to check\n * @param identifier - Issue/PR number or branch identifier\n * @returns True if the filename matches the pattern\n */\n private matchesIdentifier(fileName: string, identifier: string | number): boolean {\n const suffix = `-${identifier}`\n return fileName.endsWith(suffix)\n }\n}\n","import type { DatabaseProvider } from '../types/index.js'\nimport { EnvironmentManager } from './EnvironmentManager.js'\nimport { createLogger } from '../utils/logger.js'\n\nconst logger = createLogger({ prefix: '🗂️' })\n\n/**\n * Database Manager - orchestrates database operations with conditional execution\n * Ports functionality from bash scripts with guard conditions:\n * 1. Database provider must be properly configured (provider.isConfigured())\n * 2. The worktree's .env file must contain the configured database URL variable (default: DATABASE_URL)\n *\n * This ensures database branching only occurs for projects that actually use databases\n */\nexport class DatabaseManager {\n constructor(\n private provider: DatabaseProvider,\n private environment: EnvironmentManager,\n private databaseUrlEnvVarName: string = 'DATABASE_URL'\n ) {\n // Debug: Show which database URL variable name is configured\n if (databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`🔧 DatabaseManager configured with custom variable: ${databaseUrlEnvVarName}`)\n } else {\n logger.debug('🔧 DatabaseManager using default variable: DATABASE_URL')\n }\n }\n\n /**\n * Get the configured database URL environment variable name\n */\n getConfiguredVariableName(): string {\n return this.databaseUrlEnvVarName\n }\n\n /**\n * Check if database branching should be used\n * Requires BOTH conditions:\n * 1. Database provider is properly configured (checked via provider.isConfigured())\n * 2. .env file contains the configured database URL variable\n */\n async shouldUseDatabaseBranching(envFilePath: string): Promise<boolean> {\n // Check for provider configuration\n if (!this.provider.isConfigured()) {\n logger.debug('Skipping database branching: Database provider not configured')\n return false\n }\n\n // Check if .env has the configured database URL variable\n const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(envFilePath)\n if (!hasDatabaseUrl) {\n logger.debug(\n 'Skipping database branching: configured database URL variable not found in .env file'\n )\n return false\n }\n\n return true\n }\n\n /**\n * Create database branch only if configured\n * Returns connection string if branch was created, null if skipped\n *\n * @param branchName - Name of the branch to create\n * @param envFilePath - Path to .env file for configuration checks\n * @param cwd - Optional working directory to run commands from\n */\n async createBranchIfConfigured(\n branchName: string,\n envFilePath: string,\n cwd?: string\n ): Promise<string | null> {\n // Guard condition: check if database branching should be used\n if (!(await this.shouldUseDatabaseBranching(envFilePath))) {\n return null\n }\n\n // Check CLI availability and authentication\n if (!(await this.provider.isCliAvailable())) {\n logger.warn('Skipping database branch creation: Neon CLI not available')\n logger.warn('Install with: npm install -g neonctl')\n return null\n }\n\n try {\n const isAuth = await this.provider.isAuthenticated(cwd)\n if (!isAuth) {\n logger.warn('Skipping database branch creation: Not authenticated with Neon CLI')\n logger.warn('Run: neon auth')\n return null\n }\n } catch (error) {\n // Authentication check failed with an unexpected error - surface it\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Database authentication check failed: ${errorMessage}`)\n throw error\n }\n\n try {\n // Create the branch (which checks for preview first)\n const connectionString = await this.provider.createBranch(branchName, undefined, cwd)\n logger.success(`Database branch ready: ${this.provider.sanitizeBranchName(branchName)}`)\n return connectionString\n } catch (error) {\n logger.error(\n `Failed to create database branch: ${error instanceof Error ? error.message : String(error)}`\n )\n throw error\n }\n }\n\n /**\n * Delete database branch only if configured\n * Returns result object indicating what happened\n *\n * @param branchName - Name of the branch to delete\n * @param shouldCleanup - Boolean indicating if database cleanup should be performed (pre-fetched config)\n * @param isPreview - Whether this is a preview database branch\n * @param cwd - Optional working directory to run commands from (prevents issues with deleted directories)\n */\n async deleteBranchIfConfigured(\n branchName: string,\n shouldCleanup: boolean,\n isPreview: boolean = false,\n cwd?: string\n ): Promise<import('../types/index.js').DatabaseDeletionResult> {\n // If shouldCleanup is explicitly false, skip immediately\n if (shouldCleanup === false) {\n return {\n success: true,\n deleted: false,\n notFound: true, // Treat \"not configured\" as \"nothing to delete\"\n branchName\n }\n }\n\n // If shouldCleanup is explicitly true, validate provider configuration\n if (!this.provider.isConfigured()) {\n logger.debug('Skipping database branch deletion: Database provider not configured')\n return {\n success: true,\n deleted: false,\n notFound: true,\n branchName\n }\n }\n\n // Check CLI availability and authentication\n if (!(await this.provider.isCliAvailable())) {\n logger.info('Skipping database branch deletion: CLI tool not available')\n return {\n success: false,\n deleted: false,\n notFound: true,\n error: \"CLI tool not available\",\n branchName\n }\n }\n\n try {\n const isAuth = await this.provider.isAuthenticated(cwd)\n if (!isAuth) {\n logger.warn('Skipping database branch deletion: Not authenticated with DB Provider')\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: \"Not authenticated with DB Provider\",\n branchName\n }\n }\n } catch (error) {\n // Authentication check failed with an unexpected error - surface it\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Database authentication check failed: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: `Authentication check failed: ${errorMessage}`,\n branchName\n }\n }\n\n try {\n // Call provider and return its result directly\n const result = await this.provider.deleteBranch(branchName, isPreview, cwd)\n return result\n } catch (error) {\n // Unexpected error (shouldn't happen since provider returns result object)\n logger.warn(\n `Unexpected error in database deletion: ${error instanceof Error ? error.message : String(error)}`\n )\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: error instanceof Error ? error.message : String(error),\n branchName\n }\n }\n }\n\n /**\n * Check if .env has the configured database URL variable\n * CRITICAL: If user explicitly configured a custom variable name (not default),\n * throw an error if it's missing from .env\n */\n private async hasDatabaseUrlInEnv(envFilePath: string): Promise<boolean> {\n try {\n const envMap = await this.environment.readEnvFile(envFilePath)\n\n // Debug: Show what we're looking for\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`)\n } else {\n logger.debug('Looking for default database URL variable: DATABASE_URL')\n }\n\n // Check configured variable first\n if (envMap.has(this.databaseUrlEnvVarName)) {\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`✅ Found custom database URL variable: ${this.databaseUrlEnvVarName}`)\n } else {\n logger.debug(`✅ Found default database URL variable: DATABASE_URL`)\n }\n return true\n }\n\n // If user explicitly configured a custom variable name (not the default)\n // and it's missing, throw an error\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`❌ Custom database URL variable '${this.databaseUrlEnvVarName}' not found in .env file`)\n throw new Error(\n `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in .env file. ` +\n `Please add it to your .env file or update your iloom configuration.`\n )\n }\n\n // Fall back to DATABASE_URL when using default configuration\n const hasDefaultVar = envMap.has('DATABASE_URL')\n if (hasDefaultVar) {\n logger.debug('✅ Found fallback DATABASE_URL variable')\n } else {\n logger.debug('❌ No DATABASE_URL variable found in .env file')\n }\n return hasDefaultVar\n } catch (error) {\n // Re-throw configuration errors\n if (error instanceof Error && error.message.includes('not found in .env')) {\n throw error\n }\n // Return false for file read errors\n return false\n }\n }\n}\n"],"mappings":"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","names":["logger","fs","fs","logger"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/EnvironmentManager.ts","../src/lib/CLIIsolationManager.ts","../src/lib/DatabaseManager.ts"],"sourcesContent":["import fs from 'fs-extra'\nimport { createLogger } from '../utils/logger.js'\nimport type {\n PortAssignmentOptions,\n} from '../types/environment.js'\nimport {\n parseEnvFile,\n formatEnvLine,\n validateEnvVariable,\n} from '../utils/env.js'\nimport { calculatePortForBranch } from '../utils/port.js'\n\nconst logger = createLogger({ prefix: '📝' })\n\nexport class EnvironmentManager {\n private readonly backupSuffix: string = '.backup'\n\n /**\n * Set or update an environment variable in a .env file\n * Ports functionality from bash/utils/env-utils.sh:setEnvVar()\n * @returns The backup path if a backup was created\n */\n async setEnvVar(\n filePath: string,\n key: string,\n value: string,\n backup: boolean = false\n ): Promise<string | void> {\n // Validate variable name\n const validation = validateEnvVariable(key, value)\n if (!validation.valid) {\n throw new Error(validation.error ?? 'Invalid variable name')\n }\n\n const fileExists = await fs.pathExists(filePath)\n\n if (!fileExists) {\n // File doesn't exist, create it\n logger.info(`Creating ${filePath} with ${key}...`)\n const content = formatEnvLine(key, value)\n await fs.writeFile(filePath, content, 'utf8')\n logger.success(`${filePath} created with ${key}`)\n return\n }\n\n // File exists, read and parse it\n const existingContent = await fs.readFile(filePath, 'utf8')\n const envMap = parseEnvFile(existingContent)\n\n // Create backup if requested\n let backupPath: string | undefined\n if (backup) {\n backupPath = await this.createBackup(filePath)\n }\n\n // Update or add the variable\n envMap.set(key, value)\n\n // Rebuild the file content, preserving comments and empty lines\n const lines = existingContent.split('\\n')\n const newLines: string[] = []\n let variableUpdated = false\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n // Preserve comments and empty lines\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n newLines.push(line)\n continue\n }\n\n // Remove 'export ' prefix if present\n const cleanLine = trimmedLine.startsWith('export ')\n ? trimmedLine.substring(7)\n : trimmedLine\n\n // Check if this line contains our variable\n const equalsIndex = cleanLine.indexOf('=')\n if (equalsIndex !== -1) {\n const lineKey = cleanLine.substring(0, equalsIndex).trim()\n if (lineKey === key) {\n // Replace this line with the new value\n newLines.push(formatEnvLine(key, value))\n variableUpdated = true\n continue\n }\n }\n\n // Keep other lines as-is\n newLines.push(line)\n }\n\n // If variable wasn't in the file, add it at the end\n if (!variableUpdated) {\n logger.info(`Adding ${key} to ${filePath}...`)\n newLines.push(formatEnvLine(key, value))\n logger.success(`${key} added successfully`)\n } else {\n logger.info(`Updating ${key} in ${filePath}...`)\n logger.success(`${key} updated successfully`)\n }\n\n // Write the updated content\n const newContent = newLines.join('\\n')\n await fs.writeFile(filePath, newContent, 'utf8')\n\n return backupPath\n }\n\n /**\n * Read and parse a .env file\n */\n async readEnvFile(filePath: string): Promise<Map<string, string>> {\n try {\n const content = await fs.readFile(filePath, 'utf8')\n return parseEnvFile(content)\n } catch (error) {\n // If file doesn't exist or can't be read, return empty map\n logger.debug(\n `Could not read env file ${filePath}: ${error instanceof Error ? error.message : String(error)}`\n )\n return new Map()\n }\n }\n\n /**\n * Generic file copy helper that only copies if source exists\n * Does not throw if source file doesn't exist - just logs and returns\n * @private\n */\n async copyIfExists(\n source: string,\n destination: string\n ): Promise<void> {\n const sourceExists = await fs.pathExists(source)\n if (!sourceExists) {\n logger.debug(`Source file ${source} does not exist, skipping copy`)\n return\n }\n\n await fs.copy(source, destination, { overwrite: false })\n logger.success(`Copied ${source} to ${destination}`)\n }\n\n /**\n * Calculate unique port for workspace\n * Implements:\n * - Issue/PR: 3000 + issue/PR number\n * - Branch: 3000 + deterministic hash offset (1-999)\n */\n calculatePort(options: PortAssignmentOptions): number {\n const basePort = options.basePort ?? 3000\n\n // Priority: issueNumber > prNumber > branchName > basePort only\n if (options.issueNumber !== undefined) {\n const port = basePort + options.issueNumber\n // Validate port range\n if (port > 65535) {\n throw new Error(\n `Calculated port ${port} exceeds maximum (65535). Use a lower base port or issue number.`\n )\n }\n return port\n }\n\n if (options.prNumber !== undefined) {\n const port = basePort + options.prNumber\n // Validate port range\n if (port > 65535) {\n throw new Error(\n `Calculated port ${port} exceeds maximum (65535). Use a lower base port or PR number.`\n )\n }\n return port\n }\n\n if (options.branchName !== undefined) {\n // Use deterministic hash for branch-based workspaces\n return calculatePortForBranch(options.branchName, basePort)\n }\n\n // Fallback: basePort only (no offset)\n return basePort\n }\n\n /**\n * Set port environment variable for workspace\n */\n async setPortForWorkspace(\n envFilePath: string,\n issueNumber?: number,\n prNumber?: number,\n branchName?: string\n ): Promise<number> {\n const options: PortAssignmentOptions = {}\n if (issueNumber !== undefined) {\n options.issueNumber = issueNumber\n }\n if (prNumber !== undefined) {\n options.prNumber = prNumber\n }\n if (branchName !== undefined) {\n options.branchName = branchName\n }\n const port = this.calculatePort(options)\n await this.setEnvVar(envFilePath, 'PORT', String(port))\n return port\n }\n\n /**\n * Validate environment configuration\n */\n async validateEnvFile(\n filePath: string\n ): Promise<{ valid: boolean; errors: string[] }> {\n try {\n const content = await fs.readFile(filePath, 'utf8')\n const envMap = parseEnvFile(content)\n const errors: string[] = []\n\n // Validate each variable name\n for (const [key, value] of envMap.entries()) {\n const validation = validateEnvVariable(key, value)\n if (!validation.valid) {\n errors.push(`${key}: ${validation.error}`)\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n } catch (error) {\n return {\n valid: false,\n errors: [\n `Failed to read or parse file: ${error instanceof Error ? error.message : String(error)}`,\n ],\n }\n }\n }\n\n /**\n * Create backup of existing file\n */\n private async createBackup(filePath: string): Promise<string> {\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-')\n const backupPath = `${filePath}${this.backupSuffix}-${timestamp}`\n await fs.copy(filePath, backupPath)\n logger.debug(`Created backup at ${backupPath}`)\n return backupPath\n }\n}\n","import fs from 'fs-extra'\nimport path from 'path'\nimport os from 'os'\nimport { runScript } from '../utils/package-manager.js'\nimport { readPackageJson, hasScript } from '../utils/package-json.js'\nimport { logger } from '../utils/logger.js'\n\nexport class CLIIsolationManager {\n private readonly iloomBinDir: string\n\n constructor() {\n this.iloomBinDir = path.join(os.homedir(), '.iloom', 'bin')\n }\n\n /**\n * Setup CLI isolation for a worktree\n * - Build the project\n * - Create versioned symlinks\n * - Check PATH configuration\n * @param worktreePath Path to the worktree\n * @param identifier Issue/PR number or branch identifier\n * @param binEntries Bin entries from package.json\n * @returns Array of created symlink names\n */\n async setupCLIIsolation(\n worktreePath: string,\n identifier: string | number,\n binEntries: Record<string, string>\n ): Promise<string[]> {\n // 1. Build the project\n await this.buildProject(worktreePath)\n\n // 2. Verify bin targets exist and are executable\n await this.verifyBinTargets(worktreePath, binEntries)\n\n // 3. Create ~/.iloom/bin if needed\n await fs.ensureDir(this.iloomBinDir)\n\n // 4. Create versioned symlinks\n const symlinkNames = await this.createVersionedSymlinks(\n worktreePath,\n identifier,\n binEntries\n )\n\n // 5. Check PATH and provide instructions if needed\n await this.ensureIloomBinInPath()\n\n return symlinkNames\n }\n\n /**\n * Build the project using package.json build script\n * @param worktreePath Path to the worktree\n */\n private async buildProject(worktreePath: string): Promise<void> {\n const pkgJson = await readPackageJson(worktreePath)\n\n if (!hasScript(pkgJson, 'build')) {\n logger.warn('No build script found in package.json - skipping build')\n return\n }\n\n logger.info('Building CLI tool...')\n await runScript('build', worktreePath)\n logger.success('Build completed')\n }\n\n /**\n * Verify bin targets exist and are executable\n * @param worktreePath Path to the worktree\n * @param binEntries Bin entries from package.json\n */\n private async verifyBinTargets(\n worktreePath: string,\n binEntries: Record<string, string>\n ): Promise<void> {\n for (const binPath of Object.values(binEntries)) {\n const targetPath = path.resolve(worktreePath, binPath)\n\n // Check if file exists\n const exists = await fs.pathExists(targetPath)\n if (!exists) {\n throw new Error(`Bin target does not exist: ${targetPath}`)\n }\n\n // Check if file is executable\n try {\n await fs.access(targetPath, fs.constants.X_OK)\n } catch {\n // File is not executable, but that's okay - symlink will work anyway\n // The shebang in the file will determine how it's executed\n }\n }\n }\n\n /**\n * Create versioned symlinks in ~/.iloom/bin\n * @param worktreePath Path to the worktree\n * @param identifier Issue/PR number or branch identifier\n * @param binEntries Bin entries from package.json\n * @returns Array of created symlink names\n */\n private async createVersionedSymlinks(\n worktreePath: string,\n identifier: string | number,\n binEntries: Record<string, string>\n ): Promise<string[]> {\n const symlinkNames: string[] = []\n\n for (const [binName, binPath] of Object.entries(binEntries)) {\n const versionedName = `${binName}-${identifier}`\n const targetPath = path.resolve(worktreePath, binPath)\n const symlinkPath = path.join(this.iloomBinDir, versionedName)\n\n // Create symlink\n await fs.symlink(targetPath, symlinkPath)\n\n logger.success(`CLI available: ${versionedName}`)\n symlinkNames.push(versionedName)\n }\n\n return symlinkNames\n }\n\n /**\n * Check if ~/.iloom/bin is in PATH and provide setup instructions\n */\n private async ensureIloomBinInPath(): Promise<void> {\n const currentPath = process.env.PATH ?? ''\n if (currentPath.includes('.iloom/bin')) {\n return // Already configured\n }\n\n // Detect shell and RC file\n const shell = this.detectShell()\n const rcFile = this.getShellRcFile(shell)\n\n // Print setup instructions\n logger.warn('\\n⚠️ One-time PATH setup required:')\n logger.warn(` Add to ${rcFile}:`)\n logger.warn(` export PATH=\"$HOME/.iloom/bin:$PATH\"`)\n logger.warn(` Then run: source ${rcFile}\\n`)\n }\n\n /**\n * Detect current shell\n * @returns Shell name (zsh, bash, fish, etc.)\n */\n private detectShell(): string {\n const shell = process.env.SHELL ?? ''\n return shell.split('/').pop() ?? 'bash'\n }\n\n /**\n * Get RC file path for shell\n * @param shell Shell name\n * @returns RC file path\n */\n private getShellRcFile(shell: string): string {\n const rcFiles: Record<string, string> = {\n zsh: '~/.zshrc',\n bash: '~/.bashrc',\n fish: '~/.config/fish/config.fish'\n }\n return rcFiles[shell] ?? '~/.bashrc'\n }\n\n /**\n * Cleanup versioned CLI executables for a specific identifier\n * Removes all symlinks matching the pattern: {binName}-{identifier}\n *\n * @param identifier - Issue/PR number or branch identifier\n * @returns Array of removed symlink names\n */\n async cleanupVersionedExecutables(identifier: string | number): Promise<string[]> {\n const removed: string[] = []\n\n try {\n const files = await fs.readdir(this.iloomBinDir)\n\n for (const file of files) {\n if (this.matchesIdentifier(file, identifier)) {\n const symlinkPath = path.join(this.iloomBinDir, file)\n\n try {\n await fs.unlink(symlinkPath)\n removed.push(file)\n } catch (error) {\n // Silently skip if symlink already gone (ENOENT)\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n removed.push(file)\n continue\n }\n\n // Log warning for other errors but continue cleanup\n logger.warn(\n `Failed to remove symlink ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n }\n } catch (error) {\n // Handle missing bin directory gracefully\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n logger.warn('No CLI executables directory found - nothing to cleanup')\n return []\n }\n\n // Re-throw unexpected errors\n throw error\n }\n\n if (removed.length > 0) {\n logger.success(`Removed CLI executables: ${removed.join(', ')}`)\n }\n\n return removed\n }\n\n /**\n * Find orphaned symlinks in ~/.iloom/bin\n * Returns symlinks that point to non-existent targets\n *\n * @returns Array of orphaned symlink information\n */\n async findOrphanedSymlinks(): Promise<Array<{ name: string; path: string; brokenTarget: string }>> {\n const orphaned: Array<{ name: string; path: string; brokenTarget: string }> = []\n\n try {\n const files = await fs.readdir(this.iloomBinDir)\n\n for (const file of files) {\n const symlinkPath = path.join(this.iloomBinDir, file)\n\n try {\n const stats = await fs.lstat(symlinkPath)\n\n if (stats.isSymbolicLink()) {\n const target = await fs.readlink(symlinkPath)\n\n // Check if target exists\n try {\n await fs.access(target)\n } catch {\n // Target doesn't exist - this is an orphaned symlink\n orphaned.push({\n name: file,\n path: symlinkPath,\n brokenTarget: target\n })\n }\n }\n } catch (error) {\n // Skip files we can't read\n logger.warn(\n `Failed to check symlink ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n } catch (error) {\n // Handle missing bin directory\n const isEnoent = error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'\n if (isEnoent) {\n return []\n }\n\n // Re-throw unexpected errors\n throw error\n }\n\n return orphaned\n }\n\n /**\n * Cleanup all orphaned symlinks\n * Removes symlinks that point to non-existent targets\n *\n * @returns Number of symlinks removed\n */\n async cleanupOrphanedSymlinks(): Promise<number> {\n const orphaned = await this.findOrphanedSymlinks()\n let removedCount = 0\n\n for (const symlink of orphaned) {\n try {\n await fs.unlink(symlink.path)\n removedCount++\n logger.success(`Removed orphaned symlink: ${symlink.name}`)\n } catch (error) {\n logger.warn(\n `Failed to remove orphaned symlink ${symlink.name}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n\n return removedCount\n }\n\n /**\n * Check if a filename matches the versioned pattern for an identifier\n * Pattern: {binName}-{identifier}\n *\n * @param fileName - Name of the file to check\n * @param identifier - Issue/PR number or branch identifier\n * @returns True if the filename matches the pattern\n */\n private matchesIdentifier(fileName: string, identifier: string | number): boolean {\n const suffix = `-${identifier}`\n return fileName.endsWith(suffix)\n }\n}\n","import type { DatabaseProvider } from '../types/index.js'\nimport { EnvironmentManager } from './EnvironmentManager.js'\nimport { createLogger } from '../utils/logger.js'\n\nconst logger = createLogger({ prefix: '🗂️' })\n\n/**\n * Database Manager - orchestrates database operations with conditional execution\n * Ports functionality from bash scripts with guard conditions:\n * 1. Database provider must be properly configured (provider.isConfigured())\n * 2. The worktree's .env file must contain the configured database URL variable (default: DATABASE_URL)\n *\n * This ensures database branching only occurs for projects that actually use databases\n */\nexport class DatabaseManager {\n constructor(\n private provider: DatabaseProvider,\n private environment: EnvironmentManager,\n private databaseUrlEnvVarName: string = 'DATABASE_URL'\n ) {\n // Debug: Show which database URL variable name is configured\n if (databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`🔧 DatabaseManager configured with custom variable: ${databaseUrlEnvVarName}`)\n } else {\n logger.debug('🔧 DatabaseManager using default variable: DATABASE_URL')\n }\n }\n\n /**\n * Get the configured database URL environment variable name\n */\n getConfiguredVariableName(): string {\n return this.databaseUrlEnvVarName\n }\n\n /**\n * Check if database branching should be used\n * Requires BOTH conditions:\n * 1. Database provider is properly configured (checked via provider.isConfigured())\n * 2. .env file contains the configured database URL variable\n */\n async shouldUseDatabaseBranching(envFilePath: string): Promise<boolean> {\n // Check for provider configuration\n if (!this.provider.isConfigured()) {\n logger.debug('Skipping database branching: Database provider not configured')\n return false\n }\n\n // Check if .env has the configured database URL variable\n const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(envFilePath)\n if (!hasDatabaseUrl) {\n logger.debug(\n 'Skipping database branching: configured database URL variable not found in .env file'\n )\n return false\n }\n\n return true\n }\n\n /**\n * Create database branch only if configured\n * Returns connection string if branch was created, null if skipped\n *\n * @param branchName - Name of the branch to create\n * @param envFilePath - Path to .env file for configuration checks\n * @param cwd - Optional working directory to run commands from\n */\n async createBranchIfConfigured(\n branchName: string,\n envFilePath: string,\n cwd?: string\n ): Promise<string | null> {\n // Guard condition: check if database branching should be used\n if (!(await this.shouldUseDatabaseBranching(envFilePath))) {\n return null\n }\n\n // Check CLI availability and authentication\n if (!(await this.provider.isCliAvailable())) {\n logger.warn('Skipping database branch creation: Neon CLI not available')\n logger.warn('Install with: npm install -g neonctl')\n return null\n }\n\n try {\n const isAuth = await this.provider.isAuthenticated(cwd)\n if (!isAuth) {\n logger.warn('Skipping database branch creation: Not authenticated with Neon CLI')\n logger.warn('Run: neon auth')\n return null\n }\n } catch (error) {\n // Authentication check failed with an unexpected error - surface it\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Database authentication check failed: ${errorMessage}`)\n throw error\n }\n\n try {\n // Create the branch (which checks for preview first)\n const connectionString = await this.provider.createBranch(branchName, undefined, cwd)\n logger.success(`Database branch ready: ${this.provider.sanitizeBranchName(branchName)}`)\n return connectionString\n } catch (error) {\n logger.error(\n `Failed to create database branch: ${error instanceof Error ? error.message : String(error)}`\n )\n throw error\n }\n }\n\n /**\n * Delete database branch only if configured\n * Returns result object indicating what happened\n *\n * @param branchName - Name of the branch to delete\n * @param shouldCleanup - Boolean indicating if database cleanup should be performed (pre-fetched config)\n * @param isPreview - Whether this is a preview database branch\n * @param cwd - Optional working directory to run commands from (prevents issues with deleted directories)\n */\n async deleteBranchIfConfigured(\n branchName: string,\n shouldCleanup: boolean,\n isPreview: boolean = false,\n cwd?: string\n ): Promise<import('../types/index.js').DatabaseDeletionResult> {\n // If shouldCleanup is explicitly false, skip immediately\n if (shouldCleanup === false) {\n return {\n success: true,\n deleted: false,\n notFound: true, // Treat \"not configured\" as \"nothing to delete\"\n branchName\n }\n }\n\n // If shouldCleanup is explicitly true, validate provider configuration\n if (!this.provider.isConfigured()) {\n logger.debug('Skipping database branch deletion: Database provider not configured')\n return {\n success: true,\n deleted: false,\n notFound: true,\n branchName\n }\n }\n\n // Check CLI availability and authentication\n if (!(await this.provider.isCliAvailable())) {\n logger.info('Skipping database branch deletion: CLI tool not available')\n return {\n success: false,\n deleted: false,\n notFound: true,\n error: \"CLI tool not available\",\n branchName\n }\n }\n\n try {\n const isAuth = await this.provider.isAuthenticated(cwd)\n if (!isAuth) {\n logger.warn('Skipping database branch deletion: Not authenticated with DB Provider')\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: \"Not authenticated with DB Provider\",\n branchName\n }\n }\n } catch (error) {\n // Authentication check failed with an unexpected error - surface it\n const errorMessage = error instanceof Error ? error.message : String(error)\n logger.error(`Database authentication check failed: ${errorMessage}`)\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: `Authentication check failed: ${errorMessage}`,\n branchName\n }\n }\n\n try {\n // Call provider and return its result directly\n const result = await this.provider.deleteBranch(branchName, isPreview, cwd)\n return result\n } catch (error) {\n // Unexpected error (shouldn't happen since provider returns result object)\n logger.warn(\n `Unexpected error in database deletion: ${error instanceof Error ? error.message : String(error)}`\n )\n return {\n success: false,\n deleted: false,\n notFound: false,\n error: error instanceof Error ? error.message : String(error),\n branchName\n }\n }\n }\n\n /**\n * Check if .env has the configured database URL variable\n * CRITICAL: If user explicitly configured a custom variable name (not default),\n * throw an error if it's missing from .env\n */\n private async hasDatabaseUrlInEnv(envFilePath: string): Promise<boolean> {\n try {\n const envMap = await this.environment.readEnvFile(envFilePath)\n\n // Debug: Show what we're looking for\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`)\n } else {\n logger.debug('Looking for default database URL variable: DATABASE_URL')\n }\n\n // Check configured variable first\n if (envMap.has(this.databaseUrlEnvVarName)) {\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`✅ Found custom database URL variable: ${this.databaseUrlEnvVarName}`)\n } else {\n logger.debug(`✅ Found default database URL variable: DATABASE_URL`)\n }\n return true\n }\n\n // If user explicitly configured a custom variable name (not the default)\n // and it's missing, throw an error\n if (this.databaseUrlEnvVarName !== 'DATABASE_URL') {\n logger.debug(`❌ Custom database URL variable '${this.databaseUrlEnvVarName}' not found in .env file`)\n throw new Error(\n `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in .env file. ` +\n `Please add it to your .env file or update your iloom configuration.`\n )\n }\n\n // Fall back to DATABASE_URL when using default configuration\n const hasDefaultVar = envMap.has('DATABASE_URL')\n if (hasDefaultVar) {\n logger.debug('✅ Found fallback DATABASE_URL variable')\n } else {\n logger.debug('❌ No DATABASE_URL variable found in .env file')\n }\n return hasDefaultVar\n } catch (error) {\n // Re-throw configuration errors\n if (error instanceof Error && error.message.includes('not found in .env')) {\n throw error\n }\n // Return false for file read errors\n return false\n }\n }\n}\n"],"mappings":"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","names":["logger","fs","fs","logger"]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
executeGitCommand,
|
|
4
4
|
extractPRNumber,
|
|
5
|
+
findMainWorktreePathWithSettings,
|
|
5
6
|
generateWorktreePath,
|
|
6
7
|
getCurrentBranch,
|
|
7
8
|
getDefaultBranch,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
isPRBranch,
|
|
11
12
|
isValidGitRepo,
|
|
12
13
|
parseWorktreeList
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KOCQAD2E.js";
|
|
14
15
|
|
|
15
16
|
// src/lib/GitWorktreeManager.ts
|
|
16
17
|
import path from "path";
|
|
@@ -47,14 +48,15 @@ var GitWorktreeManager = class {
|
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
49
50
|
* Check if a worktree is the main repository worktree
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* Uses findMainWorktreePathWithSettings to determine the main worktree based on settings.
|
|
52
|
+
*
|
|
53
|
+
* @param worktree - The worktree to check
|
|
54
|
+
* @param settingsManager - SettingsManager instance for loading settings
|
|
55
|
+
* @returns true if the worktree is the main worktree
|
|
53
56
|
*/
|
|
54
|
-
async isMainWorktree(worktree) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
return mainWorktree !== void 0 && mainWorktree.path === worktree.path;
|
|
57
|
+
async isMainWorktree(worktree, settingsManager) {
|
|
58
|
+
const mainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, settingsManager);
|
|
59
|
+
return worktree.path === mainWorktreePath;
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
62
|
* Check if a worktree is a PR worktree based on naming patterns
|
|
@@ -337,13 +339,17 @@ var GitWorktreeManager = class {
|
|
|
337
339
|
* Remove multiple worktrees
|
|
338
340
|
* Returns a summary of successes and failures
|
|
339
341
|
* Automatically filters out the main worktree
|
|
342
|
+
*
|
|
343
|
+
* @param worktrees - Array of worktrees to remove
|
|
344
|
+
* @param settingsManager - SettingsManager instance for determining main worktree
|
|
345
|
+
* @param options - Cleanup options
|
|
340
346
|
*/
|
|
341
|
-
async removeWorktrees(worktrees, options = {}) {
|
|
347
|
+
async removeWorktrees(worktrees, settingsManager, options = {}) {
|
|
342
348
|
const successes = [];
|
|
343
349
|
const failures = [];
|
|
344
350
|
const skipped = [];
|
|
345
351
|
for (const worktree of worktrees) {
|
|
346
|
-
if (await this.isMainWorktree(worktree)) {
|
|
352
|
+
if (await this.isMainWorktree(worktree, settingsManager)) {
|
|
347
353
|
skipped.push({ worktree, reason: "Cannot remove main worktree" });
|
|
348
354
|
continue;
|
|
349
355
|
}
|
|
@@ -380,4 +386,4 @@ var GitWorktreeManager = class {
|
|
|
380
386
|
export {
|
|
381
387
|
GitWorktreeManager
|
|
382
388
|
};
|
|
383
|
-
//# sourceMappingURL=chunk-
|
|
389
|
+
//# sourceMappingURL=chunk-IO4WFTL2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/GitWorktreeManager.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\nimport {\n type GitWorktree,\n type WorktreeCreateOptions,\n type WorktreeListOptions,\n type WorktreeValidation,\n type WorktreeStatus,\n type WorktreeCleanupOptions,\n} from '../types/worktree.js'\nimport {\n executeGitCommand,\n parseWorktreeList,\n isPRBranch,\n extractPRNumber,\n generateWorktreePath,\n isValidGitRepo,\n getCurrentBranch,\n getRepoRoot,\n hasUncommittedChanges,\n getDefaultBranch,\n findMainWorktreePathWithSettings,\n} from '../utils/git.js'\nimport type { SettingsManager } from './SettingsManager.js'\n\n/**\n * Manages Git worktrees for the iloom CLI\n * Ports functionality from bash scripts into TypeScript\n */\nexport class GitWorktreeManager {\n private readonly _workingDirectory: string\n\n constructor(workingDirectory: string = process.cwd()) {\n this._workingDirectory = workingDirectory\n }\n\n /**\n * Get the working directory for git operations (main worktree path)\n */\n get workingDirectory(): string {\n return this._workingDirectory\n }\n\n /**\n * List all worktrees in the repository\n * Defaults to porcelain format for reliable machine parsing\n * Equivalent to: git worktree list --porcelain\n */\n async listWorktrees(options: WorktreeListOptions = {}): Promise<GitWorktree[]> {\n const args = ['worktree', 'list']\n // Default to porcelain format for consistent parsing (can be disabled with porcelain: false)\n if (options.porcelain !== false) args.push('--porcelain')\n if (options.verbose) args.push('-v')\n\n const output = await executeGitCommand(args, { cwd: this._workingDirectory })\n return parseWorktreeList(output)\n }\n\n /**\n * Find worktree for a specific branch\n * Ports: find_worktree_for_branch() from find-worktree-for-branch.sh\n */\n async findWorktreeForBranch(branchName: string): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees()\n return worktrees.find(wt => wt.branch === branchName) ?? null\n }\n\n /**\n * Check if a worktree is the main repository worktree\n * Uses findMainWorktreePathWithSettings to determine the main worktree based on settings.\n *\n * @param worktree - The worktree to check\n * @param settingsManager - SettingsManager instance for loading settings\n * @returns true if the worktree is the main worktree\n */\n async isMainWorktree(worktree: GitWorktree, settingsManager: SettingsManager): Promise<boolean> {\n const mainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, settingsManager)\n return worktree.path === mainWorktreePath\n }\n\n /**\n * Check if a worktree is a PR worktree based on naming patterns\n * Ports: is_pr_worktree() from worktree-utils.sh\n */\n isPRWorktree(worktree: GitWorktree): boolean {\n return isPRBranch(worktree.branch)\n }\n\n /**\n * Get PR number from worktree branch name\n * Ports: get_pr_number_from_worktree() from worktree-utils.sh\n */\n getPRNumberFromWorktree(worktree: GitWorktree): number | null {\n return extractPRNumber(worktree.branch)\n }\n\n /**\n * Create a new worktree\n * Ports worktree creation logic from new-branch-workflow.sh\n * @returns The absolute path to the created worktree\n */\n async createWorktree(options: WorktreeCreateOptions): Promise<string> {\n // Validate inputs\n if (!options.branch) {\n throw new Error('Branch name is required')\n }\n\n // Ensure path is absolute\n const absolutePath = path.resolve(options.path)\n\n // Check if path already exists and handle force flag\n if (await fs.pathExists(absolutePath)) {\n if (!options.force) {\n throw new Error(`Path already exists: ${absolutePath}`)\n }\n // Remove existing directory if force is true\n await fs.remove(absolutePath)\n }\n\n // Build git worktree add command\n const args = ['worktree', 'add']\n\n if (options.createBranch) {\n args.push('-b', options.branch)\n }\n\n if (options.force) {\n args.push('--force')\n }\n\n args.push(absolutePath)\n\n // Add branch name if not creating new branch\n if (!options.createBranch) {\n args.push(options.branch)\n } else if (options.baseBranch) {\n args.push(options.baseBranch)\n }\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n return absolutePath\n }\n\n /**\n * Remove a worktree and optionally clean up associated files\n * Ports worktree removal logic from cleanup-worktree.sh\n * @returns A message describing what was done (for dry-run mode)\n */\n async removeWorktree(\n worktreePath: string,\n options: WorktreeCleanupOptions = {}\n ): Promise<string | void> {\n // Validate worktree exists - use porcelain format for consistent parsing\n const worktrees = await this.listWorktrees({ porcelain: true })\n const worktree = worktrees.find(wt => wt.path === worktreePath)\n\n if (!worktree) {\n // Add debug logging to help diagnose the issue\n const { logger } = await import('../utils/logger.js')\n logger.debug(`Looking for worktree path: ${worktreePath}`)\n logger.debug(`Found ${worktrees.length} worktrees:`)\n worktrees.forEach((wt, i) => {\n logger.debug(` ${i}: path=\"${wt.path}\", branch=\"${wt.branch}\"`)\n })\n throw new Error(`Worktree not found: ${worktreePath}`)\n }\n\n // Check for uncommitted changes unless force is specified\n if (!options.force && !options.dryRun) {\n const hasChanges = await hasUncommittedChanges(worktreePath)\n if (hasChanges) {\n throw new Error(`Worktree has uncommitted changes: ${worktreePath}. Use --force to override.`)\n }\n }\n\n if (options.dryRun) {\n const actions = ['Remove worktree registration']\n if (options.removeDirectory) actions.push('Remove directory from disk')\n if (options.removeBranch) actions.push(`Remove branch: ${worktree.branch}`)\n\n return `Would perform: ${actions.join(', ')}`\n }\n\n // Remove worktree registration\n const args = ['worktree', 'remove']\n if (options.force) args.push('--force')\n args.push(worktreePath)\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n\n // Remove directory if requested\n if (options.removeDirectory && (await fs.pathExists(worktreePath))) {\n await fs.remove(worktreePath)\n }\n\n // Remove branch if requested and safe to do so\n if (options.removeBranch && !worktree.bare) {\n try {\n await executeGitCommand(['branch', '-D', worktree.branch], {\n cwd: this._workingDirectory,\n })\n } catch (error) {\n // Don't fail the whole operation if branch deletion fails\n // Just log a warning (caller can handle this)\n throw new Error(\n `Worktree removed but failed to delete branch ${worktree.branch}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n }\n\n /**\n * Validate worktree state and integrity\n */\n async validateWorktree(worktreePath: string): Promise<WorktreeValidation> {\n const issues: string[] = []\n let existsOnDisk = false\n let isValidRepo = false\n let hasValidBranch = false\n\n try {\n // Check if path exists on disk\n existsOnDisk = await fs.pathExists(worktreePath)\n if (!existsOnDisk) {\n issues.push('Worktree directory does not exist on disk')\n }\n\n // Check if it's a valid Git repository\n if (existsOnDisk) {\n isValidRepo = await isValidGitRepo(worktreePath)\n if (!isValidRepo) {\n issues.push('Directory is not a valid Git repository')\n }\n }\n\n // Check if branch reference is valid\n if (isValidRepo) {\n const currentBranch = await getCurrentBranch(worktreePath)\n hasValidBranch = currentBranch !== null\n if (!hasValidBranch) {\n issues.push('Could not determine current branch')\n }\n }\n\n // Check if worktree is registered with Git\n const worktrees = await this.listWorktrees()\n const isRegistered = worktrees.some(wt => wt.path === worktreePath)\n if (!isRegistered) {\n issues.push('Worktree is not registered with Git')\n }\n } catch (error) {\n issues.push(`Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n\n return {\n isValid: issues.length === 0,\n issues,\n existsOnDisk,\n isValidRepo,\n hasValidBranch,\n }\n }\n\n /**\n * Get detailed status information for a worktree\n */\n async getWorktreeStatus(worktreePath: string): Promise<WorktreeStatus> {\n const statusOutput = await executeGitCommand(['status', '--porcelain=v1'], {\n cwd: worktreePath,\n })\n\n let modified = 0\n let staged = 0\n let deleted = 0\n let untracked = 0\n\n const lines = statusOutput.trim().split('\\n').filter(Boolean)\n for (const line of lines) {\n const status = line.substring(0, 2)\n if (status[0] === 'M' || status[1] === 'M') modified++\n if (status[0] === 'A' || status[0] === 'D' || status[0] === 'R') staged++\n if (status[0] === 'D' || status[1] === 'D') deleted++\n if (status === '??') untracked++\n }\n\n const currentBranch = (await getCurrentBranch(worktreePath)) ?? 'unknown'\n const detached = currentBranch === 'unknown'\n\n // Get ahead/behind information\n let ahead = 0\n let behind = 0\n try {\n const aheadBehindOutput = await executeGitCommand(\n ['rev-list', '--left-right', '--count', `origin/${currentBranch}...HEAD`],\n { cwd: worktreePath }\n )\n const parts = aheadBehindOutput.trim().split('\\t')\n const behindStr = parts[0]\n const aheadStr = parts[1]\n behind = behindStr ? parseInt(behindStr, 10) || 0 : 0\n ahead = aheadStr ? parseInt(aheadStr, 10) || 0 : 0\n } catch {\n // Ignore errors for ahead/behind calculation\n }\n\n return {\n modified,\n staged,\n deleted,\n untracked,\n hasChanges: modified + staged + deleted + untracked > 0,\n branch: currentBranch,\n detached,\n ahead,\n behind,\n }\n }\n\n /**\n * Generate a suggested worktree path for a branch\n */\n generateWorktreePath(\n branchName: string,\n customRoot?: string,\n options?: { isPR?: boolean; prNumber?: number; prefix?: string }\n ): string {\n const root = customRoot ?? this._workingDirectory\n return generateWorktreePath(branchName, root, options)\n }\n\n /**\n * Sanitize a branch name for use as a directory name\n * Replaces slashes with dashes and removes invalid filesystem characters\n * Ports logic from bash script line 593: ${BRANCH_NAME//\\\\//-}\n */\n sanitizeBranchName(branchName: string): string {\n return branchName\n .replace(/\\//g, '-') // Replace slashes with dashes\n .replace(/[^a-zA-Z0-9-]/g, '-') // Replace invalid chars (including underscores) with dashes\n .replace(/-+/g, '-') // Collapse multiple dashes\n .replace(/^-|-$/g, '') // Remove leading/trailing dashes\n .toLowerCase()\n }\n\n /**\n * Check if repository is in a valid state for worktree operations\n */\n async isRepoReady(): Promise<boolean> {\n try {\n const repoRoot = await getRepoRoot(this._workingDirectory)\n return repoRoot !== null\n } catch {\n return false\n }\n }\n\n /**\n * Get repository information\n */\n async getRepoInfo(): Promise<{\n root: string | null\n defaultBranch: string\n currentBranch: string | null\n }> {\n const root = await getRepoRoot(this._workingDirectory)\n const defaultBranch = await getDefaultBranch(this._workingDirectory)\n const currentBranch = await getCurrentBranch(this._workingDirectory)\n\n return {\n root,\n defaultBranch,\n currentBranch,\n }\n }\n\n /**\n * Prune stale worktree entries (worktrees that no longer exist on disk)\n */\n async pruneWorktrees(): Promise<void> {\n await executeGitCommand(['worktree', 'prune', '-v'], { cwd: this._workingDirectory })\n }\n\n /**\n * Lock a worktree to prevent it from being pruned or moved\n */\n async lockWorktree(worktreePath: string, reason?: string): Promise<void> {\n const args = ['worktree', 'lock', worktreePath]\n if (reason) args.push('--reason', reason)\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n }\n\n /**\n * Unlock a previously locked worktree\n */\n async unlockWorktree(worktreePath: string): Promise<void> {\n await executeGitCommand(['worktree', 'unlock', worktreePath], { cwd: this._workingDirectory })\n }\n\n /**\n * Find worktrees matching an identifier (branch name, path, or PR number)\n */\n async findWorktreesByIdentifier(identifier: string): Promise<GitWorktree[]> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n return worktrees.filter(\n wt =>\n wt.branch.includes(identifier) ||\n wt.path.includes(identifier) ||\n this.getPRNumberFromWorktree(wt)?.toString() === identifier\n )\n }\n\n /**\n * Find worktree for a specific issue number using exact pattern matching\n * Matches: issue-{N} at start OR after /, -, _ (but NOT issue-{N}X where X is a digit)\n * Supports patterns like: issue-44, feat/issue-44-feature, feat-issue-44, bugfix_issue-44, etc.\n * Avoids false matches like: tissue-44, myissue-44\n * Ports: find_existing_worktree() from bash script lines 131-165\n */\n async findWorktreeForIssue(issueNumber: number): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n\n // Pattern: starts with 'issue-{N}' OR has '/issue-{N}', '-issue-{N}', '_issue-{N}' but not 'issue-{N}{digit}'\n const pattern = new RegExp(`(?:^|[/_-])issue-${issueNumber}(?:-|$)`)\n\n return worktrees.find(wt => pattern.test(wt.branch)) ?? null\n }\n\n /**\n * Find worktree for a specific PR by branch name\n * Ports: find_existing_worktree() for PR type from bash script lines 149-160\n */\n async findWorktreeForPR(prNumber: number, branchName: string): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n\n // Find by exact branch name match (prioritized)\n const byBranch = worktrees.find(wt => wt.branch === branchName)\n if (byBranch) return byBranch\n\n // Also check directory name pattern: *_pr_{N}\n const pathPattern = new RegExp(`_pr_${prNumber}$`)\n return worktrees.find(wt => pathPattern.test(wt.path)) ?? null\n }\n\n /**\n * Remove multiple worktrees\n * Returns a summary of successes and failures\n * Automatically filters out the main worktree\n *\n * @param worktrees - Array of worktrees to remove\n * @param settingsManager - SettingsManager instance for determining main worktree\n * @param options - Cleanup options\n */\n async removeWorktrees(\n worktrees: GitWorktree[],\n settingsManager: SettingsManager,\n options: WorktreeCleanupOptions = {}\n ): Promise<{\n successes: Array<{ worktree: GitWorktree }>\n failures: Array<{ worktree: GitWorktree; error: string }>\n skipped: Array<{ worktree: GitWorktree; reason: string }>\n }> {\n const successes: Array<{ worktree: GitWorktree }> = []\n const failures: Array<{ worktree: GitWorktree; error: string }> = []\n const skipped: Array<{ worktree: GitWorktree; reason: string }> = []\n\n for (const worktree of worktrees) {\n // Skip main worktree\n if (await this.isMainWorktree(worktree, settingsManager)) {\n skipped.push({ worktree, reason: 'Cannot remove main worktree' })\n continue\n }\n\n try {\n await this.removeWorktree(worktree.path, {\n ...options,\n removeDirectory: true,\n })\n successes.push({ worktree })\n } catch (error) {\n failures.push({\n worktree,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n }\n\n return { successes, failures, skipped }\n }\n\n /**\n * Format worktree information for display\n */\n formatWorktree(worktree: GitWorktree): {\n title: string\n path: string\n commit: string\n } {\n const prNumber = this.getPRNumberFromWorktree(worktree)\n const prLabel = prNumber ? ` (PR #${prNumber})` : ''\n const bareLabel = worktree.bare ? ' [main]' : ''\n\n return {\n title: `${worktree.branch}${prLabel}${bareLabel}`,\n path: worktree.path,\n commit: worktree.commit.substring(0, 7),\n }\n }\n}\n"],"mappings":"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","names":[]}
|