wyt-cli 1.0.15 → 1.0.17
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 +188 -155
- package/bin/commands/delete.js +99 -0
- package/bin/commands/run.js +44 -5
- package/bin/lib/dir.js +129 -0
- package/bin/lib/logger.js +15 -0
- package/bin/main.js +3 -1
- package/bin/templates/default/package.json +1 -1
- package/bin/templates/default/src/router/index.js +9 -5
- package/bin/templates/default/vite.config.js +1 -1
- package/package.json +2 -1
- package/bin/templates/default/src/views/Home.vue +0 -8
package/bin/commands/create.js
CHANGED
|
@@ -1,126 +1,84 @@
|
|
|
1
1
|
// commands/create.js
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
|
-
|
|
5
|
-
import { log_error, log_info, log_success } from '../lib/logger.js';
|
|
4
|
+
import { log_error, log_success, getPlainText, getInquirerOperationText } from '../lib/logger.js';
|
|
6
5
|
|
|
7
6
|
// 外部依赖
|
|
8
7
|
import { Command } from 'commander';
|
|
9
8
|
import { glob } from 'glob';
|
|
9
|
+
|
|
10
10
|
import fs from 'fs-extra';
|
|
11
|
-
import ora from 'ora';
|
|
11
|
+
// import ora from 'ora';
|
|
12
12
|
import inquirer from 'inquirer';
|
|
13
13
|
import ejs from 'ejs';
|
|
14
|
+
import { checkTargetDir, getModuleMenuConfig, getProjectConfig } from '../lib/dir.js';
|
|
14
15
|
|
|
15
|
-
import { checkTargetDir } from '../lib/dir.js';
|
|
16
|
-
|
|
17
|
-
const spinner = ora();
|
|
18
16
|
// 命令配置
|
|
19
17
|
export default function () {
|
|
20
18
|
const command = new Command('create');
|
|
21
|
-
command.description('
|
|
22
|
-
command.argument('[project-name]', '项目名称(仅支持字母、数字和中划线)', '');
|
|
23
|
-
command.option('-t, --template <template-name>', '指定项目模板', 'default');
|
|
24
|
-
command.option('-d, --dir <directory>', '指定创建目录', process.cwd());
|
|
25
|
-
command.option('-l, --list', '显示已有项目列表', true);
|
|
26
|
-
command.option('-f, --force', '强制覆盖已存在目录');
|
|
27
|
-
command.action(async (
|
|
19
|
+
command.description('创建项目');
|
|
20
|
+
// command.argument('[project-name]', '项目名称(仅支持字母、数字和中划线)', '');
|
|
21
|
+
// command.option('-t, --template <template-name>', '指定项目模板', 'default');
|
|
22
|
+
// command.option('-d, --dir <directory>', '指定创建目录', process.cwd());
|
|
23
|
+
// command.option('-l, --list', '显示已有项目列表', true);
|
|
24
|
+
// command.option('-f, --force', '强制覆盖已存在目录');
|
|
25
|
+
command.action(async () => {
|
|
28
26
|
// 校验项目根目录是否合法
|
|
29
|
-
if (!checkTargetDir(
|
|
27
|
+
if (!checkTargetDir(process.cwd())) {
|
|
30
28
|
log_error('请在 HRP3.0 项目根目录下创建项目!');
|
|
31
29
|
return;
|
|
32
30
|
}
|
|
33
|
-
spinner.start('正在获取已有项目列表...');
|
|
34
|
-
const glob_projectInfo = await checkProjectInfo(options);
|
|
35
|
-
spinner.stop();
|
|
36
31
|
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!input) return '项目名称不能为空!';
|
|
46
|
-
if (!/^[a-zA-Z0-9-]{1,50}$/.test(input)) {
|
|
47
|
-
return '名称只能包含字母、数字和中划线,且不超过50字符';
|
|
48
|
-
}
|
|
49
|
-
if (glob_projectInfo.map((item) => item.name).includes(input.toLowerCase())) {
|
|
50
|
-
return `项目名称 "${input}" 已存在,请重新输入`;
|
|
51
|
-
}
|
|
52
|
-
return true;
|
|
53
|
-
},
|
|
54
|
-
// 当命令行已传入名称时自动跳过
|
|
55
|
-
when: !projectName,
|
|
56
|
-
},
|
|
32
|
+
// 读取 module-menu.xml,获取项目列表
|
|
33
|
+
const menuXmlPath = path.resolve(process.cwd(), 'module-menu.xml');
|
|
34
|
+
if (!fs.existsSync(menuXmlPath)) return log_error('未找到模块菜单配置文件 module-menu.xml');
|
|
35
|
+
const moduleMenuConfigInfo = getModuleMenuConfig(menuXmlPath);
|
|
36
|
+
const safeApps = getProjectConfig().apps || [];
|
|
37
|
+
const existingNames = new Set(safeApps.map((app) => app.name));
|
|
38
|
+
const availableModules = moduleMenuConfigInfo.modules.filter((menu) => !existingNames.has(menu.name));
|
|
39
|
+
const answer = await inquirer.prompt([
|
|
57
40
|
{
|
|
58
|
-
type: '
|
|
59
|
-
name: '
|
|
60
|
-
message: '
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return `测试服务器端口号 "${input}" 已存在,请重新输入`;
|
|
69
|
-
}
|
|
70
|
-
return true;
|
|
71
|
-
},
|
|
41
|
+
type: 'rawlist',
|
|
42
|
+
name: 'projectInfo',
|
|
43
|
+
message: `请选择部署项目的方式:` + getInquirerOperationText('list'),
|
|
44
|
+
choices: availableModules.map((menu) => {
|
|
45
|
+
const { name, description, port } = menu;
|
|
46
|
+
return {
|
|
47
|
+
name: `${name} ${description}`,
|
|
48
|
+
value: { name, port, description },
|
|
49
|
+
};
|
|
50
|
+
}),
|
|
72
51
|
},
|
|
73
52
|
]);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
projectName = inquirerAnswers.projectName || projectName;
|
|
77
|
-
options.devServerPort = inquirerAnswers.devServerPort;
|
|
78
|
-
createAction(projectName, options);
|
|
53
|
+
const { name, port, description } = answer.projectInfo;
|
|
54
|
+
createProjectAction(name, { dir: process.cwd(), devServerPort: port, description, moduleMenuConfigInfo });
|
|
79
55
|
});
|
|
80
56
|
|
|
81
57
|
return command;
|
|
82
58
|
}
|
|
83
59
|
|
|
84
60
|
// 核心创建逻辑
|
|
85
|
-
function
|
|
61
|
+
async function createProjectAction(projectName, options) {
|
|
86
62
|
try {
|
|
87
63
|
const targetDir = path.resolve(options.dir, 'apps', projectName);
|
|
88
64
|
// 校验目录存在性
|
|
89
|
-
if (fs.existsSync(targetDir)) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
type: 'confirm',
|
|
97
|
-
name: 'confirmOverwrite',
|
|
98
|
-
message: `项目 ${projectName} 已存在,是否覆盖?`,
|
|
99
|
-
default: true,
|
|
100
|
-
},
|
|
101
|
-
])
|
|
102
|
-
.then((answers) => {
|
|
103
|
-
if (answers.confirmOverwrite) {
|
|
104
|
-
fs.emptyDirSync(targetDir);
|
|
105
|
-
createProject(projectName, options);
|
|
106
|
-
log_success(`项目 ${projectName} 强制创建成功!`);
|
|
107
|
-
} else {
|
|
108
|
-
log_info('操作已取消');
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
} else {
|
|
113
|
-
createProject(projectName, options);
|
|
114
|
-
log_success(`项目 ${projectName} 创建成功!`);
|
|
115
|
-
}
|
|
65
|
+
if (fs.existsSync(targetDir)) throw new Error(`项目 ${projectName} 已存在`);
|
|
66
|
+
|
|
67
|
+
// 创建项目模版
|
|
68
|
+
await createProject(projectName, options);
|
|
69
|
+
log_success(
|
|
70
|
+
`项目 ${projectName} 创建成功!\n\n${getPlainText('项目运行步骤:\n1. 请自行安装依赖 pnpm i\n2. 启动测试服务器 pnpm run dev')}\n\n`
|
|
71
|
+
);
|
|
116
72
|
} catch (error) {
|
|
117
73
|
log_error(error.message);
|
|
118
74
|
process.exit(1);
|
|
119
75
|
}
|
|
120
76
|
}
|
|
121
77
|
|
|
122
|
-
//
|
|
78
|
+
// 创建项目模板
|
|
123
79
|
async function createProject(projectName, options) {
|
|
80
|
+
const { moduleMenuConfigInfo } = options;
|
|
81
|
+
const routeConfig = moduleMenuConfigInfo.getRouterInfo(projectName);
|
|
124
82
|
const files = await glob('./wyt-cli/bin/templates/**/*', {
|
|
125
83
|
cwd: process.cwd(), // 以项目根目录为基准
|
|
126
84
|
absolute: true, // 返回绝对路径
|
|
@@ -138,6 +96,10 @@ async function createProject(projectName, options) {
|
|
|
138
96
|
projectName,
|
|
139
97
|
buildOutDir: `../../dist/${projectName}`,
|
|
140
98
|
devServerPort: Number(options.devServerPort),
|
|
99
|
+
routeConfig: routeConfig.map((item) => {
|
|
100
|
+
const { path, name, component } = item;
|
|
101
|
+
return { path, name, component };
|
|
102
|
+
}),
|
|
141
103
|
});
|
|
142
104
|
|
|
143
105
|
// 写入文件
|
|
@@ -150,82 +112,31 @@ async function createProject(projectName, options) {
|
|
|
150
112
|
Promise.all(renderTasks);
|
|
151
113
|
|
|
152
114
|
// 更新 root -> project.json -> apps 配置
|
|
153
|
-
updateRootProjectJson(projectName, options);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// 检测已有项目信息
|
|
157
|
-
async function checkProjectInfo(options) {
|
|
158
|
-
const files = await glob(['**/package.json'], {
|
|
159
|
-
cwd: path.resolve(options.dir, 'apps'), // 以项目根目录为基准
|
|
160
|
-
absolute: true, // 返回绝对路径
|
|
161
|
-
ignore: [
|
|
162
|
-
// 添加排除规则
|
|
163
|
-
'**/node_modules/**', // 排除 node_modules
|
|
164
|
-
'**/src/**', // 排除测试目录
|
|
165
|
-
'**/.*', // 排除隐藏文件/目录
|
|
166
|
-
],
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
let results = await Promise.all(
|
|
170
|
-
files.map(async (file) => {
|
|
171
|
-
// 识别文件类型
|
|
172
|
-
const isPkg = file.endsWith('package.json');
|
|
173
|
-
// const isVite = file.endsWith('vite.config.js');
|
|
174
|
-
// const isVite_ts = file.endsWith('vite.config.ts');
|
|
115
|
+
await updateRootProjectJson(projectName, options);
|
|
175
116
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
117
|
+
// 自动生成路由
|
|
118
|
+
generateRouteStructure(projectName, routeConfig);
|
|
119
|
+
}
|
|
179
120
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
121
|
+
// 自动生成路由
|
|
122
|
+
async function generateRouteStructure(projectName, routeConfig) {
|
|
123
|
+
// 生成视图文件
|
|
124
|
+
routeConfig.forEach((route) => {
|
|
125
|
+
const vueContent = `<template>
|
|
126
|
+
<div class="${route.name}-container">
|
|
127
|
+
${route.name} 测试页面
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
185
130
|
|
|
186
|
-
|
|
187
|
-
// // 动态导入 vite 配置
|
|
188
|
-
// const viteConfig = require(file);
|
|
189
|
-
// const port = viteConfig.default?.server.port;
|
|
190
|
-
// return { type: 'vite', port };
|
|
191
|
-
// }
|
|
131
|
+
<script setup>
|
|
192
132
|
|
|
193
|
-
|
|
194
|
-
// if (isVite_ts) {
|
|
195
|
-
// return { type: 'vite', port: 6002 };
|
|
196
|
-
// }
|
|
197
|
-
} catch (err) {
|
|
198
|
-
console.error(`文件处理失败: ${file}`, err);
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
);
|
|
203
|
-
// 排序逻辑
|
|
204
|
-
results = results.sort((a, b) => {
|
|
205
|
-
// 处理 undefined 端口为最大值
|
|
206
|
-
const portA = a.port ? parseInt(a.port) : Infinity;
|
|
207
|
-
const portB = b.port ? parseInt(b.port) : Infinity;
|
|
208
|
-
// 优先级 1: 端口号升序
|
|
209
|
-
if (portA !== portB) {
|
|
210
|
-
return portA - portB;
|
|
211
|
-
}
|
|
133
|
+
</script>`;
|
|
212
134
|
|
|
213
|
-
//
|
|
214
|
-
|
|
135
|
+
// 生成路由文件
|
|
136
|
+
const filePath = path.join(path.resolve(process.cwd(), 'apps', projectName, 'src', 'views'), route.componentPath);
|
|
137
|
+
fs.ensureDirSync(path.dirname(filePath));
|
|
138
|
+
fs.writeFileSync(filePath, vueContent);
|
|
215
139
|
});
|
|
216
|
-
|
|
217
|
-
// 组合项目信息
|
|
218
|
-
const projectList = results.map((item, index) => ({
|
|
219
|
-
序号: index + 1,
|
|
220
|
-
项目名称: item.name,
|
|
221
|
-
'DevServer 端口号': item.port || '未配置',
|
|
222
|
-
}));
|
|
223
|
-
// // 格式化输出
|
|
224
|
-
if (options.list) {
|
|
225
|
-
console.log('\n\n====== 已存在的项目列表,请不要重复 ======');
|
|
226
|
-
console.table(projectList, ['序号', '项目名称', 'DevServer 端口号']);
|
|
227
|
-
}
|
|
228
|
-
return results;
|
|
229
140
|
}
|
|
230
141
|
|
|
231
142
|
// 更新 root -> project.json -> apps 配置
|
|
@@ -242,6 +153,7 @@ async function updateRootProjectJson(projectName, options) {
|
|
|
242
153
|
appsConfig.apps.push({
|
|
243
154
|
name: projectName,
|
|
244
155
|
port: options.devServerPort,
|
|
156
|
+
description: options.description,
|
|
245
157
|
created: new Date().toISOString(),
|
|
246
158
|
});
|
|
247
159
|
// 写入更新
|
|
@@ -255,3 +167,124 @@ async function updateRootProjectJson(projectName, options) {
|
|
|
255
167
|
return false;
|
|
256
168
|
}
|
|
257
169
|
}
|
|
170
|
+
|
|
171
|
+
// 询问 -> 创建项目
|
|
172
|
+
// const inquirerAnswers = await inquirer.prompt([
|
|
173
|
+
// {
|
|
174
|
+
// type: 'input',
|
|
175
|
+
// name: 'projectName',
|
|
176
|
+
// message: '请输入项目名称:',
|
|
177
|
+
// validate: (input) => {
|
|
178
|
+
// // 校验规则
|
|
179
|
+
// if (!input) return '项目名称不能为空!';
|
|
180
|
+
// if (!/^[a-zA-Z0-9-]{1,50}$/.test(input)) {
|
|
181
|
+
// return '名称只能包含字母、数字和中划线,且不超过50字符';
|
|
182
|
+
// }
|
|
183
|
+
// if (glob_projectInfo.map((item) => item.name).includes(input.toLowerCase())) {
|
|
184
|
+
// return `项目名称 "${input}" 已存在,请重新输入`;
|
|
185
|
+
// }
|
|
186
|
+
// return true;
|
|
187
|
+
// },
|
|
188
|
+
// // 当命令行已传入名称时自动跳过
|
|
189
|
+
// when: !projectName,
|
|
190
|
+
// },
|
|
191
|
+
// {
|
|
192
|
+
// type: 'input',
|
|
193
|
+
// name: 'devServerPort',
|
|
194
|
+
// message: '请输入测试服务器端口号:',
|
|
195
|
+
// validate: (input) => {
|
|
196
|
+
// // 校验规则
|
|
197
|
+
// if (!input) return '测试服务器端口号不能为空!';
|
|
198
|
+
// if (!/^\d+$/.test(input)) {
|
|
199
|
+
// return '测试服务器端口号必须为纯数字';
|
|
200
|
+
// }
|
|
201
|
+
// if (glob_projectInfo.map((item) => item.port).includes(input)) {
|
|
202
|
+
// return `测试服务器端口号 "${input}" 已存在,请重新输入`;
|
|
203
|
+
// }
|
|
204
|
+
// return true;
|
|
205
|
+
// },
|
|
206
|
+
// },
|
|
207
|
+
// ]);
|
|
208
|
+
// if (!options.force) {
|
|
209
|
+
// throw new Error(`项目 ${projectName} 已存在,使用 --force 强制覆盖`);
|
|
210
|
+
// }
|
|
211
|
+
// inquirer
|
|
212
|
+
// .prompt([
|
|
213
|
+
// {
|
|
214
|
+
// type: 'confirm',
|
|
215
|
+
// name: 'confirmOverwrite',
|
|
216
|
+
// message: `项目 ${projectName} 已存在,是否覆盖?`,
|
|
217
|
+
// default: true,
|
|
218
|
+
// },
|
|
219
|
+
// ])
|
|
220
|
+
// .then((answers) => {
|
|
221
|
+
// if (answers.confirmOverwrite) {
|
|
222
|
+
// fs.emptyDirSync(targetDir);
|
|
223
|
+
// createProject(projectName, options);
|
|
224
|
+
// log_success(`项目 ${projectName} 强制创建成功!`);
|
|
225
|
+
// } else {
|
|
226
|
+
// log_info('操作已取消');
|
|
227
|
+
// process.exit(1);
|
|
228
|
+
// }
|
|
229
|
+
// });
|
|
230
|
+
|
|
231
|
+
// 检测已有项目信息
|
|
232
|
+
// async function checkProjectInfo(options) {
|
|
233
|
+
// const files = await glob(['**/package.json'], {
|
|
234
|
+
// cwd: path.resolve(options.dir, 'apps'), // 以项目根目录为基准
|
|
235
|
+
// absolute: true, // 返回绝对路径
|
|
236
|
+
// ignore: [
|
|
237
|
+
// // 添加排除规则
|
|
238
|
+
// '**/node_modules/**', // 排除 node_modules
|
|
239
|
+
// '**/src/**', // 排除测试目录
|
|
240
|
+
// '**/.*', // 排除隐藏文件/目录
|
|
241
|
+
// ],
|
|
242
|
+
// });
|
|
243
|
+
|
|
244
|
+
// let results = await Promise.all(
|
|
245
|
+
// files.map(async (file) => {
|
|
246
|
+
// // 识别文件类型
|
|
247
|
+
// const isPkg = file.endsWith('package.json');
|
|
248
|
+
|
|
249
|
+
// try {
|
|
250
|
+
// // 读取文件内容
|
|
251
|
+
// const content = await fs.promises.readFile(file, 'utf-8');
|
|
252
|
+
|
|
253
|
+
// if (isPkg) {
|
|
254
|
+
// // 解析 package.json
|
|
255
|
+
// const pkg = JSON.parse(content);
|
|
256
|
+
// return { port: pkg.devServer && pkg.devServer.port, name: pkg.name };
|
|
257
|
+
// }
|
|
258
|
+
// } catch (err) {
|
|
259
|
+
// console.error(`文件处理失败: ${file}`, err);
|
|
260
|
+
// return null;
|
|
261
|
+
// }
|
|
262
|
+
// })
|
|
263
|
+
// );
|
|
264
|
+
// // 排序逻辑
|
|
265
|
+
// results = results.sort((a, b) => {
|
|
266
|
+
// // 处理 undefined 端口为最大值
|
|
267
|
+
// const portA = a.port ? parseInt(a.port) : Infinity;
|
|
268
|
+
// const portB = b.port ? parseInt(b.port) : Infinity;
|
|
269
|
+
// // 优先级 1: 端口号升序
|
|
270
|
+
// if (portA !== portB) {
|
|
271
|
+
// return portA - portB;
|
|
272
|
+
// }
|
|
273
|
+
|
|
274
|
+
// // 优先级 2: 名称字母序
|
|
275
|
+
// return a.name.localeCompare(b.name);
|
|
276
|
+
// });
|
|
277
|
+
|
|
278
|
+
// // 组合项目信息
|
|
279
|
+
// const projectList = results.map((item, index) => ({
|
|
280
|
+
// 序号: index + 1,
|
|
281
|
+
// 项目名称: item.name,
|
|
282
|
+
// 'DevServer 端口号': item.port || '未配置',
|
|
283
|
+
// }));
|
|
284
|
+
// // // 格式化输出
|
|
285
|
+
// if (options.list) {
|
|
286
|
+
// console.log('\n\n====== 已存在的项目列表,请不要重复 ======');
|
|
287
|
+
// console.table(projectList, ['序号', '项目名称', 'DevServer 端口号']);
|
|
288
|
+
// }
|
|
289
|
+
// return results;
|
|
290
|
+
// }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { log_info, log_error, getInquirerOperationText } from '../lib/logger.js';
|
|
6
|
+
import { checkTargetDir, getProjectConfig } from '../lib/dir.js';
|
|
7
|
+
|
|
8
|
+
export default function () {
|
|
9
|
+
const command = new Command('delete');
|
|
10
|
+
|
|
11
|
+
command.description('删除项目').action(async () => {
|
|
12
|
+
try {
|
|
13
|
+
// 校验项目目录
|
|
14
|
+
if (!checkTargetDir(process.cwd())) {
|
|
15
|
+
log_error('请在 HRP3.0 项目根目录运行此命令!');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 获取项目列表
|
|
20
|
+
const projectConfig = getProjectConfig();
|
|
21
|
+
|
|
22
|
+
// 获取需要保留的项目(创建超过3天的)
|
|
23
|
+
const projects = projectConfig.apps.filter((project) => {
|
|
24
|
+
if (!project.created) return false; // 保留无创建时间的项目
|
|
25
|
+
|
|
26
|
+
const createdDate = new Date(project.created);
|
|
27
|
+
const currentDate = new Date();
|
|
28
|
+
return currentDate - createdDate < 3 * 24 * 60 * 60 * 1000;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (projects.length == 0) {
|
|
32
|
+
log_info('没有可删除的项目!');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 交互选择项目
|
|
37
|
+
const answer = await inquirer.prompt([
|
|
38
|
+
{
|
|
39
|
+
type: 'checkbox',
|
|
40
|
+
name: 'project',
|
|
41
|
+
message: '请选择要删除的项目:' + getInquirerOperationText('checkbox'),
|
|
42
|
+
choices: projects.map((p) => ({
|
|
43
|
+
name: p.name,
|
|
44
|
+
})),
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
// 删除项目
|
|
49
|
+
if (answer.project) {
|
|
50
|
+
await deleteProject(answer.project);
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
log_error(`删除项目失败: ${error.message}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return command;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 构建部署流程
|
|
62
|
+
async function deleteProject(projects) {
|
|
63
|
+
const appsDir = path.resolve(process.cwd(), 'apps');
|
|
64
|
+
const projectConfig = getProjectConfig();
|
|
65
|
+
const originalApps = [...projectConfig.apps]; // 原始配置备份
|
|
66
|
+
// 记录实际删除的项目列表
|
|
67
|
+
const deletedProjects = [];
|
|
68
|
+
for (const project of projects) {
|
|
69
|
+
const projectPath = path.join(appsDir, project);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
if (await fs.pathExists(projectPath)) {
|
|
73
|
+
await fs.remove(projectPath);
|
|
74
|
+
console.log(`✅ 成功删除项目目录: ${project}`);
|
|
75
|
+
deletedProjects.push(project);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`⚠️ 项目目录不存在: ${project}`);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`❌ 删除失败 [${project}]:`, error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 更新配置文件
|
|
85
|
+
if (deletedProjects.length > 0) {
|
|
86
|
+
try {
|
|
87
|
+
// 过滤保留未删除的项目
|
|
88
|
+
projectConfig.apps = projectConfig.apps.filter((app) => !deletedProjects.includes(app.name));
|
|
89
|
+
|
|
90
|
+
// 保持2空格缩进格式
|
|
91
|
+
await fs.writeJSON('project.json', projectConfig, { spaces: 2 });
|
|
92
|
+
console.log(`🔄 已更新项目配置,移除了 ${deletedProjects.length} 个项目`);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('⚠️ 配置更新失败,已保留原始配置');
|
|
95
|
+
// 发生错误时回滚配置
|
|
96
|
+
await fs.writeJSON('project.json', { ...projectConfig, apps: originalApps }, { spaces: 2 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
package/bin/commands/run.js
CHANGED
|
@@ -30,7 +30,7 @@ export default function () {
|
|
|
30
30
|
type: 'rawlist',
|
|
31
31
|
name: 'runstyle',
|
|
32
32
|
message: `请选择项目运行方式:` + getInquirerOperationText('list'),
|
|
33
|
-
default:
|
|
33
|
+
default: 0,
|
|
34
34
|
choices: [
|
|
35
35
|
{ name: '方式1:运行所有项目', value: 0 },
|
|
36
36
|
{ name: '方式2:运行指定项目', value: 1 },
|
|
@@ -62,18 +62,55 @@ export default function () {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// 运行项目
|
|
65
|
-
function runProject(projects = [], appsDir) {
|
|
66
|
-
|
|
65
|
+
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
|
+
// 运行命令
|
|
74
|
+
const command = 'npm run dev';
|
|
75
|
+
// 运行项目文档
|
|
76
|
+
log_info(`🚀 启动项目文档...`);
|
|
77
|
+
const docsPath = path.join(process.cwd(), 'docs');
|
|
78
|
+
const docsProcess = execa(command, {
|
|
79
|
+
cwd: docsPath,
|
|
80
|
+
stdio: 'inherit',
|
|
81
|
+
});
|
|
82
|
+
docsProcess.on('exit', (code) => {
|
|
83
|
+
if (code !== 0) log_error(`项目文档异常退出 (CODE: ${code})`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 运行项目
|
|
87
|
+
const configPath = path.join(process.cwd(), 'project.json');
|
|
88
|
+
let config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
89
|
+
if (config.apps) {
|
|
90
|
+
// 初始化
|
|
91
|
+
config.apps.forEach((app, index) => {
|
|
92
|
+
config.apps[index] = {
|
|
93
|
+
...app,
|
|
94
|
+
lastRun: false,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
projects.forEach(async (project) => {
|
|
67
99
|
const projectName = project.name;
|
|
68
100
|
if (projectName) {
|
|
69
101
|
const projectPath = path.join(appsDir, projectName);
|
|
70
102
|
if (!fs.existsSync(projectPath)) {
|
|
71
103
|
throw new Error(`项目 ${projectName} 不存在`);
|
|
72
104
|
}
|
|
73
|
-
|
|
74
|
-
const command = 'npm run dev';
|
|
105
|
+
|
|
75
106
|
// 执行命令
|
|
76
107
|
log_info(`🚀 启动项目 ${projectName}...`);
|
|
108
|
+
// 更新项目启动状态
|
|
109
|
+
const targetIndex = config.apps.findIndex((app) => app.name === projectName);
|
|
110
|
+
if (targetIndex !== -1) {
|
|
111
|
+
config.apps[targetIndex].lastRun = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
77
114
|
const subprocess = execa(command, {
|
|
78
115
|
cwd: projectPath,
|
|
79
116
|
stdio: 'inherit',
|
|
@@ -86,4 +123,6 @@ function runProject(projects = [], appsDir) {
|
|
|
86
123
|
});
|
|
87
124
|
}
|
|
88
125
|
});
|
|
126
|
+
|
|
127
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
89
128
|
}
|
package/bin/lib/dir.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
3
4
|
|
|
4
5
|
// 获取 apps 目录下,所有有效项目
|
|
5
6
|
export async function getProjects(appsDir) {
|
|
@@ -34,3 +35,131 @@ export function checkTargetDir(currentPath) {
|
|
|
34
35
|
|
|
35
36
|
return conflictDirs.join(',') == monoDirs.join(',');
|
|
36
37
|
}
|
|
38
|
+
|
|
39
|
+
// 获取项目配置文件信息
|
|
40
|
+
export function getProjectConfig(configPath = 'project.json') {
|
|
41
|
+
try {
|
|
42
|
+
const absPath = path.resolve(configPath);
|
|
43
|
+
if (!fs.existsSync(absPath)) {
|
|
44
|
+
throw new Error(`配置文件 ${absPath} 不存在`);
|
|
45
|
+
}
|
|
46
|
+
return fs.readJSONSync(absPath);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('配置文件读取失败:', error.message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 获取项目目录下 module-menu.xml 信息
|
|
54
|
+
export function getModuleMenuConfig(menuXmlPath) {
|
|
55
|
+
const xmlContent = fs.readFileSync(menuXmlPath, 'utf8');
|
|
56
|
+
const xmlParser = new XMLParser({
|
|
57
|
+
attributeNamePrefix: '',
|
|
58
|
+
attrNodeName: '@', //default is false
|
|
59
|
+
textNodeName: '#text',
|
|
60
|
+
ignoreAttributes: false,
|
|
61
|
+
cdataTagName: '__cdata', //default is false
|
|
62
|
+
cdataPositionChar: '\\c',
|
|
63
|
+
format: false,
|
|
64
|
+
indentBy: ' ',
|
|
65
|
+
suppressEmptyNode: false,
|
|
66
|
+
tagValueProcessor: (a) => he.encode(a, { useNamedReferences: true }), // default is a=>a
|
|
67
|
+
attrValueProcessor: (a) => he.encode(a, { isAttributeValue: isAttribute, useNamedReferences: true }), // default is a=>a
|
|
68
|
+
rootNodeName: 'element',
|
|
69
|
+
});
|
|
70
|
+
const menuData = xmlParser.parse(xmlContent) || {};
|
|
71
|
+
if (!menuData.root || !menuData.root.nodes || !menuData.root.nodes.node) {
|
|
72
|
+
console.error('module-menu.xml 格式错误');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
modules: menuData.root.nodes.node.map((item) => {
|
|
77
|
+
return {
|
|
78
|
+
port: Number('60' + item.id),
|
|
79
|
+
name: item.mod,
|
|
80
|
+
description: item.nm,
|
|
81
|
+
};
|
|
82
|
+
}),
|
|
83
|
+
getRouterInfo: (mod = 'fm') => {
|
|
84
|
+
// 递归处理节点
|
|
85
|
+
const routes = [];
|
|
86
|
+
const processNode = (node, basePath = '') => {
|
|
87
|
+
const currentDir = Number(node.deep) >= 2 ? `${mod}${node.id}` : '';
|
|
88
|
+
let fullPath = path.join(basePath, currentDir).replace(/\\+/g, '/');
|
|
89
|
+
if (fullPath == '.') fullPath = '';
|
|
90
|
+
// 当前节点是容器节点(有子节点)
|
|
91
|
+
if (node.node && node.node.length > 0) {
|
|
92
|
+
// 生成子路由
|
|
93
|
+
node.node.flatMap((child) => processNode(child, fullPath));
|
|
94
|
+
// // 创建路由容器(不需要对应组件)
|
|
95
|
+
// const children = node.node.flatMap((child) => processNode(child, fullPath));
|
|
96
|
+
// routes.push({
|
|
97
|
+
// path: node.id,
|
|
98
|
+
// name: `${mod}${node.id}`,
|
|
99
|
+
// children,
|
|
100
|
+
// });
|
|
101
|
+
}
|
|
102
|
+
// 当前节点是叶子节点
|
|
103
|
+
else {
|
|
104
|
+
let componentPath = '';
|
|
105
|
+
if (fullPath) {
|
|
106
|
+
const componentName = mod + node.id;
|
|
107
|
+
if (currentDir == componentName) {
|
|
108
|
+
componentPath = `${fullPath}/index.vue`;
|
|
109
|
+
} else {
|
|
110
|
+
componentPath = `${fullPath}/${componentName}.vue`;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
componentPath = 'index.vue';
|
|
114
|
+
}
|
|
115
|
+
routes.push({
|
|
116
|
+
path: node.id,
|
|
117
|
+
name: `${mod}${node.id}`,
|
|
118
|
+
componentPath,
|
|
119
|
+
component: `@/views/${componentPath}`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
// 生成完整路由结构
|
|
124
|
+
processNode(...menuData.root.nodes.node.filter((item) => item.mod === mod));
|
|
125
|
+
return routes;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// const node = [
|
|
131
|
+
// {
|
|
132
|
+
// node: [
|
|
133
|
+
// { deep: '2', id: '0101', nm: '待我审批' },
|
|
134
|
+
// { deep: '2', id: '0102', nm: '发起申请' },
|
|
135
|
+
// { deep: '2', id: '0103', nm: '审批中' },
|
|
136
|
+
// { deep: '2', id: '0104', nm: '已完成' },
|
|
137
|
+
// { deep: '2', id: '0105', nm: '已退回' },
|
|
138
|
+
// { deep: '2', id: '0106', nm: '暂存草稿' },
|
|
139
|
+
// { deep: '2', id: '0107', nm: '抄送通知' },
|
|
140
|
+
// ],
|
|
141
|
+
// deep: '1',
|
|
142
|
+
// id: '01',
|
|
143
|
+
// nm: '个人工作台',
|
|
144
|
+
// mod: 'mw',
|
|
145
|
+
// },
|
|
146
|
+
// {
|
|
147
|
+
// node: [
|
|
148
|
+
// { node: [{ deep: '3', id: '020101', nm: '期初数' }], deep: '2', id: '0201', nm: '系统设置' },
|
|
149
|
+
// { deep: '2', id: '0202', nm: '账套设置' },
|
|
150
|
+
// { deep: '2', id: '0203', nm: '凭证管理' },
|
|
151
|
+
// { deep: '2', id: '0204', nm: '账簿管理' },
|
|
152
|
+
// { deep: '2', id: '0205', nm: '财务处理' },
|
|
153
|
+
// { deep: '2', id: '0206', nm: '财务报表' },
|
|
154
|
+
// { deep: '2', id: '0207', nm: '出纳管理' },
|
|
155
|
+
// { deep: '2', id: '0208', nm: '往来管理' },
|
|
156
|
+
// { deep: '2', id: '0209', nm: '资产核算' },
|
|
157
|
+
// { deep: '2', id: '0210', nm: '薪资管理' },
|
|
158
|
+
// { deep: '2', id: '0211', nm: '智能记账' },
|
|
159
|
+
// ],
|
|
160
|
+
// deep: '1',
|
|
161
|
+
// id: '02',
|
|
162
|
+
// nm: '财务管理',
|
|
163
|
+
// mod: 'fm',
|
|
164
|
+
// },
|
|
165
|
+
// ];
|
package/bin/lib/logger.js
CHANGED
|
@@ -56,3 +56,18 @@ export function getFormatTimeText(time) {
|
|
|
56
56
|
export function getUnderlineText(text) {
|
|
57
57
|
return chalk.cyan.underline(text);
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
// 获取项目列表信息文本
|
|
61
|
+
// 输出效果:
|
|
62
|
+
// │ 操作步骤:
|
|
63
|
+
// │ 1. 确认项目列表
|
|
64
|
+
// │ 2. 检查依赖状态
|
|
65
|
+
// │ 3. 执行清理操作
|
|
66
|
+
export function getPlainText(text) {
|
|
67
|
+
return chalk.white(
|
|
68
|
+
text
|
|
69
|
+
.split('\n')
|
|
70
|
+
.map((line) => `│ ${line}`)
|
|
71
|
+
.join('\n')
|
|
72
|
+
);
|
|
73
|
+
}
|
package/bin/main.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import { require } from './lib/utils.js';
|
|
3
3
|
import { LOWEST_NODE_VERSION } from './lib/const.js';
|
|
4
4
|
import { log_error, log_warning } from './lib/logger.js';
|
|
5
5
|
import configureCreateCommand from './commands/create.js';
|
|
6
6
|
import configureRunCommand from './commands/run.js';
|
|
7
|
+
import configureDeleteCommand from './commands/delete.js';
|
|
7
8
|
import configureDeployCommand from './commands/deploy.js';
|
|
8
9
|
|
|
9
10
|
// 外部依赖
|
|
@@ -39,6 +40,7 @@ function handleProgramCommand() {
|
|
|
39
40
|
// 注册子命令
|
|
40
41
|
program.addCommand(configureCreateCommand());
|
|
41
42
|
program.addCommand(configureRunCommand());
|
|
43
|
+
program.addCommand(configureDeleteCommand());
|
|
42
44
|
program.addCommand(configureDeployCommand());
|
|
43
45
|
|
|
44
46
|
// 解析命令行参数
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { createRouter, createWebHashHistory } from 'vue-router';
|
|
2
2
|
const basename = process.env.NODE_ENV === 'production' ? '/<%= projectName %>/' : '';
|
|
3
3
|
|
|
4
|
+
let autoRoutes = `<%- JSON.stringify(routeConfig) %>`;
|
|
5
|
+
autoRoutes = JSON.parse(autoRoutes).map((item) => {
|
|
6
|
+
return {
|
|
7
|
+
...item,
|
|
8
|
+
path: `/${item.path}`,
|
|
9
|
+
component: () => import(/* @vite-ignore */ item.component.replace('@', '/src')),
|
|
10
|
+
};
|
|
11
|
+
});
|
|
4
12
|
const routes = [
|
|
5
|
-
|
|
6
|
-
// path: '*****',
|
|
7
|
-
// name: '*****',
|
|
8
|
-
// },
|
|
9
|
-
|
|
13
|
+
...autoRoutes,
|
|
10
14
|
// 404 路由配置
|
|
11
15
|
{
|
|
12
16
|
path: '/:pathMatch(.*)*',
|
|
@@ -6,7 +6,7 @@ const projectConfig = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), '..
|
|
|
6
6
|
const ProxyURL = projectConfig.apiUrl || 'https://hrp3.wytdev.com';
|
|
7
7
|
|
|
8
8
|
export default defineConfig({
|
|
9
|
-
base: './',
|
|
9
|
+
base: process.env.NODE_ENV == 'development' ? './' : '/<%= projectName %>',
|
|
10
10
|
build: {
|
|
11
11
|
outDir: '<%= buildOutDir %>',
|
|
12
12
|
emptyOutDir: true, // 清空输出目录
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wyt-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "HRP3.0 项目命令行工具",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"commander": "^14.0.2",
|
|
26
26
|
"ejs": "^4.0.1",
|
|
27
27
|
"execa": "^9.6.1",
|
|
28
|
+
"fast-xml-parser": "^5.3.4",
|
|
28
29
|
"fs-extra": "^11.3.3",
|
|
29
30
|
"glob": "^13.0.0",
|
|
30
31
|
"inquirer": "^13.2.0",
|