@aku11i/phantom 0.9.0 → 1.0.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 +97 -339
- package/README.md +95 -340
- package/dist/phantom.js +284 -78
- package/dist/phantom.js.map +4 -4
- package/package.json +1 -1
package/dist/phantom.js
CHANGED
|
@@ -67,6 +67,18 @@ var err = (error) => ({
|
|
|
67
67
|
var isOk = (result) => result.ok;
|
|
68
68
|
var isErr = (result) => !result.ok;
|
|
69
69
|
|
|
70
|
+
// src/core/worktree/validate.ts
|
|
71
|
+
import fs from "node:fs/promises";
|
|
72
|
+
|
|
73
|
+
// src/core/paths.ts
|
|
74
|
+
import { join } from "node:path";
|
|
75
|
+
function getPhantomDirectory(gitRoot) {
|
|
76
|
+
return join(gitRoot, ".git", "phantom", "worktrees");
|
|
77
|
+
}
|
|
78
|
+
function getWorktreePath(gitRoot, name) {
|
|
79
|
+
return join(getPhantomDirectory(gitRoot), name);
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
// src/core/worktree/errors.ts
|
|
71
83
|
var WorktreeError = class extends Error {
|
|
72
84
|
constructor(message) {
|
|
@@ -99,58 +111,39 @@ var BranchNotFoundError = class extends WorktreeError {
|
|
|
99
111
|
}
|
|
100
112
|
};
|
|
101
113
|
|
|
102
|
-
// src/core/worktree/validate.ts
|
|
103
|
-
import fs from "node:fs/promises";
|
|
104
|
-
|
|
105
|
-
// src/core/paths.ts
|
|
106
|
-
import { join } from "node:path";
|
|
107
|
-
function getPhantomDirectory(gitRoot) {
|
|
108
|
-
return join(gitRoot, ".git", "phantom", "worktrees");
|
|
109
|
-
}
|
|
110
|
-
function getWorktreePath(gitRoot, name) {
|
|
111
|
-
return join(getPhantomDirectory(gitRoot), name);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
114
|
// src/core/worktree/validate.ts
|
|
115
115
|
async function validateWorktreeExists(gitRoot, name) {
|
|
116
116
|
const worktreePath = getWorktreePath(gitRoot, name);
|
|
117
117
|
try {
|
|
118
118
|
await fs.access(worktreePath);
|
|
119
|
-
return {
|
|
120
|
-
exists: true,
|
|
121
|
-
path: worktreePath
|
|
122
|
-
};
|
|
119
|
+
return ok({ path: worktreePath });
|
|
123
120
|
} catch {
|
|
124
|
-
return
|
|
125
|
-
exists: false,
|
|
126
|
-
message: `Worktree '${name}' does not exist`
|
|
127
|
-
};
|
|
121
|
+
return err(new WorktreeNotFoundError(name));
|
|
128
122
|
}
|
|
129
123
|
}
|
|
130
124
|
async function validateWorktreeDoesNotExist(gitRoot, name) {
|
|
131
125
|
const worktreePath = getWorktreePath(gitRoot, name);
|
|
132
126
|
try {
|
|
133
127
|
await fs.access(worktreePath);
|
|
134
|
-
return
|
|
135
|
-
exists: true,
|
|
136
|
-
message: `Worktree '${name}' already exists`
|
|
137
|
-
};
|
|
128
|
+
return err(new WorktreeAlreadyExistsError(name));
|
|
138
129
|
} catch {
|
|
139
|
-
return {
|
|
140
|
-
exists: false,
|
|
141
|
-
path: worktreePath
|
|
142
|
-
};
|
|
130
|
+
return ok({ path: worktreePath });
|
|
143
131
|
}
|
|
144
132
|
}
|
|
145
133
|
function validateWorktreeName(name) {
|
|
146
134
|
if (!name || name.trim() === "") {
|
|
147
135
|
return err(new Error("Phantom name cannot be empty"));
|
|
148
136
|
}
|
|
149
|
-
|
|
150
|
-
|
|
137
|
+
const validNamePattern = /^[a-zA-Z0-9\-_.\/]+$/;
|
|
138
|
+
if (!validNamePattern.test(name)) {
|
|
139
|
+
return err(
|
|
140
|
+
new Error(
|
|
141
|
+
"Phantom name can only contain letters, numbers, hyphens, underscores, dots, and slashes"
|
|
142
|
+
)
|
|
143
|
+
);
|
|
151
144
|
}
|
|
152
|
-
if (name.
|
|
153
|
-
return err(new Error("Phantom name cannot
|
|
145
|
+
if (name.includes("..")) {
|
|
146
|
+
return err(new Error("Phantom name cannot contain consecutive dots"));
|
|
154
147
|
}
|
|
155
148
|
return ok(void 0);
|
|
156
149
|
}
|
|
@@ -218,10 +211,10 @@ async function spawnProcess(config) {
|
|
|
218
211
|
// src/core/process/exec.ts
|
|
219
212
|
async function execInWorktree(gitRoot, worktreeName, command2, options = {}) {
|
|
220
213
|
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
221
|
-
if (
|
|
222
|
-
return err(
|
|
214
|
+
if (isErr(validation)) {
|
|
215
|
+
return err(validation.error);
|
|
223
216
|
}
|
|
224
|
-
const worktreePath = validation.path;
|
|
217
|
+
const worktreePath = validation.value.path;
|
|
225
218
|
const [cmd, ...args2] = command2;
|
|
226
219
|
const stdio = options.interactive ? "inherit" : ["ignore", "inherit", "inherit"];
|
|
227
220
|
return spawnProcess({
|
|
@@ -234,13 +227,22 @@ async function execInWorktree(gitRoot, worktreeName, command2, options = {}) {
|
|
|
234
227
|
});
|
|
235
228
|
}
|
|
236
229
|
|
|
230
|
+
// src/core/process/env.ts
|
|
231
|
+
function getPhantomEnv(worktreeName, worktreePath) {
|
|
232
|
+
return {
|
|
233
|
+
PHANTOM: "1",
|
|
234
|
+
PHANTOM_NAME: worktreeName,
|
|
235
|
+
PHANTOM_PATH: worktreePath
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
237
239
|
// src/core/process/shell.ts
|
|
238
240
|
async function shellInWorktree(gitRoot, worktreeName) {
|
|
239
241
|
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
240
|
-
if (
|
|
241
|
-
return err(
|
|
242
|
+
if (isErr(validation)) {
|
|
243
|
+
return err(validation.error);
|
|
242
244
|
}
|
|
243
|
-
const worktreePath = validation.path;
|
|
245
|
+
const worktreePath = validation.value.path;
|
|
244
246
|
const shell = process.env.SHELL || "/bin/sh";
|
|
245
247
|
return spawnProcess({
|
|
246
248
|
command: shell,
|
|
@@ -249,9 +251,7 @@ async function shellInWorktree(gitRoot, worktreeName) {
|
|
|
249
251
|
cwd: worktreePath,
|
|
250
252
|
env: {
|
|
251
253
|
...process.env,
|
|
252
|
-
|
|
253
|
-
PHANTOM_NAME: worktreeName,
|
|
254
|
-
PHANTOM_PATH: worktreePath
|
|
254
|
+
...getPhantomEnv(worktreeName, worktreePath)
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
});
|
|
@@ -406,10 +406,11 @@ async function attachHandler(args2) {
|
|
|
406
406
|
exitWithError(shellResult.error.message, exitCodes.generalError);
|
|
407
407
|
}
|
|
408
408
|
} else if (values.exec) {
|
|
409
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
409
410
|
const execResult = await execInWorktree(
|
|
410
411
|
gitRoot,
|
|
411
412
|
branchName,
|
|
412
|
-
values.exec
|
|
413
|
+
[shell, "-c", values.exec],
|
|
413
414
|
{ interactive: true }
|
|
414
415
|
);
|
|
415
416
|
if (isErr(execResult)) {
|
|
@@ -715,11 +716,14 @@ async function isInsideTmux() {
|
|
|
715
716
|
return process.env.TMUX !== void 0;
|
|
716
717
|
}
|
|
717
718
|
async function executeTmuxCommand(options) {
|
|
718
|
-
const { direction, command: command2, cwd, env } = options;
|
|
719
|
+
const { direction, command: command2, args: args2, cwd, env, windowName } = options;
|
|
719
720
|
const tmuxArgs = [];
|
|
720
721
|
switch (direction) {
|
|
721
722
|
case "new":
|
|
722
723
|
tmuxArgs.push("new-window");
|
|
724
|
+
if (windowName) {
|
|
725
|
+
tmuxArgs.push("-n", windowName);
|
|
726
|
+
}
|
|
723
727
|
break;
|
|
724
728
|
case "vertical":
|
|
725
729
|
tmuxArgs.push("split-window", "-v");
|
|
@@ -737,6 +741,9 @@ async function executeTmuxCommand(options) {
|
|
|
737
741
|
}
|
|
738
742
|
}
|
|
739
743
|
tmuxArgs.push(command2);
|
|
744
|
+
if (args2 && args2.length > 0) {
|
|
745
|
+
tmuxArgs.push(...args2);
|
|
746
|
+
}
|
|
740
747
|
const result = await spawnProcess({
|
|
741
748
|
command: "tmux",
|
|
742
749
|
args: tmuxArgs
|
|
@@ -811,8 +818,8 @@ async function createWorktree(gitRoot, name, options = {}) {
|
|
|
811
818
|
await fs3.mkdir(worktreesPath, { recursive: true });
|
|
812
819
|
}
|
|
813
820
|
const validation = await validateWorktreeDoesNotExist(gitRoot, name);
|
|
814
|
-
if (validation
|
|
815
|
-
return err(
|
|
821
|
+
if (isErr(validation)) {
|
|
822
|
+
return err(validation.error);
|
|
816
823
|
}
|
|
817
824
|
try {
|
|
818
825
|
await addWorktree({
|
|
@@ -1017,11 +1024,8 @@ Opening worktree '${worktreeName}' in tmux ${tmuxDirection === "new" ? "window"
|
|
|
1017
1024
|
direction: tmuxDirection,
|
|
1018
1025
|
command: shell,
|
|
1019
1026
|
cwd: result.value.path,
|
|
1020
|
-
env:
|
|
1021
|
-
|
|
1022
|
-
PHANTOM_NAME: worktreeName,
|
|
1023
|
-
PHANTOM_PATH: result.value.path
|
|
1024
|
-
}
|
|
1027
|
+
env: getPhantomEnv(worktreeName, result.value.path),
|
|
1028
|
+
windowName: tmuxDirection === "new" ? worktreeName : void 0
|
|
1025
1029
|
});
|
|
1026
1030
|
if (isErr(tmuxResult)) {
|
|
1027
1031
|
output.error(tmuxResult.error.message);
|
|
@@ -1148,10 +1152,10 @@ async function deleteBranch(gitRoot, branchName) {
|
|
|
1148
1152
|
async function deleteWorktree(gitRoot, name, options = {}) {
|
|
1149
1153
|
const { force = false } = options;
|
|
1150
1154
|
const validation = await validateWorktreeExists(gitRoot, name);
|
|
1151
|
-
if (
|
|
1152
|
-
return err(
|
|
1155
|
+
if (isErr(validation)) {
|
|
1156
|
+
return err(validation.error);
|
|
1153
1157
|
}
|
|
1154
|
-
const worktreePath = validation.path;
|
|
1158
|
+
const worktreePath = validation.value.path;
|
|
1155
1159
|
const status = await getWorktreeStatus(worktreePath);
|
|
1156
1160
|
if (status.hasUncommittedChanges && !force) {
|
|
1157
1161
|
return err(
|
|
@@ -1306,7 +1310,7 @@ async function selectWorktreeWithFzf(gitRoot) {
|
|
|
1306
1310
|
});
|
|
1307
1311
|
const fzfResult = await selectWithFzf(list, {
|
|
1308
1312
|
prompt: "Select worktree> ",
|
|
1309
|
-
header: "Git Worktrees
|
|
1313
|
+
header: "Git Worktrees"
|
|
1310
1314
|
});
|
|
1311
1315
|
if (isErr(fzfResult)) {
|
|
1312
1316
|
return fzfResult;
|
|
@@ -1420,21 +1424,106 @@ async function deleteHandler(args2) {
|
|
|
1420
1424
|
// src/cli/handlers/exec.ts
|
|
1421
1425
|
import { parseArgs as parseArgs4 } from "node:util";
|
|
1422
1426
|
async function execHandler(args2) {
|
|
1423
|
-
const { positionals } = parseArgs4({
|
|
1427
|
+
const { positionals, values } = parseArgs4({
|
|
1424
1428
|
args: args2,
|
|
1425
|
-
options: {
|
|
1429
|
+
options: {
|
|
1430
|
+
fzf: {
|
|
1431
|
+
type: "boolean",
|
|
1432
|
+
default: false
|
|
1433
|
+
},
|
|
1434
|
+
tmux: {
|
|
1435
|
+
type: "boolean",
|
|
1436
|
+
short: "t"
|
|
1437
|
+
},
|
|
1438
|
+
"tmux-vertical": {
|
|
1439
|
+
type: "boolean"
|
|
1440
|
+
},
|
|
1441
|
+
"tmux-v": {
|
|
1442
|
+
type: "boolean"
|
|
1443
|
+
},
|
|
1444
|
+
"tmux-horizontal": {
|
|
1445
|
+
type: "boolean"
|
|
1446
|
+
},
|
|
1447
|
+
"tmux-h": {
|
|
1448
|
+
type: "boolean"
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1426
1451
|
strict: true,
|
|
1427
1452
|
allowPositionals: true
|
|
1428
1453
|
});
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1454
|
+
const useFzf = values.fzf ?? false;
|
|
1455
|
+
const tmuxOption = values.tmux || values["tmux-vertical"] || values["tmux-v"] || values["tmux-horizontal"] || values["tmux-h"];
|
|
1456
|
+
let tmuxDirection;
|
|
1457
|
+
if (values.tmux) {
|
|
1458
|
+
tmuxDirection = "new";
|
|
1459
|
+
} else if (values["tmux-vertical"] || values["tmux-v"]) {
|
|
1460
|
+
tmuxDirection = "vertical";
|
|
1461
|
+
} else if (values["tmux-horizontal"] || values["tmux-h"]) {
|
|
1462
|
+
tmuxDirection = "horizontal";
|
|
1463
|
+
}
|
|
1464
|
+
let commandArgs;
|
|
1465
|
+
if (useFzf) {
|
|
1466
|
+
if (positionals.length < 1) {
|
|
1467
|
+
exitWithError(
|
|
1468
|
+
"Usage: phantom exec --fzf <command> [args...]",
|
|
1469
|
+
exitCodes.validationError
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
commandArgs = positionals;
|
|
1473
|
+
} else {
|
|
1474
|
+
if (positionals.length < 2) {
|
|
1475
|
+
exitWithError(
|
|
1476
|
+
"Usage: phantom exec <worktree-name> <command> [args...]",
|
|
1477
|
+
exitCodes.validationError
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
commandArgs = positionals.slice(1);
|
|
1434
1481
|
}
|
|
1435
|
-
const [worktreeName, ...commandArgs] = positionals;
|
|
1436
1482
|
try {
|
|
1437
1483
|
const gitRoot = await getGitRoot();
|
|
1484
|
+
if (tmuxOption && !await isInsideTmux()) {
|
|
1485
|
+
exitWithError(
|
|
1486
|
+
"The --tmux option can only be used inside a tmux session",
|
|
1487
|
+
exitCodes.validationError
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
let worktreeName;
|
|
1491
|
+
if (useFzf) {
|
|
1492
|
+
const selectResult = await selectWorktreeWithFzf(gitRoot);
|
|
1493
|
+
if (isErr(selectResult)) {
|
|
1494
|
+
exitWithError(selectResult.error.message, exitCodes.generalError);
|
|
1495
|
+
}
|
|
1496
|
+
if (!selectResult.value) {
|
|
1497
|
+
exitWithSuccess();
|
|
1498
|
+
}
|
|
1499
|
+
worktreeName = selectResult.value.name;
|
|
1500
|
+
} else {
|
|
1501
|
+
worktreeName = positionals[0];
|
|
1502
|
+
}
|
|
1503
|
+
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
1504
|
+
if (isErr(validation)) {
|
|
1505
|
+
exitWithError(validation.error.message, exitCodes.generalError);
|
|
1506
|
+
}
|
|
1507
|
+
if (tmuxDirection) {
|
|
1508
|
+
output.log(
|
|
1509
|
+
`Executing command in worktree '${worktreeName}' in tmux ${tmuxDirection === "new" ? "window" : "pane"}...`
|
|
1510
|
+
);
|
|
1511
|
+
const [command2, ...args3] = commandArgs;
|
|
1512
|
+
const tmuxResult = await executeTmuxCommand({
|
|
1513
|
+
direction: tmuxDirection,
|
|
1514
|
+
command: command2,
|
|
1515
|
+
args: args3,
|
|
1516
|
+
cwd: validation.value.path,
|
|
1517
|
+
env: getPhantomEnv(worktreeName, validation.value.path),
|
|
1518
|
+
windowName: tmuxDirection === "new" ? worktreeName : void 0
|
|
1519
|
+
});
|
|
1520
|
+
if (isErr(tmuxResult)) {
|
|
1521
|
+
output.error(tmuxResult.error.message);
|
|
1522
|
+
const exitCode = "exitCode" in tmuxResult.error ? tmuxResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
|
|
1523
|
+
exitWithError("", exitCode);
|
|
1524
|
+
}
|
|
1525
|
+
exitWithSuccess();
|
|
1526
|
+
}
|
|
1438
1527
|
const result = await execInWorktree(
|
|
1439
1528
|
gitRoot,
|
|
1440
1529
|
worktreeName,
|
|
@@ -1528,12 +1617,37 @@ async function shellHandler(args2) {
|
|
|
1528
1617
|
fzf: {
|
|
1529
1618
|
type: "boolean",
|
|
1530
1619
|
default: false
|
|
1620
|
+
},
|
|
1621
|
+
tmux: {
|
|
1622
|
+
type: "boolean",
|
|
1623
|
+
short: "t"
|
|
1624
|
+
},
|
|
1625
|
+
"tmux-vertical": {
|
|
1626
|
+
type: "boolean"
|
|
1627
|
+
},
|
|
1628
|
+
"tmux-v": {
|
|
1629
|
+
type: "boolean"
|
|
1630
|
+
},
|
|
1631
|
+
"tmux-horizontal": {
|
|
1632
|
+
type: "boolean"
|
|
1633
|
+
},
|
|
1634
|
+
"tmux-h": {
|
|
1635
|
+
type: "boolean"
|
|
1531
1636
|
}
|
|
1532
1637
|
},
|
|
1533
1638
|
strict: true,
|
|
1534
1639
|
allowPositionals: true
|
|
1535
1640
|
});
|
|
1536
1641
|
const useFzf = values.fzf ?? false;
|
|
1642
|
+
const tmuxOption = values.tmux || values["tmux-vertical"] || values["tmux-v"] || values["tmux-horizontal"] || values["tmux-h"];
|
|
1643
|
+
let tmuxDirection;
|
|
1644
|
+
if (values.tmux) {
|
|
1645
|
+
tmuxDirection = "new";
|
|
1646
|
+
} else if (values["tmux-vertical"] || values["tmux-v"]) {
|
|
1647
|
+
tmuxDirection = "vertical";
|
|
1648
|
+
} else if (values["tmux-horizontal"] || values["tmux-h"]) {
|
|
1649
|
+
tmuxDirection = "horizontal";
|
|
1650
|
+
}
|
|
1537
1651
|
if (positionals.length === 0 && !useFzf) {
|
|
1538
1652
|
exitWithError(
|
|
1539
1653
|
"Usage: phantom shell <worktree-name> or phantom shell --fzf",
|
|
@@ -1549,6 +1663,12 @@ async function shellHandler(args2) {
|
|
|
1549
1663
|
let worktreeName;
|
|
1550
1664
|
try {
|
|
1551
1665
|
const gitRoot = await getGitRoot();
|
|
1666
|
+
if (tmuxOption && !await isInsideTmux()) {
|
|
1667
|
+
exitWithError(
|
|
1668
|
+
"The --tmux option can only be used inside a tmux session",
|
|
1669
|
+
exitCodes.validationError
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1552
1672
|
if (useFzf) {
|
|
1553
1673
|
const selectResult = await selectWorktreeWithFzf(gitRoot);
|
|
1554
1674
|
if (isErr(selectResult)) {
|
|
@@ -1562,13 +1682,31 @@ async function shellHandler(args2) {
|
|
|
1562
1682
|
worktreeName = positionals[0];
|
|
1563
1683
|
}
|
|
1564
1684
|
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
1565
|
-
if (
|
|
1566
|
-
exitWithError(
|
|
1567
|
-
|
|
1568
|
-
|
|
1685
|
+
if (isErr(validation)) {
|
|
1686
|
+
exitWithError(validation.error.message, exitCodes.generalError);
|
|
1687
|
+
}
|
|
1688
|
+
if (tmuxDirection) {
|
|
1689
|
+
output.log(
|
|
1690
|
+
`Opening worktree '${worktreeName}' in tmux ${tmuxDirection === "new" ? "window" : "pane"}...`
|
|
1569
1691
|
);
|
|
1692
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
1693
|
+
const tmuxResult = await executeTmuxCommand({
|
|
1694
|
+
direction: tmuxDirection,
|
|
1695
|
+
command: shell,
|
|
1696
|
+
cwd: validation.value.path,
|
|
1697
|
+
env: getPhantomEnv(worktreeName, validation.value.path),
|
|
1698
|
+
windowName: tmuxDirection === "new" ? worktreeName : void 0
|
|
1699
|
+
});
|
|
1700
|
+
if (isErr(tmuxResult)) {
|
|
1701
|
+
output.error(tmuxResult.error.message);
|
|
1702
|
+
const exitCode = "exitCode" in tmuxResult.error ? tmuxResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
|
|
1703
|
+
exitWithError("", exitCode);
|
|
1704
|
+
}
|
|
1705
|
+
exitWithSuccess();
|
|
1570
1706
|
}
|
|
1571
|
-
output.log(
|
|
1707
|
+
output.log(
|
|
1708
|
+
`Entering worktree '${worktreeName}' at ${validation.value.path}`
|
|
1709
|
+
);
|
|
1572
1710
|
output.log("Type 'exit' to return to your original directory\n");
|
|
1573
1711
|
const result = await shellInWorktree(gitRoot, worktreeName);
|
|
1574
1712
|
if (isErr(result)) {
|
|
@@ -1591,7 +1729,7 @@ import { parseArgs as parseArgs7 } from "node:util";
|
|
|
1591
1729
|
var package_default = {
|
|
1592
1730
|
name: "@aku11i/phantom",
|
|
1593
1731
|
packageManager: "pnpm@10.8.1",
|
|
1594
|
-
version: "0.
|
|
1732
|
+
version: "1.0.0",
|
|
1595
1733
|
description: "A powerful CLI tool for managing Git worktrees for parallel development",
|
|
1596
1734
|
keywords: [
|
|
1597
1735
|
"git",
|
|
@@ -1671,11 +1809,11 @@ import { parseArgs as parseArgs8 } from "node:util";
|
|
|
1671
1809
|
// src/core/worktree/where.ts
|
|
1672
1810
|
async function whereWorktree(gitRoot, name) {
|
|
1673
1811
|
const validation = await validateWorktreeExists(gitRoot, name);
|
|
1674
|
-
if (
|
|
1675
|
-
return err(
|
|
1812
|
+
if (isErr(validation)) {
|
|
1813
|
+
return err(validation.error);
|
|
1676
1814
|
}
|
|
1677
1815
|
return ok({
|
|
1678
|
-
path: validation.path
|
|
1816
|
+
path: validation.value.path
|
|
1679
1817
|
});
|
|
1680
1818
|
}
|
|
1681
1819
|
|
|
@@ -1955,7 +2093,7 @@ var completionHelp = {
|
|
|
1955
2093
|
// src/cli/help/create.ts
|
|
1956
2094
|
var createHelp = {
|
|
1957
2095
|
name: "create",
|
|
1958
|
-
description: "Create a new Git worktree
|
|
2096
|
+
description: "Create a new Git worktree",
|
|
1959
2097
|
usage: "phantom create <name> [options]",
|
|
1960
2098
|
options: [
|
|
1961
2099
|
{
|
|
@@ -2027,7 +2165,7 @@ var createHelp = {
|
|
|
2027
2165
|
// src/cli/help/delete.ts
|
|
2028
2166
|
var deleteHelp = {
|
|
2029
2167
|
name: "delete",
|
|
2030
|
-
description: "Delete a Git worktree
|
|
2168
|
+
description: "Delete a Git worktree",
|
|
2031
2169
|
usage: "phantom delete <name> [options]",
|
|
2032
2170
|
options: [
|
|
2033
2171
|
{
|
|
@@ -2076,7 +2214,29 @@ var deleteHelp = {
|
|
|
2076
2214
|
var execHelp = {
|
|
2077
2215
|
name: "exec",
|
|
2078
2216
|
description: "Execute a command in a worktree directory",
|
|
2079
|
-
usage: "phantom exec <worktree-name> <command> [args...]",
|
|
2217
|
+
usage: "phantom exec [options] <worktree-name> <command> [args...]",
|
|
2218
|
+
options: [
|
|
2219
|
+
{
|
|
2220
|
+
name: "--fzf",
|
|
2221
|
+
type: "boolean",
|
|
2222
|
+
description: "Use fzf for interactive worktree selection"
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
name: "--tmux, -t",
|
|
2226
|
+
type: "boolean",
|
|
2227
|
+
description: "Execute command in new tmux window"
|
|
2228
|
+
},
|
|
2229
|
+
{
|
|
2230
|
+
name: "--tmux-vertical, --tmux-v",
|
|
2231
|
+
type: "boolean",
|
|
2232
|
+
description: "Execute command in vertical split pane"
|
|
2233
|
+
},
|
|
2234
|
+
{
|
|
2235
|
+
name: "--tmux-horizontal, --tmux-h",
|
|
2236
|
+
type: "boolean",
|
|
2237
|
+
description: "Execute command in horizontal split pane"
|
|
2238
|
+
}
|
|
2239
|
+
],
|
|
2080
2240
|
examples: [
|
|
2081
2241
|
{
|
|
2082
2242
|
description: "Run npm test in a worktree",
|
|
@@ -2089,19 +2249,37 @@ var execHelp = {
|
|
|
2089
2249
|
{
|
|
2090
2250
|
description: "Run a complex command with arguments",
|
|
2091
2251
|
command: "phantom exec staging npm run build -- --production"
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
description: "Execute with interactive selection",
|
|
2255
|
+
command: "phantom exec --fzf npm run dev"
|
|
2256
|
+
},
|
|
2257
|
+
{
|
|
2258
|
+
description: "Run dev server in new tmux window",
|
|
2259
|
+
command: "phantom exec --tmux feature-auth npm run dev"
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
description: "Run tests in vertical split pane",
|
|
2263
|
+
command: "phantom exec --tmux-v feature-auth npm test"
|
|
2264
|
+
},
|
|
2265
|
+
{
|
|
2266
|
+
description: "Interactive selection with tmux",
|
|
2267
|
+
command: "phantom exec --fzf --tmux npm run dev"
|
|
2092
2268
|
}
|
|
2093
2269
|
],
|
|
2094
2270
|
notes: [
|
|
2095
2271
|
"The command is executed with the worktree directory as the working directory",
|
|
2096
2272
|
"All arguments after the worktree name are passed to the command",
|
|
2097
|
-
"The exit code of the executed command is preserved"
|
|
2273
|
+
"The exit code of the executed command is preserved",
|
|
2274
|
+
"With --fzf, select the worktree interactively before executing the command",
|
|
2275
|
+
"Tmux options require being inside a tmux session"
|
|
2098
2276
|
]
|
|
2099
2277
|
};
|
|
2100
2278
|
|
|
2101
2279
|
// src/cli/help/list.ts
|
|
2102
2280
|
var listHelp = {
|
|
2103
2281
|
name: "list",
|
|
2104
|
-
description: "List all Git worktrees
|
|
2282
|
+
description: "List all Git worktrees",
|
|
2105
2283
|
usage: "phantom list [options]",
|
|
2106
2284
|
options: [
|
|
2107
2285
|
{
|
|
@@ -2112,7 +2290,7 @@ var listHelp = {
|
|
|
2112
2290
|
{
|
|
2113
2291
|
name: "--names",
|
|
2114
2292
|
type: "boolean",
|
|
2115
|
-
description: "Output only
|
|
2293
|
+
description: "Output only worktree names (for scripts and completion)"
|
|
2116
2294
|
}
|
|
2117
2295
|
],
|
|
2118
2296
|
examples: [
|
|
@@ -2147,6 +2325,21 @@ var shellHelp = {
|
|
|
2147
2325
|
name: "--fzf",
|
|
2148
2326
|
type: "boolean",
|
|
2149
2327
|
description: "Use fzf for interactive selection"
|
|
2328
|
+
},
|
|
2329
|
+
{
|
|
2330
|
+
name: "--tmux, -t",
|
|
2331
|
+
type: "boolean",
|
|
2332
|
+
description: "Open shell in new tmux window"
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
name: "--tmux-vertical, --tmux-v",
|
|
2336
|
+
type: "boolean",
|
|
2337
|
+
description: "Open shell in vertical split pane"
|
|
2338
|
+
},
|
|
2339
|
+
{
|
|
2340
|
+
name: "--tmux-horizontal, --tmux-h",
|
|
2341
|
+
type: "boolean",
|
|
2342
|
+
description: "Open shell in horizontal split pane"
|
|
2150
2343
|
}
|
|
2151
2344
|
],
|
|
2152
2345
|
examples: [
|
|
@@ -2157,13 +2350,26 @@ var shellHelp = {
|
|
|
2157
2350
|
{
|
|
2158
2351
|
description: "Open a shell with interactive fzf selection",
|
|
2159
2352
|
command: "phantom shell --fzf"
|
|
2353
|
+
},
|
|
2354
|
+
{
|
|
2355
|
+
description: "Open a shell in a new tmux window",
|
|
2356
|
+
command: "phantom shell feature-auth --tmux"
|
|
2357
|
+
},
|
|
2358
|
+
{
|
|
2359
|
+
description: "Open a shell in a vertical tmux pane",
|
|
2360
|
+
command: "phantom shell feature-auth --tmux-v"
|
|
2361
|
+
},
|
|
2362
|
+
{
|
|
2363
|
+
description: "Interactive selection with tmux",
|
|
2364
|
+
command: "phantom shell --fzf --tmux"
|
|
2160
2365
|
}
|
|
2161
2366
|
],
|
|
2162
2367
|
notes: [
|
|
2163
2368
|
"Uses your default shell from the SHELL environment variable",
|
|
2164
2369
|
"The shell starts with the worktree directory as the working directory",
|
|
2165
2370
|
"Type 'exit' to return to your original directory",
|
|
2166
|
-
"With --fzf, you can interactively select the worktree to enter"
|
|
2371
|
+
"With --fzf, you can interactively select the worktree to enter",
|
|
2372
|
+
"Tmux options require being inside a tmux session"
|
|
2167
2373
|
]
|
|
2168
2374
|
};
|
|
2169
2375
|
|