flu-cli 2.0.3 → 2.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.
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,16 @@ 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('-p, --package <name>', '指定包名 (e.g., com.example.myapp)')
84
+ .option('-a, --author <name>', '指定作者名称')
85
+ .option('--no-cache', t('cmd.new.opt.no_cache'))
86
+ .option('--no-network', '不包含网络层(默认包含)')
87
+ .option('--remote', t('cmd.new.opt.remote'))
88
+ .option('--flutter-template <type>', 'Flutter official template (app, module, package, plugin)', 'app')
57
89
  .action((projectName, options) => {
58
90
  newProjectWithClack(projectName, options);
59
91
  });
@@ -62,14 +94,14 @@ program
62
94
  program
63
95
  .command('add [type] [name]')
64
96
  .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', '查看支持的类型列表')
97
+ .description(t('cmd.add.desc'))
98
+ .option('-f, --feature <name>', t('cmd.add.opt.feature'))
99
+ .option('--stateful', t('cmd.add.opt.stateful'))
100
+ .option('--stateless', t('cmd.add.opt.stateless'))
101
+ .option('--list-page', t('cmd.add.opt.list_page'))
102
+ .option('--no-vm', t('cmd.add.opt.no_vm'))
103
+ .option('--json <file>', t('cmd.add.opt.json'))
104
+ .option('--list', t('cmd.add.opt.list'))
73
105
  .action((type, name, options) => {
74
106
  addComponent(type, name, options);
75
107
  });
@@ -78,7 +110,7 @@ program
78
110
  program
79
111
  .command('templates [template-name]')
80
112
  .alias('t')
81
- .description('查看所有模板或指定模板详情')
113
+ .description(t('cmd.templates.desc'))
82
114
  .action((templateName) => {
83
115
  if (templateName) {
84
116
  showTemplateDetail(templateName);
@@ -87,15 +119,13 @@ program
87
119
  }
88
120
  });
89
121
 
90
- // generate-sm 命令已移除
91
-
92
122
  // ========== 缓存管理命令 ==========
93
123
  program
94
124
  .command('update-templates [template-name]')
95
125
  .alias('update')
96
126
  .alias('u')
97
- .description('更新模板缓存')
98
- .option('--force', '强制刷新并清理未跟踪文件')
127
+ .description(t('cmd.update.desc'))
128
+ .option('--force', t('cmd.update.opt.force'))
99
129
  .action((templateName, options) => {
100
130
  updateTemplates(templateName, options);
101
131
  });
@@ -103,7 +133,7 @@ program
103
133
  program
104
134
  .command('cache <action>')
105
135
  .alias('c')
106
- .description('缓存管理 (action: clean)')
136
+ .description(t('cmd.cache.desc'))
107
137
  .action((action) => {
108
138
  if (action === 'clean') {
109
139
  cleanCache();
@@ -113,13 +143,25 @@ program
113
143
  }
114
144
  });
115
145
 
146
+ // ========== 应用资源管理 ==========
147
+ import { configAssets } from './lib/commands/assets.js';
148
+
149
+ program
150
+ .command('assets')
151
+ .alias('as')
152
+ .description(t('cmd.assets.desc'))
153
+ .option('-d, --dir <path>', t('cmd.new.opt.dir'), process.cwd())
154
+ .action((options) => {
155
+ configAssets(options);
156
+ });
157
+
116
158
  // ========== 代码片段管理 ==========
117
159
  import { syncSnippets } from './lib/commands/snippets.js';
118
160
 
119
161
  program
120
162
  .command('sync-snippets')
121
163
  .alias('sync')
122
- .description('同步最新版本的标准代码片段到当前项目')
164
+ .description(t('cmd.sync.desc'))
123
165
  .action(() => {
124
166
  syncSnippets();
125
167
  });
@@ -128,35 +170,34 @@ program
128
170
  import { listAllTemplates, addTemplate, removeTemplate } from './lib/commands/template.js';
129
171
 
130
172
  const templateCmd = program.command('template');
131
-
132
173
  templateCmd
133
- .description('管理自定义项目模板')
174
+ .description(t('cmd.template.desc'))
134
175
  .action(() => {
135
176
  listAllTemplates();
136
177
  });
137
178
 
138
179
  templateCmd
139
180
  .command('list')
140
- .description('列出所有模板')
181
+ .description(t('cmd.template.list.desc'))
141
182
  .action(() => {
142
183
  listAllTemplates();
143
184
  });
144
185
 
145
186
  templateCmd
146
187
  .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', '覆盖已存在的模板')
188
+ .description(t('cmd.template.add.desc'))
189
+ .option('--local', t('cmd.template.add.opt.local'))
190
+ .option('-n, --name <name>', t('cmd.template.add.opt.name'))
191
+ .option('-b, --branch <branch>', t('cmd.template.add.opt.branch'))
192
+ .option('-d, --description <text>', t('cmd.template.add.opt.desc'))
193
+ .option('-f, --force', t('cmd.template.add.opt.force'))
153
194
  .action((id, source, options) => {
154
195
  addTemplate(id, source, options);
155
196
  });
156
197
 
157
198
  templateCmd
158
199
  .command('remove <id>')
159
- .description('删除自定义模板')
200
+ .description(t('cmd.template.remove.desc'))
160
201
  .action((id) => {
161
202
  removeTemplate(id);
162
203
  });
@@ -167,37 +208,46 @@ import { completion } from './lib/commands/completion.js';
167
208
  program
168
209
  .command('completion')
169
210
  .alias('comp')
170
- .description('生成 Shell 自动补全脚本 (bash/zsh)')
211
+ .description(t('cmd.completion.desc'))
171
212
  .action(() => {
172
213
  completion();
173
214
  });
174
215
 
175
-
176
216
  // ========== 配置管理命令 ==========
177
- import { initConfig } from './lib/commands/config.js';
217
+ import { initConfig, setConfig, getConfig } from './lib/commands/config.js';
178
218
 
179
219
  const configCmd = program.command('config');
180
-
181
220
  configCmd
182
- .description('管理项目生成配置 (.flu-cli.json)')
221
+ .description(t('cmd.config.desc'))
183
222
  .action(() => {
184
223
  configCmd.help();
185
224
  });
186
225
 
187
226
  configCmd
188
227
  .command('init')
189
- .description('初始化配置文件 (基于当前项目结构)')
190
- .option('-d, --dir <path>', '项目目录', process.cwd())
228
+ .description(t('cmd.config.init.desc'))
229
+ .option('-d, --dir <path>', t('cmd.new.opt.dir'), process.cwd())
191
230
  .option('-f, --force', '覆盖已存在的配置文件')
192
231
  .action((options) => {
193
232
  initConfig(options);
194
233
  });
195
234
 
235
+ configCmd
236
+ .command('set <key> <value>')
237
+ .description('Set global configuration (e.g., locale)')
238
+ .action((key, value) => {
239
+ setConfig(key, value);
240
+ });
241
+
242
+ configCmd
243
+ .command('get [key]')
244
+ .description('Get global configuration')
245
+ .action((key) => {
246
+ getConfig(key);
247
+ });
196
248
 
197
- // 解析命令行参数
198
249
  program.parse(process.argv);
199
250
 
200
- // 如果没有提供命令,显示帮助信息
201
251
  if (process.argv.length === 2) {
202
252
  console.log(chalk.bold.cyan('\n🚀 欢迎使用 flu-cli v2.0\n'));
203
253
  program.help({ error: false });
@@ -12,7 +12,7 @@ import {
12
12
  generateModel,
13
13
  generateComponent,
14
14
  generateModule
15
- } from '@flu-cli/core';
15
+ } from 'flu-cli-core';
16
16
 
17
17
  const logger = new ConsoleLogger();
18
18
 
@@ -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
+ }