@aku11i/phantom 0.1.0 → 0.1.2
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.md +6 -1
- package/dist/garden.js +394 -0
- package/dist/garden.js.map +7 -0
- package/dist/phantom.js +566 -0
- package/dist/phantom.js.map +7 -0
- package/package.json +6 -4
- package/src/bin/garden.ts +0 -74
- package/src/bin/phantom.ts +0 -121
- package/src/commands/exec.test.ts +0 -256
- package/src/commands/exec.ts +0 -81
- package/src/commands/shell.test.ts +0 -248
- package/src/commands/shell.ts +0 -91
- package/src/gardens/commands/create.test.ts +0 -176
- package/src/gardens/commands/create.ts +0 -67
- package/src/gardens/commands/delete.test.ts +0 -316
- package/src/gardens/commands/delete.ts +0 -134
- package/src/gardens/commands/list.test.ts +0 -243
- package/src/gardens/commands/list.ts +0 -152
- package/src/gardens/commands/where.test.ts +0 -149
- package/src/gardens/commands/where.ts +0 -53
- package/src/git/libs/add-worktree.ts +0 -16
- package/src/git/libs/get-current-branch.ts +0 -9
- package/src/git/libs/get-git-root.ts +0 -9
package/src/bin/garden.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { argv, exit } from "node:process";
|
|
4
|
-
import { gardensCreateHandler } from "../gardens/commands/create.ts";
|
|
5
|
-
import { gardensDeleteHandler } from "../gardens/commands/delete.ts";
|
|
6
|
-
import { gardensListHandler } from "../gardens/commands/list.ts";
|
|
7
|
-
import { gardensWhereHandler } from "../gardens/commands/where.ts";
|
|
8
|
-
|
|
9
|
-
interface Command {
|
|
10
|
-
name: string;
|
|
11
|
-
description: string;
|
|
12
|
-
handler?: (args: string[]) => void | Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const commands: Command[] = [
|
|
16
|
-
{
|
|
17
|
-
name: "create",
|
|
18
|
-
description: "Create a new worktree (garden)",
|
|
19
|
-
handler: gardensCreateHandler,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
name: "list",
|
|
23
|
-
description: "List all gardens",
|
|
24
|
-
handler: gardensListHandler,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: "where",
|
|
28
|
-
description: "Output the path of a specific garden",
|
|
29
|
-
handler: gardensWhereHandler,
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
name: "delete",
|
|
33
|
-
description: "Delete a garden (use --force for dirty gardens)",
|
|
34
|
-
handler: gardensDeleteHandler,
|
|
35
|
-
},
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
function printHelp() {
|
|
39
|
-
console.log("Usage: garden <command> [options]\n");
|
|
40
|
-
console.log("Commands:");
|
|
41
|
-
for (const cmd of commands) {
|
|
42
|
-
console.log(` ${cmd.name.padEnd(20)} ${cmd.description}`);
|
|
43
|
-
}
|
|
44
|
-
console.log("\nThis is an alias for 'phantom garden' commands.");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function findCommand(cmdName: string, commands: Command[]): Command | null {
|
|
48
|
-
return commands.find((cmd) => cmd.name === cmdName) || null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const args = argv.slice(2);
|
|
52
|
-
|
|
53
|
-
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
|
|
54
|
-
printHelp();
|
|
55
|
-
exit(0);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const command = findCommand(args[0], commands);
|
|
59
|
-
|
|
60
|
-
if (!command || !command.handler) {
|
|
61
|
-
console.error(`Error: Unknown command '${args[0]}'\n`);
|
|
62
|
-
printHelp();
|
|
63
|
-
exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
await command.handler(args.slice(1));
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error(
|
|
70
|
-
"Error:",
|
|
71
|
-
error instanceof Error ? error.message : String(error),
|
|
72
|
-
);
|
|
73
|
-
exit(1);
|
|
74
|
-
}
|
package/src/bin/phantom.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { argv, exit } from "node:process";
|
|
4
|
-
import { execHandler } from "../commands/exec.ts";
|
|
5
|
-
import { shellHandler } from "../commands/shell.ts";
|
|
6
|
-
import { gardensCreateHandler } from "../gardens/commands/create.ts";
|
|
7
|
-
import { gardensDeleteHandler } from "../gardens/commands/delete.ts";
|
|
8
|
-
import { gardensListHandler } from "../gardens/commands/list.ts";
|
|
9
|
-
import { gardensWhereHandler } from "../gardens/commands/where.ts";
|
|
10
|
-
|
|
11
|
-
interface Command {
|
|
12
|
-
name: string;
|
|
13
|
-
description: string;
|
|
14
|
-
subcommands?: Command[];
|
|
15
|
-
handler?: (args: string[]) => void | Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const commands: Command[] = [
|
|
19
|
-
{
|
|
20
|
-
name: "garden",
|
|
21
|
-
description: "Manage git worktrees (gardens)",
|
|
22
|
-
subcommands: [
|
|
23
|
-
{
|
|
24
|
-
name: "create",
|
|
25
|
-
description: "Create a new worktree (garden)",
|
|
26
|
-
handler: gardensCreateHandler,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: "list",
|
|
30
|
-
description: "List all gardens",
|
|
31
|
-
handler: gardensListHandler,
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: "where",
|
|
35
|
-
description: "Output the path of a specific garden",
|
|
36
|
-
handler: gardensWhereHandler,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: "delete",
|
|
40
|
-
description: "Delete a garden (use --force for dirty gardens)",
|
|
41
|
-
handler: gardensDeleteHandler,
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: "exec",
|
|
47
|
-
description: "Execute a command in a garden directory",
|
|
48
|
-
handler: execHandler,
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: "shell",
|
|
52
|
-
description: "Open interactive shell in a garden directory",
|
|
53
|
-
handler: shellHandler,
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
function printHelp(commands: Command[], prefix = "") {
|
|
58
|
-
console.log("Usage: phantom <command> [options]\n");
|
|
59
|
-
console.log("Commands:");
|
|
60
|
-
for (const cmd of commands) {
|
|
61
|
-
console.log(` ${prefix}${cmd.name.padEnd(20)} ${cmd.description}`);
|
|
62
|
-
if (cmd.subcommands) {
|
|
63
|
-
for (const subcmd of cmd.subcommands) {
|
|
64
|
-
console.log(` ${subcmd.name.padEnd(18)} ${subcmd.description}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function findCommand(
|
|
71
|
-
args: string[],
|
|
72
|
-
commands: Command[],
|
|
73
|
-
): { command: Command | null; remainingArgs: string[] } {
|
|
74
|
-
if (args.length === 0) {
|
|
75
|
-
return { command: null, remainingArgs: [] };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const [cmdName, ...rest] = args;
|
|
79
|
-
const command = commands.find((cmd) => cmd.name === cmdName);
|
|
80
|
-
|
|
81
|
-
if (!command) {
|
|
82
|
-
return { command: null, remainingArgs: args };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (command.subcommands && rest.length > 0) {
|
|
86
|
-
const { command: subcommand, remainingArgs } = findCommand(
|
|
87
|
-
rest,
|
|
88
|
-
command.subcommands,
|
|
89
|
-
);
|
|
90
|
-
if (subcommand) {
|
|
91
|
-
return { command: subcommand, remainingArgs };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { command, remainingArgs: rest };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const args = argv.slice(2);
|
|
99
|
-
|
|
100
|
-
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
|
|
101
|
-
printHelp(commands);
|
|
102
|
-
exit(0);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const { command, remainingArgs } = findCommand(args, commands);
|
|
106
|
-
|
|
107
|
-
if (!command || !command.handler) {
|
|
108
|
-
console.error(`Error: Unknown command '${args.join(" ")}'\n`);
|
|
109
|
-
printHelp(commands);
|
|
110
|
-
exit(1);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
await command.handler(remainingArgs);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error(
|
|
117
|
-
"Error:",
|
|
118
|
-
error instanceof Error ? error.message : String(error),
|
|
119
|
-
);
|
|
120
|
-
exit(1);
|
|
121
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { strictEqual } from "node:assert";
|
|
2
|
-
import { before, describe, it, mock } from "node:test";
|
|
3
|
-
|
|
4
|
-
describe("execInGarden", () => {
|
|
5
|
-
let spawnMock: ReturnType<typeof mock.fn>;
|
|
6
|
-
let whereGardenMock: ReturnType<typeof mock.fn>;
|
|
7
|
-
let execInGarden: typeof import("./exec.ts").execInGarden;
|
|
8
|
-
|
|
9
|
-
before(async () => {
|
|
10
|
-
spawnMock = mock.fn();
|
|
11
|
-
whereGardenMock = mock.fn();
|
|
12
|
-
|
|
13
|
-
mock.module("node:child_process", {
|
|
14
|
-
namedExports: {
|
|
15
|
-
spawn: spawnMock,
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
mock.module("../gardens/commands/where.ts", {
|
|
20
|
-
namedExports: {
|
|
21
|
-
whereGarden: whereGardenMock,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
({ execInGarden } = await import("./exec.ts"));
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("should return error when garden name is not provided", async () => {
|
|
29
|
-
const result = await execInGarden("", ["echo", "test"]);
|
|
30
|
-
strictEqual(result.success, false);
|
|
31
|
-
strictEqual(result.message, "Error: garden name required");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("should return error when command is not provided", async () => {
|
|
35
|
-
const result = await execInGarden("test-garden", []);
|
|
36
|
-
strictEqual(result.success, false);
|
|
37
|
-
strictEqual(result.message, "Error: command required");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("should return error when garden does not exist", async () => {
|
|
41
|
-
whereGardenMock.mock.resetCalls();
|
|
42
|
-
spawnMock.mock.resetCalls();
|
|
43
|
-
|
|
44
|
-
whereGardenMock.mock.mockImplementation(() =>
|
|
45
|
-
Promise.resolve({
|
|
46
|
-
success: false,
|
|
47
|
-
message: "Error: Garden 'nonexistent' does not exist",
|
|
48
|
-
}),
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const result = await execInGarden("nonexistent", ["echo", "test"]);
|
|
52
|
-
|
|
53
|
-
strictEqual(result.success, false);
|
|
54
|
-
strictEqual(result.message, "Error: Garden 'nonexistent' does not exist");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should execute command successfully with exit code 0", async () => {
|
|
58
|
-
whereGardenMock.mock.resetCalls();
|
|
59
|
-
spawnMock.mock.resetCalls();
|
|
60
|
-
|
|
61
|
-
// Mock successful garden location
|
|
62
|
-
whereGardenMock.mock.mockImplementation(() =>
|
|
63
|
-
Promise.resolve({
|
|
64
|
-
success: true,
|
|
65
|
-
path: "/test/repo/.git/phantom/gardens/test-garden",
|
|
66
|
-
}),
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
// Mock successful command execution
|
|
70
|
-
const mockChildProcess = {
|
|
71
|
-
on: mock.fn(
|
|
72
|
-
(
|
|
73
|
-
event: string,
|
|
74
|
-
callback: (code: number | null, signal: string | null) => void,
|
|
75
|
-
) => {
|
|
76
|
-
if (event === "exit") {
|
|
77
|
-
// Simulate successful command (exit code 0)
|
|
78
|
-
setTimeout(() => callback(0, null), 0);
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
),
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
spawnMock.mock.mockImplementation(() => mockChildProcess);
|
|
85
|
-
|
|
86
|
-
const result = await execInGarden("test-garden", ["echo", "hello"]);
|
|
87
|
-
|
|
88
|
-
strictEqual(result.success, true);
|
|
89
|
-
strictEqual(result.exitCode, 0);
|
|
90
|
-
|
|
91
|
-
// Verify spawn was called with correct arguments
|
|
92
|
-
strictEqual(spawnMock.mock.calls.length, 1);
|
|
93
|
-
const [cmd, args, options] = spawnMock.mock.calls[0].arguments as [
|
|
94
|
-
string,
|
|
95
|
-
string[],
|
|
96
|
-
{ cwd: string; stdio: string },
|
|
97
|
-
];
|
|
98
|
-
strictEqual(cmd, "echo");
|
|
99
|
-
strictEqual(args[0], "hello");
|
|
100
|
-
strictEqual(options.cwd, "/test/repo/.git/phantom/gardens/test-garden");
|
|
101
|
-
strictEqual(options.stdio, "inherit");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("should handle command execution failure with non-zero exit code", async () => {
|
|
105
|
-
whereGardenMock.mock.resetCalls();
|
|
106
|
-
spawnMock.mock.resetCalls();
|
|
107
|
-
|
|
108
|
-
// Mock successful garden location
|
|
109
|
-
whereGardenMock.mock.mockImplementation(() =>
|
|
110
|
-
Promise.resolve({
|
|
111
|
-
success: true,
|
|
112
|
-
path: "/test/repo/.git/phantom/gardens/test-garden",
|
|
113
|
-
}),
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// Mock failed command execution
|
|
117
|
-
const mockChildProcess = {
|
|
118
|
-
on: mock.fn(
|
|
119
|
-
(
|
|
120
|
-
event: string,
|
|
121
|
-
callback: (code: number | null, signal: string | null) => void,
|
|
122
|
-
) => {
|
|
123
|
-
if (event === "exit") {
|
|
124
|
-
// Simulate failed command (exit code 1)
|
|
125
|
-
setTimeout(() => callback(1, null), 0);
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
),
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
spawnMock.mock.mockImplementation(() => mockChildProcess);
|
|
132
|
-
|
|
133
|
-
const result = await execInGarden("test-garden", ["false"]);
|
|
134
|
-
|
|
135
|
-
strictEqual(result.success, false);
|
|
136
|
-
strictEqual(result.exitCode, 1);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("should handle command execution error", async () => {
|
|
140
|
-
whereGardenMock.mock.resetCalls();
|
|
141
|
-
spawnMock.mock.resetCalls();
|
|
142
|
-
|
|
143
|
-
// Mock successful garden location
|
|
144
|
-
whereGardenMock.mock.mockImplementation(() =>
|
|
145
|
-
Promise.resolve({
|
|
146
|
-
success: true,
|
|
147
|
-
path: "/test/repo/.git/phantom/gardens/test-garden",
|
|
148
|
-
}),
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
// Mock command execution error
|
|
152
|
-
const mockChildProcess = {
|
|
153
|
-
on: mock.fn((event: string, callback: (error: Error) => void) => {
|
|
154
|
-
if (event === "error") {
|
|
155
|
-
setTimeout(() => callback(new Error("Command not found")), 0);
|
|
156
|
-
}
|
|
157
|
-
}),
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
spawnMock.mock.mockImplementation(() => mockChildProcess);
|
|
161
|
-
|
|
162
|
-
const result = await execInGarden("test-garden", ["nonexistent-command"]);
|
|
163
|
-
|
|
164
|
-
strictEqual(result.success, false);
|
|
165
|
-
strictEqual(result.message, "Error executing command: Command not found");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("should handle signal termination", async () => {
|
|
169
|
-
whereGardenMock.mock.resetCalls();
|
|
170
|
-
spawnMock.mock.resetCalls();
|
|
171
|
-
|
|
172
|
-
// Mock successful garden location
|
|
173
|
-
whereGardenMock.mock.mockImplementation(() =>
|
|
174
|
-
Promise.resolve({
|
|
175
|
-
success: true,
|
|
176
|
-
path: "/test/repo/.git/phantom/gardens/test-garden",
|
|
177
|
-
}),
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
// Mock signal termination
|
|
181
|
-
const mockChildProcess = {
|
|
182
|
-
on: mock.fn(
|
|
183
|
-
(
|
|
184
|
-
event: string,
|
|
185
|
-
callback: (code: number | null, signal: string | null) => void,
|
|
186
|
-
) => {
|
|
187
|
-
if (event === "exit") {
|
|
188
|
-
// Simulate signal termination
|
|
189
|
-
setTimeout(() => callback(null, "SIGTERM"), 0);
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
),
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
spawnMock.mock.mockImplementation(() => mockChildProcess);
|
|
196
|
-
|
|
197
|
-
const result = await execInGarden("test-garden", ["long-running-command"]);
|
|
198
|
-
|
|
199
|
-
strictEqual(result.success, false);
|
|
200
|
-
strictEqual(result.message, "Command terminated by signal: SIGTERM");
|
|
201
|
-
strictEqual(result.exitCode, 143); // 128 + 15 (SIGTERM)
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("should parse complex commands with multiple arguments", async () => {
|
|
205
|
-
whereGardenMock.mock.resetCalls();
|
|
206
|
-
spawnMock.mock.resetCalls();
|
|
207
|
-
|
|
208
|
-
// Mock successful garden location
|
|
209
|
-
whereGardenMock.mock.mockImplementation(() =>
|
|
210
|
-
Promise.resolve({
|
|
211
|
-
success: true,
|
|
212
|
-
path: "/test/repo/.git/phantom/gardens/test-garden",
|
|
213
|
-
}),
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
// Mock successful command execution
|
|
217
|
-
const mockChildProcess = {
|
|
218
|
-
on: mock.fn(
|
|
219
|
-
(
|
|
220
|
-
event: string,
|
|
221
|
-
callback: (code: number | null, signal: string | null) => void,
|
|
222
|
-
) => {
|
|
223
|
-
if (event === "exit") {
|
|
224
|
-
setTimeout(() => callback(0, null), 0);
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
),
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
spawnMock.mock.mockImplementation(() => mockChildProcess);
|
|
231
|
-
|
|
232
|
-
const result = await execInGarden("test-garden", [
|
|
233
|
-
"npm",
|
|
234
|
-
"run",
|
|
235
|
-
"test",
|
|
236
|
-
"--",
|
|
237
|
-
"--verbose",
|
|
238
|
-
]);
|
|
239
|
-
|
|
240
|
-
strictEqual(result.success, true);
|
|
241
|
-
strictEqual(result.exitCode, 0);
|
|
242
|
-
|
|
243
|
-
// Verify spawn was called with correct arguments
|
|
244
|
-
const [cmd, args] = spawnMock.mock.calls[0].arguments as [
|
|
245
|
-
string,
|
|
246
|
-
string[],
|
|
247
|
-
object,
|
|
248
|
-
];
|
|
249
|
-
strictEqual(cmd, "npm");
|
|
250
|
-
strictEqual(args.length, 4);
|
|
251
|
-
strictEqual(args[0], "run");
|
|
252
|
-
strictEqual(args[1], "test");
|
|
253
|
-
strictEqual(args[2], "--");
|
|
254
|
-
strictEqual(args[3], "--verbose");
|
|
255
|
-
});
|
|
256
|
-
});
|
package/src/commands/exec.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { exit } from "node:process";
|
|
3
|
-
import { whereGarden } from "../gardens/commands/where.ts";
|
|
4
|
-
|
|
5
|
-
export async function execInGarden(
|
|
6
|
-
gardenName: string,
|
|
7
|
-
command: string[],
|
|
8
|
-
): Promise<{
|
|
9
|
-
success: boolean;
|
|
10
|
-
message?: string;
|
|
11
|
-
exitCode?: number;
|
|
12
|
-
}> {
|
|
13
|
-
if (!gardenName) {
|
|
14
|
-
return { success: false, message: "Error: garden name required" };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (!command || command.length === 0) {
|
|
18
|
-
return { success: false, message: "Error: command required" };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Validate garden exists and get its path
|
|
22
|
-
const gardenResult = await whereGarden(gardenName);
|
|
23
|
-
if (!gardenResult.success) {
|
|
24
|
-
return { success: false, message: gardenResult.message };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const gardenPath = gardenResult.path as string;
|
|
28
|
-
const [cmd, ...args] = command;
|
|
29
|
-
|
|
30
|
-
return new Promise((resolve) => {
|
|
31
|
-
const childProcess = spawn(cmd, args, {
|
|
32
|
-
cwd: gardenPath,
|
|
33
|
-
stdio: "inherit",
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
childProcess.on("error", (error) => {
|
|
37
|
-
resolve({
|
|
38
|
-
success: false,
|
|
39
|
-
message: `Error executing command: ${error.message}`,
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
childProcess.on("exit", (code, signal) => {
|
|
44
|
-
if (signal) {
|
|
45
|
-
resolve({
|
|
46
|
-
success: false,
|
|
47
|
-
message: `Command terminated by signal: ${signal}`,
|
|
48
|
-
exitCode: 128 + (signal === "SIGTERM" ? 15 : 1),
|
|
49
|
-
});
|
|
50
|
-
} else {
|
|
51
|
-
const exitCode = code ?? 0;
|
|
52
|
-
resolve({
|
|
53
|
-
success: exitCode === 0,
|
|
54
|
-
exitCode,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export async function execHandler(args: string[]): Promise<void> {
|
|
62
|
-
if (args.length < 2) {
|
|
63
|
-
console.error("Usage: phantom exec <garden-name> <command> [args...]");
|
|
64
|
-
exit(1);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const gardenName = args[0];
|
|
68
|
-
const command = args.slice(1);
|
|
69
|
-
|
|
70
|
-
const result = await execInGarden(gardenName, command);
|
|
71
|
-
|
|
72
|
-
if (!result.success) {
|
|
73
|
-
if (result.message) {
|
|
74
|
-
console.error(result.message);
|
|
75
|
-
}
|
|
76
|
-
exit(result.exitCode ?? 1);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// For successful commands, exit with the same code as the child process
|
|
80
|
-
exit(result.exitCode ?? 0);
|
|
81
|
-
}
|