generator-mico-cli 0.1.0 → 0.1.2

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 (3) hide show
  1. package/bin/mico.js +248 -54
  2. package/lib/utils.js +44 -0
  3. package/package.json +3 -1
package/bin/mico.js CHANGED
@@ -1,81 +1,275 @@
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 notifier = updateNotifier({
56
+ pkg,
57
+ updateCheckInterval: 0 // 每次都检查
58
+ });
59
+
60
+ // 等待检查完成
61
+ await notifier.fetchInfo();
62
+
63
+ if (notifier.update && notifier.update.latest !== pkg.version) {
64
+ return {
65
+ current: pkg.version,
66
+ latest: notifier.update.latest,
67
+ hasUpdate: true
68
+ };
69
+ }
70
+ return { current: pkg.version, latest: pkg.version, hasUpdate: false };
71
+ } catch {
72
+ return null;
73
+ }
31
74
  }
32
75
 
33
- const [command, generator, ...rest] = mainArgs;
76
+ /**
77
+ * 询问用户是否更新
78
+ * @returns {Promise<boolean>}
79
+ */
80
+ function askForUpdate(current, latest) {
81
+ return new Promise((resolve) => {
82
+ const rl = readline.createInterface({
83
+ input: process.stdin,
84
+ output: process.stdout
85
+ });
34
86
 
35
- if (command !== 'create' || !generator) {
36
- printHelp();
37
- process.exit(1);
87
+ console.log('');
88
+ console.log(` 📦 New version available: ${current} → \x1b[32m${latest}\x1b[0m`);
89
+ rl.question(' Do you want to update now? (Y/n) ', (answer) => {
90
+ rl.close();
91
+ const normalized = answer.trim().toLowerCase();
92
+ resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
93
+ });
94
+ });
38
95
  }
39
96
 
40
- const rootDir = path.resolve(__dirname, '..');
41
- const localGeneratorEntry = path.join(
42
- rootDir,
43
- 'generators',
44
- generator,
45
- 'index.js'
46
- );
97
+ /**
98
+ * 执行更新
99
+ * @returns {boolean} 是否更新成功
100
+ */
101
+ function performUpdate() {
102
+ console.log('');
103
+ console.log(' ⏳ Updating mico-cli...');
104
+
105
+ // 检测包管理器
106
+ const npmUserAgent = process.env.npm_config_user_agent || '';
107
+ let pm = 'npm';
108
+ if (npmUserAgent.includes('pnpm')) {
109
+ pm = 'pnpm';
110
+ } else if (npmUserAgent.includes('yarn')) {
111
+ pm = 'yarn';
112
+ }
113
+
114
+ // 执行全局安装
115
+ const installCmd = pm === 'yarn' ? 'yarn' : pm;
116
+ const installArgs =
117
+ pm === 'yarn'
118
+ ? ['global', 'add', pkg.name]
119
+ : ['install', '-g', pkg.name];
120
+
121
+ const result = spawnSync(installCmd, installArgs, {
122
+ stdio: 'inherit',
123
+ shell: process.platform === 'win32'
124
+ });
125
+
126
+ if (result.status === 0) {
127
+ console.log('');
128
+ console.log(' ✅ Update successful! Please re-run your command.');
129
+ return true;
130
+ } else {
131
+ console.log('');
132
+ console.log(' ❌ Update failed. Please try manually:');
133
+ console.log(` ${installCmd} ${installArgs.join(' ')}`);
134
+ return false;
135
+ }
136
+ }
47
137
 
48
- let localNamespace = 'generator';
49
- try {
50
- const pkg = JSON.parse(
51
- fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8')
138
+ /**
139
+ * 运行 Yeoman 生成器
140
+ */
141
+ function runGenerator(generator, rest, passthroughArgs) {
142
+ const localGeneratorEntry = path.join(
143
+ rootDir,
144
+ 'generators',
145
+ generator,
146
+ 'index.js'
52
147
  );
148
+
53
149
  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
- }
150
+ const localNamespace =
151
+ pkgName.replace(/^generator-/, '') || pkgName || 'generator';
152
+
153
+ const yoGenerator = fs.existsSync(localGeneratorEntry)
154
+ ? `${localNamespace}:${generator}`
155
+ : generator;
156
+
157
+ const yoArgs = [yoGenerator, ...rest];
158
+
159
+ if (passthroughArgs.length > 0) {
160
+ yoArgs.push('--', ...passthroughArgs);
161
+ }
162
+
163
+ const child = spawn('yo', yoArgs, { stdio: 'inherit' });
58
164
 
59
- const yoGenerator = fs.existsSync(localGeneratorEntry)
60
- ? `${localNamespace}:${generator}`
61
- : generator;
62
- const yoArgs = [yoGenerator, ...rest];
165
+ child.on('error', (error) => {
166
+ if (error && error.code === 'ENOENT') {
167
+ console.error('Cannot find "yo". Install it with: npm install -g yo');
168
+ } else {
169
+ console.error(error);
170
+ }
171
+ process.exit(1);
172
+ });
63
173
 
64
- if (passthroughArgs.length > 0) {
65
- yoArgs.push('--', ...passthroughArgs);
174
+ child.on('exit', (code) => {
175
+ process.exit(typeof code === 'number' ? code : 1);
176
+ });
66
177
  }
67
178
 
68
- const child = spawn('yo', yoArgs, { stdio: 'inherit' });
179
+ /**
180
+ * 主函数
181
+ */
182
+ async function main() {
183
+ const args = process.argv.slice(2);
69
184
 
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);
185
+ // 解析参数
186
+ const doubleDashIndex = args.indexOf('--');
187
+ const passthroughArgs =
188
+ doubleDashIndex === -1 ? [] : args.slice(doubleDashIndex + 1);
189
+ const mainArgs =
190
+ doubleDashIndex === -1 ? args : args.slice(0, doubleDashIndex);
191
+
192
+ // 检查特殊标志
193
+ const hasHelp = mainArgs.includes('--help') || mainArgs.includes('-h');
194
+ const hasVersion = mainArgs.includes('--version') || mainArgs.includes('-v');
195
+ const skipUpdateCheck = mainArgs.includes('--no-update-check');
196
+
197
+ // 过滤掉标志参数
198
+ const filteredArgs = mainArgs.filter(
199
+ (arg) =>
200
+ arg !== '--help' &&
201
+ arg !== '-h' &&
202
+ arg !== '--version' &&
203
+ arg !== '-v' &&
204
+ arg !== '--no-update-check'
205
+ );
206
+
207
+ // 处理 --version
208
+ if (hasVersion) {
209
+ printVersion();
210
+ process.exit(0);
211
+ }
212
+
213
+ // 处理 --help 或无参数
214
+ if (hasHelp || filteredArgs.length === 0) {
215
+ printHelp();
216
+ process.exit(0);
217
+ }
218
+
219
+ const [command, ...rest] = filteredArgs;
220
+
221
+ // 处理 update 命令
222
+ if (command === 'update') {
223
+ const updateInfo = await checkForUpdate();
224
+ if (updateInfo && updateInfo.hasUpdate) {
225
+ performUpdate();
226
+ } else if (updateInfo) {
227
+ console.log(` ✅ You are using the latest version (v${updateInfo.current})`);
228
+ } else {
229
+ console.log(' ⚠️ Could not check for updates');
230
+ }
231
+ process.exit(0);
75
232
  }
233
+
234
+ // 处理 create 命令
235
+ if (command === 'create') {
236
+ const generator = rest[0];
237
+ if (!generator) {
238
+ printHelp();
239
+ process.exit(1);
240
+ }
241
+
242
+ // 检查更新(除非跳过)
243
+ if (!skipUpdateCheck) {
244
+ const updateInfo = await checkForUpdate();
245
+ if (updateInfo && updateInfo.hasUpdate) {
246
+ const shouldUpdate = await askForUpdate(
247
+ updateInfo.current,
248
+ updateInfo.latest
249
+ );
250
+ if (shouldUpdate) {
251
+ const success = performUpdate();
252
+ if (success) {
253
+ process.exit(0);
254
+ }
255
+ // 更新失败则继续执行
256
+ }
257
+ console.log('');
258
+ }
259
+ }
260
+
261
+ // 运行生成器
262
+ runGenerator(generator, rest.slice(1), passthroughArgs);
263
+ return;
264
+ }
265
+
266
+ // 未知命令
267
+ console.error(`Unknown command: ${command}`);
268
+ printHelp();
76
269
  process.exit(1);
77
- });
270
+ }
78
271
 
79
- child.on('exit', (code) => {
80
- process.exit(typeof code === 'number' ? code : 1);
272
+ main().catch((err) => {
273
+ console.error(err);
274
+ process.exit(1);
81
275
  });
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.2",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": [
6
6
  "yeoman-generator",
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "files": [
16
16
  "bin",
17
+ "lib",
17
18
  "generators"
18
19
  ],
19
20
  "scripts": {
@@ -23,6 +24,7 @@
23
24
  "node": ">=18"
24
25
  },
25
26
  "dependencies": {
27
+ "update-notifier": "^7.3.1",
26
28
  "yeoman-generator": "^5.9.0"
27
29
  }
28
30
  }