flu-cli 0.0.4 → 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.
- package/CLI.md +349 -0
- package/README.md +59 -155
- package/config/dev.config.js +56 -0
- package/config/templates.js +147 -0
- package/index.js +128 -81
- package/lib/commands/add.js +472 -0
- package/lib/commands/cache.js +99 -0
- package/lib/commands/completion.js +94 -0
- package/lib/commands/generate.js +26 -0
- package/lib/commands/newClack.js +396 -0
- package/lib/commands/snippets.js +39 -0
- package/lib/commands/templates.js +84 -0
- package/lib/generators/component_generator.js +93 -0
- package/lib/generators/model_generator.js +303 -0
- package/lib/generators/module_generator.js +141 -0
- package/lib/generators/page_generator.js +322 -0
- package/lib/generators/project_generator.js +96 -0
- package/lib/generators/service_generator.js +408 -0
- package/lib/generators/state_manager_generator.js +402 -0
- package/lib/generators/viewmodel_generator.js +115 -0
- package/lib/generators/widget_generator.js +104 -0
- package/lib/templates/templateCopier.js +296 -0
- package/lib/templates/templateManager.js +191 -0
- package/lib/utils/config.js +99 -0
- package/lib/utils/flutterHelper.js +85 -0
- package/lib/utils/index_updater.js +69 -0
- package/lib/utils/logger.js +57 -0
- package/lib/utils/project_detector.js +227 -0
- package/lib/utils/snippet_loader.js +32 -0
- package/lib/utils/string_helper.js +56 -0
- package/lib/utils/templateSelectorEnquirer.js +200 -0
- package/package.json +31 -6
- package/release.sh +107 -0
- package/scripts/e2e-state-tests.js +116 -0
- package/scripts/sync-base-to-templates.js +108 -0
- package/scripts/workspace-clone-all.sh +101 -0
- package/scripts/workspace-status-all.sh +112 -0
- package/templates/README.md +138 -0
- package/templates/base_files/base_list_page.dart.template +174 -0
- package/templates/base_files/base_list_viewmodel.dart.template +134 -0
- package/templates/base_files/base_page.dart.template +251 -0
- package/templates/base_files/base_viewmodel.dart.template +77 -0
- package/templates/base_files/theme/status_views_theme.dart.template +46 -0
- package/templates/snippets/dart.code-snippets +487 -0
- package/lib/createProject.js +0 -220
- package/lib/flutterProjectCreator.js +0 -80
- package/lib/libCopier.js +0 -368
- package/lib/userInteraction.js +0 -274
- package/lib/utils.js +0 -200
- package/publish.sh +0 -29
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service 生成器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import { toPascalCase, toSnakeCase } from '../utils/string_helper.js';
|
|
9
|
+
import { updateIndexFile } from '../utils/index_updater.js';
|
|
10
|
+
import { getSnippetContent } from '../utils/snippet_loader.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 生成 Service 文件
|
|
14
|
+
*/
|
|
15
|
+
export function generateService (name, options = {}) {
|
|
16
|
+
try {
|
|
17
|
+
const {
|
|
18
|
+
feature = null,
|
|
19
|
+
type = 'api', // api, storage, auth
|
|
20
|
+
outputDir = process.cwd()
|
|
21
|
+
} = options;
|
|
22
|
+
|
|
23
|
+
// 转换命名
|
|
24
|
+
const namePascal = toPascalCase(name);
|
|
25
|
+
const nameSnake = toSnakeCase(name);
|
|
26
|
+
|
|
27
|
+
// 确定输出路径
|
|
28
|
+
let servicesDir;
|
|
29
|
+
if (feature) {
|
|
30
|
+
// Modular/Clean 架构
|
|
31
|
+
servicesDir = join(outputDir, 'lib', 'features', feature, 'services');
|
|
32
|
+
} else {
|
|
33
|
+
// Lite 架构
|
|
34
|
+
servicesDir = join(outputDir, 'lib', 'services');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 创建目录
|
|
38
|
+
if (!existsSync(servicesDir)) {
|
|
39
|
+
mkdirSync(servicesDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 生成文件内容(优先片段)
|
|
43
|
+
const keyMap = { api: 'flu.service.api', storage: 'flu.service.storage', auth: 'flu.service.auth' };
|
|
44
|
+
const key = keyMap[type] || keyMap.api;
|
|
45
|
+
const content = getSnippetContent(outputDir, key, { Name: namePascal }) || generateServiceContent(namePascal, type);
|
|
46
|
+
|
|
47
|
+
// 写入文件
|
|
48
|
+
const filePath = join(servicesDir, `${nameSnake}_service.dart`);
|
|
49
|
+
if (existsSync(filePath)) {
|
|
50
|
+
logger.error(`文件已存在: ${filePath}`);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
writeFileSync(filePath, content, 'utf8');
|
|
55
|
+
logger.success(`Service 创建成功: ${filePath}`);
|
|
56
|
+
|
|
57
|
+
// 更新 index.dart
|
|
58
|
+
updateIndexFile(servicesDir, `${nameSnake}_service.dart`);
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.error(`生成 Service 失败: ${error.message}`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 生成 Service 内容
|
|
70
|
+
*/
|
|
71
|
+
function generateServiceContent (namePascal, type) {
|
|
72
|
+
switch (type) {
|
|
73
|
+
case 'api':
|
|
74
|
+
return generateApiService(namePascal);
|
|
75
|
+
case 'storage':
|
|
76
|
+
return generateStorageService(namePascal);
|
|
77
|
+
case 'auth':
|
|
78
|
+
return generateAuthService(namePascal);
|
|
79
|
+
default:
|
|
80
|
+
return generateApiService(namePascal);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 生成 API Service
|
|
86
|
+
*/
|
|
87
|
+
function generateApiService (namePascal) {
|
|
88
|
+
return `import 'dart:convert';
|
|
89
|
+
import 'package:http/http.dart' as http;
|
|
90
|
+
|
|
91
|
+
class ${namePascal}Service {
|
|
92
|
+
final String baseUrl = 'https://api.example.com';
|
|
93
|
+
|
|
94
|
+
// GET 请求示例
|
|
95
|
+
Future<Map<String, dynamic>> getData(String id) async {
|
|
96
|
+
try {
|
|
97
|
+
final response = await http.get(
|
|
98
|
+
Uri.parse('$baseUrl/${namePascal.toLowerCase()}/$id'),
|
|
99
|
+
headers: {'Content-Type': 'application/json'},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (response.statusCode == 200) {
|
|
103
|
+
return json.decode(response.body);
|
|
104
|
+
} else {
|
|
105
|
+
throw Exception('Failed to load data: \${response.statusCode}');
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
throw Exception('Network error: \$e');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// POST 请求示例
|
|
113
|
+
Future<Map<String, dynamic>> createData(Map<String, dynamic> data) async {
|
|
114
|
+
try {
|
|
115
|
+
final response = await http.post(
|
|
116
|
+
Uri.parse('$baseUrl/${namePascal.toLowerCase()}'),
|
|
117
|
+
headers: {'Content-Type': 'application/json'},
|
|
118
|
+
body: json.encode(data),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (response.statusCode == 201 || response.statusCode == 200) {
|
|
122
|
+
return json.decode(response.body);
|
|
123
|
+
} else {
|
|
124
|
+
throw Exception('Failed to create data: \${response.statusCode}');
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
throw Exception('Network error: \$e');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// PUT 请求示例
|
|
132
|
+
Future<Map<String, dynamic>> updateData(String id, Map<String, dynamic> data) async {
|
|
133
|
+
try {
|
|
134
|
+
final response = await http.put(
|
|
135
|
+
Uri.parse('$baseUrl/${namePascal.toLowerCase()}/$id'),
|
|
136
|
+
headers: {'Content-Type': 'application/json'},
|
|
137
|
+
body: json.encode(data),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (response.statusCode == 200) {
|
|
141
|
+
return json.decode(response.body);
|
|
142
|
+
} else {
|
|
143
|
+
throw Exception('Failed to update data: \${response.statusCode}');
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
throw Exception('Network error: \$e');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// DELETE 请求示例
|
|
151
|
+
Future<void> deleteData(String id) async {
|
|
152
|
+
try {
|
|
153
|
+
final response = await http.delete(
|
|
154
|
+
Uri.parse('$baseUrl/${namePascal.toLowerCase()}/$id'),
|
|
155
|
+
headers: {'Content-Type': 'application/json'},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (response.statusCode != 200 && response.statusCode != 204) {
|
|
159
|
+
throw Exception('Failed to delete data: \${response.statusCode}');
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
throw Exception('Network error: \$e');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 生成 Storage Service
|
|
171
|
+
*/
|
|
172
|
+
function generateStorageService (namePascal) {
|
|
173
|
+
return `import 'package:shared_preferences/shared_preferences.dart';
|
|
174
|
+
import 'dart:convert';
|
|
175
|
+
|
|
176
|
+
class ${namePascal}Service {
|
|
177
|
+
static const String _keyPrefix = '${namePascal.toLowerCase()}_';
|
|
178
|
+
|
|
179
|
+
// 保存字符串
|
|
180
|
+
Future<bool> saveString(String key, String value) async {
|
|
181
|
+
try {
|
|
182
|
+
final prefs = await SharedPreferences.getInstance();
|
|
183
|
+
return await prefs.setString(_keyPrefix + key, value);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
throw Exception('Failed to save string: \$e');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 获取字符串
|
|
190
|
+
Future<String?> getString(String key) async {
|
|
191
|
+
try {
|
|
192
|
+
final prefs = await SharedPreferences.getInstance();
|
|
193
|
+
return prefs.getString(_keyPrefix + key);
|
|
194
|
+
} catch (e) {
|
|
195
|
+
throw Exception('Failed to get string: \$e');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 保存对象(JSON)
|
|
200
|
+
Future<bool> saveObject(String key, Map<String, dynamic> value) async {
|
|
201
|
+
try {
|
|
202
|
+
final prefs = await SharedPreferences.getInstance();
|
|
203
|
+
final jsonString = json.encode(value);
|
|
204
|
+
return await prefs.setString(_keyPrefix + key, jsonString);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
throw Exception('Failed to save object: \$e');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 获取对象(JSON)
|
|
211
|
+
Future<Map<String, dynamic>?> getObject(String key) async {
|
|
212
|
+
try {
|
|
213
|
+
final prefs = await SharedPreferences.getInstance();
|
|
214
|
+
final jsonString = prefs.getString(_keyPrefix + key);
|
|
215
|
+
if (jsonString == null) return null;
|
|
216
|
+
return json.decode(jsonString);
|
|
217
|
+
} catch (e) {
|
|
218
|
+
throw Exception('Failed to get object: \$e');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 保存布尔值
|
|
223
|
+
Future<bool> saveBool(String key, bool value) async {
|
|
224
|
+
try {
|
|
225
|
+
final prefs = await SharedPreferences.getInstance();
|
|
226
|
+
return await prefs.setBool(_keyPrefix + key, value);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
throw Exception('Failed to save bool: \$e');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 获取布尔值
|
|
233
|
+
Future<bool?> getBool(String key) async {
|
|
234
|
+
try {
|
|
235
|
+
final prefs = await SharedPreferences.getInstance();
|
|
236
|
+
return prefs.getBool(_keyPrefix + key);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
throw Exception('Failed to get bool: \$e');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 删除数据
|
|
243
|
+
Future<bool> remove(String key) async {
|
|
244
|
+
try {
|
|
245
|
+
final prefs = await SharedPreferences.getInstance();
|
|
246
|
+
return await prefs.remove(_keyPrefix + key);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
throw Exception('Failed to remove data: \$e');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 清空所有数据
|
|
253
|
+
Future<bool> clear() async {
|
|
254
|
+
try {
|
|
255
|
+
final prefs = await SharedPreferences.getInstance();
|
|
256
|
+
final keys = prefs.getKeys().where((key) => key.startsWith(_keyPrefix));
|
|
257
|
+
for (final key in keys) {
|
|
258
|
+
await prefs.remove(key);
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
} catch (e) {
|
|
262
|
+
throw Exception('Failed to clear data: \$e');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 生成 Auth Service
|
|
271
|
+
*/
|
|
272
|
+
function generateAuthService (namePascal) {
|
|
273
|
+
return `import 'dart:convert';
|
|
274
|
+
import 'package:http/http.dart' as http;
|
|
275
|
+
import 'package:shared_preferences/shared_preferences.dart';
|
|
276
|
+
|
|
277
|
+
class ${namePascal}Service {
|
|
278
|
+
final String baseUrl = 'https://api.example.com';
|
|
279
|
+
static const String _tokenKey = 'auth_token';
|
|
280
|
+
static const String _userKey = 'user_data';
|
|
281
|
+
|
|
282
|
+
// 登录
|
|
283
|
+
Future<Map<String, dynamic>> login(String username, String password) async {
|
|
284
|
+
try {
|
|
285
|
+
final response = await http.post(
|
|
286
|
+
Uri.parse('$baseUrl/auth/login'),
|
|
287
|
+
headers: {'Content-Type': 'application/json'},
|
|
288
|
+
body: json.encode({
|
|
289
|
+
'username': username,
|
|
290
|
+
'password': password,
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (response.statusCode == 200) {
|
|
295
|
+
final data = json.decode(response.body);
|
|
296
|
+
|
|
297
|
+
// 保存 token
|
|
298
|
+
if (data['token'] != null) {
|
|
299
|
+
await saveToken(data['token']);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 保存用户信息
|
|
303
|
+
if (data['user'] != null) {
|
|
304
|
+
await saveUser(data['user']);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return data;
|
|
308
|
+
} else {
|
|
309
|
+
throw Exception('Login failed: \${response.statusCode}');
|
|
310
|
+
}
|
|
311
|
+
} catch (e) {
|
|
312
|
+
throw Exception('Login error: \$e');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 注册
|
|
317
|
+
Future<Map<String, dynamic>> register(Map<String, dynamic> userData) async {
|
|
318
|
+
try {
|
|
319
|
+
final response = await http.post(
|
|
320
|
+
Uri.parse('$baseUrl/auth/register'),
|
|
321
|
+
headers: {'Content-Type': 'application/json'},
|
|
322
|
+
body: json.encode(userData),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
if (response.statusCode == 201 || response.statusCode == 200) {
|
|
326
|
+
return json.decode(response.body);
|
|
327
|
+
} else {
|
|
328
|
+
throw Exception('Register failed: \${response.statusCode}');
|
|
329
|
+
}
|
|
330
|
+
} catch (e) {
|
|
331
|
+
throw Exception('Register error: \$e');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 登出
|
|
336
|
+
Future<void> logout() async {
|
|
337
|
+
try {
|
|
338
|
+
final prefs = await SharedPreferences.getInstance();
|
|
339
|
+
await prefs.remove(_tokenKey);
|
|
340
|
+
await prefs.remove(_userKey);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
throw Exception('Logout error: \$e');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 保存 Token
|
|
347
|
+
Future<bool> saveToken(String token) async {
|
|
348
|
+
try {
|
|
349
|
+
final prefs = await SharedPreferences.getInstance();
|
|
350
|
+
return await prefs.setString(_tokenKey, token);
|
|
351
|
+
} catch (e) {
|
|
352
|
+
throw Exception('Failed to save token: \$e');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 获取 Token
|
|
357
|
+
Future<String?> getToken() async {
|
|
358
|
+
try {
|
|
359
|
+
final prefs = await SharedPreferences.getInstance();
|
|
360
|
+
return prefs.getString(_tokenKey);
|
|
361
|
+
} catch (e) {
|
|
362
|
+
throw Exception('Failed to get token: \$e');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 保存用户信息
|
|
367
|
+
Future<bool> saveUser(Map<String, dynamic> user) async {
|
|
368
|
+
try {
|
|
369
|
+
final prefs = await SharedPreferences.getInstance();
|
|
370
|
+
return await prefs.setString(_userKey, json.encode(user));
|
|
371
|
+
} catch (e) {
|
|
372
|
+
throw Exception('Failed to save user: \$e');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 获取用户信息
|
|
377
|
+
Future<Map<String, dynamic>?> getUser() async {
|
|
378
|
+
try {
|
|
379
|
+
final prefs = await SharedPreferences.getInstance();
|
|
380
|
+
final userString = prefs.getString(_userKey);
|
|
381
|
+
if (userString == null) return null;
|
|
382
|
+
return json.decode(userString);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
throw Exception('Failed to get user: \$e');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 检查是否已登录
|
|
389
|
+
Future<bool> isLoggedIn() async {
|
|
390
|
+
try {
|
|
391
|
+
final token = await getToken();
|
|
392
|
+
return token != null && token.isNotEmpty;
|
|
393
|
+
} catch (e) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 获取认证头
|
|
399
|
+
Future<Map<String, String>> getAuthHeaders() async {
|
|
400
|
+
final token = await getToken();
|
|
401
|
+
return {
|
|
402
|
+
'Content-Type': 'application/json',
|
|
403
|
+
if (token != null) 'Authorization': 'Bearer $token',
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
`;
|
|
408
|
+
}
|