@zhin.js/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +120 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +21 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/build.d.ts +3 -0
  7. package/dist/commands/build.d.ts.map +1 -0
  8. package/dist/commands/build.js +61 -0
  9. package/dist/commands/build.js.map +1 -0
  10. package/dist/commands/dev.d.ts +3 -0
  11. package/dist/commands/dev.d.ts.map +1 -0
  12. package/dist/commands/dev.js +162 -0
  13. package/dist/commands/dev.js.map +1 -0
  14. package/dist/commands/init.d.ts +3 -0
  15. package/dist/commands/init.d.ts.map +1 -0
  16. package/dist/commands/init.js +784 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/start.d.ts +4 -0
  19. package/dist/commands/start.d.ts.map +1 -0
  20. package/dist/commands/start.js +212 -0
  21. package/dist/commands/start.js.map +1 -0
  22. package/dist/commands/stop.d.ts +3 -0
  23. package/dist/commands/stop.d.ts.map +1 -0
  24. package/dist/commands/stop.js +24 -0
  25. package/dist/commands/stop.js.map +1 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/utils/env.d.ts +11 -0
  31. package/dist/utils/env.d.ts.map +1 -0
  32. package/dist/utils/env.js +64 -0
  33. package/dist/utils/env.js.map +1 -0
  34. package/dist/utils/logger.d.ts +8 -0
  35. package/dist/utils/logger.d.ts.map +1 -0
  36. package/dist/utils/logger.js +18 -0
  37. package/dist/utils/logger.js.map +1 -0
  38. package/dist/utils/process.d.ts +29 -0
  39. package/dist/utils/process.d.ts.map +1 -0
  40. package/dist/utils/process.js +308 -0
  41. package/dist/utils/process.js.map +1 -0
  42. package/package.json +36 -0
  43. package/src/cli.ts +25 -0
  44. package/src/commands/build.ts +68 -0
  45. package/src/commands/dev.ts +188 -0
  46. package/src/commands/init.ts +826 -0
  47. package/src/commands/start.ts +236 -0
  48. package/src/commands/stop.ts +27 -0
  49. package/src/index.ts +1 -0
  50. package/src/utils/env.ts +70 -0
  51. package/src/utils/logger.ts +21 -0
  52. package/src/utils/process.ts +348 -0
  53. package/tsconfig.json +24 -0
  54. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,236 @@
1
+ import { Command } from 'commander';
2
+ import { logger } from '../utils/logger.js';
3
+ import { loadEnvFiles } from '../utils/env.js';
4
+ import { spawn, ChildProcess } from 'child_process';
5
+ import fs from 'fs-extra';
6
+ import path from 'path';
7
+ import { startProcess } from '../utils/process.js';
8
+
9
+ export const startCommand = new Command('start')
10
+ .description('生产模式启动机器人')
11
+ .option('-d, --daemon', '后台运行', false)
12
+ .option('--log-file [file]', '日志文件路径')
13
+ .option('--bun', '使用 bun 运行(默认使用 node)', false)
14
+ .action(async (options: { daemon: boolean; logFile?: string; bun: boolean }) => {
15
+ try {
16
+ const cwd = process.cwd();
17
+
18
+ // 检查是否是Zhin项目
19
+ if (!isZhinProject(cwd)) {
20
+ logger.error('当前目录不是Zhin项目');
21
+ logger.info('请在Zhin项目根目录运行此命令');
22
+ process.exit(1);
23
+ }
24
+
25
+ // 加载环境变量文件
26
+ logger.info('🔍 正在加载环境变量...');
27
+ loadEnvFiles(cwd, 'production');
28
+
29
+ // 检查构建产物
30
+ const distPath = path.join(cwd, 'dist');
31
+ const sourcePath = path.join(cwd, 'src');
32
+ const sourceFile = path.join(sourcePath, 'index.ts');
33
+ const distFile = path.join(distPath, 'index.js');
34
+ const entryFile = options.bun ? path.relative(cwd,sourceFile) : path.relative(cwd,distFile);
35
+
36
+ if (!fs.existsSync(entryFile)) {
37
+ logger.error('构建产物不存在,请先运行 zhin build');
38
+ process.exit(1);
39
+ }
40
+
41
+ logger.info('🚀 正在生产模式启动机器人...');
42
+
43
+ // 启动机器人的函数
44
+ const startBot = async (): Promise<ChildProcess> => {
45
+ // 设置环境变量
46
+ const env = {
47
+ ...process.env,
48
+ NODE_ENV: 'production'
49
+ };
50
+
51
+ // 配置stdio
52
+ let stdio: any = 'inherit';
53
+ if (options.daemon) {
54
+ stdio = options.logFile ?
55
+ ['ignore', fs.openSync(options.logFile, 'a'), fs.openSync(options.logFile, 'a')] :
56
+ 'ignore';
57
+ }
58
+
59
+ // 选择运行时
60
+ const runtime = options.bun ? 'bun' : 'node';
61
+ const args = options.bun ? [entryFile] : ['--expose-gc', entryFile];
62
+
63
+ logger.info(`📦 启动命令: ${runtime} ${args.join(' ')}`);
64
+ return startProcess(runtime, args, cwd,options.daemon);
65
+ };
66
+
67
+ let child = await startBot();
68
+ let isRestarting = false;
69
+
70
+ // 重启函数
71
+ const restartBot =async () => {
72
+ if (isRestarting) return;
73
+ isRestarting = true;
74
+
75
+ logger.info('🔄 正在重启机器人...');
76
+
77
+ // 优雅关闭当前进程
78
+ if (child && !child.killed) {
79
+ const oldChild=child
80
+ oldChild.kill('SIGTERM');
81
+ // 如果5秒后还没关闭,强制杀掉
82
+ setTimeout(() => {
83
+ if (oldChild && !oldChild.killed) {
84
+ oldChild.kill('SIGKILL');
85
+ }
86
+ }, 5000);
87
+ }
88
+ child = await startBot();
89
+ setupChildHandlers(child);
90
+ isRestarting = false;
91
+ };
92
+
93
+ // 设置子进程处理器
94
+ const setupChildHandlers = (childProcess: ChildProcess) => {
95
+ if (options.daemon) {
96
+ // 后台运行
97
+ childProcess.unref();
98
+ logger.info(`✅ 机器人已在后台启动 (子进程PID: ${childProcess.pid})`);
99
+
100
+ if (options.logFile) {
101
+ logger.info(`📝 日志输出到: ${options.logFile}`);
102
+ }
103
+ } else {
104
+ // 前台运行
105
+ childProcess.on('error', (error) => {
106
+ if (!isRestarting) {
107
+ logger.error(`❌ 启动失败: ${error.message}`);
108
+ process.exit(1);
109
+ }
110
+ });
111
+
112
+ childProcess.on('exit', async (code) => {
113
+ if (!isRestarting) {
114
+ if (code === 51) {
115
+ return await restartBot();
116
+ }
117
+ if (code !== 0) {
118
+ logger.error(`💀 进程异常退出,代码: ${code}`);
119
+ } else {
120
+ logger.info('✅ 机器人已正常退出');
121
+ }
122
+ process.exit(code || 0);
123
+ }
124
+ });
125
+ }
126
+ };
127
+
128
+ // 设置初始子进程处理器
129
+ setupChildHandlers(child);
130
+
131
+ let killing=false
132
+ // 处理退出信号
133
+ const cleanup = () => {
134
+ if(options.daemon) return process.exit(0)
135
+ if(killing) return
136
+ killing=true
137
+ logger.info('🛑 正在关闭机器人...');
138
+ if (child && !child.killed) {
139
+ child.kill('SIGTERM');
140
+
141
+ setTimeout(() => {
142
+ if (child && !child.killed) {
143
+ child.kill('SIGKILL');
144
+ killing=false
145
+ }
146
+ }, 5000);
147
+ }
148
+
149
+ // 清理PID文件
150
+ const pidFile = path.join(cwd, '.zhin.pid');
151
+ if (fs.existsSync(pidFile)) {
152
+ fs.removeSync(pidFile);
153
+ }
154
+ };
155
+
156
+ process.on('SIGINT', cleanup);
157
+ process.on('SIGTERM', cleanup);
158
+
159
+ // 如果是后台运行,保持主进程运行以管理子进程
160
+ if (options.daemon) {
161
+ logger.info('💡 重启方式: 在插件中调用 process.exit(51)');
162
+ logger.info('💡 停止机器人: kill -TERM ' + child.pid);
163
+ process.exit(0)
164
+ } else {
165
+ // 前台运行时也显示重启提示
166
+ logger.info('💡 前台运行模式,按 Ctrl+C 退出');
167
+ logger.info('💡 重启方式: 在插件中调用 process.exit(51)');
168
+ }
169
+ } catch (error) {
170
+ logger.error(`❌ 启动失败: ${error}`);
171
+ process.exit(1);
172
+ }
173
+ });
174
+
175
+ export const restartCommand = new Command('restart')
176
+ .description('重启生产模式的机器人进程')
177
+ .action(async () => {
178
+ try {
179
+ const cwd = process.cwd();
180
+ const pidFile = path.join(cwd, '.zhin.pid');
181
+
182
+ // 检查PID文件是否存在
183
+ if (!fs.existsSync(pidFile)) {
184
+ logger.error('未找到运行中的机器人进程');
185
+ logger.info('请先使用 zhin start 启动机器人');
186
+ process.exit(1);
187
+ }
188
+
189
+ // 读取PID
190
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim());
191
+
192
+ if (isNaN(pid)) {
193
+ logger.error('PID文件格式错误');
194
+ process.exit(1);
195
+ }
196
+
197
+ try {
198
+ // 检查进程是否存在
199
+ process.kill(pid, 0);
200
+
201
+ // 发送重启信号
202
+ process.kill(pid, 51);
203
+ logger.info(`🔄 已发送重启信号给进程 ${pid}`);
204
+
205
+ } catch (error: any) {
206
+ if (error.code === 'ESRCH') {
207
+ logger.error('进程不存在,清理PID文件');
208
+ fs.removeSync(pidFile);
209
+ } else {
210
+ logger.error(`发送信号失败: ${error.message}`);
211
+ }
212
+ process.exit(1);
213
+ }
214
+
215
+ } catch (error) {
216
+ logger.error(`重启失败: ${error}`);
217
+ process.exit(1);
218
+ }
219
+ });
220
+
221
+ function isZhinProject(cwd: string): boolean {
222
+ const packageJsonPath = path.join(cwd, 'package.json');
223
+ if (!fs.existsSync(packageJsonPath)) {
224
+ return false;
225
+ }
226
+
227
+ try {
228
+ const packageJson = fs.readJsonSync(packageJsonPath);
229
+ return packageJson.dependencies && (
230
+ packageJson.dependencies['zhin.js'] ||
231
+ packageJson.devDependencies?.['zhin.js']
232
+ );
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
@@ -0,0 +1,27 @@
1
+ import { Command } from 'commander';
2
+ import { logger } from '../utils/logger.js';
3
+ import { stopProcess, getProcessStatus } from '../utils/process.js';
4
+
5
+ export const stopCommand = new Command('stop')
6
+ .description('停止机器人')
7
+ .action(async () => {
8
+ try {
9
+ const cwd = process.cwd();
10
+
11
+ // 检查是否有运行中的进程
12
+ const status = await getProcessStatus(cwd);
13
+ if (!status.running) {
14
+ logger.warn('没有运行中的机器人进程');
15
+ return;
16
+ }
17
+
18
+ logger.info(`正在停止机器人 (PID: ${status.pid})...`);
19
+
20
+ // 停止进程
21
+ await stopProcess(cwd);
22
+
23
+ } catch (error) {
24
+ logger.error(`停止失败: ${error}`);
25
+ process.exit(1);
26
+ }
27
+ });
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { logger } from './utils/logger.js';
@@ -0,0 +1,70 @@
1
+ import dotenv from 'dotenv';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { logger } from './logger.js';
5
+
6
+ /**
7
+ * 加载环境变量文件
8
+ * 加载顺序:.env -> .env.${NODE_ENV}
9
+ * 后加载的文件会覆盖前面的配置
10
+ */
11
+ export function loadEnvFiles(cwd: string, nodeEnv: string): void {
12
+ const envFiles = [
13
+ '.env',
14
+ `.env.${nodeEnv}`
15
+ ];
16
+
17
+ let loadedFiles: string[] = [];
18
+ let totalVars = 0;
19
+
20
+ for (const envFile of envFiles) {
21
+ const envPath = path.join(cwd, envFile);
22
+
23
+ if (fs.existsSync(envPath)) {
24
+ try {
25
+ const result = dotenv.config({ path: envPath });
26
+
27
+ if (result.parsed) {
28
+ const varCount = Object.keys(result.parsed).length;
29
+ totalVars += varCount;
30
+ loadedFiles.push(`${envFile} (${varCount} vars)`);
31
+
32
+ // 在debug模式下显示详细信息
33
+ if (process.env.ZHIN_DEBUG === 'true') {
34
+ logger.info(`📄 已加载环境变量文件: ${envFile}`);
35
+
36
+ // 打印加载的变量(仅在debug模式)
37
+ Object.keys(result.parsed).forEach(key => {
38
+ const value = result.parsed![key];
39
+ const displayValue = key.toLowerCase().includes('password') ||
40
+ key.toLowerCase().includes('secret') ||
41
+ key.toLowerCase().includes('token')
42
+ ? '***' : value;
43
+ logger.info(` - ${key}=${displayValue}`);
44
+ });
45
+ }
46
+ }
47
+ } catch (error) {
48
+ logger.warn(`⚠️ 加载环境变量文件 ${envFile} 失败: ${error instanceof Error ? error.message : error}`);
49
+ }
50
+ }
51
+ }
52
+
53
+ if (loadedFiles.length > 0) {
54
+ logger.info(`🔧 已加载环境变量: ${loadedFiles.join(', ')} (共 ${totalVars} 个变量)`);
55
+ } else {
56
+ if (process.env.ZHIN_DEBUG === 'true') {
57
+ logger.info('💡 未找到环境变量文件,使用系统环境变量');
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 获取环境变量优先级说明
64
+ */
65
+ export function getEnvLoadOrder(nodeEnv: string): string[] {
66
+ return [
67
+ '.env (基础配置)',
68
+ `.env.${nodeEnv} (${nodeEnv}环境特定配置,优先级更高)`
69
+ ];
70
+ }
@@ -0,0 +1,21 @@
1
+ export const logger = {
2
+ info: (message: string,...args: any[]) => {
3
+ console.log('[INFO] [CLI]:', message,...args);
4
+ },
5
+
6
+ success: (message: string,...args: any[]) => {
7
+ console.log('[SUCCESS] [CLI]:', message,...args);
8
+ },
9
+
10
+ warn: (message: string,...args: any[]) => {
11
+ console.log('[WARN] [CLI]:', message,...args);
12
+ },
13
+
14
+ error: (message: string,...args: any[]) => {
15
+ console.log('[ERROR] [CLI]:', message,...args);
16
+ },
17
+
18
+ log: (message: string,...args: any[]) => {
19
+ console.log('[LOG] [CLI]:', message,...args);
20
+ }
21
+ };
@@ -0,0 +1,348 @@
1
+ import { spawn, ChildProcess, exec } from 'child_process';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { logger } from './logger.js';
5
+ import os from 'os';
6
+
7
+ const PID_FILE = '.zhin.pid';
8
+
9
+ /**
10
+ * 检查进程是否存在的跨平台实现
11
+ */
12
+ async function isProcessRunning(pid: number): Promise<boolean> {
13
+ return new Promise((resolve) => {
14
+ const platform = os.platform();
15
+
16
+ if (platform === 'win32') {
17
+ // Windows: 使用 tasklist 命令
18
+ exec(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, (error, stdout) => {
19
+ if (error) {
20
+ resolve(false);
21
+ return;
22
+ }
23
+ // tasklist 输出包含进程信息表示进程存在
24
+ resolve(stdout.trim().length > 0 && !stdout.includes('INFO: No tasks'));
25
+ });
26
+ } else {
27
+ // Linux/macOS: 使用 ps 命令
28
+ exec(`ps -p ${pid} -o pid=`, (error, stdout) => {
29
+ if (error) {
30
+ resolve(false);
31
+ return;
32
+ }
33
+ // ps 输出包含 PID 表示进程存在
34
+ resolve(stdout.trim().length > 0);
35
+ });
36
+ }
37
+ });
38
+ }
39
+
40
+ /**
41
+ * 终止进程的跨平台实现
42
+ */
43
+ async function killProcess(pid: number, signal: string = 'SIGTERM'): Promise<boolean> {
44
+ return new Promise((resolve) => {
45
+ const platform = os.platform();
46
+
47
+ if (platform === 'win32') {
48
+ // Windows: 使用 taskkill 命令
49
+ const forceFlag = signal === 'SIGKILL' ? '/F' : '';
50
+ exec(`taskkill /PID ${pid} ${forceFlag}`, (error) => {
51
+ resolve(!error);
52
+ });
53
+ } else {
54
+ // Linux/macOS: 使用 kill 命令
55
+ const signalFlag = signal === 'SIGKILL' ? '-9' : '-15';
56
+ exec(`kill ${signalFlag} ${pid}`, (error) => {
57
+ resolve(!error);
58
+ });
59
+ }
60
+ });
61
+ }
62
+
63
+ export async function startProcess(command: string, args: string[], cwd: string,daemon:boolean): Promise<ChildProcess> {
64
+ return new Promise((resolve, reject) => {
65
+ const child = spawn(command, args, {
66
+ cwd,
67
+ detached: daemon,
68
+ stdio: 'ignore',
69
+ env: { ...process.env }
70
+ });
71
+
72
+ if(daemon) child.unref();
73
+
74
+ child.on('spawn', () => {
75
+ // 保存进程ID
76
+ const pidFile = path.join(cwd, PID_FILE);
77
+ fs.writeFileSync(pidFile, child.pid!.toString());
78
+ logger.success(`机器人已启动,PID: ${child.pid}`);
79
+ resolve(child);
80
+ });
81
+
82
+ child.on('error', (error) => {
83
+ logger.error(`启动失败: ${error.message}`);
84
+ reject(error);
85
+ });
86
+
87
+ // 设置超时检查
88
+ setTimeout(() => {
89
+ if (child.killed) {
90
+ reject(new Error('进程启动超时'));
91
+ }
92
+ }, 5000);
93
+ });
94
+ }
95
+
96
+ export async function stopProcess(cwd: string): Promise<void> {
97
+ const pidFile = path.join(cwd, PID_FILE);
98
+
99
+ if (!fs.existsSync(pidFile)) {
100
+ logger.warn('没有找到运行中的机器人进程');
101
+ return;
102
+ }
103
+
104
+ try {
105
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8'));
106
+
107
+ // 检查进程是否存在
108
+ const isRunning = await isProcessRunning(pid);
109
+ if (!isRunning) {
110
+ logger.warn('进程已不存在,清理PID文件');
111
+ fs.removeSync(pidFile);
112
+ return;
113
+ }
114
+
115
+ // 终止进程
116
+ const killed = await killProcess(pid, 'SIGTERM');
117
+ if (!killed) {
118
+ logger.warn('无法终止进程,可能进程已结束');
119
+ fs.removeSync(pidFile);
120
+ return;
121
+ }
122
+
123
+ // 等待进程结束
124
+ let attempts = 0;
125
+ while (attempts < 30) {
126
+ const stillRunning = await isProcessRunning(pid);
127
+ if (!stillRunning) {
128
+ break;
129
+ }
130
+ await new Promise(resolve => setTimeout(resolve, 100));
131
+ attempts++;
132
+ }
133
+
134
+ // 如果进程仍然存在,强制结束
135
+ if (attempts >= 30) {
136
+ logger.warn('进程未响应,尝试强制结束');
137
+ await killProcess(pid, 'SIGKILL');
138
+
139
+ // 再次等待
140
+ attempts = 0;
141
+ while (attempts < 10) {
142
+ const stillRunning = await isProcessRunning(pid);
143
+ if (!stillRunning) {
144
+ break;
145
+ }
146
+ await new Promise(resolve => setTimeout(resolve, 100));
147
+ attempts++;
148
+ }
149
+ }
150
+
151
+ fs.removeSync(pidFile);
152
+ logger.success('机器人已停止');
153
+
154
+ } catch (error) {
155
+ logger.error(`停止进程时发生错误: ${error}`);
156
+ // 清理PID文件
157
+ fs.removeSync(pidFile);
158
+ }
159
+ }
160
+
161
+ export async function getProcessStatus(cwd: string): Promise<{ running: boolean; pid?: number }> {
162
+ const pidFile = path.join(cwd, PID_FILE);
163
+
164
+ if (!fs.existsSync(pidFile)) {
165
+ return { running: false };
166
+ }
167
+
168
+ try {
169
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8'));
170
+
171
+ // 检查进程是否存在
172
+ const isRunning = await isProcessRunning(pid);
173
+ if (!isRunning) {
174
+ // 进程不存在,清理PID文件
175
+ fs.removeSync(pidFile);
176
+ return { running: false };
177
+ }
178
+
179
+ return { running: true, pid };
180
+ } catch {
181
+ return { running: false };
182
+ }
183
+ }
184
+
185
+ /**
186
+ * 获取进程详细状态信息
187
+ */
188
+ export async function getProcessStatusDetailed(cwd: string): Promise<{
189
+ running: boolean;
190
+ pid?: number;
191
+ info?: { name: string; memory?: string; cpu?: string }
192
+ }> {
193
+ const pidFile = path.join(cwd, PID_FILE);
194
+
195
+ if (!fs.existsSync(pidFile)) {
196
+ return { running: false };
197
+ }
198
+
199
+ try {
200
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8'));
201
+
202
+ // 检查进程是否存在
203
+ const isRunning = await isProcessRunning(pid);
204
+ if (!isRunning) {
205
+ // 进程不存在,清理PID文件
206
+ fs.removeSync(pidFile);
207
+ return { running: false };
208
+ }
209
+
210
+ // 获取进程详细信息
211
+ const info = await getProcessInfo(pid);
212
+
213
+ return { running: true, pid, info: info || undefined };
214
+ } catch {
215
+ return { running: false };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * 获取进程详细信息的跨平台实现
221
+ */
222
+ async function getProcessInfo(pid: number): Promise<{ name: string; memory?: string; cpu?: string } | null> {
223
+ return new Promise((resolve) => {
224
+ const platform = os.platform();
225
+
226
+ if (platform === 'win32') {
227
+ // Windows: 使用 tasklist 命令获取详细信息
228
+ exec(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, (error, stdout) => {
229
+ if (error || !stdout.trim() || stdout.includes('INFO: No tasks')) {
230
+ resolve(null);
231
+ return;
232
+ }
233
+
234
+ // 解析 CSV 格式输出: "进程名","PID","会话名","会话#","内存使用"
235
+ const lines = stdout.trim().split('\n');
236
+ if (lines.length > 0) {
237
+ const parts = lines[0].split(',');
238
+ if (parts.length >= 5) {
239
+ const name = parts[0].replace(/"/g, '');
240
+ const memory = parts[4].replace(/"/g, '');
241
+ resolve({ name, memory });
242
+ } else {
243
+ resolve({ name: 'Unknown' });
244
+ }
245
+ } else {
246
+ resolve(null);
247
+ }
248
+ });
249
+ } else {
250
+ // Linux/macOS: 使用 ps 命令获取详细信息
251
+ exec(`ps -p ${pid} -o comm=,rss=,pcpu=`, (error, stdout) => {
252
+ if (error || !stdout.trim()) {
253
+ resolve(null);
254
+ return;
255
+ }
256
+
257
+ const lines = stdout.trim().split('\n');
258
+ if (lines.length > 0) {
259
+ const parts = lines[0].trim().split(/\s+/);
260
+ if (parts.length >= 3) {
261
+ const name = parts[0];
262
+ const memory = `${(parseInt(parts[1]) / 1024).toFixed(1)}MB`;
263
+ const cpu = `${parseFloat(parts[2]).toFixed(1)}%`;
264
+ resolve({ name, memory, cpu });
265
+ } else {
266
+ resolve({ name: 'Unknown' });
267
+ }
268
+ } else {
269
+ resolve(null);
270
+ }
271
+ });
272
+ }
273
+ });
274
+ }
275
+
276
+ /**
277
+ * 获取所有相关进程的列表
278
+ */
279
+ async function getRelatedProcessesInternal(processName: string): Promise<Array<{ pid: number; name: string; memory?: string; cpu?: string }>> {
280
+ return new Promise((resolve) => {
281
+ const platform = os.platform();
282
+
283
+ if (platform === 'win32') {
284
+ // Windows: 使用 tasklist 命令
285
+ exec(`tasklist /FI "IMAGENAME eq ${processName}*" /FO CSV /NH`, (error, stdout) => {
286
+ if (error || !stdout.trim() || stdout.includes('INFO: No tasks')) {
287
+ resolve([]);
288
+ return;
289
+ }
290
+
291
+ const processes: Array<{ pid: number; name: string; memory?: string; cpu?: string }> = [];
292
+ const lines = stdout.trim().split('\n');
293
+
294
+ for (const line of lines) {
295
+ const parts = line.split(',');
296
+ if (parts.length >= 5) {
297
+ const name = parts[0].replace(/"/g, '');
298
+ const pid = parseInt(parts[1].replace(/"/g, ''));
299
+ const memory = parts[4].replace(/"/g, '');
300
+ if (!isNaN(pid)) {
301
+ processes.push({ pid, name, memory });
302
+ }
303
+ }
304
+ }
305
+
306
+ resolve(processes);
307
+ });
308
+ } else {
309
+ // Linux/macOS: 使用 ps 命令
310
+ exec(`ps -C "${processName}" -o pid=,comm=,rss=,pcpu=`, (error, stdout) => {
311
+ if (error || !stdout.trim()) {
312
+ resolve([]);
313
+ return;
314
+ }
315
+
316
+ const processes: Array<{ pid: number; name: string; memory?: string; cpu?: string }> = [];
317
+ const lines = stdout.trim().split('\n');
318
+
319
+ for (const line of lines) {
320
+ const parts = line.trim().split(/\s+/);
321
+ if (parts.length >= 4) {
322
+ const pid = parseInt(parts[0]);
323
+ const name = parts[1];
324
+ const memory = `${(parseInt(parts[2]) / 1024).toFixed(1)}MB`;
325
+ const cpu = `${parseFloat(parts[3]).toFixed(1)}%`;
326
+ if (!isNaN(pid)) {
327
+ processes.push({ pid, name, memory, cpu });
328
+ }
329
+ }
330
+ }
331
+
332
+ resolve(processes);
333
+ });
334
+ }
335
+ });
336
+ }
337
+
338
+ /**
339
+ * 获取所有相关进程列表
340
+ */
341
+ export async function getRelatedProcesses(processName: string): Promise<Array<{
342
+ pid: number;
343
+ name: string;
344
+ memory?: string;
345
+ cpu?: string
346
+ }>> {
347
+ return await getRelatedProcessesInternal(processName);
348
+ }