@aku11i/phantom 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +254 -0
- package/README.md +30 -32
- package/dist/phantom.js +249 -185
- package/dist/phantom.js.map +4 -4
- package/package.json +5 -7
- package/dist/garden.js +0 -458
- package/dist/garden.js.map +0 -7
package/dist/phantom.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/bin/phantom.ts
|
|
4
4
|
import { argv, exit as exit6 } from "node:process";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
6
|
+
// src/commands/create.ts
|
|
7
7
|
import { access as access2, mkdir } from "node:fs/promises";
|
|
8
8
|
import { join as join2 } from "node:path";
|
|
9
9
|
import { exit as exit3 } from "node:process";
|
|
@@ -26,45 +26,45 @@ async function getGitRoot() {
|
|
|
26
26
|
return stdout.trim();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// src/
|
|
29
|
+
// src/commands/shell.ts
|
|
30
30
|
import { spawn } from "node:child_process";
|
|
31
31
|
import { exit as exit2 } from "node:process";
|
|
32
32
|
|
|
33
|
-
// src/
|
|
33
|
+
// src/commands/where.ts
|
|
34
34
|
import { access } from "node:fs/promises";
|
|
35
35
|
import { join } from "node:path";
|
|
36
36
|
import { exit } from "node:process";
|
|
37
|
-
async function
|
|
37
|
+
async function whereWorktree(name) {
|
|
38
38
|
if (!name) {
|
|
39
|
-
return { success: false, message: "Error:
|
|
39
|
+
return { success: false, message: "Error: worktree name required" };
|
|
40
40
|
}
|
|
41
41
|
try {
|
|
42
42
|
const gitRoot = await getGitRoot();
|
|
43
|
-
const
|
|
44
|
-
const
|
|
43
|
+
const worktreesPath = join(gitRoot, ".git", "phantom", "worktrees");
|
|
44
|
+
const worktreePath = join(worktreesPath, name);
|
|
45
45
|
try {
|
|
46
|
-
await access(
|
|
46
|
+
await access(worktreePath);
|
|
47
47
|
} catch {
|
|
48
48
|
return {
|
|
49
49
|
success: false,
|
|
50
|
-
message: `Error:
|
|
50
|
+
message: `Error: Worktree '${name}' does not exist`
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
return {
|
|
54
54
|
success: true,
|
|
55
|
-
path:
|
|
55
|
+
path: worktreePath
|
|
56
56
|
};
|
|
57
57
|
} catch (error) {
|
|
58
58
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
59
59
|
return {
|
|
60
60
|
success: false,
|
|
61
|
-
message: `Error locating
|
|
61
|
+
message: `Error locating worktree: ${errorMessage}`
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
async function
|
|
65
|
+
async function whereHandler(args2) {
|
|
66
66
|
const name = args2[0];
|
|
67
|
-
const result = await
|
|
67
|
+
const result = await whereWorktree(name);
|
|
68
68
|
if (!result.success) {
|
|
69
69
|
console.error(result.message);
|
|
70
70
|
exit(1);
|
|
@@ -72,26 +72,26 @@ async function gardensWhereHandler(args2) {
|
|
|
72
72
|
console.log(result.path);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// src/
|
|
76
|
-
async function
|
|
77
|
-
if (!
|
|
78
|
-
return { success: false, message: "Error:
|
|
75
|
+
// src/commands/shell.ts
|
|
76
|
+
async function shellInWorktree(worktreeName) {
|
|
77
|
+
if (!worktreeName) {
|
|
78
|
+
return { success: false, message: "Error: worktree name required" };
|
|
79
79
|
}
|
|
80
|
-
const
|
|
81
|
-
if (!
|
|
82
|
-
return { success: false, message:
|
|
80
|
+
const worktreeResult = await whereWorktree(worktreeName);
|
|
81
|
+
if (!worktreeResult.success) {
|
|
82
|
+
return { success: false, message: worktreeResult.message };
|
|
83
83
|
}
|
|
84
|
-
const
|
|
84
|
+
const worktreePath = worktreeResult.path;
|
|
85
85
|
const shell = process.env.SHELL || "/bin/sh";
|
|
86
86
|
return new Promise((resolve) => {
|
|
87
87
|
const childProcess = spawn(shell, [], {
|
|
88
|
-
cwd:
|
|
88
|
+
cwd: worktreePath,
|
|
89
89
|
stdio: "inherit",
|
|
90
90
|
env: {
|
|
91
91
|
...process.env,
|
|
92
|
-
// Add environment variable to indicate we're in a
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// Add environment variable to indicate we're in a worktree
|
|
93
|
+
WORKTREE_NAME: worktreeName,
|
|
94
|
+
WORKTREE_PATH: worktreePath
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
childProcess.on("error", (error) => {
|
|
@@ -119,18 +119,18 @@ async function shellInGarden(gardenName) {
|
|
|
119
119
|
}
|
|
120
120
|
async function shellHandler(args2) {
|
|
121
121
|
if (args2.length < 1) {
|
|
122
|
-
console.error("Usage: phantom shell <
|
|
122
|
+
console.error("Usage: phantom shell <worktree-name>");
|
|
123
123
|
exit2(1);
|
|
124
124
|
}
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
if (!
|
|
128
|
-
console.error(
|
|
125
|
+
const worktreeName = args2[0];
|
|
126
|
+
const worktreeResult = await whereWorktree(worktreeName);
|
|
127
|
+
if (!worktreeResult.success) {
|
|
128
|
+
console.error(worktreeResult.message);
|
|
129
129
|
exit2(1);
|
|
130
130
|
}
|
|
131
|
-
console.log(`Entering
|
|
131
|
+
console.log(`Entering worktree '${worktreeName}' at ${worktreeResult.path}`);
|
|
132
132
|
console.log("Type 'exit' to return to your original directory\n");
|
|
133
|
-
const result = await
|
|
133
|
+
const result = await shellInWorktree(worktreeName);
|
|
134
134
|
if (!result.success) {
|
|
135
135
|
if (result.message) {
|
|
136
136
|
console.error(result.message);
|
|
@@ -140,25 +140,25 @@ async function shellHandler(args2) {
|
|
|
140
140
|
exit2(result.exitCode ?? 0);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
// src/
|
|
144
|
-
async function
|
|
143
|
+
// src/commands/create.ts
|
|
144
|
+
async function createWorktree(name) {
|
|
145
145
|
if (!name) {
|
|
146
|
-
return { success: false, message: "Error:
|
|
146
|
+
return { success: false, message: "Error: worktree name required" };
|
|
147
147
|
}
|
|
148
148
|
try {
|
|
149
149
|
const gitRoot = await getGitRoot();
|
|
150
|
-
const
|
|
151
|
-
const worktreePath = join2(
|
|
150
|
+
const worktreesPath = join2(gitRoot, ".git", "phantom", "worktrees");
|
|
151
|
+
const worktreePath = join2(worktreesPath, name);
|
|
152
152
|
try {
|
|
153
|
-
await access2(
|
|
153
|
+
await access2(worktreesPath);
|
|
154
154
|
} catch {
|
|
155
|
-
await mkdir(
|
|
155
|
+
await mkdir(worktreesPath, { recursive: true });
|
|
156
156
|
}
|
|
157
157
|
try {
|
|
158
158
|
await access2(worktreePath);
|
|
159
159
|
return {
|
|
160
160
|
success: false,
|
|
161
|
-
message: `Error:
|
|
161
|
+
message: `Error: worktree '${name}' already exists`
|
|
162
162
|
};
|
|
163
163
|
} catch {
|
|
164
164
|
}
|
|
@@ -169,21 +169,21 @@ async function createGarden(name) {
|
|
|
169
169
|
});
|
|
170
170
|
return {
|
|
171
171
|
success: true,
|
|
172
|
-
message: `Created
|
|
172
|
+
message: `Created worktree '${name}' at ${worktreePath}`,
|
|
173
173
|
path: worktreePath
|
|
174
174
|
};
|
|
175
175
|
} catch (error) {
|
|
176
176
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
177
177
|
return {
|
|
178
178
|
success: false,
|
|
179
|
-
message: `Error creating
|
|
179
|
+
message: `Error creating worktree: ${errorMessage}`
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
-
async function
|
|
183
|
+
async function createHandler(args2) {
|
|
184
184
|
const name = args2[0];
|
|
185
185
|
const openShell = args2.includes("--shell");
|
|
186
|
-
const result = await
|
|
186
|
+
const result = await createWorktree(name);
|
|
187
187
|
if (!result.success) {
|
|
188
188
|
console.error(result.message);
|
|
189
189
|
exit3(1);
|
|
@@ -191,9 +191,9 @@ async function gardensCreateHandler(args2) {
|
|
|
191
191
|
console.log(result.message);
|
|
192
192
|
if (openShell && result.path) {
|
|
193
193
|
console.log(`
|
|
194
|
-
Entering
|
|
194
|
+
Entering worktree '${name}' at ${result.path}`);
|
|
195
195
|
console.log("Type 'exit' to return to your original directory\n");
|
|
196
|
-
const shellResult = await
|
|
196
|
+
const shellResult = await shellInWorktree(name);
|
|
197
197
|
if (!shellResult.success) {
|
|
198
198
|
if (shellResult.message) {
|
|
199
199
|
console.error(shellResult.message);
|
|
@@ -204,35 +204,35 @@ Entering garden '${name}' at ${result.path}`);
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// src/
|
|
207
|
+
// src/commands/delete.ts
|
|
208
208
|
import { exec as exec3 } from "node:child_process";
|
|
209
209
|
import { access as access3 } from "node:fs/promises";
|
|
210
210
|
import { join as join3 } from "node:path";
|
|
211
211
|
import { exit as exit4 } from "node:process";
|
|
212
212
|
import { promisify as promisify3 } from "node:util";
|
|
213
213
|
var execAsync3 = promisify3(exec3);
|
|
214
|
-
async function
|
|
214
|
+
async function deleteWorktree(name, options = {}) {
|
|
215
215
|
if (!name) {
|
|
216
|
-
return { success: false, message: "Error:
|
|
216
|
+
return { success: false, message: "Error: worktree name required" };
|
|
217
217
|
}
|
|
218
218
|
const { force = false } = options;
|
|
219
219
|
try {
|
|
220
220
|
const gitRoot = await getGitRoot();
|
|
221
|
-
const
|
|
222
|
-
const
|
|
221
|
+
const worktreesPath = join3(gitRoot, ".git", "phantom", "worktrees");
|
|
222
|
+
const worktreePath = join3(worktreesPath, name);
|
|
223
223
|
try {
|
|
224
|
-
await access3(
|
|
224
|
+
await access3(worktreePath);
|
|
225
225
|
} catch {
|
|
226
226
|
return {
|
|
227
227
|
success: false,
|
|
228
|
-
message: `Error:
|
|
228
|
+
message: `Error: Worktree '${name}' does not exist`
|
|
229
229
|
};
|
|
230
230
|
}
|
|
231
231
|
let hasUncommittedChanges = false;
|
|
232
232
|
let changedFiles = 0;
|
|
233
233
|
try {
|
|
234
234
|
const { stdout } = await execAsync3("git status --porcelain", {
|
|
235
|
-
cwd:
|
|
235
|
+
cwd: worktreePath
|
|
236
236
|
});
|
|
237
237
|
const changes = stdout.trim();
|
|
238
238
|
if (changes) {
|
|
@@ -245,37 +245,37 @@ async function deleteGarden(name, options = {}) {
|
|
|
245
245
|
if (hasUncommittedChanges && !force) {
|
|
246
246
|
return {
|
|
247
247
|
success: false,
|
|
248
|
-
message: `Error:
|
|
248
|
+
message: `Error: Worktree '${name}' has uncommitted changes (${changedFiles} files). Use --force to delete anyway.`,
|
|
249
249
|
hasUncommittedChanges: true,
|
|
250
250
|
changedFiles
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
253
|
try {
|
|
254
|
-
await execAsync3(`git worktree remove "${
|
|
254
|
+
await execAsync3(`git worktree remove "${worktreePath}"`, {
|
|
255
255
|
cwd: gitRoot
|
|
256
256
|
});
|
|
257
257
|
} catch (error) {
|
|
258
258
|
try {
|
|
259
|
-
await execAsync3(`git worktree remove --force "${
|
|
259
|
+
await execAsync3(`git worktree remove --force "${worktreePath}"`, {
|
|
260
260
|
cwd: gitRoot
|
|
261
261
|
});
|
|
262
262
|
} catch {
|
|
263
263
|
return {
|
|
264
264
|
success: false,
|
|
265
|
-
message: `Error: Failed to remove worktree
|
|
265
|
+
message: `Error: Failed to remove worktree '${name}'`
|
|
266
266
|
};
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
-
const branchName = `phantom/
|
|
269
|
+
const branchName = `phantom/worktrees/${name}`;
|
|
270
270
|
try {
|
|
271
271
|
await execAsync3(`git branch -D "${branchName}"`, {
|
|
272
272
|
cwd: gitRoot
|
|
273
273
|
});
|
|
274
274
|
} catch {
|
|
275
275
|
}
|
|
276
|
-
let message = `Deleted
|
|
276
|
+
let message = `Deleted worktree '${name}' and its branch '${branchName}'`;
|
|
277
277
|
if (hasUncommittedChanges) {
|
|
278
|
-
message = `Warning:
|
|
278
|
+
message = `Warning: Worktree '${name}' had uncommitted changes (${changedFiles} files)
|
|
279
279
|
${message}`;
|
|
280
280
|
}
|
|
281
281
|
return {
|
|
@@ -288,16 +288,16 @@ ${message}`;
|
|
|
288
288
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
289
289
|
return {
|
|
290
290
|
success: false,
|
|
291
|
-
message: `Error deleting
|
|
291
|
+
message: `Error deleting worktree: ${errorMessage}`
|
|
292
292
|
};
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
|
-
async function
|
|
295
|
+
async function deleteHandler(args2) {
|
|
296
296
|
const forceIndex = args2.indexOf("--force");
|
|
297
297
|
const force = forceIndex !== -1;
|
|
298
298
|
const filteredArgs = args2.filter((arg) => arg !== "--force");
|
|
299
299
|
const name = filteredArgs[0];
|
|
300
|
-
const result = await
|
|
300
|
+
const result = await deleteWorktree(name, { force });
|
|
301
301
|
if (!result.success) {
|
|
302
302
|
console.error(result.message);
|
|
303
303
|
exit4(1);
|
|
@@ -305,32 +305,93 @@ async function gardensDeleteHandler(args2) {
|
|
|
305
305
|
console.log(result.message);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
// src/
|
|
308
|
+
// src/commands/exec.ts
|
|
309
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
310
|
+
import { exit as exit5 } from "node:process";
|
|
311
|
+
async function execInWorktree(worktreeName, command2) {
|
|
312
|
+
if (!worktreeName) {
|
|
313
|
+
return { success: false, message: "Error: worktree name required" };
|
|
314
|
+
}
|
|
315
|
+
if (!command2 || command2.length === 0) {
|
|
316
|
+
return { success: false, message: "Error: command required" };
|
|
317
|
+
}
|
|
318
|
+
const worktreeResult = await whereWorktree(worktreeName);
|
|
319
|
+
if (!worktreeResult.success) {
|
|
320
|
+
return { success: false, message: worktreeResult.message };
|
|
321
|
+
}
|
|
322
|
+
const worktreePath = worktreeResult.path;
|
|
323
|
+
const [cmd, ...args2] = command2;
|
|
324
|
+
return new Promise((resolve) => {
|
|
325
|
+
const childProcess = spawn2(cmd, args2, {
|
|
326
|
+
cwd: worktreePath,
|
|
327
|
+
stdio: "inherit"
|
|
328
|
+
});
|
|
329
|
+
childProcess.on("error", (error) => {
|
|
330
|
+
resolve({
|
|
331
|
+
success: false,
|
|
332
|
+
message: `Error executing command: ${error.message}`
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
childProcess.on("exit", (code, signal) => {
|
|
336
|
+
if (signal) {
|
|
337
|
+
resolve({
|
|
338
|
+
success: false,
|
|
339
|
+
message: `Command terminated by signal: ${signal}`,
|
|
340
|
+
exitCode: 128 + (signal === "SIGTERM" ? 15 : 1)
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
const exitCode = code ?? 0;
|
|
344
|
+
resolve({
|
|
345
|
+
success: exitCode === 0,
|
|
346
|
+
exitCode
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async function execHandler(args2) {
|
|
353
|
+
if (args2.length < 2) {
|
|
354
|
+
console.error("Usage: phantom exec <worktree-name> <command> [args...]");
|
|
355
|
+
exit5(1);
|
|
356
|
+
}
|
|
357
|
+
const worktreeName = args2[0];
|
|
358
|
+
const command2 = args2.slice(1);
|
|
359
|
+
const result = await execInWorktree(worktreeName, command2);
|
|
360
|
+
if (!result.success) {
|
|
361
|
+
if (result.message) {
|
|
362
|
+
console.error(result.message);
|
|
363
|
+
}
|
|
364
|
+
exit5(result.exitCode ?? 1);
|
|
365
|
+
}
|
|
366
|
+
exit5(result.exitCode ?? 0);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/commands/list.ts
|
|
309
370
|
import { exec as exec4 } from "node:child_process";
|
|
310
371
|
import { access as access4, readdir } from "node:fs/promises";
|
|
311
372
|
import { join as join4 } from "node:path";
|
|
312
373
|
import { promisify as promisify4 } from "node:util";
|
|
313
374
|
var execAsync4 = promisify4(exec4);
|
|
314
|
-
async function
|
|
375
|
+
async function listWorktrees() {
|
|
315
376
|
try {
|
|
316
377
|
const gitRoot = await getGitRoot();
|
|
317
|
-
const
|
|
378
|
+
const worktreesPath = join4(gitRoot, ".git", "phantom", "worktrees");
|
|
318
379
|
try {
|
|
319
|
-
await access4(
|
|
380
|
+
await access4(worktreesPath);
|
|
320
381
|
} catch {
|
|
321
382
|
return {
|
|
322
383
|
success: true,
|
|
323
|
-
|
|
324
|
-
message: "No
|
|
384
|
+
worktrees: [],
|
|
385
|
+
message: "No worktrees found (worktrees directory doesn't exist)"
|
|
325
386
|
};
|
|
326
387
|
}
|
|
327
|
-
let
|
|
388
|
+
let worktreeNames;
|
|
328
389
|
try {
|
|
329
|
-
const entries = await readdir(
|
|
390
|
+
const entries = await readdir(worktreesPath);
|
|
330
391
|
const validEntries = await Promise.all(
|
|
331
392
|
entries.map(async (entry) => {
|
|
332
393
|
try {
|
|
333
|
-
const entryPath = join4(
|
|
394
|
+
const entryPath = join4(worktreesPath, entry);
|
|
334
395
|
await access4(entryPath);
|
|
335
396
|
return entry;
|
|
336
397
|
} catch {
|
|
@@ -338,30 +399,30 @@ async function listGardens() {
|
|
|
338
399
|
}
|
|
339
400
|
})
|
|
340
401
|
);
|
|
341
|
-
|
|
402
|
+
worktreeNames = validEntries.filter(
|
|
342
403
|
(entry) => entry !== null
|
|
343
404
|
);
|
|
344
405
|
} catch {
|
|
345
406
|
return {
|
|
346
407
|
success: true,
|
|
347
|
-
|
|
348
|
-
message: "No
|
|
408
|
+
worktrees: [],
|
|
409
|
+
message: "No worktrees found (unable to read worktrees directory)"
|
|
349
410
|
};
|
|
350
411
|
}
|
|
351
|
-
if (
|
|
412
|
+
if (worktreeNames.length === 0) {
|
|
352
413
|
return {
|
|
353
414
|
success: true,
|
|
354
|
-
|
|
355
|
-
message: "No
|
|
415
|
+
worktrees: [],
|
|
416
|
+
message: "No worktrees found"
|
|
356
417
|
};
|
|
357
418
|
}
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
const
|
|
419
|
+
const worktrees = await Promise.all(
|
|
420
|
+
worktreeNames.map(async (name) => {
|
|
421
|
+
const worktreePath = join4(worktreesPath, name);
|
|
361
422
|
let branch = "unknown";
|
|
362
423
|
try {
|
|
363
424
|
const { stdout } = await execAsync4("git branch --show-current", {
|
|
364
|
-
cwd:
|
|
425
|
+
cwd: worktreePath
|
|
365
426
|
});
|
|
366
427
|
branch = stdout.trim() || "detached HEAD";
|
|
367
428
|
} catch {
|
|
@@ -371,7 +432,7 @@ async function listGardens() {
|
|
|
371
432
|
let changedFiles;
|
|
372
433
|
try {
|
|
373
434
|
const { stdout } = await execAsync4("git status --porcelain", {
|
|
374
|
-
cwd:
|
|
435
|
+
cwd: worktreePath
|
|
375
436
|
});
|
|
376
437
|
const changes = stdout.trim();
|
|
377
438
|
if (changes) {
|
|
@@ -391,147 +452,146 @@ async function listGardens() {
|
|
|
391
452
|
);
|
|
392
453
|
return {
|
|
393
454
|
success: true,
|
|
394
|
-
|
|
455
|
+
worktrees
|
|
395
456
|
};
|
|
396
457
|
} catch (error) {
|
|
397
458
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
398
459
|
return {
|
|
399
460
|
success: false,
|
|
400
|
-
message: `Error listing
|
|
461
|
+
message: `Error listing worktrees: ${errorMessage}`
|
|
401
462
|
};
|
|
402
463
|
}
|
|
403
464
|
}
|
|
404
|
-
async function
|
|
405
|
-
const result = await
|
|
465
|
+
async function listHandler() {
|
|
466
|
+
const result = await listWorktrees();
|
|
406
467
|
if (!result.success) {
|
|
407
468
|
console.error(result.message);
|
|
408
469
|
return;
|
|
409
470
|
}
|
|
410
|
-
if (!result.
|
|
411
|
-
console.log(result.message || "No
|
|
471
|
+
if (!result.worktrees || result.worktrees.length === 0) {
|
|
472
|
+
console.log(result.message || "No worktrees found");
|
|
412
473
|
return;
|
|
413
474
|
}
|
|
414
|
-
console.log("
|
|
415
|
-
for (const
|
|
416
|
-
const statusText =
|
|
475
|
+
console.log("Worktrees:");
|
|
476
|
+
for (const worktree of result.worktrees) {
|
|
477
|
+
const statusText = worktree.status === "clean" ? "[clean]" : `[dirty: ${worktree.changedFiles} files]`;
|
|
417
478
|
console.log(
|
|
418
|
-
` ${
|
|
479
|
+
` ${worktree.name.padEnd(20)} (branch: ${worktree.branch.padEnd(20)}) ${statusText}`
|
|
419
480
|
);
|
|
420
481
|
}
|
|
421
482
|
console.log(`
|
|
422
|
-
Total: ${result.
|
|
483
|
+
Total: ${result.worktrees.length} worktrees`);
|
|
423
484
|
}
|
|
424
485
|
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
486
|
+
// package.json
|
|
487
|
+
var package_default = {
|
|
488
|
+
name: "@aku11i/phantom",
|
|
489
|
+
packageManager: "pnpm@10.8.1",
|
|
490
|
+
version: "0.3.0",
|
|
491
|
+
description: "A powerful CLI tool for managing Git worktrees for parallel development",
|
|
492
|
+
keywords: [
|
|
493
|
+
"git",
|
|
494
|
+
"worktree",
|
|
495
|
+
"cli",
|
|
496
|
+
"phantom",
|
|
497
|
+
"workspace",
|
|
498
|
+
"development",
|
|
499
|
+
"parallel"
|
|
500
|
+
],
|
|
501
|
+
homepage: "https://github.com/aku11i/phantom#readme",
|
|
502
|
+
bugs: {
|
|
503
|
+
url: "https://github.com/aku11i/phantom/issues"
|
|
504
|
+
},
|
|
505
|
+
repository: {
|
|
506
|
+
type: "git",
|
|
507
|
+
url: "git+https://github.com/aku11i/phantom.git"
|
|
508
|
+
},
|
|
509
|
+
license: "MIT",
|
|
510
|
+
author: "aku11i",
|
|
511
|
+
type: "module",
|
|
512
|
+
bin: {
|
|
513
|
+
phantom: "./dist/phantom.js"
|
|
514
|
+
},
|
|
515
|
+
scripts: {
|
|
516
|
+
start: "node ./src/bin/phantom.ts",
|
|
517
|
+
phantom: "node ./src/bin/phantom.ts",
|
|
518
|
+
build: "node build.ts",
|
|
519
|
+
"type-check": "tsc --noEmit",
|
|
520
|
+
test: "node --test --experimental-strip-types --experimental-test-module-mocks src/**/*.test.ts",
|
|
521
|
+
lint: "biome check .",
|
|
522
|
+
fix: "biome check --write .",
|
|
523
|
+
ready: "pnpm fix && pnpm type-check && pnpm test",
|
|
524
|
+
"ready:check": "pnpm lint && pnpm type-check && pnpm test",
|
|
525
|
+
prepublishOnly: "pnpm ready:check && pnpm build"
|
|
526
|
+
},
|
|
527
|
+
engines: {
|
|
528
|
+
node: ">=22.0.0"
|
|
529
|
+
},
|
|
530
|
+
files: [
|
|
531
|
+
"dist/",
|
|
532
|
+
"README.md",
|
|
533
|
+
"LICENSE"
|
|
534
|
+
],
|
|
535
|
+
devDependencies: {
|
|
536
|
+
"@biomejs/biome": "^1.9.4",
|
|
537
|
+
"@types/node": "^22.15.29",
|
|
538
|
+
esbuild: "^0.25.5",
|
|
539
|
+
typescript: "^5.8.3"
|
|
438
540
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
stdio: "inherit"
|
|
445
|
-
});
|
|
446
|
-
childProcess.on("error", (error) => {
|
|
447
|
-
resolve({
|
|
448
|
-
success: false,
|
|
449
|
-
message: `Error executing command: ${error.message}`
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
childProcess.on("exit", (code, signal) => {
|
|
453
|
-
if (signal) {
|
|
454
|
-
resolve({
|
|
455
|
-
success: false,
|
|
456
|
-
message: `Command terminated by signal: ${signal}`,
|
|
457
|
-
exitCode: 128 + (signal === "SIGTERM" ? 15 : 1)
|
|
458
|
-
});
|
|
459
|
-
} else {
|
|
460
|
-
const exitCode = code ?? 0;
|
|
461
|
-
resolve({
|
|
462
|
-
success: exitCode === 0,
|
|
463
|
-
exitCode
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
});
|
|
467
|
-
});
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/commands/version.ts
|
|
544
|
+
function getVersion() {
|
|
545
|
+
return package_default.version;
|
|
468
546
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
exit5(1);
|
|
473
|
-
}
|
|
474
|
-
const gardenName = args2[0];
|
|
475
|
-
const command2 = args2.slice(1);
|
|
476
|
-
const result = await execInGarden(gardenName, command2);
|
|
477
|
-
if (!result.success) {
|
|
478
|
-
if (result.message) {
|
|
479
|
-
console.error(result.message);
|
|
480
|
-
}
|
|
481
|
-
exit5(result.exitCode ?? 1);
|
|
482
|
-
}
|
|
483
|
-
exit5(result.exitCode ?? 0);
|
|
547
|
+
function versionHandler() {
|
|
548
|
+
const version = getVersion();
|
|
549
|
+
console.log(`Phantom v${version}`);
|
|
484
550
|
}
|
|
485
551
|
|
|
486
552
|
// src/bin/phantom.ts
|
|
487
553
|
var commands = [
|
|
488
554
|
{
|
|
489
|
-
name: "
|
|
490
|
-
description: "
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
{
|
|
508
|
-
name: "delete",
|
|
509
|
-
description: "Delete a garden (use --force for dirty gardens)",
|
|
510
|
-
handler: gardensDeleteHandler
|
|
511
|
-
}
|
|
512
|
-
]
|
|
555
|
+
name: "create",
|
|
556
|
+
description: "Create a new worktree [--shell to open shell]",
|
|
557
|
+
handler: createHandler
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: "list",
|
|
561
|
+
description: "List all worktrees",
|
|
562
|
+
handler: listHandler
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: "where",
|
|
566
|
+
description: "Output the path of a specific worktree",
|
|
567
|
+
handler: whereHandler
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: "delete",
|
|
571
|
+
description: "Delete a worktree (use --force for uncommitted changes)",
|
|
572
|
+
handler: deleteHandler
|
|
513
573
|
},
|
|
514
574
|
{
|
|
515
575
|
name: "exec",
|
|
516
|
-
description: "Execute a command in a
|
|
576
|
+
description: "Execute a command in a worktree directory",
|
|
517
577
|
handler: execHandler
|
|
518
578
|
},
|
|
519
579
|
{
|
|
520
580
|
name: "shell",
|
|
521
|
-
description: "Open interactive shell in a
|
|
581
|
+
description: "Open interactive shell in a worktree directory",
|
|
522
582
|
handler: shellHandler
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "version",
|
|
586
|
+
description: "Display phantom version",
|
|
587
|
+
handler: versionHandler
|
|
523
588
|
}
|
|
524
589
|
];
|
|
525
|
-
function printHelp(commands2
|
|
590
|
+
function printHelp(commands2) {
|
|
526
591
|
console.log("Usage: phantom <command> [options]\n");
|
|
527
592
|
console.log("Commands:");
|
|
528
593
|
for (const cmd of commands2) {
|
|
529
|
-
console.log(` ${
|
|
530
|
-
if (cmd.subcommands) {
|
|
531
|
-
for (const subcmd of cmd.subcommands) {
|
|
532
|
-
console.log(` ${subcmd.name.padEnd(18)} ${subcmd.description}`);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
594
|
+
console.log(` ${cmd.name.padEnd(12)} ${cmd.description}`);
|
|
535
595
|
}
|
|
536
596
|
}
|
|
537
597
|
function findCommand(args2, commands2) {
|
|
@@ -559,6 +619,10 @@ if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
|
|
|
559
619
|
printHelp(commands);
|
|
560
620
|
exit6(0);
|
|
561
621
|
}
|
|
622
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
623
|
+
versionHandler();
|
|
624
|
+
exit6(0);
|
|
625
|
+
}
|
|
562
626
|
var { command, remainingArgs } = findCommand(args, commands);
|
|
563
627
|
if (!command || !command.handler) {
|
|
564
628
|
console.error(`Error: Unknown command '${args.join(" ")}'
|