flu-cli 2.0.5 → 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
|
@@ -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
|
|
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 = {
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
14
|
+
* 使用模板的 pubspec.yaml 合并覆盖目标项目(历史兼容保留)
|
|
15
15
|
*/
|
|
16
|
-
export async function mergePubspecFromTemplate
|
|
17
|
-
//
|
|
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
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
const
|
|
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
|
}
|
package/lib/utils/config.js
CHANGED
|
@@ -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
|
|
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
|
|
34
|
-
|
|
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
|
|
44
|
-
|
|
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
|
|
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
|
|
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
|
}
|
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)
|