@aku11i/phantom 0.3.0 → 0.4.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 +70 -24
- package/README.md +68 -22
- package/dist/garden.js +458 -0
- package/dist/garden.js.map +7 -0
- package/dist/phantom.js +672 -390
- package/dist/phantom.js.map +4 -4
- package/package.json +3 -2
package/dist/phantom.js
CHANGED
|
@@ -1,493 +1,723 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/phantom.ts
|
|
4
|
-
import { argv, exit
|
|
4
|
+
import { argv, exit } from "node:process";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
7
|
-
import {
|
|
8
|
-
import { join as join2 } from "node:path";
|
|
9
|
-
import { exit as exit3 } from "node:process";
|
|
6
|
+
// src/cli/handlers/create.ts
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
10
8
|
|
|
11
|
-
// src/git/
|
|
12
|
-
import {
|
|
9
|
+
// src/core/git/executor.ts
|
|
10
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
13
11
|
import { promisify } from "node:util";
|
|
14
|
-
var
|
|
15
|
-
async function
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
var execFile = promisify(execFileCallback);
|
|
13
|
+
async function executeGitCommand(args2, options = {}) {
|
|
14
|
+
try {
|
|
15
|
+
const result = await execFile("git", args2, {
|
|
16
|
+
cwd: options.cwd,
|
|
17
|
+
env: options.env || process.env,
|
|
18
|
+
encoding: "utf8"
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
stdout: result.stdout.trim(),
|
|
22
|
+
stderr: result.stderr.trim()
|
|
23
|
+
};
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error && typeof error === "object" && "stdout" in error && "stderr" in error) {
|
|
26
|
+
const execError = error;
|
|
27
|
+
if (execError.stderr?.trim()) {
|
|
28
|
+
throw new Error(execError.stderr.trim());
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
stdout: execError.stdout?.trim() || "",
|
|
32
|
+
stderr: execError.stderr?.trim() || ""
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function executeGitCommandInDirectory(directory, args2) {
|
|
39
|
+
return executeGitCommand(["-C", directory, ...args2], {});
|
|
18
40
|
}
|
|
19
41
|
|
|
20
|
-
// src/git/libs/get-git-root.ts
|
|
21
|
-
import { exec as exec2 } from "node:child_process";
|
|
22
|
-
import { promisify as promisify2 } from "node:util";
|
|
23
|
-
var execAsync2 = promisify2(exec2);
|
|
42
|
+
// src/core/git/libs/get-git-root.ts
|
|
24
43
|
async function getGitRoot() {
|
|
25
|
-
const { stdout } = await
|
|
26
|
-
return stdout
|
|
44
|
+
const { stdout } = await executeGitCommand(["rev-parse", "--show-toplevel"]);
|
|
45
|
+
return stdout;
|
|
27
46
|
}
|
|
28
47
|
|
|
29
|
-
// src/
|
|
30
|
-
|
|
31
|
-
|
|
48
|
+
// src/core/types/result.ts
|
|
49
|
+
var ok = (value) => ({
|
|
50
|
+
ok: true,
|
|
51
|
+
value
|
|
52
|
+
});
|
|
53
|
+
var err = (error) => ({
|
|
54
|
+
ok: false,
|
|
55
|
+
error
|
|
56
|
+
});
|
|
57
|
+
var isOk = (result) => result.ok;
|
|
58
|
+
var isErr = (result) => !result.ok;
|
|
32
59
|
|
|
33
|
-
// src/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
// src/core/worktree/errors.ts
|
|
61
|
+
var WorktreeError = class extends Error {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "WorktreeError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var WorktreeNotFoundError = class extends WorktreeError {
|
|
68
|
+
constructor(name) {
|
|
69
|
+
super(`Worktree '${name}' not found`);
|
|
70
|
+
this.name = "WorktreeNotFoundError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var WorktreeAlreadyExistsError = class extends WorktreeError {
|
|
74
|
+
constructor(name) {
|
|
75
|
+
super(`Worktree '${name}' already exists`);
|
|
76
|
+
this.name = "WorktreeAlreadyExistsError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var GitOperationError = class extends WorktreeError {
|
|
80
|
+
constructor(operation, details) {
|
|
81
|
+
super(`Git ${operation} failed: ${details}`);
|
|
82
|
+
this.name = "GitOperationError";
|
|
40
83
|
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/core/worktree/validate.ts
|
|
87
|
+
import fs from "node:fs/promises";
|
|
88
|
+
|
|
89
|
+
// src/core/paths.ts
|
|
90
|
+
import { join } from "node:path";
|
|
91
|
+
function getPhantomDirectory(gitRoot) {
|
|
92
|
+
return join(gitRoot, ".git", "phantom", "worktrees");
|
|
93
|
+
}
|
|
94
|
+
function getWorktreePath(gitRoot, name) {
|
|
95
|
+
return join(getPhantomDirectory(gitRoot), name);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/core/worktree/validate.ts
|
|
99
|
+
async function validateWorktreeExists(gitRoot, name) {
|
|
100
|
+
const worktreePath = getWorktreePath(gitRoot, name);
|
|
41
101
|
try {
|
|
42
|
-
|
|
43
|
-
const worktreesPath = join(gitRoot, ".git", "phantom", "worktrees");
|
|
44
|
-
const worktreePath = join(worktreesPath, name);
|
|
45
|
-
try {
|
|
46
|
-
await access(worktreePath);
|
|
47
|
-
} catch {
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
message: `Error: Worktree '${name}' does not exist`
|
|
51
|
-
};
|
|
52
|
-
}
|
|
102
|
+
await fs.access(worktreePath);
|
|
53
103
|
return {
|
|
54
|
-
|
|
104
|
+
exists: true,
|
|
55
105
|
path: worktreePath
|
|
56
106
|
};
|
|
57
|
-
} catch
|
|
58
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
107
|
+
} catch {
|
|
59
108
|
return {
|
|
60
|
-
|
|
61
|
-
message: `
|
|
109
|
+
exists: false,
|
|
110
|
+
message: `Worktree '${name}' does not exist`
|
|
62
111
|
};
|
|
63
112
|
}
|
|
64
113
|
}
|
|
65
|
-
async function
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
async function validateWorktreeDoesNotExist(gitRoot, name) {
|
|
115
|
+
const worktreePath = getWorktreePath(gitRoot, name);
|
|
116
|
+
try {
|
|
117
|
+
await fs.access(worktreePath);
|
|
118
|
+
return {
|
|
119
|
+
exists: true,
|
|
120
|
+
message: `Worktree '${name}' already exists`
|
|
121
|
+
};
|
|
122
|
+
} catch {
|
|
123
|
+
return {
|
|
124
|
+
exists: false,
|
|
125
|
+
path: worktreePath
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function validatePhantomDirectoryExists(gitRoot) {
|
|
130
|
+
const phantomDir = getPhantomDirectory(gitRoot);
|
|
131
|
+
try {
|
|
132
|
+
await fs.access(phantomDir);
|
|
133
|
+
return true;
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function listValidWorktrees(gitRoot) {
|
|
139
|
+
const phantomDir = getPhantomDirectory(gitRoot);
|
|
140
|
+
if (!await validatePhantomDirectoryExists(gitRoot)) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const entries = await fs.readdir(phantomDir);
|
|
145
|
+
const validWorktrees = [];
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const result = await validateWorktreeExists(gitRoot, entry);
|
|
148
|
+
if (result.exists) {
|
|
149
|
+
validWorktrees.push(entry);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return validWorktrees;
|
|
153
|
+
} catch {
|
|
154
|
+
return [];
|
|
71
155
|
}
|
|
72
|
-
console.log(result.path);
|
|
73
156
|
}
|
|
74
157
|
|
|
75
|
-
// src/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
158
|
+
// src/core/process/spawn.ts
|
|
159
|
+
import {
|
|
160
|
+
spawn as nodeSpawn
|
|
161
|
+
} from "node:child_process";
|
|
162
|
+
|
|
163
|
+
// src/core/process/errors.ts
|
|
164
|
+
var ProcessError = class extends Error {
|
|
165
|
+
exitCode;
|
|
166
|
+
constructor(message, exitCode) {
|
|
167
|
+
super(message);
|
|
168
|
+
this.name = "ProcessError";
|
|
169
|
+
this.exitCode = exitCode;
|
|
79
170
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
171
|
+
};
|
|
172
|
+
var ProcessExecutionError = class extends ProcessError {
|
|
173
|
+
constructor(command2, exitCode) {
|
|
174
|
+
super(`Command '${command2}' failed with exit code ${exitCode}`, exitCode);
|
|
175
|
+
this.name = "ProcessExecutionError";
|
|
83
176
|
}
|
|
84
|
-
|
|
85
|
-
|
|
177
|
+
};
|
|
178
|
+
var ProcessSignalError = class extends ProcessError {
|
|
179
|
+
constructor(signal) {
|
|
180
|
+
const exitCode = 128 + (signal === "SIGTERM" ? 15 : 1);
|
|
181
|
+
super(`Command terminated by signal: ${signal}`, exitCode);
|
|
182
|
+
this.name = "ProcessSignalError";
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
var ProcessSpawnError = class extends ProcessError {
|
|
186
|
+
constructor(command2, details) {
|
|
187
|
+
super(`Error executing command '${command2}': ${details}`);
|
|
188
|
+
this.name = "ProcessSpawnError";
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/core/process/spawn.ts
|
|
193
|
+
async function spawnProcess(config) {
|
|
86
194
|
return new Promise((resolve) => {
|
|
87
|
-
const
|
|
88
|
-
|
|
195
|
+
const { command: command2, args: args2 = [], options = {} } = config;
|
|
196
|
+
const childProcess = nodeSpawn(command2, args2, {
|
|
89
197
|
stdio: "inherit",
|
|
90
|
-
|
|
91
|
-
...process.env,
|
|
92
|
-
// Add environment variable to indicate we're in a worktree
|
|
93
|
-
WORKTREE_NAME: worktreeName,
|
|
94
|
-
WORKTREE_PATH: worktreePath
|
|
95
|
-
}
|
|
198
|
+
...options
|
|
96
199
|
});
|
|
97
200
|
childProcess.on("error", (error) => {
|
|
98
|
-
resolve(
|
|
99
|
-
success: false,
|
|
100
|
-
message: `Error starting shell: ${error.message}`
|
|
101
|
-
});
|
|
201
|
+
resolve(err(new ProcessSpawnError(command2, error.message)));
|
|
102
202
|
});
|
|
103
203
|
childProcess.on("exit", (code, signal) => {
|
|
104
204
|
if (signal) {
|
|
105
|
-
resolve(
|
|
106
|
-
success: false,
|
|
107
|
-
message: `Shell terminated by signal: ${signal}`,
|
|
108
|
-
exitCode: 128 + (signal === "SIGTERM" ? 15 : 1)
|
|
109
|
-
});
|
|
205
|
+
resolve(err(new ProcessSignalError(signal)));
|
|
110
206
|
} else {
|
|
111
207
|
const exitCode = code ?? 0;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
208
|
+
if (exitCode === 0) {
|
|
209
|
+
resolve(ok({ exitCode }));
|
|
210
|
+
} else {
|
|
211
|
+
resolve(err(new ProcessExecutionError(command2, exitCode)));
|
|
212
|
+
}
|
|
116
213
|
}
|
|
117
214
|
});
|
|
118
215
|
});
|
|
119
216
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!result.success) {
|
|
135
|
-
if (result.message) {
|
|
136
|
-
console.error(result.message);
|
|
217
|
+
|
|
218
|
+
// src/core/process/exec.ts
|
|
219
|
+
async function execInWorktree(gitRoot, worktreeName, command2) {
|
|
220
|
+
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
221
|
+
if (!validation.exists) {
|
|
222
|
+
return err(new WorktreeNotFoundError(worktreeName));
|
|
223
|
+
}
|
|
224
|
+
const worktreePath = validation.path;
|
|
225
|
+
const [cmd, ...args2] = command2;
|
|
226
|
+
return spawnProcess({
|
|
227
|
+
command: cmd,
|
|
228
|
+
args: args2,
|
|
229
|
+
options: {
|
|
230
|
+
cwd: worktreePath
|
|
137
231
|
}
|
|
138
|
-
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/core/process/shell.ts
|
|
236
|
+
async function shellInWorktree(gitRoot, worktreeName) {
|
|
237
|
+
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
238
|
+
if (!validation.exists) {
|
|
239
|
+
return err(new WorktreeNotFoundError(worktreeName));
|
|
139
240
|
}
|
|
140
|
-
|
|
241
|
+
const worktreePath = validation.path;
|
|
242
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
243
|
+
return spawnProcess({
|
|
244
|
+
command: shell,
|
|
245
|
+
args: [],
|
|
246
|
+
options: {
|
|
247
|
+
cwd: worktreePath,
|
|
248
|
+
env: {
|
|
249
|
+
...process.env,
|
|
250
|
+
PHANTOM: "1",
|
|
251
|
+
PHANTOM_NAME: worktreeName,
|
|
252
|
+
PHANTOM_PATH: worktreePath
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/core/worktree/create.ts
|
|
259
|
+
import fs2 from "node:fs/promises";
|
|
260
|
+
|
|
261
|
+
// src/core/git/libs/add-worktree.ts
|
|
262
|
+
async function addWorktree(options) {
|
|
263
|
+
const { path, branch, commitish = "HEAD" } = options;
|
|
264
|
+
await executeGitCommand(["worktree", "add", path, "-b", branch, commitish]);
|
|
141
265
|
}
|
|
142
266
|
|
|
143
|
-
// src/
|
|
144
|
-
async function createWorktree(name) {
|
|
145
|
-
|
|
146
|
-
|
|
267
|
+
// src/core/worktree/create.ts
|
|
268
|
+
async function createWorktree(gitRoot, name, options = {}) {
|
|
269
|
+
const { branch = name, commitish = "HEAD" } = options;
|
|
270
|
+
const worktreesPath = getPhantomDirectory(gitRoot);
|
|
271
|
+
const worktreePath = getWorktreePath(gitRoot, name);
|
|
272
|
+
try {
|
|
273
|
+
await fs2.access(worktreesPath);
|
|
274
|
+
} catch {
|
|
275
|
+
await fs2.mkdir(worktreesPath, { recursive: true });
|
|
276
|
+
}
|
|
277
|
+
const validation = await validateWorktreeDoesNotExist(gitRoot, name);
|
|
278
|
+
if (validation.exists) {
|
|
279
|
+
return err(new WorktreeAlreadyExistsError(name));
|
|
147
280
|
}
|
|
148
281
|
try {
|
|
149
|
-
const gitRoot = await getGitRoot();
|
|
150
|
-
const worktreesPath = join2(gitRoot, ".git", "phantom", "worktrees");
|
|
151
|
-
const worktreePath = join2(worktreesPath, name);
|
|
152
|
-
try {
|
|
153
|
-
await access2(worktreesPath);
|
|
154
|
-
} catch {
|
|
155
|
-
await mkdir(worktreesPath, { recursive: true });
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
await access2(worktreePath);
|
|
159
|
-
return {
|
|
160
|
-
success: false,
|
|
161
|
-
message: `Error: worktree '${name}' already exists`
|
|
162
|
-
};
|
|
163
|
-
} catch {
|
|
164
|
-
}
|
|
165
282
|
await addWorktree({
|
|
166
283
|
path: worktreePath,
|
|
167
|
-
branch
|
|
168
|
-
commitish
|
|
284
|
+
branch,
|
|
285
|
+
commitish
|
|
169
286
|
});
|
|
170
|
-
return {
|
|
171
|
-
success: true,
|
|
287
|
+
return ok({
|
|
172
288
|
message: `Created worktree '${name}' at ${worktreePath}`,
|
|
173
289
|
path: worktreePath
|
|
174
|
-
};
|
|
290
|
+
});
|
|
175
291
|
} catch (error) {
|
|
176
292
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
177
|
-
return
|
|
178
|
-
success: false,
|
|
179
|
-
message: `Error creating worktree: ${errorMessage}`
|
|
180
|
-
};
|
|
293
|
+
return err(new GitOperationError("worktree add", errorMessage));
|
|
181
294
|
}
|
|
182
295
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (shellResult.message) {
|
|
199
|
-
console.error(shellResult.message);
|
|
200
|
-
}
|
|
201
|
-
exit3(shellResult.exitCode ?? 1);
|
|
202
|
-
}
|
|
203
|
-
exit3(shellResult.exitCode ?? 0);
|
|
296
|
+
|
|
297
|
+
// src/cli/output.ts
|
|
298
|
+
var output = {
|
|
299
|
+
log: (message) => {
|
|
300
|
+
console.log(message);
|
|
301
|
+
},
|
|
302
|
+
error: (message) => {
|
|
303
|
+
console.error(message);
|
|
304
|
+
},
|
|
305
|
+
table: (data) => {
|
|
306
|
+
console.table(data);
|
|
307
|
+
},
|
|
308
|
+
processOutput: (proc) => {
|
|
309
|
+
proc.stdout?.pipe(process.stdout);
|
|
310
|
+
proc.stderr?.pipe(process.stderr);
|
|
204
311
|
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/cli/errors.ts
|
|
315
|
+
var exitCodes = {
|
|
316
|
+
success: 0,
|
|
317
|
+
generalError: 1,
|
|
318
|
+
notFound: 2,
|
|
319
|
+
validationError: 3
|
|
320
|
+
};
|
|
321
|
+
function exitWithSuccess() {
|
|
322
|
+
process.exit(exitCodes.success);
|
|
323
|
+
}
|
|
324
|
+
function exitWithError(message, exitCode = exitCodes.generalError) {
|
|
325
|
+
output.error(message);
|
|
326
|
+
process.exit(exitCode);
|
|
205
327
|
}
|
|
206
328
|
|
|
207
|
-
// src/
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
329
|
+
// src/cli/handlers/create.ts
|
|
330
|
+
async function createHandler(args2) {
|
|
331
|
+
const { values, positionals } = parseArgs({
|
|
332
|
+
args: args2,
|
|
333
|
+
options: {
|
|
334
|
+
shell: {
|
|
335
|
+
type: "boolean",
|
|
336
|
+
short: "s"
|
|
337
|
+
},
|
|
338
|
+
exec: {
|
|
339
|
+
type: "string",
|
|
340
|
+
short: "x"
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
strict: true,
|
|
344
|
+
allowPositionals: true
|
|
345
|
+
});
|
|
346
|
+
if (positionals.length === 0) {
|
|
347
|
+
exitWithError(
|
|
348
|
+
"Please provide a name for the new worktree",
|
|
349
|
+
exitCodes.validationError
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const worktreeName = positionals[0];
|
|
353
|
+
const openShell = values.shell ?? false;
|
|
354
|
+
const execCommand = values.exec;
|
|
355
|
+
if (openShell && execCommand) {
|
|
356
|
+
exitWithError(
|
|
357
|
+
"Cannot use --shell and --exec together",
|
|
358
|
+
exitCodes.validationError
|
|
359
|
+
);
|
|
217
360
|
}
|
|
218
|
-
const { force = false } = options;
|
|
219
361
|
try {
|
|
220
362
|
const gitRoot = await getGitRoot();
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
} catch {
|
|
226
|
-
return {
|
|
227
|
-
success: false,
|
|
228
|
-
message: `Error: Worktree '${name}' does not exist`
|
|
229
|
-
};
|
|
363
|
+
const result = await createWorktree(gitRoot, worktreeName);
|
|
364
|
+
if (isErr(result)) {
|
|
365
|
+
const exitCode = result.error instanceof WorktreeAlreadyExistsError ? exitCodes.validationError : exitCodes.generalError;
|
|
366
|
+
exitWithError(result.error.message, exitCode);
|
|
230
367
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
368
|
+
output.log(result.value.message);
|
|
369
|
+
if (execCommand && isOk(result)) {
|
|
370
|
+
output.log(
|
|
371
|
+
`
|
|
372
|
+
Executing command in worktree '${worktreeName}': ${execCommand}`
|
|
373
|
+
);
|
|
374
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
375
|
+
const execResult = await execInWorktree(gitRoot, worktreeName, [
|
|
376
|
+
shell,
|
|
377
|
+
"-c",
|
|
378
|
+
execCommand
|
|
379
|
+
]);
|
|
380
|
+
if (isErr(execResult)) {
|
|
381
|
+
output.error(execResult.error.message);
|
|
382
|
+
const exitCode = "exitCode" in execResult.error ? execResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
|
|
383
|
+
exitWithError("", exitCode);
|
|
241
384
|
}
|
|
242
|
-
|
|
243
|
-
hasUncommittedChanges = false;
|
|
385
|
+
process.exit(execResult.value.exitCode ?? 0);
|
|
244
386
|
}
|
|
245
|
-
if (
|
|
387
|
+
if (openShell && isOk(result)) {
|
|
388
|
+
output.log(
|
|
389
|
+
`
|
|
390
|
+
Entering worktree '${worktreeName}' at ${result.value.path}`
|
|
391
|
+
);
|
|
392
|
+
output.log("Type 'exit' to return to your original directory\n");
|
|
393
|
+
const shellResult = await shellInWorktree(gitRoot, worktreeName);
|
|
394
|
+
if (isErr(shellResult)) {
|
|
395
|
+
output.error(shellResult.error.message);
|
|
396
|
+
const exitCode = "exitCode" in shellResult.error ? shellResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
|
|
397
|
+
exitWithError("", exitCode);
|
|
398
|
+
}
|
|
399
|
+
process.exit(shellResult.value.exitCode ?? 0);
|
|
400
|
+
}
|
|
401
|
+
exitWithSuccess();
|
|
402
|
+
} catch (error) {
|
|
403
|
+
exitWithError(
|
|
404
|
+
error instanceof Error ? error.message : String(error),
|
|
405
|
+
exitCodes.generalError
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/cli/handlers/delete.ts
|
|
411
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
412
|
+
|
|
413
|
+
// src/core/worktree/delete.ts
|
|
414
|
+
async function getWorktreeStatus(worktreePath) {
|
|
415
|
+
try {
|
|
416
|
+
const { stdout } = await executeGitCommandInDirectory(worktreePath, [
|
|
417
|
+
"status",
|
|
418
|
+
"--porcelain"
|
|
419
|
+
]);
|
|
420
|
+
if (stdout) {
|
|
246
421
|
return {
|
|
247
|
-
success: false,
|
|
248
|
-
message: `Error: Worktree '${name}' has uncommitted changes (${changedFiles} files). Use --force to delete anyway.`,
|
|
249
422
|
hasUncommittedChanges: true,
|
|
250
|
-
changedFiles
|
|
423
|
+
changedFiles: stdout.split("\n").length
|
|
251
424
|
};
|
|
252
425
|
}
|
|
426
|
+
} catch {
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
hasUncommittedChanges: false,
|
|
430
|
+
changedFiles: 0
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
async function removeWorktree(gitRoot, worktreePath, force = false) {
|
|
434
|
+
try {
|
|
435
|
+
await executeGitCommand(["worktree", "remove", worktreePath], {
|
|
436
|
+
cwd: gitRoot
|
|
437
|
+
});
|
|
438
|
+
} catch (error) {
|
|
253
439
|
try {
|
|
254
|
-
await
|
|
255
|
-
cwd: gitRoot
|
|
256
|
-
});
|
|
257
|
-
} catch (error) {
|
|
258
|
-
try {
|
|
259
|
-
await execAsync3(`git worktree remove --force "${worktreePath}"`, {
|
|
260
|
-
cwd: gitRoot
|
|
261
|
-
});
|
|
262
|
-
} catch {
|
|
263
|
-
return {
|
|
264
|
-
success: false,
|
|
265
|
-
message: `Error: Failed to remove worktree '${name}'`
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
const branchName = `phantom/worktrees/${name}`;
|
|
270
|
-
try {
|
|
271
|
-
await execAsync3(`git branch -D "${branchName}"`, {
|
|
440
|
+
await executeGitCommand(["worktree", "remove", "--force", worktreePath], {
|
|
272
441
|
cwd: gitRoot
|
|
273
442
|
});
|
|
274
443
|
} catch {
|
|
444
|
+
throw new Error("Failed to remove worktree");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async function deleteBranch(gitRoot, branchName) {
|
|
449
|
+
try {
|
|
450
|
+
await executeGitCommand(["branch", "-D", branchName], { cwd: gitRoot });
|
|
451
|
+
return ok(true);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
454
|
+
return err(new GitOperationError("branch delete", errorMessage));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async function deleteWorktree(gitRoot, name, options = {}) {
|
|
458
|
+
const { force = false } = options;
|
|
459
|
+
const validation = await validateWorktreeExists(gitRoot, name);
|
|
460
|
+
if (!validation.exists) {
|
|
461
|
+
return err(new WorktreeNotFoundError(name));
|
|
462
|
+
}
|
|
463
|
+
const worktreePath = validation.path;
|
|
464
|
+
const status = await getWorktreeStatus(worktreePath);
|
|
465
|
+
if (status.hasUncommittedChanges && !force) {
|
|
466
|
+
return err(
|
|
467
|
+
new WorktreeError(
|
|
468
|
+
`Worktree '${name}' has uncommitted changes (${status.changedFiles} files). Use --force to delete anyway.`
|
|
469
|
+
)
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
await removeWorktree(gitRoot, worktreePath, force);
|
|
474
|
+
const branchName = name;
|
|
475
|
+
const branchResult = await deleteBranch(gitRoot, branchName);
|
|
476
|
+
let message;
|
|
477
|
+
if (isOk(branchResult)) {
|
|
478
|
+
message = `Deleted worktree '${name}' and its branch '${branchName}'`;
|
|
479
|
+
} else {
|
|
480
|
+
message = `Deleted worktree '${name}'`;
|
|
481
|
+
message += `
|
|
482
|
+
Note: Branch '${branchName}' could not be deleted: ${branchResult.error.message}`;
|
|
275
483
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
message = `Warning: Worktree '${name}' had uncommitted changes (${changedFiles} files)
|
|
484
|
+
if (status.hasUncommittedChanges) {
|
|
485
|
+
message = `Warning: Worktree '${name}' had uncommitted changes (${status.changedFiles} files)
|
|
279
486
|
${message}`;
|
|
280
487
|
}
|
|
281
|
-
return {
|
|
282
|
-
success: true,
|
|
488
|
+
return ok({
|
|
283
489
|
message,
|
|
284
|
-
hasUncommittedChanges,
|
|
285
|
-
changedFiles: hasUncommittedChanges ? changedFiles : void 0
|
|
286
|
-
};
|
|
490
|
+
hasUncommittedChanges: status.hasUncommittedChanges,
|
|
491
|
+
changedFiles: status.hasUncommittedChanges ? status.changedFiles : void 0
|
|
492
|
+
});
|
|
287
493
|
} catch (error) {
|
|
288
494
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
289
|
-
return
|
|
290
|
-
success: false,
|
|
291
|
-
message: `Error deleting worktree: ${errorMessage}`
|
|
292
|
-
};
|
|
495
|
+
return err(new GitOperationError("worktree remove", errorMessage));
|
|
293
496
|
}
|
|
294
497
|
}
|
|
498
|
+
|
|
499
|
+
// src/cli/handlers/delete.ts
|
|
295
500
|
async function deleteHandler(args2) {
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
501
|
+
const { values, positionals } = parseArgs2({
|
|
502
|
+
args: args2,
|
|
503
|
+
options: {
|
|
504
|
+
force: {
|
|
505
|
+
type: "boolean",
|
|
506
|
+
short: "f"
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
strict: true,
|
|
510
|
+
allowPositionals: true
|
|
511
|
+
});
|
|
512
|
+
if (positionals.length === 0) {
|
|
513
|
+
exitWithError(
|
|
514
|
+
"Please provide a worktree name to delete",
|
|
515
|
+
exitCodes.validationError
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
const worktreeName = positionals[0];
|
|
519
|
+
const forceDelete = values.force ?? false;
|
|
520
|
+
try {
|
|
521
|
+
const gitRoot = await getGitRoot();
|
|
522
|
+
const result = await deleteWorktree(gitRoot, worktreeName, {
|
|
523
|
+
force: forceDelete
|
|
524
|
+
});
|
|
525
|
+
if (isErr(result)) {
|
|
526
|
+
const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.validationError : result.error instanceof WorktreeError && result.error.message.includes("uncommitted changes") ? exitCodes.validationError : exitCodes.generalError;
|
|
527
|
+
exitWithError(result.error.message, exitCode);
|
|
528
|
+
}
|
|
529
|
+
output.log(result.value.message);
|
|
530
|
+
exitWithSuccess();
|
|
531
|
+
} catch (error) {
|
|
532
|
+
exitWithError(
|
|
533
|
+
error instanceof Error ? error.message : String(error),
|
|
534
|
+
exitCodes.generalError
|
|
535
|
+
);
|
|
304
536
|
}
|
|
305
|
-
console.log(result.message);
|
|
306
537
|
}
|
|
307
538
|
|
|
308
|
-
// src/
|
|
309
|
-
import {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
539
|
+
// src/cli/handlers/exec.ts
|
|
540
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
541
|
+
async function execHandler(args2) {
|
|
542
|
+
const { positionals } = parseArgs3({
|
|
543
|
+
args: args2,
|
|
544
|
+
options: {},
|
|
545
|
+
strict: true,
|
|
546
|
+
allowPositionals: true
|
|
547
|
+
});
|
|
548
|
+
if (positionals.length < 2) {
|
|
549
|
+
exitWithError(
|
|
550
|
+
"Usage: phantom exec <worktree-name> <command> [args...]",
|
|
551
|
+
exitCodes.validationError
|
|
552
|
+
);
|
|
314
553
|
}
|
|
315
|
-
|
|
316
|
-
|
|
554
|
+
const [worktreeName, ...commandArgs] = positionals;
|
|
555
|
+
try {
|
|
556
|
+
const gitRoot = await getGitRoot();
|
|
557
|
+
const result = await execInWorktree(gitRoot, worktreeName, commandArgs);
|
|
558
|
+
if (isErr(result)) {
|
|
559
|
+
const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.notFound : result.error.exitCode || exitCodes.generalError;
|
|
560
|
+
exitWithError(result.error.message, exitCode);
|
|
561
|
+
}
|
|
562
|
+
process.exit(result.value.exitCode);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
exitWithError(
|
|
565
|
+
error instanceof Error ? error.message : String(error),
|
|
566
|
+
exitCodes.generalError
|
|
567
|
+
);
|
|
317
568
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/cli/handlers/list.ts
|
|
572
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
573
|
+
|
|
574
|
+
// src/core/worktree/list.ts
|
|
575
|
+
async function getWorktreeBranch(worktreePath) {
|
|
576
|
+
try {
|
|
577
|
+
const { stdout } = await executeGitCommandInDirectory(worktreePath, [
|
|
578
|
+
"branch",
|
|
579
|
+
"--show-current"
|
|
580
|
+
]);
|
|
581
|
+
return stdout || "(detached HEAD)";
|
|
582
|
+
} catch {
|
|
583
|
+
return "unknown";
|
|
321
584
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
585
|
+
}
|
|
586
|
+
async function getWorktreeStatus2(worktreePath) {
|
|
587
|
+
try {
|
|
588
|
+
const { stdout } = await executeGitCommandInDirectory(worktreePath, [
|
|
589
|
+
"status",
|
|
590
|
+
"--porcelain"
|
|
591
|
+
]);
|
|
592
|
+
return !stdout;
|
|
593
|
+
} catch {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async function getWorktreeInfo(gitRoot, name) {
|
|
598
|
+
const worktreePath = getWorktreePath(gitRoot, name);
|
|
599
|
+
const [branch, isClean] = await Promise.all([
|
|
600
|
+
getWorktreeBranch(worktreePath),
|
|
601
|
+
getWorktreeStatus2(worktreePath)
|
|
602
|
+
]);
|
|
603
|
+
return {
|
|
604
|
+
name,
|
|
605
|
+
path: worktreePath,
|
|
606
|
+
branch,
|
|
607
|
+
isClean
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async function listWorktrees(gitRoot) {
|
|
611
|
+
if (!await validatePhantomDirectoryExists(gitRoot)) {
|
|
612
|
+
return ok({
|
|
613
|
+
worktrees: [],
|
|
614
|
+
message: "No worktrees found (worktrees directory doesn't exist)"
|
|
328
615
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
616
|
+
}
|
|
617
|
+
const worktreeNames = await listValidWorktrees(gitRoot);
|
|
618
|
+
if (worktreeNames.length === 0) {
|
|
619
|
+
return ok({
|
|
620
|
+
worktrees: [],
|
|
621
|
+
message: "No worktrees found"
|
|
334
622
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
} else {
|
|
343
|
-
const exitCode = code ?? 0;
|
|
344
|
-
resolve({
|
|
345
|
-
success: exitCode === 0,
|
|
346
|
-
exitCode
|
|
347
|
-
});
|
|
348
|
-
}
|
|
623
|
+
}
|
|
624
|
+
try {
|
|
625
|
+
const worktrees = await Promise.all(
|
|
626
|
+
worktreeNames.map((name) => getWorktreeInfo(gitRoot, name))
|
|
627
|
+
);
|
|
628
|
+
return ok({
|
|
629
|
+
worktrees
|
|
349
630
|
});
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
|
|
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);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
633
|
+
throw new Error(`Failed to list worktrees: ${errorMessage}`);
|
|
365
634
|
}
|
|
366
|
-
exit5(result.exitCode ?? 0);
|
|
367
635
|
}
|
|
368
636
|
|
|
369
|
-
// src/
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
637
|
+
// src/cli/handlers/list.ts
|
|
638
|
+
async function listHandler(args2 = []) {
|
|
639
|
+
parseArgs4({
|
|
640
|
+
args: args2,
|
|
641
|
+
options: {},
|
|
642
|
+
strict: true,
|
|
643
|
+
allowPositionals: false
|
|
644
|
+
});
|
|
376
645
|
try {
|
|
377
646
|
const gitRoot = await getGitRoot();
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
} catch {
|
|
382
|
-
return {
|
|
383
|
-
success: true,
|
|
384
|
-
worktrees: [],
|
|
385
|
-
message: "No worktrees found (worktrees directory doesn't exist)"
|
|
386
|
-
};
|
|
647
|
+
const result = await listWorktrees(gitRoot);
|
|
648
|
+
if (isErr(result)) {
|
|
649
|
+
exitWithError("Failed to list worktrees", exitCodes.generalError);
|
|
387
650
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
entries.map(async (entry) => {
|
|
393
|
-
try {
|
|
394
|
-
const entryPath = join4(worktreesPath, entry);
|
|
395
|
-
await access4(entryPath);
|
|
396
|
-
return entry;
|
|
397
|
-
} catch {
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
})
|
|
401
|
-
);
|
|
402
|
-
worktreeNames = validEntries.filter(
|
|
403
|
-
(entry) => entry !== null
|
|
404
|
-
);
|
|
405
|
-
} catch {
|
|
406
|
-
return {
|
|
407
|
-
success: true,
|
|
408
|
-
worktrees: [],
|
|
409
|
-
message: "No worktrees found (unable to read worktrees directory)"
|
|
410
|
-
};
|
|
651
|
+
const { worktrees, message } = result.value;
|
|
652
|
+
if (worktrees.length === 0) {
|
|
653
|
+
output.log(message || "No worktrees found.");
|
|
654
|
+
process.exit(exitCodes.success);
|
|
411
655
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
};
|
|
656
|
+
const maxNameLength = Math.max(...worktrees.map((wt) => wt.name.length));
|
|
657
|
+
for (const worktree of worktrees) {
|
|
658
|
+
const paddedName = worktree.name.padEnd(maxNameLength + 2);
|
|
659
|
+
const branchInfo = worktree.branch ? `(${worktree.branch})` : "";
|
|
660
|
+
const status = !worktree.isClean ? " [dirty]" : "";
|
|
661
|
+
output.log(`${paddedName} ${branchInfo}${status}`);
|
|
418
662
|
}
|
|
419
|
-
|
|
420
|
-
worktreeNames.map(async (name) => {
|
|
421
|
-
const worktreePath = join4(worktreesPath, name);
|
|
422
|
-
let branch = "unknown";
|
|
423
|
-
try {
|
|
424
|
-
const { stdout } = await execAsync4("git branch --show-current", {
|
|
425
|
-
cwd: worktreePath
|
|
426
|
-
});
|
|
427
|
-
branch = stdout.trim() || "detached HEAD";
|
|
428
|
-
} catch {
|
|
429
|
-
branch = "unknown";
|
|
430
|
-
}
|
|
431
|
-
let status = "clean";
|
|
432
|
-
let changedFiles;
|
|
433
|
-
try {
|
|
434
|
-
const { stdout } = await execAsync4("git status --porcelain", {
|
|
435
|
-
cwd: worktreePath
|
|
436
|
-
});
|
|
437
|
-
const changes = stdout.trim();
|
|
438
|
-
if (changes) {
|
|
439
|
-
status = "dirty";
|
|
440
|
-
changedFiles = changes.split("\n").length;
|
|
441
|
-
}
|
|
442
|
-
} catch {
|
|
443
|
-
status = "clean";
|
|
444
|
-
}
|
|
445
|
-
return {
|
|
446
|
-
name,
|
|
447
|
-
branch,
|
|
448
|
-
status,
|
|
449
|
-
changedFiles
|
|
450
|
-
};
|
|
451
|
-
})
|
|
452
|
-
);
|
|
453
|
-
return {
|
|
454
|
-
success: true,
|
|
455
|
-
worktrees
|
|
456
|
-
};
|
|
663
|
+
process.exit(exitCodes.success);
|
|
457
664
|
} catch (error) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
};
|
|
665
|
+
exitWithError(
|
|
666
|
+
error instanceof Error ? error.message : String(error),
|
|
667
|
+
exitCodes.generalError
|
|
668
|
+
);
|
|
463
669
|
}
|
|
464
670
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
671
|
+
|
|
672
|
+
// src/cli/handlers/shell.ts
|
|
673
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
674
|
+
async function shellHandler(args2) {
|
|
675
|
+
const { positionals } = parseArgs5({
|
|
676
|
+
args: args2,
|
|
677
|
+
options: {},
|
|
678
|
+
strict: true,
|
|
679
|
+
allowPositionals: true
|
|
680
|
+
});
|
|
681
|
+
if (positionals.length === 0) {
|
|
682
|
+
exitWithError(
|
|
683
|
+
"Usage: phantom shell <worktree-name>",
|
|
684
|
+
exitCodes.validationError
|
|
685
|
+
);
|
|
474
686
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
687
|
+
const worktreeName = positionals[0];
|
|
688
|
+
try {
|
|
689
|
+
const gitRoot = await getGitRoot();
|
|
690
|
+
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
691
|
+
if (!validation.exists) {
|
|
692
|
+
exitWithError(
|
|
693
|
+
validation.message || `Worktree '${worktreeName}' not found`,
|
|
694
|
+
exitCodes.generalError
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
output.log(`Entering worktree '${worktreeName}' at ${validation.path}`);
|
|
698
|
+
output.log("Type 'exit' to return to your original directory\n");
|
|
699
|
+
const result = await shellInWorktree(gitRoot, worktreeName);
|
|
700
|
+
if (isErr(result)) {
|
|
701
|
+
const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.notFound : result.error.exitCode || exitCodes.generalError;
|
|
702
|
+
exitWithError(result.error.message, exitCode);
|
|
703
|
+
}
|
|
704
|
+
process.exit(result.value.exitCode);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
exitWithError(
|
|
707
|
+
error instanceof Error ? error.message : String(error),
|
|
708
|
+
exitCodes.generalError
|
|
480
709
|
);
|
|
481
710
|
}
|
|
482
|
-
console.log(`
|
|
483
|
-
Total: ${result.worktrees.length} worktrees`);
|
|
484
711
|
}
|
|
485
712
|
|
|
713
|
+
// src/cli/handlers/version.ts
|
|
714
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
715
|
+
|
|
486
716
|
// package.json
|
|
487
717
|
var package_default = {
|
|
488
718
|
name: "@aku11i/phantom",
|
|
489
719
|
packageManager: "pnpm@10.8.1",
|
|
490
|
-
version: "0.
|
|
720
|
+
version: "0.4.0",
|
|
491
721
|
description: "A powerful CLI tool for managing Git worktrees for parallel development",
|
|
492
722
|
keywords: [
|
|
493
723
|
"git",
|
|
@@ -516,7 +746,7 @@ var package_default = {
|
|
|
516
746
|
start: "node ./src/bin/phantom.ts",
|
|
517
747
|
phantom: "node ./src/bin/phantom.ts",
|
|
518
748
|
build: "node build.ts",
|
|
519
|
-
"type-check": "
|
|
749
|
+
"type-check": "tsgo --noEmit",
|
|
520
750
|
test: "node --test --experimental-strip-types --experimental-test-module-mocks src/**/*.test.ts",
|
|
521
751
|
lint: "biome check .",
|
|
522
752
|
fix: "biome check --write .",
|
|
@@ -535,25 +765,77 @@ var package_default = {
|
|
|
535
765
|
devDependencies: {
|
|
536
766
|
"@biomejs/biome": "^1.9.4",
|
|
537
767
|
"@types/node": "^22.15.29",
|
|
768
|
+
"@typescript/native-preview": "7.0.0-dev.20250602.1",
|
|
538
769
|
esbuild: "^0.25.5",
|
|
539
770
|
typescript: "^5.8.3"
|
|
540
771
|
}
|
|
541
772
|
};
|
|
542
773
|
|
|
543
|
-
// src/
|
|
774
|
+
// src/core/version.ts
|
|
544
775
|
function getVersion() {
|
|
545
776
|
return package_default.version;
|
|
546
777
|
}
|
|
547
|
-
|
|
778
|
+
|
|
779
|
+
// src/cli/handlers/version.ts
|
|
780
|
+
function versionHandler(args2 = []) {
|
|
781
|
+
parseArgs6({
|
|
782
|
+
args: args2,
|
|
783
|
+
options: {},
|
|
784
|
+
strict: true,
|
|
785
|
+
allowPositionals: false
|
|
786
|
+
});
|
|
548
787
|
const version = getVersion();
|
|
549
|
-
|
|
788
|
+
output.log(`Phantom v${version}`);
|
|
789
|
+
exitWithSuccess();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/cli/handlers/where.ts
|
|
793
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
794
|
+
|
|
795
|
+
// src/core/worktree/where.ts
|
|
796
|
+
async function whereWorktree(gitRoot, name) {
|
|
797
|
+
const validation = await validateWorktreeExists(gitRoot, name);
|
|
798
|
+
if (!validation.exists) {
|
|
799
|
+
return err(new WorktreeNotFoundError(name));
|
|
800
|
+
}
|
|
801
|
+
return ok({
|
|
802
|
+
path: validation.path
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/cli/handlers/where.ts
|
|
807
|
+
async function whereHandler(args2) {
|
|
808
|
+
const { positionals } = parseArgs7({
|
|
809
|
+
args: args2,
|
|
810
|
+
options: {},
|
|
811
|
+
strict: true,
|
|
812
|
+
allowPositionals: true
|
|
813
|
+
});
|
|
814
|
+
if (positionals.length === 0) {
|
|
815
|
+
exitWithError("Please provide a worktree name", exitCodes.validationError);
|
|
816
|
+
}
|
|
817
|
+
const worktreeName = positionals[0];
|
|
818
|
+
try {
|
|
819
|
+
const gitRoot = await getGitRoot();
|
|
820
|
+
const result = await whereWorktree(gitRoot, worktreeName);
|
|
821
|
+
if (isErr(result)) {
|
|
822
|
+
exitWithError(result.error.message, exitCodes.notFound);
|
|
823
|
+
}
|
|
824
|
+
output.log(result.value.path);
|
|
825
|
+
exitWithSuccess();
|
|
826
|
+
} catch (error) {
|
|
827
|
+
exitWithError(
|
|
828
|
+
error instanceof Error ? error.message : String(error),
|
|
829
|
+
exitCodes.generalError
|
|
830
|
+
);
|
|
831
|
+
}
|
|
550
832
|
}
|
|
551
833
|
|
|
552
834
|
// src/bin/phantom.ts
|
|
553
835
|
var commands = [
|
|
554
836
|
{
|
|
555
837
|
name: "create",
|
|
556
|
-
description: "Create a new worktree [--shell
|
|
838
|
+
description: "Create a new worktree [--shell | --exec <command>]",
|
|
557
839
|
handler: createHandler
|
|
558
840
|
},
|
|
559
841
|
{
|
|
@@ -617,18 +899,18 @@ function findCommand(args2, commands2) {
|
|
|
617
899
|
var args = argv.slice(2);
|
|
618
900
|
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
|
|
619
901
|
printHelp(commands);
|
|
620
|
-
|
|
902
|
+
exit(0);
|
|
621
903
|
}
|
|
622
904
|
if (args[0] === "--version" || args[0] === "-v") {
|
|
623
905
|
versionHandler();
|
|
624
|
-
|
|
906
|
+
exit(0);
|
|
625
907
|
}
|
|
626
908
|
var { command, remainingArgs } = findCommand(args, commands);
|
|
627
909
|
if (!command || !command.handler) {
|
|
628
910
|
console.error(`Error: Unknown command '${args.join(" ")}'
|
|
629
911
|
`);
|
|
630
912
|
printHelp(commands);
|
|
631
|
-
|
|
913
|
+
exit(1);
|
|
632
914
|
}
|
|
633
915
|
try {
|
|
634
916
|
await command.handler(remainingArgs);
|
|
@@ -637,6 +919,6 @@ try {
|
|
|
637
919
|
"Error:",
|
|
638
920
|
error instanceof Error ? error.message : String(error)
|
|
639
921
|
);
|
|
640
|
-
|
|
922
|
+
exit(1);
|
|
641
923
|
}
|
|
642
924
|
//# sourceMappingURL=phantom.js.map
|