aico-cli 0.4.8 → 0.4.9

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.
@@ -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.8";
16
+ const version = "0.4.9";
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
@@ -47,39 +47,6 @@ class ProcessManager extends EventEmitter {
47
47
  this.cleanup();
48
48
  });
49
49
  }
50
- /**
51
- * 获取适用于当前平台的命令路径
52
- */
53
- getPlatformCommand(command) {
54
- const isWindows = process.platform === "win32";
55
- if (isWindows) {
56
- const commandMappings = {
57
- "npm": "npm.cmd",
58
- "npx": "npx.cmd",
59
- "node": "node.exe",
60
- "claude": "claude.cmd"
61
- };
62
- return commandMappings[command] || command;
63
- }
64
- return command;
65
- }
66
- /**
67
- * 检查命令是否存在
68
- */
69
- async checkCommandExists(command) {
70
- try {
71
- const { execSync } = await import('node:child_process');
72
- const isWindows = process.platform === "win32";
73
- if (isWindows) {
74
- execSync(`where ${command}`, { stdio: "ignore" });
75
- } else {
76
- execSync(`which ${command}`, { stdio: "ignore" });
77
- }
78
- return true;
79
- } catch {
80
- return false;
81
- }
82
- }
83
50
  /**
84
51
  * 启动进程
85
52
  */
@@ -88,12 +55,7 @@ class ProcessManager extends EventEmitter {
88
55
  const processName = options.name || command;
89
56
  this.log(`\u542F\u52A8\u8FDB\u7A0B: ${processName}`, "info");
90
57
  try {
91
- const platformCommand = this.getPlatformCommand(command);
92
- const commandExists = await this.checkCommandExists(platformCommand);
93
- if (!commandExists) {
94
- throw new Error(`\u547D\u4EE4 '${platformCommand}' \u4E0D\u5B58\u5728\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5\u5E76\u6DFB\u52A0\u5230PATH\u73AF\u5883\u53D8\u91CF`);
95
- }
96
- const child = spawn(platformCommand, args, {
58
+ const child = spawn(command, args, {
97
59
  cwd: options.cwd || process.cwd(),
98
60
  env: { ...process.env, ...options.env },
99
61
  stdio: options.stdio || "pipe",
@@ -129,25 +91,13 @@ class ProcessManager extends EventEmitter {
129
91
  const exitCode = await new Promise((resolve, reject) => {
130
92
  child.on("close", (code) => {
131
93
  if (timeoutId) clearTimeout(timeoutId);
132
- if (child.pid) {
133
- this.unregisterProcess(child.pid);
134
- }
94
+ this.unregisterProcess(child.pid);
135
95
  resolve(code ?? 0);
136
96
  });
137
97
  child.on("error", (error) => {
138
98
  if (timeoutId) clearTimeout(timeoutId);
139
- if (child.pid) {
140
- this.unregisterProcess(child.pid);
141
- }
142
- let enhancedError = error;
143
- if (error.message?.includes("ENOENT")) {
144
- const isWindows = process.platform === "win32";
145
- const commandInfo = isWindows ? `\u547D\u4EE4 '${platformCommand}' \u4E0D\u5B58\u5728\u3002\u5728Windows\u4E0A\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5Node.js\u5E76\u6DFB\u52A0\u5230PATH\u73AF\u5883\u53D8\u91CF\u3002\u5C1D\u8BD5\u4F7F\u7528\u5B8C\u6574\u8DEF\u5F84\u6216\u68C0\u67E5\u547D\u4EE4\u662F\u5426\u6B63\u786E\u3002` : `\u547D\u4EE4 '${platformCommand}' \u4E0D\u5B58\u5728\u3002\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5\u5E76\u6DFB\u52A0\u5230PATH\u73AF\u5883\u53D8\u91CF\u3002`;
146
- enhancedError = new Error(`${error.message}
147
- ${commandInfo}`);
148
- enhancedError.stack = error.stack;
149
- }
150
- reject(enhancedError);
99
+ this.unregisterProcess(child.pid);
100
+ reject(error);
151
101
  });
152
102
  });
153
103
  const duration = Date.now() - startTime;
@@ -180,12 +130,6 @@ ${commandInfo}`);
180
130
  registerProcess(child, name) {
181
131
  if (!child.pid) {
182
132
  this.log(`\u65E0\u6CD5\u6CE8\u518C\u8FDB\u7A0B ${name}: \u6CA1\u6709PID`, "error");
183
- child.on("close", (code) => {
184
- this.log(`\u8FDB\u7A0B ${name} (\u65E0PID) \u5DF2\u5173\u95ED\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
185
- });
186
- child.on("error", (error) => {
187
- this.log(`\u8FDB\u7A0B ${name} (\u65E0PID) \u53D1\u751F\u9519\u8BEF: ${error}`, "error");
188
- });
189
133
  return;
190
134
  }
191
135
  if (this.processes.has(child.pid)) {
@@ -229,10 +173,6 @@ ${commandInfo}`);
229
173
  * 注销进程
230
174
  */
231
175
  unregisterProcess(pid) {
232
- if (!pid) {
233
- this.log(`\u5C1D\u8BD5\u6CE8\u9500\u65E0\u6548\u7684\u8FDB\u7A0B (PID: undefined)`, "warning");
234
- return;
235
- }
236
176
  const info = this.processes.get(pid);
237
177
  if (info) {
238
178
  this.log(`\u6CE8\u9500\u8FDB\u7A0B: ${info.name} (PID: ${pid})`, "info");
@@ -306,20 +246,14 @@ ${commandInfo}`);
306
246
  * 清理所有进程
307
247
  */
308
248
  cleanup() {
309
- const isWindows = process.platform === "win32";
310
- if (isWindows) {
311
- this.cleanupWindowsProcesses();
312
- }
313
249
  for (const [pid, info] of this.processes) {
314
250
  if (info.status === "running") {
315
251
  try {
316
- const signal = isWindows ? "SIGTERM" : "SIGKILL";
317
- info.process.kill(signal);
252
+ info.process.kill("SIGKILL");
318
253
  } catch {
319
254
  }
320
255
  }
321
256
  }
322
- this.cleanupZombieProcesses();
323
257
  this.processes.clear();
324
258
  }
325
259
  /**
@@ -401,18 +335,9 @@ ${commandInfo}`);
401
335
  cleanupZombieProcesses() {
402
336
  let cleanedCount = 0;
403
337
  const runningProcesses = this.getRunningProcessList();
404
- const isWindows = process.platform === "win32";
405
- this.log(`\u5F00\u59CB\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B\uFF0C\u5F53\u524D\u8FD0\u884C\u4E2D\u8FDB\u7A0B\u6570: ${runningProcesses.length}`, "info");
406
338
  for (const process2 of runningProcesses) {
407
339
  try {
408
340
  process2.process.kill(0);
409
- if (isWindows) {
410
- if (process2.process.exitCode !== null) {
411
- this.log(`\u6E05\u7406Windows\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
412
- this.unregisterProcess(process2.pid);
413
- cleanedCount++;
414
- }
415
- }
416
341
  } catch (error) {
417
342
  this.log(`\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
418
343
  this.unregisterProcess(process2.pid);
@@ -424,56 +349,6 @@ ${commandInfo}`);
424
349
  }
425
350
  return cleanedCount;
426
351
  }
427
- /**
428
- * 深度清理进程,包括可能的残留进程
429
- */
430
- deepCleanup() {
431
- const isWindows = process.platform === "win32";
432
- let totalCleaned = 0;
433
- this.log("\u5F00\u59CB\u6DF1\u5EA6\u6E05\u7406\u8FDB\u7A0B...", "info");
434
- totalCleaned += this.cleanupZombieProcesses();
435
- if (isWindows) {
436
- totalCleaned += this.cleanupWindowsProcesses();
437
- }
438
- const remainingProcesses = this.getProcessCount();
439
- if (remainingProcesses > 0) {
440
- this.log(`\u5F3A\u5236\u6E05\u7406\u5269\u4F59\u7684 ${remainingProcesses} \u4E2A\u8FDB\u7A0B`, "warning");
441
- this.cleanup();
442
- totalCleaned += remainingProcesses;
443
- }
444
- this.log(`\u6DF1\u5EA6\u6E05\u7406\u5B8C\u6210\uFF0C\u603B\u5171\u6E05\u7406\u4E86 ${totalCleaned} \u4E2A\u8FDB\u7A0B`, "info");
445
- return totalCleaned;
446
- }
447
- /**
448
- * Windows平台特定的进程清理
449
- * 针对Windows的进程管理特点进行优化
450
- */
451
- cleanupWindowsProcesses() {
452
- const isWindows = process.platform === "win32";
453
- if (!isWindows) {
454
- return 0;
455
- }
456
- let cleanedCount = 0;
457
- const runningProcesses = this.getRunningProcessList();
458
- this.log("\u6267\u884CWindows\u5E73\u53F0\u7279\u5B9A\u8FDB\u7A0B\u6E05\u7406...", "info");
459
- for (const process2 of runningProcesses) {
460
- try {
461
- if (process2.process.exitCode !== null) {
462
- this.log(`\u6E05\u7406Windows\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
463
- this.unregisterProcess(process2.pid);
464
- cleanedCount++;
465
- }
466
- } catch (error) {
467
- this.log(`\u5F3A\u5236\u6E05\u7406Windows\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "error");
468
- this.unregisterProcess(process2.pid);
469
- cleanedCount++;
470
- }
471
- }
472
- if (cleanedCount > 0) {
473
- this.log(`Windows\u5E73\u53F0\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u8FDB\u7A0B`, "info");
474
- }
475
- return cleanedCount;
476
- }
477
352
  /**
478
353
  * 添加进程到列表(用于测试)
479
354
  */
@@ -486,52 +361,33 @@ ${commandInfo}`);
486
361
  */
487
362
  async replaceProcess(command, args = [], options = {}) {
488
363
  const processName = options.name || command;
489
- const isWindows = process.platform === "win32";
490
364
  try {
365
+ this.log(`\u6E05\u7406 ${this.processes.size} \u4E2A\u7BA1\u7406\u8FDB\u7A0B...`, "info");
491
366
  this.cleanup();
492
367
  const { spawn: spawn2 } = await import('node:child_process');
493
- const finalCommand = this.getPlatformCommand(command);
494
- const finalArgs = args;
495
- const child = spawn2(finalCommand, finalArgs, {
368
+ const child = spawn2(command, args, {
496
369
  stdio: "inherit",
497
370
  // 继承所有 stdio
498
371
  cwd: options.cwd || process.cwd(),
499
372
  env: { ...process.env, ...options.env },
500
- shell: false,
501
- // 禁用shell模式,避免进程泄漏
502
- // Windows上使用更严格的进程控制
503
- windowsHide: isWindows
504
- // Windows上隐藏子进程窗口
373
+ shell: options.shell || process.platform === "win32"
374
+ // 移除 detached: true,避免子进程脱离管理
375
+ // detached: false 是默认值,子进程会随着父进程退出而退出
505
376
  });
506
377
  child.on("error", (error) => {
507
378
  this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
508
- this.cleanup();
509
379
  process.exit(1);
510
380
  });
511
381
  child.on("exit", (code) => {
512
382
  this.log(`\u66FF\u6362\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
513
- this.cleanup();
514
383
  process.exit(code || 0);
515
384
  });
516
385
  this.registerProcess(child, processName);
517
- if (isWindows) {
518
- const checkInterval = setInterval(() => {
519
- if (child.exitCode !== null) {
520
- clearInterval(checkInterval);
521
- this.cleanup();
522
- process.exit(child.exitCode || 0);
523
- }
524
- }, 1e3);
525
- child.on("exit", () => {
526
- clearInterval(checkInterval);
527
- });
528
- }
529
386
  await new Promise(() => {
530
387
  });
531
388
  process.exit(0);
532
389
  } catch (error) {
533
390
  this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
534
- this.cleanup();
535
391
  process.exit(1);
536
392
  }
537
393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aico-cli",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "packageManager": "pnpm@9.15.9",
5
5
  "description": "AI CLI",
6
6
  "repository": {