@4399ywkf/cli 0.0.3 → 0.0.5

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 (2) hide show
  1. package/bin/cli.js +360 -70
  2. package/package.json +17 -7
package/bin/cli.js CHANGED
@@ -1,14 +1,75 @@
1
1
  #!/usr/bin/env node
2
- const program = require("commander")
3
- const inquirer = require("inquirer")
4
- const path = require("path")
5
- const ora = require('ora') // 引入ora
6
- const fs = require('fs-extra')
7
- const { exec } = require('child_process');
2
+ const program = require("commander");
3
+ const inquirer = require("inquirer");
4
+ const path = require("path");
5
+ const ora = require("ora");
6
+ const fs = require("fs-extra");
7
+ const { exec } = require("child_process");
8
+
9
+ // 美化输出相关
10
+ const chalk = require("chalk");
11
+ const boxen = require("boxen");
12
+ const figlet = require("figlet");
8
13
 
9
14
  // const templates = require("./templates.js")
10
- const { getGitReposList } = require('./api.js')
11
- const package = require("../package.json")
15
+ const { getGitReposList } = require("./api.js");
16
+ const package = require("../package.json");
17
+
18
+ // 美化输出函数
19
+ function printBanner() {
20
+ console.log(
21
+ chalk.cyan(
22
+ figlet.textSync("YWKF CLI", {
23
+ font: "Standard",
24
+ horizontalLayout: "default",
25
+ verticalLayout: "default",
26
+ })
27
+ )
28
+ );
29
+ console.log(chalk.gray(`版本: ${package.version}\n`));
30
+ }
31
+
32
+ function printSuccess(message) {
33
+ console.log(chalk.green("✅ " + message));
34
+ }
35
+
36
+ function printError(message) {
37
+ console.log(chalk.red("❌ " + message));
38
+ }
39
+
40
+ function printInfo(message) {
41
+ console.log(chalk.blue("ℹ️ " + message));
42
+ }
43
+
44
+ function printWarning(message) {
45
+ console.log(chalk.yellow("⚠️ " + message));
46
+ }
47
+
48
+ function printFinalInstructions(projectName) {
49
+ const instructions = [
50
+ chalk.green("🎉 项目创建成功!"),
51
+ "",
52
+ chalk.cyan("接下来的步骤:"),
53
+ chalk.gray(" 1. 进入项目目录"),
54
+ chalk.white(` cd ${projectName}`),
55
+ chalk.gray(" 2. 安装依赖"),
56
+ chalk.white(" pnpm i"),
57
+ chalk.gray(" 3. 启动项目"),
58
+ chalk.white(" pnpm start"),
59
+ "",
60
+ chalk.magenta("🚀 开始你的开发之旅吧!"),
61
+ ].join("\n");
62
+
63
+ console.log(
64
+ boxen(instructions, {
65
+ padding: 1,
66
+ margin: 1,
67
+ borderStyle: "round",
68
+ borderColor: "cyan",
69
+ backgroundColor: "black",
70
+ })
71
+ );
72
+ }
12
73
 
13
74
  function cloneRepository(repoUrl, destination) {
14
75
  return new Promise((resolve, reject) => {
@@ -23,122 +84,351 @@ function cloneRepository(repoUrl, destination) {
23
84
  });
24
85
  }
25
86
 
87
+ // 新添加:更新环境模板文件
88
+ async function updateEnvTemplate(filePath, config) {
89
+ try {
90
+ // 读取模板文件内容
91
+ let templateContent = await fs.readFile(filePath, "utf8");
92
+
93
+ // 替换模板变量
94
+ const replacements = {
95
+ "{{APP_CNAME}}": `"${config.appCname}"`,
96
+ "{{APP_NAME}}": `"${config.appName}"`,
97
+ "{{SENTRY_ENABLED}}": `"${config.sentryEnabled ? "true" : "false"}"`,
98
+ "{{SENTRY_DSN}}": `"${config.sentryDsn}"`,
99
+ };
100
+
101
+ // 执行替换
102
+ for (const [placeholder, value] of Object.entries(replacements)) {
103
+ templateContent = templateContent.replace(
104
+ new RegExp(placeholder, "g"),
105
+ value
106
+ );
107
+ }
108
+
109
+ // 写回文件
110
+ await fs.writeFile(filePath, templateContent, "utf8");
111
+ } catch (error) {
112
+ throw new Error(`更新模板文件失败: ${error.message}`);
113
+ }
114
+ }
115
+
116
+ // 配置 webpack 子应用的环境变量(填充模板)
117
+ async function configureWebpackSubApp(projectPath, projectName) {
118
+ printInfo("正在配置 Webpack 子应用环境变量...");
119
+
120
+ const envFilePath = path.join(projectPath, "config", "env", ".env.public");
121
+
122
+ // 检查模板文件是否存在
123
+ if (!fs.existsSync(envFilePath)) {
124
+ printWarning("未找到 .env.public 模板文件,跳过配置");
125
+ return;
126
+ }
127
+
128
+ // 询问用户配置选项
129
+ const { configureEnv } = await inquirer.prompt({
130
+ type: "confirm",
131
+ name: "configureEnv",
132
+ message: chalk.cyan("🔧 是否要自定义应用配置?"),
133
+ default: true,
134
+ });
135
+
136
+ if (!configureEnv) {
137
+ // 如果不配置,使用默认值填充模板
138
+ const defaultConfig = {
139
+ appCname: projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
140
+ appName: projectName
141
+ .toLowerCase()
142
+ .replace(/[^a-z0-9-]/g, "-")
143
+ .split("-")
144
+ .map((word, index) =>
145
+ index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
146
+ )
147
+ .join(""),
148
+ outputPath: "dist",
149
+ publicPath: "/",
150
+ };
151
+
152
+ await updateEnvTemplate(envFilePath, defaultConfig);
153
+ printInfo("已使用默认配置填充模板");
154
+ return;
155
+ }
156
+
157
+ // 获取用户输入的配置
158
+ const config = await inquirer.prompt([
159
+ {
160
+ type: "input",
161
+ name: "appName",
162
+ message: chalk.cyan("📝 请输入应用代码名称(APP_NAME):"),
163
+ default: projectName.toLowerCase().replace(/[^a-z0-9-_]/g, "-"),
164
+ validate: (input) => {
165
+ if (!input.trim()) {
166
+ return "应用代码名称不能为空!";
167
+ }
168
+ if (!/^[a-z0-9-_]+$/.test(input)) {
169
+ return "应用代码名称只能包含小写字母、数字、横线和下划线!";
170
+ }
171
+ return true;
172
+ },
173
+ },
174
+ {
175
+ type: "input",
176
+ name: "appCname",
177
+ message: chalk.cyan("📝 请输入应用名称 (APP_CNAME):"),
178
+ default: projectName.toLowerCase().replace(/[^a-z0-9-_]/g, "-"),
179
+ validate: (input) => {
180
+ if (!input.trim()) {
181
+ return "应用名称不能为空!";
182
+ }
183
+
184
+ // 支持中文、英文、数字、横线、下划线,必须以字母或中文开头
185
+ if (
186
+ !/^[\u4e00-\u9fff\u3400-\u4dbfa-zA-Z][\u4e00-\u9fff\u3400-\u4dbfa-zA-Z0-9_-]*$/.test(
187
+ input
188
+ )
189
+ ) {
190
+ return "应用名称必须以中文或字母开头,可包含中文、字母、数字、横线和下划线!";
191
+ }
192
+
193
+ return true;
194
+ },
195
+ },
196
+ {
197
+ type: "confirm",
198
+ name: "sentryEnabled",
199
+ message: chalk.cyan("📝 是否开启 Sentry?"),
200
+ default: true,
201
+ },
202
+ // 如果开启 sentry 则需要输入 sentry 的 dsn
203
+ {
204
+ type: "input",
205
+ name: "sentryDsn",
206
+ message: chalk.cyan("📝 请输入 Sentry 的 DSN:"),
207
+ default: "https://207b65a59562963635abde2cfce82651@sentry.gz4399.com/28",
208
+ when: (answers) => answers.sentryEnabled,
209
+ },
210
+ ]);
211
+
212
+ try {
213
+ // 更新模板文件
214
+ await updateEnvTemplate(envFilePath, config);
215
+ printSuccess(`已更新环境配置文件:${chalk.bold(".env.public")}`);
216
+
217
+ // 显示配置信息
218
+ console.log();
219
+ printInfo("应用配置信息:");
220
+ console.log(chalk.gray(` 应用代码名称: ${chalk.white(config.appCname)}`));
221
+ console.log(chalk.gray(` 应用名称: ${chalk.white(config.appName)}`));
222
+ console.log();
223
+ } catch (error) {
224
+ printError(`更新环境配置文件失败:${error.message}`);
225
+ throw error;
226
+ }
227
+ }
228
+
26
229
  program
27
230
  .command("create [projectName]")
28
231
  .description("创建模版")
29
- .option('-t, --template <template>', '模版名称')
232
+ .option("-t, --template <template>", "模版名称")
30
233
  .action(async (projectName, options) => {
31
- const getRepoLoading = ora('获取模版列表...')
32
- getRepoLoading.start()
33
- // const templates = await getGitReposList('Emma-Alpha')
234
+ // 显示Banner
235
+ printBanner();
236
+
237
+ const getRepoLoading = ora({
238
+ text: "正在获取模版列表...",
239
+ spinner: "dots",
240
+ });
241
+ getRepoLoading.start();
242
+
243
+ // 模拟异步获取模版数据
34
244
  const templates = {
35
245
  webpack: {
36
246
  main: [
37
247
  {
38
- value: "https://ywgit.gz4399.com/ywkf/webpack-mainApplicate_demo.git",
39
- name: "webpack-mainApplicate_demo"
40
- }
248
+ value:
249
+ "https://ywgit.gz4399.com/ywkf/webpack-mainApplicate_demo.git",
250
+ name: "webpack-mainApplicate_demo",
251
+ },
41
252
  ],
42
253
  sub: [
43
254
  {
44
- value: "https://ywgit.gz4399.com/ywkf/webpack-subApplicate_demo.git",
45
- name: "webpack-subApplicate_demo"
46
- }
47
- ]
255
+ value:
256
+ "https://ywgit.gz4399.com/ywkf/webpack-subApplicate_demo.git",
257
+ name: "webpack-subApplicate_demo",
258
+ },
259
+ ],
48
260
  },
49
261
  vite: {
50
262
  main: [
51
263
  {
52
264
  value: "https://ywgit.gz4399.com/ywkf/vite-mainApplicate_demo.git",
53
- name: "vite-mainApplicate_demo"
54
- }
265
+ name: "vite-mainApplicate_demo",
266
+ },
55
267
  ],
56
268
  sub: [
57
269
  {
58
270
  value: "https://ywgit.gz4399.com/ywkf/vite-subApplicate_demo",
59
- name: "vite-subApplicate_demo"
60
- }
61
- ]
62
- }
63
- }
271
+ name: "vite-subApplicate_demo",
272
+ },
273
+ ],
274
+ },
275
+ };
64
276
 
65
- getRepoLoading.succeed('获取模版列表成功!')
277
+ getRepoLoading.succeed();
278
+ printSuccess("模版列表获取成功!");
279
+ console.log(); // 空行
66
280
 
67
281
  // 1. 如果用户没有传入名称就交互式输入
68
282
  if (!projectName) {
69
283
  const { name } = await inquirer.prompt({
70
284
  type: "input",
71
285
  name: "name",
72
- message: "请输入项目名称:",
73
- })
74
- projectName = name // 赋值输入的项目名称
286
+ message: chalk.cyan("📝 请输入项目名称:"),
287
+ validate: (input) => {
288
+ if (!input.trim()) {
289
+ return "项目名称不能为空!";
290
+ }
291
+ return true;
292
+ },
293
+ });
294
+ projectName = name;
75
295
  }
76
- console.log('项目名称:', projectName)
296
+ printInfo(`项目名称:${chalk.bold(projectName)}`);
77
297
 
78
298
  // 2. 选择工具类型
79
299
  const { toolType } = await inquirer.prompt({
80
- type: 'list',
81
- name: 'toolType',
82
- message: '请选择工具类型:',
83
- choices: ['webpack', 'vite']
84
- })
85
- console.log('工具类型:', toolType)
300
+ type: "list",
301
+ name: "toolType",
302
+ message: chalk.cyan("🔧 请选择工具类型:"),
303
+ choices: [
304
+ {
305
+ name: `${chalk.blue("📦 Webpack")} - 成熟稳定的打包工具`,
306
+ value: "webpack",
307
+ },
308
+ { name: `${chalk.green("⚡ Vite")} - 快速的构建工具`, value: "vite" },
309
+ ],
310
+ });
311
+ printInfo(`工具类型:${chalk.bold(toolType)}`);
86
312
 
87
313
  // 3. 选择应用方向
88
314
  const { appDirection } = await inquirer.prompt({
89
- type: 'list',
90
- name: 'appDirection',
91
- message: '请选择应用方向:',
92
- choices: ['main', 'sub']
93
- })
94
- console.log('应用方向:', appDirection)
315
+ type: "list",
316
+ name: "appDirection",
317
+ message: chalk.cyan("🎯 请选择应用方向:"),
318
+ choices: [
319
+ { name: `${chalk.magenta("🏠 主应用")} - 微前端主应用`, value: "main" },
320
+ { name: `${chalk.yellow("🧩 子应用")} - 微前端子应用`, value: "sub" },
321
+ ],
322
+ });
323
+ printInfo(
324
+ `应用方向:${chalk.bold(appDirection === "main" ? "主应用" : "子应用")}`
325
+ );
95
326
 
96
327
  // 4. 选择模版
97
328
  let projectTemplate;
329
+
98
330
  if (options.template) {
99
- projectTemplate = templates[toolType][appDirection].find(template => template.name === options.template)?.value;
331
+ const foundTemplate = templates[toolType][appDirection].find(
332
+ (template) => template.name === options.template
333
+ );
334
+ if (foundTemplate) {
335
+ projectTemplate = foundTemplate.value;
336
+ printSuccess(`使用指定模版:${chalk.bold(options.template)}`);
337
+ } else {
338
+ printWarning(`未找到指定模版 "${options.template}",请重新选择`);
339
+ }
100
340
  }
101
341
 
102
342
  if (!projectTemplate) {
103
- const { template } = await inquirer.prompt({
104
- type: 'list',
105
- name: 'template',
106
- message: '请选择模版:',
107
- choices: templates[toolType][appDirection]
108
- })
109
- projectTemplate = template; // 赋值选择的项目模板
343
+ const availableTemplates = templates[toolType][appDirection];
344
+
345
+ // 如果只有一个模版,直接使用它
346
+ if (availableTemplates.length === 1) {
347
+ projectTemplate = availableTemplates[0].value;
348
+ } else {
349
+ // 多个模版时让用户选择
350
+ const { template } = await inquirer.prompt({
351
+ type: "list",
352
+ name: "template",
353
+ message: chalk.cyan("📋 请选择模版:"),
354
+ choices: availableTemplates.map((t) => ({
355
+ name: `${chalk.white(t.name)}`,
356
+ value: t.value,
357
+ })),
358
+ });
359
+ projectTemplate = template;
360
+ }
110
361
  }
111
- console.log('模版:', projectTemplate)
362
+
363
+ console.log(); // 空行
112
364
 
113
365
  // 获取目标文件夹路径
114
- const dest = path.join(process.cwd(), projectName)
366
+ const dest = path.join(process.cwd(), projectName);
115
367
  // 判断文件夹是否存在,存在就交互询问用户是否覆盖
116
368
  if (fs.existsSync(dest)) {
369
+ printWarning(`目录 "${projectName}" 已存在!`);
117
370
  const { force } = await inquirer.prompt({
118
- type: 'confirm',
119
- name: 'force',
120
- message: '目录已存在,是否覆盖?'
121
- })
122
- // 如果覆盖就删除文件夹继续往下执行,否的话就退出进程
123
- force ? fs.removeSync(dest) : process.exit(1)
371
+ type: "confirm",
372
+ name: "force",
373
+ message: chalk.yellow("⚠️ 是否覆盖现有目录?"),
374
+ default: false,
375
+ });
376
+ if (force) {
377
+ fs.removeSync(dest);
378
+ printSuccess("已删除现有目录");
379
+ } else {
380
+ printInfo("操作已取消");
381
+ process.exit(1);
382
+ }
124
383
  }
125
384
 
126
385
  // 开始loading
127
- const loading = ora('正在下载模版...')
128
- loading.start()
386
+ const loading = ora({
387
+ text: "正在下载模版...",
388
+ spinner: "bouncingBar",
389
+ });
390
+ loading.start();
391
+
129
392
  // 5. 开始下载模版
130
393
  try {
131
394
  const result = await cloneRepository(projectTemplate, dest);
132
- loading.succeed('创建模版成功!') // 成功loading
133
- console.log(`\ncd ${projectName}`)
134
- console.log('pnpm i')
135
- console.log('pnpm start\n')
395
+ loading.succeed();
396
+ printSuccess("模版下载成功!");
397
+
398
+ // 删除.git文件夹
399
+ const gitDir = path.join(dest, ".git");
400
+ if (fs.existsSync(gitDir)) {
401
+ fs.removeSync(gitDir);
402
+ printSuccess("已清理 .git 文件夹");
403
+ }
404
+
405
+ // 新添加:如果是 webpack 子应用,进行环境配置
406
+ if (toolType === "webpack" && appDirection === "sub") {
407
+ console.log(); // 空行
408
+ await configureWebpackSubApp(dest, projectName);
409
+ }
410
+
411
+ console.log(); // 空行
412
+ printFinalInstructions(projectName);
136
413
  } catch (error) {
137
- loading.fail('创建模版失败:' + error) // 失败loading
414
+ loading.fail();
415
+ printError("模版下载失败:" + error);
416
+ process.exit(1);
138
417
  }
139
- })
418
+ });
140
419
 
141
420
  // 定义当前版本
142
- program.version(`v${package.version}`)
143
- program.on('--help', () => { }) // 添加--help
144
- program.parse(process.argv)
421
+ program.version(`v${package.version}`);
422
+ program.on("--help", () => {
423
+ printBanner();
424
+ console.log(chalk.gray("使用示例:"));
425
+ console.log(chalk.white(" @4399ywkf/cli create my-project"));
426
+ console.log(
427
+ chalk.white(
428
+ " @4399ywkf/cli create my-project -t webpack-mainApplicate_demo"
429
+ )
430
+ );
431
+ console.log();
432
+ });
433
+
434
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4399ywkf/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "运维开发部脚手架",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,9 +9,6 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "scripts": {
13
- "test": "echo \"Error: no test specified\" && exit 1"
14
- },
15
12
  "repository": {
16
13
  "type": "git"
17
14
  },
@@ -24,12 +21,25 @@
24
21
  "download-git-repo": "^3.0.2",
25
22
  "fs-extra": "^11.1.1",
26
23
  "inquirer": "^8.2.5",
27
- "ora": "^5.4.1"
24
+ "ora": "^5.4.1",
25
+ "chalk": "^4.1.2",
26
+ "boxen": "^5.1.2",
27
+ "figlet": "^1.5.2"
28
28
  },
29
29
  "files": [
30
30
  "bin"
31
31
  ],
32
32
  "authors": [
33
33
  "ywjszx@4399.com"
34
- ]
35
- }
34
+ ],
35
+ "scripts": {
36
+ "test": "echo \"Error: no test specified\" && exit 1",
37
+ "dev": "node bin/cli.js",
38
+ "debug": "node --inspect-brk bin/cli.js",
39
+ "debug:create": "node --inspect-brk bin/cli.js create test-project",
40
+ "debug:create-with-template": "node --inspect-brk bin/cli.js create test-project -t webpack-mainApplicate_demo",
41
+ "debug:help": "node --inspect-brk bin/cli.js --help",
42
+ "link": "npm link",
43
+ "unlink": "npm unlink -g"
44
+ }
45
+ }