flu-cli 0.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CLI.md +349 -0
  2. package/README.md +59 -276
  3. package/config/dev.config.js +56 -0
  4. package/config/templates.js +147 -0
  5. package/index.js +128 -81
  6. package/lib/commands/add.js +472 -0
  7. package/lib/commands/cache.js +99 -0
  8. package/lib/commands/completion.js +94 -0
  9. package/lib/commands/generate.js +26 -0
  10. package/lib/commands/newClack.js +396 -0
  11. package/lib/commands/snippets.js +39 -0
  12. package/lib/commands/templates.js +84 -0
  13. package/lib/generators/component_generator.js +93 -0
  14. package/lib/generators/model_generator.js +303 -0
  15. package/lib/generators/module_generator.js +141 -0
  16. package/lib/generators/page_generator.js +322 -0
  17. package/lib/generators/project_generator.js +96 -0
  18. package/lib/generators/service_generator.js +408 -0
  19. package/lib/generators/state_manager_generator.js +402 -0
  20. package/lib/generators/viewmodel_generator.js +115 -0
  21. package/lib/generators/widget_generator.js +104 -0
  22. package/lib/templates/templateCopier.js +296 -0
  23. package/lib/templates/templateManager.js +191 -0
  24. package/lib/utils/config.js +99 -0
  25. package/lib/utils/flutterHelper.js +85 -0
  26. package/lib/utils/index_updater.js +69 -0
  27. package/lib/utils/logger.js +57 -0
  28. package/lib/utils/project_detector.js +227 -0
  29. package/lib/utils/snippet_loader.js +32 -0
  30. package/lib/utils/string_helper.js +56 -0
  31. package/lib/utils/templateSelectorEnquirer.js +200 -0
  32. package/package.json +31 -6
  33. package/release.sh +107 -0
  34. package/scripts/e2e-state-tests.js +116 -0
  35. package/scripts/sync-base-to-templates.js +108 -0
  36. package/scripts/workspace-clone-all.sh +101 -0
  37. package/scripts/workspace-status-all.sh +112 -0
  38. package/templates/README.md +138 -0
  39. package/templates/base_files/base_list_page.dart.template +174 -0
  40. package/templates/base_files/base_list_viewmodel.dart.template +134 -0
  41. package/templates/base_files/base_page.dart.template +251 -0
  42. package/templates/base_files/base_viewmodel.dart.template +77 -0
  43. package/templates/base_files/theme/status_views_theme.dart.template +46 -0
  44. package/templates/snippets/dart.code-snippets +487 -0
  45. package/lib/createProject.js +0 -220
  46. package/lib/flutterProjectCreator.js +0 -80
  47. package/lib/libCopier.js +0 -368
  48. package/lib/userInteraction.js +0 -274
  49. package/lib/utils.js +0 -200
  50. package/publish.sh +0 -29
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Model 生成器
3
+ */
4
+
5
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { logger } from '../utils/logger.js';
8
+ import { toPascalCase, toSnakeCase, toCamelCase } from '../utils/string_helper.js';
9
+ import { updateIndexFile } from '../utils/index_updater.js';
10
+ import { getSnippetContent } from '../utils/snippet_loader.js';
11
+
12
+ /**
13
+ * 生成 Model 文件
14
+ */
15
+ export function generateModel (name, options = {}) {
16
+ try {
17
+ const {
18
+ feature = null,
19
+ jsonFile = null,
20
+ outputDir = process.cwd()
21
+ } = options;
22
+
23
+ // 转换命名
24
+ const namePascal = toPascalCase(name);
25
+ const nameSnake = toSnakeCase(name);
26
+
27
+ // 确定输出路径
28
+ let modelsDir;
29
+ if (feature) {
30
+ // Modular/Clean 架构
31
+ modelsDir = join(outputDir, 'lib', 'features', feature, 'models');
32
+ } else {
33
+ // Lite 架构
34
+ modelsDir = join(outputDir, 'lib', 'models');
35
+ }
36
+
37
+ // 创建目录
38
+ if (!existsSync(modelsDir)) {
39
+ mkdirSync(modelsDir, { recursive: true });
40
+ }
41
+
42
+ // 生成文件内容
43
+ let content;
44
+ if (jsonFile && existsSync(jsonFile)) {
45
+ // 从 JSON 文件生成
46
+ const jsonContent = readFileSync(jsonFile, 'utf8');
47
+ const jsonData = JSON.parse(jsonContent);
48
+ content = generateModelFromJson(namePascal, jsonData);
49
+ } else {
50
+ // 优先片段,其次基础模板
51
+ content = getSnippetContent(outputDir, 'flu.model', { Name: namePascal }) || generateBasicModel(namePascal);
52
+ }
53
+
54
+ // 写入文件
55
+ const filePath = join(modelsDir, `${nameSnake}_model.dart`);
56
+ if (existsSync(filePath)) {
57
+ logger.error(`文件已存在: ${filePath}`);
58
+ return false;
59
+ }
60
+
61
+ writeFileSync(filePath, content, 'utf8');
62
+ logger.success(`Model 创建成功: ${filePath}`);
63
+
64
+ // 更新 index.dart
65
+ updateIndexFile(modelsDir, `${nameSnake}_model.dart`);
66
+
67
+ return true;
68
+
69
+ } catch (error) {
70
+ logger.error(`生成 Model 失败: ${error.message}`);
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * 生成基础 Model
77
+ */
78
+ function generateBasicModel (namePascal) {
79
+ return `class ${namePascal}Model {
80
+ final String id;
81
+ final String name;
82
+ final DateTime createdAt;
83
+
84
+ ${namePascal}Model({
85
+ required this.id,
86
+ required this.name,
87
+ required this.createdAt,
88
+ });
89
+
90
+ // 从 JSON 创建
91
+ factory ${namePascal}Model.fromJson(Map<String, dynamic> json) {
92
+ return ${namePascal}Model(
93
+ id: json['id'] as String,
94
+ name: json['name'] as String,
95
+ createdAt: DateTime.parse(json['created_at'] as String),
96
+ );
97
+ }
98
+
99
+ // 转换为 JSON
100
+ Map<String, dynamic> toJson() {
101
+ return {
102
+ 'id': id,
103
+ 'name': name,
104
+ 'created_at': createdAt.toIso8601String(),
105
+ };
106
+ }
107
+
108
+ // 复制并修改
109
+ ${namePascal}Model copyWith({
110
+ String? id,
111
+ String? name,
112
+ DateTime? createdAt,
113
+ }) {
114
+ return ${namePascal}Model(
115
+ id: id ?? this.id,
116
+ name: name ?? this.name,
117
+ createdAt: createdAt ?? this.createdAt,
118
+ );
119
+ }
120
+
121
+ @override
122
+ String toString() {
123
+ return '${namePascal}Model(id: $id, name: $name, createdAt: $createdAt)';
124
+ }
125
+
126
+ @override
127
+ bool operator ==(Object other) {
128
+ if (identical(this, other)) return true;
129
+ return other is ${namePascal}Model &&
130
+ other.id == id &&
131
+ other.name == name &&
132
+ other.createdAt == createdAt;
133
+ }
134
+
135
+ @override
136
+ int get hashCode {
137
+ return id.hashCode ^ name.hashCode ^ createdAt.hashCode;
138
+ }
139
+ }
140
+ `;
141
+ }
142
+
143
+ /**
144
+ * 从 JSON 生成 Model
145
+ */
146
+ function generateModelFromJson (namePascal, jsonData) {
147
+ const fields = analyzeJsonStructure(jsonData);
148
+
149
+ // 生成字段声明
150
+ const fieldDeclarations = fields.map(f => ` final ${f.type} ${f.name};`).join('\n');
151
+
152
+ // 生成构造函数参数
153
+ const constructorParams = fields.map(f => ` required this.${f.name},`).join('\n');
154
+
155
+ // 生成 fromJson 字段
156
+ const fromJsonFields = fields.map(f => {
157
+ if (f.type === 'String') {
158
+ return ` ${f.name}: json['${f.jsonKey}'] as String,`;
159
+ } else if (f.type === 'int') {
160
+ return ` ${f.name}: json['${f.jsonKey}'] as int,`;
161
+ } else if (f.type === 'double') {
162
+ return ` ${f.name}: (json['${f.jsonKey}'] as num).toDouble(),`;
163
+ } else if (f.type === 'bool') {
164
+ return ` ${f.name}: json['${f.jsonKey}'] as bool,`;
165
+ } else if (f.type === 'DateTime') {
166
+ return ` ${f.name}: DateTime.parse(json['${f.jsonKey}'] as String),`;
167
+ } else if (f.type.startsWith('List<')) {
168
+ return ` ${f.name}: (json['${f.jsonKey}'] as List).cast<${f.type.slice(5, -1)}>(),`;
169
+ } else {
170
+ return ` ${f.name}: json['${f.jsonKey}'],`;
171
+ }
172
+ }).join('\n');
173
+
174
+ // 生成 toJson 字段
175
+ const toJsonFields = fields.map(f => {
176
+ if (f.type === 'DateTime') {
177
+ return ` '${f.jsonKey}': ${f.name}.toIso8601String(),`;
178
+ } else {
179
+ return ` '${f.jsonKey}': ${f.name},`;
180
+ }
181
+ }).join('\n');
182
+
183
+ // 生成 copyWith 参数
184
+ const copyWithParams = fields.map(f => ` ${f.type}? ${f.name},`).join('\n');
185
+ const copyWithFields = fields.map(f => ` ${f.name}: ${f.name} ?? this.${f.name},`).join('\n');
186
+
187
+ // 生成 toString
188
+ const toStringFields = fields.map(f => `${f.name}: $${f.name}`).join(', ');
189
+
190
+ // 生成 == 比较
191
+ const equalityChecks = fields.map(f => ` other.${f.name} == ${f.name}`).join(' &&\n');
192
+
193
+ // 生成 hashCode
194
+ const hashCodeFields = fields.map(f => `${f.name}.hashCode`).join(' ^ ');
195
+
196
+ return `class ${namePascal}Model {
197
+ ${fieldDeclarations}
198
+
199
+ ${namePascal}Model({
200
+ ${constructorParams}
201
+ });
202
+
203
+ // 从 JSON 创建
204
+ factory ${namePascal}Model.fromJson(Map<String, dynamic> json) {
205
+ return ${namePascal}Model(
206
+ ${fromJsonFields}
207
+ );
208
+ }
209
+
210
+ // 转换为 JSON
211
+ Map<String, dynamic> toJson() {
212
+ return {
213
+ ${toJsonFields}
214
+ };
215
+ }
216
+
217
+ // 复制并修改
218
+ ${namePascal}Model copyWith({
219
+ ${copyWithParams}
220
+ }) {
221
+ return ${namePascal}Model(
222
+ ${copyWithFields}
223
+ );
224
+ }
225
+
226
+ @override
227
+ String toString() {
228
+ return '${namePascal}Model(${toStringFields})';
229
+ }
230
+
231
+ @override
232
+ bool operator ==(Object other) {
233
+ if (identical(this, other)) return true;
234
+ return other is ${namePascal}Model &&
235
+ ${equalityChecks};
236
+ }
237
+
238
+ @override
239
+ int get hashCode {
240
+ return ${hashCodeFields};
241
+ }
242
+ }
243
+ `;
244
+ }
245
+
246
+ /**
247
+ * 分析 JSON 结构
248
+ */
249
+ function analyzeJsonStructure (jsonData) {
250
+ const fields = [];
251
+
252
+ for (const [key, value] of Object.entries(jsonData)) {
253
+ const camelKey = toCamelCase(key);
254
+ const type = inferDartType(value);
255
+
256
+ fields.push({
257
+ name: camelKey,
258
+ jsonKey: key,
259
+ type: type
260
+ });
261
+ }
262
+
263
+ return fields;
264
+ }
265
+
266
+ /**
267
+ * 推断 Dart 类型
268
+ */
269
+ function inferDartType (value) {
270
+ if (value === null) return 'dynamic';
271
+
272
+ const type = typeof value;
273
+
274
+ if (type === 'string') {
275
+ // 尝试判断是否是日期
276
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
277
+ return 'DateTime';
278
+ }
279
+ return 'String';
280
+ }
281
+
282
+ if (type === 'number') {
283
+ return Number.isInteger(value) ? 'int' : 'double';
284
+ }
285
+
286
+ if (type === 'boolean') {
287
+ return 'bool';
288
+ }
289
+
290
+ if (Array.isArray(value)) {
291
+ if (value.length === 0) {
292
+ return 'List<dynamic>';
293
+ }
294
+ const itemType = inferDartType(value[0]);
295
+ return `List<${itemType}>`;
296
+ }
297
+
298
+ if (type === 'object') {
299
+ return 'Map<String, dynamic>';
300
+ }
301
+
302
+ return 'dynamic';
303
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * 模块生成器
3
+ * 用于创建完整的功能模块结构
4
+ */
5
+
6
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { logger } from '../utils/logger.js';
9
+ import { toSnakeCase } from '../utils/string_helper.js';
10
+
11
+ /**
12
+ * 生成模块
13
+ */
14
+ export function generateModule (name, options = {}) {
15
+ try {
16
+ const {
17
+ outputDir = process.cwd()
18
+ } = options;
19
+
20
+ // 转换命名
21
+ const nameSnake = toSnakeCase(name);
22
+
23
+ // 确定输出路径
24
+ const moduleDir = join(outputDir, 'lib', 'features', nameSnake);
25
+
26
+ // 检查模块是否已存在
27
+ if (existsSync(moduleDir)) {
28
+ logger.error(`模块已存在: ${moduleDir}`);
29
+ return false;
30
+ }
31
+
32
+ // 创建模块目录结构
33
+ const directories = [
34
+ 'pages',
35
+ 'viewmodels',
36
+ 'widgets',
37
+ 'services',
38
+ 'models'
39
+ ];
40
+
41
+ logger.info(`创建模块: ${nameSnake}`);
42
+
43
+ // 创建所有目录
44
+ for (const dir of directories) {
45
+ const dirPath = join(moduleDir, dir);
46
+ mkdirSync(dirPath, { recursive: true });
47
+
48
+ // 创建 index.dart 文件
49
+ const indexPath = join(dirPath, 'index.dart');
50
+ const indexContent = generateIndexContent(dir, nameSnake);
51
+ writeFileSync(indexPath, indexContent, 'utf8');
52
+
53
+ logger.success(`创建目录: ${dir}/`);
54
+ }
55
+
56
+ // 创建模块根目录的 index.dart
57
+ const moduleIndexPath = join(moduleDir, 'index.dart');
58
+ const moduleIndexContent = generateModuleIndexContent(nameSnake);
59
+ writeFileSync(moduleIndexPath, moduleIndexContent, 'utf8');
60
+
61
+ logger.newLine();
62
+ logger.success(`✅ 模块创建成功: features/${nameSnake}/`);
63
+ logger.newLine();
64
+
65
+ // 显示模块结构
66
+ logger.info('模块结构:');
67
+ console.log(` features/${nameSnake}/`);
68
+ console.log(` ├── pages/`);
69
+ console.log(` │ └── index.dart`);
70
+ console.log(` ├── viewmodels/`);
71
+ console.log(` │ └── index.dart`);
72
+ console.log(` ├── widgets/`);
73
+ console.log(` │ └── index.dart`);
74
+ console.log(` ├── services/`);
75
+ console.log(` │ └── index.dart`);
76
+ console.log(` ├── models/`);
77
+ console.log(` │ └── index.dart`);
78
+ console.log(` └── index.dart`);
79
+
80
+ logger.newLine();
81
+ logger.info('下一步:');
82
+ console.log(` 1. 使用 "flu-cli add page <name> -f ${nameSnake}" 添加页面`);
83
+ console.log(` 2. 使用 "flu-cli add service <name> -f ${nameSnake}" 添加服务`);
84
+ console.log(` 3. 使用 "flu-cli add model <name> -f ${nameSnake}" 添加模型`);
85
+
86
+ return true;
87
+
88
+ } catch (error) {
89
+ logger.error(`生成模块失败: ${error.message}`);
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 生成目录的 index.dart 内容
96
+ */
97
+ function generateIndexContent (dirName, moduleName) {
98
+ const comments = {
99
+ pages: '// 导出所有页面',
100
+ viewmodels: '// 导出所有 ViewModel',
101
+ widgets: '// 导出所有 Widget',
102
+ services: '// 导出所有 Service',
103
+ models: '// 导出所有 Model'
104
+ };
105
+
106
+ const examples = {
107
+ pages: `// export 'home_page.dart';`,
108
+ viewmodels: `// export 'home_viewmodel.dart';`,
109
+ widgets: `// export 'custom_widget.dart';`,
110
+ services: `// export 'api_service.dart';`,
111
+ models: `// export 'user_model.dart';`
112
+ };
113
+
114
+ return `${comments[dirName] || '// 导出文件'}
115
+
116
+ ${examples[dirName] || '// export \'example.dart\';'}
117
+ `;
118
+ }
119
+
120
+ /**
121
+ * 生成模块根目录的 index.dart 内容
122
+ */
123
+ function generateModuleIndexContent (moduleName) {
124
+ return `// ${moduleName} 模块导出
125
+
126
+ // 导出页面
127
+ export 'pages/index.dart';
128
+
129
+ // 导出 ViewModel
130
+ export 'viewmodels/index.dart';
131
+
132
+ // 导出 Widget
133
+ export 'widgets/index.dart';
134
+
135
+ // 导出 Service
136
+ export 'services/index.dart';
137
+
138
+ // 导出 Model
139
+ export 'models/index.dart';
140
+ `;
141
+ }