UNPKG

@tianyio/quality-helper

Version:

A comprehensive quality helper tool for project scaffolding and management

1 lines 54.7 kB
{"version":3,"file":"services-BHKno3F_.cjs","sources":["../src/services/MergeService.ts","../src/services/BuildToolsService.ts","../src/services/FileService.ts","../src/services/FileBrowserService.ts","../src/services/UIService.ts","../src/services/ConfigService.ts","../src/services/ProjectOptionsService.ts"],"sourcesContent":["import type { ConfigFileInfo } from './ConfigService.ts'\nimport { FileMerger } from '../core/FileMerger.ts'\n\n/**\n * 配置合并服务类\n * 负责智能合并不同类型的配置文件内容,提供业务相关的合并策略\n */\nexport class MergeService {\n private configMerger: FileMerger\n\n constructor() {\n this.configMerger = new FileMerger()\n }\n\n /**\n * 确定配置文件的更新模式\n */\n async determineUpdateMode(\n targetContent: string,\n templateContent: string,\n config: ConfigFileInfo\n ): Promise<'merge' | 'overwrite' | 'skip'> {\n // 如果内容完全相同,跳过\n if (targetContent.trim() === templateContent.trim()) {\n return 'skip'\n }\n const needOverride = ['.gitconfig', '.editorconfig']\n // 如果targetContent为空,应该覆盖\n if (\n config.group === 'git-hooks' ||\n needOverride.includes(config.name) ||\n !targetContent.trim()\n ) {\n return 'overwrite'\n }\n\n // 默认使用合并模式\n return 'merge'\n }\n\n /**\n * 智能合并配置文件内容\n */\n async mergeConfigContent(\n targetContent: string,\n templateContent: string,\n config: Required<Pick<ConfigFileInfo, 'name'>> & Partial<Omit<ConfigFileInfo, 'name'>>\n ): Promise<string> {\n // 使用通用合并算法\n return this.configMerger.mergeConfigContent(targetContent, templateContent, config)\n }\n}\n","import { join, relative } from 'path'\nimport type { ArchitectureType, ProjectType } from '../types.ts'\nimport { FileOperations } from '../core/FileOperations.ts'\nimport { MergeService } from './MergeService.ts'\n\n/**\n * 构建工具服务类\n * 用于处理 vite 配置文件和 typescript 配置文件\n */\nexport class BuildToolsService {\n private mergeService: MergeService\n // 用于跟踪已更新的 monorepo 根目录配置文件,避免重复更新\n private updatedMonorepoConfigs: Set<string>\n\n constructor() {\n this.mergeService = new MergeService()\n this.updatedMonorepoConfigs = new Set<string>()\n }\n\n /**\n * 判断 typescript 配置文件是否拆分基文件和特异文件\n */\n private isTsConfigSeparated(content: string): boolean {\n try {\n const tsConfig = JSON.parse(content)\n\n // 处理 extends 可能为字符串、数组或其他类型的情况\n const checkExtends = (extendsValue: unknown): boolean => {\n // 更宽松的正则表达式来匹配 extends 值,去除引号\n const reg = /^(?:\\.\\.\\/|\\.\\/|[\\w-]+\\/)*tsconfig\\.json$/\n // extends 是单个字符串\n if (typeof extendsValue === 'string') {\n // 去除引号后测试\n const cleanValue = extendsValue.replace(/^[\"']|[\"']$/g, '')\n return reg.test(cleanValue)\n }\n // extends 是字符串数组\n if (Array.isArray(extendsValue)) {\n return extendsValue.some(item => {\n if (typeof item === 'string') {\n const cleanValue = item.replace(/^[\"']|[\"']$/g, '')\n return reg.test(cleanValue)\n }\n return false\n })\n }\n // 情况3:extends 是其他类型(不符合规范,返回false)\n return false\n }\n\n return checkExtends(tsConfig.extends ?? '')\n } catch {\n // 如果解析失败,认为没有拆分\n return false\n }\n }\n\n /**\n * 判断 vite 配置文件是否拆分基文件和特异文件\n */\n private isViteConfigSeparated(content: string): boolean {\n // 检查是否通过 import 导入了其他 vite 配置文件\n // 使用更宽松的正则表达式来匹配 import 语句\n const reg = /import\\s+.*\\s+from\\s+[\"'][.\\/]+.*vite\\.config\\.(?:ts|js)[\"']/\n return reg.test(content)\n }\n\n /**\n * 检查配置文件状态\n */\n private async checkConfigStatus(\n projectPath: string,\n type: 'vite' | 'ts'\n ): Promise<'not-exists' | 'separated' | 'not-separated'> {\n const fileName = type === 'vite' ? 'vite.config.ts' : 'tsconfig.json'\n try {\n const configPath = join(projectPath, fileName)\n\n // 检查配置文件是否存在\n const exists = await FileOperations.exists(configPath)\n if (!exists) {\n return 'not-exists'\n }\n\n // 读取配置文件内容\n const content = await FileOperations.readFile(configPath)\n\n // 检查是否拆分基文件和特异文件\n let isSeparated: boolean\n if (type === 'vite') {\n isSeparated = this.isViteConfigSeparated(content)\n } else {\n isSeparated = this.isTsConfigSeparated(content)\n }\n\n return isSeparated ? 'separated' : 'not-separated'\n } catch {\n // 如果出现错误,默认认为配置文件不存在\n return 'not-exists'\n }\n }\n\n /**\n * 处理配置文件\n */\n async process(\n configType: 'vite' | 'ts',\n templatesDir: string,\n projectPath: string,\n rootPath: string,\n architecture: ArchitectureType,\n projectType: ProjectType\n ): Promise<void> {\n // 确定情景类型\n\n // 识别目标项目 vite 配置文件的情况\n const status = await this.checkConfigStatus(projectPath, configType)\n\n switch (status) {\n case 'not-exists':\n await this.handleProjectConfigNotExists(\n templatesDir,\n projectPath,\n rootPath,\n configType,\n architecture,\n projectType\n )\n break\n case 'separated':\n await this.handleProjectConfigSeparated(\n templatesDir,\n projectPath,\n rootPath,\n configType,\n architecture,\n projectType\n )\n break\n case 'not-separated':\n await this.handleProjectConfigNotSeparated(\n templatesDir,\n projectPath,\n rootPath,\n configType,\n architecture,\n projectType\n )\n break\n }\n }\n\n /**\n * 计算相对路径\n */\n private calculateRelativePath(from: string, to: string): string {\n const relativePath = relative(from, to).replace(/\\\\/g, '/')\n // 确保路径以 ./ 或 ../ 开头\n if (!relativePath.startsWith('./') && !relativePath.startsWith('../')) {\n return `./${relativePath}`\n }\n return relativePath\n }\n\n /**\n * 更新基配置文件(避免重复更新)\n */\n private async updateBaseConfigFile(targetPath: string, templatePath: string): Promise<void> {\n const configKey = targetPath\n\n if (!this.updatedMonorepoConfigs.has(configKey)) {\n const templateContent = await FileOperations.readFile(templatePath)\n await FileOperations.writeFile(targetPath, templateContent)\n this.updatedMonorepoConfigs.add(configKey)\n }\n }\n\n /**\n * 合并并更新基配置文件(避免重复更新)\n */\n private async mergeAndUpdateBaseConfigFile(\n configsFilePath: string,\n templatePath: string,\n configName: string\n ): Promise<void> {\n const configKey = configsFilePath\n\n if (!this.updatedMonorepoConfigs.has(configKey)) {\n const templateContent = await FileOperations.readFile(templatePath)\n const existingContent = await FileOperations.readFile(configsFilePath)\n const mergedContent = await this.mergeConfigContent(\n existingContent,\n templateContent,\n configName\n )\n await FileOperations.writeFile(configsFilePath, mergedContent)\n this.updatedMonorepoConfigs.add(configKey)\n }\n }\n\n /**\n * 通用方法:处理项目配置文件不存在的情况\n */\n private async handleProjectConfigNotExists(\n templatesDir: string,\n projectPath: string,\n rootPath: string,\n configType: 'vite' | 'ts',\n architecture: ArchitectureType,\n projectType: ProjectType\n ): Promise<void> {\n // 1. 确保根目录下存在configs文件夹\n await FileOperations.ensureDir(join(rootPath, 'configs'))\n\n // 2. 将模板中的基础配置文件复制到 configs 中\n const fileName = configType === 'vite' ? 'vite.config.ts' : 'tsconfig.json'\n const baseTemplatePath = join(templatesDir, 'configs', 'build-tools', fileName)\n const baseTargetPath = join(rootPath, 'configs', fileName)\n await this.updateBaseConfigFile(baseTargetPath, baseTemplatePath)\n\n if (architecture === 'monorepo' && projectType === 'root') return\n\n // 3. 读取应用类型的配置文件\n const templatePath = join(\n templatesDir,\n 'configs',\n 'build-tools',\n projectType === 'lib' ? 'libs' : 'apps',\n fileName\n )\n let templateContent = await FileOperations.readFile(templatePath)\n\n // 4. 计算相对于项目根目录的 configs 目录路径\n const relativeConfigsPath = this.calculateRelativePath(projectPath, join(rootPath, 'configs'))\n // 4. 根据配置类型进行特定处理\n if (configType === 'vite') {\n // 在读取到的文件顶部增加 import 语句\n templateContent = `import getBaseConfig from '${relativeConfigsPath}/vite.config'\\n${templateContent}`\n\n // 将 return config 替换为 return mergeConfig(getBaseConfig, config)\n templateContent = templateContent.replace(\n /return\\s+config/g,\n 'return mergeConfig(getBaseConfig(env), config)'\n )\n } else {\n // 解析 tsconfig.json 内容\n const tsConfig = JSON.parse(templateContent)\n // 添加 extends 属性\n tsConfig.extends = `${relativeConfigsPath}/tsconfig.json`\n templateContent = JSON.stringify(tsConfig, null, 2)\n }\n\n // 5. 使用上述处理后的内容,在项目根目录创建配置文件\n await FileOperations.writeFile(join(projectPath, fileName), templateContent)\n }\n\n /**\n * 通用方法:处理项目配置文件拆分的情况\n */\n private async handleProjectConfigSeparated(\n templatesDir: string,\n projectPath: string,\n rootPath: string,\n configType: 'vite' | 'ts',\n architecture: ArchitectureType,\n projectType: ProjectType\n ): Promise<void> {\n // 1. 以模板中的基础配置文件作为模板文件,对配置基文件进行更新\n const fileName = configType === 'vite' ? 'vite.config.ts' : 'tsconfig.json'\n const baseTargetPath = join(rootPath, 'configs', fileName)\n\n const baseTemplatePath = join(templatesDir, 'configs', 'build-tools', fileName)\n\n if (architecture === 'monorepo') {\n await this.mergeAndUpdateBaseConfigFile(baseTargetPath, baseTemplatePath, fileName)\n } else {\n const baseTemplateContent = await FileOperations.readFile(baseTemplatePath)\n // 使用 MergeService 更新基文件\n const baseTargetContent = await FileOperations.readFile(baseTargetPath)\n const mergedBaseContent = await this.mergeConfigContent(\n baseTargetContent,\n baseTemplateContent,\n fileName\n )\n await FileOperations.writeFile(baseTargetPath, mergedBaseContent)\n }\n if (architecture === 'monorepo' && projectType === 'root') return\n\n // 2. 读取应用类型的配置文件\n const templatePath = join(\n templatesDir,\n 'configs',\n 'build-tools',\n projectType === 'lib' ? 'libs' : 'apps',\n fileName\n )\n const templateContent = await FileOperations.readFile(templatePath)\n\n // 3. 使用上述处理后的内容作为模板,对项目根目录下的配置文件进行更新\n const targetPath = join(projectPath, fileName)\n const targetContent = await FileOperations.readFile(targetPath)\n const mergedProjectContent = await this.mergeConfigContent(\n targetContent,\n templateContent,\n fileName\n )\n await FileOperations.writeFile(targetPath, mergedProjectContent)\n }\n\n /**\n * 通用方法:处理项目配置文件未拆分的情况\n */\n private async handleProjectConfigNotSeparated(\n templatesDir: string,\n projectPath: string,\n rootPath: string,\n configType: 'vite' | 'ts',\n architecture: ArchitectureType,\n projectType: ProjectType\n ): Promise<void> {\n const fileName = configType === 'vite' ? 'vite.config.ts' : 'tsconfig.json'\n const projectConfigPath = join(projectPath, fileName)\n const backupConfigPath = `${projectConfigPath}.backup`\n\n if (await FileOperations.exists(projectConfigPath)) {\n await FileOperations.rename(projectConfigPath, backupConfigPath)\n console.log(`原始配置文件已备份为: ${backupConfigPath}`)\n }\n\n // 2. 按照配置文件不存在的逻辑处理\n // 调用 handleProjectConfigNotExists 方法处理\n await this.handleProjectConfigNotExists(\n templatesDir,\n projectPath,\n rootPath,\n configType,\n architecture, // 假设为 monorepo 架构\n projectType\n )\n }\n\n /**\n * 智能合并配置文件内容\n */\n private async mergeConfigContent(\n targetContent: string,\n templateContent: string,\n configName: string\n ): Promise<string> {\n const commentPrefix: string = configName === 'vite.config.ts' ? '// ' : '/*'\n const commentSuffix: string = configName === 'vite.config.ts' ? ' ' : '*/'\n // 使用通用合并算法\n return this.mergeService.mergeConfigContent(targetContent, templateContent, {\n name: configName,\n commentPrefix,\n commentSuffix,\n })\n }\n}\n","import { join, dirname } from 'path'\nimport type { ArchitectureType, ProjectType } from '../types.ts'\nimport { FileOperations } from '../core/FileOperations.ts'\nimport { MergeService } from './MergeService.ts'\nimport type { ConfigFileInfo } from './ConfigService.ts'\nimport { BuildToolsService } from './BuildToolsService.ts'\n\n/**\n * 配置文件服务类\n * 负责处理单个配置文件的更新和特殊文件处理\n */\nexport class FileService {\n private templatesDir: string\n private mergeService: MergeService\n private buildToolsService: BuildToolsService\n\n constructor(templatesDir: string) {\n this.templatesDir = templatesDir\n this.mergeService = new MergeService()\n this.buildToolsService = new BuildToolsService()\n }\n\n /**\n * 更新单个配置文件\n */\n async updateConfigFile(\n config: ConfigFileInfo,\n projectRoot: string,\n rootPath: string,\n architecture: ArchitectureType,\n projectType: ProjectType\n ): Promise<boolean> {\n try {\n // 特殊处理 vite.config.ts 文件\n if (config.name.startsWith('vite.config')) {\n await this.buildToolsService.process(\n 'vite',\n this.templatesDir,\n projectRoot,\n rootPath,\n architecture,\n projectType\n )\n // Vite配置文件已更新\n return true\n }\n\n // 特殊处理 tsconfig.json 文件\n if (config.name === 'tsconfig.json') {\n // 使用BuildToolsConfigService提供的方法处理tsconfig.json\n await this.buildToolsService.process(\n 'ts',\n this.templatesDir,\n projectRoot,\n rootPath,\n architecture,\n projectType\n )\n // TypeScript配置文件已更新\n return true\n }\n\n let updateCtx\n // 特殊处理 .pnpmrc 文件\n if (config.name === '.pnpmrc' || config.name === '.npmrc') {\n // 需要做拼接\n updateCtx = await this.getAutoUpdateModeOfNpmrc(config, architecture)\n } else {\n // 自动选择更新策略\n updateCtx = await this.getAutoUpdateMode(config)\n }\n\n if (updateCtx.updateMode === 'skip') {\n // 文件内容相同,静默跳过\n return false\n }\n\n // 更新文件内容\n let finalContent: string\n if (updateCtx.updateMode === 'overwrite') {\n // 完全覆盖模式\n finalContent = updateCtx.templateContent\n } else {\n // 智能合并模式\n finalContent = await this.mergeService.mergeConfigContent(\n updateCtx.targetContent,\n updateCtx.templateContent,\n config\n )\n }\n\n // Git hooks 需要可执行权限\n await FileOperations.writeFile(\n config.path,\n finalContent,\n config.group === 'git-hooks' ? { mode: 0o755 } : 'utf8'\n )\n return true\n } catch (error: any) {\n // 统一错误处理格式,不直接输出到控制台\n // 错误将在上层处理中统一显示\n throw new Error(`更新配置文件失败 ${config.name}: ${error.message}`)\n }\n }\n\n /**\n * 自动选择更新模式\n */\n private async getAutoUpdateMode(config: ConfigFileInfo): Promise<{\n updateMode: 'merge' | 'overwrite' | 'skip'\n targetContent: string\n templateContent: string\n }> {\n const { templatePath, path: targetPath } = config\n // 读取模板文件内容\n const templateContent = await FileOperations.readFile(templatePath)\n // 检查目标文件是否存在\n const targetExists = await FileOperations.exists(targetPath)\n let targetContent = ''\n\n if (targetExists) {\n targetContent = await FileOperations.readFile(targetPath)\n } else {\n // 确保目标目录存在\n await FileOperations.ensureDir(dirname(targetPath))\n }\n\n // 使用ConfigMergeService来判断更新模式\n const updateMode = await this.mergeService.determineUpdateMode(\n targetContent,\n templateContent,\n config\n )\n\n return {\n updateMode,\n targetContent,\n templateContent,\n }\n }\n\n /**\n * 更新 .pnpmrc 文件或 .npmrc 文件\n */\n private async getAutoUpdateModeOfNpmrc(\n config: ConfigFileInfo,\n architecture: ArchitectureType\n ): Promise<{\n updateMode: 'merge' | 'overwrite' | 'skip'\n targetContent: string\n templateContent: string\n }> {\n // 1、基础模版路径 config.templatePath\n const baseTemplatePath = config.templatePath\n // 2、架构特异性模板路径\n const architectureTemplatePath = join(\n this.templatesDir,\n 'configs',\n 'npm',\n architecture,\n config.name.replace(/^\\./, '')\n )\n\n // 3、读取模版文件内容\n const baseTemplateContent = await FileOperations.readFile(baseTemplatePath)\n const architectureTemplateContent = await FileOperations.readFile(architectureTemplatePath)\n // 4、拼接两个模板内容,生成新的模版内容\n const templateContent = await this.mergeService.mergeConfigContent(\n architectureTemplateContent,\n baseTemplateContent,\n config\n )\n // 5、读取目标文件内容\n const targetExists = await FileOperations.exists(config.path)\n let targetContent = ''\n\n if (targetExists) {\n targetContent = await FileOperations.readFile(config.path)\n } else {\n // 确保目标目录存在\n await FileOperations.ensureDir(dirname(config.path))\n }\n\n // 6、使用新的模板内容与目标文件内容进行对比,分析更新模式\n const updateMode = await this.mergeService.determineUpdateMode(\n targetContent,\n templateContent,\n config\n )\n\n // 7、根据更新模式进行更新\n return {\n updateMode,\n targetContent,\n templateContent,\n }\n }\n}\n","import { readdir } from 'fs/promises'\nimport { join, dirname, basename } from 'path'\nimport prompts from 'prompts'\nimport chalk from 'chalk'\n\n/**\n * 文件浏览器服务\n * 专门负责文件系统浏览和选择功能\n */\nexport class FileBrowserService {\n /**\n * 浏览文件系统选择 package.json 文件\n * @param startDir 起始目录\n * @returns 选择的文件路径,如果未选择则返回 null\n */\n async browseForPackageJson(startDir: string): Promise<string | null> {\n let currentDir = startDir\n let selectedFile: string | null = null\n\n while (true) {\n const { action, file } = await this.browseDirectory(currentDir, startDir)\n\n if (action === 'cancel') {\n console.log(chalk.yellow('⚠️ 操作已取消'))\n return null\n }\n\n if (action === 'select' && file) {\n selectedFile = file\n break\n }\n\n if (action === 'navigate' && file) {\n currentDir = file\n }\n\n if (action === 'back') {\n const parentDir = dirname(currentDir)\n if (parentDir !== currentDir) {\n currentDir = parentDir\n }\n }\n }\n\n if (selectedFile) {\n console.log(chalk.green(`✓ 已选择: ${basename(selectedFile)}`))\n }\n\n return selectedFile\n }\n\n /**\n * 浏览目录\n */\n private async browseDirectory(\n currentDir: string,\n startDir: string\n ): Promise<{ action: 'select' | 'navigate' | 'back' | 'cancel'; file?: string }> {\n try {\n const entries = await readdir(currentDir, { withFileTypes: true })\n const relativePath = this.getRelativePath(currentDir, startDir)\n\n console.log(chalk.blue(`\\n📁 当前目录: ${relativePath}`))\n\n const choices = await this.buildChoices(entries, currentDir)\n\n if (choices.length === 1) {\n console.log(chalk.yellow('⚠️ 当前目录中没有找到 package.json 文件'))\n return { action: 'cancel' }\n }\n\n const { selection } = await prompts({\n type: 'select',\n name: 'selection',\n message: '请选择操作:',\n choices,\n instructions: false,\n })\n\n return selection ?? { action: 'cancel' }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : '未知错误'\n console.log(chalk.red(`❌ 无法访问目录: ${errorMessage}`))\n return { action: 'cancel' }\n }\n }\n\n /**\n * 构建选择列表\n */\n private async buildChoices(\n entries: Array<{ name: string; isFile(): boolean; isDirectory(): boolean }>,\n currentDir: string\n ): Promise<\n Array<{\n title: string\n value: { action: string; file?: string }\n description?: string\n }>\n > {\n const choices: Array<{\n title: string\n value: { action: string; file?: string }\n description?: string\n }> = []\n\n // 返回上级目录选项\n if (dirname(currentDir) !== currentDir) {\n choices.push({\n title: '📁 .. (返回上级目录)',\n value: { action: 'back' },\n description: '返回上级目录',\n })\n }\n\n // 检查当前目录是否有 package.json 文件\n const hasPackageJson = entries.some(entry => entry.isFile() && entry.name === 'package.json')\n\n if (hasPackageJson) {\n choices.push({\n title: '📄 package.json',\n value: { action: 'select', file: join(currentDir, 'package.json') },\n description: '选择此文件',\n })\n }\n\n // 只显示包含 package.json 的目录\n await this.addDirectoriesWithPackageJson(choices, entries, currentDir)\n\n // 取消选项\n choices.push({\n title: '❌ 取消',\n value: { action: 'cancel' },\n description: '取消选择',\n })\n\n return choices\n }\n\n /**\n * 添加包含 package.json 的目录\n */\n private async addDirectoriesWithPackageJson(\n choices: Array<{\n title: string\n value: { action: string; file?: string }\n description?: string\n }>,\n entries: Array<{ name: string; isDirectory(): boolean }>,\n currentDir: string\n ): Promise<void> {\n const directoriesWithPackageJson = await this.getDirectoriesWithPackageJson(entries, currentDir)\n\n directoriesWithPackageJson.forEach(dir => {\n choices.push({\n title: `📁 ${dir.name}/`,\n value: { action: 'navigate', file: join(currentDir, dir.name) },\n description: '进入目录',\n })\n })\n }\n\n /**\n * 获取包含 package.json 的目录列表\n */\n private async getDirectoriesWithPackageJson(\n entries: Array<{ name: string; isDirectory(): boolean }>,\n currentDir: string\n ): Promise<Array<{ name: string }>> {\n const directoriesWithPackageJson: Array<{ name: string }> = []\n\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith('.')) {\n try {\n const subEntries = await readdir(join(currentDir, entry.name), { withFileTypes: true })\n const hasPackageJson = subEntries.some(\n subEntry => subEntry.isFile() && subEntry.name === 'package.json'\n )\n\n if (hasPackageJson) {\n directoriesWithPackageJson.push({ name: entry.name })\n }\n } catch {\n // 忽略无法访问的目录\n }\n }\n }\n\n return directoriesWithPackageJson.sort((a, b) => a.name.localeCompare(b.name))\n }\n\n /**\n * 获取相对路径\n */\n private getRelativePath(currentDir: string, startDir: string): string {\n return currentDir.replace(startDir, '').replace(/^[\\\\/]/, '') || '.'\n }\n}\n","/*\n * @Description :\n * @Author : tcsl-fast-tianyu\n * @Date : 2025-09-11\n * @LastEditors : tcsl-fast-tianyu\n * @LastEditTime : 2025-10-21\n */\nimport prompts from 'prompts'\nimport chalk from 'chalk'\nimport type { ConfigFileInfo } from './ConfigService.ts'\nimport { ArrayUtils } from '../utils/array-utils.ts'\nimport { FileBrowserService } from './FileBrowserService.ts'\n\n/**\n * 配置UI服务类\n * 负责处理用户界面交互相关功能\n */\nexport class UIService {\n private fileBrowserService: FileBrowserService\n\n constructor() {\n this.fileBrowserService = new FileBrowserService()\n }\n\n /**\n * 选择配置文件(交互式)\n */\n async selectConfigFiles(allConfigs: ConfigFileInfo[]): Promise<ConfigFileInfo[]> {\n if (allConfigs.length === 0) {\n return []\n }\n\n // 按组分类配置文件\n const groups = ArrayUtils.groupBy(allConfigs, config => config.group)\n const selectedConfigs: ConfigFileInfo[] = []\n const groupEntries = Array.from(groups.entries())\n const totalGroups = groupEntries.length\n\n // 逐组展示并选择文件\n for (let i = 0; i < totalGroups; i++) {\n const groupEntry = ArrayUtils.safeGet(groupEntries, i)\n if (!groupEntry) continue\n\n const [groupName, groupConfigs] = groupEntry\n const currentIndex = i + 1\n\n // 显示进度和组信息\n console.log(\n `${chalk.cyan(`[${currentIndex}/${totalGroups}]`)} ${chalk.bold(groupName)} ${chalk.gray(`(${groupConfigs.length} 个文件)`)}`\n )\n\n // 为当前组创建文件选择列表\n const fileChoices = groupConfigs.map((config: ConfigFileInfo) => ({\n title: config.name,\n value: config,\n }))\n\n const { selectedFiles } = await prompts({\n type: 'multiselect',\n name: 'selectedFiles',\n message: '选择要更新的文件:',\n choices: fileChoices,\n instructions: false,\n })\n\n if (!selectedFiles || selectedFiles.length === 0) {\n continue\n }\n\n // 处理选择结果\n selectedConfigs.push(...selectedFiles)\n }\n\n if (selectedConfigs.length === 0) {\n console.log(chalk.yellow('⚠️ 未选择任何配置文件'))\n return []\n }\n\n // 显示最终选择的文件汇总\n this.displaySelectionSummary(selectedConfigs)\n\n return selectedConfigs\n }\n\n /**\n * 选择依赖列表文件(交互式)\n * @param projectRoot 项目根目录\n * @returns 选择的文件路径,如果未选择则返回 null\n */\n async selectDependencyFile(projectRoot: string): Promise<string | null> {\n console.log(chalk.blue('\\n📦 依赖列表文件选择'))\n console.log(chalk.gray('您可以选择使用本地 package.json 文件,或使用项目默认的依赖列表'))\n\n // 询问是否要使用自定义 package.json 文件\n const { useCustomFile } = await prompts({\n type: 'confirm',\n name: 'useCustomFile',\n message: '是否要使用自定义 package.json 文件?',\n initial: false,\n })\n\n if (!useCustomFile) {\n console.log(chalk.green('✓ 使用项目默认依赖列表'))\n return null\n }\n\n // 浏览文件系统选择 package.json 文件\n return await this.fileBrowserService.browseForPackageJson(projectRoot)\n }\n\n /**\n * 显示选择汇总\n */\n private displaySelectionSummary(selectedConfigs: ConfigFileInfo[]): void {\n console.log(\n `\\n${chalk.blue('📋')} ${chalk.bold('最终选择汇总:')} 共 ${selectedConfigs.length} 个文件`\n )\n\n const finalGroups = ArrayUtils.groupBy(selectedConfigs, config => config.group)\n\n Array.from(finalGroups.entries()).forEach(([groupName, configs]) => {\n console.log(` ${chalk.green('✓')} ${groupName}: ${configs.length} 个文件`)\n })\n }\n}\n","import chalk from 'chalk'\nimport ora from 'ora'\nimport type { ArchitectureType, ProjectType, UpdateConfigOptions } from '../types.ts'\nimport { FileService } from './FileService.ts'\nimport { Discovery } from '../core/Discovery.ts'\nimport { UIService } from './UIService.ts'\n/**\n * 配置文件信息接口\n */\nexport interface ConfigFileInfo {\n name: string\n path: string\n templatePath: string\n description: string\n commentPrefix?: string\n commentSuffix?: string\n group: string\n priority: number\n}\n\n/**\n * 配置服务类\n * 负责管理项目配置文件的扫描、分析和更新\n */\nexport class ConfigService {\n private templatesDir: string\n private fileService: FileService\n private uiService: UIService\n\n constructor(templatesDir: string) {\n this.templatesDir = templatesDir\n this.fileService = new FileService(templatesDir)\n this.uiService = new UIService()\n }\n\n /**\n * 更新项目配置文件\n */\n async updateConfig(options?: UpdateConfigOptions): Promise<void> {\n // 添加初始加载状态\n const initialSpinner = options?.silent ? null : ora('正在初始化配置更新...').start()\n\n try {\n let projectPath, rootPath\n if (options?.projectPath) {\n // eslint-disable-next-line prefer-destructuring\n projectPath = options.projectPath\n // eslint-disable-next-line prefer-destructuring\n rootPath = options.rootPath\n } else {\n const { projectPath: discoveredProjectPath, rootPath: discoveredRootPath } =\n await Discovery.findProjectRoot()\n projectPath = discoveredProjectPath\n rootPath = discoveredRootPath\n }\n\n if (!projectPath || !rootPath) {\n if (initialSpinner) initialSpinner.fail('未找到项目根目录(package.json)')\n throw new Error('未找到项目根目录(package.json)')\n }\n\n // 添加架构检测加载状态\n if (initialSpinner) {\n initialSpinner.text = '正在检测项目架构...'\n }\n\n // 检测项目架构类型\n const architecture =\n options?.architectureType ?? (await Discovery.detectArchitectureType(rootPath))\n\n // 检测项目类型\n const projectType = options?.projectType ?? (await Discovery.detectProjectType(projectPath))\n\n if (initialSpinner) {\n initialSpinner.succeed(\n chalk.gray(\n `📋 项目架构: ${architecture};项目类型: ${projectType === 'app' ? '应用' : projectType === 'lib' ? '库' : '根目录'}`\n )\n )\n }\n // 获取可用的配置文件列表\n const allConfigs = await Discovery.getAvailableConfigFiles(\n this.templatesDir,\n projectPath,\n options\n )\n\n if (allConfigs.length === 0) {\n if (!options?.silent) {\n console.log(chalk.yellow('⚠️ 未找到可更新的配置文件'))\n }\n return\n }\n\n // 选择要更新的文件(静默模式下选择所有文件)\n const selectedConfigs = options?.silent\n ? allConfigs\n : await this.uiService.selectConfigFiles(allConfigs)\n\n if (selectedConfigs.length === 0) {\n return\n }\n\n await this.updateSelectedConfigs(\n selectedConfigs,\n projectPath,\n rootPath,\n architecture,\n projectType,\n options?.silent\n )\n } catch (error) {\n if (initialSpinner) {\n initialSpinner.fail('配置更新初始化失败')\n }\n throw error\n }\n }\n\n /**\n * 更新选中的配置文件\n */\n private async updateSelectedConfigs(\n selectedConfigs: ConfigFileInfo[],\n projectRoot: string,\n rootPath: string,\n architecture: ArchitectureType,\n projectType: ProjectType,\n silent?: boolean\n ): Promise<void> {\n let spinner = silent ? null : ora('正在更新配置文件...').start()\n\n try {\n let updatedCount = 0\n let hasEnvFiles = false\n for (const config of selectedConfigs) {\n try {\n await this.fileService.updateConfigFile(\n config,\n projectRoot,\n rootPath,\n architecture,\n projectType\n )\n updatedCount++\n // 检查是否包含环境变量文件\n if (config.group === 'env' || config.name.startsWith('.env')) {\n hasEnvFiles = true\n }\n } catch (error: any) {\n // 记录失败的文件,但继续处理其他文件\n if (!silent && spinner) {\n spinner.warn(chalk.yellow(`⚠️ ${error.message}`))\n spinner = ora('正在更新配置文件...').start()\n }\n }\n }\n\n if (spinner) {\n const totalFiles = selectedConfigs.length\n const failedCount = totalFiles - updatedCount\n let message = `配置更新完成!成功更新 ${updatedCount}/${totalFiles} 个文件`\n\n if (failedCount > 0) {\n message += chalk.yellow(` (${failedCount} 个文件更新失败)`)\n }\n\n if (hasEnvFiles) {\n message += chalk.gray(' • 已更新环境变量配置')\n }\n\n spinner.succeed(chalk.green(message))\n }\n\n // 只有在更新了环境变量文件时才检查并更新package.json中的构建脚本\n if (hasEnvFiles) {\n await Discovery.updatePackageJsonScripts(projectRoot)\n }\n } catch (error: any) {\n if (spinner) {\n spinner.fail(chalk.red('配置文件更新失败'))\n }\n throw error\n }\n }\n}\n","import chalk from 'chalk'\nimport prompts from 'prompts'\nimport { join } from 'path'\nimport type { ArchitectureType, FrameworkType, ProjectOptions } from '../types.ts'\nimport type { CreateProjectOptions } from '../workflows/CreateProjectWorkflow.ts'\n\n/**\n * 项目选项服务\n * 负责处理用户输入和项目配置选项的获取\n */\nexport class ProjectOptionsService {\n constructor() {\n // 初始化服务\n }\n /**\n * 获取项目创建选项\n */\n async getProjectOptions(\n projectName?: string,\n options: ProjectOptions = {}\n ): Promise<CreateProjectOptions> {\n console.log()\n // 开始创建项目\n console.log()\n\n // 获取项目基本信息\n const name = await this.getProjectName(projectName)\n const architecture = await this.getArchitectureType(options)\n const framework = await this.getFrameworkType(options)\n\n // 获取项目路径\n const targetPath = await this.getTargetPath()\n\n // 显示项目配置摘要\n await this.displayProjectSummary({\n projectName: name,\n architectureType: architecture,\n framework,\n targetPath,\n })\n\n return {\n projectName: name,\n architectureType: architecture,\n framework,\n targetPath,\n }\n }\n\n /**\n * 获取项目名称\n */\n private async getProjectName(projectName?: string): Promise<string> {\n if (projectName) {\n return projectName\n }\n\n const response = await prompts(\n {\n type: 'text',\n name: 'projectName',\n message: '请输入项目名称:',\n initial: 'my-project',\n validate: (value: string) => {\n if (!value.trim()) return '项目名称不能为空'\n if (!/^[a-zA-Z0-9-_]+$/.test(value)) return '项目名称只能包含字母、数字、连字符和下划线'\n return true\n },\n },\n {\n onCancel: () => {\n // 已取消项目创建\n process.exit(0)\n },\n }\n )\n\n if (!response.projectName) {\n // 已取消项目创建\n process.exit(0)\n }\n\n return response.projectName\n }\n\n /**\n * 获取架构类型\n */\n private async getArchitectureType(options: ProjectOptions): Promise<ArchitectureType> {\n if (options.architecture) {\n return options.architecture\n }\n\n if (options.monorepo) {\n return 'monorepo'\n }\n\n if (options.single) {\n return 'single'\n }\n\n const response = await prompts(\n {\n type: 'select',\n name: 'architecture',\n message: '选择项目架构:',\n choices: [\n {\n title: '单体仓库 (Single Repository)',\n value: 'single',\n description: '适合小到中型项目,所有代码在一个仓库中',\n },\n {\n title: 'Monorepo (多包管理)',\n value: 'monorepo',\n description: '适合大型项目,支持多个包的统一管理',\n },\n ],\n initial: 0,\n },\n {\n onCancel: () => {\n // 已取消项目创建\n process.exit(0)\n },\n }\n )\n\n if (!response.architecture) {\n // 已取消项目创建\n process.exit(0)\n }\n\n return response.architecture\n }\n\n /**\n * 获取框架类型\n */\n private async getFrameworkType(options: ProjectOptions): Promise<FrameworkType> {\n if (options.framework) {\n return options.framework\n }\n\n // 目前只支持 Vue3,直接返回\n return 'vue3'\n }\n\n /**\n * 获取目标路径\n */\n private async getTargetPath(): Promise<string> {\n // 直接在当前目录下创建项目文件夹\n return process.cwd()\n }\n\n /**\n * 显示项目配置摘要\n */\n private async displayProjectSummary(config: {\n projectName: string\n architectureType: ArchitectureType\n framework: FrameworkType\n targetPath: string\n }): Promise<void> {\n console.log()\n // 项目配置摘要\n console.log(chalk.gray('─'.repeat(50)))\n console.log(`${chalk.bold('项目名称:')} ${chalk.cyan(config.projectName)}`)\n console.log(\n `${chalk.bold('项目架构:')} ${chalk.cyan(config.architectureType === 'monorepo' ? 'Monorepo' : 'Single Repository')}`\n )\n console.log(`${chalk.bold('开发框架:')} ${chalk.cyan('Vue 3')}`)\n console.log(\n `${chalk.bold('创建路径:')} ${chalk.cyan(join(config.targetPath, config.projectName))}`\n )\n console.log(chalk.gray('─'.repeat(50)))\n console.log()\n }\n}\n"],"names":["MergeService","configMerger","constructor","this","FileMerger","determineUpdateMode","targetContent","templateContent","config","trim","group","includes","name","mergeConfigContent","BuildToolsService","mergeService","updatedMonorepoConfigs","Set","isTsConfigSeparated","content","tsConfig","JSON","parse","extendsValue","reg","cleanValue","replace","test","Array","isArray","some","item","checkExtends","extends","isViteConfigSeparated","checkConfigStatus","projectPath","type","fileName","configPath","join","FileOperations","exists","readFile","isSeparated","process","configType","templatesDir","rootPath","architecture","projectType","handleProjectConfigNotExists","handleProjectConfigSeparated","handleProjectConfigNotSeparated","calculateRelativePath","from","to","relativePath","relative","startsWith","updateBaseConfigFile","targetPath","templatePath","configKey","has","writeFile","add","mergeAndUpdateBaseConfigFile","configsFilePath","configName","existingContent","mergedContent","ensureDir","baseTemplatePath","baseTargetPath","relativeConfigsPath","stringify","baseTemplateContent","baseTargetContent","mergedBaseContent","mergedProjectContent","projectConfigPath","backupConfigPath","rename","console","log","commentPrefix","commentSuffix","FileService","buildToolsService","updateConfigFile","projectRoot","updateCtx","finalContent","getAutoUpdateModeOfNpmrc","getAutoUpdateMode","updateMode","path","mode","error","Error","message","dirname","architectureTemplatePath","architectureTemplateContent","FileBrowserService","browseForPackageJson","startDir","currentDir","selectedFile","action","file","browseDirectory","chalk","yellow","parentDir","green","basename","entries","readdir","withFileTypes","getRelativePath","blue","choices","buildChoices","length","selection","prompts","instructions","errorMessage","red","push","title","value","description","entry","isFile","addDirectoriesWithPackageJson","getDirectoriesWithPackageJson","forEach","dir","directoriesWithPackageJson","isDirectory","subEntries","subEntry","sort","a","b","localeCompare","UIService","fileBrowserService","selectConfigFiles","allConfigs","groups","ArrayUtils","groupBy","selectedConfigs","groupEntries","totalGroups","i","groupEntry","safeGet","groupName","groupConfigs","currentIndex","cyan","bold","gray","fileChoices","map","selectedFiles","displaySelectionSummary","selectDependencyFile","useCustomFile","initial","finalGroups","configs","fileService","uiService","updateConfig","options","initialSpinner","silent","ora","start","discoveredProjectPath","discoveredRootPath","Discovery","findProjectRoot","fail","text","architectureType","detectArchitectureType","detectProjectType","succeed","getAvailableConfigFiles","updateSelectedConfigs","spinner","updatedCount","hasEnvFiles","warn","totalFiles","failedCount","updatePackageJsonScripts","getProjectOptions","projectName","getProjectName","getArchitectureType","framework","getFrameworkType","getTargetPath","displayProjectSummary","response","validate","onCancel","exit","monorepo","single","cwd","repeat"],"mappings":"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