generator-mico-cli 0.1.0 → 0.1.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.
package/bin/mico.js CHANGED
@@ -1,81 +1,277 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { spawn } = require('node:child_process');
4
+ const { spawn, spawnSync } = require('node:child_process');
5
5
  const fs = require('node:fs');
6
6
  const path = require('node:path');
7
+ const readline = require('node:readline');
7
8
 
9
+ const rootDir = path.resolve(__dirname, '..');
10
+ const pkg = JSON.parse(
11
+ fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8')
12
+ );
13
+
14
+ /**
15
+ * 打印帮助信息
16
+ */
8
17
  function printHelp() {
9
- console.log('Usage: mico create <generator> [args]');
10
- console.log(' mico create <generator> -- [args]');
11
- console.log('');
12
- console.log('Examples:');
13
- console.log(' mico create mirco-react');
14
- console.log(' mico create mico-cli -- --help');
15
- console.log('');
16
- console.log('Notes:');
17
- console.log(' Requires Yeoman CLI (yo) installed globally.');
18
- console.log(' If a local generator exists at generators/<name>, it will be used.');
18
+ console.log(`
19
+ Usage: mico <command> [options]
20
+
21
+ Commands:
22
+ create <generator> Run a generator (e.g., mico create subapp-react)
23
+ update Update mico-cli to the latest version
24
+
25
+ Options:
26
+ --help, -h Show this help message
27
+ --version, -v Show version number
28
+ --no-update-check Skip update check
29
+
30
+ Examples:
31
+ mico create subapp-react
32
+ mico create app
33
+ mico update
34
+
35
+ Notes:
36
+ Requires Yeoman CLI (yo) installed globally.
37
+ If a local generator exists at generators/<name>, it will be used.
38
+ `);
19
39
  }
20
40
 
21
- const args = process.argv.slice(2);
22
- const doubleDashIndex = args.indexOf('--');
23
- const passthroughArgs =
24
- doubleDashIndex === -1 ? [] : args.slice(doubleDashIndex + 1);
25
- const mainArgs = doubleDashIndex === -1 ? args : args.slice(0, doubleDashIndex);
26
- const hasHelp = mainArgs.includes('--help') || mainArgs.includes('-h');
41
+ /**
42
+ * 打印版本信息
43
+ */
44
+ function printVersion() {
45
+ console.log(`mico-cli v${pkg.version}`);
46
+ }
27
47
 
28
- if (mainArgs.length === 0 || hasHelp) {
29
- printHelp();
30
- process.exit(0);
48
+ /**
49
+ * 检查是否有新版本
50
+ * @returns {Promise<{current: string, latest: string, hasUpdate: boolean} | null>}
51
+ */
52
+ async function checkForUpdate() {
53
+ try {
54
+ const updateNotifier = (await import('update-notifier')).default;
55
+ const semver = (await import('semver')).default;
56
+ const notifier = updateNotifier({
57
+ pkg,
58
+ updateCheckInterval: 0 // 每次都检查
59
+ });
60
+
61
+ // 等待检查完成
62
+ await notifier.fetchInfo();
63
+
64
+ // 使用 semver 比较,确保 latest > current
65
+ if (notifier.update && semver.gt(notifier.update.latest, pkg.version)) {
66
+ return {
67
+ current: pkg.version,
68
+ latest: notifier.update.latest,
69
+ hasUpdate: true
70
+ };
71
+ }
72
+ return { current: pkg.version, latest: pkg.version, hasUpdate: false };
73
+ } catch {
74
+ return null;
75
+ }
31
76
  }
32
77
 
33
- const [command, generator, ...rest] = mainArgs;
78
+ /**
79
+ * 询问用户是否更新
80
+ * @returns {Promise<boolean>}
81
+ */
82
+ function askForUpdate(current, latest) {
83
+ return new Promise((resolve) => {
84
+ const rl = readline.createInterface({
85
+ input: process.stdin,
86
+ output: process.stdout
87
+ });
34
88
 
35
- if (command !== 'create' || !generator) {
36
- printHelp();
37
- process.exit(1);
89
+ console.log('');
90
+ console.log(` 📦 New version available: ${current} → \x1b[32m${latest}\x1b[0m`);
91
+ rl.question(' Do you want to update now? (Y/n) ', (answer) => {
92
+ rl.close();
93
+ const normalized = answer.trim().toLowerCase();
94
+ resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
95
+ });
96
+ });
38
97
  }
39
98
 
40
- const rootDir = path.resolve(__dirname, '..');
41
- const localGeneratorEntry = path.join(
42
- rootDir,
43
- 'generators',
44
- generator,
45
- 'index.js'
46
- );
99
+ /**
100
+ * 执行更新
101
+ * @returns {boolean} 是否更新成功
102
+ */
103
+ function performUpdate() {
104
+ console.log('');
105
+ console.log(' ⏳ Updating mico-cli...');
106
+
107
+ // 检测包管理器
108
+ const npmUserAgent = process.env.npm_config_user_agent || '';
109
+ let pm = 'npm';
110
+ if (npmUserAgent.includes('pnpm')) {
111
+ pm = 'pnpm';
112
+ } else if (npmUserAgent.includes('yarn')) {
113
+ pm = 'yarn';
114
+ }
115
+
116
+ // 执行全局安装
117
+ const installCmd = pm === 'yarn' ? 'yarn' : pm;
118
+ const installArgs =
119
+ pm === 'yarn'
120
+ ? ['global', 'add', pkg.name]
121
+ : ['install', '-g', pkg.name];
122
+
123
+ const result = spawnSync(installCmd, installArgs, {
124
+ stdio: 'inherit',
125
+ shell: process.platform === 'win32'
126
+ });
127
+
128
+ if (result.status === 0) {
129
+ console.log('');
130
+ console.log(' ✅ Update successful! Please re-run your command.');
131
+ return true;
132
+ } else {
133
+ console.log('');
134
+ console.log(' ❌ Update failed. Please try manually:');
135
+ console.log(` ${installCmd} ${installArgs.join(' ')}`);
136
+ return false;
137
+ }
138
+ }
47
139
 
48
- let localNamespace = 'generator';
49
- try {
50
- const pkg = JSON.parse(
51
- fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8')
140
+ /**
141
+ * 运行 Yeoman 生成器
142
+ */
143
+ function runGenerator(generator, rest, passthroughArgs) {
144
+ const localGeneratorEntry = path.join(
145
+ rootDir,
146
+ 'generators',
147
+ generator,
148
+ 'index.js'
52
149
  );
150
+
53
151
  const pkgName = typeof pkg.name === 'string' ? pkg.name : '';
54
- localNamespace = pkgName.replace(/^generator-/, '') || pkgName || localNamespace;
55
- } catch {
56
- // Fall back to a generic namespace
57
- }
152
+ const localNamespace =
153
+ pkgName.replace(/^generator-/, '') || pkgName || 'generator';
154
+
155
+ const yoGenerator = fs.existsSync(localGeneratorEntry)
156
+ ? `${localNamespace}:${generator}`
157
+ : generator;
158
+
159
+ const yoArgs = [yoGenerator, ...rest];
160
+
161
+ if (passthroughArgs.length > 0) {
162
+ yoArgs.push('--', ...passthroughArgs);
163
+ }
164
+
165
+ const child = spawn('yo', yoArgs, { stdio: 'inherit' });
58
166
 
59
- const yoGenerator = fs.existsSync(localGeneratorEntry)
60
- ? `${localNamespace}:${generator}`
61
- : generator;
62
- const yoArgs = [yoGenerator, ...rest];
167
+ child.on('error', (error) => {
168
+ if (error && error.code === 'ENOENT') {
169
+ console.error('Cannot find "yo". Install it with: npm install -g yo');
170
+ } else {
171
+ console.error(error);
172
+ }
173
+ process.exit(1);
174
+ });
63
175
 
64
- if (passthroughArgs.length > 0) {
65
- yoArgs.push('--', ...passthroughArgs);
176
+ child.on('exit', (code) => {
177
+ process.exit(typeof code === 'number' ? code : 1);
178
+ });
66
179
  }
67
180
 
68
- const child = spawn('yo', yoArgs, { stdio: 'inherit' });
181
+ /**
182
+ * 主函数
183
+ */
184
+ async function main() {
185
+ const args = process.argv.slice(2);
69
186
 
70
- child.on('error', (error) => {
71
- if (error && error.code === 'ENOENT') {
72
- console.error('Cannot find "yo". Install it with: npm install -g yo');
73
- } else {
74
- console.error(error);
187
+ // 解析参数
188
+ const doubleDashIndex = args.indexOf('--');
189
+ const passthroughArgs =
190
+ doubleDashIndex === -1 ? [] : args.slice(doubleDashIndex + 1);
191
+ const mainArgs =
192
+ doubleDashIndex === -1 ? args : args.slice(0, doubleDashIndex);
193
+
194
+ // 检查特殊标志
195
+ const hasHelp = mainArgs.includes('--help') || mainArgs.includes('-h');
196
+ const hasVersion = mainArgs.includes('--version') || mainArgs.includes('-v');
197
+ const skipUpdateCheck = mainArgs.includes('--no-update-check');
198
+
199
+ // 过滤掉标志参数
200
+ const filteredArgs = mainArgs.filter(
201
+ (arg) =>
202
+ arg !== '--help' &&
203
+ arg !== '-h' &&
204
+ arg !== '--version' &&
205
+ arg !== '-v' &&
206
+ arg !== '--no-update-check'
207
+ );
208
+
209
+ // 处理 --version
210
+ if (hasVersion) {
211
+ printVersion();
212
+ process.exit(0);
213
+ }
214
+
215
+ // 处理 --help 或无参数
216
+ if (hasHelp || filteredArgs.length === 0) {
217
+ printHelp();
218
+ process.exit(0);
219
+ }
220
+
221
+ const [command, ...rest] = filteredArgs;
222
+
223
+ // 处理 update 命令
224
+ if (command === 'update') {
225
+ const updateInfo = await checkForUpdate();
226
+ if (updateInfo && updateInfo.hasUpdate) {
227
+ performUpdate();
228
+ } else if (updateInfo) {
229
+ console.log(` ✅ You are using the latest version (v${updateInfo.current})`);
230
+ } else {
231
+ console.log(' ⚠️ Could not check for updates');
232
+ }
233
+ process.exit(0);
75
234
  }
235
+
236
+ // 处理 create 命令
237
+ if (command === 'create') {
238
+ const generator = rest[0];
239
+ if (!generator) {
240
+ printHelp();
241
+ process.exit(1);
242
+ }
243
+
244
+ // 检查更新(除非跳过)
245
+ if (!skipUpdateCheck) {
246
+ const updateInfo = await checkForUpdate();
247
+ if (updateInfo && updateInfo.hasUpdate) {
248
+ const shouldUpdate = await askForUpdate(
249
+ updateInfo.current,
250
+ updateInfo.latest
251
+ );
252
+ if (shouldUpdate) {
253
+ const success = performUpdate();
254
+ if (success) {
255
+ process.exit(0);
256
+ }
257
+ // 更新失败则继续执行
258
+ }
259
+ console.log('');
260
+ }
261
+ }
262
+
263
+ // 运行生成器
264
+ runGenerator(generator, rest.slice(1), passthroughArgs);
265
+ return;
266
+ }
267
+
268
+ // 未知命令
269
+ console.error(`Unknown command: ${command}`);
270
+ printHelp();
76
271
  process.exit(1);
77
- });
272
+ }
78
273
 
79
- child.on('exit', (code) => {
80
- process.exit(typeof code === 'number' ? code : 1);
274
+ main().catch((err) => {
275
+ console.error(err);
276
+ process.exit(1);
81
277
  });
@@ -48,6 +48,39 @@ const TEMPLATE_EXTENSIONS = new Set([
48
48
  ]);
49
49
 
50
50
  module.exports = class extends Generator {
51
+ /**
52
+ * 初始化阶段:检查运行环境
53
+ */
54
+ initializing() {
55
+ // 使用当前工作目录,而不是 Yeoman 的 destinationRoot
56
+ this.monorepoRoot = process.cwd();
57
+ const appsDir = path.join(this.monorepoRoot, 'apps');
58
+ const workspaceFile = path.join(this.monorepoRoot, 'pnpm-workspace.yaml');
59
+
60
+ // 检查是否在 monorepo 中
61
+ if (!fs.existsSync(appsDir)) {
62
+ this.log('');
63
+ this.log('❌ Error: apps directory not found.');
64
+ this.log('');
65
+ this.log(' This generator must be run from a monorepo root that contains an "apps" directory.');
66
+ this.log(` Current directory: ${this.monorepoRoot}`);
67
+ this.log('');
68
+ this.log(' Usage:');
69
+ this.log(' cd /path/to/your-monorepo');
70
+ this.log(' mico create subapp-react');
71
+ this.log('');
72
+ process.exit(1);
73
+ }
74
+
75
+ // 警告:检查是否是 pnpm workspace
76
+ if (!fs.existsSync(workspaceFile)) {
77
+ this.log('');
78
+ this.log('⚠️ Warning: pnpm-workspace.yaml not found.');
79
+ this.log(' Make sure you are in the correct monorepo.');
80
+ this.log('');
81
+ }
82
+ }
83
+
51
84
  async prompting() {
52
85
  this.answers = await this.prompt([
53
86
  {
@@ -59,12 +92,16 @@ module.exports = class extends Generator {
59
92
  validate: (input) => {
60
93
  const value = toKebab(input);
61
94
  if (!value) return 'App name is required';
95
+ // 检查目标目录是否已存在
96
+ const destDir = path.join(this.monorepoRoot, 'apps', value);
97
+ if (fs.existsSync(destDir)) {
98
+ return `Target already exists: apps/${value}`;
99
+ }
62
100
  return true;
63
101
  }
64
102
  }
65
103
  ]);
66
104
 
67
- this.monorepoRoot = this.destinationRoot();
68
105
  this.appName = toKebab(this.answers.appName);
69
106
  this.appNamePascal = toPascal(this.appName);
70
107
  this.templateDir = this.templatePath('homepage');
@@ -72,28 +109,6 @@ module.exports = class extends Generator {
72
109
  }
73
110
 
74
111
  writing() {
75
- const appsDir = path.join(this.monorepoRoot, 'apps');
76
- const workspaceFile = path.join(this.monorepoRoot, 'pnpm-workspace.yaml');
77
-
78
- // 检查是否在 monorepo 中
79
- if (!fs.existsSync(appsDir)) {
80
- this.env.error(
81
- `apps directory not found in ${this.monorepoRoot}. Run this generator from the monorepo root.`
82
- );
83
- return;
84
- }
85
-
86
- // 警告:检查是否是 pnpm workspace
87
- if (!fs.existsSync(workspaceFile)) {
88
- this.log.warning(
89
- 'pnpm-workspace.yaml not found. Make sure you are in the correct monorepo.'
90
- );
91
- }
92
-
93
- if (fs.existsSync(this.destDir)) {
94
- this.env.error(`Target already exists: ${this.destDir}`);
95
- return;
96
- }
97
112
 
98
113
  // 模板数据
99
114
  const templateData = {
package/lib/utils.js ADDED
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 转换为 kebab-case
5
+ * @param {string} input
6
+ * @returns {string}
7
+ */
8
+ function toKebab(input) {
9
+ return String(input)
10
+ .trim()
11
+ .replace(/[\s_]+/g, '-')
12
+ .replace(/[^a-zA-Z0-9-]/g, '-')
13
+ .replace(/-+/g, '-')
14
+ .replace(/^-|-$/g, '')
15
+ .toLowerCase();
16
+ }
17
+
18
+ /**
19
+ * 转换为 PascalCase
20
+ * @param {string} input
21
+ * @returns {string}
22
+ */
23
+ function toPascal(input) {
24
+ return toKebab(input)
25
+ .split('-')
26
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
27
+ .join('');
28
+ }
29
+
30
+ /**
31
+ * 转换为 camelCase
32
+ * @param {string} input
33
+ * @returns {string}
34
+ */
35
+ function toCamel(input) {
36
+ const pascal = toPascal(input);
37
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
38
+ }
39
+
40
+ module.exports = {
41
+ toKebab,
42
+ toPascal,
43
+ toCamel
44
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-mico-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": [
6
6
  "yeoman-generator",
@@ -8,12 +8,13 @@
8
8
  "cli"
9
9
  ],
10
10
  "license": "MIT",
11
- "main": "generators/app/index.js",
11
+ "main": "generators/subapp-react/index.js",
12
12
  "bin": {
13
13
  "mico": "bin/mico.js"
14
14
  },
15
15
  "files": [
16
16
  "bin",
17
+ "lib",
17
18
  "generators"
18
19
  ],
19
20
  "scripts": {
@@ -23,6 +24,8 @@
23
24
  "node": ">=18"
24
25
  },
25
26
  "dependencies": {
27
+ "semver": "^7.6.3",
28
+ "update-notifier": "^7.3.1",
26
29
  "yeoman-generator": "^5.9.0"
27
30
  }
28
31
  }