@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.
- package/README.md +120 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +61 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +3 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +162 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +784 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/start.d.ts +4 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +212 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stop.d.ts +3 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +24 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/env.d.ts +11 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +64 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +18 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/process.d.ts +29 -0
- package/dist/utils/process.d.ts.map +1 -0
- package/dist/utils/process.js +308 -0
- package/dist/utils/process.js.map +1 -0
- package/package.json +36 -0
- package/src/cli.ts +25 -0
- package/src/commands/build.ts +68 -0
- package/src/commands/dev.ts +188 -0
- package/src/commands/init.ts +826 -0
- package/src/commands/start.ts +236 -0
- package/src/commands/stop.ts +27 -0
- package/src/index.ts +1 -0
- package/src/utils/env.ts +70 -0
- package/src/utils/logger.ts +21 -0
- package/src/utils/process.ts +348 -0
- package/tsconfig.json +24 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { spawn, 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
|
+
const PID_FILE = '.zhin.pid';
|
|
7
|
+
/**
|
|
8
|
+
* 检查进程是否存在的跨平台实现
|
|
9
|
+
*/
|
|
10
|
+
async function isProcessRunning(pid) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
if (platform === 'win32') {
|
|
14
|
+
// Windows: 使用 tasklist 命令
|
|
15
|
+
exec(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, (error, stdout) => {
|
|
16
|
+
if (error) {
|
|
17
|
+
resolve(false);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// tasklist 输出包含进程信息表示进程存在
|
|
21
|
+
resolve(stdout.trim().length > 0 && !stdout.includes('INFO: No tasks'));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Linux/macOS: 使用 ps 命令
|
|
26
|
+
exec(`ps -p ${pid} -o pid=`, (error, stdout) => {
|
|
27
|
+
if (error) {
|
|
28
|
+
resolve(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// ps 输出包含 PID 表示进程存在
|
|
32
|
+
resolve(stdout.trim().length > 0);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 终止进程的跨平台实现
|
|
39
|
+
*/
|
|
40
|
+
async function killProcess(pid, signal = 'SIGTERM') {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const platform = os.platform();
|
|
43
|
+
if (platform === 'win32') {
|
|
44
|
+
// Windows: 使用 taskkill 命令
|
|
45
|
+
const forceFlag = signal === 'SIGKILL' ? '/F' : '';
|
|
46
|
+
exec(`taskkill /PID ${pid} ${forceFlag}`, (error) => {
|
|
47
|
+
resolve(!error);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Linux/macOS: 使用 kill 命令
|
|
52
|
+
const signalFlag = signal === 'SIGKILL' ? '-9' : '-15';
|
|
53
|
+
exec(`kill ${signalFlag} ${pid}`, (error) => {
|
|
54
|
+
resolve(!error);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function startProcess(command, args, cwd, daemon) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const child = spawn(command, args, {
|
|
62
|
+
cwd,
|
|
63
|
+
detached: daemon,
|
|
64
|
+
stdio: 'ignore',
|
|
65
|
+
env: { ...process.env }
|
|
66
|
+
});
|
|
67
|
+
if (daemon)
|
|
68
|
+
child.unref();
|
|
69
|
+
child.on('spawn', () => {
|
|
70
|
+
// 保存进程ID
|
|
71
|
+
const pidFile = path.join(cwd, PID_FILE);
|
|
72
|
+
fs.writeFileSync(pidFile, child.pid.toString());
|
|
73
|
+
logger.success(`机器人已启动,PID: ${child.pid}`);
|
|
74
|
+
resolve(child);
|
|
75
|
+
});
|
|
76
|
+
child.on('error', (error) => {
|
|
77
|
+
logger.error(`启动失败: ${error.message}`);
|
|
78
|
+
reject(error);
|
|
79
|
+
});
|
|
80
|
+
// 设置超时检查
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
if (child.killed) {
|
|
83
|
+
reject(new Error('进程启动超时'));
|
|
84
|
+
}
|
|
85
|
+
}, 5000);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export async function stopProcess(cwd) {
|
|
89
|
+
const pidFile = path.join(cwd, PID_FILE);
|
|
90
|
+
if (!fs.existsSync(pidFile)) {
|
|
91
|
+
logger.warn('没有找到运行中的机器人进程');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8'));
|
|
96
|
+
// 检查进程是否存在
|
|
97
|
+
const isRunning = await isProcessRunning(pid);
|
|
98
|
+
if (!isRunning) {
|
|
99
|
+
logger.warn('进程已不存在,清理PID文件');
|
|
100
|
+
fs.removeSync(pidFile);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// 终止进程
|
|
104
|
+
const killed = await killProcess(pid, 'SIGTERM');
|
|
105
|
+
if (!killed) {
|
|
106
|
+
logger.warn('无法终止进程,可能进程已结束');
|
|
107
|
+
fs.removeSync(pidFile);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// 等待进程结束
|
|
111
|
+
let attempts = 0;
|
|
112
|
+
while (attempts < 30) {
|
|
113
|
+
const stillRunning = await isProcessRunning(pid);
|
|
114
|
+
if (!stillRunning) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
118
|
+
attempts++;
|
|
119
|
+
}
|
|
120
|
+
// 如果进程仍然存在,强制结束
|
|
121
|
+
if (attempts >= 30) {
|
|
122
|
+
logger.warn('进程未响应,尝试强制结束');
|
|
123
|
+
await killProcess(pid, 'SIGKILL');
|
|
124
|
+
// 再次等待
|
|
125
|
+
attempts = 0;
|
|
126
|
+
while (attempts < 10) {
|
|
127
|
+
const stillRunning = await isProcessRunning(pid);
|
|
128
|
+
if (!stillRunning) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
132
|
+
attempts++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
fs.removeSync(pidFile);
|
|
136
|
+
logger.success('机器人已停止');
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
logger.error(`停止进程时发生错误: ${error}`);
|
|
140
|
+
// 清理PID文件
|
|
141
|
+
fs.removeSync(pidFile);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export async function getProcessStatus(cwd) {
|
|
145
|
+
const pidFile = path.join(cwd, PID_FILE);
|
|
146
|
+
if (!fs.existsSync(pidFile)) {
|
|
147
|
+
return { running: false };
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8'));
|
|
151
|
+
// 检查进程是否存在
|
|
152
|
+
const isRunning = await isProcessRunning(pid);
|
|
153
|
+
if (!isRunning) {
|
|
154
|
+
// 进程不存在,清理PID文件
|
|
155
|
+
fs.removeSync(pidFile);
|
|
156
|
+
return { running: false };
|
|
157
|
+
}
|
|
158
|
+
return { running: true, pid };
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { running: false };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 获取进程详细状态信息
|
|
166
|
+
*/
|
|
167
|
+
export async function getProcessStatusDetailed(cwd) {
|
|
168
|
+
const pidFile = path.join(cwd, PID_FILE);
|
|
169
|
+
if (!fs.existsSync(pidFile)) {
|
|
170
|
+
return { running: false };
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8'));
|
|
174
|
+
// 检查进程是否存在
|
|
175
|
+
const isRunning = await isProcessRunning(pid);
|
|
176
|
+
if (!isRunning) {
|
|
177
|
+
// 进程不存在,清理PID文件
|
|
178
|
+
fs.removeSync(pidFile);
|
|
179
|
+
return { running: false };
|
|
180
|
+
}
|
|
181
|
+
// 获取进程详细信息
|
|
182
|
+
const info = await getProcessInfo(pid);
|
|
183
|
+
return { running: true, pid, info: info || undefined };
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return { running: false };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 获取进程详细信息的跨平台实现
|
|
191
|
+
*/
|
|
192
|
+
async function getProcessInfo(pid) {
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
const platform = os.platform();
|
|
195
|
+
if (platform === 'win32') {
|
|
196
|
+
// Windows: 使用 tasklist 命令获取详细信息
|
|
197
|
+
exec(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, (error, stdout) => {
|
|
198
|
+
if (error || !stdout.trim() || stdout.includes('INFO: No tasks')) {
|
|
199
|
+
resolve(null);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// 解析 CSV 格式输出: "进程名","PID","会话名","会话#","内存使用"
|
|
203
|
+
const lines = stdout.trim().split('\n');
|
|
204
|
+
if (lines.length > 0) {
|
|
205
|
+
const parts = lines[0].split(',');
|
|
206
|
+
if (parts.length >= 5) {
|
|
207
|
+
const name = parts[0].replace(/"/g, '');
|
|
208
|
+
const memory = parts[4].replace(/"/g, '');
|
|
209
|
+
resolve({ name, memory });
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
resolve({ name: 'Unknown' });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
resolve(null);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// Linux/macOS: 使用 ps 命令获取详细信息
|
|
222
|
+
exec(`ps -p ${pid} -o comm=,rss=,pcpu=`, (error, stdout) => {
|
|
223
|
+
if (error || !stdout.trim()) {
|
|
224
|
+
resolve(null);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const lines = stdout.trim().split('\n');
|
|
228
|
+
if (lines.length > 0) {
|
|
229
|
+
const parts = lines[0].trim().split(/\s+/);
|
|
230
|
+
if (parts.length >= 3) {
|
|
231
|
+
const name = parts[0];
|
|
232
|
+
const memory = `${(parseInt(parts[1]) / 1024).toFixed(1)}MB`;
|
|
233
|
+
const cpu = `${parseFloat(parts[2]).toFixed(1)}%`;
|
|
234
|
+
resolve({ name, memory, cpu });
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
resolve({ name: 'Unknown' });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
resolve(null);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 获取所有相关进程的列表
|
|
249
|
+
*/
|
|
250
|
+
async function getRelatedProcessesInternal(processName) {
|
|
251
|
+
return new Promise((resolve) => {
|
|
252
|
+
const platform = os.platform();
|
|
253
|
+
if (platform === 'win32') {
|
|
254
|
+
// Windows: 使用 tasklist 命令
|
|
255
|
+
exec(`tasklist /FI "IMAGENAME eq ${processName}*" /FO CSV /NH`, (error, stdout) => {
|
|
256
|
+
if (error || !stdout.trim() || stdout.includes('INFO: No tasks')) {
|
|
257
|
+
resolve([]);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const processes = [];
|
|
261
|
+
const lines = stdout.trim().split('\n');
|
|
262
|
+
for (const line of lines) {
|
|
263
|
+
const parts = line.split(',');
|
|
264
|
+
if (parts.length >= 5) {
|
|
265
|
+
const name = parts[0].replace(/"/g, '');
|
|
266
|
+
const pid = parseInt(parts[1].replace(/"/g, ''));
|
|
267
|
+
const memory = parts[4].replace(/"/g, '');
|
|
268
|
+
if (!isNaN(pid)) {
|
|
269
|
+
processes.push({ pid, name, memory });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
resolve(processes);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Linux/macOS: 使用 ps 命令
|
|
278
|
+
exec(`ps -C "${processName}" -o pid=,comm=,rss=,pcpu=`, (error, stdout) => {
|
|
279
|
+
if (error || !stdout.trim()) {
|
|
280
|
+
resolve([]);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const processes = [];
|
|
284
|
+
const lines = stdout.trim().split('\n');
|
|
285
|
+
for (const line of lines) {
|
|
286
|
+
const parts = line.trim().split(/\s+/);
|
|
287
|
+
if (parts.length >= 4) {
|
|
288
|
+
const pid = parseInt(parts[0]);
|
|
289
|
+
const name = parts[1];
|
|
290
|
+
const memory = `${(parseInt(parts[2]) / 1024).toFixed(1)}MB`;
|
|
291
|
+
const cpu = `${parseFloat(parts[3]).toFixed(1)}%`;
|
|
292
|
+
if (!isNaN(pid)) {
|
|
293
|
+
processes.push({ pid, name, memory, cpu });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
resolve(processes);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* 获取所有相关进程列表
|
|
304
|
+
*/
|
|
305
|
+
export async function getRelatedProcesses(processName) {
|
|
306
|
+
return await getRelatedProcessesInternal(processName);
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zhin.js/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zhin机器人框架CLI工具",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"zhin": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"commander": "^11.1.0",
|
|
18
|
+
"inquirer": "^9.2.12",
|
|
19
|
+
"chalk": "^5.3.0",
|
|
20
|
+
"ora": "^7.0.1",
|
|
21
|
+
"fs-extra": "^11.1.1",
|
|
22
|
+
"cross-spawn": "^7.0.3",
|
|
23
|
+
"dotenv": "^16.3.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/fs-extra": "^11.0.4",
|
|
27
|
+
"@types/cross-spawn": "^6.0.6",
|
|
28
|
+
"@types/inquirer": "^9.0.7",
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"typescript": "^5.3.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"clean": "rm -rf dist"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { initCommand } from './commands/init.js';
|
|
5
|
+
import { startCommand, restartCommand } from './commands/start.js';
|
|
6
|
+
import { stopCommand } from './commands/stop.js';
|
|
7
|
+
import { devCommand } from './commands/dev.js';
|
|
8
|
+
import { buildCommand } from './commands/build.js';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('zhin')
|
|
14
|
+
.description('Zhin机器人框架CLI工具')
|
|
15
|
+
.version('1.0.0');
|
|
16
|
+
|
|
17
|
+
// 注册命令
|
|
18
|
+
program.addCommand(initCommand);
|
|
19
|
+
program.addCommand(startCommand);
|
|
20
|
+
program.addCommand(restartCommand);
|
|
21
|
+
program.addCommand(stopCommand);
|
|
22
|
+
program.addCommand(devCommand);
|
|
23
|
+
program.addCommand(buildCommand);
|
|
24
|
+
|
|
25
|
+
program.parse();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
export const buildCommand = new Command('build')
|
|
8
|
+
.description('构建机器人项目')
|
|
9
|
+
.option('--clean', '清理输出目录', false)
|
|
10
|
+
.action(async (options: { clean: boolean }) => {
|
|
11
|
+
try {
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
|
|
14
|
+
// 检查是否是Zhin项目
|
|
15
|
+
if (!isZhinProject(cwd)) {
|
|
16
|
+
logger.error('当前目录不是Zhin项目');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const distPath = path.join(cwd, 'dist');
|
|
21
|
+
|
|
22
|
+
// 清理输出目录
|
|
23
|
+
if (options.clean && fs.existsSync(distPath)) {
|
|
24
|
+
logger.info('正在清理输出目录...');
|
|
25
|
+
await fs.remove(distPath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
logger.info('正在构建项目...');
|
|
29
|
+
|
|
30
|
+
// 使用TypeScript编译
|
|
31
|
+
const child = spawn('npx', ['tsc'], {
|
|
32
|
+
cwd,
|
|
33
|
+
stdio: 'inherit'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
child.on('close', (code) => {
|
|
37
|
+
if (code === 0) {
|
|
38
|
+
logger.info('构建完成!');
|
|
39
|
+
} else {
|
|
40
|
+
logger.error(`构建失败,退出码: ${code}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on('error', (error) => {
|
|
46
|
+
logger.error(`构建失败: ${error.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.error(`构建失败: ${error}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function isZhinProject(cwd: string): boolean {
|
|
57
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
58
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const packageJson = fs.readJsonSync(packageJsonPath);
|
|
64
|
+
return packageJson.dependencies && packageJson.dependencies['zhin.js'];
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
|
|
8
|
+
export const devCommand = new Command('dev')
|
|
9
|
+
.option('-p, --port [port]', 'HMR服务端口', '3000')
|
|
10
|
+
.option('--verbose', '显示详细日志', false)
|
|
11
|
+
.option('--bun', '使用 bun 运行(默认使用 node', false)
|
|
12
|
+
.action(async (options: { port: string; verbose: boolean; bun: boolean }) => {
|
|
13
|
+
try {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
|
|
16
|
+
// 检查是否是Zhin项目
|
|
17
|
+
if (!isZhinProject(cwd)) {
|
|
18
|
+
logger.error('当前目录不是Zhin项目');
|
|
19
|
+
logger.info('请在Zhin项目根目录运行此命令,或使用 zhin init 初始化项目');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 加载环境变量文件
|
|
24
|
+
logger.info('🔍 正在加载环境变量...');
|
|
25
|
+
loadEnvFiles(cwd, 'development');
|
|
26
|
+
|
|
27
|
+
// 检查src目录是否存在
|
|
28
|
+
const srcPath = path.join(cwd, 'src');
|
|
29
|
+
if (!fs.existsSync(srcPath)) {
|
|
30
|
+
logger.error('src目录不存在');
|
|
31
|
+
logger.info('请确保项目结构正确,src目录包含入口文件');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 检查入口文件
|
|
36
|
+
const entryFile = path.join(srcPath, 'index.ts');
|
|
37
|
+
if (!fs.existsSync(entryFile)) {
|
|
38
|
+
logger.error('入口文件 src/index.ts 不存在');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
logger.info('📦 开发模式启动中...');
|
|
43
|
+
|
|
44
|
+
// 启动机器人的函数
|
|
45
|
+
const startBot = (): ChildProcess => {
|
|
46
|
+
// 设置环境变量
|
|
47
|
+
const env = {
|
|
48
|
+
...process.env,
|
|
49
|
+
NODE_ENV: 'development',
|
|
50
|
+
ZHIN_DEV_MODE: 'true',
|
|
51
|
+
ZHIN_HMR_PORT: options.port,
|
|
52
|
+
ZHIN_VERBOSE: options.verbose ? 'true' : 'false'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// 选择运行时
|
|
56
|
+
const runtime = options.bun ? 'bun' : 'tsx';
|
|
57
|
+
const args = options.bun ? ['src/index.ts'] : ['--expose-gc', 'src/index.ts'];
|
|
58
|
+
|
|
59
|
+
logger.info(`📦 启动命令: ${runtime} ${args.join(' ')}`);
|
|
60
|
+
|
|
61
|
+
// 启动机器人
|
|
62
|
+
return spawn(runtime, args, {
|
|
63
|
+
cwd,
|
|
64
|
+
stdio: 'inherit',
|
|
65
|
+
env
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let child = startBot();
|
|
70
|
+
let isRestarting = false;
|
|
71
|
+
let isKilling = false;
|
|
72
|
+
|
|
73
|
+
// 重启函数
|
|
74
|
+
const restartBot = async () => {
|
|
75
|
+
if (isRestarting || isKilling) return;
|
|
76
|
+
isRestarting = true;
|
|
77
|
+
|
|
78
|
+
logger.info('🔄 正在重启开发服务器...');
|
|
79
|
+
|
|
80
|
+
// 优雅关闭当前进程
|
|
81
|
+
if (child && !child.killed) {
|
|
82
|
+
const oldChild=child
|
|
83
|
+
oldChild.kill('SIGTERM');
|
|
84
|
+
// 如果5秒后还没关闭,强制杀掉
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
if (oldChild && !oldChild.killed) {
|
|
87
|
+
oldChild.kill('SIGKILL');
|
|
88
|
+
}
|
|
89
|
+
}, 5000);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
child = startBot();
|
|
93
|
+
setupChildHandlers(child);
|
|
94
|
+
isRestarting = false;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 设置子进程处理器
|
|
98
|
+
const setupChildHandlers = (childProcess: ChildProcess) => {
|
|
99
|
+
childProcess.on('error', (error) => {
|
|
100
|
+
if (!isRestarting) {
|
|
101
|
+
logger.error(`❌ 启动失败: ${error.message}`);
|
|
102
|
+
// 提供常见问题的解决建议
|
|
103
|
+
if (error.message.includes('ENOENT')) {
|
|
104
|
+
if (options.bun) {
|
|
105
|
+
logger.info('💡 请确保已安装 bun: https://bun.sh/');
|
|
106
|
+
} else {
|
|
107
|
+
logger.info('💡 请确保已安装 tsx: npm install -D tsx');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
childProcess.on('exit', (code) => {
|
|
116
|
+
if (!isRestarting && !isKilling) {
|
|
117
|
+
if (code === 51) {
|
|
118
|
+
return restartBot();
|
|
119
|
+
}
|
|
120
|
+
if (code !== 0) {
|
|
121
|
+
logger.error(`🔄 进程退出,代码: ${code}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// 设置初始子进程处理器
|
|
128
|
+
setupChildHandlers(child);
|
|
129
|
+
|
|
130
|
+
// 保存主进程PID文件(虽然开发模式不提供CLI重启,但保留用于进程管理)
|
|
131
|
+
const pidFile = path.join(cwd, '.zhin-dev.pid');
|
|
132
|
+
fs.writeFileSync(pidFile, process.pid.toString());
|
|
133
|
+
|
|
134
|
+
// 处理退出信号
|
|
135
|
+
const cleanup = () => {
|
|
136
|
+
if (isKilling) return;
|
|
137
|
+
logger.info('🛑 正在关闭开发服务器...');
|
|
138
|
+
isKilling = true;
|
|
139
|
+
|
|
140
|
+
if (child && !child.killed) {
|
|
141
|
+
child.kill('SIGTERM');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 给子进程一些时间优雅退出
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
if (child && !child.killed) {
|
|
147
|
+
child.kill('SIGKILL');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 清理PID文件
|
|
151
|
+
if (fs.existsSync(pidFile)) {
|
|
152
|
+
fs.removeSync(pidFile);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
logger.info('✅ 开发服务器已关闭');
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}, 3000);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
process.on('SIGINT', cleanup);
|
|
161
|
+
process.on('SIGTERM', cleanup);
|
|
162
|
+
|
|
163
|
+
// 显示开发模式提示
|
|
164
|
+
logger.info('💡 开发模式运行中,按 Ctrl+C 退出');
|
|
165
|
+
logger.info('💡 重启方式: 在插件中调用 process.exit(51)');
|
|
166
|
+
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.error(`❌ 开发模式启动失败: ${error}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
function isZhinProject(cwd: string): boolean {
|
|
174
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
175
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const packageJson = fs.readJsonSync(packageJsonPath);
|
|
181
|
+
return packageJson.dependencies && (
|
|
182
|
+
packageJson.dependencies['zhin.js'] ||
|
|
183
|
+
packageJson.devDependencies?.['zhin.js']
|
|
184
|
+
);
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|