flu-cli 2.0.6 → 2.1.0

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 (45) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +17 -4
  3. package/config/dev.config.js +11 -11
  4. package/config/templates.js +10 -10
  5. package/index.js +554 -102
  6. package/lib/commands/add.js +365 -266
  7. package/lib/commands/assets.js +77 -78
  8. package/lib/commands/cache.js +29 -52
  9. package/lib/commands/completion.js +13 -11
  10. package/lib/commands/config.js +150 -44
  11. package/lib/commands/init-ai-base.js +89 -0
  12. package/lib/commands/newClack.js +269 -178
  13. package/lib/commands/snippets.js +58 -43
  14. package/lib/commands/template.js +98 -58
  15. package/lib/commands/templates.js +101 -57
  16. package/lib/commands/upload.js +313 -0
  17. package/lib/commands/vnext-options.js +206 -0
  18. package/lib/generators/model_generator.js +91 -88
  19. package/lib/generators/page_generator.js +100 -93
  20. package/lib/generators/service_generator.js +44 -39
  21. package/lib/generators/viewmodel_generator.js +25 -29
  22. package/lib/generators/widget_generator.js +30 -35
  23. package/lib/templates/templateCopier.js +14 -15
  24. package/lib/templates/templateManager.js +22 -21
  25. package/lib/utils/config.js +37 -20
  26. package/lib/utils/flutterHelper.js +2 -2
  27. package/lib/utils/i18n.js +3 -3
  28. package/lib/utils/index_updater.js +22 -23
  29. package/lib/utils/json-output.js +59 -0
  30. package/lib/utils/logger.js +17 -17
  31. package/lib/utils/project_detector.js +66 -66
  32. package/lib/utils/snippet_loader.js +21 -19
  33. package/lib/utils/string_helper.js +13 -13
  34. package/lib/utils/templateSelectorEnquirer.js +94 -108
  35. package/locales/en-US.json +1 -1
  36. package/locales/zh-CN.json +2 -2
  37. package/package.json +60 -57
  38. package/scripts/smoke-vnext-generate.mjs +1934 -0
  39. package/scripts/smoke-vnext-params.mjs +92 -0
  40. package/CLI.md +0 -513
  41. package/release.sh +0 -529
  42. package/scripts/e2e-state-tests.js +0 -116
  43. package/scripts/sync-base-to-templates.js +0 -108
  44. package/scripts/workspace-clone-all.sh +0 -101
  45. package/scripts/workspace-status-all.sh +0 -112
@@ -4,112 +4,109 @@
4
4
  * 该文件从 flu-cli-core 迁移回来,以保持 core 的 Headless 纯净。
5
5
  */
6
6
 
7
- import enquirer from 'enquirer';
8
- import chalk from 'chalk';
9
- import { existsSync } from 'fs';
10
- import { basename } from 'path';
11
- import { BUILTIN_TEMPLATES, ConfigManager } from 'flu-cli-core';
7
+ import enquirer from 'enquirer'
8
+ import chalk from 'chalk'
9
+ import { existsSync } from 'fs'
10
+ import { basename } from 'path'
11
+ import { BUILTIN_TEMPLATES, ConfigManager } from 'flu-cli-core'
12
12
 
13
- const { Select } = enquirer;
13
+ const { Select } = enquirer
14
14
 
15
15
  /**
16
16
  * 选择模板(带实时预览 - Enquirer 版本)
17
17
  */
18
- export async function selectTemplateWithEnquirer () {
19
- const builtinTemplates = Object.values(BUILTIN_TEMPLATES);
20
- const configManager = ConfigManager.getInstance();
21
- const customTemplates = configManager.getTemplates();
22
- const defaultTpl = configManager.getDefaultTemplate();
18
+ export async function selectTemplateWithEnquirer() {
19
+ const builtinTemplates = Object.values(BUILTIN_TEMPLATES)
20
+ const configManager = ConfigManager.getInstance()
21
+ const customTemplates = configManager.getTemplates()
22
+ const defaultTpl = configManager.getDefaultTemplate()
23
23
 
24
24
  // 创建自定义的 Select prompt
25
25
  class TemplateSelect extends Select {
26
26
  constructor(options) {
27
- super(options);
27
+ super(options)
28
28
  }
29
29
 
30
- async render () {
31
- const { size } = this.state;
32
- const index = this.index;
30
+ async render() {
31
+ const { size } = this.state
32
+ const index = this.index
33
33
  // 选择项与模板索引可能不一致,安全获取
34
- const choice = this.choices[index];
35
- const isBuiltin = choice?.name?.startsWith('builtin:');
36
- const builtinKey = isBuiltin ? choice.name.split(':')[1] : '';
37
- const template = isBuiltin ? BUILTIN_TEMPLATES[builtinKey] : null;
34
+ const choice = this.choices[index]
35
+ const isBuiltin = choice?.name?.startsWith('builtin:')
36
+ const builtinKey = isBuiltin ? choice.name.split(':')[1] : ''
37
+ const template = isBuiltin ? BUILTIN_TEMPLATES[builtinKey] : null
38
38
 
39
- let prompt = '';
40
- let prefix = await this.prefix();
41
- let separator = await this.separator();
42
- let message = await this.message();
39
+ let prompt = ''
40
+ let prefix = await this.prefix()
41
+ let separator = await this.separator()
42
+ let message = await this.message()
43
43
 
44
44
  if (this.options.promptLine !== false) {
45
- prompt = [prefix, message, separator].filter(Boolean).join(' ');
46
- this.state.prompt = prompt;
45
+ prompt = [prefix, message, separator].filter(Boolean).join(' ')
46
+ this.state.prompt = prompt
47
47
  }
48
48
 
49
- let output = prompt + '\n\n';
49
+ let output = prompt + '\n\n'
50
50
 
51
51
  // 显示选项列表
52
52
  for (let i = 0; i < this.choices.length; i++) {
53
- const choice = this.choices[i];
54
- const isSelected = i === index;
55
- const prefixStr = isSelected ? chalk.cyan('❯') : ' ';
56
- const style = isSelected ? chalk.cyan : chalk.gray;
57
- output += `${prefixStr} ${style(choice.message)}\n`;
53
+ const choice = this.choices[i]
54
+ const isSelected = i === index
55
+ const prefixStr = isSelected ? chalk.cyan('❯') : ' '
56
+ const style = isSelected ? chalk.cyan : chalk.gray
57
+ output += `${prefixStr} ${style(choice.message)}\n`
58
58
  if (choice.hint) {
59
- output += ` ${chalk.gray(choice.hint)}\n`;
59
+ output += ` ${chalk.gray(choice.hint)}\n`
60
60
  }
61
61
  }
62
62
 
63
63
  // 显示当前模板的结构预览(仅内置模板)
64
- output += '\n' + chalk.cyan('─'.repeat(70)) + '\n';
64
+ output += '\n' + chalk.cyan('─'.repeat(70)) + '\n'
65
65
  if (template) {
66
- output += chalk.yellow('📁 项目结构预览:\n\n');
67
- const structureLines = template.structure.trim().split('\n');
66
+ output += chalk.yellow('📁 项目结构预览:\n\n')
67
+ const structureLines = template.structure.trim().split('\n')
68
68
  structureLines.forEach((line) => {
69
- output += chalk.gray(line) + '\n';
70
- });
71
- output += '\n';
72
- output += chalk.cyan('✨ 特性: ') + chalk.gray(template.features.join(', ')) + '\n';
73
- output += chalk.cyan('📊 适用: ') + chalk.gray(`${template.teamSize}, ${template.codeSize}`) + '\n';
69
+ output += chalk.gray(line) + '\n'
70
+ })
71
+ output += '\n'
72
+ output += chalk.cyan('✨ 特性: ') + chalk.gray(template.features.join(', ')) + '\n'
73
+ output +=
74
+ chalk.cyan('📊 适用: ') + chalk.gray(`${template.teamSize}, ${template.codeSize}`) + '\n'
74
75
  } else {
75
- output += chalk.yellow('自定义模板:不提供结构预览') + '\n';
76
+ output += chalk.yellow('自定义模板:不提供结构预览') + '\n'
76
77
  }
77
- output += chalk.cyan('─'.repeat(70));
78
+ output += chalk.cyan('─'.repeat(70))
78
79
 
79
- this.clear(size);
80
- this.write(output);
80
+ this.clear(size)
81
+ this.write(output)
81
82
  }
82
83
  }
83
84
 
84
85
  // 构建选择项:内置模板 + 自定义模板 + 新增自定义入口
85
- const builtinChoices = builtinTemplates.map(t => ({
86
+ const builtinChoices = builtinTemplates.map((t) => ({
86
87
  name: `builtin:${t.name.toLowerCase()}`,
87
88
  message: `${t.displayName}`,
88
- }));
89
- const customChoices = customTemplates.map(ct => ({
89
+ }))
90
+ const customChoices = customTemplates.map((ct) => ({
90
91
  name: `custom:${ct.id}`,
91
92
  message: `自定义:${ct.name}`,
92
93
  hint: ct.type === 'git' ? ct.url : `本地: ${ct.path}`,
93
- }));
94
+ }))
94
95
  const addCustomChoice = {
95
96
  name: '__add_custom__',
96
97
  message: chalk.yellow('➕ 新增自定义模板(本地或 Git)'),
97
- };
98
+ }
98
99
 
99
- const allChoices = [...builtinChoices, ...customChoices, addCustomChoice];
100
+ const allChoices = [...builtinChoices, ...customChoices, addCustomChoice]
100
101
 
101
102
  // 计算默认选中索引
102
- let initialIndex = 0;
103
+ let initialIndex = 0
103
104
  if (defaultTpl && defaultTpl.type === 'builtin') {
104
- const idx = allChoices.findIndex(
105
- c => c.name === `builtin:${defaultTpl.idOrName}`
106
- );
107
- if (idx >= 0) initialIndex = idx;
105
+ const idx = allChoices.findIndex((c) => c.name === `builtin:${defaultTpl.idOrName}`)
106
+ if (idx >= 0) initialIndex = idx
108
107
  } else if (defaultTpl && defaultTpl.type === 'custom') {
109
- const idx = allChoices.findIndex(
110
- c => c.name === `custom:${defaultTpl.idOrName}`
111
- );
112
- if (idx >= 0) initialIndex = idx;
108
+ const idx = allChoices.findIndex((c) => c.name === `custom:${defaultTpl.idOrName}`)
109
+ if (idx >= 0) initialIndex = idx
113
110
  }
114
111
 
115
112
  const templatePrompt = new TemplateSelect({
@@ -117,13 +114,13 @@ export async function selectTemplateWithEnquirer () {
117
114
  message: '选择项目模板(使用 ↑↓ 键切换,实时查看结构)',
118
115
  choices: allChoices,
119
116
  initial: initialIndex,
120
- });
117
+ })
121
118
 
122
119
  try {
123
- const answer = await templatePrompt.run();
120
+ const answer = await templatePrompt.run()
124
121
 
125
122
  // 清除屏幕上的大量输出,只保留简洁的确认信息
126
- console.clear();
123
+ console.clear()
127
124
 
128
125
  // 显示选中确认
129
126
  if (answer === '__add_custom__') {
@@ -135,8 +132,8 @@ export async function selectTemplateWithEnquirer () {
135
132
  { name: 'local', message: '本地目录' },
136
133
  { name: 'git', message: 'Git 仓库' },
137
134
  ],
138
- });
139
- const sourceType = await typePrompt.run();
135
+ })
136
+ const sourceType = await typePrompt.run()
140
137
 
141
138
  // 输入路径或 URL
142
139
  const inputPrompt = new enquirer.Input({
@@ -145,86 +142,75 @@ export async function selectTemplateWithEnquirer () {
145
142
  sourceType === 'git'
146
143
  ? '请输入 Git 仓库地址(如 https://... 或 git@...)'
147
144
  : '请输入本地模板目录绝对路径',
148
- });
149
- const pathOrUrl = await inputPrompt.run();
145
+ })
146
+ const pathOrUrl = await inputPrompt.run()
150
147
 
151
148
  // 分支(Git)
152
- let branch = 'main';
149
+ let branch = 'main'
153
150
  if (sourceType === 'git') {
154
151
  const branchPrompt = new enquirer.Input({
155
152
  name: 'branch',
156
153
  message: '请输入分支名称(默认 main)',
157
154
  initial: 'main',
158
- });
159
- branch = await branchPrompt.run();
160
- if (!branch) branch = 'main';
155
+ })
156
+ branch = await branchPrompt.run()
157
+ if (!branch) branch = 'main'
161
158
  }
162
159
 
163
160
  // 基本校验
164
161
  if (sourceType === 'local' && !existsSync(pathOrUrl)) {
165
- console.log(chalk.red('本地目录不存在:'), pathOrUrl);
166
- process.exit(1);
162
+ console.log(chalk.red('本地目录不存在:'), pathOrUrl)
163
+ process.exit(1)
167
164
  }
168
165
 
169
166
  // 生成 id 与名称
170
167
  const nameGuess =
171
168
  sourceType === 'git'
172
169
  ? pathOrUrl.split('/').pop()?.replace('.git', '') || 'custom'
173
- : basename(pathOrUrl);
174
- const id = `${nameGuess
175
- .replace(/\W+/g, '-')
176
- .toLowerCase()}-${Date.now()}`;
170
+ : basename(pathOrUrl)
171
+ const id = `${nameGuess.replace(/\W+/g, '-').toLowerCase()}-${Date.now()}`
177
172
 
178
173
  const newTemplate = {
179
174
  id,
180
175
  name: nameGuess,
181
176
  type: sourceType,
182
177
  ...(sourceType === 'git' ? { url: pathOrUrl, branch } : { path: pathOrUrl }),
183
- };
178
+ }
184
179
 
185
- configManager.addTemplate(newTemplate);
180
+ configManager.addTemplate(newTemplate)
186
181
 
187
- console.log(
188
- chalk.green(' 已保存自定义模板:'),
189
- chalk.cyan.bold(newTemplate.name)
190
- );
191
- configManager.setDefaultTemplate('custom', newTemplate.id);
192
- return { kind: 'custom', id: newTemplate.id };
182
+ console.log(chalk.green('✓ 已保存自定义模板:'), chalk.cyan.bold(newTemplate.name))
183
+ configManager.setDefaultTemplate('custom', newTemplate.id)
184
+ return { kind: 'custom', id: newTemplate.id }
193
185
  }
194
186
 
195
187
  if (answer.startsWith('builtin:')) {
196
- const name = answer.split(':')[1];
197
- const selectedTemplate = BUILTIN_TEMPLATES[name];
198
- console.log(
199
- chalk.green(' 已选择:'),
200
- chalk.cyan.bold(selectedTemplate.displayName)
201
- );
202
- configManager.setDefaultTemplate('builtin', name);
203
- return { kind: 'builtin', name };
188
+ const name = answer.split(':')[1]
189
+ const selectedTemplate = BUILTIN_TEMPLATES[name]
190
+ console.log(chalk.green('✓ 已选择:'), chalk.cyan.bold(selectedTemplate.displayName))
191
+ configManager.setDefaultTemplate('builtin', name)
192
+ return { kind: 'builtin', name }
204
193
  }
205
194
 
206
195
  if (answer.startsWith('custom:')) {
207
- const id = answer.split(':')[1];
208
- const ct = customTemplates.find(t => t.id === id);
209
- console.log(
210
- chalk.green(' 已选择自定义:'),
211
- chalk.cyan.bold(ct?.name || id)
212
- );
213
- configManager.setDefaultTemplate('custom', id);
214
- return { kind: 'custom', id };
196
+ const id = answer.split(':')[1]
197
+ const ct = customTemplates.find((t) => t.id === id)
198
+ console.log(chalk.green('✓ 已选择自定义:'), chalk.cyan.bold(ct?.name || id))
199
+ configManager.setDefaultTemplate('custom', id)
200
+ return { kind: 'custom', id }
215
201
  }
216
202
 
217
203
  // 兜底(保持兼容)
218
- const nameFallback = answer;
219
- configManager.setDefaultTemplate('builtin', nameFallback);
220
- const selectedTemplate = BUILTIN_TEMPLATES[nameFallback];
204
+ const nameFallback = answer
205
+ configManager.setDefaultTemplate('builtin', nameFallback)
206
+ const selectedTemplate = BUILTIN_TEMPLATES[nameFallback]
221
207
  console.log(
222
208
  chalk.green('✓ 已选择:'),
223
- chalk.cyan.bold(selectedTemplate?.displayName || nameFallback)
224
- );
225
- return { kind: 'builtin', name: nameFallback };
209
+ chalk.cyan.bold(selectedTemplate?.displayName || nameFallback),
210
+ )
211
+ return { kind: 'builtin', name: nameFallback }
226
212
  } catch (error) {
227
- console.log(chalk.yellow('\n操作已取消'));
228
- process.exit(0);
213
+ console.log(chalk.yellow('\n操作已取消'))
214
+ process.exit(0)
229
215
  }
230
216
  }
@@ -56,4 +56,4 @@
56
56
  "snippets.sync_success": "✅ Code snippets synced successfully!",
57
57
  "snippets.detect_config": "ℹ️ Project config detected, filtering snippets...",
58
58
  "snippets.applied_filter": "Architecture-aware filtering applied (BasePage: {basePage}, ViewModel: {viewModel})"
59
- }
59
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "cli.description": "🚀 Flutter MVVM 脚手架工具",
2
+ "cli.description": "🚀 Flutter 全流程效率工具链",
3
3
  "cmd.new.desc": "创建 Flutter 项目(实时模板预览)",
4
4
  "cmd.new.opt.template": "模板类型: lite, modular, clean",
5
5
  "cmd.new.opt.state": "状态管理器: default, provider, getx",
@@ -56,4 +56,4 @@
56
56
  "snippets.sync_success": "✅ 代码片段同步成功!",
57
57
  "snippets.detect_config": "ℹ️ 检测到项目配置,正在按需过滤代码片段...",
58
58
  "snippets.applied_filter": "已应用架构感知过滤 (BasePage: {basePage}, ViewModel: {viewModel})"
59
- }
59
+ }
package/package.json CHANGED
@@ -1,59 +1,62 @@
1
1
  {
2
- "name": "flu-cli",
3
- "version": "2.0.6",
4
- "description": "Flutter MVVM 脚手架工具",
5
- "main": "index.js",
6
- "type": "module",
7
- "bin": {
8
- "flu-cli": "./index.js"
9
- },
10
- "publishConfig": {
11
- "tag": "latest"
12
- },
13
- "scripts": {
14
- "start": "node ./index.js",
15
- "dev": "NODE_ENV=development node ./index.js --",
16
- "prod": "NODE_ENV=production node ./index.js",
17
- "prepublishOnly": "NODE_ENV=production"
18
- },
19
- "keywords": [
20
- "flutter",
21
- "cli",
22
- "v2",
23
- "scaffold",
24
- "mvvm",
25
- "template",
26
- "generator"
27
- ],
28
- "author": "火叶工作室",
29
- "license": "MIT",
30
- "repository": {
31
- "type": "git",
32
- "url": "https://gitee.com/flu-cli/flu-cli.git"
33
- },
34
- "homepage": "http://huozhiye.cn/flu-cli",
35
- "bugs": {
36
- "url": "https://gitee.com/flu-cli/flu-cli/issues"
37
- },
38
- "dependencies": {
39
- "@clack/prompts": "^0.11.0",
40
- "flu-cli-core": "^1.0.2",
41
- "chalk": "^4.1.2",
42
- "commander": "^11.1.0",
43
- "date-fns": "^4.1.0",
44
- "enquirer": "^2.4.1",
45
- "fs-extra": "^11.2.0",
46
- "handlebars": "^4.7.8",
47
- "inquirer": "^8.2.6",
48
- "json5": "^2.2.3",
49
- "ora": "^5.4.1",
50
- "simple-git": "^3.20.0"
51
- },
52
- "devDependencies": {
53
- "@types/fs-extra": "^11.0.4",
54
- "@types/inquirer": "^9.0.7"
55
- },
56
- "engines": {
57
- "node": ">=14.0.0"
58
- }
2
+ "name": "flu-cli",
3
+ "version": "2.1.0",
4
+ "description": "Flutter 全流程效率工具链",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "flu-cli": "./index.js"
9
+ },
10
+ "publishConfig": {
11
+ "tag": "latest"
12
+ },
13
+ "scripts": {
14
+ "start": "node ./index.js",
15
+ "dev": "NODE_ENV=development node ./index.js --",
16
+ "prod": "NODE_ENV=production node ./index.js",
17
+ "build": "node -e \"console.log('flu-cli: no build step')\"",
18
+ "smoke:vnext-params": "node scripts/smoke-vnext-params.mjs",
19
+ "smoke:vnext-generate": "node scripts/smoke-vnext-generate.mjs",
20
+ "prepublishOnly": "NODE_ENV=production"
21
+ },
22
+ "keywords": [
23
+ "flutter",
24
+ "cli",
25
+ "v2",
26
+ "scaffold",
27
+ "mvvm",
28
+ "template",
29
+ "generator"
30
+ ],
31
+ "author": "火叶工作室",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://gitee.com/flu-cli/flu-cli.git"
36
+ },
37
+ "homepage": "http://huozhiye.cn/flu-cli",
38
+ "bugs": {
39
+ "url": "https://gitee.com/flu-cli/flu-cli/issues"
40
+ },
41
+ "dependencies": {
42
+ "@clack/prompts": "^0.11.0",
43
+ "flu-cli-core": "^1.0.6",
44
+ "chalk": "^4.1.2",
45
+ "commander": "^11.1.0",
46
+ "date-fns": "^4.1.0",
47
+ "enquirer": "^2.4.1",
48
+ "fs-extra": "^11.2.0",
49
+ "handlebars": "^4.7.8",
50
+ "inquirer": "^8.2.6",
51
+ "json5": "^2.2.3",
52
+ "ora": "^5.4.1",
53
+ "simple-git": "^3.20.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/fs-extra": "^11.0.4",
57
+ "@types/inquirer": "^9.0.7"
58
+ },
59
+ "engines": {
60
+ "node": ">=14.0.0"
61
+ }
59
62
  }