wyt-cli 1.0.20 → 1.0.22
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/create.js +0 -121
- package/bin/commands/deploy.js +66 -50
- package/bin/commands/run.js +265 -93
- package/bin/lib/dir.js +1 -1
- package/bin/main.js +1 -1
- package/bin/templates/default/src/router/index.js +2 -2
- package/bin/templates/default/vite.config.js +1 -0
- package/package.json +1 -1
package/bin/commands/create.js
CHANGED
|
@@ -196,124 +196,3 @@ async function updateRootProjectJson(projectName, options) {
|
|
|
196
196
|
return false;
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
// 询问 -> 创建项目
|
|
201
|
-
// const inquirerAnswers = await inquirer.prompt([
|
|
202
|
-
// {
|
|
203
|
-
// type: 'input',
|
|
204
|
-
// name: 'projectName',
|
|
205
|
-
// message: '请输入项目名称:',
|
|
206
|
-
// validate: (input) => {
|
|
207
|
-
// // 校验规则
|
|
208
|
-
// if (!input) return '项目名称不能为空!';
|
|
209
|
-
// if (!/^[a-zA-Z0-9-]{1,50}$/.test(input)) {
|
|
210
|
-
// return '名称只能包含字母、数字和中划线,且不超过50字符';
|
|
211
|
-
// }
|
|
212
|
-
// if (glob_projectInfo.map((item) => item.name).includes(input.toLowerCase())) {
|
|
213
|
-
// return `项目名称 "${input}" 已存在,请重新输入`;
|
|
214
|
-
// }
|
|
215
|
-
// return true;
|
|
216
|
-
// },
|
|
217
|
-
// // 当命令行已传入名称时自动跳过
|
|
218
|
-
// when: !projectName,
|
|
219
|
-
// },
|
|
220
|
-
// {
|
|
221
|
-
// type: 'input',
|
|
222
|
-
// name: 'devServerPort',
|
|
223
|
-
// message: '请输入测试服务器端口号:',
|
|
224
|
-
// validate: (input) => {
|
|
225
|
-
// // 校验规则
|
|
226
|
-
// if (!input) return '测试服务器端口号不能为空!';
|
|
227
|
-
// if (!/^\d+$/.test(input)) {
|
|
228
|
-
// return '测试服务器端口号必须为纯数字';
|
|
229
|
-
// }
|
|
230
|
-
// if (glob_projectInfo.map((item) => item.port).includes(input)) {
|
|
231
|
-
// return `测试服务器端口号 "${input}" 已存在,请重新输入`;
|
|
232
|
-
// }
|
|
233
|
-
// return true;
|
|
234
|
-
// },
|
|
235
|
-
// },
|
|
236
|
-
// ]);
|
|
237
|
-
// if (!options.force) {
|
|
238
|
-
// throw new Error(`项目 ${projectName} 已存在,使用 --force 强制覆盖`);
|
|
239
|
-
// }
|
|
240
|
-
// inquirer
|
|
241
|
-
// .prompt([
|
|
242
|
-
// {
|
|
243
|
-
// type: 'confirm',
|
|
244
|
-
// name: 'confirmOverwrite',
|
|
245
|
-
// message: `项目 ${projectName} 已存在,是否覆盖?`,
|
|
246
|
-
// default: true,
|
|
247
|
-
// },
|
|
248
|
-
// ])
|
|
249
|
-
// .then((answers) => {
|
|
250
|
-
// if (answers.confirmOverwrite) {
|
|
251
|
-
// fs.emptyDirSync(targetDir);
|
|
252
|
-
// createProject(projectName, options);
|
|
253
|
-
// log_success(`项目 ${projectName} 强制创建成功!`);
|
|
254
|
-
// } else {
|
|
255
|
-
// log_info('操作已取消');
|
|
256
|
-
// process.exit(1);
|
|
257
|
-
// }
|
|
258
|
-
// });
|
|
259
|
-
|
|
260
|
-
// 检测已有项目信息
|
|
261
|
-
// async function checkProjectInfo(options) {
|
|
262
|
-
// const files = await glob(['**/package.json'], {
|
|
263
|
-
// cwd: path.resolve(options.dir, 'apps'), // 以项目根目录为基准
|
|
264
|
-
// absolute: true, // 返回绝对路径
|
|
265
|
-
// ignore: [
|
|
266
|
-
// // 添加排除规则
|
|
267
|
-
// '**/node_modules/**', // 排除 node_modules
|
|
268
|
-
// '**/src/**', // 排除测试目录
|
|
269
|
-
// '**/.*', // 排除隐藏文件/目录
|
|
270
|
-
// ],
|
|
271
|
-
// });
|
|
272
|
-
|
|
273
|
-
// let results = await Promise.all(
|
|
274
|
-
// files.map(async (file) => {
|
|
275
|
-
// // 识别文件类型
|
|
276
|
-
// const isPkg = file.endsWith('package.json');
|
|
277
|
-
|
|
278
|
-
// try {
|
|
279
|
-
// // 读取文件内容
|
|
280
|
-
// const content = await fs.promises.readFile(file, 'utf-8');
|
|
281
|
-
|
|
282
|
-
// if (isPkg) {
|
|
283
|
-
// // 解析 package.json
|
|
284
|
-
// const pkg = JSON.parse(content);
|
|
285
|
-
// return { port: pkg.devServer && pkg.devServer.port, name: pkg.name };
|
|
286
|
-
// }
|
|
287
|
-
// } catch (err) {
|
|
288
|
-
// console.error(`文件处理失败: ${file}`, err);
|
|
289
|
-
// return null;
|
|
290
|
-
// }
|
|
291
|
-
// })
|
|
292
|
-
// );
|
|
293
|
-
// // 排序逻辑
|
|
294
|
-
// results = results.sort((a, b) => {
|
|
295
|
-
// // 处理 undefined 端口为最大值
|
|
296
|
-
// const portA = a.port ? parseInt(a.port) : Infinity;
|
|
297
|
-
// const portB = b.port ? parseInt(b.port) : Infinity;
|
|
298
|
-
// // 优先级 1: 端口号升序
|
|
299
|
-
// if (portA !== portB) {
|
|
300
|
-
// return portA - portB;
|
|
301
|
-
// }
|
|
302
|
-
|
|
303
|
-
// // 优先级 2: 名称字母序
|
|
304
|
-
// return a.name.localeCompare(b.name);
|
|
305
|
-
// });
|
|
306
|
-
|
|
307
|
-
// // 组合项目信息
|
|
308
|
-
// const projectList = results.map((item, index) => ({
|
|
309
|
-
// 序号: index + 1,
|
|
310
|
-
// 项目名称: item.name,
|
|
311
|
-
// 'DevServer 端口号': item.port || '未配置',
|
|
312
|
-
// }));
|
|
313
|
-
// // // 格式化输出
|
|
314
|
-
// if (options.list) {
|
|
315
|
-
// console.log('\n\n====== 已存在的项目列表,请不要重复 ======');
|
|
316
|
-
// console.table(projectList, ['序号', '项目名称', 'DevServer 端口号']);
|
|
317
|
-
// }
|
|
318
|
-
// return results;
|
|
319
|
-
// }
|
package/bin/commands/deploy.js
CHANGED
|
@@ -20,59 +20,75 @@ const startTime = Date.now(); // 计时器
|
|
|
20
20
|
export default function () {
|
|
21
21
|
const command = new Command('deploy');
|
|
22
22
|
|
|
23
|
-
command
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
command
|
|
24
|
+
.description('部署项目')
|
|
25
|
+
.option('-f, --filter <name>', '部署指定项目')
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
try {
|
|
28
|
+
// 校验项目目录
|
|
29
|
+
if (!checkTargetDir(process.cwd())) {
|
|
30
|
+
log_error('请在 HRP3.0 项目根目录运行此命令!');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{ name: '方式1:部署所有项目', value: 0 },
|
|
46
|
-
{ name: '方式2:部署指定项目', value: 1 },
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
type: 'checkbox',
|
|
51
|
-
name: 'project',
|
|
52
|
-
message: '请选择要部署的项目:' + getInquirerOperationText('checkbox'),
|
|
53
|
-
when: (answers) => answers.deploystyle == 1,
|
|
54
|
-
choices: projects.map((p) => ({
|
|
55
|
-
name: p.name,
|
|
56
|
-
})),
|
|
57
|
-
},
|
|
58
|
-
]);
|
|
59
|
-
|
|
60
|
-
// 准备部署目录
|
|
61
|
-
await initDeployDir();
|
|
62
|
-
|
|
63
|
-
// 构建并部署
|
|
64
|
-
if (answer.deploystyle === 0) {
|
|
65
|
-
await buildAndDeploy(projects.map((p) => p.name));
|
|
66
|
-
}
|
|
34
|
+
// 项目根目录
|
|
35
|
+
const appsDir = path.resolve(process.cwd(), 'apps');
|
|
36
|
+
|
|
37
|
+
// 获取项目列表
|
|
38
|
+
const projects = await getProjects(appsDir);
|
|
39
|
+
|
|
40
|
+
if (options.filter) {
|
|
41
|
+
const filtered = projects.filter((p) => p.name.includes(options.filter));
|
|
42
|
+
if (filtered.length === 0) {
|
|
43
|
+
log_error(`找不到包含"${options.filter}"的项目`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// 准备部署目录
|
|
47
|
+
await initDeployDir();
|
|
67
48
|
|
|
68
|
-
|
|
69
|
-
|
|
49
|
+
// 构建并部署
|
|
50
|
+
await buildAndDeploy(filtered.map((p) => p.name));
|
|
51
|
+
} else {
|
|
52
|
+
// 交互选择项目
|
|
53
|
+
const answer = await inquirer.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: 'rawlist',
|
|
56
|
+
name: 'deploystyle',
|
|
57
|
+
message: `请选择部署项目的方式:` + getInquirerOperationText('list'),
|
|
58
|
+
default: 0,
|
|
59
|
+
choices: [
|
|
60
|
+
{ name: '方式1:部署所有项目', value: 0 },
|
|
61
|
+
{ name: '方式2:部署指定项目', value: 1 },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'checkbox',
|
|
66
|
+
name: 'project',
|
|
67
|
+
message: '请选择要部署的项目:' + getInquirerOperationText('checkbox'),
|
|
68
|
+
when: (answers) => answers.deploystyle == 1,
|
|
69
|
+
choices: projects.map((p) => ({
|
|
70
|
+
name: p.name,
|
|
71
|
+
})),
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// 准备部署目录
|
|
76
|
+
await initDeployDir();
|
|
77
|
+
|
|
78
|
+
// 构建并部署
|
|
79
|
+
if (answer.deploystyle === 0) {
|
|
80
|
+
await buildAndDeploy(projects.map((p) => p.name));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (answer.deploystyle === 1 && answer.project) {
|
|
84
|
+
await buildAndDeploy(answer.project);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
log_error(`部署失败: ${error.message}`);
|
|
89
|
+
process.exit(1);
|
|
70
90
|
}
|
|
71
|
-
}
|
|
72
|
-
log_error(`部署失败: ${error.message}`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
91
|
+
});
|
|
76
92
|
|
|
77
93
|
return command;
|
|
78
94
|
}
|
package/bin/commands/run.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execa } 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';
|
|
@@ -10,66 +11,72 @@ import { checkTargetDir, getProjects } from '../lib/dir.js';
|
|
|
10
11
|
export default function () {
|
|
11
12
|
const command = new Command('run');
|
|
12
13
|
|
|
13
|
-
command
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
command
|
|
15
|
+
.description('运行项目')
|
|
16
|
+
.option('-f, --filter <name>', '运行指定项目')
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
try {
|
|
19
|
+
// 校验项目根目录是否合法
|
|
20
|
+
if (!checkTargetDir(process.cwd())) {
|
|
21
|
+
log_error('请在 HRP3.0 项目根目录下运行此命令!');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
25
|
+
// 项目根目录
|
|
26
|
+
const appsDir = path.resolve(process.cwd(), 'apps');
|
|
27
|
+
|
|
28
|
+
// 获取项目列表
|
|
29
|
+
const projects = await getProjects(appsDir);
|
|
30
|
+
|
|
31
|
+
// 运行指定项目
|
|
32
|
+
if (options.filter) {
|
|
33
|
+
const filtered = projects.filter((p) => p.name.includes(options.filter));
|
|
34
|
+
if (filtered.length === 0) {
|
|
35
|
+
log_error(`找不到包含"${options.filter}"的项目`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
runProject([{ name: 'core' }, { name: filtered[0].name }], appsDir);
|
|
39
|
+
} else {
|
|
40
|
+
// 交互选择项目
|
|
41
|
+
const answer = await inquirer.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: 'rawlist',
|
|
44
|
+
name: 'runstyle',
|
|
45
|
+
message: `请选择项目运行方式:` + getInquirerOperationText('list'),
|
|
46
|
+
default: 0,
|
|
47
|
+
choices: [
|
|
48
|
+
{ name: '方式1:运行所有项目', value: 0 },
|
|
49
|
+
{ name: '方式2:运行指定项目', value: 1 },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'checkbox',
|
|
54
|
+
name: 'project',
|
|
55
|
+
message: '请选择要运行的项目:' + getInquirerOperationText('checkbox'),
|
|
56
|
+
when: (answers) => answers.runstyle == 1,
|
|
57
|
+
choices: projects.map((p) => ({
|
|
58
|
+
checked: p.name === 'core',
|
|
59
|
+
name: `${p.name} (端口号: ${p.port || '未配置'}) ${p.description || ''}`,
|
|
60
|
+
value: p,
|
|
61
|
+
})),
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
// 运行项目
|
|
66
|
+
if (answer.runstyle === 0) runProject(projects, appsDir);
|
|
67
|
+
if (answer.runstyle === 1 && answer.project) runProject(answer.project, appsDir);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
log_error(error.message);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
60
74
|
|
|
61
75
|
return command;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
// 运行项目
|
|
65
79
|
async function runProject(projects = [], appsDir) {
|
|
66
|
-
// 更新 packages 最新版本
|
|
67
|
-
log_info(`🔄 检查并更新 packages 最新版本...`);
|
|
68
|
-
await execa('pnpm', ['install'], {
|
|
69
|
-
cwd: process.cwd(),
|
|
70
|
-
stdio: 'inherit',
|
|
71
|
-
});
|
|
72
|
-
|
|
73
80
|
// 运行命令
|
|
74
81
|
const command = 'npm run dev';
|
|
75
82
|
const commandEnv = {
|
|
@@ -77,64 +84,229 @@ async function runProject(projects = [], appsDir) {
|
|
|
77
84
|
NODE_ENV: 'development',
|
|
78
85
|
PROJECT_RUN_MODE: 'wyt-cli',
|
|
79
86
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const docsProcess = execa(command, {
|
|
85
|
-
cwd: docsPath,
|
|
87
|
+
// 更新 packages 最新版本
|
|
88
|
+
log_info(`🔄 检查并更新 packages 最新版本...`);
|
|
89
|
+
await execa('pnpm', ['install'], {
|
|
90
|
+
cwd: process.cwd(),
|
|
86
91
|
stdio: 'inherit',
|
|
87
|
-
env: commandEnv,
|
|
88
92
|
});
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
await execa('pnpm', ['check:dist'], {
|
|
94
|
+
cwd: process.cwd(),
|
|
95
|
+
stdio: 'inherit',
|
|
96
|
+
env: commandEnv,
|
|
91
97
|
});
|
|
92
98
|
|
|
93
|
-
//
|
|
99
|
+
// 运行项目文档
|
|
100
|
+
runDocsProjects(commandEnv);
|
|
101
|
+
// log_info(`🚀 启动项目文档...`);
|
|
102
|
+
// const docsPath = path.join(process.cwd(), 'docs');
|
|
103
|
+
// const docsProcess = execa(command, {
|
|
104
|
+
// cwd: docsPath,
|
|
105
|
+
// stdio: 'inherit',
|
|
106
|
+
// env: commandEnv,
|
|
107
|
+
// });
|
|
108
|
+
// docsProcess.on('exit', (code) => {
|
|
109
|
+
// if (code !== 0) log_error(`项目文档异常退出 (CODE: ${code})`);
|
|
110
|
+
// });
|
|
111
|
+
|
|
112
|
+
// 更新项目启动状态
|
|
94
113
|
const configPath = path.join(process.cwd(), 'project.json');
|
|
95
114
|
let config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
96
115
|
if (config.apps) {
|
|
97
116
|
// 初始化
|
|
98
117
|
config.apps.forEach((app, index) => {
|
|
99
118
|
config.apps[index] = {
|
|
100
|
-
|
|
119
|
+
name: app.name,
|
|
101
120
|
lastRun: false,
|
|
102
121
|
};
|
|
103
122
|
});
|
|
104
123
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
// 运行项目
|
|
125
|
+
// projects.forEach(async (project) => {
|
|
126
|
+
// const projectName = project.name;
|
|
127
|
+
// if (projectName) {
|
|
128
|
+
// const projectPath = path.join(appsDir, projectName);
|
|
129
|
+
// if (!fs.existsSync(projectPath)) {
|
|
130
|
+
// throw new Error(`项目 ${projectName} 不存在`);
|
|
131
|
+
// }
|
|
132
|
+
|
|
133
|
+
// // 执行命令
|
|
134
|
+
// log_info(`🚀 启动项目 ${projectName}...`);
|
|
135
|
+
// // 更新项目启动状态
|
|
136
|
+
// const targetIndex = config.apps.findIndex((app) => app.name === projectName);
|
|
137
|
+
// if (targetIndex !== -1) {
|
|
138
|
+
// config.apps[targetIndex].lastRun = true;
|
|
139
|
+
// }
|
|
140
|
+
|
|
141
|
+
// execa(command, {
|
|
142
|
+
// cwd: projectPath,
|
|
143
|
+
// stdio: 'inherit',
|
|
144
|
+
// env: commandEnv,
|
|
145
|
+
// });
|
|
146
|
+
// }
|
|
147
|
+
// });
|
|
148
|
+
runAppsProjects(command, commandEnv, projects, appsDir, config);
|
|
149
|
+
|
|
150
|
+
// 运行时配置更新
|
|
151
|
+
const runtimeConfigPath = path.join(process.cwd(), 'runtime.config.json');
|
|
152
|
+
fs.writeFileSync(runtimeConfigPath, JSON.stringify({ runtime_apps: config.apps }, null, 2));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 运行 apps 里面的项目
|
|
156
|
+
|
|
157
|
+
async function runAppsProjects(command, commandEnv, projects, appsDir, config) {
|
|
158
|
+
// 1. 创建一个数组来保存所有子进程的引用
|
|
159
|
+
const childProcesses = [];
|
|
160
|
+
|
|
161
|
+
// 2. 监听 Ctrl + C 信号
|
|
162
|
+
const handleSigint = () => {
|
|
163
|
+
console.log('\n🛑 检测到 Ctrl + C,正在关闭所有测试服务...');
|
|
164
|
+
|
|
165
|
+
// 遍历并杀死所有子进程
|
|
166
|
+
childProcesses.forEach((cp) => {
|
|
167
|
+
if (!cp.killed) {
|
|
168
|
+
cp.kill('SIGTERM');
|
|
111
169
|
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// 退出主进程
|
|
173
|
+
process.exit(0);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// 常规监听(Linux/macOS 有效)
|
|
177
|
+
process.on('SIGINT', handleSigint);
|
|
178
|
+
|
|
179
|
+
// 强制监听终端输入(Windows 环境下必须加这一段)
|
|
180
|
+
if (process.platform === 'win32') {
|
|
181
|
+
const rl = readline.createInterface({
|
|
182
|
+
input: process.stdin,
|
|
183
|
+
output: process.stdout,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
rl.on('SIGINT', function () {
|
|
187
|
+
// 当 readline 捕获到 Ctrl+C 时,手动触发 process 的 SIGINT 事件
|
|
188
|
+
process.emit('SIGINT');
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 运行所有项目
|
|
193
|
+
try {
|
|
194
|
+
for (const project of projects) {
|
|
195
|
+
const projectName = project.name;
|
|
196
|
+
if (projectName) {
|
|
197
|
+
const projectPath = path.join(appsDir, projectName);
|
|
198
|
+
if (!fs.existsSync(projectPath)) {
|
|
199
|
+
throw new Error(`项目 ${projectName} 不存在`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
log_info(`🚀 启动项目 ${projectName}...`);
|
|
203
|
+
|
|
204
|
+
const targetIndex = config.apps.findIndex((app) => app.name === projectName);
|
|
205
|
+
if (targetIndex !== -1) {
|
|
206
|
+
config.apps[targetIndex].lastRun = true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 3. 执行命令,并将返回的子进程实例存入数组
|
|
210
|
+
const cp = execa(command, {
|
|
211
|
+
cwd: projectPath,
|
|
212
|
+
stdio: 'inherit',
|
|
213
|
+
env: commandEnv,
|
|
214
|
+
cleanup: true, // 当父进程退出时,自动杀死子进程
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
childProcesses.push(cp);
|
|
112
218
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
219
|
+
// 可选:监听单个进程的意外退出
|
|
220
|
+
cp.on('exit', (code) => {
|
|
221
|
+
if (code !== 0 && code !== null) {
|
|
222
|
+
log_info(`⚠️ 项目 ${projectName} 已退出,退出码: ${code}`);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
119
225
|
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 4. 保持主进程存活,直到所有子进程都结束
|
|
229
|
+
// 因为 dev 服务通常不会主动结束,所以这里会一直挂起,直到按下 Ctrl+C
|
|
230
|
+
await Promise.all(childProcesses.map((cp) => cp.catch(() => {})));
|
|
231
|
+
|
|
232
|
+
console.log('✅ 所有服务已正常退出');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('❌ 启动过程中发生错误:', error.message);
|
|
235
|
+
|
|
236
|
+
// 如果启动阶段就报错,也要清理可能已经启动的子进程
|
|
237
|
+
childProcesses.forEach((cp) => cp.kill());
|
|
238
|
+
process.exit(1);
|
|
239
|
+
} finally {
|
|
240
|
+
// 移除监听,防止内存泄漏
|
|
241
|
+
process.removeListener('SIGINT', handleSigint);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 运行文档项目
|
|
246
|
+
function runDocsProjects(commandEnv) {
|
|
247
|
+
const docsPath = path.join(process.cwd(), 'docs');
|
|
248
|
+
|
|
249
|
+
// 根据操作系统构建不同的启动命令
|
|
250
|
+
let terminalCommand;
|
|
251
|
+
let terminalArgs;
|
|
120
252
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
253
|
+
if (process.platform === 'win32') {
|
|
254
|
+
// Windows: 使用 cmd 的 start 命令开启新窗口
|
|
255
|
+
// /c: 执行完毕后关闭窗口(这里不用,我们要保持窗口)
|
|
256
|
+
// /k: 执行完毕后保留窗口(非常适合运行 dev server)
|
|
257
|
+
terminalCommand = 'cmd';
|
|
258
|
+
terminalArgs = [
|
|
259
|
+
'/c',
|
|
260
|
+
'start',
|
|
261
|
+
'VitePress Docs', // 新终端窗口的标题
|
|
262
|
+
'cmd',
|
|
263
|
+
'/k',
|
|
264
|
+
`cd ${docsPath} && vitepress dev --port 7000`, // 在新窗口中执行的命令
|
|
265
|
+
];
|
|
266
|
+
} else {
|
|
267
|
+
// macOS / Linux:
|
|
268
|
+
// macOS 默认使用 open -a Terminal
|
|
269
|
+
// Linux 桌面环境通常支持 xdg-open 或直接运行终端程序
|
|
270
|
+
// 这里以 macOS 为例,如果是 Linux,可能需要替换为 'gnome-terminal' 或 'xterm'
|
|
271
|
+
|
|
272
|
+
if (process.platform === 'darwin') {
|
|
273
|
+
// macOS: 使用 AppleScript 打开 Terminal.app 并执行命令
|
|
274
|
+
terminalCommand = 'osascript';
|
|
275
|
+
terminalArgs = [
|
|
276
|
+
'-e',
|
|
277
|
+
`tell application "Terminal"
|
|
278
|
+
do script "cd ${docsPath} && vitepress dev --port 7000"
|
|
279
|
+
activate
|
|
280
|
+
end tell`,
|
|
281
|
+
];
|
|
282
|
+
} else {
|
|
283
|
+
// Linux (以 gnome-terminal 为例,根据你的桌面环境可能需要调整)
|
|
284
|
+
terminalCommand = 'gnome-terminal';
|
|
285
|
+
terminalArgs = [
|
|
286
|
+
'--',
|
|
287
|
+
'bash',
|
|
288
|
+
'-c',
|
|
289
|
+
`cd ${docsPath} && vitepress dev --port 7000; exec bash`, // exec bash 保证进程结束后终端不关闭
|
|
290
|
+
];
|
|
136
291
|
}
|
|
137
|
-
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
log_info(`🚀 在新终端窗口中启动项目文档...`);
|
|
138
295
|
|
|
139
|
-
|
|
296
|
+
try {
|
|
297
|
+
// 注意:这里不需要再指定 cwd 为 docsPath 了,因为我们在命令里已经处理了目录切换
|
|
298
|
+
// 也不需要 stdio: 'inherit',因为它的输出会去新窗口
|
|
299
|
+
const docsProcess = execa(terminalCommand, terminalArgs, {
|
|
300
|
+
stdio: 'ignore', // 忽略输入输出,因为它在新窗口里
|
|
301
|
+
env: commandEnv,
|
|
302
|
+
detached: true, // 关键:让该进程独立于父进程运行
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// 关键:取消父进程对子进程的引用,让操作系统接管
|
|
306
|
+
docsProcess.unref();
|
|
307
|
+
|
|
308
|
+
// 注意:此时不需要也不应该将 docsProcess push 到 childProcesses 数组里了!
|
|
309
|
+
} catch (error) {
|
|
310
|
+
log_error(`❌ 启动文档终端失败: ${error.message}`);
|
|
311
|
+
}
|
|
140
312
|
}
|
package/bin/lib/dir.js
CHANGED
package/bin/main.js
CHANGED
|
@@ -33,7 +33,7 @@ main();
|
|
|
33
33
|
// 处理 CLI 命令
|
|
34
34
|
function handleProgramCommand() {
|
|
35
35
|
// 基本信息
|
|
36
|
-
program.name('cli
|
|
36
|
+
program.name('wyt-cli').description(`这是 hrp3.0 项目的命令行工具,当前版本为${version}`);
|
|
37
37
|
program.version(version, '-v, --version', '显示版本信息');
|
|
38
38
|
program.helpOption('-h, --help', '显示帮助信息').helpCommand(false);
|
|
39
39
|
|
|
@@ -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 = [
|