@aku11i/phantom 0.6.0 → 0.8.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 +33 -3
- package/README.md +33 -3
- package/dist/phantom.js +816 -124
- package/dist/phantom.js.map +4 -4
- package/package.json +1 -1
package/dist/phantom.js
CHANGED
|
@@ -44,9 +44,9 @@ async function executeGitCommandInDirectory(directory, args2) {
|
|
|
44
44
|
|
|
45
45
|
// src/core/git/libs/get-git-root.ts
|
|
46
46
|
async function getGitRoot() {
|
|
47
|
-
const { stdout } = await executeGitCommand(["rev-parse", "--git-common-dir"]);
|
|
48
|
-
if (
|
|
49
|
-
return resolve(process.cwd(), dirname(
|
|
47
|
+
const { stdout: stdout2 } = await executeGitCommand(["rev-parse", "--git-common-dir"]);
|
|
48
|
+
if (stdout2.endsWith("/.git") || stdout2 === ".git") {
|
|
49
|
+
return resolve(process.cwd(), dirname(stdout2));
|
|
50
50
|
}
|
|
51
51
|
const { stdout: toplevel } = await executeGitCommand([
|
|
52
52
|
"rev-parse",
|
|
@@ -216,18 +216,20 @@ async function spawnProcess(config) {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// src/core/process/exec.ts
|
|
219
|
-
async function execInWorktree(gitRoot, worktreeName, command2) {
|
|
219
|
+
async function execInWorktree(gitRoot, worktreeName, command2, options = {}) {
|
|
220
220
|
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
221
221
|
if (!validation.exists) {
|
|
222
222
|
return err(new WorktreeNotFoundError(worktreeName));
|
|
223
223
|
}
|
|
224
224
|
const worktreePath = validation.path;
|
|
225
225
|
const [cmd, ...args2] = command2;
|
|
226
|
+
const stdio = options.interactive ? "inherit" : ["ignore", "inherit", "inherit"];
|
|
226
227
|
return spawnProcess({
|
|
227
228
|
command: cmd,
|
|
228
229
|
args: args2,
|
|
229
230
|
options: {
|
|
230
|
-
cwd: worktreePath
|
|
231
|
+
cwd: worktreePath,
|
|
232
|
+
stdio
|
|
231
233
|
}
|
|
232
234
|
});
|
|
233
235
|
}
|
|
@@ -407,7 +409,8 @@ async function attachHandler(args2) {
|
|
|
407
409
|
const execResult = await execInWorktree(
|
|
408
410
|
gitRoot,
|
|
409
411
|
branchName,
|
|
410
|
-
values.exec.split(" ")
|
|
412
|
+
values.exec.split(" "),
|
|
413
|
+
{ interactive: true }
|
|
411
414
|
);
|
|
412
415
|
if (isErr(execResult)) {
|
|
413
416
|
exitWithError(execResult.error.message, exitCodes.generalError);
|
|
@@ -458,6 +461,20 @@ function validateConfig(config) {
|
|
|
458
461
|
);
|
|
459
462
|
}
|
|
460
463
|
}
|
|
464
|
+
if (postCreate.commands !== void 0) {
|
|
465
|
+
if (!Array.isArray(postCreate.commands)) {
|
|
466
|
+
return err(
|
|
467
|
+
new ConfigValidationError("postCreate.commands must be an array")
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
if (!postCreate.commands.every((c) => typeof c === "string")) {
|
|
471
|
+
return err(
|
|
472
|
+
new ConfigValidationError(
|
|
473
|
+
"postCreate.commands must contain only strings"
|
|
474
|
+
)
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
461
478
|
}
|
|
462
479
|
return ok(config);
|
|
463
480
|
}
|
|
@@ -741,17 +758,42 @@ async function createHandler(args2) {
|
|
|
741
758
|
Warning: Failed to copy some files: ${result.value.copyError}`
|
|
742
759
|
);
|
|
743
760
|
}
|
|
761
|
+
if (isOk(configResult) && configResult.value.postCreate?.commands) {
|
|
762
|
+
const commands2 = configResult.value.postCreate.commands;
|
|
763
|
+
output.log("\nRunning post-create commands...");
|
|
764
|
+
for (const command2 of commands2) {
|
|
765
|
+
output.log(`Executing: ${command2}`);
|
|
766
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
767
|
+
const cmdResult = await execInWorktree(gitRoot, worktreeName, [
|
|
768
|
+
shell,
|
|
769
|
+
"-c",
|
|
770
|
+
command2
|
|
771
|
+
]);
|
|
772
|
+
if (isErr(cmdResult)) {
|
|
773
|
+
output.error(`Failed to execute command: ${cmdResult.error.message}`);
|
|
774
|
+
const exitCode = "exitCode" in cmdResult.error ? cmdResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
|
|
775
|
+
exitWithError(`Post-create command failed: ${command2}`, exitCode);
|
|
776
|
+
}
|
|
777
|
+
if (cmdResult.value.exitCode !== 0) {
|
|
778
|
+
exitWithError(
|
|
779
|
+
`Post-create command failed: ${command2}`,
|
|
780
|
+
cmdResult.value.exitCode
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
744
785
|
if (execCommand && isOk(result)) {
|
|
745
786
|
output.log(
|
|
746
787
|
`
|
|
747
788
|
Executing command in worktree '${worktreeName}': ${execCommand}`
|
|
748
789
|
);
|
|
749
790
|
const shell = process.env.SHELL || "/bin/sh";
|
|
750
|
-
const execResult = await execInWorktree(
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
execCommand
|
|
754
|
-
|
|
791
|
+
const execResult = await execInWorktree(
|
|
792
|
+
gitRoot,
|
|
793
|
+
worktreeName,
|
|
794
|
+
[shell, "-c", execCommand],
|
|
795
|
+
{ interactive: true }
|
|
796
|
+
);
|
|
755
797
|
if (isErr(execResult)) {
|
|
756
798
|
output.error(execResult.error.message);
|
|
757
799
|
const exitCode = "exitCode" in execResult.error ? execResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
|
|
@@ -809,14 +851,14 @@ import { parseArgs as parseArgs3 } from "node:util";
|
|
|
809
851
|
|
|
810
852
|
// src/core/git/libs/list-worktrees.ts
|
|
811
853
|
async function listWorktrees(gitRoot) {
|
|
812
|
-
const { stdout } = await executeGitCommand([
|
|
854
|
+
const { stdout: stdout2 } = await executeGitCommand([
|
|
813
855
|
"worktree",
|
|
814
856
|
"list",
|
|
815
857
|
"--porcelain"
|
|
816
858
|
]);
|
|
817
859
|
const worktrees = [];
|
|
818
860
|
let currentWorktree = {};
|
|
819
|
-
const lines =
|
|
861
|
+
const lines = stdout2.split("\n").filter((line) => line.length > 0);
|
|
820
862
|
for (const line of lines) {
|
|
821
863
|
if (line.startsWith("worktree ")) {
|
|
822
864
|
if (currentWorktree.path) {
|
|
@@ -870,14 +912,14 @@ async function getCurrentWorktree(gitRoot) {
|
|
|
870
912
|
// src/core/worktree/delete.ts
|
|
871
913
|
async function getWorktreeStatus(worktreePath) {
|
|
872
914
|
try {
|
|
873
|
-
const { stdout } = await executeGitCommandInDirectory(worktreePath, [
|
|
915
|
+
const { stdout: stdout2 } = await executeGitCommandInDirectory(worktreePath, [
|
|
874
916
|
"status",
|
|
875
917
|
"--porcelain"
|
|
876
918
|
]);
|
|
877
|
-
if (
|
|
919
|
+
if (stdout2) {
|
|
878
920
|
return {
|
|
879
921
|
hasUncommittedChanges: true,
|
|
880
|
-
changedFiles:
|
|
922
|
+
changedFiles: stdout2.split("\n").length
|
|
881
923
|
};
|
|
882
924
|
}
|
|
883
925
|
} catch {
|
|
@@ -953,6 +995,154 @@ ${message}`;
|
|
|
953
995
|
}
|
|
954
996
|
}
|
|
955
997
|
|
|
998
|
+
// src/core/utils/fzf.ts
|
|
999
|
+
import { spawn } from "node:child_process";
|
|
1000
|
+
async function selectWithFzf(items, options = {}) {
|
|
1001
|
+
return new Promise((resolve2) => {
|
|
1002
|
+
const args2 = [];
|
|
1003
|
+
if (options.prompt) {
|
|
1004
|
+
args2.push("--prompt", options.prompt);
|
|
1005
|
+
}
|
|
1006
|
+
if (options.header) {
|
|
1007
|
+
args2.push("--header", options.header);
|
|
1008
|
+
}
|
|
1009
|
+
if (options.previewCommand) {
|
|
1010
|
+
args2.push("--preview", options.previewCommand);
|
|
1011
|
+
}
|
|
1012
|
+
const fzf = spawn("fzf", args2, {
|
|
1013
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1014
|
+
});
|
|
1015
|
+
let result = "";
|
|
1016
|
+
let errorOutput = "";
|
|
1017
|
+
fzf.stdout.on("data", (data) => {
|
|
1018
|
+
result += data.toString();
|
|
1019
|
+
});
|
|
1020
|
+
if (fzf.stderr) {
|
|
1021
|
+
fzf.stderr.on("data", (data) => {
|
|
1022
|
+
errorOutput += data.toString();
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
fzf.on("error", (error) => {
|
|
1026
|
+
if (error.message.includes("ENOENT")) {
|
|
1027
|
+
resolve2(
|
|
1028
|
+
err(new Error("fzf command not found. Please install fzf first."))
|
|
1029
|
+
);
|
|
1030
|
+
} else {
|
|
1031
|
+
resolve2(err(error));
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
fzf.on("close", (code) => {
|
|
1035
|
+
if (code === 0) {
|
|
1036
|
+
const selected = result.trim();
|
|
1037
|
+
resolve2(ok(selected || null));
|
|
1038
|
+
} else if (code === 1) {
|
|
1039
|
+
resolve2(ok(null));
|
|
1040
|
+
} else if (code === 130) {
|
|
1041
|
+
resolve2(ok(null));
|
|
1042
|
+
} else {
|
|
1043
|
+
resolve2(err(new Error(`fzf exited with code ${code}: ${errorOutput}`)));
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
fzf.stdin.write(items.join("\n"));
|
|
1047
|
+
fzf.stdin.end();
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/core/worktree/list.ts
|
|
1052
|
+
async function getWorktreeStatus2(worktreePath) {
|
|
1053
|
+
try {
|
|
1054
|
+
const { stdout: stdout2 } = await executeGitCommandInDirectory(worktreePath, [
|
|
1055
|
+
"status",
|
|
1056
|
+
"--porcelain"
|
|
1057
|
+
]);
|
|
1058
|
+
return !stdout2;
|
|
1059
|
+
} catch {
|
|
1060
|
+
return true;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
async function listWorktrees2(gitRoot) {
|
|
1064
|
+
try {
|
|
1065
|
+
const gitWorktrees = await listWorktrees(gitRoot);
|
|
1066
|
+
const phantomDir = getPhantomDirectory(gitRoot);
|
|
1067
|
+
const phantomWorktrees = gitWorktrees.filter(
|
|
1068
|
+
(worktree) => worktree.path.startsWith(phantomDir)
|
|
1069
|
+
);
|
|
1070
|
+
if (phantomWorktrees.length === 0) {
|
|
1071
|
+
return ok({
|
|
1072
|
+
worktrees: [],
|
|
1073
|
+
message: "No worktrees found"
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
const worktrees = await Promise.all(
|
|
1077
|
+
phantomWorktrees.map(async (gitWorktree) => {
|
|
1078
|
+
const name = gitWorktree.path.substring(phantomDir.length + 1);
|
|
1079
|
+
const isClean = await getWorktreeStatus2(gitWorktree.path);
|
|
1080
|
+
return {
|
|
1081
|
+
name,
|
|
1082
|
+
path: gitWorktree.path,
|
|
1083
|
+
branch: gitWorktree.branch || "(detached HEAD)",
|
|
1084
|
+
isClean
|
|
1085
|
+
};
|
|
1086
|
+
})
|
|
1087
|
+
);
|
|
1088
|
+
return ok({
|
|
1089
|
+
worktrees
|
|
1090
|
+
});
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1093
|
+
throw new Error(`Failed to list worktrees: ${errorMessage}`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/core/worktree/select.ts
|
|
1098
|
+
async function selectWorktreeWithFzf(gitRoot) {
|
|
1099
|
+
const listResult = await listWorktrees2(gitRoot);
|
|
1100
|
+
if (isErr(listResult)) {
|
|
1101
|
+
return listResult;
|
|
1102
|
+
}
|
|
1103
|
+
const { worktrees } = listResult.value;
|
|
1104
|
+
if (worktrees.length === 0) {
|
|
1105
|
+
return {
|
|
1106
|
+
ok: true,
|
|
1107
|
+
value: null
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
const list = worktrees.map((wt) => {
|
|
1111
|
+
const branchInfo = wt.branch ? `(${wt.branch})` : "";
|
|
1112
|
+
const status = !wt.isClean ? " [dirty]" : "";
|
|
1113
|
+
return `${wt.name} ${branchInfo}${status}`;
|
|
1114
|
+
});
|
|
1115
|
+
const fzfResult = await selectWithFzf(list, {
|
|
1116
|
+
prompt: "Select worktree> ",
|
|
1117
|
+
header: "Git Worktrees (Phantoms)"
|
|
1118
|
+
});
|
|
1119
|
+
if (isErr(fzfResult)) {
|
|
1120
|
+
return fzfResult;
|
|
1121
|
+
}
|
|
1122
|
+
if (!fzfResult.value) {
|
|
1123
|
+
return {
|
|
1124
|
+
ok: true,
|
|
1125
|
+
value: null
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
const selectedName = fzfResult.value.split(" ")[0];
|
|
1129
|
+
const selectedWorktree = worktrees.find((wt) => wt.name === selectedName);
|
|
1130
|
+
if (!selectedWorktree) {
|
|
1131
|
+
return {
|
|
1132
|
+
ok: false,
|
|
1133
|
+
error: new Error("Selected worktree not found")
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
ok: true,
|
|
1138
|
+
value: {
|
|
1139
|
+
name: selectedWorktree.name,
|
|
1140
|
+
branch: selectedWorktree.branch,
|
|
1141
|
+
isClean: selectedWorktree.isClean
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
956
1146
|
// src/cli/handlers/delete.ts
|
|
957
1147
|
async function deleteHandler(args2) {
|
|
958
1148
|
const { values, positionals } = parseArgs3({
|
|
@@ -964,21 +1154,32 @@ async function deleteHandler(args2) {
|
|
|
964
1154
|
},
|
|
965
1155
|
current: {
|
|
966
1156
|
type: "boolean"
|
|
1157
|
+
},
|
|
1158
|
+
fzf: {
|
|
1159
|
+
type: "boolean",
|
|
1160
|
+
default: false
|
|
967
1161
|
}
|
|
968
1162
|
},
|
|
969
1163
|
strict: true,
|
|
970
1164
|
allowPositionals: true
|
|
971
1165
|
});
|
|
972
1166
|
const deleteCurrent = values.current ?? false;
|
|
973
|
-
|
|
1167
|
+
const useFzf = values.fzf ?? false;
|
|
1168
|
+
if (positionals.length === 0 && !deleteCurrent && !useFzf) {
|
|
974
1169
|
exitWithError(
|
|
975
|
-
"Please provide a worktree name to delete
|
|
1170
|
+
"Please provide a worktree name to delete, use --current to delete the current worktree, or use --fzf for interactive selection",
|
|
976
1171
|
exitCodes.validationError
|
|
977
1172
|
);
|
|
978
1173
|
}
|
|
979
|
-
if (positionals.length > 0 && deleteCurrent) {
|
|
1174
|
+
if ((positionals.length > 0 || useFzf) && deleteCurrent) {
|
|
980
1175
|
exitWithError(
|
|
981
|
-
"Cannot specify
|
|
1176
|
+
"Cannot specify --current with a worktree name or --fzf option",
|
|
1177
|
+
exitCodes.validationError
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
if (positionals.length > 0 && useFzf) {
|
|
1181
|
+
exitWithError(
|
|
1182
|
+
"Cannot specify both a worktree name and --fzf option",
|
|
982
1183
|
exitCodes.validationError
|
|
983
1184
|
);
|
|
984
1185
|
}
|
|
@@ -995,6 +1196,15 @@ async function deleteHandler(args2) {
|
|
|
995
1196
|
);
|
|
996
1197
|
}
|
|
997
1198
|
worktreeName = currentWorktree;
|
|
1199
|
+
} else if (useFzf) {
|
|
1200
|
+
const selectResult = await selectWorktreeWithFzf(gitRoot);
|
|
1201
|
+
if (isErr(selectResult)) {
|
|
1202
|
+
exitWithError(selectResult.error.message, exitCodes.generalError);
|
|
1203
|
+
}
|
|
1204
|
+
if (!selectResult.value) {
|
|
1205
|
+
exitWithSuccess();
|
|
1206
|
+
}
|
|
1207
|
+
worktreeName = selectResult.value.name;
|
|
998
1208
|
} else {
|
|
999
1209
|
worktreeName = positionals[0];
|
|
1000
1210
|
}
|
|
@@ -1033,7 +1243,12 @@ async function execHandler(args2) {
|
|
|
1033
1243
|
const [worktreeName, ...commandArgs] = positionals;
|
|
1034
1244
|
try {
|
|
1035
1245
|
const gitRoot = await getGitRoot();
|
|
1036
|
-
const result = await execInWorktree(
|
|
1246
|
+
const result = await execInWorktree(
|
|
1247
|
+
gitRoot,
|
|
1248
|
+
worktreeName,
|
|
1249
|
+
commandArgs,
|
|
1250
|
+
{ interactive: true }
|
|
1251
|
+
);
|
|
1037
1252
|
if (isErr(result)) {
|
|
1038
1253
|
const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.notFound : result.error.exitCode || exitCodes.generalError;
|
|
1039
1254
|
exitWithError(result.error.message, exitCode);
|
|
@@ -1049,78 +1264,45 @@ async function execHandler(args2) {
|
|
|
1049
1264
|
|
|
1050
1265
|
// src/cli/handlers/list.ts
|
|
1051
1266
|
import { parseArgs as parseArgs5 } from "node:util";
|
|
1052
|
-
|
|
1053
|
-
// src/core/worktree/list.ts
|
|
1054
|
-
async function getWorktreeStatus2(worktreePath) {
|
|
1055
|
-
try {
|
|
1056
|
-
const { stdout } = await executeGitCommandInDirectory(worktreePath, [
|
|
1057
|
-
"status",
|
|
1058
|
-
"--porcelain"
|
|
1059
|
-
]);
|
|
1060
|
-
return !stdout;
|
|
1061
|
-
} catch {
|
|
1062
|
-
return true;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
async function listWorktrees2(gitRoot) {
|
|
1066
|
-
try {
|
|
1067
|
-
const gitWorktrees = await listWorktrees(gitRoot);
|
|
1068
|
-
const phantomDir = getPhantomDirectory(gitRoot);
|
|
1069
|
-
const phantomWorktrees = gitWorktrees.filter(
|
|
1070
|
-
(worktree) => worktree.path.startsWith(phantomDir)
|
|
1071
|
-
);
|
|
1072
|
-
if (phantomWorktrees.length === 0) {
|
|
1073
|
-
return ok({
|
|
1074
|
-
worktrees: [],
|
|
1075
|
-
message: "No worktrees found"
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
const worktrees = await Promise.all(
|
|
1079
|
-
phantomWorktrees.map(async (gitWorktree) => {
|
|
1080
|
-
const name = gitWorktree.path.substring(phantomDir.length + 1);
|
|
1081
|
-
const isClean = await getWorktreeStatus2(gitWorktree.path);
|
|
1082
|
-
return {
|
|
1083
|
-
name,
|
|
1084
|
-
path: gitWorktree.path,
|
|
1085
|
-
branch: gitWorktree.branch || "(detached HEAD)",
|
|
1086
|
-
isClean
|
|
1087
|
-
};
|
|
1088
|
-
})
|
|
1089
|
-
);
|
|
1090
|
-
return ok({
|
|
1091
|
-
worktrees
|
|
1092
|
-
});
|
|
1093
|
-
} catch (error) {
|
|
1094
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1095
|
-
throw new Error(`Failed to list worktrees: ${errorMessage}`);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
// src/cli/handlers/list.ts
|
|
1100
1267
|
async function listHandler(args2 = []) {
|
|
1101
|
-
parseArgs5({
|
|
1268
|
+
const { values } = parseArgs5({
|
|
1102
1269
|
args: args2,
|
|
1103
|
-
options: {
|
|
1270
|
+
options: {
|
|
1271
|
+
fzf: {
|
|
1272
|
+
type: "boolean",
|
|
1273
|
+
default: false
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1104
1276
|
strict: true,
|
|
1105
1277
|
allowPositionals: false
|
|
1106
1278
|
});
|
|
1107
1279
|
try {
|
|
1108
1280
|
const gitRoot = await getGitRoot();
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1281
|
+
if (values.fzf) {
|
|
1282
|
+
const selectResult = await selectWorktreeWithFzf(gitRoot);
|
|
1283
|
+
if (isErr(selectResult)) {
|
|
1284
|
+
exitWithError(selectResult.error.message, exitCodes.generalError);
|
|
1285
|
+
}
|
|
1286
|
+
if (selectResult.value) {
|
|
1287
|
+
output.log(selectResult.value.name);
|
|
1288
|
+
}
|
|
1289
|
+
} else {
|
|
1290
|
+
const result = await listWorktrees2(gitRoot);
|
|
1291
|
+
if (isErr(result)) {
|
|
1292
|
+
exitWithError("Failed to list worktrees", exitCodes.generalError);
|
|
1293
|
+
}
|
|
1294
|
+
const { worktrees, message } = result.value;
|
|
1295
|
+
if (worktrees.length === 0) {
|
|
1296
|
+
output.log(message || "No worktrees found.");
|
|
1297
|
+
process.exit(exitCodes.success);
|
|
1298
|
+
}
|
|
1299
|
+
const maxNameLength = Math.max(...worktrees.map((wt) => wt.name.length));
|
|
1300
|
+
for (const worktree of worktrees) {
|
|
1301
|
+
const paddedName = worktree.name.padEnd(maxNameLength + 2);
|
|
1302
|
+
const branchInfo = worktree.branch ? `(${worktree.branch})` : "";
|
|
1303
|
+
const status = !worktree.isClean ? " [dirty]" : "";
|
|
1304
|
+
output.log(`${paddedName} ${branchInfo}${status}`);
|
|
1305
|
+
}
|
|
1124
1306
|
}
|
|
1125
1307
|
process.exit(exitCodes.success);
|
|
1126
1308
|
} catch (error) {
|
|
@@ -1134,21 +1316,45 @@ async function listHandler(args2 = []) {
|
|
|
1134
1316
|
// src/cli/handlers/shell.ts
|
|
1135
1317
|
import { parseArgs as parseArgs6 } from "node:util";
|
|
1136
1318
|
async function shellHandler(args2) {
|
|
1137
|
-
const { positionals } = parseArgs6({
|
|
1319
|
+
const { positionals, values } = parseArgs6({
|
|
1138
1320
|
args: args2,
|
|
1139
|
-
options: {
|
|
1321
|
+
options: {
|
|
1322
|
+
fzf: {
|
|
1323
|
+
type: "boolean",
|
|
1324
|
+
default: false
|
|
1325
|
+
}
|
|
1326
|
+
},
|
|
1140
1327
|
strict: true,
|
|
1141
1328
|
allowPositionals: true
|
|
1142
1329
|
});
|
|
1143
|
-
|
|
1330
|
+
const useFzf = values.fzf ?? false;
|
|
1331
|
+
if (positionals.length === 0 && !useFzf) {
|
|
1144
1332
|
exitWithError(
|
|
1145
|
-
"Usage: phantom shell <worktree-name>",
|
|
1333
|
+
"Usage: phantom shell <worktree-name> or phantom shell --fzf",
|
|
1146
1334
|
exitCodes.validationError
|
|
1147
1335
|
);
|
|
1148
1336
|
}
|
|
1149
|
-
|
|
1337
|
+
if (positionals.length > 0 && useFzf) {
|
|
1338
|
+
exitWithError(
|
|
1339
|
+
"Cannot specify both a worktree name and --fzf option",
|
|
1340
|
+
exitCodes.validationError
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
let worktreeName;
|
|
1150
1344
|
try {
|
|
1151
1345
|
const gitRoot = await getGitRoot();
|
|
1346
|
+
if (useFzf) {
|
|
1347
|
+
const selectResult = await selectWorktreeWithFzf(gitRoot);
|
|
1348
|
+
if (isErr(selectResult)) {
|
|
1349
|
+
exitWithError(selectResult.error.message, exitCodes.generalError);
|
|
1350
|
+
}
|
|
1351
|
+
if (!selectResult.value) {
|
|
1352
|
+
exitWithSuccess();
|
|
1353
|
+
}
|
|
1354
|
+
worktreeName = selectResult.value.name;
|
|
1355
|
+
} else {
|
|
1356
|
+
worktreeName = positionals[0];
|
|
1357
|
+
}
|
|
1152
1358
|
const validation = await validateWorktreeExists(gitRoot, worktreeName);
|
|
1153
1359
|
if (!validation.exists) {
|
|
1154
1360
|
exitWithError(
|
|
@@ -1179,7 +1385,7 @@ import { parseArgs as parseArgs7 } from "node:util";
|
|
|
1179
1385
|
var package_default = {
|
|
1180
1386
|
name: "@aku11i/phantom",
|
|
1181
1387
|
packageManager: "pnpm@10.8.1",
|
|
1182
|
-
version: "0.
|
|
1388
|
+
version: "0.8.0",
|
|
1183
1389
|
description: "A powerful CLI tool for managing Git worktrees for parallel development",
|
|
1184
1390
|
keywords: [
|
|
1185
1391
|
"git",
|
|
@@ -1269,81 +1475,559 @@ async function whereWorktree(gitRoot, name) {
|
|
|
1269
1475
|
|
|
1270
1476
|
// src/cli/handlers/where.ts
|
|
1271
1477
|
async function whereHandler(args2) {
|
|
1272
|
-
const { positionals } = parseArgs8({
|
|
1478
|
+
const { positionals, values } = parseArgs8({
|
|
1273
1479
|
args: args2,
|
|
1274
|
-
options: {
|
|
1480
|
+
options: {
|
|
1481
|
+
fzf: {
|
|
1482
|
+
type: "boolean",
|
|
1483
|
+
default: false
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1275
1486
|
strict: true,
|
|
1276
1487
|
allowPositionals: true
|
|
1277
1488
|
});
|
|
1278
|
-
|
|
1279
|
-
|
|
1489
|
+
const useFzf = values.fzf ?? false;
|
|
1490
|
+
if (positionals.length === 0 && !useFzf) {
|
|
1491
|
+
exitWithError(
|
|
1492
|
+
"Usage: phantom where <worktree-name> or phantom where --fzf",
|
|
1493
|
+
exitCodes.validationError
|
|
1494
|
+
);
|
|
1280
1495
|
}
|
|
1281
|
-
|
|
1496
|
+
if (positionals.length > 0 && useFzf) {
|
|
1497
|
+
exitWithError(
|
|
1498
|
+
"Cannot specify both a worktree name and --fzf option",
|
|
1499
|
+
exitCodes.validationError
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
let worktreeName;
|
|
1503
|
+
let gitRoot;
|
|
1282
1504
|
try {
|
|
1283
|
-
|
|
1284
|
-
const result = await whereWorktree(gitRoot, worktreeName);
|
|
1285
|
-
if (isErr(result)) {
|
|
1286
|
-
exitWithError(result.error.message, exitCodes.notFound);
|
|
1287
|
-
}
|
|
1288
|
-
output.log(result.value.path);
|
|
1289
|
-
exitWithSuccess();
|
|
1505
|
+
gitRoot = await getGitRoot();
|
|
1290
1506
|
} catch (error) {
|
|
1291
1507
|
exitWithError(
|
|
1292
1508
|
error instanceof Error ? error.message : String(error),
|
|
1293
1509
|
exitCodes.generalError
|
|
1294
1510
|
);
|
|
1295
1511
|
}
|
|
1512
|
+
if (useFzf) {
|
|
1513
|
+
const selectResult = await selectWorktreeWithFzf(gitRoot);
|
|
1514
|
+
if (isErr(selectResult)) {
|
|
1515
|
+
exitWithError(selectResult.error.message, exitCodes.generalError);
|
|
1516
|
+
}
|
|
1517
|
+
if (!selectResult.value) {
|
|
1518
|
+
exitWithSuccess();
|
|
1519
|
+
}
|
|
1520
|
+
worktreeName = selectResult.value.name;
|
|
1521
|
+
} else {
|
|
1522
|
+
worktreeName = positionals[0];
|
|
1523
|
+
}
|
|
1524
|
+
const result = await whereWorktree(gitRoot, worktreeName);
|
|
1525
|
+
if (isErr(result)) {
|
|
1526
|
+
exitWithError(result.error.message, exitCodes.notFound);
|
|
1527
|
+
}
|
|
1528
|
+
output.log(result.value.path);
|
|
1529
|
+
exitWithSuccess();
|
|
1296
1530
|
}
|
|
1297
1531
|
|
|
1532
|
+
// src/cli/help.ts
|
|
1533
|
+
import { stdout } from "node:process";
|
|
1534
|
+
var HelpFormatter = class {
|
|
1535
|
+
width;
|
|
1536
|
+
indent = " ";
|
|
1537
|
+
constructor() {
|
|
1538
|
+
this.width = stdout.columns || 80;
|
|
1539
|
+
}
|
|
1540
|
+
formatMainHelp(commands2) {
|
|
1541
|
+
const lines = [];
|
|
1542
|
+
lines.push(this.bold("Phantom - Git Worktree Manager"));
|
|
1543
|
+
lines.push("");
|
|
1544
|
+
lines.push(
|
|
1545
|
+
this.dim(
|
|
1546
|
+
"A CLI tool for managing Git worktrees with enhanced functionality"
|
|
1547
|
+
)
|
|
1548
|
+
);
|
|
1549
|
+
lines.push("");
|
|
1550
|
+
lines.push(this.section("USAGE"));
|
|
1551
|
+
lines.push(`${this.indent}phantom <command> [options]`);
|
|
1552
|
+
lines.push("");
|
|
1553
|
+
lines.push(this.section("COMMANDS"));
|
|
1554
|
+
const maxNameLength = Math.max(...commands2.map((cmd) => cmd.name.length));
|
|
1555
|
+
for (const cmd of commands2) {
|
|
1556
|
+
const paddedName = cmd.name.padEnd(maxNameLength + 2);
|
|
1557
|
+
lines.push(`${this.indent}${this.cyan(paddedName)}${cmd.description}`);
|
|
1558
|
+
}
|
|
1559
|
+
lines.push("");
|
|
1560
|
+
lines.push(this.section("GLOBAL OPTIONS"));
|
|
1561
|
+
const helpOption = "-h, --help";
|
|
1562
|
+
const versionOption = "-v, --version";
|
|
1563
|
+
const globalOptionWidth = Math.max(helpOption.length, versionOption.length) + 2;
|
|
1564
|
+
lines.push(
|
|
1565
|
+
`${this.indent}${this.cyan(helpOption.padEnd(globalOptionWidth))}Show help`
|
|
1566
|
+
);
|
|
1567
|
+
lines.push(
|
|
1568
|
+
`${this.indent}${this.cyan(versionOption.padEnd(globalOptionWidth))}Show version`
|
|
1569
|
+
);
|
|
1570
|
+
lines.push("");
|
|
1571
|
+
lines.push(
|
|
1572
|
+
this.dim(
|
|
1573
|
+
"Run 'phantom <command> --help' for more information on a command."
|
|
1574
|
+
)
|
|
1575
|
+
);
|
|
1576
|
+
return lines.join("\n");
|
|
1577
|
+
}
|
|
1578
|
+
formatCommandHelp(help) {
|
|
1579
|
+
const lines = [];
|
|
1580
|
+
lines.push(this.bold(`phantom ${help.name}`));
|
|
1581
|
+
lines.push(this.dim(help.description));
|
|
1582
|
+
lines.push("");
|
|
1583
|
+
lines.push(this.section("USAGE"));
|
|
1584
|
+
lines.push(`${this.indent}${help.usage}`);
|
|
1585
|
+
lines.push("");
|
|
1586
|
+
if (help.options && help.options.length > 0) {
|
|
1587
|
+
lines.push(this.section("OPTIONS"));
|
|
1588
|
+
const maxOptionLength = Math.max(
|
|
1589
|
+
...help.options.map((opt) => this.formatOptionName(opt).length)
|
|
1590
|
+
);
|
|
1591
|
+
for (const option of help.options) {
|
|
1592
|
+
const optionName = this.formatOptionName(option);
|
|
1593
|
+
const paddedName = optionName.padEnd(maxOptionLength + 2);
|
|
1594
|
+
const description = this.wrapText(
|
|
1595
|
+
option.description,
|
|
1596
|
+
maxOptionLength + 4
|
|
1597
|
+
);
|
|
1598
|
+
lines.push(`${this.indent}${this.cyan(paddedName)}${description[0]}`);
|
|
1599
|
+
for (let i = 1; i < description.length; i++) {
|
|
1600
|
+
lines.push(
|
|
1601
|
+
`${this.indent}${" ".repeat(maxOptionLength + 2)}${description[i]}`
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
if (option.example) {
|
|
1605
|
+
const exampleIndent = " ".repeat(maxOptionLength + 4);
|
|
1606
|
+
lines.push(
|
|
1607
|
+
`${this.indent}${exampleIndent}${this.dim(`Example: ${option.example}`)}`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
lines.push("");
|
|
1612
|
+
}
|
|
1613
|
+
if (help.examples && help.examples.length > 0) {
|
|
1614
|
+
lines.push(this.section("EXAMPLES"));
|
|
1615
|
+
for (const example of help.examples) {
|
|
1616
|
+
lines.push(`${this.indent}${this.dim(example.description)}`);
|
|
1617
|
+
lines.push(`${this.indent}${this.indent}$ ${example.command}`);
|
|
1618
|
+
lines.push("");
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
if (help.notes && help.notes.length > 0) {
|
|
1622
|
+
lines.push(this.section("NOTES"));
|
|
1623
|
+
for (const note of help.notes) {
|
|
1624
|
+
const wrappedNote = this.wrapText(note, 2);
|
|
1625
|
+
for (const line of wrappedNote) {
|
|
1626
|
+
lines.push(`${this.indent}${line}`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
lines.push("");
|
|
1630
|
+
}
|
|
1631
|
+
return lines.join("\n");
|
|
1632
|
+
}
|
|
1633
|
+
formatOptionName(option) {
|
|
1634
|
+
const parts = [];
|
|
1635
|
+
if (option.short) {
|
|
1636
|
+
parts.push(`-${option.short},`);
|
|
1637
|
+
}
|
|
1638
|
+
parts.push(`--${option.name}`);
|
|
1639
|
+
if (option.type === "string") {
|
|
1640
|
+
parts.push(option.multiple ? "<value>..." : "<value>");
|
|
1641
|
+
}
|
|
1642
|
+
return parts.join(" ");
|
|
1643
|
+
}
|
|
1644
|
+
wrapText(text, indent) {
|
|
1645
|
+
const maxWidth = this.width - indent - 2;
|
|
1646
|
+
const words = text.split(" ");
|
|
1647
|
+
const lines = [];
|
|
1648
|
+
let currentLine = "";
|
|
1649
|
+
for (const word of words) {
|
|
1650
|
+
if (currentLine.length + word.length + 1 > maxWidth) {
|
|
1651
|
+
lines.push(currentLine);
|
|
1652
|
+
currentLine = word;
|
|
1653
|
+
} else {
|
|
1654
|
+
currentLine = currentLine ? `${currentLine} ${word}` : word;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
if (currentLine) {
|
|
1658
|
+
lines.push(currentLine);
|
|
1659
|
+
}
|
|
1660
|
+
return lines;
|
|
1661
|
+
}
|
|
1662
|
+
section(text) {
|
|
1663
|
+
return this.bold(text);
|
|
1664
|
+
}
|
|
1665
|
+
bold(text) {
|
|
1666
|
+
return `\x1B[1m${text}\x1B[0m`;
|
|
1667
|
+
}
|
|
1668
|
+
dim(text) {
|
|
1669
|
+
return `\x1B[2m${text}\x1B[0m`;
|
|
1670
|
+
}
|
|
1671
|
+
cyan(text) {
|
|
1672
|
+
return `\x1B[36m${text}\x1B[0m`;
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
var helpFormatter = new HelpFormatter();
|
|
1676
|
+
|
|
1677
|
+
// src/cli/help/attach.ts
|
|
1678
|
+
var attachHelp = {
|
|
1679
|
+
name: "attach",
|
|
1680
|
+
description: "Attach to an existing branch by creating a new worktree",
|
|
1681
|
+
usage: "phantom attach <worktree-name> <branch-name> [options]",
|
|
1682
|
+
options: [
|
|
1683
|
+
{
|
|
1684
|
+
name: "shell",
|
|
1685
|
+
short: "s",
|
|
1686
|
+
type: "boolean",
|
|
1687
|
+
description: "Open an interactive shell in the worktree after attaching"
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
name: "exec",
|
|
1691
|
+
short: "x",
|
|
1692
|
+
type: "string",
|
|
1693
|
+
description: "Execute a command in the worktree after attaching",
|
|
1694
|
+
example: "--exec 'git pull'"
|
|
1695
|
+
}
|
|
1696
|
+
],
|
|
1697
|
+
examples: [
|
|
1698
|
+
{
|
|
1699
|
+
description: "Attach to an existing branch",
|
|
1700
|
+
command: "phantom attach review-pr main"
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
description: "Attach to a remote branch and open a shell",
|
|
1704
|
+
command: "phantom attach hotfix origin/hotfix-v1.2 --shell"
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
description: "Attach to a branch and pull latest changes",
|
|
1708
|
+
command: "phantom attach staging origin/staging --exec 'git pull'"
|
|
1709
|
+
}
|
|
1710
|
+
],
|
|
1711
|
+
notes: [
|
|
1712
|
+
"The branch must already exist (locally or remotely)",
|
|
1713
|
+
"If attaching to a remote branch, it will be checked out locally",
|
|
1714
|
+
"Only one of --shell or --exec options can be used at a time"
|
|
1715
|
+
]
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
// src/cli/help/create.ts
|
|
1719
|
+
var createHelp = {
|
|
1720
|
+
name: "create",
|
|
1721
|
+
description: "Create a new Git worktree (phantom)",
|
|
1722
|
+
usage: "phantom create <name> [options]",
|
|
1723
|
+
options: [
|
|
1724
|
+
{
|
|
1725
|
+
name: "shell",
|
|
1726
|
+
short: "s",
|
|
1727
|
+
type: "boolean",
|
|
1728
|
+
description: "Open an interactive shell in the new worktree after creation"
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
name: "exec",
|
|
1732
|
+
short: "x",
|
|
1733
|
+
type: "string",
|
|
1734
|
+
description: "Execute a command in the new worktree after creation",
|
|
1735
|
+
example: "--exec 'npm install'"
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
name: "tmux",
|
|
1739
|
+
short: "t",
|
|
1740
|
+
type: "boolean",
|
|
1741
|
+
description: "Open the worktree in a new tmux window (requires being inside tmux)"
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
name: "tmux-vertical",
|
|
1745
|
+
type: "boolean",
|
|
1746
|
+
description: "Open the worktree in a vertical tmux pane (requires being inside tmux)"
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
name: "tmux-horizontal",
|
|
1750
|
+
type: "boolean",
|
|
1751
|
+
description: "Open the worktree in a horizontal tmux pane (requires being inside tmux)"
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
name: "copy-file",
|
|
1755
|
+
type: "string",
|
|
1756
|
+
multiple: true,
|
|
1757
|
+
description: "Copy specified files from the current worktree to the new one. Can be used multiple times",
|
|
1758
|
+
example: "--copy-file .env --copy-file config.local.json"
|
|
1759
|
+
}
|
|
1760
|
+
],
|
|
1761
|
+
examples: [
|
|
1762
|
+
{
|
|
1763
|
+
description: "Create a new worktree named 'feature-auth'",
|
|
1764
|
+
command: "phantom create feature-auth"
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
description: "Create a worktree and open a shell in it",
|
|
1768
|
+
command: "phantom create bugfix-123 --shell"
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
description: "Create a worktree and run npm install",
|
|
1772
|
+
command: "phantom create new-feature --exec 'npm install'"
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
description: "Create a worktree in a new tmux window",
|
|
1776
|
+
command: "phantom create experiment --tmux"
|
|
1777
|
+
},
|
|
1778
|
+
{
|
|
1779
|
+
description: "Create a worktree and copy environment files",
|
|
1780
|
+
command: "phantom create staging --copy-file .env --copy-file database.yml"
|
|
1781
|
+
}
|
|
1782
|
+
],
|
|
1783
|
+
notes: [
|
|
1784
|
+
"The worktree name will be used as the branch name",
|
|
1785
|
+
"Only one of --shell, --exec, or --tmux options can be used at a time",
|
|
1786
|
+
"File copying can also be configured in phantom.config.json"
|
|
1787
|
+
]
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
// src/cli/help/delete.ts
|
|
1791
|
+
var deleteHelp = {
|
|
1792
|
+
name: "delete",
|
|
1793
|
+
description: "Delete a Git worktree (phantom)",
|
|
1794
|
+
usage: "phantom delete <name> [options]",
|
|
1795
|
+
options: [
|
|
1796
|
+
{
|
|
1797
|
+
name: "force",
|
|
1798
|
+
short: "f",
|
|
1799
|
+
type: "boolean",
|
|
1800
|
+
description: "Force deletion even if the worktree has uncommitted or unpushed changes"
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
name: "--current",
|
|
1804
|
+
type: "boolean",
|
|
1805
|
+
description: "Delete the current worktree"
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
name: "--fzf",
|
|
1809
|
+
type: "boolean",
|
|
1810
|
+
description: "Use fzf for interactive selection"
|
|
1811
|
+
}
|
|
1812
|
+
],
|
|
1813
|
+
examples: [
|
|
1814
|
+
{
|
|
1815
|
+
description: "Delete a worktree",
|
|
1816
|
+
command: "phantom delete feature-auth"
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
description: "Force delete a worktree with uncommitted changes",
|
|
1820
|
+
command: "phantom delete experimental --force"
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
description: "Delete the current worktree",
|
|
1824
|
+
command: "phantom delete --current"
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
description: "Delete a worktree with interactive fzf selection",
|
|
1828
|
+
command: "phantom delete --fzf"
|
|
1829
|
+
}
|
|
1830
|
+
],
|
|
1831
|
+
notes: [
|
|
1832
|
+
"By default, deletion will fail if the worktree has uncommitted changes",
|
|
1833
|
+
"The associated branch will also be deleted if it's not checked out elsewhere",
|
|
1834
|
+
"With --fzf, you can interactively select the worktree to delete"
|
|
1835
|
+
]
|
|
1836
|
+
};
|
|
1837
|
+
|
|
1838
|
+
// src/cli/help/exec.ts
|
|
1839
|
+
var execHelp = {
|
|
1840
|
+
name: "exec",
|
|
1841
|
+
description: "Execute a command in a worktree directory",
|
|
1842
|
+
usage: "phantom exec <worktree-name> <command> [args...]",
|
|
1843
|
+
examples: [
|
|
1844
|
+
{
|
|
1845
|
+
description: "Run npm test in a worktree",
|
|
1846
|
+
command: "phantom exec feature-auth npm test"
|
|
1847
|
+
},
|
|
1848
|
+
{
|
|
1849
|
+
description: "Check git status in a worktree",
|
|
1850
|
+
command: "phantom exec bugfix-123 git status"
|
|
1851
|
+
},
|
|
1852
|
+
{
|
|
1853
|
+
description: "Run a complex command with arguments",
|
|
1854
|
+
command: "phantom exec staging npm run build -- --production"
|
|
1855
|
+
}
|
|
1856
|
+
],
|
|
1857
|
+
notes: [
|
|
1858
|
+
"The command is executed with the worktree directory as the working directory",
|
|
1859
|
+
"All arguments after the worktree name are passed to the command",
|
|
1860
|
+
"The exit code of the executed command is preserved"
|
|
1861
|
+
]
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1864
|
+
// src/cli/help/list.ts
|
|
1865
|
+
var listHelp = {
|
|
1866
|
+
name: "list",
|
|
1867
|
+
description: "List all Git worktrees (phantoms)",
|
|
1868
|
+
usage: "phantom list [options]",
|
|
1869
|
+
options: [
|
|
1870
|
+
{
|
|
1871
|
+
name: "--fzf",
|
|
1872
|
+
type: "boolean",
|
|
1873
|
+
description: "Use fzf for interactive selection"
|
|
1874
|
+
}
|
|
1875
|
+
],
|
|
1876
|
+
examples: [
|
|
1877
|
+
{
|
|
1878
|
+
description: "List all worktrees",
|
|
1879
|
+
command: "phantom list"
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
description: "List worktrees with interactive fzf selection",
|
|
1883
|
+
command: "phantom list --fzf"
|
|
1884
|
+
}
|
|
1885
|
+
],
|
|
1886
|
+
notes: [
|
|
1887
|
+
"Shows all worktrees with their paths and associated branches",
|
|
1888
|
+
"The main worktree is marked as '(bare)' if using a bare repository",
|
|
1889
|
+
"With --fzf, outputs only the selected worktree name"
|
|
1890
|
+
]
|
|
1891
|
+
};
|
|
1892
|
+
|
|
1893
|
+
// src/cli/help/shell.ts
|
|
1894
|
+
var shellHelp = {
|
|
1895
|
+
name: "shell",
|
|
1896
|
+
description: "Open an interactive shell in a worktree directory",
|
|
1897
|
+
usage: "phantom shell <worktree-name> [options]",
|
|
1898
|
+
options: [
|
|
1899
|
+
{
|
|
1900
|
+
name: "--fzf",
|
|
1901
|
+
type: "boolean",
|
|
1902
|
+
description: "Use fzf for interactive selection"
|
|
1903
|
+
}
|
|
1904
|
+
],
|
|
1905
|
+
examples: [
|
|
1906
|
+
{
|
|
1907
|
+
description: "Open a shell in a worktree",
|
|
1908
|
+
command: "phantom shell feature-auth"
|
|
1909
|
+
},
|
|
1910
|
+
{
|
|
1911
|
+
description: "Open a shell with interactive fzf selection",
|
|
1912
|
+
command: "phantom shell --fzf"
|
|
1913
|
+
}
|
|
1914
|
+
],
|
|
1915
|
+
notes: [
|
|
1916
|
+
"Uses your default shell from the SHELL environment variable",
|
|
1917
|
+
"The shell starts with the worktree directory as the working directory",
|
|
1918
|
+
"Type 'exit' to return to your original directory",
|
|
1919
|
+
"With --fzf, you can interactively select the worktree to enter"
|
|
1920
|
+
]
|
|
1921
|
+
};
|
|
1922
|
+
|
|
1923
|
+
// src/cli/help/version.ts
|
|
1924
|
+
var versionHelp = {
|
|
1925
|
+
name: "version",
|
|
1926
|
+
description: "Display phantom version information",
|
|
1927
|
+
usage: "phantom version",
|
|
1928
|
+
examples: [
|
|
1929
|
+
{
|
|
1930
|
+
description: "Show version",
|
|
1931
|
+
command: "phantom version"
|
|
1932
|
+
}
|
|
1933
|
+
],
|
|
1934
|
+
notes: ["Also accessible via 'phantom --version' or 'phantom -v'"]
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1937
|
+
// src/cli/help/where.ts
|
|
1938
|
+
var whereHelp = {
|
|
1939
|
+
name: "where",
|
|
1940
|
+
description: "Output the filesystem path of a specific worktree",
|
|
1941
|
+
usage: "phantom where <worktree-name> [options]",
|
|
1942
|
+
options: [
|
|
1943
|
+
{
|
|
1944
|
+
name: "--fzf",
|
|
1945
|
+
type: "boolean",
|
|
1946
|
+
description: "Use fzf for interactive selection"
|
|
1947
|
+
}
|
|
1948
|
+
],
|
|
1949
|
+
examples: [
|
|
1950
|
+
{
|
|
1951
|
+
description: "Get the path of a worktree",
|
|
1952
|
+
command: "phantom where feature-auth"
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
description: "Change directory to a worktree",
|
|
1956
|
+
command: "cd $(phantom where staging)"
|
|
1957
|
+
},
|
|
1958
|
+
{
|
|
1959
|
+
description: "Get path with interactive fzf selection",
|
|
1960
|
+
command: "phantom where --fzf"
|
|
1961
|
+
},
|
|
1962
|
+
{
|
|
1963
|
+
description: "Change directory using fzf selection",
|
|
1964
|
+
command: "cd $(phantom where --fzf)"
|
|
1965
|
+
}
|
|
1966
|
+
],
|
|
1967
|
+
notes: [
|
|
1968
|
+
"Outputs only the path, making it suitable for use in scripts",
|
|
1969
|
+
"Exits with an error code if the worktree doesn't exist",
|
|
1970
|
+
"With --fzf, you can interactively select the worktree"
|
|
1971
|
+
]
|
|
1972
|
+
};
|
|
1973
|
+
|
|
1298
1974
|
// src/bin/phantom.ts
|
|
1299
1975
|
var commands = [
|
|
1300
1976
|
{
|
|
1301
1977
|
name: "create",
|
|
1302
|
-
description: "Create a new worktree
|
|
1303
|
-
handler: createHandler
|
|
1978
|
+
description: "Create a new Git worktree (phantom)",
|
|
1979
|
+
handler: createHandler,
|
|
1980
|
+
help: createHelp
|
|
1304
1981
|
},
|
|
1305
1982
|
{
|
|
1306
1983
|
name: "attach",
|
|
1307
|
-
description: "Attach to an existing branch
|
|
1308
|
-
handler: attachHandler
|
|
1984
|
+
description: "Attach to an existing branch by creating a new worktree",
|
|
1985
|
+
handler: attachHandler,
|
|
1986
|
+
help: attachHelp
|
|
1309
1987
|
},
|
|
1310
1988
|
{
|
|
1311
1989
|
name: "list",
|
|
1312
|
-
description: "List all worktrees",
|
|
1313
|
-
handler: listHandler
|
|
1990
|
+
description: "List all Git worktrees (phantoms)",
|
|
1991
|
+
handler: listHandler,
|
|
1992
|
+
help: listHelp
|
|
1314
1993
|
},
|
|
1315
1994
|
{
|
|
1316
1995
|
name: "where",
|
|
1317
|
-
description: "Output the path of a specific worktree",
|
|
1318
|
-
handler: whereHandler
|
|
1996
|
+
description: "Output the filesystem path of a specific worktree",
|
|
1997
|
+
handler: whereHandler,
|
|
1998
|
+
help: whereHelp
|
|
1319
1999
|
},
|
|
1320
2000
|
{
|
|
1321
2001
|
name: "delete",
|
|
1322
|
-
description: "Delete a worktree (
|
|
1323
|
-
handler: deleteHandler
|
|
2002
|
+
description: "Delete a Git worktree (phantom)",
|
|
2003
|
+
handler: deleteHandler,
|
|
2004
|
+
help: deleteHelp
|
|
1324
2005
|
},
|
|
1325
2006
|
{
|
|
1326
2007
|
name: "exec",
|
|
1327
2008
|
description: "Execute a command in a worktree directory",
|
|
1328
|
-
handler: execHandler
|
|
2009
|
+
handler: execHandler,
|
|
2010
|
+
help: execHelp
|
|
1329
2011
|
},
|
|
1330
2012
|
{
|
|
1331
2013
|
name: "shell",
|
|
1332
|
-
description: "Open interactive shell in a worktree directory",
|
|
1333
|
-
handler: shellHandler
|
|
2014
|
+
description: "Open an interactive shell in a worktree directory",
|
|
2015
|
+
handler: shellHandler,
|
|
2016
|
+
help: shellHelp
|
|
1334
2017
|
},
|
|
1335
2018
|
{
|
|
1336
2019
|
name: "version",
|
|
1337
|
-
description: "Display phantom version",
|
|
1338
|
-
handler: versionHandler
|
|
2020
|
+
description: "Display phantom version information",
|
|
2021
|
+
handler: versionHandler,
|
|
2022
|
+
help: versionHelp
|
|
1339
2023
|
}
|
|
1340
2024
|
];
|
|
1341
2025
|
function printHelp(commands2) {
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
2026
|
+
const simpleCommands = commands2.map((cmd) => ({
|
|
2027
|
+
name: cmd.name,
|
|
2028
|
+
description: cmd.description
|
|
2029
|
+
}));
|
|
2030
|
+
console.log(helpFormatter.formatMainHelp(simpleCommands));
|
|
1347
2031
|
}
|
|
1348
2032
|
function findCommand(args2, commands2) {
|
|
1349
2033
|
if (args2.length === 0) {
|
|
@@ -1381,6 +2065,14 @@ if (!command || !command.handler) {
|
|
|
1381
2065
|
printHelp(commands);
|
|
1382
2066
|
exit(1);
|
|
1383
2067
|
}
|
|
2068
|
+
if (remainingArgs.includes("--help") || remainingArgs.includes("-h")) {
|
|
2069
|
+
if (command.help) {
|
|
2070
|
+
console.log(helpFormatter.formatCommandHelp(command.help));
|
|
2071
|
+
} else {
|
|
2072
|
+
console.log(`Help not available for command '${command.name}'`);
|
|
2073
|
+
}
|
|
2074
|
+
exit(0);
|
|
2075
|
+
}
|
|
1384
2076
|
try {
|
|
1385
2077
|
await command.handler(remainingArgs);
|
|
1386
2078
|
} catch (error) {
|