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.
@@ -13,11 +13,11 @@ import { getTemplate, isValidTemplate, getAllTemplates } from '../../config/temp
13
13
  import { selectTemplateWithEnquirer } from '../utils/templateSelectorEnquirer.js';
14
14
  import { cloneOrUpdateTemplate } from '../templates/templateManager.js';
15
15
  import { getAuthorName, saveAuthorName, saveDefaultTemplate } from '../utils/config.js';
16
- import { ConfigManager } from '@flu-cli/core';
16
+ import { ConfigManager, ProjectGenerator } from '@flu-cli/core';
17
17
  import { copyTemplate, replaceVariables, copyCustomTemplate, ensurePubspecName, mergePubspecFromTemplate, cleanupTemplateFiles } from '../templates/templateCopier.js';
18
18
  import { runFlutterCreate } from '../utils/flutterHelper.js';
19
- import { ProjectGenerator } from '../generators/project_generator.js';
20
19
  import { syncSnippets } from './snippets.js';
20
+ import { configAssets } from './assets.js';
21
21
 
22
22
  /**
23
23
  * 创建新项目
@@ -52,19 +52,42 @@ export async function newProjectWithClack (projectName, options) {
52
52
  // Step 2: 选择模板(实时预览,支持内置/自定义)
53
53
  templateSelection = await selectTemplateWithEnquirer();
54
54
 
55
- // Step 2.5: 选择状态管理器
56
- const smChoice = await p.select({
57
- message: '请选择状态管理器',
58
- options: [
59
- { value: 'default', label: 'ChangeNotifier (默认)' },
60
- { value: 'provider', label: 'Provider' },
61
- { value: 'getx', label: 'GetX' },
62
- { value: 'riverpod', label: 'Riverpod' }
63
- ],
64
- initialValue: 'default'
65
- });
66
- if (!p.isCancel(smChoice)) {
55
+ // Step 2.5: 选择状态管理器 (Native 模板跳过)
56
+ let includeNetworkLayer = true; // 默认包含网络层
57
+ if (templateSelection.kind === 'builtin' && templateSelection.name === 'native') {
58
+ stateManager = 'default';
59
+ includeNetworkLayer = false; // Native 模板不需要网络层
60
+ } else {
61
+ const smChoice = await p.select({
62
+ message: '请选择状态管理器',
63
+ options: [
64
+ { value: 'default', label: 'ChangeNotifier (默认)' },
65
+ { value: 'provider', label: 'Provider' },
66
+ { value: 'getx', label: 'GetX' }
67
+ // { value: 'riverpod', label: 'Riverpod' }
68
+ ],
69
+ initialValue: 'default'
70
+ });
71
+ if (p.isCancel(smChoice)) {
72
+ p.cancel('操作已取消');
73
+ process.exit(0);
74
+ }
67
75
  stateManager = smChoice;
76
+
77
+ // Step 2.6: 选择是否包含网络层
78
+ const nlChoice = await p.select({
79
+ message: '是否包含网络层?',
80
+ options: [
81
+ { value: true, label: '包含网络层 (推荐)', hint: '包含 Dio 网络工具、AppConfig、StorageUtil' },
82
+ { value: false, label: '不包含网络层', hint: '纯净项目,适合工具类或离线应用' }
83
+ ],
84
+ initialValue: true
85
+ });
86
+ if (p.isCancel(nlChoice)) {
87
+ p.cancel('操作已取消');
88
+ process.exit(0);
89
+ }
90
+ includeNetworkLayer = nlChoice;
68
91
  }
69
92
 
70
93
  // Step 3: 项目路径(含冲突检测与删除确认)
@@ -158,6 +181,7 @@ export async function newProjectWithClack (projectName, options) {
158
181
  console.log('');
159
182
  console.log(` ${chalk.gray('项目名称:')} ${chalk.green(finalProjectName)}`);
160
183
  console.log(` ${chalk.gray('项目模板:')} ${chalk.green(templateDisplay)}`);
184
+ console.log(` ${chalk.gray('网络层:')} ${chalk.green(includeNetworkLayer ? '包含' : '不包含')}`);
161
185
  console.log(` ${chalk.gray('项目路径:')} ${chalk.green(projectPath)}`);
162
186
  console.log(` ${chalk.gray('包名:')} ${chalk.green(packageName)}`);
163
187
  console.log(` ${chalk.gray('作者:')} ${chalk.green(author)}`);
@@ -173,7 +197,7 @@ export async function newProjectWithClack (projectName, options) {
173
197
  }
174
198
 
175
199
  // Step 8: 创建项目
176
- await createProject(finalProjectName, templateSelection, projectPath, { projectName: finalProjectName, packageName, author, stateManager }, options);
200
+ await createProject(finalProjectName, templateSelection, projectPath, { projectName: finalProjectName, packageName, author, stateManager, includeNetworkLayer }, options);
177
201
 
178
202
  } else {
179
203
  // ========== 命令行模式 ==========
@@ -251,7 +275,8 @@ export async function newProjectWithClack (projectName, options) {
251
275
  }
252
276
 
253
277
  // CLI 模式支持 --state 传参
254
- await createProject(finalProjectName, templateSelection, projectDir, { projectName: finalProjectName, packageName, author, stateManager }, options);
278
+ const includeNetworkLayer = options.network !== false; // 默认包含,除非明确指定 --no-network
279
+ await createProject(finalProjectName, templateSelection, projectDir, { projectName: finalProjectName, packageName, author, stateManager, includeNetworkLayer }, options);
255
280
  }
256
281
  } catch (error) {
257
282
  p.cancel(`创建失败: ${error.message}`);
@@ -266,106 +291,44 @@ async function createProject (projectName, templateSelection, projectDir, projec
266
291
  const s = p.spinner();
267
292
 
268
293
  try {
269
- // 1. 准备模板
270
- s.start('正在准备模板...');
271
- let templatePath;
272
- if (templateSelection?.kind === 'builtin') {
273
- const template = getTemplate(templateSelection.name);
274
- // 根据运行环境决定是否使用本地模板
275
- // 生产环境(production)默认走远程,其他环境默认走本地(除非显式使用 --remote)
276
- const runMode = process.env.FLU_CLI_MODE || process.env.NODE_ENV || 'development';
277
- const useLocal = !options.remote && runMode !== 'production' && (template.local || false);
278
- const __here = dirname(fileURLToPath(import.meta.url));
279
- const cliRoot = dirname(dirname(__here)); // flu-cli/flu-cli
280
- const workspaceRoot = dirname(cliRoot);
281
- const sourceLocalPath = join(workspaceRoot, `template-${templateSelection.name}`);
282
- const forceUpdate = useLocal ? true : !options.cache;
283
- templatePath = await cloneOrUpdateTemplate(
284
- templateSelection.name,
285
- template.repo,
286
- template.branch,
287
- forceUpdate,
288
- useLocal,
289
- sourceLocalPath
290
- );
291
- } else if (templateSelection?.kind === 'custom') {
292
- const configManager = ConfigManager.getInstance();
293
- const ct = configManager.getTemplate(templateSelection.id);
294
-
295
- if (!ct) {
296
- s.stop('模板准备失败');
297
- throw new Error('未找到自定义模板');
298
- }
299
- if (ct.type === 'local') {
300
- templatePath = await cloneOrUpdateTemplate(
301
- ct.id,
302
- null,
303
- 'main',
304
- !options.cache,
305
- true,
306
- ct.path || ct.url // 兼容旧配置
307
- );
308
- } else {
309
- templatePath = await cloneOrUpdateTemplate(
310
- ct.id,
311
- ct.url,
312
- ct.branch || 'main',
313
- !options.cache,
314
- false
315
- );
316
- }
317
- } else {
318
- throw new Error('模板选择无效');
319
- }
320
-
321
- if (!templatePath) {
322
- s.stop('模板准备失败');
323
- throw new Error('模板准备失败');
324
- }
325
- s.stop('✓ 模板准备完成');
326
-
327
- // 2. 创建 Flutter 项目
328
- s.start('正在创建 Flutter 项目...');
329
- const created = await runFlutterCreate(projectDir, projectInfo.projectName, projectInfo.packageName);
330
- if (created) {
331
- s.stop('✓ Flutter 项目创建成功');
332
- } else {
333
- s.stop('Flutter 项目创建失败');
334
- }
335
-
336
- // 3. 复制模板文件(自定义模板仅覆盖通用代码,保留平台与包名)
337
- s.start('正在复制模板文件...');
338
- const isCustom = templateSelection?.kind === 'custom';
339
- const copied = isCustom
340
- ? await copyCustomTemplate(templatePath, projectDir)
341
- : await copyTemplate(templatePath, projectDir);
294
+ // 1. 设置生成选项
295
+ const templateId = templateSelection.kind === 'builtin'
296
+ ? templateSelection.name
297
+ : templateSelection.id;
298
+
299
+ const generatorOptions = {
300
+ templateType: templateId,
301
+ stateManager: projectInfo.stateManager || 'default',
302
+ packageName: projectInfo.packageName,
303
+ outputDir: dirname(projectDir),
304
+ createFlutterProject: true, // v2 总是需要 flutter create
305
+ forceUpdate: !options.cache,
306
+ author: projectInfo.author,
307
+ flutterTemplate: options.flutterTemplate || 'app',
308
+ includeNetworkLayer: projectInfo.includeNetworkLayer !== false // 默认包含网络层
309
+ };
310
+
311
+ // 2. 调用核心生成器
312
+ s.start('正在生成项目 (Core Engine)...');
313
+ const pg = new ProjectGenerator();
314
+ const success = await pg.generate(projectName, generatorOptions);
342
315
 
343
- if (!copied) {
344
- s.stop('模板文件复制失败');
345
- throw new Error('模板文件复制失败');
316
+ if (!success) {
317
+ s.stop('项目生成失败');
318
+ throw new Error('核心生成器返回失败');
346
319
  }
347
- s.stop('✓ 模板文件复制成功');
348
-
349
- // 4. 配置项目
350
- s.start('正在配置项目...');
351
- const pg = new ProjectGenerator();
352
- await pg.processTemplates(projectDir);
353
- await replaceVariables(projectDir, projectInfo);
354
- if (isCustom) {
355
- mergePubspecFromTemplate(templatePath, projectDir, projectInfo.projectName);
356
- } else {
357
- ensurePubspecName(projectDir, projectInfo.projectName);
320
+ s.stop('✓ 项目生成成功');
321
+
322
+ // 3. CLI 特色功能:同步标准代码片段 (Native 模板跳过)
323
+ const isNative = templateId === 'native' || templateId === 'none';
324
+ if (!isNative) {
325
+ s.start('正在同步代码片段...');
326
+ await syncSnippets(projectDir);
327
+ s.stop('✓ 代码片段同步完成');
358
328
  }
359
- // 配置状态管理器
360
- await pg.configureStateManager(projectDir, projectInfo.stateManager || 'default');
361
-
362
- // 同步标准代码片段
363
- s.start('正在同步代码片段...');
364
- await syncSnippets(projectDir);
365
- s.stop('✓ 代码片段同步完成');
366
329
 
330
+ // 4. 清理模板残余(ProjectGenerator 可能已经做了一部分,这里确保万一)
367
331
  cleanupTemplateFiles(projectDir);
368
- s.stop('✓ 项目配置完成');
369
332
 
370
333
  // 完成
371
334
  p.outro(
@@ -376,7 +339,17 @@ async function createProject (projectName, templateSelection, projectDir, projec
376
339
  chalk.yellow(` flutter run`)
377
340
  );
378
341
 
379
- // 记忆默认模板
342
+ // 5. 提示资源配置
343
+ const shouldConfigAssets = await p.confirm({
344
+ message: '项目已创建。是否现在就配置 App 图标和启动图?',
345
+ initialValue: false
346
+ });
347
+
348
+ if (shouldConfigAssets) {
349
+ await configAssets({ dir: projectDir });
350
+ }
351
+
352
+ // 6. 记忆默认模板
380
353
  if (templateSelection?.kind === 'builtin') {
381
354
  saveDefaultTemplate({ type: 'builtin', idOrName: templateSelection.name });
382
355
  } else if (templateSelection?.kind === 'custom') {
@@ -2,8 +2,8 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { fileURLToPath } from 'url';
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ import { ProjectConfigManager, getTemplatesRootDir } from '@flu-cli/core';
6
+ import { t } from '../utils/i18n.js';
7
7
 
8
8
  /**
9
9
  * 同步代码片段到当前项目
@@ -13,23 +13,64 @@ export async function syncSnippets (targetDir) {
13
13
  const vscodeDir = path.join(projectRoot, '.vscode');
14
14
  const targetFile = path.join(vscodeDir, 'dart.code-snippets');
15
15
 
16
- // 获取 centralized snippets 路径
17
- // 假设结构: packages/v2/lib/commands/snippets.js -> packages/v2/templates/snippets/dart.code-snippets
18
- const snippetsSource = path.join(__dirname, '../../templates/snippets/dart.code-snippets');
16
+ // 获取 centralized snippets 路径 (从 Core 的模板根目录获取)
17
+ const templatesDir = getTemplatesRootDir();
18
+ const snippetsSource = path.join(templatesDir, 'snippets/dart.code-snippets');
19
19
 
20
20
  if (!fs.existsSync(snippetsSource)) {
21
- console.log(chalk.red('❌ 错误: 找不到标准代码片段源文件'));
21
+ console.log(chalk.red(`❌ 错误: 找不到标准代码片段源文件: ${snippetsSource}`));
22
22
  return;
23
23
  }
24
24
 
25
25
  try {
26
+ // 读取原始 Snippets
27
+ const originalSnippets = await fs.readJson(snippetsSource);
28
+ let finalSnippets = { ...originalSnippets };
29
+
30
+ // 读取项目配置
31
+ const config = ProjectConfigManager.loadConfig(projectRoot);
32
+
33
+ if (config) {
34
+ console.log(chalk.cyan(t('snippets.detect_config')));
35
+ const pageConfig = config.generators?.page;
36
+ const vmConfig = config.generators?.viewModel;
37
+
38
+ const withBasePage = pageConfig?.withBasePage ?? true;
39
+ const withViewModel = pageConfig?.withViewModel ?? true;
40
+ const withBaseViewModel = vmConfig?.withBaseViewModel ?? true;
41
+
42
+ // 过滤规则
43
+ if (!withBasePage) {
44
+ delete finalSnippets['flu.stPage'];
45
+ delete finalSnippets['flu.listPage'];
46
+ }
47
+
48
+ if (!withViewModel) {
49
+ delete finalSnippets['flu.stPage'];
50
+ delete finalSnippets['flu.viewmodel'];
51
+ delete finalSnippets['flu.listPage'];
52
+ delete finalSnippets['flu.listViewModel'];
53
+ }
54
+
55
+ if (!withBaseViewModel) {
56
+ delete finalSnippets['flu.viewmodel'];
57
+ delete finalSnippets['flu.listViewModel'];
58
+ }
59
+ }
60
+
26
61
  // 确保 .vscode 目录存在
27
62
  await fs.ensureDir(vscodeDir);
28
63
 
29
- // 复制文件
30
- await fs.copy(snippetsSource, targetFile, { overwrite: true });
64
+ // 写入文件
65
+ await fs.writeJson(targetFile, finalSnippets, { spaces: 2 });
31
66
 
32
- console.log(chalk.green('✅ 代码片段同步成功!'));
67
+ console.log(chalk.green(t('snippets.sync_success')));
68
+ if (config) {
69
+ console.log(chalk.gray(t('snippets.applied_filter', {
70
+ basePage: config.generators?.page?.withBasePage ? 'ON' : 'OFF',
71
+ viewModel: config.generators?.page?.withViewModel ? 'ON' : 'OFF'
72
+ })));
73
+ }
33
74
  console.log(chalk.gray(`已更新: ${targetFile}`));
34
75
  console.log(chalk.cyan('提示: 重启 VS Code 或运行 "Developer: Reload Window" 以应用更改'));
35
76
 
@@ -1,108 +1,24 @@
1
1
  /**
2
- * 模板复制器
3
- * 职责:复制内置/自定义模板至目标目录,并执行变量替换与文件筛选
4
- * 特性:保留平台工程与包名、支持 .template 文件落地、清理模板特有文件
2
+ * 模板复制器 - 桥接至 @flu-cli/core
5
3
  */
6
4
 
7
- import fsExtra from 'fs-extra';
8
- import { join } from 'path';
9
- import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs';
10
- import { logger } from '../utils/logger.js';
11
-
12
- const { copySync, removeSync } = fsExtra;
5
+ export {
6
+ copyTemplate,
7
+ copyCustomTemplate,
8
+ replaceVariables,
9
+ ensurePubspecName,
10
+ cleanupTemplateFiles
11
+ } from '@flu-cli/core';
13
12
 
14
13
  /**
15
- * 复制模板到目标目录
16
- * @param {string} templatePath - 模板路径
17
- * @param {string} targetPath - 目标路径
18
- * @returns {Promise<boolean>} 是否成功
14
+ * 使用模板的 pubspec.yaml 合并覆盖目标项目 (仅限 V2 保持逻辑)
19
15
  */
20
- export async function copyTemplate (templatePath, targetPath) {
21
- try {
22
- // 检查模板路径是否存在
23
- if (!existsSync(templatePath)) {
24
- logger.error(`模板路径不存在: ${templatePath}`);
25
- return false;
26
- }
27
-
28
- // 需要排除的文件和目录
29
- const excludes = [
30
- '.git',
31
- '.github',
32
- 'node_modules',
33
- '.flu-cli.yaml',
34
- 'README.template.md',
35
- '.DS_Store',
36
- 'pubspec.yaml' // 排除本地调试用的 pubspec.yaml
37
- ];
38
-
39
- // 复制文件,排除特定文件
40
- copySync(templatePath, targetPath, {
41
- filter: (src) => {
42
- const basename = src.split('/').pop();
43
- return !excludes.includes(basename);
44
- },
45
- overwrite: true
46
- });
47
-
48
- // 处理 pubspec.yaml.template -> pubspec.yaml
49
- const templatePubspec = join(templatePath, 'pubspec.yaml.template');
50
- const targetPubspec = join(targetPath, 'pubspec.yaml');
51
- if (existsSync(templatePubspec)) {
52
- // 复制 .template 文件并覆盖 Flutter 生成的 pubspec.yaml
53
- copySync(templatePubspec, targetPubspec, { overwrite: true });
54
- logger.info('已使用模板的 pubspec.yaml.template');
55
- } else {
56
- logger.warn('未找到 pubspec.yaml.template,使用 Flutter 默认配置');
57
- }
58
-
59
- return true;
60
-
61
- } catch (error) {
62
- logger.error(`复制模板失败: ${error.message}`);
63
- return false;
64
- }
65
- }
66
-
67
- // 仅复制自定义模板中的通用代码与配置,保留目标工程的平台与包名
68
- export async function copyCustomTemplate (templatePath, targetPath) {
69
- try {
70
- if (!existsSync(templatePath)) {
71
- logger.error(`模板路径不存在: ${templatePath}`);
72
- return false;
73
- }
74
-
75
- const includeDirs = ['lib', 'assets', '.vscode'];
76
- const includeFiles = ['analysis_options.yaml', 'README.md', 'pubspec.yaml'];
77
-
78
- for (const dir of includeDirs) {
79
- const srcDir = join(templatePath, dir);
80
- const dstDir = join(targetPath, dir);
81
- if (existsSync(srcDir)) {
82
- copySync(srcDir, dstDir, { overwrite: true });
83
- }
84
- }
85
-
86
- for (const file of includeFiles) {
87
- const srcFile = join(templatePath, file);
88
- const dstFile = join(targetPath, file);
89
- if (existsSync(srcFile)) {
90
- copySync(srcFile, dstFile, { overwrite: true });
91
- }
92
- }
93
-
94
- return true;
95
- } catch (error) {
96
- logger.error(`复制自定义模板失败: ${error.message}`);
97
- return false;
98
- }
99
- }
100
-
101
- /**
102
- * 使用模板的 pubspec.yaml 合并覆盖目标项目(仅修改 name 保持项目名)
103
- */
104
- export function mergePubspecFromTemplate (templatePath, projectDir, projectName) {
16
+ export async function mergePubspecFromTemplate (templatePath, projectDir, projectName) {
17
+ // 暂时保留该逻辑或移动到 core (考虑到 core 主要是 ProjectGenerator 使用,可以先在 v2 桥接)
18
+ // TODO: 后续可以完全迁移到 core
105
19
  try {
20
+ const { readFileSync, writeFileSync, existsSync } = await import('fs');
21
+ const { join } = await import('path');
106
22
  const tplPub = join(templatePath, 'pubspec.yaml');
107
23
  const dstPub = join(projectDir, 'pubspec.yaml');
108
24
  if (!existsSync(tplPub) || !existsSync(dstPub)) return;
@@ -117,180 +33,3 @@ export function mergePubspecFromTemplate (templatePath, projectDir, projectName)
117
33
  // 忽略错误
118
34
  }
119
35
  }
120
-
121
- /**
122
- * 替换文件中的变量
123
- * @param {string} projectDir - 项目目录
124
- * @param {object} variables - 变量对象
125
- */
126
- export async function replaceVariables (projectDir, variables) {
127
- try {
128
- // 需要替换变量的文件模式
129
- const patterns = [
130
- '**/*.dart',
131
- 'pubspec.yaml',
132
- 'README.md',
133
- 'android/app/build.gradle',
134
- 'ios/Runner/Info.plist'
135
- ];
136
-
137
- // 变量映射
138
- const replacements = {
139
- '{{projectName}}': variables.projectName,
140
- '{{project_name}}': variables.projectName,
141
- '{{package_name}}': variables.packageName,
142
- '{{author}}': variables.author || 'Your Name',
143
- '{{year}}': new Date().getFullYear().toString()
144
- };
145
-
146
- // 递归替换目录中的文件
147
- replaceInDirectory(projectDir, replacements);
148
-
149
- // 修复 test 文件中的导入路径
150
- fixTestFile(projectDir, variables.projectName);
151
-
152
- return true;
153
-
154
- } catch (error) {
155
- logger.error(`替换变量失败: ${error.message}`);
156
- return false;
157
- }
158
- }
159
-
160
- /**
161
- * 递归替换目录中的文件内容
162
- */
163
- function replaceInDirectory (dir, replacements) {
164
- const files = readdirSync(dir);
165
-
166
- files.forEach(file => {
167
- const filePath = join(dir, file);
168
- const stat = statSync(filePath);
169
-
170
- if (stat.isDirectory()) {
171
- // 跳过特定目录
172
- if (['.git', 'node_modules', '.dart_tool', 'build'].includes(file)) {
173
- return;
174
- }
175
- replaceInDirectory(filePath, replacements);
176
- } else if (stat.isFile()) {
177
- // 只处理文本文件
178
- if (isTextFile(filePath)) {
179
- replaceInFile(filePath, replacements);
180
- }
181
- }
182
- });
183
- }
184
-
185
- /**
186
- * 替换单个文件中的内容
187
- */
188
- function replaceInFile (filePath, replacements) {
189
- try {
190
- let content = readFileSync(filePath, 'utf8');
191
- let modified = false;
192
-
193
- // 执行所有替换
194
- Object.entries(replacements).forEach(([pattern, value]) => {
195
- if (content.includes(pattern)) {
196
- content = content.replace(new RegExp(pattern, 'g'), value);
197
- modified = true;
198
- }
199
- });
200
-
201
- // 如果有修改,写回文件
202
- if (modified) {
203
- writeFileSync(filePath, content, 'utf8');
204
- }
205
-
206
- } catch (error) {
207
- // 忽略二进制文件等错误
208
- }
209
- }
210
-
211
- /**
212
- * 判断是否为文本文件
213
- */
214
- function isTextFile (filePath) {
215
- const textExtensions = [
216
- '.dart', '.yaml', '.yml', '.json', '.md', '.txt',
217
- '.gradle', '.xml', '.plist', '.swift', '.kt', '.java',
218
- '.js', '.ts', '.html', '.css', '.sh'
219
- ];
220
-
221
- return textExtensions.some(ext => filePath.endsWith(ext));
222
- }
223
-
224
- /**
225
- * 修复 test 文件中的导入路径
226
- */
227
- function fixTestFile (projectDir, projectName) {
228
- const testFile = join(projectDir, 'test', 'widget_test.dart');
229
-
230
- if (existsSync(testFile)) {
231
- try {
232
- let content = readFileSync(testFile, 'utf8');
233
-
234
- // 替换导入路径:从 main.dart 改为 app.dart
235
- content = content.replace(
236
- `import 'package:${projectName}/main.dart';`,
237
- `import 'package:${projectName}/app.dart';`
238
- );
239
-
240
- // 如果测试内容还是默认的计数器测试,可以注释掉或删除
241
- if (content.includes('Counter increments smoke test')) {
242
- content = `import 'package:flutter_test/flutter_test.dart';
243
- import 'package:${projectName}/app.dart';
244
-
245
- void main() {
246
- testWidgets('App smoke test', (WidgetTester tester) async {
247
- await tester.pumpWidget(const App());
248
- expect(find.byType(App), findsOneWidget);
249
- });
250
- }
251
- `;
252
- }
253
-
254
- writeFileSync(testFile, content, 'utf8');
255
- } catch (error) {
256
- // 忽略错误
257
- }
258
- }
259
- }
260
-
261
- /**
262
- * 强制校正 pubspec.yaml 的 name 字段为项目名
263
- */
264
- export function ensurePubspecName (projectDir, projectName) {
265
- try {
266
- const pubspec = join(projectDir, 'pubspec.yaml');
267
- if (!existsSync(pubspec)) return;
268
- let content = readFileSync(pubspec, 'utf8');
269
- if (/^name:\s+/m.test(content)) {
270
- content = content.replace(/^name:\s+.*/m, `name: ${projectName}`);
271
- } else {
272
- content = `name: ${projectName}\n` + content;
273
- }
274
- writeFileSync(pubspec, content, 'utf8');
275
- } catch (error) {
276
- // 忽略错误
277
- }
278
- }
279
-
280
- /**
281
- * 清理模板特有文件
282
- */
283
- export function cleanupTemplateFiles (projectDir) {
284
- const filesToRemove = [
285
- '.flu-cli.yaml',
286
- 'README.template.md',
287
- '.git'
288
- ];
289
-
290
- filesToRemove.forEach(file => {
291
- const filePath = join(projectDir, file);
292
- if (existsSync(filePath)) {
293
- removeSync(filePath);
294
- }
295
- });
296
- }