@iloom/cli 0.1.14

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.
Files changed (161) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +711 -0
  3. package/dist/ClaudeContextManager-XOSXQ67R.js +13 -0
  4. package/dist/ClaudeContextManager-XOSXQ67R.js.map +1 -0
  5. package/dist/ClaudeService-YSZ6EXWP.js +12 -0
  6. package/dist/ClaudeService-YSZ6EXWP.js.map +1 -0
  7. package/dist/GitHubService-F7Z3XJOS.js +11 -0
  8. package/dist/GitHubService-F7Z3XJOS.js.map +1 -0
  9. package/dist/LoomLauncher-MODG2SEM.js +263 -0
  10. package/dist/LoomLauncher-MODG2SEM.js.map +1 -0
  11. package/dist/NeonProvider-PAGPUH7F.js +12 -0
  12. package/dist/NeonProvider-PAGPUH7F.js.map +1 -0
  13. package/dist/PromptTemplateManager-7FINLRDE.js +9 -0
  14. package/dist/PromptTemplateManager-7FINLRDE.js.map +1 -0
  15. package/dist/SettingsManager-VAZF26S2.js +19 -0
  16. package/dist/SettingsManager-VAZF26S2.js.map +1 -0
  17. package/dist/SettingsMigrationManager-MTQIMI54.js +146 -0
  18. package/dist/SettingsMigrationManager-MTQIMI54.js.map +1 -0
  19. package/dist/add-issue-22JBNOML.js +54 -0
  20. package/dist/add-issue-22JBNOML.js.map +1 -0
  21. package/dist/agents/iloom-issue-analyze-and-plan.md +580 -0
  22. package/dist/agents/iloom-issue-analyzer.md +290 -0
  23. package/dist/agents/iloom-issue-complexity-evaluator.md +224 -0
  24. package/dist/agents/iloom-issue-enhancer.md +266 -0
  25. package/dist/agents/iloom-issue-implementer.md +262 -0
  26. package/dist/agents/iloom-issue-planner.md +358 -0
  27. package/dist/agents/iloom-issue-reviewer.md +63 -0
  28. package/dist/chunk-2ZPFJQ3B.js +63 -0
  29. package/dist/chunk-2ZPFJQ3B.js.map +1 -0
  30. package/dist/chunk-37DYYFVK.js +29 -0
  31. package/dist/chunk-37DYYFVK.js.map +1 -0
  32. package/dist/chunk-BLCTGFZN.js +121 -0
  33. package/dist/chunk-BLCTGFZN.js.map +1 -0
  34. package/dist/chunk-CP2NU2JC.js +545 -0
  35. package/dist/chunk-CP2NU2JC.js.map +1 -0
  36. package/dist/chunk-CWR2SANQ.js +39 -0
  37. package/dist/chunk-CWR2SANQ.js.map +1 -0
  38. package/dist/chunk-F3XBU2R7.js +110 -0
  39. package/dist/chunk-F3XBU2R7.js.map +1 -0
  40. package/dist/chunk-GEHQXLEI.js +130 -0
  41. package/dist/chunk-GEHQXLEI.js.map +1 -0
  42. package/dist/chunk-GYCR2LOU.js +143 -0
  43. package/dist/chunk-GYCR2LOU.js.map +1 -0
  44. package/dist/chunk-GZP4UGGM.js +48 -0
  45. package/dist/chunk-GZP4UGGM.js.map +1 -0
  46. package/dist/chunk-H4E4THUZ.js +55 -0
  47. package/dist/chunk-H4E4THUZ.js.map +1 -0
  48. package/dist/chunk-HPJJSYNS.js +644 -0
  49. package/dist/chunk-HPJJSYNS.js.map +1 -0
  50. package/dist/chunk-JBH2ZYYZ.js +220 -0
  51. package/dist/chunk-JBH2ZYYZ.js.map +1 -0
  52. package/dist/chunk-JNKJ7NJV.js +78 -0
  53. package/dist/chunk-JNKJ7NJV.js.map +1 -0
  54. package/dist/chunk-JQ7VOSTC.js +437 -0
  55. package/dist/chunk-JQ7VOSTC.js.map +1 -0
  56. package/dist/chunk-KQDEK2ZW.js +199 -0
  57. package/dist/chunk-KQDEK2ZW.js.map +1 -0
  58. package/dist/chunk-O2QWO64Z.js +179 -0
  59. package/dist/chunk-O2QWO64Z.js.map +1 -0
  60. package/dist/chunk-OC4H6HJD.js +248 -0
  61. package/dist/chunk-OC4H6HJD.js.map +1 -0
  62. package/dist/chunk-PR7FKQBG.js +120 -0
  63. package/dist/chunk-PR7FKQBG.js.map +1 -0
  64. package/dist/chunk-PXZBAC2M.js +250 -0
  65. package/dist/chunk-PXZBAC2M.js.map +1 -0
  66. package/dist/chunk-QEPVTTHD.js +383 -0
  67. package/dist/chunk-QEPVTTHD.js.map +1 -0
  68. package/dist/chunk-RSRO7564.js +203 -0
  69. package/dist/chunk-RSRO7564.js.map +1 -0
  70. package/dist/chunk-SJUQ2NDR.js +146 -0
  71. package/dist/chunk-SJUQ2NDR.js.map +1 -0
  72. package/dist/chunk-SPYPLHMK.js +177 -0
  73. package/dist/chunk-SPYPLHMK.js.map +1 -0
  74. package/dist/chunk-SSCQCCJ7.js +75 -0
  75. package/dist/chunk-SSCQCCJ7.js.map +1 -0
  76. package/dist/chunk-SSR5AVRJ.js +41 -0
  77. package/dist/chunk-SSR5AVRJ.js.map +1 -0
  78. package/dist/chunk-T7QPXANZ.js +315 -0
  79. package/dist/chunk-T7QPXANZ.js.map +1 -0
  80. package/dist/chunk-U3WU5OWO.js +203 -0
  81. package/dist/chunk-U3WU5OWO.js.map +1 -0
  82. package/dist/chunk-W3DQTW63.js +124 -0
  83. package/dist/chunk-W3DQTW63.js.map +1 -0
  84. package/dist/chunk-WKEWRSDB.js +151 -0
  85. package/dist/chunk-WKEWRSDB.js.map +1 -0
  86. package/dist/chunk-Y7SAGNUT.js +66 -0
  87. package/dist/chunk-Y7SAGNUT.js.map +1 -0
  88. package/dist/chunk-YETJNRQM.js +39 -0
  89. package/dist/chunk-YETJNRQM.js.map +1 -0
  90. package/dist/chunk-YYSKGAZT.js +384 -0
  91. package/dist/chunk-YYSKGAZT.js.map +1 -0
  92. package/dist/chunk-ZZZWQGTS.js +169 -0
  93. package/dist/chunk-ZZZWQGTS.js.map +1 -0
  94. package/dist/claude-7LUVDZZ4.js +17 -0
  95. package/dist/claude-7LUVDZZ4.js.map +1 -0
  96. package/dist/cleanup-3LUWPSM7.js +412 -0
  97. package/dist/cleanup-3LUWPSM7.js.map +1 -0
  98. package/dist/cli-overrides-XFZWY7CM.js +16 -0
  99. package/dist/cli-overrides-XFZWY7CM.js.map +1 -0
  100. package/dist/cli.js +603 -0
  101. package/dist/cli.js.map +1 -0
  102. package/dist/color-ZVALX37U.js +21 -0
  103. package/dist/color-ZVALX37U.js.map +1 -0
  104. package/dist/enhance-XJIQHVPD.js +166 -0
  105. package/dist/enhance-XJIQHVPD.js.map +1 -0
  106. package/dist/env-MDFL4ZXL.js +23 -0
  107. package/dist/env-MDFL4ZXL.js.map +1 -0
  108. package/dist/feedback-23CLXKFT.js +158 -0
  109. package/dist/feedback-23CLXKFT.js.map +1 -0
  110. package/dist/finish-CY4CIH6O.js +1608 -0
  111. package/dist/finish-CY4CIH6O.js.map +1 -0
  112. package/dist/git-LVRZ57GJ.js +43 -0
  113. package/dist/git-LVRZ57GJ.js.map +1 -0
  114. package/dist/ignite-WXEF2ID5.js +359 -0
  115. package/dist/ignite-WXEF2ID5.js.map +1 -0
  116. package/dist/index.d.ts +1341 -0
  117. package/dist/index.js +3058 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/init-RHACUR4E.js +123 -0
  120. package/dist/init-RHACUR4E.js.map +1 -0
  121. package/dist/installation-detector-VARGFFRZ.js +11 -0
  122. package/dist/installation-detector-VARGFFRZ.js.map +1 -0
  123. package/dist/logger-MKYH4UDV.js +12 -0
  124. package/dist/logger-MKYH4UDV.js.map +1 -0
  125. package/dist/mcp/chunk-6SDFJ42P.js +62 -0
  126. package/dist/mcp/chunk-6SDFJ42P.js.map +1 -0
  127. package/dist/mcp/claude-YHHHLSXH.js +249 -0
  128. package/dist/mcp/claude-YHHHLSXH.js.map +1 -0
  129. package/dist/mcp/color-QS5BFCNN.js +168 -0
  130. package/dist/mcp/color-QS5BFCNN.js.map +1 -0
  131. package/dist/mcp/github-comment-server.js +165 -0
  132. package/dist/mcp/github-comment-server.js.map +1 -0
  133. package/dist/mcp/terminal-SDCMDVD7.js +202 -0
  134. package/dist/mcp/terminal-SDCMDVD7.js.map +1 -0
  135. package/dist/open-X6BTENPV.js +278 -0
  136. package/dist/open-X6BTENPV.js.map +1 -0
  137. package/dist/prompt-ANTQWHUF.js +13 -0
  138. package/dist/prompt-ANTQWHUF.js.map +1 -0
  139. package/dist/prompts/issue-prompt.txt +230 -0
  140. package/dist/prompts/pr-prompt.txt +35 -0
  141. package/dist/prompts/regular-prompt.txt +14 -0
  142. package/dist/run-2JCPQAX3.js +278 -0
  143. package/dist/run-2JCPQAX3.js.map +1 -0
  144. package/dist/schema/settings.schema.json +221 -0
  145. package/dist/start-LWVRBJ6S.js +982 -0
  146. package/dist/start-LWVRBJ6S.js.map +1 -0
  147. package/dist/terminal-3D6TUAKJ.js +16 -0
  148. package/dist/terminal-3D6TUAKJ.js.map +1 -0
  149. package/dist/test-git-XPF4SZXJ.js +52 -0
  150. package/dist/test-git-XPF4SZXJ.js.map +1 -0
  151. package/dist/test-prefix-XGFXFAYN.js +68 -0
  152. package/dist/test-prefix-XGFXFAYN.js.map +1 -0
  153. package/dist/test-tabs-JRKY3QMM.js +69 -0
  154. package/dist/test-tabs-JRKY3QMM.js.map +1 -0
  155. package/dist/test-webserver-M2I3EV4J.js +62 -0
  156. package/dist/test-webserver-M2I3EV4J.js.map +1 -0
  157. package/dist/update-3ZT2XX2G.js +79 -0
  158. package/dist/update-3ZT2XX2G.js.map +1 -0
  159. package/dist/update-notifier-QSSEB5KC.js +11 -0
  160. package/dist/update-notifier-QSSEB5KC.js.map +1 -0
  161. package/package.json +113 -0
@@ -0,0 +1,644 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ calculatePortForBranch
4
+ } from "./chunk-37DYYFVK.js";
5
+ import {
6
+ runScript
7
+ } from "./chunk-BLCTGFZN.js";
8
+ import {
9
+ hasScript,
10
+ readPackageJson
11
+ } from "./chunk-2ZPFJQ3B.js";
12
+ import {
13
+ formatEnvLine,
14
+ parseEnvFile,
15
+ validateEnvVariable
16
+ } from "./chunk-SJUQ2NDR.js";
17
+ import {
18
+ createLogger,
19
+ logger
20
+ } from "./chunk-GEHQXLEI.js";
21
+
22
+ // src/lib/EnvironmentManager.ts
23
+ import fs from "fs-extra";
24
+ var logger2 = createLogger({ prefix: "\u{1F4DD}" });
25
+ var EnvironmentManager = class {
26
+ constructor() {
27
+ this.backupSuffix = ".backup";
28
+ }
29
+ /**
30
+ * Set or update an environment variable in a .env file
31
+ * Ports functionality from bash/utils/env-utils.sh:setEnvVar()
32
+ * @returns The backup path if a backup was created
33
+ */
34
+ async setEnvVar(filePath, key, value, backup = false) {
35
+ const validation = validateEnvVariable(key, value);
36
+ if (!validation.valid) {
37
+ throw new Error(validation.error ?? "Invalid variable name");
38
+ }
39
+ const fileExists = await fs.pathExists(filePath);
40
+ if (!fileExists) {
41
+ logger2.info(`Creating ${filePath} with ${key}...`);
42
+ const content = formatEnvLine(key, value);
43
+ await fs.writeFile(filePath, content, "utf8");
44
+ logger2.success(`${filePath} created with ${key}`);
45
+ return;
46
+ }
47
+ const existingContent = await fs.readFile(filePath, "utf8");
48
+ const envMap = parseEnvFile(existingContent);
49
+ let backupPath;
50
+ if (backup) {
51
+ backupPath = await this.createBackup(filePath);
52
+ }
53
+ envMap.set(key, value);
54
+ const lines = existingContent.split("\n");
55
+ const newLines = [];
56
+ let variableUpdated = false;
57
+ for (const line of lines) {
58
+ const trimmedLine = line.trim();
59
+ if (!trimmedLine || trimmedLine.startsWith("#")) {
60
+ newLines.push(line);
61
+ continue;
62
+ }
63
+ const cleanLine = trimmedLine.startsWith("export ") ? trimmedLine.substring(7) : trimmedLine;
64
+ const equalsIndex = cleanLine.indexOf("=");
65
+ if (equalsIndex !== -1) {
66
+ const lineKey = cleanLine.substring(0, equalsIndex).trim();
67
+ if (lineKey === key) {
68
+ newLines.push(formatEnvLine(key, value));
69
+ variableUpdated = true;
70
+ continue;
71
+ }
72
+ }
73
+ newLines.push(line);
74
+ }
75
+ if (!variableUpdated) {
76
+ logger2.info(`Adding ${key} to ${filePath}...`);
77
+ newLines.push(formatEnvLine(key, value));
78
+ logger2.success(`${key} added successfully`);
79
+ } else {
80
+ logger2.info(`Updating ${key} in ${filePath}...`);
81
+ logger2.success(`${key} updated successfully`);
82
+ }
83
+ const newContent = newLines.join("\n");
84
+ await fs.writeFile(filePath, newContent, "utf8");
85
+ return backupPath;
86
+ }
87
+ /**
88
+ * Read and parse a .env file
89
+ */
90
+ async readEnvFile(filePath) {
91
+ try {
92
+ const content = await fs.readFile(filePath, "utf8");
93
+ return parseEnvFile(content);
94
+ } catch (error) {
95
+ logger2.debug(
96
+ `Could not read env file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
97
+ );
98
+ return /* @__PURE__ */ new Map();
99
+ }
100
+ }
101
+ /**
102
+ * Generic file copy helper that only copies if source exists
103
+ * Does not throw if source file doesn't exist - just logs and returns
104
+ * @private
105
+ */
106
+ async copyIfExists(source, destination) {
107
+ const sourceExists = await fs.pathExists(source);
108
+ if (!sourceExists) {
109
+ logger2.debug(`Source file ${source} does not exist, skipping copy`);
110
+ return;
111
+ }
112
+ await fs.copy(source, destination, { overwrite: false });
113
+ logger2.success(`Copied ${source} to ${destination}`);
114
+ }
115
+ /**
116
+ * Calculate unique port for workspace
117
+ * Implements:
118
+ * - Issue/PR: 3000 + issue/PR number
119
+ * - Branch: 3000 + deterministic hash offset (1-999)
120
+ */
121
+ calculatePort(options) {
122
+ const basePort = options.basePort ?? 3e3;
123
+ if (options.issueNumber !== void 0) {
124
+ const port = basePort + options.issueNumber;
125
+ if (port > 65535) {
126
+ throw new Error(
127
+ `Calculated port ${port} exceeds maximum (65535). Use a lower base port or issue number.`
128
+ );
129
+ }
130
+ return port;
131
+ }
132
+ if (options.prNumber !== void 0) {
133
+ const port = basePort + options.prNumber;
134
+ if (port > 65535) {
135
+ throw new Error(
136
+ `Calculated port ${port} exceeds maximum (65535). Use a lower base port or PR number.`
137
+ );
138
+ }
139
+ return port;
140
+ }
141
+ if (options.branchName !== void 0) {
142
+ return calculatePortForBranch(options.branchName, basePort);
143
+ }
144
+ return basePort;
145
+ }
146
+ /**
147
+ * Set port environment variable for workspace
148
+ */
149
+ async setPortForWorkspace(envFilePath, issueNumber, prNumber, branchName) {
150
+ const options = {};
151
+ if (issueNumber !== void 0) {
152
+ options.issueNumber = issueNumber;
153
+ }
154
+ if (prNumber !== void 0) {
155
+ options.prNumber = prNumber;
156
+ }
157
+ if (branchName !== void 0) {
158
+ options.branchName = branchName;
159
+ }
160
+ const port = this.calculatePort(options);
161
+ await this.setEnvVar(envFilePath, "PORT", String(port));
162
+ return port;
163
+ }
164
+ /**
165
+ * Validate environment configuration
166
+ */
167
+ async validateEnvFile(filePath) {
168
+ try {
169
+ const content = await fs.readFile(filePath, "utf8");
170
+ const envMap = parseEnvFile(content);
171
+ const errors = [];
172
+ for (const [key, value] of envMap.entries()) {
173
+ const validation = validateEnvVariable(key, value);
174
+ if (!validation.valid) {
175
+ errors.push(`${key}: ${validation.error}`);
176
+ }
177
+ }
178
+ return {
179
+ valid: errors.length === 0,
180
+ errors
181
+ };
182
+ } catch (error) {
183
+ return {
184
+ valid: false,
185
+ errors: [
186
+ `Failed to read or parse file: ${error instanceof Error ? error.message : String(error)}`
187
+ ]
188
+ };
189
+ }
190
+ }
191
+ /**
192
+ * Create backup of existing file
193
+ */
194
+ async createBackup(filePath) {
195
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
196
+ const backupPath = `${filePath}${this.backupSuffix}-${timestamp}`;
197
+ await fs.copy(filePath, backupPath);
198
+ logger2.debug(`Created backup at ${backupPath}`);
199
+ return backupPath;
200
+ }
201
+ };
202
+
203
+ // src/lib/CLIIsolationManager.ts
204
+ import fs2 from "fs-extra";
205
+ import path from "path";
206
+ import os from "os";
207
+ var CLIIsolationManager = class {
208
+ constructor() {
209
+ this.iloomBinDir = path.join(os.homedir(), ".iloom", "bin");
210
+ }
211
+ /**
212
+ * Setup CLI isolation for a worktree
213
+ * - Build the project
214
+ * - Create versioned symlinks
215
+ * - Check PATH configuration
216
+ * @param worktreePath Path to the worktree
217
+ * @param identifier Issue/PR number or branch identifier
218
+ * @param binEntries Bin entries from package.json
219
+ * @returns Array of created symlink names
220
+ */
221
+ async setupCLIIsolation(worktreePath, identifier, binEntries) {
222
+ await this.buildProject(worktreePath);
223
+ await this.verifyBinTargets(worktreePath, binEntries);
224
+ await fs2.ensureDir(this.iloomBinDir);
225
+ const symlinkNames = await this.createVersionedSymlinks(
226
+ worktreePath,
227
+ identifier,
228
+ binEntries
229
+ );
230
+ await this.ensureIloomBinInPath();
231
+ return symlinkNames;
232
+ }
233
+ /**
234
+ * Build the project using package.json build script
235
+ * @param worktreePath Path to the worktree
236
+ */
237
+ async buildProject(worktreePath) {
238
+ const pkgJson = await readPackageJson(worktreePath);
239
+ if (!hasScript(pkgJson, "build")) {
240
+ logger.warn("No build script found in package.json - skipping build");
241
+ return;
242
+ }
243
+ logger.info("Building CLI tool...");
244
+ await runScript("build", worktreePath);
245
+ logger.success("Build completed");
246
+ }
247
+ /**
248
+ * Verify bin targets exist and are executable
249
+ * @param worktreePath Path to the worktree
250
+ * @param binEntries Bin entries from package.json
251
+ */
252
+ async verifyBinTargets(worktreePath, binEntries) {
253
+ for (const binPath of Object.values(binEntries)) {
254
+ const targetPath = path.resolve(worktreePath, binPath);
255
+ const exists = await fs2.pathExists(targetPath);
256
+ if (!exists) {
257
+ throw new Error(`Bin target does not exist: ${targetPath}`);
258
+ }
259
+ try {
260
+ await fs2.access(targetPath, fs2.constants.X_OK);
261
+ } catch {
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Create versioned symlinks in ~/.iloom/bin
267
+ * @param worktreePath Path to the worktree
268
+ * @param identifier Issue/PR number or branch identifier
269
+ * @param binEntries Bin entries from package.json
270
+ * @returns Array of created symlink names
271
+ */
272
+ async createVersionedSymlinks(worktreePath, identifier, binEntries) {
273
+ const symlinkNames = [];
274
+ for (const [binName, binPath] of Object.entries(binEntries)) {
275
+ const versionedName = `${binName}-${identifier}`;
276
+ const targetPath = path.resolve(worktreePath, binPath);
277
+ const symlinkPath = path.join(this.iloomBinDir, versionedName);
278
+ await fs2.symlink(targetPath, symlinkPath);
279
+ logger.success(`CLI available: ${versionedName}`);
280
+ symlinkNames.push(versionedName);
281
+ }
282
+ return symlinkNames;
283
+ }
284
+ /**
285
+ * Check if ~/.iloom/bin is in PATH and provide setup instructions
286
+ */
287
+ async ensureIloomBinInPath() {
288
+ const currentPath = process.env.PATH ?? "";
289
+ if (currentPath.includes(".iloom/bin")) {
290
+ return;
291
+ }
292
+ const shell = this.detectShell();
293
+ const rcFile = this.getShellRcFile(shell);
294
+ logger.warn("\n\u26A0\uFE0F One-time PATH setup required:");
295
+ logger.warn(` Add to ${rcFile}:`);
296
+ logger.warn(` export PATH="$HOME/.iloom/bin:$PATH"`);
297
+ logger.warn(` Then run: source ${rcFile}
298
+ `);
299
+ }
300
+ /**
301
+ * Detect current shell
302
+ * @returns Shell name (zsh, bash, fish, etc.)
303
+ */
304
+ detectShell() {
305
+ const shell = process.env.SHELL ?? "";
306
+ return shell.split("/").pop() ?? "bash";
307
+ }
308
+ /**
309
+ * Get RC file path for shell
310
+ * @param shell Shell name
311
+ * @returns RC file path
312
+ */
313
+ getShellRcFile(shell) {
314
+ const rcFiles = {
315
+ zsh: "~/.zshrc",
316
+ bash: "~/.bashrc",
317
+ fish: "~/.config/fish/config.fish"
318
+ };
319
+ return rcFiles[shell] ?? "~/.bashrc";
320
+ }
321
+ /**
322
+ * Cleanup versioned CLI executables for a specific identifier
323
+ * Removes all symlinks matching the pattern: {binName}-{identifier}
324
+ *
325
+ * @param identifier - Issue/PR number or branch identifier
326
+ * @returns Array of removed symlink names
327
+ */
328
+ async cleanupVersionedExecutables(identifier) {
329
+ const removed = [];
330
+ try {
331
+ const files = await fs2.readdir(this.iloomBinDir);
332
+ for (const file of files) {
333
+ if (this.matchesIdentifier(file, identifier)) {
334
+ const symlinkPath = path.join(this.iloomBinDir, file);
335
+ try {
336
+ await fs2.unlink(symlinkPath);
337
+ removed.push(file);
338
+ } catch (error) {
339
+ const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
340
+ if (isEnoent) {
341
+ removed.push(file);
342
+ continue;
343
+ }
344
+ logger.warn(
345
+ `Failed to remove symlink ${file}: ${error instanceof Error ? error.message : "Unknown error"}`
346
+ );
347
+ }
348
+ }
349
+ }
350
+ } catch (error) {
351
+ const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
352
+ if (isEnoent) {
353
+ logger.warn("No CLI executables directory found - nothing to cleanup");
354
+ return [];
355
+ }
356
+ throw error;
357
+ }
358
+ if (removed.length > 0) {
359
+ logger.success(`Removed CLI executables: ${removed.join(", ")}`);
360
+ }
361
+ return removed;
362
+ }
363
+ /**
364
+ * Find orphaned symlinks in ~/.iloom/bin
365
+ * Returns symlinks that point to non-existent targets
366
+ *
367
+ * @returns Array of orphaned symlink information
368
+ */
369
+ async findOrphanedSymlinks() {
370
+ const orphaned = [];
371
+ try {
372
+ const files = await fs2.readdir(this.iloomBinDir);
373
+ for (const file of files) {
374
+ const symlinkPath = path.join(this.iloomBinDir, file);
375
+ try {
376
+ const stats = await fs2.lstat(symlinkPath);
377
+ if (stats.isSymbolicLink()) {
378
+ const target = await fs2.readlink(symlinkPath);
379
+ try {
380
+ await fs2.access(target);
381
+ } catch {
382
+ orphaned.push({
383
+ name: file,
384
+ path: symlinkPath,
385
+ brokenTarget: target
386
+ });
387
+ }
388
+ }
389
+ } catch (error) {
390
+ logger.warn(
391
+ `Failed to check symlink ${file}: ${error instanceof Error ? error.message : "Unknown error"}`
392
+ );
393
+ }
394
+ }
395
+ } catch (error) {
396
+ const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
397
+ if (isEnoent) {
398
+ return [];
399
+ }
400
+ throw error;
401
+ }
402
+ return orphaned;
403
+ }
404
+ /**
405
+ * Cleanup all orphaned symlinks
406
+ * Removes symlinks that point to non-existent targets
407
+ *
408
+ * @returns Number of symlinks removed
409
+ */
410
+ async cleanupOrphanedSymlinks() {
411
+ const orphaned = await this.findOrphanedSymlinks();
412
+ let removedCount = 0;
413
+ for (const symlink of orphaned) {
414
+ try {
415
+ await fs2.unlink(symlink.path);
416
+ removedCount++;
417
+ logger.success(`Removed orphaned symlink: ${symlink.name}`);
418
+ } catch (error) {
419
+ logger.warn(
420
+ `Failed to remove orphaned symlink ${symlink.name}: ${error instanceof Error ? error.message : "Unknown error"}`
421
+ );
422
+ }
423
+ }
424
+ return removedCount;
425
+ }
426
+ /**
427
+ * Check if a filename matches the versioned pattern for an identifier
428
+ * Pattern: {binName}-{identifier}
429
+ *
430
+ * @param fileName - Name of the file to check
431
+ * @param identifier - Issue/PR number or branch identifier
432
+ * @returns True if the filename matches the pattern
433
+ */
434
+ matchesIdentifier(fileName, identifier) {
435
+ const suffix = `-${identifier}`;
436
+ return fileName.endsWith(suffix);
437
+ }
438
+ };
439
+
440
+ // src/lib/DatabaseManager.ts
441
+ var logger3 = createLogger({ prefix: "\u{1F5C2}\uFE0F" });
442
+ var DatabaseManager = class {
443
+ constructor(provider, environment, databaseUrlEnvVarName = "DATABASE_URL") {
444
+ this.provider = provider;
445
+ this.environment = environment;
446
+ this.databaseUrlEnvVarName = databaseUrlEnvVarName;
447
+ if (databaseUrlEnvVarName !== "DATABASE_URL") {
448
+ logger3.debug(`\u{1F527} DatabaseManager configured with custom variable: ${databaseUrlEnvVarName}`);
449
+ } else {
450
+ logger3.debug("\u{1F527} DatabaseManager using default variable: DATABASE_URL");
451
+ }
452
+ }
453
+ /**
454
+ * Get the configured database URL environment variable name
455
+ */
456
+ getConfiguredVariableName() {
457
+ return this.databaseUrlEnvVarName;
458
+ }
459
+ /**
460
+ * Check if database branching should be used
461
+ * Requires BOTH conditions:
462
+ * 1. Database provider is properly configured (checked via provider.isConfigured())
463
+ * 2. .env file contains the configured database URL variable
464
+ */
465
+ async shouldUseDatabaseBranching(envFilePath) {
466
+ if (!this.provider.isConfigured()) {
467
+ logger3.debug("Skipping database branching: Database provider not configured");
468
+ return false;
469
+ }
470
+ const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(envFilePath);
471
+ if (!hasDatabaseUrl) {
472
+ logger3.debug(
473
+ "Skipping database branching: configured database URL variable not found in .env file"
474
+ );
475
+ return false;
476
+ }
477
+ return true;
478
+ }
479
+ /**
480
+ * Create database branch only if configured
481
+ * Returns connection string if branch was created, null if skipped
482
+ *
483
+ * @param branchName - Name of the branch to create
484
+ * @param envFilePath - Path to .env file for configuration checks
485
+ * @param cwd - Optional working directory to run commands from
486
+ */
487
+ async createBranchIfConfigured(branchName, envFilePath, cwd) {
488
+ if (!await this.shouldUseDatabaseBranching(envFilePath)) {
489
+ return null;
490
+ }
491
+ if (!await this.provider.isCliAvailable()) {
492
+ logger3.warn("Skipping database branch creation: Neon CLI not available");
493
+ logger3.warn("Install with: npm install -g neonctl");
494
+ return null;
495
+ }
496
+ try {
497
+ const isAuth = await this.provider.isAuthenticated(cwd);
498
+ if (!isAuth) {
499
+ logger3.warn("Skipping database branch creation: Not authenticated with Neon CLI");
500
+ logger3.warn("Run: neon auth");
501
+ return null;
502
+ }
503
+ } catch (error) {
504
+ const errorMessage = error instanceof Error ? error.message : String(error);
505
+ logger3.error(`Database authentication check failed: ${errorMessage}`);
506
+ throw error;
507
+ }
508
+ try {
509
+ const connectionString = await this.provider.createBranch(branchName, void 0, cwd);
510
+ logger3.success(`Database branch ready: ${this.provider.sanitizeBranchName(branchName)}`);
511
+ return connectionString;
512
+ } catch (error) {
513
+ logger3.error(
514
+ `Failed to create database branch: ${error instanceof Error ? error.message : String(error)}`
515
+ );
516
+ throw error;
517
+ }
518
+ }
519
+ /**
520
+ * Delete database branch only if configured
521
+ * Returns result object indicating what happened
522
+ *
523
+ * @param branchName - Name of the branch to delete
524
+ * @param shouldCleanup - Boolean indicating if database cleanup should be performed (pre-fetched config)
525
+ * @param isPreview - Whether this is a preview database branch
526
+ * @param cwd - Optional working directory to run commands from (prevents issues with deleted directories)
527
+ */
528
+ async deleteBranchIfConfigured(branchName, shouldCleanup, isPreview = false, cwd) {
529
+ if (shouldCleanup === false) {
530
+ return {
531
+ success: true,
532
+ deleted: false,
533
+ notFound: true,
534
+ // Treat "not configured" as "nothing to delete"
535
+ branchName
536
+ };
537
+ }
538
+ if (!this.provider.isConfigured()) {
539
+ logger3.debug("Skipping database branch deletion: Database provider not configured");
540
+ return {
541
+ success: true,
542
+ deleted: false,
543
+ notFound: true,
544
+ branchName
545
+ };
546
+ }
547
+ if (!await this.provider.isCliAvailable()) {
548
+ logger3.info("Skipping database branch deletion: CLI tool not available");
549
+ return {
550
+ success: false,
551
+ deleted: false,
552
+ notFound: true,
553
+ error: "CLI tool not available",
554
+ branchName
555
+ };
556
+ }
557
+ try {
558
+ const isAuth = await this.provider.isAuthenticated(cwd);
559
+ if (!isAuth) {
560
+ logger3.warn("Skipping database branch deletion: Not authenticated with DB Provider");
561
+ return {
562
+ success: false,
563
+ deleted: false,
564
+ notFound: false,
565
+ error: "Not authenticated with DB Provider",
566
+ branchName
567
+ };
568
+ }
569
+ } catch (error) {
570
+ const errorMessage = error instanceof Error ? error.message : String(error);
571
+ logger3.error(`Database authentication check failed: ${errorMessage}`);
572
+ return {
573
+ success: false,
574
+ deleted: false,
575
+ notFound: false,
576
+ error: `Authentication check failed: ${errorMessage}`,
577
+ branchName
578
+ };
579
+ }
580
+ try {
581
+ const result = await this.provider.deleteBranch(branchName, isPreview, cwd);
582
+ return result;
583
+ } catch (error) {
584
+ logger3.warn(
585
+ `Unexpected error in database deletion: ${error instanceof Error ? error.message : String(error)}`
586
+ );
587
+ return {
588
+ success: false,
589
+ deleted: false,
590
+ notFound: false,
591
+ error: error instanceof Error ? error.message : String(error),
592
+ branchName
593
+ };
594
+ }
595
+ }
596
+ /**
597
+ * Check if .env has the configured database URL variable
598
+ * CRITICAL: If user explicitly configured a custom variable name (not default),
599
+ * throw an error if it's missing from .env
600
+ */
601
+ async hasDatabaseUrlInEnv(envFilePath) {
602
+ try {
603
+ const envMap = await this.environment.readEnvFile(envFilePath);
604
+ if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
605
+ logger3.debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`);
606
+ } else {
607
+ logger3.debug("Looking for default database URL variable: DATABASE_URL");
608
+ }
609
+ if (envMap.has(this.databaseUrlEnvVarName)) {
610
+ if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
611
+ logger3.debug(`\u2705 Found custom database URL variable: ${this.databaseUrlEnvVarName}`);
612
+ } else {
613
+ logger3.debug(`\u2705 Found default database URL variable: DATABASE_URL`);
614
+ }
615
+ return true;
616
+ }
617
+ if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
618
+ logger3.debug(`\u274C Custom database URL variable '${this.databaseUrlEnvVarName}' not found in .env file`);
619
+ throw new Error(
620
+ `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in .env file. Please add it to your .env file or update your iloom configuration.`
621
+ );
622
+ }
623
+ const hasDefaultVar = envMap.has("DATABASE_URL");
624
+ if (hasDefaultVar) {
625
+ logger3.debug("\u2705 Found fallback DATABASE_URL variable");
626
+ } else {
627
+ logger3.debug("\u274C No DATABASE_URL variable found in .env file");
628
+ }
629
+ return hasDefaultVar;
630
+ } catch (error) {
631
+ if (error instanceof Error && error.message.includes("not found in .env")) {
632
+ throw error;
633
+ }
634
+ return false;
635
+ }
636
+ }
637
+ };
638
+
639
+ export {
640
+ EnvironmentManager,
641
+ CLIIsolationManager,
642
+ DatabaseManager
643
+ };
644
+ //# sourceMappingURL=chunk-HPJJSYNS.js.map