wyt-cli 1.0.21 → 1.0.23
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/bin/commands/run.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { execa } from 'execa';
|
|
2
|
+
import { execa, execaSync } from 'execa';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
|
+
import readline from 'readline';
|
|
6
7
|
|
|
7
8
|
import { log_info, log_error, getInquirerOperationText } from '../lib/logger.js';
|
|
8
9
|
import { checkTargetDir, getProjects } from '../lib/dir.js';
|
|
9
10
|
|
|
11
|
+
const MEMORY_LIMIT = 90; // 建议内存阈值
|
|
12
|
+
|
|
10
13
|
export default function () {
|
|
11
14
|
const command = new Command('run');
|
|
12
15
|
|
|
@@ -83,6 +86,7 @@ async function runProject(projects = [], appsDir) {
|
|
|
83
86
|
NODE_ENV: 'development',
|
|
84
87
|
PROJECT_RUN_MODE: 'wyt-cli',
|
|
85
88
|
};
|
|
89
|
+
|
|
86
90
|
// 更新 packages 最新版本
|
|
87
91
|
log_info(`🔄 检查并更新 packages 最新版本...`);
|
|
88
92
|
await execa('pnpm', ['install'], {
|
|
@@ -95,19 +99,23 @@ async function runProject(projects = [], appsDir) {
|
|
|
95
99
|
env: commandEnv,
|
|
96
100
|
});
|
|
97
101
|
|
|
102
|
+
// 检测内存占用率
|
|
103
|
+
checkSystemMemoryUsage();
|
|
104
|
+
|
|
98
105
|
// 运行项目文档
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
106
|
+
runDocsProjects(commandEnv);
|
|
107
|
+
// log_info(`🚀 启动项目文档...`);
|
|
108
|
+
// const docsPath = path.join(process.cwd(), 'docs');
|
|
109
|
+
// const docsProcess = execa(command, {
|
|
110
|
+
// cwd: docsPath,
|
|
111
|
+
// stdio: 'inherit',
|
|
112
|
+
// env: commandEnv,
|
|
113
|
+
// });
|
|
114
|
+
// docsProcess.on('exit', (code) => {
|
|
115
|
+
// if (code !== 0) log_error(`项目文档异常退出 (CODE: ${code})`);
|
|
116
|
+
// });
|
|
109
117
|
|
|
110
|
-
//
|
|
118
|
+
// 更新项目启动状态
|
|
111
119
|
const configPath = path.join(process.cwd(), 'project.json');
|
|
112
120
|
let config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
113
121
|
if (config.apps) {
|
|
@@ -119,41 +127,243 @@ async function runProject(projects = [], appsDir) {
|
|
|
119
127
|
};
|
|
120
128
|
});
|
|
121
129
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
// 运行项目
|
|
131
|
+
// projects.forEach(async (project) => {
|
|
132
|
+
// const projectName = project.name;
|
|
133
|
+
// if (projectName) {
|
|
134
|
+
// const projectPath = path.join(appsDir, projectName);
|
|
135
|
+
// if (!fs.existsSync(projectPath)) {
|
|
136
|
+
// throw new Error(`项目 ${projectName} 不存在`);
|
|
137
|
+
// }
|
|
129
138
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
// // 执行命令
|
|
140
|
+
// log_info(`🚀 启动项目 ${projectName}...`);
|
|
141
|
+
// // 更新项目启动状态
|
|
142
|
+
// const targetIndex = config.apps.findIndex((app) => app.name === projectName);
|
|
143
|
+
// if (targetIndex !== -1) {
|
|
144
|
+
// config.apps[targetIndex].lastRun = true;
|
|
145
|
+
// }
|
|
137
146
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
log_info('正在优雅关闭进程...');
|
|
147
|
-
subprocess.kill('SIGINT'); // 杀死子进程
|
|
148
|
-
process.exit(0); // 退出父进程
|
|
149
|
-
});
|
|
150
|
-
subprocess.on('exit', (code) => {
|
|
151
|
-
if (code !== 0) log_error(`项目 ${projectName} 异常退出 (CODE: ${code})`);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
});
|
|
147
|
+
// execa(command, {
|
|
148
|
+
// cwd: projectPath,
|
|
149
|
+
// stdio: 'inherit',
|
|
150
|
+
// env: commandEnv,
|
|
151
|
+
// });
|
|
152
|
+
// }
|
|
153
|
+
// });
|
|
154
|
+
runAppsProjects(command, commandEnv, projects, appsDir, config);
|
|
155
155
|
|
|
156
156
|
// 运行时配置更新
|
|
157
157
|
const runtimeConfigPath = path.join(process.cwd(), 'runtime.config.json');
|
|
158
158
|
fs.writeFileSync(runtimeConfigPath, JSON.stringify({ runtime_apps: config.apps }, null, 2));
|
|
159
159
|
}
|
|
160
|
+
|
|
161
|
+
// 运行 apps 里面的项目
|
|
162
|
+
|
|
163
|
+
async function runAppsProjects(command, commandEnv, projects, appsDir, config) {
|
|
164
|
+
// 1. 创建一个数组来保存所有子进程的引用
|
|
165
|
+
const childProcesses = [];
|
|
166
|
+
|
|
167
|
+
// 2. 监听 Ctrl + C 信号
|
|
168
|
+
const handleSigint = () => {
|
|
169
|
+
console.log('\n🛑 检测到 Ctrl + C,正在关闭所有测试服务...');
|
|
170
|
+
|
|
171
|
+
// 遍历并杀死所有子进程
|
|
172
|
+
childProcesses.forEach((cp) => {
|
|
173
|
+
if (!cp.killed) {
|
|
174
|
+
cp.kill('SIGTERM');
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// 退出主进程
|
|
179
|
+
process.exit(0);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// 常规监听(Linux/macOS 有效)
|
|
183
|
+
process.on('SIGINT', handleSigint);
|
|
184
|
+
|
|
185
|
+
// 强制监听终端输入(Windows 环境下必须加这一段)
|
|
186
|
+
if (process.platform === 'win32') {
|
|
187
|
+
const rl = readline.createInterface({
|
|
188
|
+
input: process.stdin,
|
|
189
|
+
output: process.stdout,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
rl.on('SIGINT', function () {
|
|
193
|
+
// 当 readline 捕获到 Ctrl+C 时,手动触发 process 的 SIGINT 事件
|
|
194
|
+
process.emit('SIGINT');
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 运行所有项目
|
|
199
|
+
try {
|
|
200
|
+
for (const project of projects) {
|
|
201
|
+
const projectName = project.name;
|
|
202
|
+
if (projectName) {
|
|
203
|
+
const projectPath = path.join(appsDir, projectName);
|
|
204
|
+
if (!fs.existsSync(projectPath)) {
|
|
205
|
+
throw new Error(`项目 ${projectName} 不存在`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
log_info(`🚀 启动项目 ${projectName}...`);
|
|
209
|
+
|
|
210
|
+
const targetIndex = config.apps.findIndex((app) => app.name === projectName);
|
|
211
|
+
if (targetIndex !== -1) {
|
|
212
|
+
config.apps[targetIndex].lastRun = true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 3. 执行命令,并将返回的子进程实例存入数组
|
|
216
|
+
const cp = execa(command, {
|
|
217
|
+
cwd: projectPath,
|
|
218
|
+
stdio: 'inherit',
|
|
219
|
+
env: commandEnv,
|
|
220
|
+
cleanup: true, // 当父进程退出时,自动杀死子进程
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
childProcesses.push(cp);
|
|
224
|
+
|
|
225
|
+
// 可选:监听单个进程的意外退出
|
|
226
|
+
cp.on('exit', (code) => {
|
|
227
|
+
if (code !== 0 && code !== null) {
|
|
228
|
+
log_info(`⚠️ 项目 ${projectName} 已退出,退出码: ${code}`);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 4. 保持主进程存活,直到所有子进程都结束
|
|
235
|
+
// 因为 dev 服务通常不会主动结束,所以这里会一直挂起,直到按下 Ctrl+C
|
|
236
|
+
await Promise.all(childProcesses.map((cp) => cp.catch(() => {})));
|
|
237
|
+
|
|
238
|
+
console.log('✅ 所有服务已正常退出');
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('❌ 启动过程中发生错误:', error.message);
|
|
241
|
+
|
|
242
|
+
// 如果启动阶段就报错,也要清理可能已经启动的子进程
|
|
243
|
+
childProcesses.forEach((cp) => cp.kill());
|
|
244
|
+
process.exit(1);
|
|
245
|
+
} finally {
|
|
246
|
+
// 移除监听,防止内存泄漏
|
|
247
|
+
process.removeListener('SIGINT', handleSigint);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 运行文档项目
|
|
252
|
+
function runDocsProjects(commandEnv) {
|
|
253
|
+
const docsPath = path.join(process.cwd(), 'docs');
|
|
254
|
+
|
|
255
|
+
// 根据操作系统构建不同的启动命令
|
|
256
|
+
let terminalCommand;
|
|
257
|
+
let terminalArgs;
|
|
258
|
+
|
|
259
|
+
if (process.platform === 'win32') {
|
|
260
|
+
// Windows: 使用 cmd 的 start 命令开启新窗口
|
|
261
|
+
// /c: 执行完毕后关闭窗口(这里不用,我们要保持窗口)
|
|
262
|
+
// /k: 执行完毕后保留窗口(非常适合运行 dev server)
|
|
263
|
+
terminalCommand = 'cmd';
|
|
264
|
+
terminalArgs = [
|
|
265
|
+
'/c',
|
|
266
|
+
'start',
|
|
267
|
+
'VitePress Docs', // 新终端窗口的标题
|
|
268
|
+
'cmd',
|
|
269
|
+
'/k',
|
|
270
|
+
`cd ${docsPath} && vitepress dev --port 7000`, // 在新窗口中执行的命令
|
|
271
|
+
];
|
|
272
|
+
} else {
|
|
273
|
+
// macOS / Linux:
|
|
274
|
+
// macOS 默认使用 open -a Terminal
|
|
275
|
+
// Linux 桌面环境通常支持 xdg-open 或直接运行终端程序
|
|
276
|
+
// 这里以 macOS 为例,如果是 Linux,可能需要替换为 'gnome-terminal' 或 'xterm'
|
|
277
|
+
|
|
278
|
+
if (process.platform === 'darwin') {
|
|
279
|
+
// macOS: 使用 AppleScript 打开 Terminal.app 并执行命令
|
|
280
|
+
terminalCommand = 'osascript';
|
|
281
|
+
terminalArgs = [
|
|
282
|
+
'-e',
|
|
283
|
+
`tell application "Terminal"
|
|
284
|
+
do script "cd ${docsPath} && vitepress dev --port 7000"
|
|
285
|
+
activate
|
|
286
|
+
end tell`,
|
|
287
|
+
];
|
|
288
|
+
} else {
|
|
289
|
+
// Linux (以 gnome-terminal 为例,根据你的桌面环境可能需要调整)
|
|
290
|
+
terminalCommand = 'gnome-terminal';
|
|
291
|
+
terminalArgs = [
|
|
292
|
+
'--',
|
|
293
|
+
'bash',
|
|
294
|
+
'-c',
|
|
295
|
+
`cd ${docsPath} && vitepress dev --port 7000; exec bash`, // exec bash 保证进程结束后终端不关闭
|
|
296
|
+
];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
log_info(`🚀 在新终端窗口中启动项目文档...`);
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
// 注意:这里不需要再指定 cwd 为 docsPath 了,因为我们在命令里已经处理了目录切换
|
|
304
|
+
// 也不需要 stdio: 'inherit',因为它的输出会去新窗口
|
|
305
|
+
const docsProcess = execa(terminalCommand, terminalArgs, {
|
|
306
|
+
stdio: 'ignore', // 忽略输入输出,因为它在新窗口里
|
|
307
|
+
env: commandEnv,
|
|
308
|
+
detached: true, // 关键:让该进程独立于父进程运行
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// 关键:取消父进程对子进程的引用,让操作系统接管
|
|
312
|
+
docsProcess.unref();
|
|
313
|
+
|
|
314
|
+
// 注意:此时不需要也不应该将 docsProcess push 到 childProcesses 数组里了!
|
|
315
|
+
} catch (error) {
|
|
316
|
+
log_error(`❌ 启动文档终端失败: ${error.message}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 检查系统内存占用率
|
|
321
|
+
function checkSystemMemoryUsage() {
|
|
322
|
+
const memoryUsage = getSystemMemoryUsage();
|
|
323
|
+
if (memoryUsage !== -1 && memoryUsage > MEMORY_LIMIT) {
|
|
324
|
+
log_error(`⚠️ 系统内存占用率过高,当前占用率: ${memoryUsage.toFixed(2)}%,不建议运行 Hrp3.0 项目!`);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 获取系统内存占用率
|
|
331
|
+
* @returns {Promise<number>} 返回 0-100 之间的数字,代表内存占用百分比
|
|
332
|
+
*/
|
|
333
|
+
function getSystemMemoryUsage() {
|
|
334
|
+
try {
|
|
335
|
+
if (process.platform === 'win32') {
|
|
336
|
+
// Windows: 使用 wmic 命令获取总内存和可用内存
|
|
337
|
+
const totalMemCmd = execaSync('wmic', ['OS', 'get', 'TotalVisibleMemorySize', '/value']);
|
|
338
|
+
const freeMemCmd = execaSync('wmic', ['OS', 'get', 'FreePhysicalMemory', '/value']);
|
|
339
|
+
|
|
340
|
+
// 解析命令输出,提取数值 (输出格式类似:TotalVisibleMemorySize=16384000)
|
|
341
|
+
const totalMem = parseInt(totalMemCmd.stdout.split('=')[1], 10);
|
|
342
|
+
const freeMem = parseInt(freeMemCmd.stdout.split('=')[1], 10);
|
|
343
|
+
|
|
344
|
+
const usedMem = totalMem - freeMem;
|
|
345
|
+
return (usedMem / totalMem) * 100;
|
|
346
|
+
} else {
|
|
347
|
+
// Linux / macOS: 使用 free -b 命令 (以字节为单位)
|
|
348
|
+
// -b 确保所有系统输出格式一致,便于解析
|
|
349
|
+
const { stdout } = execaSync('free', ['-b']);
|
|
350
|
+
|
|
351
|
+
// 输出格式类似:
|
|
352
|
+
// total used free shared buff/cache available
|
|
353
|
+
// Mem: 16777216000 8388608000 4194304000 209715200 4194304000 5242880000
|
|
354
|
+
const lines = stdout.split('\n');
|
|
355
|
+
const memLine = lines[1].split(/\s+/);
|
|
356
|
+
|
|
357
|
+
const totalMem = parseInt(memLine[1], 10);
|
|
358
|
+
// 注意:在 Linux 中,看可用内存应该看 available 列(第7个,索引6),而不是 free 列
|
|
359
|
+
// 因为 buff/cache 中的内存是可以被快速释放使用的
|
|
360
|
+
const availableMem = parseInt(memLine[6], 10);
|
|
361
|
+
|
|
362
|
+
const usedMem = totalMem - availableMem;
|
|
363
|
+
return (usedMem / totalMem) * 100;
|
|
364
|
+
}
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.warn('⚠️ 无法获取系统内存信息,跳过内存检测:', error.message);
|
|
367
|
+
return -1; // 返回 -1 表示检测失败,后续逻辑可以放行
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -2,11 +2,11 @@ import { createRouter, createWebHashHistory } from 'vue-router';
|
|
|
2
2
|
import { defaultRoutes } from './router-list.json';
|
|
3
3
|
const basename = process.env.NODE_ENV === 'production' ? '/<%= projectName %>/' : '';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const modules = import.meta.glob('/src/views/**/index.vue');
|
|
6
6
|
const autoRoutes = defaultRoutes.map((item) => {
|
|
7
7
|
return {
|
|
8
8
|
...item,
|
|
9
|
-
component:
|
|
9
|
+
component: modules[item.component],
|
|
10
10
|
};
|
|
11
11
|
});
|
|
12
12
|
const routes = [
|