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.
Files changed (45) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +17 -4
  3. package/config/dev.config.js +11 -11
  4. package/config/templates.js +10 -10
  5. package/index.js +554 -102
  6. package/lib/commands/add.js +365 -266
  7. package/lib/commands/assets.js +77 -78
  8. package/lib/commands/cache.js +29 -52
  9. package/lib/commands/completion.js +13 -11
  10. package/lib/commands/config.js +150 -44
  11. package/lib/commands/init-ai-base.js +89 -0
  12. package/lib/commands/newClack.js +269 -178
  13. package/lib/commands/snippets.js +58 -43
  14. package/lib/commands/template.js +98 -58
  15. package/lib/commands/templates.js +101 -57
  16. package/lib/commands/upload.js +313 -0
  17. package/lib/commands/vnext-options.js +206 -0
  18. package/lib/generators/model_generator.js +91 -88
  19. package/lib/generators/page_generator.js +100 -93
  20. package/lib/generators/service_generator.js +44 -39
  21. package/lib/generators/viewmodel_generator.js +25 -29
  22. package/lib/generators/widget_generator.js +30 -35
  23. package/lib/templates/templateCopier.js +14 -15
  24. package/lib/templates/templateManager.js +22 -21
  25. package/lib/utils/config.js +37 -20
  26. package/lib/utils/flutterHelper.js +2 -2
  27. package/lib/utils/i18n.js +3 -3
  28. package/lib/utils/index_updater.js +22 -23
  29. package/lib/utils/json-output.js +59 -0
  30. package/lib/utils/logger.js +17 -17
  31. package/lib/utils/project_detector.js +66 -66
  32. package/lib/utils/snippet_loader.js +21 -19
  33. package/lib/utils/string_helper.js +13 -13
  34. package/lib/utils/templateSelectorEnquirer.js +94 -108
  35. package/locales/en-US.json +1 -1
  36. package/locales/zh-CN.json +2 -2
  37. package/package.json +60 -57
  38. package/scripts/smoke-vnext-generate.mjs +1934 -0
  39. package/scripts/smoke-vnext-params.mjs +92 -0
  40. package/CLI.md +0 -513
  41. package/release.sh +0 -529
  42. package/scripts/e2e-state-tests.js +0 -116
  43. package/scripts/sync-base-to-templates.js +0 -108
  44. package/scripts/workspace-clone-all.sh +0 -101
  45. package/scripts/workspace-status-all.sh +0 -112
@@ -2,89 +2,94 @@
2
2
  * Service 生成器
3
3
  */
4
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';
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
11
 
12
12
  /**
13
13
  * 生成 Service 文件
14
14
  */
15
- export function generateService (name, options = {}) {
15
+ export function generateService(name, options = {}) {
16
16
  try {
17
17
  const {
18
18
  feature = null,
19
19
  type = 'api', // api, storage, auth
20
- outputDir = process.cwd()
21
- } = options;
20
+ outputDir = process.cwd(),
21
+ } = options
22
22
 
23
23
  // 转换命名
24
- const namePascal = toPascalCase(name);
25
- const nameSnake = toSnakeCase(name);
24
+ const namePascal = toPascalCase(name)
25
+ const nameSnake = toSnakeCase(name)
26
26
 
27
27
  // 确定输出路径
28
- let servicesDir;
28
+ let servicesDir
29
29
  if (feature) {
30
30
  // Modular/Clean 架构
31
- servicesDir = join(outputDir, 'lib', 'features', feature, 'services');
31
+ servicesDir = join(outputDir, 'lib', 'features', feature, 'services')
32
32
  } else {
33
33
  // Lite 架构
34
- servicesDir = join(outputDir, 'lib', 'services');
34
+ servicesDir = join(outputDir, 'lib', 'services')
35
35
  }
36
36
 
37
37
  // 创建目录
38
38
  if (!existsSync(servicesDir)) {
39
- mkdirSync(servicesDir, { recursive: true });
39
+ mkdirSync(servicesDir, { recursive: true })
40
40
  }
41
41
 
42
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);
43
+ const keyMap = {
44
+ api: 'flu.service.api',
45
+ storage: 'flu.service.storage',
46
+ auth: 'flu.service.auth',
47
+ }
48
+ const key = keyMap[type] || keyMap.api
49
+ const content =
50
+ getSnippetContent(outputDir, key, { Name: namePascal }) ||
51
+ generateServiceContent(namePascal, type)
46
52
 
47
53
  // 写入文件
48
- const filePath = join(servicesDir, `${nameSnake}_service.dart`);
54
+ const filePath = join(servicesDir, `${nameSnake}_service.dart`)
49
55
  if (existsSync(filePath)) {
50
- logger.error(`文件已存在: ${filePath}`);
51
- return false;
56
+ logger.error(`文件已存在: ${filePath}`)
57
+ return false
52
58
  }
53
59
 
54
- writeFileSync(filePath, content, 'utf8');
55
- logger.success(`Service 创建成功: ${filePath}`);
60
+ writeFileSync(filePath, content, 'utf8')
61
+ logger.success(`Service 创建成功: ${filePath}`)
56
62
 
57
63
  // 更新 index.dart
58
- updateIndexFile(servicesDir, `${nameSnake}_service.dart`);
59
-
60
- return true;
64
+ updateIndexFile(servicesDir, `${nameSnake}_service.dart`)
61
65
 
66
+ return true
62
67
  } catch (error) {
63
- logger.error(`生成 Service 失败: ${error.message}`);
64
- return false;
68
+ logger.error(`生成 Service 失败: ${error.message}`)
69
+ return false
65
70
  }
66
71
  }
67
72
 
68
73
  /**
69
74
  * 生成 Service 内容
70
75
  */
71
- function generateServiceContent (namePascal, type) {
76
+ function generateServiceContent(namePascal, type) {
72
77
  switch (type) {
73
78
  case 'api':
74
- return generateApiService(namePascal);
79
+ return generateApiService(namePascal)
75
80
  case 'storage':
76
- return generateStorageService(namePascal);
81
+ return generateStorageService(namePascal)
77
82
  case 'auth':
78
- return generateAuthService(namePascal);
83
+ return generateAuthService(namePascal)
79
84
  default:
80
- return generateApiService(namePascal);
85
+ return generateApiService(namePascal)
81
86
  }
82
87
  }
83
88
 
84
89
  /**
85
90
  * 生成 API Service
86
91
  */
87
- function generateApiService (namePascal) {
92
+ function generateApiService(namePascal) {
88
93
  return `import 'dart:convert';
89
94
  import 'package:http/http.dart' as http;
90
95
 
@@ -163,13 +168,13 @@ class ${namePascal}Service {
163
168
  }
164
169
  }
165
170
  }
166
- `;
171
+ `
167
172
  }
168
173
 
169
174
  /**
170
175
  * 生成 Storage Service
171
176
  */
172
- function generateStorageService (namePascal) {
177
+ function generateStorageService(namePascal) {
173
178
  return `import 'package:shared_preferences/shared_preferences.dart';
174
179
  import 'dart:convert';
175
180
 
@@ -263,13 +268,13 @@ class ${namePascal}Service {
263
268
  }
264
269
  }
265
270
  }
266
- `;
271
+ `
267
272
  }
268
273
 
269
274
  /**
270
275
  * 生成 Auth Service
271
276
  */
272
- function generateAuthService (namePascal) {
277
+ function generateAuthService(namePascal) {
273
278
  return `import 'dart:convert';
274
279
  import 'package:http/http.dart' as http;
275
280
  import 'package:shared_preferences/shared_preferences.dart';
@@ -404,5 +409,5 @@ class ${namePascal}Service {
404
409
  };
405
410
  }
406
411
  }
407
- `;
412
+ `
408
413
  }
@@ -2,69 +2,65 @@
2
2
  * ViewModel 生成器
3
3
  */
4
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';
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
10
 
11
11
  /**
12
12
  * 生成 ViewModel 文件
13
13
  */
14
- export function generateViewModel (name, options = {}) {
14
+ export function generateViewModel(name, options = {}) {
15
15
  try {
16
- const {
17
- feature = null,
18
- outputDir = process.cwd()
19
- } = options;
16
+ const { feature = null, outputDir = process.cwd() } = options
20
17
 
21
18
  // 转换命名
22
- const namePascal = toPascalCase(name);
23
- const nameSnake = toSnakeCase(name);
19
+ const namePascal = toPascalCase(name)
20
+ const nameSnake = toSnakeCase(name)
24
21
 
25
22
  // 确定输出路径
26
- let vmDir;
23
+ let vmDir
27
24
  if (feature) {
28
25
  // Modular/Clean 架构
29
- vmDir = join(outputDir, 'lib', 'features', feature, 'viewmodels');
26
+ vmDir = join(outputDir, 'lib', 'features', feature, 'viewmodels')
30
27
  } else {
31
28
  // Lite 架构
32
- vmDir = join(outputDir, 'lib', 'viewmodels');
29
+ vmDir = join(outputDir, 'lib', 'viewmodels')
33
30
  }
34
31
 
35
32
  // 创建目录
36
33
  if (!existsSync(vmDir)) {
37
- mkdirSync(vmDir, { recursive: true });
34
+ mkdirSync(vmDir, { recursive: true })
38
35
  }
39
36
 
40
37
  // 生成文件内容
41
- const content = generateViewModelContent(namePascal);
38
+ const content = generateViewModelContent(namePascal)
42
39
 
43
40
  // 写入文件
44
- const filePath = join(vmDir, `${nameSnake}_viewmodel.dart`);
41
+ const filePath = join(vmDir, `${nameSnake}_viewmodel.dart`)
45
42
  if (existsSync(filePath)) {
46
- logger.error(`文件已存在: ${filePath}`);
47
- return false;
43
+ logger.error(`文件已存在: ${filePath}`)
44
+ return false
48
45
  }
49
46
 
50
- writeFileSync(filePath, content, 'utf8');
51
- logger.success(`ViewModel 创建成功: ${filePath}`);
47
+ writeFileSync(filePath, content, 'utf8')
48
+ logger.success(`ViewModel 创建成功: ${filePath}`)
52
49
 
53
50
  // 更新 index.dart
54
- updateIndexFile(vmDir, `${nameSnake}_viewmodel.dart`);
55
-
56
- return true;
51
+ updateIndexFile(vmDir, `${nameSnake}_viewmodel.dart`)
57
52
 
53
+ return true
58
54
  } catch (error) {
59
- logger.error(`生成 ViewModel 失败: ${error.message}`);
60
- return false;
55
+ logger.error(`生成 ViewModel 失败: ${error.message}`)
56
+ return false
61
57
  }
62
58
  }
63
59
 
64
60
  /**
65
61
  * 生成 ViewModel 内容
66
62
  */
67
- function generateViewModelContent (namePascal) {
63
+ function generateViewModelContent(namePascal) {
68
64
  return `import 'package:flutter/foundation.dart';
69
65
 
70
66
  class ${namePascal}ViewModel extends ChangeNotifier {
@@ -111,5 +107,5 @@ class ${namePascal}ViewModel extends ChangeNotifier {
111
107
  super.dispose();
112
108
  }
113
109
  }
114
- `;
110
+ `
115
111
  }
@@ -2,71 +2,66 @@
2
2
  * Widget 生成器
3
3
  */
4
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 { detectProjectTemplate, getWidgetPath } from '../utils/project_detector.js';
11
- import { getSnippetContent } from '../utils/snippet_loader.js';
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 { detectProjectTemplate, getWidgetPath } from '../utils/project_detector.js'
11
+ import { getSnippetContent } from '../utils/snippet_loader.js'
12
12
 
13
13
  /**
14
14
  * 生成 Widget 文件
15
15
  */
16
- export function generateWidget (name, options = {}) {
16
+ export function generateWidget(name, options = {}) {
17
17
  try {
18
- const {
19
- feature = null,
20
- stateful = false,
21
- outputDir = process.cwd()
22
- } = options;
18
+ const { feature = null, stateful = false, outputDir = process.cwd() } = options
23
19
 
24
20
  // 检测项目模板类型
25
- const template = detectProjectTemplate(outputDir);
26
- logger.info(`检测到项目类型: ${template || '未知'}`);
21
+ const template = detectProjectTemplate(outputDir)
22
+ logger.info(`检测到项目类型: ${template || '未知'}`)
27
23
 
28
24
  // 转换命名
29
- const namePascal = toPascalCase(name);
30
- const nameSnake = toSnakeCase(name);
25
+ const namePascal = toPascalCase(name)
26
+ const nameSnake = toSnakeCase(name)
31
27
 
32
28
  // 根据模板类型确定输出路径
33
- const widgetsDir = getWidgetPath(outputDir, feature);
29
+ const widgetsDir = getWidgetPath(outputDir, feature)
34
30
 
35
31
  // 创建目录
36
32
  if (!existsSync(widgetsDir)) {
37
- mkdirSync(widgetsDir, { recursive: true });
33
+ mkdirSync(widgetsDir, { recursive: true })
38
34
  }
39
35
 
40
36
  // 生成文件内容(优先片段)
41
- const key = stateful ? 'flu.stWidget' : 'flu.lessWidget';
42
- const contentFromSnippet = getSnippetContent(outputDir, key, { Name: namePascal });
43
- const content = contentFromSnippet || generateWidgetContent(namePascal, stateful);
37
+ const key = stateful ? 'flu.stWidget' : 'flu.lessWidget'
38
+ const contentFromSnippet = getSnippetContent(outputDir, key, { Name: namePascal })
39
+ const content = contentFromSnippet || generateWidgetContent(namePascal, stateful)
44
40
 
45
41
  // 写入文件
46
- const filePath = join(widgetsDir, `${nameSnake}_widget.dart`);
42
+ const filePath = join(widgetsDir, `${nameSnake}_widget.dart`)
47
43
  if (existsSync(filePath)) {
48
- logger.error(`文件已存在: ${filePath}`);
49
- return false;
44
+ logger.error(`文件已存在: ${filePath}`)
45
+ return false
50
46
  }
51
47
 
52
- writeFileSync(filePath, content, 'utf8');
53
- logger.success(`Widget 创建成功: ${filePath}`);
48
+ writeFileSync(filePath, content, 'utf8')
49
+ logger.success(`Widget 创建成功: ${filePath}`)
54
50
 
55
51
  // 更新 index.dart
56
- updateIndexFile(widgetsDir, `${nameSnake}_widget.dart`);
57
-
58
- return true;
52
+ updateIndexFile(widgetsDir, `${nameSnake}_widget.dart`)
59
53
 
54
+ return true
60
55
  } catch (error) {
61
- logger.error(`生成 Widget 失败: ${error.message}`);
62
- return false;
56
+ logger.error(`生成 Widget 失败: ${error.message}`)
57
+ return false
63
58
  }
64
59
  }
65
60
 
66
61
  /**
67
62
  * 生成 Widget 内容
68
63
  */
69
- function generateWidgetContent (namePascal, stateful) {
64
+ function generateWidgetContent(namePascal, stateful) {
70
65
  if (stateful) {
71
66
  return `import 'package:flutter/material.dart';
72
67
 
@@ -85,7 +80,7 @@ class _${namePascal}WidgetState extends State<${namePascal}Widget> {
85
80
  );
86
81
  }
87
82
  }
88
- `;
83
+ `
89
84
  } else {
90
85
  return `import 'package:flutter/material.dart';
91
86
 
@@ -99,6 +94,6 @@ class ${namePascal}Widget extends StatelessWidget {
99
94
  );
100
95
  }
101
96
  }
102
- `;
97
+ `
103
98
  }
104
99
  }
@@ -7,28 +7,27 @@ export {
7
7
  copyCustomTemplate,
8
8
  replaceVariables,
9
9
  ensurePubspecName,
10
- cleanupTemplateFiles
11
- } from 'flu-cli-core';
10
+ cleanupTemplateFiles,
11
+ } from 'flu-cli-core'
12
12
 
13
13
  /**
14
- * 使用模板的 pubspec.yaml 合并覆盖目标项目 (仅限 V2 保持逻辑)
14
+ * 使用模板的 pubspec.yaml 合并覆盖目标项目(历史兼容保留)
15
15
  */
16
- export async function mergePubspecFromTemplate (templatePath, projectDir, projectName) {
17
- // 暂时保留该逻辑或移动到 core (考虑到 core 主要是 ProjectGenerator 使用,可以先在 v2 桥接)
18
- // TODO: 后续可以完全迁移到 core
16
+ export async function mergePubspecFromTemplate(templatePath, projectDir, projectName) {
17
+ // 该逻辑仍保留在 CLI 桥接层,后续如需统一迁移到 core,可直接替换调用点。
19
18
  try {
20
- const { readFileSync, writeFileSync, existsSync } = await import('fs');
21
- const { join } = await import('path');
22
- const tplPub = join(templatePath, 'pubspec.yaml');
23
- const dstPub = join(projectDir, 'pubspec.yaml');
24
- if (!existsSync(tplPub) || !existsSync(dstPub)) return;
25
- let content = readFileSync(tplPub, 'utf8');
19
+ const { readFileSync, writeFileSync, existsSync } = await import('fs')
20
+ const { join } = await import('path')
21
+ const tplPub = join(templatePath, 'pubspec.yaml')
22
+ const dstPub = join(projectDir, 'pubspec.yaml')
23
+ if (!existsSync(tplPub) || !existsSync(dstPub)) return
24
+ let content = readFileSync(tplPub, 'utf8')
26
25
  if (/^name:\s+/m.test(content)) {
27
- content = content.replace(/^name:\s+.*/m, `name: ${projectName}`);
26
+ content = content.replace(/^name:\s+.*/m, `name: ${projectName}`)
28
27
  } else {
29
- content = `name: ${projectName}\n` + content;
28
+ content = `name: ${projectName}\n` + content
30
29
  }
31
- writeFileSync(dstPub, content, 'utf8');
30
+ writeFileSync(dstPub, content, 'utf8')
32
31
  } catch (error) {
33
32
  // 忽略错误
34
33
  }
@@ -1,48 +1,49 @@
1
1
  /**
2
2
  * 模板管理器 - 桥接至 flu-cli-core
3
3
  */
4
- import { TemplateManager } from 'flu-cli-core';
4
+ import { TemplateManager } from 'flu-cli-core'
5
5
 
6
- const templateManager = TemplateManager.getInstance();
6
+ const templateManager = TemplateManager.getInstance()
7
7
 
8
8
  /**
9
9
  * 获取模板缓存路径
10
10
  */
11
- export function getTemplateCachePath (templateName) {
11
+ export function getTemplateCachePath(templateName) {
12
12
  // 保持向前兼容,虽然 core 现在内部管理缓存路径
13
- return templateManager.getTemplatePath(templateName);
13
+ return templateManager.getTemplatePath(templateName)
14
14
  }
15
15
 
16
16
  /**
17
17
  * 克隆或更新模板
18
18
  */
19
- export async function cloneOrUpdateTemplate (templateName, repoUrl, branch = 'main', forceUpdate = false, isLocal = false, sourceLocalPath = null) {
20
- // 如果是本地模板,我们需要在 core 的配置中确保一致性,或者直接处理
21
- // 实际上 v2 的 cloneOrUpdateTemplate 承担了“查找并准备”的职责
22
- // 我们直接调用 core 的 prepareTemplate
23
-
24
- // NOTE: v2 这里的参数逻辑与 core 的 TemplateManager 略有不同
25
- // v2 会根据 templateName 去查找模板
26
- return await templateManager.prepareTemplate(templateName, undefined, forceUpdate);
19
+ export async function cloneOrUpdateTemplate(
20
+ templateName,
21
+ repoUrl,
22
+ branch = 'main',
23
+ forceUpdate = false,
24
+ isLocal = false,
25
+ sourceLocalPath = null,
26
+ ) {
27
+ // 保持 CLI 与 core 的模板准备逻辑一致,由 core 统一负责查找、缓存与准备。
28
+ return await templateManager.prepareTemplate(templateName, undefined, forceUpdate)
27
29
  }
28
30
 
29
31
  /**
30
32
  * 检查模板是否有更新
31
33
  */
32
- export async function checkTemplateUpdate (templateName) {
33
- return await templateManager.checkUpdate(templateName);
34
+ export async function checkTemplateUpdate(templateName) {
35
+ return await templateManager.checkUpdate(templateName)
34
36
  }
35
37
 
36
38
  /**
37
39
  * 获取模板信息
38
40
  */
39
- export async function getTemplateInfo (templateName) {
40
- // v2 原本返回 path, lastCommit, exists
41
- // core TemplateManager 目前没提供完整的 getTemplateInfo,但我们可以简单封装
42
- const path = templateManager.getTemplatePath(templateName);
43
- const exists = !!path; // 简化处理
41
+ export async function getTemplateInfo(templateName) {
42
+ // 保留最小兼容信息,避免 CLI 侧直接依赖模板管理实现细节。
43
+ const path = templateManager.getTemplatePath(templateName)
44
+ const exists = !!path // 简化处理
44
45
  return {
45
46
  path,
46
- exists
47
- };
47
+ exists,
48
+ }
48
49
  }
@@ -2,60 +2,77 @@
2
2
  * 配置管理 - 桥接至 flu-cli-core
3
3
  */
4
4
 
5
- import { ConfigManager } from 'flu-cli-core';
5
+ import { ConfigManager } from 'flu-cli-core'
6
6
 
7
- const configManager = ConfigManager.getInstance();
7
+ const configManager = ConfigManager.getInstance()
8
8
 
9
9
  /**
10
10
  * 读取配置 (由于 ConfigManager 内部管理,此方法仅用于向下兼容)
11
11
  */
12
- export function getConfig () {
13
- return configManager['config'] || {};
12
+ export function getConfig() {
13
+ return configManager['config'] || {}
14
14
  }
15
15
 
16
16
  /**
17
17
  * 保存配置
18
18
  */
19
- export function saveConfig (config) {
19
+ export function saveConfig(config) {
20
20
  // ConfigManager 内部会自动保存,这里仅为兼容
21
21
  }
22
22
 
23
23
  /**
24
24
  * 获取作者名字
25
25
  */
26
- export function getAuthorName () {
27
- return configManager.getAuthorName();
26
+ export function getAuthorName() {
27
+ return configManager.getAuthorName()
28
28
  }
29
29
 
30
30
  /**
31
31
  * 保存作者名字
32
32
  */
33
- export function saveAuthorName (name) {
34
- configManager.setAuthorName(name);
33
+ export function saveAuthorName(name) {
34
+ try {
35
+ configManager.setAuthorName(name)
36
+ } catch (error) {
37
+ if (!shouldIgnoreConfigPersistenceError(error)) {
38
+ throw error
39
+ }
40
+ }
35
41
  }
36
42
 
37
43
  // 读取默认模板(builtin/custom)
38
- export function getDefaultTemplate () {
39
- return configManager.getDefaultTemplate();
44
+ export function getDefaultTemplate() {
45
+ return configManager.getDefaultTemplate()
40
46
  }
41
47
 
42
48
  // 保存默认模板(builtin/custom)
43
- export function saveDefaultTemplate (defaultTemplate) {
44
- configManager.setDefaultTemplate(defaultTemplate.type, defaultTemplate.idOrName);
49
+ export function saveDefaultTemplate(defaultTemplate) {
50
+ try {
51
+ configManager.setDefaultTemplate(defaultTemplate.type, defaultTemplate.idOrName)
52
+ } catch (error) {
53
+ if (!shouldIgnoreConfigPersistenceError(error)) {
54
+ throw error
55
+ }
56
+ }
45
57
  }
46
58
 
47
59
  // 获取自定义模板列表
48
- export function getCustomTemplates () {
49
- return configManager.getTemplates();
60
+ export function getCustomTemplates() {
61
+ return configManager.getTemplates()
50
62
  }
51
63
 
52
64
  // 根据 id 查找自定义模板
53
- export function findCustomTemplateById (id) {
54
- return configManager.getTemplate(id) || null;
65
+ export function findCustomTemplateById(id) {
66
+ return configManager.getTemplate(id) || null
55
67
  }
56
68
 
57
69
  // 新增或更新自定义模板
58
- export function addOrUpdateCustomTemplate (tpl) {
59
- configManager.addTemplate(tpl);
60
- return tpl;
70
+ export function addOrUpdateCustomTemplate(tpl) {
71
+ configManager.addTemplate(tpl)
72
+ return tpl
73
+ }
74
+
75
+ function shouldIgnoreConfigPersistenceError(error) {
76
+ const code = error?.code
77
+ return code === 'EPERM' || code === 'EACCES' || code === 'EROFS'
61
78
  }
@@ -6,5 +6,5 @@ export {
6
6
  runFlutterCreate,
7
7
  runFlutterPubGet,
8
8
  checkFlutterInstalled,
9
- getFlutterVersion
10
- } from 'flu-cli-core';
9
+ getFlutterVersion,
10
+ } from 'flu-cli-core'
package/lib/utils/i18n.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 国际化工具 - 桥接至 flu-cli-core
3
3
  */
4
- import { I18nManager, t as coreT } from 'flu-cli-core';
4
+ import { I18nManager, t as coreT } from 'flu-cli-core'
5
5
 
6
- export const i18n = I18nManager.getInstance();
7
- export const t = (key, params) => coreT(key, params);
6
+ export const i18n = I18nManager.getInstance()
7
+ export const t = (key, params) => coreT(key, params)