flu-cli 2.0.2 → 2.0.4

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/index.js CHANGED
@@ -4,19 +4,53 @@
4
4
  * flu-cli v2.0
5
5
  * Flutter MVVM 脚手架工具
6
6
  *
7
- * 支持多种架构模板和代码生成功能
7
+ * 支持多种架构模板 and 代码生成功能
8
8
  */
9
9
 
10
+ // ========== 加载 .env 文件 ==========
11
+ import { readFileSync, existsSync } from 'fs';
12
+ import { resolve } from 'path';
13
+
14
+ function loadEnvFile () {
15
+ try {
16
+ const envPath = resolve(process.cwd(), '.env');
17
+ if (existsSync(envPath)) {
18
+ const envContent = readFileSync(envPath, 'utf8');
19
+ const lines = envContent.split('\n');
20
+
21
+ for (const line of lines) {
22
+ const trimmed = line.trim();
23
+ // 跳过注释和空行
24
+ if (!trimmed || trimmed.startsWith('#')) continue;
25
+
26
+ const [key, ...valueParts] = trimmed.split('=');
27
+ const value = valueParts.join('=').trim();
28
+
29
+ // 移除引号
30
+ const cleanValue = value.replace(/^["']|["']$/g, '');
31
+
32
+ // 只设置未定义的环境变量
33
+ if (!process.env[key]) {
34
+ process.env[key] = cleanValue;
35
+ }
36
+ }
37
+ }
38
+ } catch (error) {
39
+ // 忽略加载错误
40
+ }
41
+ }
42
+
43
+ loadEnvFile();
44
+
10
45
  import { program } from 'commander';
11
46
  import chalk from 'chalk';
12
- import { readFileSync } from 'fs';
13
47
  import { fileURLToPath } from 'url';
14
48
  import { dirname, join } from 'path';
15
49
  import { newProjectWithClack } from './lib/commands/newClack.js';
16
50
  import { addComponent } from './lib/commands/add.js';
17
51
  import { listTemplates, showTemplateDetail } from './lib/commands/templates.js';
18
52
  import { updateTemplates, cleanCache } from './lib/commands/cache.js';
19
- // generate-sm 命令已移除
53
+ import { t } from './lib/utils/i18n.js';
20
54
 
21
55
  // 获取 package.json
22
56
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -25,10 +59,9 @@ const packageJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'ut
25
59
  // 设置版本和描述
26
60
  program
27
61
  .version(packageJson.version)
28
- .description(chalk.cyan('🚀 Flutter MVVM 脚手架工具'));
62
+ .description(chalk.cyan(t('cli.description')));
29
63
 
30
64
  // ========== 创建项目命令(V1 已废弃) ==========
31
- // V1 命令已移除,请使用 'flu new' 命令
32
65
  program
33
66
  .command('create')
34
67
  .description('⚠️ 已废弃:请使用 flu new 命令')
@@ -36,11 +69,6 @@ program
36
69
  console.log(chalk.yellow('\n⚠️ create 命令已在 V2 中废弃\n'));
37
70
  console.log(chalk.cyan('请使用新命令:'));
38
71
  console.log(chalk.green(' flu new <project-name>\n'));
39
- console.log(chalk.gray('示例:'));
40
- console.log(chalk.gray(' flu new my_app --template lite'));
41
- console.log(chalk.gray(' flu new my_app --template modular'));
42
- console.log(chalk.gray(' flu new my_app --template clean\n'));
43
- console.log(chalk.blue('查看帮助:flu new --help\n'));
44
72
  process.exit(0);
45
73
  });
46
74
 
@@ -48,12 +76,14 @@ program
48
76
  program
49
77
  .command('new [project-name]')
50
78
  .alias('n')
51
- .description('创建 Flutter 项目(实时模板预览)')
52
- .option('-t, --template <type>', '模板类型: lite, modular, clean')
53
- .option('-s, --state <type>', '状态管理器: default, provider, getx, riverpod', 'default')
54
- .option('-d, --dir <path>', '项目存放目录', process.cwd())
55
- .option('--no-cache', '不使用缓存,强制从 Git 拉取')
56
- .option('--remote', '使用远程 Gitee 模板(默认使用本地模板)')
79
+ .description(t('cmd.new.desc'))
80
+ .option('-t, --template <type>', t('cmd.new.opt.template'))
81
+ .option('-s, --state <type>', t('cmd.new.opt.state'), 'default')
82
+ .option('-d, --dir <path>', t('cmd.new.opt.dir'), process.cwd())
83
+ .option('--no-cache', t('cmd.new.opt.no_cache'))
84
+ .option('--no-network', '不包含网络层(默认包含)')
85
+ .option('--remote', t('cmd.new.opt.remote'))
86
+ .option('--flutter-template <type>', 'Flutter official template (app, module, package, plugin)', 'app')
57
87
  .action((projectName, options) => {
58
88
  newProjectWithClack(projectName, options);
59
89
  });
@@ -62,14 +92,14 @@ program
62
92
  program
63
93
  .command('add [type] [name]')
64
94
  .alias('a')
65
- .description('添加 Flutter 代码组件 (page/p, widget/w, component/c, ...)')
66
- .option('-f, --feature <name>', '所属功能模块(仅 modular/clean 模式)')
67
- .option('--stateful', '创建 StatefulWidget(默认 StatelessWidget)')
68
- .option('--stateless', '强制创建 StatelessWidget(即使有 ViewModel,不推荐)')
69
- .option('--list-page', '创建列表页 (BaseListPage)')
70
- .option('--no-vm', '不生成 ViewModel(仅 page 类型)')
71
- .option('--json <file>', '从 JSON 文件生成(仅 model 类型)')
72
- .option('--list', '查看支持的类型列表')
95
+ .description(t('cmd.add.desc'))
96
+ .option('-f, --feature <name>', t('cmd.add.opt.feature'))
97
+ .option('--stateful', t('cmd.add.opt.stateful'))
98
+ .option('--stateless', t('cmd.add.opt.stateless'))
99
+ .option('--list-page', t('cmd.add.opt.list_page'))
100
+ .option('--no-vm', t('cmd.add.opt.no_vm'))
101
+ .option('--json <file>', t('cmd.add.opt.json'))
102
+ .option('--list', t('cmd.add.opt.list'))
73
103
  .action((type, name, options) => {
74
104
  addComponent(type, name, options);
75
105
  });
@@ -78,7 +108,7 @@ program
78
108
  program
79
109
  .command('templates [template-name]')
80
110
  .alias('t')
81
- .description('查看所有模板或指定模板详情')
111
+ .description(t('cmd.templates.desc'))
82
112
  .action((templateName) => {
83
113
  if (templateName) {
84
114
  showTemplateDetail(templateName);
@@ -87,15 +117,13 @@ program
87
117
  }
88
118
  });
89
119
 
90
- // generate-sm 命令已移除
91
-
92
120
  // ========== 缓存管理命令 ==========
93
121
  program
94
122
  .command('update-templates [template-name]')
95
123
  .alias('update')
96
124
  .alias('u')
97
- .description('更新模板缓存')
98
- .option('--force', '强制刷新并清理未跟踪文件')
125
+ .description(t('cmd.update.desc'))
126
+ .option('--force', t('cmd.update.opt.force'))
99
127
  .action((templateName, options) => {
100
128
  updateTemplates(templateName, options);
101
129
  });
@@ -103,7 +131,7 @@ program
103
131
  program
104
132
  .command('cache <action>')
105
133
  .alias('c')
106
- .description('缓存管理 (action: clean)')
134
+ .description(t('cmd.cache.desc'))
107
135
  .action((action) => {
108
136
  if (action === 'clean') {
109
137
  cleanCache();
@@ -113,13 +141,25 @@ program
113
141
  }
114
142
  });
115
143
 
144
+ // ========== 应用资源管理 ==========
145
+ import { configAssets } from './lib/commands/assets.js';
146
+
147
+ program
148
+ .command('assets')
149
+ .alias('as')
150
+ .description(t('cmd.assets.desc'))
151
+ .option('-d, --dir <path>', t('cmd.new.opt.dir'), process.cwd())
152
+ .action((options) => {
153
+ configAssets(options);
154
+ });
155
+
116
156
  // ========== 代码片段管理 ==========
117
157
  import { syncSnippets } from './lib/commands/snippets.js';
118
158
 
119
159
  program
120
160
  .command('sync-snippets')
121
161
  .alias('sync')
122
- .description('同步最新版本的标准代码片段到当前项目')
162
+ .description(t('cmd.sync.desc'))
123
163
  .action(() => {
124
164
  syncSnippets();
125
165
  });
@@ -128,35 +168,34 @@ program
128
168
  import { listAllTemplates, addTemplate, removeTemplate } from './lib/commands/template.js';
129
169
 
130
170
  const templateCmd = program.command('template');
131
-
132
171
  templateCmd
133
- .description('管理自定义项目模板')
172
+ .description(t('cmd.template.desc'))
134
173
  .action(() => {
135
174
  listAllTemplates();
136
175
  });
137
176
 
138
177
  templateCmd
139
178
  .command('list')
140
- .description('列出所有模板')
179
+ .description(t('cmd.template.list.desc'))
141
180
  .action(() => {
142
181
  listAllTemplates();
143
182
  });
144
183
 
145
184
  templateCmd
146
185
  .command('add <id> <source>')
147
- .description('添加自定义模板 (id: 唯一标识, source: Git URL 或本地路径)')
148
- .option('--local', '添加本地模板 (source 为路径)')
149
- .option('-n, --name <name>', '显示名称')
150
- .option('-b, --branch <branch>', 'Git 分支 (默认 main)')
151
- .option('-d, --description <text>', '描述信息')
152
- .option('-f, --force', '覆盖已存在的模板')
186
+ .description(t('cmd.template.add.desc'))
187
+ .option('--local', t('cmd.template.add.opt.local'))
188
+ .option('-n, --name <name>', t('cmd.template.add.opt.name'))
189
+ .option('-b, --branch <branch>', t('cmd.template.add.opt.branch'))
190
+ .option('-d, --description <text>', t('cmd.template.add.opt.desc'))
191
+ .option('-f, --force', t('cmd.template.add.opt.force'))
153
192
  .action((id, source, options) => {
154
193
  addTemplate(id, source, options);
155
194
  });
156
195
 
157
196
  templateCmd
158
197
  .command('remove <id>')
159
- .description('删除自定义模板')
198
+ .description(t('cmd.template.remove.desc'))
160
199
  .action((id) => {
161
200
  removeTemplate(id);
162
201
  });
@@ -167,37 +206,46 @@ import { completion } from './lib/commands/completion.js';
167
206
  program
168
207
  .command('completion')
169
208
  .alias('comp')
170
- .description('生成 Shell 自动补全脚本 (bash/zsh)')
209
+ .description(t('cmd.completion.desc'))
171
210
  .action(() => {
172
211
  completion();
173
212
  });
174
213
 
175
-
176
214
  // ========== 配置管理命令 ==========
177
- import { initConfig } from './lib/commands/config.js';
215
+ import { initConfig, setConfig, getConfig } from './lib/commands/config.js';
178
216
 
179
217
  const configCmd = program.command('config');
180
-
181
218
  configCmd
182
- .description('管理项目生成配置 (.flu-cli.json)')
219
+ .description(t('cmd.config.desc'))
183
220
  .action(() => {
184
221
  configCmd.help();
185
222
  });
186
223
 
187
224
  configCmd
188
225
  .command('init')
189
- .description('初始化配置文件 (基于当前项目结构)')
190
- .option('-d, --dir <path>', '项目目录', process.cwd())
226
+ .description(t('cmd.config.init.desc'))
227
+ .option('-d, --dir <path>', t('cmd.new.opt.dir'), process.cwd())
191
228
  .option('-f, --force', '覆盖已存在的配置文件')
192
229
  .action((options) => {
193
230
  initConfig(options);
194
231
  });
195
232
 
233
+ configCmd
234
+ .command('set <key> <value>')
235
+ .description('Set global configuration (e.g., locale)')
236
+ .action((key, value) => {
237
+ setConfig(key, value);
238
+ });
239
+
240
+ configCmd
241
+ .command('get [key]')
242
+ .description('Get global configuration')
243
+ .action((key) => {
244
+ getConfig(key);
245
+ });
196
246
 
197
- // 解析命令行参数
198
247
  program.parse(process.argv);
199
248
 
200
- // 如果没有提供命令,显示帮助信息
201
249
  if (process.argv.length === 2) {
202
250
  console.log(chalk.bold.cyan('\n🚀 欢迎使用 flu-cli v2.0\n'));
203
251
  program.help({ error: false });
@@ -162,7 +162,7 @@ async function addPage (name, options) {
162
162
  logger.info(`不生成 ViewModel`);
163
163
  }
164
164
 
165
- const success = generatePage(name, {
165
+ const success = await generatePage(name, {
166
166
  feature,
167
167
  stateful: finalStateful,
168
168
  stateless: finalStateless,
@@ -209,7 +209,7 @@ async function addWidget (name, options) {
209
209
  logger.info(`类型: StatefulWidget`);
210
210
  }
211
211
 
212
- const success = generateWidget(name, {
212
+ const success = await generateWidget(name, {
213
213
  feature,
214
214
  stateful,
215
215
  outputDir: process.cwd()
@@ -234,7 +234,7 @@ async function addComponentItem (name, options) {
234
234
  logger.info(`功能模块: ${feature}`);
235
235
  }
236
236
 
237
- const success = generateComponent(name, {
237
+ const success = await generateComponent(name, {
238
238
  feature,
239
239
  outputDir: options.outputDir || process.cwd()
240
240
  }, logger);
@@ -258,7 +258,7 @@ async function addViewModel (name, options) {
258
258
  logger.info(`功能模块: ${feature}`);
259
259
  }
260
260
 
261
- const success = generateViewModel(name, {
261
+ const success = await generateViewModel(name, {
262
262
  feature,
263
263
  outputDir: process.cwd()
264
264
  }, logger);
@@ -282,7 +282,7 @@ async function addService (name, options) {
282
282
  logger.info(`功能模块: ${feature}`);
283
283
  }
284
284
 
285
- const success = generateService(name, {
285
+ const success = await generateService(name, {
286
286
  feature,
287
287
  outputDir: process.cwd()
288
288
  }, logger);
@@ -386,7 +386,7 @@ async function addModel (name, options) {
386
386
  logger.info(`从 JSON 文件生成: ${json}`);
387
387
  }
388
388
 
389
- const success = generateModel(name, {
389
+ const success = await generateModel(name, {
390
390
  feature,
391
391
  jsonFile: options.json || json,
392
392
  jsonData,
@@ -405,7 +405,7 @@ async function addModel (name, options) {
405
405
  async function addModule (name, options) {
406
406
  logger.info(`生成模块: ${name}`);
407
407
 
408
- const success = generateModule(name, {
408
+ const success = await generateModule(name, {
409
409
  outputDir: options.outputDir || process.cwd()
410
410
  });
411
411
 
@@ -0,0 +1,183 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import { join } from 'path';
4
+ import { existsSync } from 'fs';
5
+ import { AppAssetsManager, ConsoleLogger } from '@flu-cli/core';
6
+ import { t } from '../utils/i18n.js';
7
+
8
+ const logger = new ConsoleLogger();
9
+
10
+ /**
11
+ * 交互式配置应用资源
12
+ */
13
+ export async function configAssets (options) {
14
+ const projectPath = options.dir || process.cwd();
15
+
16
+ // 检查是否在 Flutter 项目中
17
+ if (!existsSync(join(projectPath, 'pubspec.yaml'))) {
18
+ logger.error(t('assets.not_flutter'));
19
+ return;
20
+ }
21
+
22
+ p.intro(chalk.cyan.bold(t('assets.intro')));
23
+
24
+ try {
25
+ const assets = {};
26
+
27
+ // 1. 配置应用图标
28
+ const setupIcon = await p.confirm({
29
+ message: t('assets.setup_icon'),
30
+ initialValue: true
31
+ });
32
+
33
+ if (p.isCancel(setupIcon)) {
34
+ p.cancel(t('common.cancel'));
35
+ return;
36
+ }
37
+
38
+ if (setupIcon) {
39
+ const iconPath = await p.text({
40
+ message: t('assets.icon_path'),
41
+ placeholder: 'assets/logo.png',
42
+ validate: (value) => {
43
+ if (!value) return '路径不能为空';
44
+ if (!existsSync(join(projectPath, value)) && !existsSync(value)) {
45
+ return '找不到该文件,请确认路径是否正确';
46
+ }
47
+ }
48
+ });
49
+
50
+ if (p.isCancel(iconPath)) {
51
+ p.cancel(t('common.cancel'));
52
+ return;
53
+ }
54
+ assets.appIcon = iconPath;
55
+ }
56
+
57
+ // 2. 配置启动图
58
+ const setupSplash = await p.confirm({
59
+ message: t('assets.setup_splash'),
60
+ initialValue: true
61
+ });
62
+
63
+ if (p.isCancel(setupSplash)) {
64
+ p.cancel(t('common.cancel'));
65
+ return;
66
+ }
67
+
68
+ if (setupSplash) {
69
+ const splashLogo = await p.text({
70
+ message: t('assets.splash_logo'),
71
+ placeholder: 'assets/logo.png',
72
+ validate: (value) => {
73
+ if (!value) return '路径不能为空';
74
+ if (!existsSync(join(projectPath, value)) && !existsSync(value)) {
75
+ return '找不到该文件,请确认路径是否正确';
76
+ }
77
+ }
78
+ });
79
+
80
+ if (p.isCancel(splashLogo)) {
81
+ p.cancel(t('common.cancel'));
82
+ return;
83
+ }
84
+ assets.splashLogo = splashLogo;
85
+
86
+ const bgColor = await p.text({
87
+ message: t('assets.bg_color'),
88
+ placeholder: '#FFFFFF',
89
+ initialValue: '#FFFFFF'
90
+ });
91
+
92
+ if (p.isCancel(bgColor)) {
93
+ p.cancel(t('common.cancel'));
94
+ return;
95
+ }
96
+ assets.splashBackgroundColor = bgColor;
97
+
98
+ const useBgImage = await p.confirm({
99
+ message: t('assets.use_bg_image'),
100
+ initialValue: false
101
+ });
102
+
103
+ if (p.isCancel(useBgImage)) {
104
+ p.cancel(t('common.cancel'));
105
+ return;
106
+ }
107
+
108
+ if (useBgImage) {
109
+ const bgImagePath = await p.text({
110
+ message: t('assets.bg_image_path'),
111
+ validate: (value) => {
112
+ if (!value) return '路径不能为空';
113
+ if (!existsSync(join(projectPath, value)) && !existsSync(value)) {
114
+ return '找不到该文件';
115
+ }
116
+ }
117
+ });
118
+ if (p.isCancel(bgImagePath)) {
119
+ p.cancel(t('common.cancel'));
120
+ return;
121
+ }
122
+ assets.splashBackground = bgImagePath;
123
+ }
124
+
125
+ // 暗黑模式支持
126
+ const enableDark = await p.confirm({
127
+ message: t('assets.setup_dark'),
128
+ initialValue: false
129
+ });
130
+
131
+ if (p.isCancel(enableDark)) {
132
+ p.cancel(t('common.cancel'));
133
+ return;
134
+ }
135
+
136
+ if (enableDark) {
137
+ assets.enableDarkMode = true;
138
+
139
+ const darkBgColor = await p.text({
140
+ message: t('assets.dark_bg_color'),
141
+ placeholder: '#000000',
142
+ initialValue: '#000000'
143
+ });
144
+ if (!p.isCancel(darkBgColor)) assets.splashBackgroundColorDark = darkBgColor;
145
+
146
+ const useDarkLogo = await p.confirm({
147
+ message: t('assets.use_dark_logo'),
148
+ initialValue: false
149
+ });
150
+ if (useDarkLogo) {
151
+ const darkLogoPath = await p.text({
152
+ message: t('assets.dark_logo_path')
153
+ });
154
+ if (!p.isCancel(darkLogoPath)) assets.splashLogoDark = darkLogoPath;
155
+ }
156
+ }
157
+ }
158
+
159
+ if (Object.keys(assets).length === 0) {
160
+ p.note(t('assets.no_assets'));
161
+ p.outro(chalk.gray(t('common.bye')));
162
+ return;
163
+ }
164
+
165
+ const s = p.spinner();
166
+ s.start(t('assets.configuring'));
167
+
168
+ const assetsManager = new AppAssetsManager();
169
+ const success = await assetsManager.setupAppAssets(projectPath, assets, logger);
170
+
171
+ if (success) {
172
+ s.stop(t('assets.success'));
173
+ p.outro(chalk.green.bold(t('assets.done')));
174
+ } else {
175
+ s.stop(t('assets.failed'));
176
+ p.outro(chalk.red(t('assets.error_outro')));
177
+ }
178
+
179
+ } catch (error) {
180
+ p.cancel(`发生错误: ${error.message}`);
181
+ process.exit(1);
182
+ }
183
+ }
@@ -3,7 +3,7 @@
3
3
  * 职责:初始化和管理项目配置文件
4
4
  */
5
5
 
6
- import { ProjectConfigManager, detectProjectTemplate } from '@flu-cli/core';
6
+ import { ProjectConfigManager, detectProjectTemplate, ConfigManager } from '@flu-cli/core';
7
7
  import { writeFileSync, existsSync } from 'fs';
8
8
  import { join } from 'path';
9
9
  import chalk from 'chalk';
@@ -68,3 +68,43 @@ export function initConfig (options) {
68
68
  process.exit(1);
69
69
  }
70
70
  }
71
+
72
+ /**
73
+ * 设置全局配置
74
+ */
75
+ export function setConfig (key, value) {
76
+ const config = ConfigManager.getInstance();
77
+
78
+ // 目前只支持 locale
79
+ if (key === 'locale') {
80
+ if (!['zh-CN', 'en-US'].includes(value)) {
81
+ console.log(chalk.red(`不支持的语言: ${value}`));
82
+ console.log(chalk.gray('支持: zh-CN, en-US'));
83
+ return;
84
+ }
85
+ config.setLocale(value);
86
+ console.log(chalk.green(`✓ 全局配置已更新: ${key} = ${value}`));
87
+ } else {
88
+ console.log(chalk.yellow(`不支持的配置项: ${key}`));
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 获取全局配置
94
+ */
95
+ export function getConfig (key) {
96
+ const config = ConfigManager.getInstance();
97
+
98
+ if (key === 'locale') {
99
+ const val = config.getLocale();
100
+ console.log(`${key} = ${val}`);
101
+ } else {
102
+ // 简单起见,如果 key 未知,显示 list
103
+ if (key) {
104
+ console.log(chalk.yellow(`未知配置项: ${key}`));
105
+ } else {
106
+ console.log(`locale = ${config.getLocale()}`);
107
+ console.log(`author = ${config.getAuthorName()}`);
108
+ }
109
+ }
110
+ }