flu-cli 2.0.0 → 2.0.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.
- package/CLI.md +226 -63
- package/README.md +56 -29
- package/index.js +61 -12
- package/lib/commands/add.js +177 -54
- package/lib/commands/completion.js +0 -2
- package/lib/commands/config.js +70 -0
- package/lib/commands/newClack.js +9 -6
- package/lib/commands/template.js +113 -0
- package/lib/utils/templateSelectorEnquirer.js +13 -10
- package/package.json +5 -3
- package/templates/snippets/dart.code-snippets +168 -263
- package/lib/commands/generate.js +0 -26
- package/lib/generators/component_generator.js +0 -93
- package/lib/generators/module_generator.js +0 -141
package/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { newProjectWithClack } from './lib/commands/newClack.js';
|
|
|
16
16
|
import { addComponent } from './lib/commands/add.js';
|
|
17
17
|
import { listTemplates, showTemplateDetail } from './lib/commands/templates.js';
|
|
18
18
|
import { updateTemplates, cleanCache } from './lib/commands/cache.js';
|
|
19
|
-
|
|
19
|
+
// generate-sm 命令已移除
|
|
20
20
|
|
|
21
21
|
// 获取 package.json
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -68,7 +68,6 @@ program
|
|
|
68
68
|
.option('--stateless', '强制创建 StatelessWidget(即使有 ViewModel,不推荐)')
|
|
69
69
|
.option('--list-page', '创建列表页 (BaseListPage)')
|
|
70
70
|
.option('--no-vm', '不生成 ViewModel(仅 page 类型)')
|
|
71
|
-
.option('--type <type>', 'Service 类型: api, storage, auth(仅 service 类型)', 'api')
|
|
72
71
|
.option('--json <file>', '从 JSON 文件生成(仅 model 类型)')
|
|
73
72
|
.option('--list', '查看支持的类型列表')
|
|
74
73
|
.action((type, name, options) => {
|
|
@@ -88,15 +87,7 @@ program
|
|
|
88
87
|
}
|
|
89
88
|
});
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
.command('generate-sm <type>')
|
|
93
|
-
.alias('g')
|
|
94
|
-
.description('为现有项目生成状态管理适配与配置')
|
|
95
|
-
.option('-d, --dir <path>', '项目目录', process.cwd())
|
|
96
|
-
.option('-m, --module <name>', '模块名称(modular/clean 支持分模块适配器布局)')
|
|
97
|
-
.action((type, options) => {
|
|
98
|
-
generateStateManager(type, options);
|
|
99
|
-
});
|
|
90
|
+
// generate-sm 命令已移除
|
|
100
91
|
|
|
101
92
|
// ========== 缓存管理命令 ==========
|
|
102
93
|
program
|
|
@@ -133,6 +124,43 @@ program
|
|
|
133
124
|
syncSnippets();
|
|
134
125
|
});
|
|
135
126
|
|
|
127
|
+
// ========== 模板管理命令 (New) ==========
|
|
128
|
+
import { listAllTemplates, addTemplate, removeTemplate } from './lib/commands/template.js';
|
|
129
|
+
|
|
130
|
+
const templateCmd = program.command('template');
|
|
131
|
+
|
|
132
|
+
templateCmd
|
|
133
|
+
.description('管理自定义项目模板')
|
|
134
|
+
.action(() => {
|
|
135
|
+
listAllTemplates();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
templateCmd
|
|
139
|
+
.command('list')
|
|
140
|
+
.description('列出所有模板')
|
|
141
|
+
.action(() => {
|
|
142
|
+
listAllTemplates();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
templateCmd
|
|
146
|
+
.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', '覆盖已存在的模板')
|
|
153
|
+
.action((id, source, options) => {
|
|
154
|
+
addTemplate(id, source, options);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
templateCmd
|
|
158
|
+
.command('remove <id>')
|
|
159
|
+
.description('删除自定义模板')
|
|
160
|
+
.action((id) => {
|
|
161
|
+
removeTemplate(id);
|
|
162
|
+
});
|
|
163
|
+
|
|
136
164
|
// ========== 自动补全 ==========
|
|
137
165
|
import { completion } from './lib/commands/completion.js';
|
|
138
166
|
|
|
@@ -145,11 +173,32 @@ program
|
|
|
145
173
|
});
|
|
146
174
|
|
|
147
175
|
|
|
176
|
+
// ========== 配置管理命令 ==========
|
|
177
|
+
import { initConfig } from './lib/commands/config.js';
|
|
178
|
+
|
|
179
|
+
const configCmd = program.command('config');
|
|
180
|
+
|
|
181
|
+
configCmd
|
|
182
|
+
.description('管理项目生成配置 (.flu-cli.json)')
|
|
183
|
+
.action(() => {
|
|
184
|
+
configCmd.help();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
configCmd
|
|
188
|
+
.command('init')
|
|
189
|
+
.description('初始化配置文件 (基于当前项目结构)')
|
|
190
|
+
.option('-d, --dir <path>', '项目目录', process.cwd())
|
|
191
|
+
.option('-f, --force', '覆盖已存在的配置文件')
|
|
192
|
+
.action((options) => {
|
|
193
|
+
initConfig(options);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
|
|
148
197
|
// 解析命令行参数
|
|
149
198
|
program.parse(process.argv);
|
|
150
199
|
|
|
151
200
|
// 如果没有提供命令,显示帮助信息
|
|
152
201
|
if (process.argv.length === 2) {
|
|
153
202
|
console.log(chalk.bold.cyan('\n🚀 欢迎使用 flu-cli v2.0\n'));
|
|
154
|
-
program.help();
|
|
203
|
+
program.help({ error: false });
|
|
155
204
|
}
|
package/lib/commands/add.js
CHANGED
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
* 生成页面、Widget、Component、ViewModel 等
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
import {
|
|
7
|
+
ConsoleLogger,
|
|
8
|
+
generatePage,
|
|
9
|
+
generateWidget,
|
|
10
|
+
generateViewModel,
|
|
11
|
+
generateService,
|
|
12
|
+
generateModel,
|
|
13
|
+
generateComponent,
|
|
14
|
+
generateModule
|
|
15
|
+
} from '@flu-cli/core';
|
|
16
|
+
|
|
17
|
+
const logger = new ConsoleLogger();
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* 添加组件
|
|
@@ -45,6 +49,11 @@ export async function addComponent (type, name, options) {
|
|
|
45
49
|
|
|
46
50
|
const normalizedType = typeMap[type.toLowerCase()] || type.toLowerCase();
|
|
47
51
|
|
|
52
|
+
// 自动推断 feature (如果未指定)
|
|
53
|
+
if (!options.feature) {
|
|
54
|
+
options.feature = inferFeatureFromCwd();
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
switch (normalizedType) {
|
|
49
58
|
case 'page':
|
|
50
59
|
await addPage(name, options);
|
|
@@ -102,25 +111,65 @@ async function addPage (name, options) {
|
|
|
102
111
|
logger.info(`功能模块: ${feature}`);
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
|
|
114
|
+
// 交互式选择页面类型 (如果未指定任何标志)
|
|
115
|
+
let finalStateful = stateful;
|
|
116
|
+
let finalStateless = stateless;
|
|
117
|
+
let finalNoVm = noVm;
|
|
118
|
+
let finalListPage = listPage;
|
|
119
|
+
|
|
120
|
+
if (!stateful && !stateless && !listPage) {
|
|
121
|
+
try {
|
|
122
|
+
const inquirer = (await import('inquirer')).default;
|
|
123
|
+
const { pageType } = await inquirer.prompt([
|
|
124
|
+
{
|
|
125
|
+
type: 'list',
|
|
126
|
+
name: 'pageType',
|
|
127
|
+
message: '请选择页面类型:',
|
|
128
|
+
choices: [
|
|
129
|
+
{ name: 'Stateful(BasePage) + ViewModel (推荐)', value: 'default' },
|
|
130
|
+
{ name: 'Stateful (no ViewModel)', value: 'stateful_no_vm' },
|
|
131
|
+
{ name: 'Stateless', value: 'stateless' },
|
|
132
|
+
{ name: 'List Page (列表页)', value: 'list_page' }
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
if (pageType === 'stateful_no_vm') {
|
|
138
|
+
finalStateful = true;
|
|
139
|
+
finalNoVm = true;
|
|
140
|
+
} else if (pageType === 'stateless') {
|
|
141
|
+
finalStateless = true;
|
|
142
|
+
finalNoVm = true; // Stateless 通常不需要 VM 绑定
|
|
143
|
+
} else if (pageType === 'list_page') {
|
|
144
|
+
finalListPage = true;
|
|
145
|
+
}
|
|
146
|
+
// default: stateful=false (use config default), stateless=false, noVm=false
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if (process.stdout.isTTY) {
|
|
149
|
+
logger.warn('交互式选择已取消,使用默认配置');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (finalListPage) {
|
|
106
155
|
logger.info(`类型: 列表页 (BaseListPage)`);
|
|
107
|
-
} else if (
|
|
156
|
+
} else if (finalStateless) {
|
|
108
157
|
logger.info(`强制类型: StatelessWidget`);
|
|
109
|
-
} else if (
|
|
158
|
+
} else if (finalStateful) {
|
|
110
159
|
logger.info(`类型: StatefulWidget`);
|
|
111
160
|
}
|
|
112
|
-
if (
|
|
161
|
+
if (finalNoVm) {
|
|
113
162
|
logger.info(`不生成 ViewModel`);
|
|
114
163
|
}
|
|
115
164
|
|
|
116
165
|
const success = generatePage(name, {
|
|
117
166
|
feature,
|
|
118
|
-
stateful,
|
|
119
|
-
stateless,
|
|
120
|
-
withViewModel: !
|
|
121
|
-
isListPage:
|
|
167
|
+
stateful: finalStateful,
|
|
168
|
+
stateless: finalStateless,
|
|
169
|
+
withViewModel: !finalNoVm,
|
|
170
|
+
isListPage: finalListPage,
|
|
122
171
|
outputDir: process.cwd()
|
|
123
|
-
});
|
|
172
|
+
}, logger);
|
|
124
173
|
|
|
125
174
|
if (success) {
|
|
126
175
|
logger.newLine();
|
|
@@ -130,16 +179,16 @@ async function addPage (name, options) {
|
|
|
130
179
|
logger.info('下一步:');
|
|
131
180
|
if (feature) {
|
|
132
181
|
console.log(` 1. 在 lib/features/${feature}/pages/${name}_page.dart 中实现页面逻辑`);
|
|
133
|
-
if (!
|
|
182
|
+
if (!finalNoVm) {
|
|
134
183
|
console.log(` 2. 在 lib/features/${feature}/viewmodels/${name}_viewmodel.dart 中实现业务逻辑`);
|
|
135
184
|
}
|
|
136
185
|
} else {
|
|
137
186
|
console.log(` 1. 在 lib/pages/${name}_page.dart 中实现页面逻辑`);
|
|
138
|
-
if (!
|
|
187
|
+
if (!finalNoVm) {
|
|
139
188
|
console.log(` 2. 在 lib/viewmodels/${name}_viewmodel.dart 中实现业务逻辑`);
|
|
140
189
|
}
|
|
141
190
|
}
|
|
142
|
-
console.log(` ${
|
|
191
|
+
console.log(` ${finalNoVm ? 2 : 3}. 在路由中注册新页面`);
|
|
143
192
|
}
|
|
144
193
|
}
|
|
145
194
|
|
|
@@ -164,7 +213,7 @@ async function addWidget (name, options) {
|
|
|
164
213
|
feature,
|
|
165
214
|
stateful,
|
|
166
215
|
outputDir: process.cwd()
|
|
167
|
-
});
|
|
216
|
+
}, logger);
|
|
168
217
|
|
|
169
218
|
if (success) {
|
|
170
219
|
logger.newLine();
|
|
@@ -187,8 +236,8 @@ async function addComponentItem (name, options) {
|
|
|
187
236
|
|
|
188
237
|
const success = generateComponent(name, {
|
|
189
238
|
feature,
|
|
190
|
-
outputDir: process.cwd()
|
|
191
|
-
});
|
|
239
|
+
outputDir: options.outputDir || process.cwd()
|
|
240
|
+
}, logger);
|
|
192
241
|
|
|
193
242
|
if (success) {
|
|
194
243
|
logger.newLine();
|
|
@@ -212,7 +261,7 @@ async function addViewModel (name, options) {
|
|
|
212
261
|
const success = generateViewModel(name, {
|
|
213
262
|
feature,
|
|
214
263
|
outputDir: process.cwd()
|
|
215
|
-
});
|
|
264
|
+
}, logger);
|
|
216
265
|
|
|
217
266
|
if (success) {
|
|
218
267
|
logger.newLine();
|
|
@@ -225,36 +274,22 @@ async function addViewModel (name, options) {
|
|
|
225
274
|
*/
|
|
226
275
|
async function addService (name, options) {
|
|
227
276
|
const {
|
|
228
|
-
feature = null
|
|
229
|
-
type = 'api'
|
|
277
|
+
feature = null
|
|
230
278
|
} = options;
|
|
231
279
|
|
|
232
280
|
logger.info(`生成 Service: ${name}`);
|
|
233
281
|
if (feature) {
|
|
234
282
|
logger.info(`功能模块: ${feature}`);
|
|
235
283
|
}
|
|
236
|
-
logger.info(`类型: ${type}`);
|
|
237
284
|
|
|
238
285
|
const success = generateService(name, {
|
|
239
286
|
feature,
|
|
240
|
-
type,
|
|
241
287
|
outputDir: process.cwd()
|
|
242
|
-
});
|
|
288
|
+
}, logger);
|
|
243
289
|
|
|
244
290
|
if (success) {
|
|
245
291
|
logger.newLine();
|
|
246
292
|
logger.success('✅ Service 生成完成!');
|
|
247
|
-
logger.newLine();
|
|
248
|
-
|
|
249
|
-
if (type === 'api') {
|
|
250
|
-
logger.info('提示: 记得在 pubspec.yaml 中添加 http 依赖');
|
|
251
|
-
console.log(' dependencies:');
|
|
252
|
-
console.log(' http: ^1.1.0');
|
|
253
|
-
} else if (type === 'storage' || type === 'auth') {
|
|
254
|
-
logger.info('提示: 记得在 pubspec.yaml 中添加 shared_preferences 依赖');
|
|
255
|
-
console.log(' dependencies:');
|
|
256
|
-
console.log(' shared_preferences: ^2.2.0');
|
|
257
|
-
}
|
|
258
293
|
}
|
|
259
294
|
}
|
|
260
295
|
|
|
@@ -271,15 +306,92 @@ async function addModel (name, options) {
|
|
|
271
306
|
if (feature) {
|
|
272
307
|
logger.info(`功能模块: ${feature}`);
|
|
273
308
|
}
|
|
274
|
-
|
|
309
|
+
|
|
310
|
+
// 交互式获取 JSON 数据
|
|
311
|
+
let jsonData = null;
|
|
312
|
+
if (!json) {
|
|
313
|
+
try {
|
|
314
|
+
const inquirer = (await import('inquirer')).default;
|
|
315
|
+
const { source } = await inquirer.prompt([
|
|
316
|
+
{
|
|
317
|
+
type: 'list',
|
|
318
|
+
name: 'source',
|
|
319
|
+
message: '请选择 JSON 数据源:',
|
|
320
|
+
choices: [
|
|
321
|
+
{ name: '使用默认模板', value: 'default' },
|
|
322
|
+
{ name: '输入 JSON (粘贴)', value: 'input' },
|
|
323
|
+
{ name: '使用编辑器输入 (推荐)', value: 'editor' },
|
|
324
|
+
{ name: '选择 JSON 文件', value: 'file' }
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
if (source === 'input') {
|
|
330
|
+
const { content } = await inquirer.prompt([
|
|
331
|
+
{
|
|
332
|
+
type: 'input',
|
|
333
|
+
name: 'content',
|
|
334
|
+
message: '请输入 JSON 内容:',
|
|
335
|
+
validate: (input) => {
|
|
336
|
+
try {
|
|
337
|
+
JSON.parse(input);
|
|
338
|
+
return true;
|
|
339
|
+
} catch (e) {
|
|
340
|
+
return `JSON 格式错误: ${e.message}`;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
]);
|
|
345
|
+
jsonData = JSON.parse(content);
|
|
346
|
+
} else if (source === 'editor') {
|
|
347
|
+
const { content } = await inquirer.prompt([
|
|
348
|
+
{
|
|
349
|
+
type: 'editor',
|
|
350
|
+
name: 'content',
|
|
351
|
+
message: '请在编辑器中输入 JSON 内容,保存并关闭以继续',
|
|
352
|
+
default: '{\n "example": "value"\n}',
|
|
353
|
+
validate: (input) => {
|
|
354
|
+
try {
|
|
355
|
+
JSON.parse(input);
|
|
356
|
+
return true;
|
|
357
|
+
} catch (e) {
|
|
358
|
+
return `JSON 格式错误: ${e.message}`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
]);
|
|
363
|
+
jsonData = JSON.parse(content);
|
|
364
|
+
} else if (source === 'file') {
|
|
365
|
+
const { filePath } = await inquirer.prompt([
|
|
366
|
+
{
|
|
367
|
+
type: 'input',
|
|
368
|
+
name: 'filePath',
|
|
369
|
+
message: '请输入 JSON 文件路径:',
|
|
370
|
+
validate: (input) => {
|
|
371
|
+
if (!input) return '路径不能为空';
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
]);
|
|
376
|
+
// 这里只是更新 options.json,后续逻辑会处理
|
|
377
|
+
options.json = filePath;
|
|
378
|
+
}
|
|
379
|
+
} catch (e) {
|
|
380
|
+
// 如果交互失败(非 TTY),则忽略,使用默认行为
|
|
381
|
+
if (process.stdout.isTTY) {
|
|
382
|
+
logger.warn('交互式输入已取消,使用默认模板');
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
275
386
|
logger.info(`从 JSON 文件生成: ${json}`);
|
|
276
387
|
}
|
|
277
388
|
|
|
278
389
|
const success = generateModel(name, {
|
|
279
390
|
feature,
|
|
280
|
-
jsonFile: json,
|
|
391
|
+
jsonFile: options.json || json,
|
|
392
|
+
jsonData,
|
|
281
393
|
outputDir: process.cwd()
|
|
282
|
-
});
|
|
394
|
+
}, logger);
|
|
283
395
|
|
|
284
396
|
if (success) {
|
|
285
397
|
logger.newLine();
|
|
@@ -294,7 +406,7 @@ async function addModule (name, options) {
|
|
|
294
406
|
logger.info(`生成模块: ${name}`);
|
|
295
407
|
|
|
296
408
|
const success = generateModule(name, {
|
|
297
|
-
outputDir: process.cwd()
|
|
409
|
+
outputDir: options.outputDir || process.cwd()
|
|
298
410
|
});
|
|
299
411
|
|
|
300
412
|
if (!success) {
|
|
@@ -381,25 +493,23 @@ async function printSupportedTypes () {
|
|
|
381
493
|
name: 'service',
|
|
382
494
|
emoji: '⚙️',
|
|
383
495
|
desc: '服务层',
|
|
384
|
-
detail: '
|
|
496
|
+
detail: '生成基础 Service 文件',
|
|
385
497
|
options: [
|
|
386
|
-
'-f, --feature <name> 指定功能模块'
|
|
387
|
-
'--type <type> 服务类型: api, storage, auth (默认: api)'
|
|
498
|
+
'-f, --feature <name> 指定功能模块'
|
|
388
499
|
],
|
|
389
500
|
examples: [
|
|
390
|
-
'flu-cli add service user
|
|
391
|
-
'flu-cli add service
|
|
392
|
-
'flu-cli add service auth --type auth --feature user'
|
|
501
|
+
'flu-cli add service user',
|
|
502
|
+
'flu-cli add service auth --feature user'
|
|
393
503
|
]
|
|
394
504
|
},
|
|
395
505
|
{
|
|
396
506
|
name: 'model',
|
|
397
507
|
emoji: '📊',
|
|
398
508
|
desc: '数据模型',
|
|
399
|
-
detail: '生成数据模型类',
|
|
509
|
+
detail: '生成数据模型类 (支持交互式输入 JSON)',
|
|
400
510
|
options: [
|
|
401
511
|
'-f, --feature <name> 指定功能模块',
|
|
402
|
-
'--json <file> 从 JSON 文件生成'
|
|
512
|
+
'--json <file> 从 JSON 文件生成 (可选)'
|
|
403
513
|
],
|
|
404
514
|
examples: [
|
|
405
515
|
'flu-cli add model user',
|
|
@@ -459,8 +569,8 @@ async function printSupportedTypes () {
|
|
|
459
569
|
console.log(chalk.gray(' flu-cli add page home\n'));
|
|
460
570
|
console.log(chalk.white(' # 创建带功能模块的页面'));
|
|
461
571
|
console.log(chalk.gray(' flu-cli add page login --feature auth\n'));
|
|
462
|
-
console.log(chalk.white(' # 创建一个
|
|
463
|
-
console.log(chalk.gray(' flu-cli add service user
|
|
572
|
+
console.log(chalk.white(' # 创建一个 Service'));
|
|
573
|
+
console.log(chalk.gray(' flu-cli add service user\n'));
|
|
464
574
|
|
|
465
575
|
// 提示信息
|
|
466
576
|
console.log(chalk.bold.cyan('💡 提示:\n'));
|
|
@@ -470,3 +580,16 @@ async function printSupportedTypes () {
|
|
|
470
580
|
console.log(chalk.gray(' • 更多帮助请运行: flu-cli add --help\n'));
|
|
471
581
|
}
|
|
472
582
|
|
|
583
|
+
/**
|
|
584
|
+
* 从当前工作目录推断 feature 名称
|
|
585
|
+
* 规则: 如果路径包含 /features/<name>/...,则返回 <name>
|
|
586
|
+
*/
|
|
587
|
+
function inferFeatureFromCwd () {
|
|
588
|
+
const cwd = process.cwd();
|
|
589
|
+
const match = cwd.match(/features[\\/]([^\\/]+)/);
|
|
590
|
+
if (match && match[1]) {
|
|
591
|
+
return match[1];
|
|
592
|
+
}
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config 命令
|
|
3
|
+
* 职责:初始化和管理项目配置文件
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ProjectConfigManager, detectProjectTemplate } from '@flu-cli/core';
|
|
7
|
+
import { writeFileSync, existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 初始化配置文件
|
|
13
|
+
*/
|
|
14
|
+
export function initConfig (options) {
|
|
15
|
+
try {
|
|
16
|
+
const projectDir = options.dir || process.cwd();
|
|
17
|
+
const configPath = join(projectDir, '.flu-cli.json');
|
|
18
|
+
|
|
19
|
+
if (existsSync(configPath) && !options.force) {
|
|
20
|
+
console.log(chalk.yellow('⚠️ 配置文件已存在 (.flu-cli.json)'));
|
|
21
|
+
console.log(chalk.gray('使用 --force 覆盖'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 检测当前项目模板,生成对应的默认配置
|
|
26
|
+
const template = detectProjectTemplate(projectDir) || 'custom';
|
|
27
|
+
let config = ProjectConfigManager.getDefaultConfigTemplate(template);
|
|
28
|
+
|
|
29
|
+
// Smart Init: 现实检查,防止默认配置依赖的 BasePage/BaseViewModel 在项目中不存在
|
|
30
|
+
try {
|
|
31
|
+
const pageConfig = config.generators && config.generators.page;
|
|
32
|
+
if (pageConfig && pageConfig.withBasePage) {
|
|
33
|
+
const possiblePaths = [
|
|
34
|
+
join(projectDir, 'lib/base/base_page.dart'),
|
|
35
|
+
join(projectDir, 'lib/core/base/base_page.dart'),
|
|
36
|
+
join(projectDir, 'lib/core/presentation/base/base_page.dart')
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
if (pageConfig.basePageImport && pageConfig.basePageImport.startsWith('lib/')) {
|
|
40
|
+
possiblePaths.unshift(join(projectDir, pageConfig.basePageImport));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const basePageExists = possiblePaths.some(p => existsSync(p));
|
|
44
|
+
|
|
45
|
+
if (!basePageExists) {
|
|
46
|
+
console.log(chalk.yellow('Smart Init: 未检测到 BasePage 文件,降级为原生模式 (Native Mode)'));
|
|
47
|
+
if (config.generators && config.generators.page) {
|
|
48
|
+
config.generators.page.withBasePage = false;
|
|
49
|
+
config.generators.page.withViewModel = false;
|
|
50
|
+
}
|
|
51
|
+
if (config.generators && config.generators.viewModel) {
|
|
52
|
+
config.generators.viewModel.withBaseViewModel = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.log(chalk.gray(`Smart Init 检查失败: ${e.message || e}`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
61
|
+
|
|
62
|
+
console.log(chalk.green(`✓ 配置文件已生成: ${configPath}`));
|
|
63
|
+
console.log(chalk.cyan(`ℹ️ 基于检测到的模板类型: ${template}`));
|
|
64
|
+
console.log(chalk.gray('你可以编辑 .flu-cli.json 来自定义生成规则'));
|
|
65
|
+
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(chalk.red(`初始化配置失败: ${error.message}`));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
package/lib/commands/newClack.js
CHANGED
|
@@ -9,10 +9,11 @@ import chalk from 'chalk';
|
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { existsSync } from 'fs';
|
|
12
|
-
import { getTemplate, isValidTemplate } from '../../config/templates.js';
|
|
12
|
+
import { getTemplate, isValidTemplate, getAllTemplates } from '../../config/templates.js';
|
|
13
13
|
import { selectTemplateWithEnquirer } from '../utils/templateSelectorEnquirer.js';
|
|
14
14
|
import { cloneOrUpdateTemplate } from '../templates/templateManager.js';
|
|
15
|
-
import { getAuthorName, saveAuthorName,
|
|
15
|
+
import { getAuthorName, saveAuthorName, saveDefaultTemplate } from '../utils/config.js';
|
|
16
|
+
import { ConfigManager } from '@flu-cli/core';
|
|
16
17
|
import { copyTemplate, replaceVariables, copyCustomTemplate, ensurePubspecName, mergePubspecFromTemplate, cleanupTemplateFiles } from '../templates/templateCopier.js';
|
|
17
18
|
import { runFlutterCreate } from '../utils/flutterHelper.js';
|
|
18
19
|
import { ProjectGenerator } from '../generators/project_generator.js';
|
|
@@ -150,7 +151,7 @@ export async function newProjectWithClack (projectName, options) {
|
|
|
150
151
|
// Step 7: 显示信息并确认
|
|
151
152
|
const templateDisplay = templateSelection?.kind === 'builtin'
|
|
152
153
|
? getTemplate(templateSelection.name).displayName
|
|
153
|
-
: (
|
|
154
|
+
: (ConfigManager.getInstance().getTemplate(templateSelection.id)?.name || '自定义模板');
|
|
154
155
|
|
|
155
156
|
console.log('');
|
|
156
157
|
console.log(chalk.cyan('📋 项目信息确认:'));
|
|
@@ -288,7 +289,9 @@ async function createProject (projectName, templateSelection, projectDir, projec
|
|
|
288
289
|
sourceLocalPath
|
|
289
290
|
);
|
|
290
291
|
} else if (templateSelection?.kind === 'custom') {
|
|
291
|
-
const
|
|
292
|
+
const configManager = ConfigManager.getInstance();
|
|
293
|
+
const ct = configManager.getTemplate(templateSelection.id);
|
|
294
|
+
|
|
292
295
|
if (!ct) {
|
|
293
296
|
s.stop('模板准备失败');
|
|
294
297
|
throw new Error('未找到自定义模板');
|
|
@@ -300,12 +303,12 @@ async function createProject (projectName, templateSelection, projectDir, projec
|
|
|
300
303
|
'main',
|
|
301
304
|
!options.cache,
|
|
302
305
|
true,
|
|
303
|
-
ct.
|
|
306
|
+
ct.path || ct.url // 兼容旧配置
|
|
304
307
|
);
|
|
305
308
|
} else {
|
|
306
309
|
templatePath = await cloneOrUpdateTemplate(
|
|
307
310
|
ct.id,
|
|
308
|
-
ct.
|
|
311
|
+
ct.url,
|
|
309
312
|
ct.branch || 'main',
|
|
310
313
|
!options.cache,
|
|
311
314
|
false
|