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.
- package/CHANGELOG.md +23 -0
- package/README.md +17 -4
- package/config/dev.config.js +11 -11
- package/config/templates.js +10 -10
- package/index.js +554 -102
- package/lib/commands/add.js +365 -266
- package/lib/commands/assets.js +77 -78
- package/lib/commands/cache.js +29 -52
- package/lib/commands/completion.js +13 -11
- package/lib/commands/config.js +150 -44
- package/lib/commands/init-ai-base.js +89 -0
- package/lib/commands/newClack.js +269 -178
- package/lib/commands/snippets.js +58 -43
- package/lib/commands/template.js +98 -58
- package/lib/commands/templates.js +101 -57
- package/lib/commands/upload.js +313 -0
- package/lib/commands/vnext-options.js +206 -0
- package/lib/generators/model_generator.js +91 -88
- package/lib/generators/page_generator.js +100 -93
- package/lib/generators/service_generator.js +44 -39
- package/lib/generators/viewmodel_generator.js +25 -29
- package/lib/generators/widget_generator.js +30 -35
- package/lib/templates/templateCopier.js +14 -15
- package/lib/templates/templateManager.js +22 -21
- package/lib/utils/config.js +37 -20
- package/lib/utils/flutterHelper.js +2 -2
- package/lib/utils/i18n.js +3 -3
- package/lib/utils/index_updater.js +22 -23
- package/lib/utils/json-output.js +59 -0
- package/lib/utils/logger.js +17 -17
- package/lib/utils/project_detector.js +66 -66
- package/lib/utils/snippet_loader.js +21 -19
- package/lib/utils/string_helper.js +13 -13
- package/lib/utils/templateSelectorEnquirer.js +94 -108
- package/locales/en-US.json +1 -1
- package/locales/zh-CN.json +2 -2
- package/package.json +60 -57
- package/scripts/smoke-vnext-generate.mjs +1934 -0
- package/scripts/smoke-vnext-params.mjs +92 -0
- package/CLI.md +0 -513
- package/release.sh +0 -529
- package/scripts/e2e-state-tests.js +0 -116
- package/scripts/sync-base-to-templates.js +0 -108
- package/scripts/workspace-clone-all.sh +0 -101
- package/scripts/workspace-status-all.sh +0 -112
package/lib/commands/add.js
CHANGED
|
@@ -11,115 +11,119 @@ import {
|
|
|
11
11
|
generateService,
|
|
12
12
|
generateModel,
|
|
13
13
|
generateComponent,
|
|
14
|
-
generateModule
|
|
15
|
-
} from 'flu-cli-core'
|
|
14
|
+
generateModule,
|
|
15
|
+
} from 'flu-cli-core'
|
|
16
|
+
import { listGeneratorSelectableOptionsWithReport } from './config.js'
|
|
16
17
|
|
|
17
|
-
const logger = new ConsoleLogger()
|
|
18
|
+
const logger = new ConsoleLogger()
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* 添加组件
|
|
21
22
|
*/
|
|
22
|
-
export async function addComponent
|
|
23
|
+
export async function addComponent(type, name, options) {
|
|
24
|
+
const diagnostics = []
|
|
23
25
|
// 处理 --list 参数
|
|
24
|
-
if (options.list ||
|
|
25
|
-
await printSupportedTypes()
|
|
26
|
-
return
|
|
26
|
+
if (options.list || type === '--list') {
|
|
27
|
+
await printSupportedTypes()
|
|
28
|
+
return { ok: true, diagnostics }
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
if (!type || !name) {
|
|
30
|
-
logger.error('请指定类型和名称')
|
|
31
|
-
await printSupportedTypes()
|
|
32
|
-
|
|
32
|
+
logger.error('请指定类型和名称')
|
|
33
|
+
await printSupportedTypes()
|
|
34
|
+
diagnostics.push('请指定类型和名称')
|
|
35
|
+
return { ok: false, diagnostics }
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
logger.title(`📝 添加 ${type}: ${name}`)
|
|
38
|
+
logger.title(`📝 添加 ${type}: ${name}`)
|
|
36
39
|
|
|
37
40
|
try {
|
|
38
41
|
// 处理别名
|
|
39
42
|
const typeMap = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const normalizedType = typeMap[type.toLowerCase()] || type.toLowerCase()
|
|
43
|
+
p: 'page',
|
|
44
|
+
w: 'widget',
|
|
45
|
+
c: 'component',
|
|
46
|
+
v: 'viewmodel',
|
|
47
|
+
vm: 'viewmodel',
|
|
48
|
+
s: 'service',
|
|
49
|
+
m: 'model',
|
|
50
|
+
mod: 'module',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const normalizedType = typeMap[type.toLowerCase()] || type.toLowerCase()
|
|
51
54
|
|
|
52
55
|
// 自动推断 feature (如果未指定)
|
|
53
56
|
if (!options.feature) {
|
|
54
|
-
options.feature = inferFeatureFromCwd()
|
|
57
|
+
options.feature = inferFeatureFromCwd()
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
switch (normalizedType) {
|
|
58
61
|
case 'page':
|
|
59
|
-
await addPage(name, options)
|
|
60
|
-
break;
|
|
62
|
+
return await addPage(name, options)
|
|
61
63
|
|
|
62
64
|
case 'widget':
|
|
63
|
-
await addWidget(name, options)
|
|
64
|
-
break;
|
|
65
|
+
return await addWidget(name, options)
|
|
65
66
|
|
|
66
67
|
case 'component':
|
|
67
|
-
await addComponentItem(name, options)
|
|
68
|
-
break;
|
|
68
|
+
return await addComponentItem(name, options)
|
|
69
69
|
|
|
70
70
|
case 'viewmodel':
|
|
71
|
-
await addViewModel(name, options)
|
|
72
|
-
break;
|
|
71
|
+
return await addViewModel(name, options)
|
|
73
72
|
|
|
74
73
|
case 'service':
|
|
75
|
-
await addService(name, options)
|
|
76
|
-
break;
|
|
74
|
+
return await addService(name, options)
|
|
77
75
|
|
|
78
76
|
case 'model':
|
|
79
|
-
await addModel(name, options)
|
|
80
|
-
break;
|
|
77
|
+
return await addModel(name, options)
|
|
81
78
|
|
|
82
79
|
case 'module':
|
|
83
|
-
await addModule(name, options)
|
|
84
|
-
break;
|
|
80
|
+
return await addModule(name, options)
|
|
85
81
|
|
|
86
82
|
default:
|
|
87
|
-
logger.error(`未知的类型: ${type}`)
|
|
88
|
-
logger.info(
|
|
89
|
-
|
|
83
|
+
logger.error(`未知的类型: ${type}`)
|
|
84
|
+
logger.info(
|
|
85
|
+
'支持的类型: page (p), widget (w), component (c), vm (v), service (s), model (m), module (mod)',
|
|
86
|
+
)
|
|
87
|
+
diagnostics.push(`未知的类型: ${type}`)
|
|
88
|
+
return { ok: false, diagnostics }
|
|
90
89
|
}
|
|
91
|
-
|
|
92
90
|
} catch (error) {
|
|
93
|
-
logger.error(`生成失败: ${error.message}`)
|
|
91
|
+
logger.error(`生成失败: ${error.message}`)
|
|
92
|
+
diagnostics.push(`生成失败: ${error.message}`)
|
|
93
|
+
return { ok: false, diagnostics }
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* 添加页面
|
|
99
99
|
*/
|
|
100
|
-
async function addPage
|
|
100
|
+
async function addPage(name, options) {
|
|
101
101
|
const {
|
|
102
102
|
feature = null,
|
|
103
103
|
stateful = false,
|
|
104
104
|
stateless = false,
|
|
105
105
|
noVm = false,
|
|
106
|
-
listPage = false
|
|
107
|
-
} = options
|
|
106
|
+
listPage = false,
|
|
107
|
+
} = options
|
|
108
108
|
|
|
109
|
-
logger.info(`生成页面: ${name}`)
|
|
109
|
+
logger.info(`生成页面: ${name}`)
|
|
110
110
|
if (feature) {
|
|
111
|
-
logger.info(`功能模块: ${feature}`)
|
|
111
|
+
logger.info(`功能模块: ${feature}`)
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// 交互式选择页面类型 (如果未指定任何标志)
|
|
115
|
-
let finalStateful = stateful
|
|
116
|
-
let finalStateless = stateless
|
|
117
|
-
let finalNoVm = noVm
|
|
118
|
-
let finalListPage = listPage
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
let finalStateful = stateful
|
|
116
|
+
let finalStateless = stateless
|
|
117
|
+
let finalNoVm = noVm
|
|
118
|
+
let finalListPage = listPage
|
|
119
|
+
let selectedPageMixins = []
|
|
120
|
+
let selectedPageMixinImports = []
|
|
121
|
+
const shouldSkipInteractivePrompt =
|
|
122
|
+
process.env.FLU_CLI_NON_INTERACTIVE === '1' || !process.stdout.isTTY
|
|
123
|
+
|
|
124
|
+
if (!stateful && !stateless && !listPage && !shouldSkipInteractivePrompt) {
|
|
121
125
|
try {
|
|
122
|
-
const inquirer = (await import('inquirer')).default
|
|
126
|
+
const inquirer = (await import('inquirer')).default
|
|
123
127
|
const { pageType } = await inquirer.prompt([
|
|
124
128
|
{
|
|
125
129
|
type: 'list',
|
|
@@ -129,189 +133,240 @@ async function addPage (name, options) {
|
|
|
129
133
|
{ name: 'Stateful(BasePage) + ViewModel (推荐)', value: 'default' },
|
|
130
134
|
{ name: 'Stateful (no ViewModel)', value: 'stateful_no_vm' },
|
|
131
135
|
{ name: 'Stateless', value: 'stateless' },
|
|
132
|
-
{ name: 'List Page (列表页)', value: 'list_page' }
|
|
133
|
-
]
|
|
134
|
-
}
|
|
135
|
-
])
|
|
136
|
+
{ name: 'List Page (列表页)', value: 'list_page' },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
])
|
|
136
140
|
|
|
137
141
|
if (pageType === 'stateful_no_vm') {
|
|
138
|
-
finalStateful = true
|
|
139
|
-
finalNoVm = true
|
|
142
|
+
finalStateful = true
|
|
143
|
+
finalNoVm = true
|
|
140
144
|
} else if (pageType === 'stateless') {
|
|
141
|
-
finalStateless = true
|
|
142
|
-
finalNoVm = true
|
|
145
|
+
finalStateless = true
|
|
146
|
+
finalNoVm = true // Stateless 通常不需要 VM 绑定
|
|
143
147
|
} else if (pageType === 'list_page') {
|
|
144
|
-
finalListPage = true
|
|
148
|
+
finalListPage = true
|
|
145
149
|
}
|
|
146
150
|
// default: stateful=false (use config default), stateless=false, noVm=false
|
|
147
151
|
} catch (e) {
|
|
148
152
|
if (process.stdout.isTTY) {
|
|
149
|
-
logger.warn('交互式选择已取消,使用默认配置')
|
|
153
|
+
logger.warn('交互式选择已取消,使用默认配置')
|
|
150
154
|
}
|
|
151
155
|
}
|
|
156
|
+
} else if (!stateful && !stateless && !listPage && shouldSkipInteractivePrompt) {
|
|
157
|
+
logger.info('检测到非交互环境,页面类型使用默认推荐配置')
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
if (finalListPage) {
|
|
155
|
-
logger.info(`类型: 列表页 (BaseListPage)`)
|
|
161
|
+
logger.info(`类型: 列表页 (BaseListPage)`)
|
|
156
162
|
} else if (finalStateless) {
|
|
157
|
-
logger.info(`强制类型: StatelessWidget`)
|
|
163
|
+
logger.info(`强制类型: StatelessWidget`)
|
|
158
164
|
} else if (finalStateful) {
|
|
159
|
-
logger.info(`类型: StatefulWidget`)
|
|
165
|
+
logger.info(`类型: StatefulWidget`)
|
|
160
166
|
}
|
|
161
167
|
if (finalNoVm) {
|
|
162
|
-
logger.info(`不生成 ViewModel`)
|
|
168
|
+
logger.info(`不生成 ViewModel`)
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
if (typeof options.mixins === 'string' && options.mixins.trim()) {
|
|
172
|
+
const selected = resolveRequestedMixins('page', options.mixins)
|
|
173
|
+
selectedPageMixins = selected.mixins
|
|
174
|
+
selectedPageMixinImports = selected.mixinImports
|
|
175
|
+
} else if (!shouldSkipInteractivePrompt) {
|
|
176
|
+
const selected = await promptGeneratorMixins('page')
|
|
177
|
+
selectedPageMixins = selected.mixins
|
|
178
|
+
selectedPageMixinImports = selected.mixinImports
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const success = await generatePage(
|
|
182
|
+
name,
|
|
183
|
+
{
|
|
184
|
+
feature,
|
|
185
|
+
stateful: finalStateful,
|
|
186
|
+
stateless: finalStateless,
|
|
187
|
+
withViewModel: !finalNoVm,
|
|
188
|
+
isListPage: finalListPage,
|
|
189
|
+
overrideMixins: selectedPageMixins,
|
|
190
|
+
overrideMixinImports: selectedPageMixinImports,
|
|
191
|
+
outputDir: process.cwd(),
|
|
192
|
+
},
|
|
193
|
+
logger,
|
|
194
|
+
)
|
|
173
195
|
|
|
174
196
|
if (success) {
|
|
175
|
-
logger.newLine()
|
|
176
|
-
logger.success('✅ 页面生成完成!')
|
|
177
|
-
logger.newLine()
|
|
197
|
+
logger.newLine()
|
|
198
|
+
logger.success('✅ 页面生成完成!')
|
|
199
|
+
logger.newLine()
|
|
178
200
|
|
|
179
|
-
logger.info('下一步:')
|
|
201
|
+
logger.info('下一步:')
|
|
180
202
|
if (feature) {
|
|
181
|
-
console.log(` 1. 在 lib/features/${feature}/pages/${name}_page.dart 中实现页面逻辑`)
|
|
203
|
+
console.log(` 1. 在 lib/features/${feature}/pages/${name}_page.dart 中实现页面逻辑`)
|
|
182
204
|
if (!finalNoVm) {
|
|
183
|
-
console.log(
|
|
205
|
+
console.log(
|
|
206
|
+
` 2. 在 lib/features/${feature}/viewmodels/${name}_viewmodel.dart 中实现业务逻辑`,
|
|
207
|
+
)
|
|
184
208
|
}
|
|
185
209
|
} else {
|
|
186
|
-
console.log(` 1. 在 lib/pages/${name}_page.dart 中实现页面逻辑`)
|
|
210
|
+
console.log(` 1. 在 lib/pages/${name}_page.dart 中实现页面逻辑`)
|
|
187
211
|
if (!finalNoVm) {
|
|
188
|
-
console.log(` 2. 在 lib/viewmodels/${name}_viewmodel.dart 中实现业务逻辑`)
|
|
212
|
+
console.log(` 2. 在 lib/viewmodels/${name}_viewmodel.dart 中实现业务逻辑`)
|
|
189
213
|
}
|
|
190
214
|
}
|
|
191
|
-
console.log(` ${finalNoVm ? 2 : 3}. 在路由中注册新页面`)
|
|
215
|
+
console.log(` ${finalNoVm ? 2 : 3}. 在路由中注册新页面`)
|
|
192
216
|
}
|
|
217
|
+
return { ok: success, diagnostics: success ? [] : ['页面生成失败'] }
|
|
193
218
|
}
|
|
194
219
|
|
|
195
220
|
/**
|
|
196
221
|
* 添加 Widget
|
|
197
222
|
*/
|
|
198
|
-
async function addWidget
|
|
199
|
-
const {
|
|
200
|
-
feature = null,
|
|
201
|
-
stateful = false
|
|
202
|
-
} = options;
|
|
223
|
+
async function addWidget(name, options) {
|
|
224
|
+
const { feature = null, stateful = false } = options
|
|
203
225
|
|
|
204
|
-
logger.info(`生成 Widget: ${name}`)
|
|
226
|
+
logger.info(`生成 Widget: ${name}`)
|
|
205
227
|
if (feature) {
|
|
206
|
-
logger.info(`功能模块: ${feature}`)
|
|
228
|
+
logger.info(`功能模块: ${feature}`)
|
|
207
229
|
}
|
|
208
230
|
if (stateful) {
|
|
209
|
-
logger.info(`类型: StatefulWidget`)
|
|
231
|
+
logger.info(`类型: StatefulWidget`)
|
|
210
232
|
}
|
|
211
233
|
|
|
212
|
-
const success = await generateWidget(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
const success = await generateWidget(
|
|
235
|
+
name,
|
|
236
|
+
{
|
|
237
|
+
feature,
|
|
238
|
+
stateful,
|
|
239
|
+
outputDir: process.cwd(),
|
|
240
|
+
},
|
|
241
|
+
logger,
|
|
242
|
+
)
|
|
217
243
|
|
|
218
244
|
if (success) {
|
|
219
|
-
logger.newLine()
|
|
220
|
-
logger.success('✅ Widget 生成完成!')
|
|
245
|
+
logger.newLine()
|
|
246
|
+
logger.success('✅ Widget 生成完成!')
|
|
221
247
|
}
|
|
248
|
+
return { ok: success, diagnostics: success ? [] : ['Widget 生成失败'] }
|
|
222
249
|
}
|
|
223
250
|
|
|
224
251
|
/**
|
|
225
252
|
* 添加 Component
|
|
226
253
|
*/
|
|
227
|
-
async function addComponentItem
|
|
228
|
-
const {
|
|
229
|
-
feature = null
|
|
230
|
-
} = options;
|
|
254
|
+
async function addComponentItem(name, options) {
|
|
255
|
+
const { feature = null } = options
|
|
231
256
|
|
|
232
|
-
logger.info(`生成 Component: ${name}`)
|
|
257
|
+
logger.info(`生成 Component: ${name}`)
|
|
233
258
|
if (feature) {
|
|
234
|
-
logger.info(`功能模块: ${feature}`)
|
|
259
|
+
logger.info(`功能模块: ${feature}`)
|
|
235
260
|
}
|
|
236
261
|
|
|
237
|
-
const success = await generateComponent(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
262
|
+
const success = await generateComponent(
|
|
263
|
+
name,
|
|
264
|
+
{
|
|
265
|
+
feature,
|
|
266
|
+
outputDir: options.outputDir || process.cwd(),
|
|
267
|
+
},
|
|
268
|
+
logger,
|
|
269
|
+
)
|
|
241
270
|
|
|
242
271
|
if (success) {
|
|
243
|
-
logger.newLine()
|
|
244
|
-
logger.success('✅ Component 生成完成!')
|
|
272
|
+
logger.newLine()
|
|
273
|
+
logger.success('✅ Component 生成完成!')
|
|
245
274
|
}
|
|
275
|
+
return { ok: success, diagnostics: success ? [] : ['Component 生成失败'] }
|
|
246
276
|
}
|
|
247
277
|
|
|
248
278
|
/**
|
|
249
279
|
* 添加 ViewModel
|
|
250
280
|
*/
|
|
251
|
-
async function addViewModel
|
|
252
|
-
const {
|
|
253
|
-
|
|
254
|
-
|
|
281
|
+
async function addViewModel(name, options) {
|
|
282
|
+
const { feature = null } = options
|
|
283
|
+
const shouldSkipInteractivePrompt =
|
|
284
|
+
process.env.FLU_CLI_NON_INTERACTIVE === '1' || !process.stdout.isTTY
|
|
255
285
|
|
|
256
|
-
logger.info(`生成 ViewModel: ${name}`)
|
|
286
|
+
logger.info(`生成 ViewModel: ${name}`)
|
|
257
287
|
if (feature) {
|
|
258
|
-
logger.info(`功能模块: ${feature}`)
|
|
288
|
+
logger.info(`功能模块: ${feature}`)
|
|
259
289
|
}
|
|
260
290
|
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
291
|
+
const selected =
|
|
292
|
+
typeof options.mixins === 'string' && options.mixins.trim()
|
|
293
|
+
? resolveRequestedMixins('viewModel', options.mixins)
|
|
294
|
+
: shouldSkipInteractivePrompt
|
|
295
|
+
? { mixins: [], mixinImports: [] }
|
|
296
|
+
: await promptGeneratorMixins('viewModel')
|
|
297
|
+
|
|
298
|
+
const success = await generateViewModel(
|
|
299
|
+
name,
|
|
300
|
+
{
|
|
301
|
+
feature,
|
|
302
|
+
overrideMixins: selected.mixins,
|
|
303
|
+
overrideMixinImports: selected.mixinImports,
|
|
304
|
+
outputDir: process.cwd(),
|
|
305
|
+
},
|
|
306
|
+
logger,
|
|
307
|
+
)
|
|
265
308
|
|
|
266
309
|
if (success) {
|
|
267
|
-
logger.newLine()
|
|
268
|
-
logger.success('✅ ViewModel 生成完成!')
|
|
310
|
+
logger.newLine()
|
|
311
|
+
logger.success('✅ ViewModel 生成完成!')
|
|
269
312
|
}
|
|
313
|
+
return { ok: success, diagnostics: success ? [] : ['ViewModel 生成失败'] }
|
|
270
314
|
}
|
|
271
315
|
|
|
272
316
|
/**
|
|
273
317
|
* 添加 Service
|
|
274
318
|
*/
|
|
275
|
-
async function addService
|
|
276
|
-
const {
|
|
277
|
-
|
|
278
|
-
|
|
319
|
+
async function addService(name, options) {
|
|
320
|
+
const { feature = null } = options
|
|
321
|
+
const shouldSkipInteractivePrompt =
|
|
322
|
+
process.env.FLU_CLI_NON_INTERACTIVE === '1' || !process.stdout.isTTY
|
|
279
323
|
|
|
280
|
-
logger.info(`生成 Service: ${name}`)
|
|
324
|
+
logger.info(`生成 Service: ${name}`)
|
|
281
325
|
if (feature) {
|
|
282
|
-
logger.info(`功能模块: ${feature}`)
|
|
326
|
+
logger.info(`功能模块: ${feature}`)
|
|
283
327
|
}
|
|
284
328
|
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
329
|
+
const selected =
|
|
330
|
+
typeof options.mixins === 'string' && options.mixins.trim()
|
|
331
|
+
? resolveRequestedMixins('service', options.mixins)
|
|
332
|
+
: shouldSkipInteractivePrompt
|
|
333
|
+
? { mixins: [], mixinImports: [] }
|
|
334
|
+
: await promptGeneratorMixins('service')
|
|
335
|
+
|
|
336
|
+
const success = await generateService(
|
|
337
|
+
name,
|
|
338
|
+
{
|
|
339
|
+
feature,
|
|
340
|
+
overrideMixins: selected.mixins,
|
|
341
|
+
overrideMixinImports: selected.mixinImports,
|
|
342
|
+
outputDir: process.cwd(),
|
|
343
|
+
},
|
|
344
|
+
logger,
|
|
345
|
+
)
|
|
289
346
|
|
|
290
347
|
if (success) {
|
|
291
|
-
logger.newLine()
|
|
292
|
-
logger.success('✅ Service 生成完成!')
|
|
348
|
+
logger.newLine()
|
|
349
|
+
logger.success('✅ Service 生成完成!')
|
|
293
350
|
}
|
|
351
|
+
return { ok: success, diagnostics: success ? [] : ['Service 生成失败'] }
|
|
294
352
|
}
|
|
295
353
|
|
|
296
354
|
/**
|
|
297
355
|
* 添加 Model
|
|
298
356
|
*/
|
|
299
|
-
async function addModel
|
|
300
|
-
const {
|
|
301
|
-
feature = null,
|
|
302
|
-
json = null
|
|
303
|
-
} = options;
|
|
357
|
+
async function addModel(name, options) {
|
|
358
|
+
const { feature = null, json = null } = options
|
|
304
359
|
|
|
305
|
-
logger.info(`生成 Model: ${name}`)
|
|
360
|
+
logger.info(`生成 Model: ${name}`)
|
|
306
361
|
if (feature) {
|
|
307
|
-
logger.info(`功能模块: ${feature}`)
|
|
362
|
+
logger.info(`功能模块: ${feature}`)
|
|
308
363
|
}
|
|
309
364
|
|
|
310
365
|
// 交互式获取 JSON 数据
|
|
311
|
-
let jsonData = null
|
|
366
|
+
let jsonData = null
|
|
312
367
|
if (!json) {
|
|
313
368
|
try {
|
|
314
|
-
const inquirer = (await import('inquirer')).default
|
|
369
|
+
const inquirer = (await import('inquirer')).default
|
|
315
370
|
const { source } = await inquirer.prompt([
|
|
316
371
|
{
|
|
317
372
|
type: 'list',
|
|
@@ -321,10 +376,10 @@ async function addModel (name, options) {
|
|
|
321
376
|
{ name: '使用默认模板', value: 'default' },
|
|
322
377
|
{ name: '输入 JSON (粘贴)', value: 'input' },
|
|
323
378
|
{ name: '使用编辑器输入 (推荐)', value: 'editor' },
|
|
324
|
-
{ name: '选择 JSON 文件', value: 'file' }
|
|
325
|
-
]
|
|
326
|
-
}
|
|
327
|
-
])
|
|
379
|
+
{ name: '选择 JSON 文件', value: 'file' },
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
])
|
|
328
383
|
|
|
329
384
|
if (source === 'input') {
|
|
330
385
|
const { content } = await inquirer.prompt([
|
|
@@ -334,15 +389,15 @@ async function addModel (name, options) {
|
|
|
334
389
|
message: '请输入 JSON 内容:',
|
|
335
390
|
validate: (input) => {
|
|
336
391
|
try {
|
|
337
|
-
JSON.parse(input)
|
|
338
|
-
return true
|
|
392
|
+
JSON.parse(input)
|
|
393
|
+
return true
|
|
339
394
|
} catch (e) {
|
|
340
|
-
return `JSON 格式错误: ${e.message}
|
|
395
|
+
return `JSON 格式错误: ${e.message}`
|
|
341
396
|
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
])
|
|
345
|
-
jsonData = JSON.parse(content)
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
])
|
|
400
|
+
jsonData = JSON.parse(content)
|
|
346
401
|
} else if (source === 'editor') {
|
|
347
402
|
const { content } = await inquirer.prompt([
|
|
348
403
|
{
|
|
@@ -352,15 +407,15 @@ async function addModel (name, options) {
|
|
|
352
407
|
default: '{\n "example": "value"\n}',
|
|
353
408
|
validate: (input) => {
|
|
354
409
|
try {
|
|
355
|
-
JSON.parse(input)
|
|
356
|
-
return true
|
|
410
|
+
JSON.parse(input)
|
|
411
|
+
return true
|
|
357
412
|
} catch (e) {
|
|
358
|
-
return `JSON 格式错误: ${e.message}
|
|
413
|
+
return `JSON 格式错误: ${e.message}`
|
|
359
414
|
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
])
|
|
363
|
-
jsonData = JSON.parse(content)
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
])
|
|
418
|
+
jsonData = JSON.parse(content)
|
|
364
419
|
} else if (source === 'file') {
|
|
365
420
|
const { filePath } = await inquirer.prompt([
|
|
366
421
|
{
|
|
@@ -368,67 +423,74 @@ async function addModel (name, options) {
|
|
|
368
423
|
name: 'filePath',
|
|
369
424
|
message: '请输入 JSON 文件路径:',
|
|
370
425
|
validate: (input) => {
|
|
371
|
-
if (!input) return '路径不能为空'
|
|
372
|
-
return true
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
])
|
|
426
|
+
if (!input) return '路径不能为空'
|
|
427
|
+
return true
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
])
|
|
376
431
|
// 这里只是更新 options.json,后续逻辑会处理
|
|
377
|
-
options.json = filePath
|
|
432
|
+
options.json = filePath
|
|
378
433
|
}
|
|
379
434
|
} catch (e) {
|
|
380
435
|
// 如果交互失败(非 TTY),则忽略,使用默认行为
|
|
381
436
|
if (process.stdout.isTTY) {
|
|
382
|
-
logger.warn('交互式输入已取消,使用默认模板')
|
|
437
|
+
logger.warn('交互式输入已取消,使用默认模板')
|
|
383
438
|
}
|
|
384
439
|
}
|
|
385
440
|
} else {
|
|
386
|
-
logger.info(`从 JSON 文件生成: ${json}`)
|
|
441
|
+
logger.info(`从 JSON 文件生成: ${json}`)
|
|
387
442
|
}
|
|
388
443
|
|
|
389
|
-
const success = await generateModel(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
444
|
+
const success = await generateModel(
|
|
445
|
+
name,
|
|
446
|
+
{
|
|
447
|
+
feature,
|
|
448
|
+
jsonFile: options.json || json,
|
|
449
|
+
jsonData,
|
|
450
|
+
outputDir: process.cwd(),
|
|
451
|
+
},
|
|
452
|
+
logger,
|
|
453
|
+
)
|
|
395
454
|
|
|
396
455
|
if (success) {
|
|
397
|
-
logger.newLine()
|
|
398
|
-
logger.success('✅ Model 生成完成!')
|
|
456
|
+
logger.newLine()
|
|
457
|
+
logger.success('✅ Model 生成完成!')
|
|
399
458
|
}
|
|
459
|
+
return { ok: success, diagnostics: success ? [] : ['Model 生成失败'] }
|
|
400
460
|
}
|
|
401
461
|
|
|
402
462
|
/**
|
|
403
463
|
* 添加模块
|
|
404
464
|
*/
|
|
405
|
-
async function addModule
|
|
406
|
-
logger.info(`生成模块: ${name}`)
|
|
465
|
+
async function addModule(name, options) {
|
|
466
|
+
logger.info(`生成模块: ${name}`)
|
|
407
467
|
|
|
408
468
|
const success = await generateModule(name, {
|
|
409
|
-
outputDir: options.outputDir || process.cwd()
|
|
410
|
-
})
|
|
469
|
+
outputDir: options.outputDir || process.cwd(),
|
|
470
|
+
})
|
|
411
471
|
|
|
412
472
|
if (!success) {
|
|
413
|
-
logger.error('模块创建失败')
|
|
473
|
+
logger.error('模块创建失败')
|
|
474
|
+
return { ok: false, diagnostics: ['模块创建失败'] }
|
|
414
475
|
}
|
|
476
|
+
return { ok: true, diagnostics: [] }
|
|
415
477
|
}
|
|
416
478
|
|
|
417
479
|
/**
|
|
418
480
|
* 打印支持的类型列表
|
|
419
481
|
*/
|
|
420
|
-
async function printSupportedTypes
|
|
421
|
-
const chalk = (await import('chalk')).default
|
|
482
|
+
async function printSupportedTypes() {
|
|
483
|
+
const chalk = (await import('chalk')).default
|
|
422
484
|
|
|
423
|
-
console.log(chalk.bold.cyan('\n📦 flu-cli add - 代码生成工具\n'))
|
|
424
|
-
console.log(chalk.gray('用于快速生成 Flutter 项目中的各种代码组件\n'))
|
|
485
|
+
console.log(chalk.bold.cyan('\n📦 flu-cli add - 代码生成工具\n'))
|
|
486
|
+
console.log(chalk.gray('用于快速生成 Flutter 项目中的各种代码组件\n'))
|
|
425
487
|
|
|
426
488
|
// 基本用法
|
|
427
|
-
console.log(chalk.bold.yellow('📖 基本用法:'))
|
|
428
|
-
console.log(chalk.white(' flu-cli add <type> <name> [options]\n'))
|
|
489
|
+
console.log(chalk.bold.yellow('📖 基本用法:'))
|
|
490
|
+
console.log(chalk.white(' flu-cli add <type> <name> [options]\n'))
|
|
429
491
|
|
|
430
492
|
// 支持的类型
|
|
431
|
-
console.log(chalk.bold.yellow('🎯 支持的组件类型:\n'))
|
|
493
|
+
console.log(chalk.bold.yellow('🎯 支持的组件类型:\n'))
|
|
432
494
|
|
|
433
495
|
const types = [
|
|
434
496
|
{
|
|
@@ -441,66 +503,48 @@ async function printSupportedTypes () {
|
|
|
441
503
|
'--stateful 创建 StatefulWidget',
|
|
442
504
|
'--stateless 强制创建 StatelessWidget',
|
|
443
505
|
'--list-page 创建列表页 (BaseListPage)',
|
|
444
|
-
'--no-vm 不生成 ViewModel'
|
|
506
|
+
'--no-vm 不生成 ViewModel',
|
|
445
507
|
],
|
|
446
508
|
examples: [
|
|
447
509
|
'flu-cli add page home',
|
|
448
510
|
'flu-cli add page login --feature auth',
|
|
449
|
-
'flu-cli add page profile --stateful --no-vm'
|
|
450
|
-
]
|
|
511
|
+
'flu-cli add page profile --stateful --no-vm',
|
|
512
|
+
],
|
|
451
513
|
},
|
|
452
514
|
{
|
|
453
515
|
name: 'widget',
|
|
454
516
|
emoji: '🧩',
|
|
455
517
|
desc: '通用组件',
|
|
456
518
|
detail: '生成可复用的 Widget 组件',
|
|
457
|
-
options: [
|
|
458
|
-
|
|
459
|
-
'--stateful 创建 StatefulWidget'
|
|
460
|
-
],
|
|
461
|
-
examples: [
|
|
462
|
-
'flu-cli add widget custom_button',
|
|
463
|
-
'flu-cli add widget user_avatar --stateful'
|
|
464
|
-
]
|
|
519
|
+
options: ['-f, --feature <name> 指定功能模块', '--stateful 创建 StatefulWidget'],
|
|
520
|
+
examples: ['flu-cli add widget custom_button', 'flu-cli add widget user_avatar --stateful'],
|
|
465
521
|
},
|
|
466
522
|
{
|
|
467
523
|
name: 'component',
|
|
468
524
|
emoji: '🔧',
|
|
469
525
|
desc: '业务组件',
|
|
470
526
|
detail: '生成特定业务场景的组件',
|
|
471
|
-
options: [
|
|
472
|
-
'-f, --feature <name> 指定功能模块'
|
|
473
|
-
],
|
|
527
|
+
options: ['-f, --feature <name> 指定功能模块'],
|
|
474
528
|
examples: [
|
|
475
529
|
'flu-cli add component product_card',
|
|
476
|
-
'flu-cli add component order_item --feature shop'
|
|
477
|
-
]
|
|
530
|
+
'flu-cli add component order_item --feature shop',
|
|
531
|
+
],
|
|
478
532
|
},
|
|
479
533
|
{
|
|
480
534
|
name: 'vm / viewmodel',
|
|
481
535
|
emoji: '🎮',
|
|
482
536
|
desc: '视图模型',
|
|
483
537
|
detail: '生成独立的 ViewModel 文件',
|
|
484
|
-
options: [
|
|
485
|
-
|
|
486
|
-
],
|
|
487
|
-
examples: [
|
|
488
|
-
'flu-cli add vm settings',
|
|
489
|
-
'flu-cli add viewmodel user --feature auth'
|
|
490
|
-
]
|
|
538
|
+
options: ['-f, --feature <name> 指定功能模块'],
|
|
539
|
+
examples: ['flu-cli add vm settings', 'flu-cli add viewmodel user --feature auth'],
|
|
491
540
|
},
|
|
492
541
|
{
|
|
493
542
|
name: 'service',
|
|
494
543
|
emoji: '⚙️',
|
|
495
544
|
desc: '服务层',
|
|
496
545
|
detail: '生成基础 Service 文件',
|
|
497
|
-
options: [
|
|
498
|
-
|
|
499
|
-
],
|
|
500
|
-
examples: [
|
|
501
|
-
'flu-cli add service user',
|
|
502
|
-
'flu-cli add service auth --feature user'
|
|
503
|
-
]
|
|
546
|
+
options: ['-f, --feature <name> 指定功能模块'],
|
|
547
|
+
examples: ['flu-cli add service user', 'flu-cli add service auth --feature user'],
|
|
504
548
|
},
|
|
505
549
|
{
|
|
506
550
|
name: 'model',
|
|
@@ -509,12 +553,9 @@ async function printSupportedTypes () {
|
|
|
509
553
|
detail: '生成数据模型类 (支持交互式输入 JSON)',
|
|
510
554
|
options: [
|
|
511
555
|
'-f, --feature <name> 指定功能模块',
|
|
512
|
-
'--json <file> 从 JSON 文件生成 (可选)'
|
|
556
|
+
'--json <file> 从 JSON 文件生成 (可选)',
|
|
513
557
|
],
|
|
514
|
-
examples: [
|
|
515
|
-
'flu-cli add model user',
|
|
516
|
-
'flu-cli add model product --json ./data/product.json'
|
|
517
|
-
]
|
|
558
|
+
examples: ['flu-cli add model user', 'flu-cli add model product --json ./data/product.json'],
|
|
518
559
|
},
|
|
519
560
|
{
|
|
520
561
|
name: 'module',
|
|
@@ -522,74 +563,132 @@ async function printSupportedTypes () {
|
|
|
522
563
|
desc: '完整模块',
|
|
523
564
|
detail: '生成包含多个文件的完整功能模块',
|
|
524
565
|
options: [],
|
|
525
|
-
examples: [
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
]
|
|
529
|
-
}
|
|
530
|
-
];
|
|
566
|
+
examples: ['flu-cli add module shop', 'flu-cli add module user_profile'],
|
|
567
|
+
},
|
|
568
|
+
]
|
|
531
569
|
|
|
532
570
|
types.forEach((t, index) => {
|
|
533
571
|
// 类型名称和描述
|
|
534
|
-
console.log(chalk.bold.green(` ${t.emoji} ${t.name}`))
|
|
535
|
-
console.log(chalk.gray(` ${t.detail}`))
|
|
572
|
+
console.log(chalk.bold.green(` ${t.emoji} ${t.name}`))
|
|
573
|
+
console.log(chalk.gray(` ${t.detail}`))
|
|
536
574
|
|
|
537
575
|
// 可用选项
|
|
538
576
|
if (t.options.length > 0) {
|
|
539
|
-
console.log(chalk.cyan(' 选项:'))
|
|
540
|
-
t.options.forEach(opt => {
|
|
541
|
-
console.log(chalk.gray(` ${opt}`))
|
|
542
|
-
})
|
|
577
|
+
console.log(chalk.cyan(' 选项:'))
|
|
578
|
+
t.options.forEach((opt) => {
|
|
579
|
+
console.log(chalk.gray(` ${opt}`))
|
|
580
|
+
})
|
|
543
581
|
}
|
|
544
582
|
|
|
545
583
|
// 使用示例
|
|
546
584
|
if (t.examples.length > 0) {
|
|
547
|
-
console.log(chalk.cyan(' 示例:'))
|
|
548
|
-
t.examples.forEach(ex => {
|
|
549
|
-
console.log(chalk.white(` ${ex}`))
|
|
550
|
-
})
|
|
585
|
+
console.log(chalk.cyan(' 示例:'))
|
|
586
|
+
t.examples.forEach((ex) => {
|
|
587
|
+
console.log(chalk.white(` ${ex}`))
|
|
588
|
+
})
|
|
551
589
|
}
|
|
552
590
|
|
|
553
591
|
// 添加分隔线(最后一项除外)
|
|
554
592
|
if (index < types.length - 1) {
|
|
555
|
-
console.log('')
|
|
593
|
+
console.log('')
|
|
556
594
|
}
|
|
557
|
-
})
|
|
595
|
+
})
|
|
558
596
|
|
|
559
597
|
// 通用选项
|
|
560
|
-
console.log(chalk.bold.yellow('\n⚡ 通用选项:\n'))
|
|
561
|
-
console.log(chalk.white(' --list 查看此帮助信息'))
|
|
562
|
-
console.log(chalk.white(' -f, --feature <name> 指定功能模块(modular/clean 架构)\n'))
|
|
598
|
+
console.log(chalk.bold.yellow('\n⚡ 通用选项:\n'))
|
|
599
|
+
console.log(chalk.white(' --list 查看此帮助信息'))
|
|
600
|
+
console.log(chalk.white(' -f, --feature <name> 指定功能模块(modular/clean 架构)\n'))
|
|
563
601
|
|
|
564
602
|
// 快速开始
|
|
565
|
-
console.log(chalk.bold.yellow('🚀 快速开始:\n'))
|
|
566
|
-
console.log(chalk.white(' # 查看所有支持的类型'))
|
|
567
|
-
console.log(chalk.gray(' flu-cli add --list\n'))
|
|
568
|
-
console.log(chalk.white(' # 创建一个简单页面'))
|
|
569
|
-
console.log(chalk.gray(' flu-cli add page home\n'))
|
|
570
|
-
console.log(chalk.white(' # 创建带功能模块的页面'))
|
|
571
|
-
console.log(chalk.gray(' flu-cli add page login --feature auth\n'))
|
|
572
|
-
console.log(chalk.white(' # 创建一个 Service'))
|
|
573
|
-
console.log(chalk.gray(' flu-cli add service user\n'))
|
|
603
|
+
console.log(chalk.bold.yellow('🚀 快速开始:\n'))
|
|
604
|
+
console.log(chalk.white(' # 查看所有支持的类型'))
|
|
605
|
+
console.log(chalk.gray(' flu-cli add --list\n'))
|
|
606
|
+
console.log(chalk.white(' # 创建一个简单页面'))
|
|
607
|
+
console.log(chalk.gray(' flu-cli add page home\n'))
|
|
608
|
+
console.log(chalk.white(' # 创建带功能模块的页面'))
|
|
609
|
+
console.log(chalk.gray(' flu-cli add page login --feature auth\n'))
|
|
610
|
+
console.log(chalk.white(' # 创建一个 Service'))
|
|
611
|
+
console.log(chalk.gray(' flu-cli add service user\n'))
|
|
574
612
|
|
|
575
613
|
// 提示信息
|
|
576
|
-
console.log(chalk.bold.cyan('💡 提示:\n'))
|
|
577
|
-
console.log(
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
console.log(chalk.gray(' •
|
|
614
|
+
console.log(chalk.bold.cyan('💡 提示:\n'))
|
|
615
|
+
console.log(
|
|
616
|
+
chalk.gray(' • 支持简写: p(page), w(widget), c(component), v(vm), s(service), m(model)'),
|
|
617
|
+
)
|
|
618
|
+
console.log(chalk.gray(' • 使用 -f 或 --feature 可将组件生成到指定功能模块下'))
|
|
619
|
+
console.log(chalk.gray(' • page 类型默认会同时生成 ViewModel,使用 --no-vm 可禁用'))
|
|
620
|
+
console.log(chalk.gray(' • 更多帮助请运行: flu-cli add --help\n'))
|
|
581
621
|
}
|
|
582
622
|
|
|
583
623
|
/**
|
|
584
624
|
* 从当前工作目录推断 feature 名称
|
|
585
625
|
* 规则: 如果路径包含 /features/<name>/...,则返回 <name>
|
|
586
626
|
*/
|
|
587
|
-
function inferFeatureFromCwd
|
|
588
|
-
const cwd = process.cwd()
|
|
589
|
-
const match = cwd.match(/features[\\/]([^\\/]+)/)
|
|
627
|
+
function inferFeatureFromCwd() {
|
|
628
|
+
const cwd = process.cwd()
|
|
629
|
+
const match = cwd.match(/features[\\/]([^\\/]+)/)
|
|
590
630
|
if (match && match[1]) {
|
|
591
|
-
return match[1]
|
|
631
|
+
return match[1]
|
|
592
632
|
}
|
|
593
|
-
return null
|
|
633
|
+
return null
|
|
594
634
|
}
|
|
595
635
|
|
|
636
|
+
async function promptGeneratorMixins(target) {
|
|
637
|
+
const mixinOptions = getAvailableMixins(target)
|
|
638
|
+
|
|
639
|
+
if (mixinOptions.length === 0) {
|
|
640
|
+
return { mixins: [], mixinImports: [] }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
const inquirer = (await import('inquirer')).default
|
|
645
|
+
const { selectedMixins } = await inquirer.prompt([
|
|
646
|
+
{
|
|
647
|
+
type: 'checkbox',
|
|
648
|
+
name: 'selectedMixins',
|
|
649
|
+
message: `选择要附加到${getGeneratorTargetLabel(target)}的 Mixin(可多选):`,
|
|
650
|
+
choices: mixinOptions.map((item) => ({
|
|
651
|
+
name: item.name,
|
|
652
|
+
value: item.name,
|
|
653
|
+
short: item.name,
|
|
654
|
+
})),
|
|
655
|
+
},
|
|
656
|
+
])
|
|
657
|
+
|
|
658
|
+
const selected = mixinOptions.filter((item) => selectedMixins.includes(item.name))
|
|
659
|
+
return {
|
|
660
|
+
mixins: selected.map((item) => item.name),
|
|
661
|
+
mixinImports: selected.map((item) => item.import),
|
|
662
|
+
}
|
|
663
|
+
} catch {
|
|
664
|
+
return { mixins: [], mixinImports: [] }
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function getGeneratorTargetLabel(target) {
|
|
669
|
+
if (target === 'page') return '页面'
|
|
670
|
+
if (target === 'viewModel') return 'ViewModel'
|
|
671
|
+
return 'Service'
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function getAvailableMixins(target) {
|
|
675
|
+
const reportResult = listGeneratorSelectableOptionsWithReport({
|
|
676
|
+
dir: process.cwd(),
|
|
677
|
+
target,
|
|
678
|
+
})
|
|
679
|
+
return reportResult?.report?.mixins || []
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function resolveRequestedMixins(target, rawValue) {
|
|
683
|
+
const available = getAvailableMixins(target)
|
|
684
|
+
const requestedNames = String(rawValue)
|
|
685
|
+
.split(',')
|
|
686
|
+
.map((item) => item.trim())
|
|
687
|
+
.filter(Boolean)
|
|
688
|
+
|
|
689
|
+
const selected = available.filter((item) => requestedNames.includes(item.name))
|
|
690
|
+
return {
|
|
691
|
+
mixins: selected.map((item) => item.name),
|
|
692
|
+
mixinImports: selected.map((item) => item.import),
|
|
693
|
+
}
|
|
694
|
+
}
|