aico-cli 0.4.2 → 0.4.4
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/dist/chunks/run-command.mjs +47 -0
- package/dist/chunks/simple-config.mjs +1 -1
- package/dist/cli.mjs +474 -88
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { p as processManager } from '../cli.mjs';
|
|
2
|
+
import 'cac';
|
|
3
|
+
import 'ansis';
|
|
4
|
+
import './simple-config.mjs';
|
|
5
|
+
import 'inquirer';
|
|
6
|
+
import 'tinyexec';
|
|
7
|
+
import 'node:os';
|
|
8
|
+
import 'node:fs';
|
|
9
|
+
import 'node:child_process';
|
|
10
|
+
import 'node:util';
|
|
11
|
+
import 'child_process';
|
|
12
|
+
import 'util';
|
|
13
|
+
import 'ora';
|
|
14
|
+
import 'dayjs';
|
|
15
|
+
import 'node:path';
|
|
16
|
+
import 'pathe';
|
|
17
|
+
import 'node:url';
|
|
18
|
+
import 'node:events';
|
|
19
|
+
|
|
20
|
+
async function run(command) {
|
|
21
|
+
try {
|
|
22
|
+
const parts = command.split(" ").filter((part) => part.trim());
|
|
23
|
+
if (parts.length === 0) {
|
|
24
|
+
throw new Error("\u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
|
|
25
|
+
}
|
|
26
|
+
const cmd = parts[0];
|
|
27
|
+
const args = parts.slice(1);
|
|
28
|
+
const result = await processManager.spawn(cmd, args, {
|
|
29
|
+
name: cmd,
|
|
30
|
+
timeout: 6e4,
|
|
31
|
+
// 60秒超时
|
|
32
|
+
stdio: "pipe"
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
stdout: result.stdout || "",
|
|
36
|
+
stderr: result.stderr || ""
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof Error) {
|
|
40
|
+
throw new Error(`Command failed: ${command}
|
|
41
|
+
Error: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { run };
|
|
@@ -13,7 +13,7 @@ import { join as join$1 } from 'node:path';
|
|
|
13
13
|
import { join, dirname, basename } from 'pathe';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
|
|
16
|
-
const version = "0.4.
|
|
16
|
+
const version = "0.4.4";
|
|
17
17
|
|
|
18
18
|
function displayBanner(subtitle) {
|
|
19
19
|
const defaultSubtitle = "\u4E00\u952E\u914D\u7F6E\u4F60\u7684\u5F00\u53D1\u73AF\u5883";
|
package/dist/cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import ansis from 'ansis';
|
|
|
4
4
|
import { N as createEscapablePrompt, O as displayBannerWithInfo, a as init, P as executeWithEscapeSupport, Q as handleExitPromptError, R as handleGeneralError, T as EscapeKeyPressed, U as displayBanner, V as ConfigCheckerInstaller, W as updateAicoConfig, X as version, Y as InstallationComposer, Z as readAicoConfig } from './chunks/simple-config.mjs';
|
|
5
5
|
import inquirer$1 from 'inquirer';
|
|
6
6
|
import { spawn, exec as exec$1 } from 'node:child_process';
|
|
7
|
+
import { EventEmitter } from 'node:events';
|
|
7
8
|
import 'tinyexec';
|
|
8
9
|
import 'node:os';
|
|
9
10
|
import 'node:fs';
|
|
@@ -22,25 +23,434 @@ const inquirer = {
|
|
|
22
23
|
createPromptModule: inquirer$1.createPromptModule
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
class ProcessManager extends EventEmitter {
|
|
27
|
+
processes = /* @__PURE__ */ new Map();
|
|
28
|
+
isShuttingDown = false;
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
this.setupSignalHandlers();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 设置信号处理器
|
|
35
|
+
*/
|
|
36
|
+
setupSignalHandlers() {
|
|
37
|
+
process.on("SIGINT", () => {
|
|
38
|
+
this.shutdown("SIGINT");
|
|
39
|
+
});
|
|
40
|
+
process.on("SIGTERM", () => {
|
|
41
|
+
this.shutdown("SIGTERM");
|
|
42
|
+
});
|
|
43
|
+
process.on("beforeExit", () => {
|
|
44
|
+
this.cleanup();
|
|
45
|
+
});
|
|
46
|
+
process.on("exit", () => {
|
|
47
|
+
this.cleanup();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 启动进程
|
|
52
|
+
*/
|
|
53
|
+
async spawn(command, args = [], options = {}) {
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
const processName = options.name || command;
|
|
56
|
+
this.log(`\u542F\u52A8\u8FDB\u7A0B: ${processName}`, "info");
|
|
57
|
+
try {
|
|
58
|
+
const child = spawn(command, args, {
|
|
59
|
+
cwd: options.cwd || process.cwd(),
|
|
60
|
+
env: { ...process.env, ...options.env },
|
|
61
|
+
stdio: options.stdio || "pipe",
|
|
62
|
+
shell: options.shell || process.platform === "win32"
|
|
31
63
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
64
|
+
this.registerProcess(child, processName);
|
|
65
|
+
let timeoutId;
|
|
66
|
+
let isTimeout = false;
|
|
67
|
+
if (options.timeout) {
|
|
68
|
+
timeoutId = setTimeout(() => {
|
|
69
|
+
isTimeout = true;
|
|
70
|
+
this.log(`\u8FDB\u7A0B ${processName} \u8D85\u65F6\uFF0C\u6B63\u5728\u7EC8\u6B62...`, "warning");
|
|
71
|
+
child.kill("SIGTERM");
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
if (child.pid && !child.killed) {
|
|
74
|
+
child.kill("SIGKILL");
|
|
75
|
+
}
|
|
76
|
+
}, 1e3);
|
|
77
|
+
}, options.timeout);
|
|
78
|
+
}
|
|
79
|
+
let stdout = "";
|
|
80
|
+
let stderr = "";
|
|
81
|
+
if (child.stdout) {
|
|
82
|
+
child.stdout.on("data", (data) => {
|
|
83
|
+
stdout += data.toString();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (child.stderr) {
|
|
87
|
+
child.stderr.on("data", (data) => {
|
|
88
|
+
stderr += data.toString();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
92
|
+
child.on("close", (code) => {
|
|
93
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
94
|
+
this.unregisterProcess(child.pid);
|
|
95
|
+
resolve(code ?? 0);
|
|
96
|
+
});
|
|
97
|
+
child.on("error", (error) => {
|
|
98
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
99
|
+
this.unregisterProcess(child.pid);
|
|
100
|
+
reject(error);
|
|
101
|
+
});
|
|
35
102
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
103
|
+
const duration = Date.now() - startTime;
|
|
104
|
+
const success = exitCode === 0 && !isTimeout;
|
|
105
|
+
this.log(
|
|
106
|
+
`\u8FDB\u7A0B ${processName} \u6267\u884C${success ? "\u6210\u529F" : "\u5931\u8D25"} (${duration}ms)`,
|
|
107
|
+
success ? "success" : "error"
|
|
108
|
+
);
|
|
109
|
+
return {
|
|
110
|
+
exitCode,
|
|
111
|
+
stdout: stdout || void 0,
|
|
112
|
+
stderr: stderr || void 0,
|
|
113
|
+
success,
|
|
114
|
+
duration
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const duration = Date.now() - startTime;
|
|
118
|
+
this.log(`\u8FDB\u7A0B ${processName} \u6267\u884C\u5931\u8D25: ${error}`, "error");
|
|
119
|
+
return {
|
|
120
|
+
exitCode: 1,
|
|
121
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
122
|
+
success: false,
|
|
123
|
+
duration
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 注册进程
|
|
129
|
+
*/
|
|
130
|
+
registerProcess(child, name) {
|
|
131
|
+
if (!child.pid) {
|
|
132
|
+
this.log(`\u65E0\u6CD5\u6CE8\u518C\u8FDB\u7A0B ${name}: \u6CA1\u6709PID`, "error");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (this.processes.has(child.pid)) {
|
|
136
|
+
this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u5DF2\u7ECF\u6CE8\u518C\u8FC7`, "warning");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const processInfo = {
|
|
140
|
+
pid: child.pid,
|
|
141
|
+
name,
|
|
142
|
+
startTime: /* @__PURE__ */ new Date(),
|
|
143
|
+
status: "running",
|
|
144
|
+
process: child
|
|
145
|
+
};
|
|
146
|
+
this.processes.set(child.pid, processInfo);
|
|
147
|
+
let isCleanedUp = false;
|
|
148
|
+
const cleanup = () => {
|
|
149
|
+
if (!isCleanedUp) {
|
|
150
|
+
isCleanedUp = true;
|
|
151
|
+
this.unregisterProcess(child.pid);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
child.on("close", (code) => {
|
|
155
|
+
const info = this.processes.get(child.pid);
|
|
156
|
+
if (info && !isCleanedUp) {
|
|
157
|
+
info.status = code === 0 ? "completed" : "failed";
|
|
158
|
+
info.exitCode = code ?? void 0;
|
|
159
|
+
this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u5DF2\u5173\u95ED\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
|
|
160
|
+
}
|
|
161
|
+
cleanup();
|
|
162
|
+
});
|
|
163
|
+
child.on("error", (error) => {
|
|
164
|
+
const info = this.processes.get(child.pid);
|
|
165
|
+
if (info && !isCleanedUp) {
|
|
166
|
+
info.status = "failed";
|
|
167
|
+
this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u53D1\u751F\u9519\u8BEF: ${error}`, "error");
|
|
168
|
+
}
|
|
169
|
+
cleanup();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 注销进程
|
|
174
|
+
*/
|
|
175
|
+
unregisterProcess(pid) {
|
|
176
|
+
const info = this.processes.get(pid);
|
|
177
|
+
if (info) {
|
|
178
|
+
this.log(`\u6CE8\u9500\u8FDB\u7A0B: ${info.name} (PID: ${pid})`, "info");
|
|
179
|
+
this.processes.delete(pid);
|
|
180
|
+
} else {
|
|
181
|
+
this.log(`\u5C1D\u8BD5\u6CE8\u9500\u4E0D\u5B58\u5728\u7684\u8FDB\u7A0B (PID: ${pid})`, "warning");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 公开的注销进程方法(用于测试)
|
|
186
|
+
*/
|
|
187
|
+
unregisterProcessForTesting(pid) {
|
|
188
|
+
this.unregisterProcess(pid);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 优雅关闭
|
|
192
|
+
*/
|
|
193
|
+
async shutdown(signal) {
|
|
194
|
+
if (this.isShuttingDown) return;
|
|
195
|
+
this.isShuttingDown = true;
|
|
196
|
+
this.log(`\u6B63\u5728\u5173\u95ED ${this.processes.size} \u4E2A\u8FDB\u7A0B...`, "warning");
|
|
197
|
+
const firstSignalTime = Date.now();
|
|
198
|
+
let forceShutdownRequested = false;
|
|
199
|
+
const forceShutdownHandler = () => {
|
|
200
|
+
forceShutdownRequested = true;
|
|
201
|
+
this.log("\u7528\u6237\u8BF7\u6C42\u5F3A\u5236\u5173\u95ED\uFF0C\u7ACB\u5373\u7EC8\u6B62\u6240\u6709\u8FDB\u7A0B...", "warning");
|
|
202
|
+
};
|
|
203
|
+
process.once("SIGINT", forceShutdownHandler);
|
|
204
|
+
let runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
|
|
205
|
+
if (runningProcesses.length > 0) {
|
|
206
|
+
this.log(`\u5411 ${runningProcesses.length} \u4E2A\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B\u53D1\u9001SIGTERM\u4FE1\u53F7...`, "info");
|
|
207
|
+
for (const info of runningProcesses) {
|
|
208
|
+
try {
|
|
209
|
+
info.process.kill("SIGTERM");
|
|
210
|
+
info.status = "killed";
|
|
211
|
+
} catch (error) {
|
|
212
|
+
this.log(`\u65E0\u6CD5\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid}): ${error}`, "error");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const maxWaitTime = 1e3;
|
|
216
|
+
const checkInterval = 100;
|
|
217
|
+
let waitedTime = 0;
|
|
218
|
+
while (waitedTime < maxWaitTime && !forceShutdownRequested) {
|
|
219
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
220
|
+
waitedTime += checkInterval;
|
|
221
|
+
runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
|
|
222
|
+
if (runningProcesses.length === 0) {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
process.removeListener("SIGINT", forceShutdownHandler);
|
|
228
|
+
runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
|
|
229
|
+
if (runningProcesses.length > 0) {
|
|
230
|
+
this.log(`\u5F3A\u5236\u7EC8\u6B62 ${runningProcesses.length} \u4E2A\u4ECD\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B...`, "warning");
|
|
231
|
+
for (const info of runningProcesses) {
|
|
232
|
+
try {
|
|
233
|
+
info.process.kill("SIGKILL");
|
|
234
|
+
this.log(`\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid})`, "warning");
|
|
235
|
+
} catch (error) {
|
|
236
|
+
this.log(`\u65E0\u6CD5\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid}): ${error}`, "error");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const totalTime = Date.now() - firstSignalTime;
|
|
241
|
+
this.log(`\u5173\u95ED\u5B8C\u6210\uFF0C\u8017\u65F6 ${totalTime}ms`, "success");
|
|
242
|
+
this.cleanup();
|
|
243
|
+
process.exit(0);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 清理所有进程
|
|
247
|
+
*/
|
|
248
|
+
cleanup() {
|
|
249
|
+
for (const [pid, info] of this.processes) {
|
|
250
|
+
if (info.status === "running") {
|
|
251
|
+
try {
|
|
252
|
+
info.process.kill("SIGKILL");
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
this.processes.clear();
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 获取当前运行的进程数量
|
|
261
|
+
*/
|
|
262
|
+
getProcessCount() {
|
|
263
|
+
return this.processes.size;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* 获取运行中的进程数量
|
|
267
|
+
*/
|
|
268
|
+
getRunningProcessCount() {
|
|
269
|
+
return Array.from(this.processes.values()).filter((p) => p.status === "running").length;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* 获取进程列表
|
|
273
|
+
*/
|
|
274
|
+
getProcessList() {
|
|
275
|
+
return Array.from(this.processes.values());
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* 获取运行中的进程列表
|
|
279
|
+
*/
|
|
280
|
+
getRunningProcessList() {
|
|
281
|
+
return Array.from(this.processes.values()).filter((p) => p.status === "running");
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 检查是否存在进程泄漏(运行中的进程过多)
|
|
285
|
+
*/
|
|
286
|
+
checkForProcessLeaks(maxAllowed = 10) {
|
|
287
|
+
const runningCount = this.getRunningProcessCount();
|
|
288
|
+
if (runningCount > maxAllowed) {
|
|
289
|
+
this.log(`\u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u8FDB\u7A0B\u6CC4\u6F0F: ${runningCount} \u4E2A\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B (\u8D85\u8FC7\u9650\u5236 ${maxAllowed})`, "warning");
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* 获取进程管理器状态报告
|
|
296
|
+
*/
|
|
297
|
+
getStatusReport() {
|
|
298
|
+
const total = this.getProcessCount();
|
|
299
|
+
const running = this.getRunningProcessCount();
|
|
300
|
+
const completed = Array.from(this.processes.values()).filter((p) => p.status === "completed").length;
|
|
301
|
+
const failed = Array.from(this.processes.values()).filter((p) => p.status === "failed").length;
|
|
302
|
+
const killed = Array.from(this.processes.values()).filter((p) => p.status === "killed").length;
|
|
303
|
+
let report = `\u8FDB\u7A0B\u7BA1\u7406\u5668\u72B6\u6001\u62A5\u544A
|
|
304
|
+
`;
|
|
305
|
+
report += `==================
|
|
306
|
+
`;
|
|
307
|
+
report += `\u603B\u8FDB\u7A0B\u6570: ${total}
|
|
308
|
+
`;
|
|
309
|
+
report += `\u8FD0\u884C\u4E2D: ${running}
|
|
310
|
+
`;
|
|
311
|
+
report += `\u5DF2\u5B8C\u6210: ${completed}
|
|
312
|
+
`;
|
|
313
|
+
report += `\u5931\u8D25: ${failed}
|
|
314
|
+
`;
|
|
315
|
+
report += `\u5DF2\u7EC8\u6B62: ${killed}
|
|
316
|
+
`;
|
|
317
|
+
report += `\u5173\u95ED\u72B6\u6001: ${this.isShuttingDown ? "\u6B63\u5728\u5173\u95ED" : "\u6B63\u5E38\u8FD0\u884C"}
|
|
318
|
+
`;
|
|
319
|
+
if (running > 0) {
|
|
320
|
+
report += `
|
|
321
|
+
\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B:
|
|
322
|
+
`;
|
|
323
|
+
this.getRunningProcessList().forEach((process2) => {
|
|
324
|
+
const duration = Date.now() - process2.startTime.getTime();
|
|
325
|
+
const durationSeconds = Math.floor(duration / 1e3);
|
|
326
|
+
report += ` - ${process2.name} (PID: ${process2.pid}) - \u8FD0\u884C\u65F6\u95F4: ${durationSeconds}s
|
|
327
|
+
`;
|
|
39
328
|
});
|
|
40
|
-
|
|
41
|
-
|
|
329
|
+
}
|
|
330
|
+
return report;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 强制清理僵尸进程(状态为运行中但实际已退出的进程)
|
|
334
|
+
*/
|
|
335
|
+
cleanupZombieProcesses() {
|
|
336
|
+
let cleanedCount = 0;
|
|
337
|
+
const runningProcesses = this.getRunningProcessList();
|
|
338
|
+
for (const process2 of runningProcesses) {
|
|
339
|
+
try {
|
|
340
|
+
process2.process.kill(0);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
this.log(`\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
|
|
343
|
+
this.unregisterProcess(process2.pid);
|
|
344
|
+
cleanedCount++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (cleanedCount > 0) {
|
|
348
|
+
this.log(`\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u50F5\u5C38\u8FDB\u7A0B`, "info");
|
|
349
|
+
}
|
|
350
|
+
return cleanedCount;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* 添加进程到列表(用于测试)
|
|
354
|
+
*/
|
|
355
|
+
addProcessForTesting(processInfo) {
|
|
356
|
+
this.processes.set(processInfo.pid, processInfo);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 替换当前进程(exec 模式)
|
|
360
|
+
* 这个方法会完全替换当前进程,不会返回
|
|
361
|
+
*/
|
|
362
|
+
async replaceProcess(command, args = [], options = {}) {
|
|
363
|
+
const processName = options.name || command;
|
|
364
|
+
try {
|
|
365
|
+
this.log(`\u6E05\u7406 ${this.processes.size} \u4E2A\u7BA1\u7406\u8FDB\u7A0B...`, "info");
|
|
366
|
+
this.cleanup();
|
|
367
|
+
const { spawn: spawn2 } = await import('node:child_process');
|
|
368
|
+
const child = spawn2(command, args, {
|
|
369
|
+
stdio: "inherit",
|
|
370
|
+
// 继承所有 stdio
|
|
371
|
+
cwd: options.cwd || process.cwd(),
|
|
372
|
+
env: { ...process.env, ...options.env },
|
|
373
|
+
shell: options.shell || process.platform === "win32"
|
|
374
|
+
// 移除 detached: true,避免子进程脱离管理
|
|
375
|
+
// detached: false 是默认值,子进程会随着父进程退出而退出
|
|
376
|
+
});
|
|
377
|
+
child.on("error", (error) => {
|
|
378
|
+
this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
|
|
379
|
+
process.exit(1);
|
|
42
380
|
});
|
|
381
|
+
child.on("exit", (code) => {
|
|
382
|
+
this.log(`\u66FF\u6362\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
|
|
383
|
+
process.exit(code || 0);
|
|
384
|
+
});
|
|
385
|
+
this.registerProcess(child, processName);
|
|
386
|
+
await new Promise(() => {
|
|
387
|
+
});
|
|
388
|
+
process.exit(0);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* 终止特定进程
|
|
396
|
+
*/
|
|
397
|
+
killProcess(pid, signal = "SIGTERM") {
|
|
398
|
+
const info = this.processes.get(pid);
|
|
399
|
+
if (info && info.status === "running") {
|
|
400
|
+
try {
|
|
401
|
+
info.process.kill(signal);
|
|
402
|
+
info.status = "killed";
|
|
403
|
+
return true;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 日志输出
|
|
412
|
+
*/
|
|
413
|
+
log(message, level = "info") {
|
|
414
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
415
|
+
const prefix = `[${timestamp}] [ProcessManager]`;
|
|
416
|
+
switch (level) {
|
|
417
|
+
case "info":
|
|
418
|
+
console.log(ansis.gray(`${prefix} ${message}`));
|
|
419
|
+
break;
|
|
420
|
+
case "warning":
|
|
421
|
+
console.log(ansis.yellow(`${prefix} ${message}`));
|
|
422
|
+
break;
|
|
423
|
+
case "error":
|
|
424
|
+
console.error(ansis.red(`${prefix} ${message}`));
|
|
425
|
+
break;
|
|
426
|
+
case "success":
|
|
427
|
+
console.log(ansis.green(`${prefix} ${message}`));
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const processManager = new ProcessManager();
|
|
433
|
+
|
|
434
|
+
const processManager$1 = {
|
|
435
|
+
__proto__: null,
|
|
436
|
+
ProcessManager: ProcessManager,
|
|
437
|
+
processManager: processManager
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
async function getCurrentVersion() {
|
|
441
|
+
try {
|
|
442
|
+
const result = await processManager.spawn("npm", ["list", "-g", "aico-cli", "--depth=0"], {
|
|
443
|
+
name: "npm list",
|
|
444
|
+
stdio: "pipe",
|
|
445
|
+
shell: process.platform === "win32",
|
|
446
|
+
timeout: 1e4
|
|
447
|
+
// 10秒超时
|
|
43
448
|
});
|
|
449
|
+
if (result.success && result.stdout) {
|
|
450
|
+
const match = result.stdout.match(/aico-cli@(\d+\.\d+\.\d+)/);
|
|
451
|
+
return match ? match[1] : null;
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
44
454
|
} catch {
|
|
45
455
|
return null;
|
|
46
456
|
}
|
|
@@ -55,48 +465,22 @@ async function updateAicoCli() {
|
|
|
55
465
|
console.log(ansis.gray(`\u{1F4E6} \u5F53\u524D\u7248\u672C: ${currentVersion}`));
|
|
56
466
|
}
|
|
57
467
|
console.log(ansis.cyan("\u23F3 \u6B63\u5728\u6267\u884C\u81EA\u52A8\u66F4\u65B0..."));
|
|
58
|
-
const
|
|
59
|
-
|
|
468
|
+
const result = await processManager.spawn("npm", ["install", "-g", "aico-cli"], {
|
|
469
|
+
name: "npm install",
|
|
60
470
|
stdio: "inherit",
|
|
61
471
|
// 继承父进程的 stdio,显示安装进度
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
const timeout = setTimeout(() => {
|
|
67
|
-
console.log(ansis.yellow("\n\u26A0\uFE0F \u66F4\u65B0\u65F6\u95F4\u8F83\u957F\uFF0C\u53EF\u80FD\u9047\u5230\u7F51\u7EDC\u95EE\u9898"));
|
|
68
|
-
console.log(ansis.cyan("\u{1F4A1} \u5EFA\u8BAE\u6309 Ctrl+C \u53D6\u6D88\uFF0C\u7136\u540E\u624B\u52A8\u6267\u884C:"));
|
|
69
|
-
console.log(ansis.white(" npm install -g aico-cli"));
|
|
70
|
-
}, 15e3);
|
|
71
|
-
await new Promise((resolve, reject) => {
|
|
72
|
-
child.on("close", (code) => {
|
|
73
|
-
clearTimeout(timeout);
|
|
74
|
-
if (code === 0) {
|
|
75
|
-
console.log(ansis.green("\n\u2705 aico-cli \u66F4\u65B0\u5B8C\u6210"));
|
|
76
|
-
resolve();
|
|
77
|
-
} else {
|
|
78
|
-
reject(new Error(`npm \u8FDB\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`));
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
child.on("error", (error) => {
|
|
82
|
-
clearTimeout(timeout);
|
|
83
|
-
reject(error);
|
|
84
|
-
});
|
|
85
|
-
const handleInterrupt = () => {
|
|
86
|
-
clearTimeout(timeout);
|
|
87
|
-
child.kill("SIGINT");
|
|
88
|
-
console.log(ansis.yellow("\n\n\u26A0\uFE0F \u66F4\u65B0\u5DF2\u53D6\u6D88"));
|
|
89
|
-
console.log(ansis.cyan("\u{1F4A1} \u8BF7\u624B\u52A8\u6267\u884C: npm install -g aico-cli"));
|
|
90
|
-
process.exit(0);
|
|
91
|
-
};
|
|
92
|
-
process.once("SIGINT", handleInterrupt);
|
|
93
|
-
child.on("close", () => {
|
|
94
|
-
process.removeListener("SIGINT", handleInterrupt);
|
|
95
|
-
});
|
|
472
|
+
timeout: 3e5,
|
|
473
|
+
// 5分钟超时(npm安装可能较慢)
|
|
474
|
+
signalHandling: "graceful"
|
|
96
475
|
});
|
|
476
|
+
if (result.success) {
|
|
477
|
+
console.log(ansis.green("\n\u2705 aico-cli \u66F4\u65B0\u5B8C\u6210"));
|
|
478
|
+
} else {
|
|
479
|
+
throw new Error(`npm \u8FDB\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${result.exitCode}`);
|
|
480
|
+
}
|
|
97
481
|
} catch (error) {
|
|
98
482
|
console.error(ansis.red("\u274C \u66F4\u65B0 aico-cli \u5931\u8D25:"));
|
|
99
|
-
if (error.code === "ENOENT") {
|
|
483
|
+
if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
|
|
100
484
|
console.error(ansis.yellow("\u{1F4A1} \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 npm"));
|
|
101
485
|
console.error(
|
|
102
486
|
ansis.gray(" \u8BF7\u8BBF\u95EE https://nodejs.org/ \u4E0B\u8F7D\u5E76\u5B89\u88C5 Node.js")
|
|
@@ -106,6 +490,7 @@ async function updateAicoCli() {
|
|
|
106
490
|
}
|
|
107
491
|
console.error(ansis.cyan("\n\u{1F4A1} \u6216\u8005\u8BF7\u590D\u5236\u4EE5\u4E0B\u547D\u4EE4\u5728\u547D\u4EE4\u884C\u4E2D\u624B\u52A8\u6267\u884C:"));
|
|
108
492
|
console.error(ansis.white(" npm install -g aico-cli"));
|
|
493
|
+
processManager.cleanup();
|
|
109
494
|
throw error;
|
|
110
495
|
}
|
|
111
496
|
}
|
|
@@ -116,39 +501,13 @@ promisify$1(exec$1);
|
|
|
116
501
|
|
|
117
502
|
promisify$1(exec$1);
|
|
118
503
|
|
|
119
|
-
const execAsync = promisify(exec);
|
|
120
|
-
async function run(command) {
|
|
121
|
-
try {
|
|
122
|
-
const result = await execAsync(command);
|
|
123
|
-
return result;
|
|
124
|
-
} catch (error) {
|
|
125
|
-
if (error instanceof Error) {
|
|
126
|
-
throw new Error(`Command failed: ${command}
|
|
127
|
-
Error: ${error.message}`);
|
|
128
|
-
}
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const runCommand = {
|
|
134
|
-
__proto__: null,
|
|
135
|
-
run: run
|
|
136
|
-
};
|
|
137
|
-
|
|
138
504
|
async function tryStartClaude(command, args, options) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} else {
|
|
146
|
-
reject(new Error(`Claude \u8FDB\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`));
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
child.on("error", (error) => {
|
|
150
|
-
reject(error);
|
|
151
|
-
});
|
|
505
|
+
await processManager.replaceProcess(command, args, {
|
|
506
|
+
name: "Claude Code",
|
|
507
|
+
cwd: options.cwd,
|
|
508
|
+
stdio: "inherit",
|
|
509
|
+
// 必须继承 stdio 才能正确交互
|
|
510
|
+
shell: options.shell
|
|
152
511
|
});
|
|
153
512
|
}
|
|
154
513
|
async function startClaudeCodeEditor(_lang) {
|
|
@@ -182,7 +541,7 @@ async function startClaudeCodeEditor(_lang) {
|
|
|
182
541
|
}
|
|
183
542
|
} catch (error) {
|
|
184
543
|
console.error(ansis.red("\u274C \u542F\u52A8\u4EE3\u7801\u7F16\u8F91\u5668\u5931\u8D25:"));
|
|
185
|
-
if (error.code === "ENOENT" || error.message?.includes("command not found")) {
|
|
544
|
+
if (error.code === "ENOENT" || error.message?.includes("command not found") || error.message?.includes("ENOENT")) {
|
|
186
545
|
console.error(ansis.yellow("\u{1F4A1} \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 Claude Code:"));
|
|
187
546
|
console.error(ansis.gray(" npm install -g @anthropic-ai/claude-code"));
|
|
188
547
|
if (process.platform === "win32") {
|
|
@@ -196,6 +555,7 @@ async function startClaudeCodeEditor(_lang) {
|
|
|
196
555
|
}
|
|
197
556
|
console.error(ansis.cyan("\n\u{1F4A1} \u6216\u8005\u8BF7\u590D\u5236\u4EE5\u4E0B\u547D\u4EE4\u5728\u547D\u4EE4\u884C\u4E2D\u624B\u52A8\u6267\u884C:"));
|
|
198
557
|
console.error(ansis.white(" claude --dangerously-skip-permissions"));
|
|
558
|
+
processManager.cleanup();
|
|
199
559
|
throw error;
|
|
200
560
|
}
|
|
201
561
|
}
|
|
@@ -212,7 +572,7 @@ async function launchCodeEditor() {
|
|
|
212
572
|
}
|
|
213
573
|
async function launchCUI() {
|
|
214
574
|
try {
|
|
215
|
-
const { run } = await
|
|
575
|
+
const { run } = await import('./chunks/run-command.mjs');
|
|
216
576
|
console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5PM2...\x1B[0m");
|
|
217
577
|
await run("npm install -g pm2");
|
|
218
578
|
console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5AICO\u53EF\u89C6\u5316\u754C\u9762...\x1B[0m");
|
|
@@ -504,6 +864,8 @@ async function startCodeEditor() {
|
|
|
504
864
|
await startClaudeCodeEditor();
|
|
505
865
|
} catch (error) {
|
|
506
866
|
console.error(ansis.red(`\u2716 \u542F\u52A8\u4EE3\u7801\u7F16\u8F91\u5668\u5931\u8D25: ${error}`));
|
|
867
|
+
const { processManager } = await Promise.resolve().then(function () { return processManager$1; });
|
|
868
|
+
processManager.cleanup();
|
|
507
869
|
process.exit(1);
|
|
508
870
|
}
|
|
509
871
|
}
|
|
@@ -529,14 +891,22 @@ function customizeHelp(sections) {
|
|
|
529
891
|
` ${ansis.cyan("aico c")} (--company) \u516C\u53F8\u914D\u7F6E`,
|
|
530
892
|
` ${ansis.cyan("aico p")} (--personal) \u4E2A\u4EBA\u914D\u7F6E`,
|
|
531
893
|
` ${ansis.cyan("aico ui")} (--ui) \u542F\u52A8\u53EF\u89C6\u5316\u754C\u9762`,
|
|
532
|
-
` ${ansis.cyan("aico h")} (--help) \u663E\u793A\u5E2E\u52A9
|
|
894
|
+
` ${ansis.cyan("aico h")} (--help) \u663E\u793A\u5E2E\u52A9`,
|
|
895
|
+
"",
|
|
896
|
+
ansis.gray(" \u8FDB\u7A0B\u7BA1\u7406:"),
|
|
897
|
+
` ${ansis.cyan("aico test-process")} \u6D4B\u8BD5\u8FDB\u7A0B\u7BA1\u7406\u529F\u80FD`,
|
|
898
|
+
` ${ansis.cyan("aico monitor-process")} \u542F\u52A8\u8FDB\u7A0B\u76D1\u63A7`,
|
|
899
|
+
` ${ansis.cyan("aico cleanup-process")} \u6E05\u7406\u6240\u6709\u8FDB\u7A0B`,
|
|
900
|
+
` ${ansis.cyan("aico test-replacement")} \u6D4B\u8BD5\u8FDB\u7A0B\u66FF\u6362\u529F\u80FD`,
|
|
901
|
+
` ${ansis.cyan("aico test-claude-start")} \u6D4B\u8BD5Claude Code\u542F\u52A8\u6D41\u7A0B`,
|
|
902
|
+
` ${ansis.cyan("aico test-start-flow")} \u6D4B\u8BD5\u5B9E\u9645\u542F\u52A8\u6D41\u7A0B\uFF08\u6A21\u62DF\uFF09`
|
|
533
903
|
].join("\n")
|
|
534
904
|
});
|
|
535
905
|
return sections;
|
|
536
906
|
}
|
|
537
907
|
async function startUI() {
|
|
538
908
|
try {
|
|
539
|
-
const { run } = await
|
|
909
|
+
const { run } = await import('./chunks/run-command.mjs');
|
|
540
910
|
console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5\u667A\u80FD\u8F6F\u4EF6\u661F\u5DE5\u5382\u53EF\u89C6\u5316\u754C\u9762...\x1B[0m");
|
|
541
911
|
await run("npm install -g pm2");
|
|
542
912
|
await run("npm install -g aico-cui");
|
|
@@ -572,4 +942,20 @@ async function main() {
|
|
|
572
942
|
setupCommands(cli);
|
|
573
943
|
cli.parse();
|
|
574
944
|
}
|
|
575
|
-
|
|
945
|
+
process.on("uncaughtException", (error) => {
|
|
946
|
+
console.error("\u672A\u6355\u83B7\u7684\u5F02\u5E38:", error);
|
|
947
|
+
processManager.cleanup();
|
|
948
|
+
process.exit(1);
|
|
949
|
+
});
|
|
950
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
951
|
+
console.error("\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:", reason);
|
|
952
|
+
processManager.cleanup();
|
|
953
|
+
process.exit(1);
|
|
954
|
+
});
|
|
955
|
+
main().catch((error) => {
|
|
956
|
+
console.error("CLI \u542F\u52A8\u5931\u8D25:", error);
|
|
957
|
+
processManager.cleanup();
|
|
958
|
+
process.exit(1);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
export { processManager as p };
|