flu-cli 2.0.3 → 2.0.4

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.
@@ -1,191 +1,48 @@
1
1
  /**
2
- * 模板管理器
3
- * 负责克隆、更新和管理模板缓存
2
+ * 模板管理器 - 桥接至 @flu-cli/core
4
3
  */
4
+ import { TemplateManager } from '@flu-cli/core';
5
5
 
6
- import simpleGit from 'simple-git';
7
- import { join, dirname } from 'path';
8
- import { existsSync, mkdirSync } from 'fs';
9
- import { homedir } from 'os';
10
- import { fileURLToPath } from 'url';
11
- import fsExtra from 'fs-extra';
12
- import { logger } from '../utils/logger.js';
13
- import { getConfig } from '../../config/dev.config.js';
14
-
15
- const { copySync, emptyDirSync } = fsExtra;
16
-
17
- // 获取当前文件所在目录
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
-
21
- // 模板缓存目录
22
- const CACHE_DIR = join(homedir(), '.flu-cli', 'templates');
23
-
24
- // 本地模板目录(用于开发测试)
25
- const LOCAL_TEMPLATES_DIR = join(__dirname, '..', '..', 'templates');
26
-
27
- /**
28
- * 确保缓存目录存在
29
- */
30
- function ensureCacheDir () {
31
- if (!existsSync(CACHE_DIR)) {
32
- mkdirSync(CACHE_DIR, { recursive: true });
33
- }
34
- }
6
+ const templateManager = TemplateManager.getInstance();
35
7
 
36
8
  /**
37
9
  * 获取模板缓存路径
38
10
  */
39
11
  export function getTemplateCachePath (templateName) {
40
- return join(CACHE_DIR, templateName);
12
+ // 保持向前兼容,虽然 core 现在内部管理缓存路径
13
+ return templateManager.getTemplatePath(templateName);
41
14
  }
42
15
 
43
16
  /**
44
17
  * 克隆或更新模板
45
- * @param {string} templateName - 模板名称
46
- * @param {string} repoUrl - Git 仓库地址(可选,本地模板不需要)
47
- * @param {string} branch - 分支名称
48
- * @param {boolean} forceUpdate - 是否强制更新
49
- * @param {boolean} isLocal - 是否为本地模板
50
- * @param {string} sourceLocalPath - 自定义本地模板路径
51
- * @returns {Promise<string>} 模板路径
52
18
  */
53
19
  export async function cloneOrUpdateTemplate (templateName, repoUrl, branch = 'main', forceUpdate = false, isLocal = false, sourceLocalPath = null) {
54
- try {
55
- ensureCacheDir();
56
-
57
- const templatePath = getTemplateCachePath(templateName);
58
- const localTemplatePath = sourceLocalPath || join(LOCAL_TEMPLATES_DIR, templateName);
59
-
60
- // 优先级判断:
61
- // 1. 如果明确指定 isLocal=false 且有 repoUrl,使用远程
62
- // 2. 如果本地模板存在且 isLocal=true,使用本地
63
- // 3. 如果本地模板不存在但有 repoUrl,使用远程
64
- // 4. 否则报错
65
-
66
- const shouldUseLocal = isLocal && existsSync(localTemplatePath);
67
- const shouldUseRemote = repoUrl && (!isLocal || !existsSync(localTemplatePath));
68
-
69
- // 使用本地模板
70
- if (shouldUseLocal) {
71
- logger.info(`使用本地模板: ${templateName}`);
72
-
73
- // 如果缓存不存在或需要强制更新,复制本地模板到缓存
74
- if (!existsSync(templatePath) || forceUpdate) {
75
- if (existsSync(templatePath)) {
76
- emptyDirSync(templatePath);
77
- }
78
- copySync(localTemplatePath, templatePath, { overwrite: true });
79
- } else {
80
- logger.info(`使用缓存的本地模板`);
81
- }
82
-
83
- return templatePath;
84
- }
85
-
86
- // 使用远程 Git 模板
87
- if (shouldUseRemote) {
88
- logger.info(`使用远程模板: ${templateName}`);
89
-
90
- const git = simpleGit();
20
+ // 如果是本地模板,我们需要在 core 的配置中确保一致性,或者直接处理
21
+ // 实际上 v2 的 cloneOrUpdateTemplate 承担了“查找并准备”的职责
22
+ // 我们直接调用 core 的 prepareTemplate
91
23
 
92
- // 如果模板已存在
93
- if (existsSync(templatePath)) {
94
- if (forceUpdate) {
95
- const templateGit = simpleGit(templatePath);
96
- await templateGit.fetch();
97
- await templateGit.reset(['--hard', `origin/${branch}`]);
98
- await templateGit.clean('f', ['-d', '-x']);
99
- } else {
100
- logger.info(`使用缓存的远程模板`);
101
- }
102
-
103
- return templatePath;
104
- }
105
-
106
- // 克隆新模板
107
- logger.info(`从 Gitee 克隆模板 ${templateName}...`);
108
- try {
109
- await git.clone(repoUrl, templatePath, ['--branch', branch, '--single-branch', '--depth', '1']);
110
- logger.success(`模板 ${templateName} 克隆成功`);
111
- } catch (cloneError) {
112
- logger.error(`克隆模板失败: ${cloneError.message}`);
113
- logger.warn('可能的原因:');
114
- logger.warn(' 1. 网络连接问题');
115
- logger.warn(' 2. 仓库不存在或无访问权限');
116
- logger.warn(' 3. Git 未安装或配置错误');
117
- logger.info('提示:可以使用 --remote 选项尝试从远程下载');
118
- return null;
119
- }
120
-
121
- return templatePath;
122
- }
123
-
124
- // 无法获取模板
125
- logger.error(`无法获取模板 ${templateName}: 本地模板不存在且未提供远程仓库地址`);
126
- return null;
127
-
128
- } catch (error) {
129
- logger.error(`模板操作失败: ${error.message}`);
130
- return null;
131
- }
24
+ // NOTE: v2 这里的参数逻辑与 core 的 TemplateManager 略有不同
25
+ // v2 会根据 templateName 去查找模板
26
+ return await templateManager.prepareTemplate(templateName, undefined, forceUpdate);
132
27
  }
133
28
 
134
29
  /**
135
30
  * 检查模板是否有更新
136
31
  */
137
32
  export async function checkTemplateUpdate (templateName) {
138
- try {
139
- const templatePath = getTemplateCachePath(templateName);
140
-
141
- if (!existsSync(templatePath)) {
142
- return { hasUpdate: false, message: '模板未缓存' };
143
- }
144
-
145
- const git = simpleGit(templatePath);
146
-
147
- // 获取本地和远程的最新提交
148
- await git.fetch();
149
- const status = await git.status();
150
-
151
- if (status.behind > 0) {
152
- return {
153
- hasUpdate: true,
154
- message: `有 ${status.behind} 个新提交`,
155
- behind: status.behind
156
- };
157
- }
158
-
159
- return { hasUpdate: false, message: '已是最新版本' };
160
-
161
- } catch (error) {
162
- logger.error(`检查更新失败: ${error.message}`);
163
- return { hasUpdate: false, message: '检查失败' };
164
- }
33
+ return await templateManager.checkUpdate(templateName);
165
34
  }
166
35
 
167
36
  /**
168
37
  * 获取模板信息
169
38
  */
170
39
  export async function getTemplateInfo (templateName) {
171
- try {
172
- const templatePath = getTemplateCachePath(templateName);
173
-
174
- if (!existsSync(templatePath)) {
175
- return null;
176
- }
177
-
178
- const git = simpleGit(templatePath);
179
- const log = await git.log({ maxCount: 1 });
180
-
181
- return {
182
- path: templatePath,
183
- lastCommit: log.latest,
184
- exists: true
185
- };
186
-
187
- } catch (error) {
188
- logger.error(`获取模板信息失败: ${error.message}`);
189
- return null;
190
- }
40
+ // v2 原本返回 path, lastCommit, exists
41
+ // core TemplateManager 目前没提供完整的 getTemplateInfo,但我们可以简单封装
42
+ const path = templateManager.getTemplatePath(templateName);
43
+ const exists = !!path; // 简化处理
44
+ return {
45
+ path,
46
+ exists
47
+ };
191
48
  }
@@ -1,99 +1,61 @@
1
1
  /**
2
- * 配置管理 - 缓存用户设置
2
+ * 配置管理 - 桥接至 @flu-cli/core
3
3
  */
4
4
 
5
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
6
- import { join, dirname } from 'path';
7
- import { homedir } from 'os';
5
+ import { ConfigManager } from '@flu-cli/core';
8
6
 
9
- const CONFIG_DIR = join(homedir(), '.flu-cli');
10
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
7
+ const configManager = ConfigManager.getInstance();
11
8
 
12
9
  /**
13
- * 读取配置
10
+ * 读取配置 (由于 ConfigManager 内部管理,此方法仅用于向下兼容)
14
11
  */
15
12
  export function getConfig () {
16
- try {
17
- if (existsSync(CONFIG_FILE)) {
18
- const data = readFileSync(CONFIG_FILE, 'utf8');
19
- return JSON.parse(data);
20
- }
21
- } catch (error) {
22
- // 忽略错误,返回默认配置
23
- }
24
- return {};
13
+ return configManager['config'] || {};
25
14
  }
26
15
 
27
16
  /**
28
17
  * 保存配置
29
18
  */
30
19
  export function saveConfig (config) {
31
- try {
32
- // 确保目录存在
33
- if (!existsSync(CONFIG_DIR)) {
34
- mkdirSync(CONFIG_DIR, { recursive: true });
35
- }
36
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
37
- } catch (error) {
38
- // 忽略错误
39
- }
20
+ // ConfigManager 内部会自动保存,这里仅为兼容
40
21
  }
41
22
 
42
23
  /**
43
24
  * 获取作者名字
44
25
  */
45
26
  export function getAuthorName () {
46
- const config = getConfig();
47
- return config.authorName || 'Your Name';
27
+ return configManager.getAuthorName();
48
28
  }
49
29
 
50
30
  /**
51
31
  * 保存作者名字
52
32
  */
53
33
  export function saveAuthorName (name) {
54
- const config = getConfig();
55
- config.authorName = name;
56
- saveConfig(config);
34
+ configManager.setAuthorName(name);
57
35
  }
58
36
 
59
37
  // 读取默认模板(builtin/custom)
60
38
  export function getDefaultTemplate () {
61
- const config = getConfig();
62
- return config.defaultTemplate || { type: 'builtin', idOrName: 'lite' };
39
+ return configManager.getDefaultTemplate();
63
40
  }
64
41
 
65
42
  // 保存默认模板(builtin/custom)
66
43
  export function saveDefaultTemplate (defaultTemplate) {
67
- const config = getConfig();
68
- config.defaultTemplate = defaultTemplate;
69
- saveConfig(config);
44
+ configManager.setDefaultTemplate(defaultTemplate.type, defaultTemplate.idOrName);
70
45
  }
71
46
 
72
47
  // 获取自定义模板列表
73
48
  export function getCustomTemplates () {
74
- const config = getConfig();
75
- return Array.isArray(config.customTemplates) ? config.customTemplates : [];
49
+ return configManager.getTemplates();
76
50
  }
77
51
 
78
52
  // 根据 id 查找自定义模板
79
53
  export function findCustomTemplateById (id) {
80
- const list = getCustomTemplates();
81
- return list.find(t => t.id === id) || null;
54
+ return configManager.getTemplate(id) || null;
82
55
  }
83
56
 
84
57
  // 新增或更新自定义模板
85
58
  export function addOrUpdateCustomTemplate (tpl) {
86
- const config = getConfig();
87
- const list = Array.isArray(config.customTemplates) ? config.customTemplates : [];
88
- const idx = list.findIndex(x => x.id === tpl.id);
89
- if (idx >= 0) {
90
- list[idx] = { ...list[idx], ...tpl };
91
- } else {
92
- list.push(tpl);
93
- }
94
- config.customTemplates = list;
95
- // 顺带更新最后使用时间
96
- tpl.lastUsedAt = Date.now();
97
- saveConfig(config);
59
+ configManager.addTemplate(tpl);
98
60
  return tpl;
99
61
  }
@@ -1,85 +1,10 @@
1
1
  /**
2
- * Flutter 辅助工具
3
- * 职责:封装 flutter create/pub get/版本检测等命令调用
2
+ * Flutter 辅助工具 - 桥接至 @flu-cli/core
4
3
  */
5
4
 
6
- import { exec } from 'child_process';
7
- import { promisify } from 'util';
8
- import { logger } from './logger.js';
9
-
10
- const execAsync = promisify(exec);
11
-
12
- /**
13
- * 运行 flutter create 命令
14
- * @param {string} projectDir - 项目目录
15
- * @param {string} projectName - 项目名称
16
- * @param {string} packageName - 包名
17
- * @returns {Promise<boolean>} 是否成功
18
- */
19
- export async function runFlutterCreate (projectDir, projectName, packageName) {
20
- try {
21
- const command = `flutter create --project-name ${projectName} --org ${packageName} ${projectDir}`;
22
-
23
- const { stdout, stderr } = await execAsync(command);
24
-
25
- if (stderr && !stderr.includes('Warning')) {
26
- logger.warn(stderr);
27
- }
28
-
29
- return stdout.includes('All done!') || stdout.includes('Created project');
30
-
31
- } catch (error) {
32
- logger.error(`Flutter create 失败: ${error.message}`);
33
- return false;
34
- }
35
- }
36
-
37
- /**
38
- * 运行 flutter pub get
39
- * @param {string} projectDir - 项目目录
40
- * @returns {Promise<boolean>} 是否成功
41
- */
42
- export async function runFlutterPubGet (projectDir) {
43
- try {
44
- const { stdout, stderr } = await execAsync('flutter pub get', {
45
- cwd: projectDir
46
- });
47
-
48
- if (stderr) {
49
- logger.warn(stderr);
50
- }
51
-
52
- return true;
53
-
54
- } catch (error) {
55
- logger.error(`Flutter pub get 失败: ${error.message}`);
56
- return false;
57
- }
58
- }
59
-
60
- /**
61
- * 检查 Flutter 是否安装
62
- * @returns {Promise<boolean>} 是否已安装
63
- */
64
- export async function checkFlutterInstalled () {
65
- try {
66
- await execAsync('flutter --version');
67
- return true;
68
- } catch (error) {
69
- return false;
70
- }
71
- }
72
-
73
- /**
74
- * 获取 Flutter 版本
75
- * @returns {Promise<string|null>} Flutter 版本
76
- */
77
- export async function getFlutterVersion () {
78
- try {
79
- const { stdout } = await execAsync('flutter --version');
80
- const match = stdout.match(/Flutter (\d+\.\d+\.\d+)/);
81
- return match ? match[1] : null;
82
- } catch (error) {
83
- return null;
84
- }
85
- }
5
+ export {
6
+ runFlutterCreate,
7
+ runFlutterPubGet,
8
+ checkFlutterInstalled,
9
+ getFlutterVersion
10
+ } from '@flu-cli/core';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 国际化工具 - 桥接至 @flu-cli/core
3
+ */
4
+ import { I18nManager, t as coreT } from '@flu-cli/core';
5
+
6
+ export const i18n = I18nManager.getInstance();
7
+ export const t = (key, params) => coreT(key, params);
@@ -1,15 +1,14 @@
1
1
  /**
2
2
  * 使用 Enquirer 实现实时模板预览
3
- * 当用户上下切换时,实时显示对应模板的结构
3
+ * 职责:负责 CLI 环境下的模板选择交互逻辑
4
+ * 该文件从 @flu-cli/core 迁移回来,以保持 core 的 Headless 纯净。
4
5
  */
5
6
 
6
7
  import enquirer from 'enquirer';
7
8
  import chalk from 'chalk';
8
9
  import { existsSync } from 'fs';
9
10
  import { basename } from 'path';
10
- import { getAllTemplates } from '../../config/templates.js';
11
- import { ConfigManager } from '@flu-cli/core';
12
- import { getDefaultTemplate, saveDefaultTemplate } from '../utils/config.js';
11
+ import { BUILTIN_TEMPLATES, ConfigManager } from '@flu-cli/core';
13
12
 
14
13
  const { Select } = enquirer;
15
14
 
@@ -17,24 +16,27 @@ const { Select } = enquirer;
17
16
  * 选择模板(带实时预览 - Enquirer 版本)
18
17
  */
19
18
  export async function selectTemplateWithEnquirer () {
20
- const templates = getAllTemplates();
19
+ const builtinTemplates = Object.values(BUILTIN_TEMPLATES);
21
20
  const configManager = ConfigManager.getInstance();
22
21
  const customTemplates = configManager.getTemplates();
23
- const defaultTpl = getDefaultTemplate();
22
+ const defaultTpl = configManager.getDefaultTemplate();
24
23
 
25
24
  // 创建自定义的 Select prompt
26
25
  class TemplateSelect extends Select {
26
+ constructor(options) {
27
+ super(options);
28
+ }
29
+
27
30
  async render () {
28
- const { submitted, size } = this.state;
31
+ const { size } = this.state;
29
32
  const index = this.index;
30
33
  // 选择项与模板索引可能不一致,安全获取
31
34
  const choice = this.choices[index];
32
35
  const isBuiltin = choice?.name?.startsWith('builtin:');
33
- const builtinIndex = isBuiltin ? templates.findIndex(t => `builtin:${t.name.toLowerCase()}` === choice.name) : -1;
34
- const template = isBuiltin && builtinIndex >= 0 ? templates[builtinIndex] : null;
36
+ const builtinKey = isBuiltin ? choice.name.split(':')[1] : '';
37
+ const template = isBuiltin ? BUILTIN_TEMPLATES[builtinKey] : null;
35
38
 
36
39
  let prompt = '';
37
- let header = await this.header();
38
40
  let prefix = await this.prefix();
39
41
  let separator = await this.separator();
40
42
  let message = await this.message();
@@ -50,9 +52,9 @@ export async function selectTemplateWithEnquirer () {
50
52
  for (let i = 0; i < this.choices.length; i++) {
51
53
  const choice = this.choices[i];
52
54
  const isSelected = i === index;
53
- const prefix = isSelected ? chalk.cyan('❯') : ' ';
55
+ const prefixStr = isSelected ? chalk.cyan('❯') : ' ';
54
56
  const style = isSelected ? chalk.cyan : chalk.gray;
55
- output += `${prefix} ${style(choice.message)}\n`;
57
+ output += `${prefixStr} ${style(choice.message)}\n`;
56
58
  if (choice.hint) {
57
59
  output += ` ${chalk.gray(choice.hint)}\n`;
58
60
  }
@@ -63,7 +65,7 @@ export async function selectTemplateWithEnquirer () {
63
65
  if (template) {
64
66
  output += chalk.yellow('📁 项目结构预览:\n\n');
65
67
  const structureLines = template.structure.trim().split('\n');
66
- structureLines.forEach(line => {
68
+ structureLines.forEach((line) => {
67
69
  output += chalk.gray(line) + '\n';
68
70
  });
69
71
  output += '\n';
@@ -80,38 +82,45 @@ export async function selectTemplateWithEnquirer () {
80
82
  }
81
83
 
82
84
  // 构建选择项:内置模板 + 自定义模板 + 新增自定义入口
83
- const builtinChoices = templates.map(t => ({
85
+ const builtinChoices = builtinTemplates.map(t => ({
84
86
  name: `builtin:${t.name.toLowerCase()}`,
85
- message: `${t.displayName}`
87
+ message: `${t.displayName}`,
86
88
  }));
87
89
  const customChoices = customTemplates.map(ct => ({
88
90
  name: `custom:${ct.id}`,
89
91
  message: `自定义:${ct.name}`,
90
- hint: ct.type === 'git' ? ct.url : `本地: ${ct.path}`
92
+ hint: ct.type === 'git' ? ct.url : `本地: ${ct.path}`,
91
93
  }));
92
- const addCustomChoice = { name: '__add_custom__', message: chalk.yellow('➕ 新增自定义模板(本地或 Git)') };
94
+ const addCustomChoice = {
95
+ name: '__add_custom__',
96
+ message: chalk.yellow('➕ 新增自定义模板(本地或 Git)'),
97
+ };
93
98
 
94
99
  const allChoices = [...builtinChoices, ...customChoices, addCustomChoice];
95
100
 
96
101
  // 计算默认选中索引
97
102
  let initialIndex = 0;
98
103
  if (defaultTpl && defaultTpl.type === 'builtin') {
99
- const idx = allChoices.findIndex(c => c.name === `builtin:${defaultTpl.idOrName}`);
104
+ const idx = allChoices.findIndex(
105
+ c => c.name === `builtin:${defaultTpl.idOrName}`
106
+ );
100
107
  if (idx >= 0) initialIndex = idx;
101
108
  } else if (defaultTpl && defaultTpl.type === 'custom') {
102
- const idx = allChoices.findIndex(c => c.name === `custom:${defaultTpl.idOrName}`);
109
+ const idx = allChoices.findIndex(
110
+ c => c.name === `custom:${defaultTpl.idOrName}`
111
+ );
103
112
  if (idx >= 0) initialIndex = idx;
104
113
  }
105
114
 
106
- const prompt = new TemplateSelect({
115
+ const templatePrompt = new TemplateSelect({
107
116
  name: 'template',
108
117
  message: '选择项目模板(使用 ↑↓ 键切换,实时查看结构)',
109
118
  choices: allChoices,
110
- initial: initialIndex
119
+ initial: initialIndex,
111
120
  });
112
121
 
113
122
  try {
114
- const answer = await prompt.run();
123
+ const answer = await templatePrompt.run();
115
124
 
116
125
  // 清除屏幕上的大量输出,只保留简洁的确认信息
117
126
  console.clear();
@@ -124,15 +133,18 @@ export async function selectTemplateWithEnquirer () {
124
133
  message: '选择自定义模板来源',
125
134
  choices: [
126
135
  { name: 'local', message: '本地目录' },
127
- { name: 'git', message: 'Git 仓库' }
128
- ]
136
+ { name: 'git', message: 'Git 仓库' },
137
+ ],
129
138
  });
130
139
  const sourceType = await typePrompt.run();
131
140
 
132
141
  // 输入路径或 URL
133
142
  const inputPrompt = new enquirer.Input({
134
143
  name: 'pathOrUrl',
135
- message: sourceType === 'git' ? '请输入 Git 仓库地址(如 https://... 或 git@...)' : '请输入本地模板目录绝对路径'
144
+ message:
145
+ sourceType === 'git'
146
+ ? '请输入 Git 仓库地址(如 https://... 或 git@...)'
147
+ : '请输入本地模板目录绝对路径',
136
148
  });
137
149
  const pathOrUrl = await inputPrompt.run();
138
150
 
@@ -142,7 +154,7 @@ export async function selectTemplateWithEnquirer () {
142
154
  const branchPrompt = new enquirer.Input({
143
155
  name: 'branch',
144
156
  message: '请输入分支名称(默认 main)',
145
- initial: 'main'
157
+ initial: 'main',
146
158
  });
147
159
  branch = await branchPrompt.run();
148
160
  if (!branch) branch = 'main';
@@ -155,47 +167,62 @@ export async function selectTemplateWithEnquirer () {
155
167
  }
156
168
 
157
169
  // 生成 id 与名称
158
- const nameGuess = sourceType === 'git'
159
- ? (pathOrUrl.split('/').pop() || 'custom')
160
- : basename(pathOrUrl);
161
- const id = `${nameGuess.replace(/\W+/g, '-').toLowerCase()}-${Date.now()}`;
170
+ const nameGuess =
171
+ sourceType === 'git'
172
+ ? pathOrUrl.split('/').pop()?.replace('.git', '') || 'custom'
173
+ : basename(pathOrUrl);
174
+ const id = `${nameGuess
175
+ .replace(/\W+/g, '-')
176
+ .toLowerCase()}-${Date.now()}`;
162
177
 
163
178
  const newTemplate = {
164
179
  id,
165
180
  name: nameGuess,
166
181
  type: sourceType,
167
- ...(sourceType === 'git' ? { url: pathOrUrl, branch } : { path: pathOrUrl })
182
+ ...(sourceType === 'git' ? { url: pathOrUrl, branch } : { path: pathOrUrl }),
168
183
  };
169
184
 
170
185
  configManager.addTemplate(newTemplate);
171
186
 
172
- console.log(chalk.green('✓ 已保存自定义模板:'), chalk.cyan.bold(newTemplate.name));
173
- saveDefaultTemplate({ type: 'custom', idOrName: newTemplate.id });
187
+ console.log(
188
+ chalk.green('✓ 已保存自定义模板:'),
189
+ chalk.cyan.bold(newTemplate.name)
190
+ );
191
+ configManager.setDefaultTemplate('custom', newTemplate.id);
174
192
  return { kind: 'custom', id: newTemplate.id };
175
193
  }
176
194
 
177
195
  if (answer.startsWith('builtin:')) {
178
196
  const name = answer.split(':')[1];
179
- const selectedTemplate = templates.find(t => t.name.toLowerCase() === name);
180
- console.log(chalk.green('✓ 已选择:'), chalk.cyan.bold(selectedTemplate.displayName));
181
- saveDefaultTemplate({ type: 'builtin', idOrName: name });
197
+ const selectedTemplate = BUILTIN_TEMPLATES[name];
198
+ console.log(
199
+ chalk.green('✓ 已选择:'),
200
+ chalk.cyan.bold(selectedTemplate.displayName)
201
+ );
202
+ configManager.setDefaultTemplate('builtin', name);
182
203
  return { kind: 'builtin', name };
183
204
  }
184
205
 
185
206
  if (answer.startsWith('custom:')) {
186
207
  const id = answer.split(':')[1];
187
208
  const ct = customTemplates.find(t => t.id === id);
188
- console.log(chalk.green('✓ 已选择自定义:'), chalk.cyan.bold(ct?.name || id));
189
- saveDefaultTemplate({ type: 'custom', idOrName: id });
209
+ console.log(
210
+ chalk.green('✓ 已选择自定义:'),
211
+ chalk.cyan.bold(ct?.name || id)
212
+ );
213
+ configManager.setDefaultTemplate('custom', id);
190
214
  return { kind: 'custom', id };
191
215
  }
192
216
 
193
217
  // 兜底(保持兼容)
194
- const name = answer;
195
- saveDefaultTemplate({ type: 'builtin', idOrName: name });
196
- const selectedTemplate = templates.find(t => t.name.toLowerCase() === name);
197
- console.log(chalk.green('✓ 已选择:'), chalk.cyan.bold(selectedTemplate?.displayName || name));
198
- return { kind: 'builtin', name };
218
+ const nameFallback = answer;
219
+ configManager.setDefaultTemplate('builtin', nameFallback);
220
+ const selectedTemplate = BUILTIN_TEMPLATES[nameFallback];
221
+ console.log(
222
+ chalk.green('✓ 已选择:'),
223
+ chalk.cyan.bold(selectedTemplate?.displayName || nameFallback)
224
+ );
225
+ return { kind: 'builtin', name: nameFallback };
199
226
  } catch (error) {
200
227
  console.log(chalk.yellow('\n操作已取消'));
201
228
  process.exit(0);