import * as path from 'path'; import * as fs from 'fs-extra'; import { TemplateNotFoundError, FileSystemError, RulesNotFoundError } from '../errors'; /** * 规则文件信息接口 */ export interface RuleFile { relativePath: string; // 相对路径(如 .cursor/rules/xxx.md) absolutePath: string; // 绝对路径 content: string; // 文件内容 size: number; // 内容长度 } /** * 规则文件对比结果接口 */ export interface RulesDiff { toAdd: RuleFile[]; // 需要新增的文件 toUpdate: RuleFile[]; // 需要更新的文件 unchanged: RuleFile[]; // 无需变更的文件 } /** * 规则同步结果接口 */ export interface RulesSyncResult { added: string[]; // 新增的文件列表 updated: string[]; // 更新的文件列表 unchanged: string[]; // 未变更的文件列表 } // 规则目录配置 const RULES_DIRS = [ '.cursor/rules', '.kiro/steering' ]; // 支持的规则文件扩展名 const RULES_EXTENSIONS = ['.md', '.mdc']; /** * 默认模板配置 */ const defaultTemplates: { [key: string]: { folder: string } } = { uniapp: { folder: 'uniapp' }, uniappx: { folder: 'uniappx' } }; /** * 获取模板根目录路径 */ function getTemplateRootPath(): string { return path.resolve(__dirname, '../../template'); } /** * 获取模板规则目录路径 * @param templateName 模板名称 * @returns 模板根目录路径 */ export function getTemplateRulesPath(templateName: string): string { const templateConfig = defaultTemplates[templateName]; if (!templateConfig) { throw new TemplateNotFoundError(templateName); } return path.join(getTemplateRootPath(), templateConfig.folder); } /** * 扫描指定目录下的规则文件 * @param baseDir 基础目录路径 * @returns 规则文件数组 */ export async function scanRulesFiles(baseDir: string): Promise { const ruleFiles: RuleFile[] = []; for (const rulesDir of RULES_DIRS) { const fullRulesDir = path.join(baseDir, rulesDir); if (!await fs.pathExists(fullRulesDir)) { continue; } try { const files = await fs.readdir(fullRulesDir); for (const file of files) { const ext = path.extname(file).toLowerCase(); if (!RULES_EXTENSIONS.includes(ext)) { continue; } const absolutePath = path.join(fullRulesDir, file); const stat = await fs.stat(absolutePath); if (!stat.isFile()) { continue; } const content = await fs.readFile(absolutePath, 'utf-8'); const relativePath = path.join(rulesDir, file); ruleFiles.push({ relativePath, absolutePath, content, size: content.length }); } } catch (error) { if (error instanceof Error) { throw new FileSystemError(`扫描规则文件失败: ${error.message}`, error); } throw new FileSystemError('扫描规则文件时发生未知错误'); } } return ruleFiles; } /** * 对比模板规则文件和项目规则文件 * @param templateRules 模板规则文件数组 * @param projectRules 项目规则文件数组 * @returns 规则文件对比结果 */ export function compareRules(templateRules: RuleFile[], projectRules: RuleFile[]): RulesDiff { const projectRulesMap = new Map(); for (const rule of projectRules) { projectRulesMap.set(rule.relativePath, rule); } const toAdd: RuleFile[] = []; const toUpdate: RuleFile[] = []; const unchanged: RuleFile[] = []; for (const templateRule of templateRules) { const projectRule = projectRulesMap.get(templateRule.relativePath); if (!projectRule) { // 项目中不存在该规则文件,需要新增 toAdd.push(templateRule); } else if (templateRule.size > projectRule.size) { // 模板文件内容更长,需要更新 toUpdate.push(templateRule); } else { // 模板文件内容等于或短于项目文件,保持不变 unchanged.push(templateRule); } } return { toAdd, toUpdate, unchanged }; } /** * 同步规则文件从模板到目标目录 * @param templateName 模板名称 * @param targetDir 目标目录 * @returns 同步结果 */ export async function syncRules(templateName: string, targetDir: string): Promise { const templatePath = getTemplateRulesPath(templateName); // 检查模板目录是否存在 if (!await fs.pathExists(templatePath)) { throw new TemplateNotFoundError(templateName); } // 扫描模板和项目的规则文件 const templateRules = await scanRulesFiles(templatePath); const projectRules = await scanRulesFiles(targetDir); // 如果模板中没有规则文件 if (templateRules.length === 0) { throw new RulesNotFoundError(templateName); } // 对比规则文件 const diff = compareRules(templateRules, projectRules); const result: RulesSyncResult = { added: [], updated: [], unchanged: diff.unchanged.map(r => r.relativePath) }; // 复制需要新增的文件 for (const rule of diff.toAdd) { const targetPath = path.join(targetDir, rule.relativePath); try { await fs.ensureDir(path.dirname(targetPath)); await fs.writeFile(targetPath, rule.content, 'utf-8'); result.added.push(rule.relativePath); } catch (error) { if (error instanceof Error) { throw new FileSystemError(`复制规则文件失败: ${error.message}`, error); } throw new FileSystemError('复制规则文件时发生未知错误'); } } // 更新需要覆盖的文件 for (const rule of diff.toUpdate) { const targetPath = path.join(targetDir, rule.relativePath); try { await fs.writeFile(targetPath, rule.content, 'utf-8'); result.updated.push(rule.relativePath); } catch (error) { if (error instanceof Error) { throw new FileSystemError(`更新规则文件失败: ${error.message}`, error); } throw new FileSystemError('更新规则文件时发生未知错误'); } } return result; }