UNPKG

@tianyio/quality-helper

Version:

A comprehensive quality helper tool for project scaffolding and management

476 lines (475 loc) 15.8 kB
import { join, dirname } from "path"; import { F as FileOperations } from "./core-file-ops-d43o99eg.js"; class Discovery { /** * 获取可用的配置文件列表 */ static async getAvailableConfigFiles(templatesDir, projectRoot, options) { const configs = []; await Discovery.scanTemplateDirectories(templatesDir, projectRoot, configs, options); return configs; } /** * 应用过滤选项 */ static applyFilters(dirInfos, options) { const { includeOnly = [], excludeGitHooks, excludeStaticFiles, excludeScripts } = options ?? {}; const include = includeOnly.length > 0; if (include || excludeGitHooks || excludeStaticFiles || excludeScripts) { return dirInfos.filter(({ dir }) => { const [, configType] = dir.split("/"); if (include) { return includeOnly.includes(configType); } if (excludeGitHooks && configType === "git-hooks") { return false; } if (excludeStaticFiles && configType === "static-files") { return false; } if (excludeScripts && configType === "scripts") { return false; } return true; }); } else { return dirInfos; } } /** * 扫描模板目录 */ static async scanTemplateDirectories(templatesDir, projectRoot, configs, options) { const templateDirs = [ { dir: "configs/rules", targetDir: "rules", priority: 11 }, { dir: "configs/env", targetDir: "env", priority: 10 }, { dir: "configs/static-files", targetDir: "", priority: 9 }, { dir: "configs/types", targetDir: "types", priority: 8 }, { dir: "configs/develop-tools", targetDir: "", priority: 7 }, { dir: `configs/build-tools`, targetDir: "", priority: 6 }, { dir: `configs/scripts`, targetDir: "scripts", priority: 5 }, { dir: "configs/npm", targetDir: "", priority: 4 }, { dir: "configs/git-hooks", targetDir: ".husky", priority: 3 }, { dir: "configs/git-config", targetDir: "", priority: 2 }, { dir: "configs/docs", targetDir: "", priority: 1 } ]; const filteredTemplateDirs = Discovery.applyFilters(templateDirs, options); for (const { dir, targetDir, priority } of filteredTemplateDirs) { const templateSubDir = join(templatesDir, dir); try { const files = await FileOperations.readDir(templateSubDir); await Discovery.processTemplateFiles( files, templateSubDir, targetDir, priority, dir, projectRoot, configs, options ); } catch { continue; } } } /** * 处理模板文件 */ static async processTemplateFiles(files, templateSubDir, targetDir, priority, dir, projectRoot, configs, options) { for (const file of files) { if (Discovery.shouldSkipFile(file, options)) { continue; } const templatePath = join(templateSubDir, file); const { finalTargetPath, displayName } = Discovery.resolveFilePaths( file, targetDir, projectRoot ); const exists = configs.some((config) => config.name === displayName); if (!exists) { const group = Discovery.determineFileGroup(dir); const [commentPrefix, commentSuffix] = Discovery.getCommentFix(displayName); const configInfo = { name: displayName, path: finalTargetPath, templatePath, description: Discovery.getFileDescription(displayName), commentPrefix, commentSuffix, group, priority }; configs.push(configInfo); } } } /** * 判断是否应该跳过文件 */ static shouldSkipFile(file, options) { if (file === ".vscode") { return true; } if (options?.excludeNpmrc && file === "npmrc") { return true; } const skipPatterns = [ /^\.DS_Store$/, /^Thumbs\.db$/, /^node_modules$/, /^dist$/, /^build$/, /^\.cache/, /^\.temp/, /^\.tmp/, /~$/, /\.bak$/, /\.swp$/, /\.swo$/ ]; if (skipPatterns.some((pattern) => pattern.test(file))) return true; const specialFiles = ["apps", "libs", "single", "monorepo"]; return specialFiles.includes(file); } /** * 解析文件路径 */ static resolveFilePaths(file, targetDir, projectRoot) { let finalTargetPath; let displayName; const needAddDot = [ "browserslistrc", "editorconfig", "lintstagedrc", "prettierignore", "gitattributes", "gitconfig", "gitignore", "npmrc", "pnpmrc" ]; if (file === "vite.svg") { finalTargetPath = join(projectRoot, "public", file); displayName = file; } else if (needAddDot.includes(file)) { finalTargetPath = join(projectRoot, `.${file}`); displayName = `.${file}`; } else { finalTargetPath = targetDir ? join(projectRoot, targetDir, file) : join(projectRoot, file); displayName = file; } return { finalTargetPath, displayName }; } /** * 确定文件分组 */ static determineFileGroup(dir) { if (dir === "configs/types") return "types"; if (dir === "configs/rules") return "rules"; if (dir === "configs/env") return "env"; if (dir === "configs/git-hooks") return "git-hooks"; if (dir === "configs/npm") return "npm"; if (dir === "configs/develop-tools") return "develop-tools"; if (dir === "configs/git-config") return "git-config"; if (dir.includes("configs/build-tools")) return "build-tools"; if (dir.includes("configs/docs")) return "docs"; if (dir === "configs/static-files") return "static-files"; if (dir === "configs/scripts") return "scripts"; return "other"; } /** * 获取文件描述 */ static getFileDescription(fileName) { const descriptions = { eslint: "ESLint 代码质量检查配置", prettier: "Prettier 代码格式化配置", ".editorconfig": "EditorConfig 编辑器配置", ".gitignore": "Git 忽略文件配置", ".gitattributes": "Git 属性配置", "package.json": "Node.js 项目配置文件", "tsconfig.json": "TypeScript 编译配置", "vite.config": "Vite 构建工具配置", ".npmrc": "npm 包管理器配置", ".pnpmrc": "pnpm 包管理器配置", ".env": "环境变量配置文件", "base.d.ts": "TypeScript 环境变量类型定义", "pre-commit": "Git 提交前钩子", "commit-msg": "Git 提交信息钩子", "vite.svg": "Vite 项目图标", "babel.config": "Babel编译配置(预设、插件)", ".browserslistrc": "浏览器兼容性配置文件", "commitlint.config": "CommitLint 规范提交信息", lintstage: "LintStaged 提交前代码检查" }; for (const key in descriptions) { if (fileName.includes(key)) { return descriptions[key]; } } return `${fileName} 配置文件`; } /** * 获取注释前缀 */ static getCommentFix(fileName) { if (fileName.endsWith(".js") || fileName.endsWith(".ts") || fileName.endsWith(".jsx") || fileName.endsWith(".tsx")) { return ["//", ""]; } if (fileName.endsWith(".css") || fileName.endsWith(".scss") || fileName.endsWith(".sass") || fileName.endsWith(".less")) { return ["/*", "*/"]; } if (fileName.endsWith(".html") || fileName.endsWith(".xml") || fileName.endsWith(".svg") || fileName.endsWith(".vue")) { return ["<!--", "-->"]; } if (fileName.endsWith(".sh") || fileName.endsWith(".bash") || fileName.endsWith(".zsh")) { return ["#", ""]; } if (fileName.endsWith(".py") || fileName.endsWith(".rb") || fileName.endsWith(".php")) { return ["#", ""]; } if (fileName.endsWith(".yaml") || fileName.endsWith(".yml")) { return ["#", ""]; } const commentSupportedFiles = [ ".browserslistrc", ".editorconfig", ".gitattributes", ".gitconfig", ".gitignore", ".lintstagedrc", ".npmrc", ".pnpmrc", ".prettierignore" ]; if (fileName.startsWith(".env") || commentSupportedFiles.includes(fileName)) { return ["#", ""]; } return ["//", ""]; } /** * 检查是否为 monorepo 根目录 */ static async isMonorepoRoot(projectRoot) { try { const workspaceFiles = ["pnpm-workspace.yaml", "lerna.json", "rush.json"]; for (const file of workspaceFiles) { if (await FileOperations.exists(join(projectRoot, file))) { return true; } } const packageJsonPath = join(projectRoot, "package.json"); const packageJsonContent = await FileOperations.readFile(packageJsonPath); const packageJson = JSON.parse(packageJsonContent); if (packageJson.workspaces) { return true; } return false; } catch { return false; } } /** * 查找项目根目录信息对象 * 返回包含项目根目录和顶层根目录的对象 * - projectRoot: 根据项目类型返回适当的根目录 * - 如果项目是monorepo架构的子项目,返回子项目的根目录 * - 如果项目是monorepo项目非子项目,返回monorepo项目根目录 * - 如果是single架构的项目,返回项目根目录 * - rootPath: 总是返回最顶层的项目根目录 * - 如果项目是monorepo架构的子项目,返回monorepo项目的根目录 * - 如果项目是monorepo项目非子项目,返回monorepo项目根目录 * - 如果是single架构的项目,返回项目根目录 */ static async findProjectRoot(startDir = process.cwd()) { let currentDir = startDir; let projectPath = null; let rootPath = null; while (currentDir !== dirname(currentDir)) { const packageJsonPath = join(currentDir, "package.json"); if (await FileOperations.exists(packageJsonPath)) { projectPath ??= currentDir; rootPath = currentDir; const isMonorepoRoot = await Discovery.isMonorepoRoot(currentDir); if (isMonorepoRoot) { return { projectPath, rootPath: currentDir }; } } currentDir = dirname(currentDir); } return { projectPath, rootPath }; } /** * 查找指定目录下的所有package.json文件 */ static async findPackageJsonFiles(dir) { const packageJsonFiles = []; const searchDirectory = async (currentDir) => { const entries = await FileOperations.readDirWithFileTypes(currentDir); for (const entry of entries) { const fullPath = join(currentDir, entry.name); if (entry.name === "package.json" && entry.isFile()) { packageJsonFiles.push(fullPath); } else if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") { await searchDirectory(fullPath); } } }; await searchDirectory(dir); return packageJsonFiles; } /** * 检测项目架构类型 */ static async detectArchitectureType(root) { return await Discovery.isMonorepoRoot(root) ? "monorepo" : "single"; } /** * 检测项目类型(应用项目或库项目) * 基于npm库项目的典型特征进行判断 */ static async detectProjectType(projectRoot) { try { const packageJsonPath = join(projectRoot, "package.json"); if (!await FileOperations.exists(packageJsonPath)) { return "app"; } const architectureType = await Discovery.detectArchitectureType(projectRoot); if (architectureType === "monorepo") { const isMonorepoRoot = await Discovery.isMonorepoRoot(projectRoot); if (isMonorepoRoot) { return "root"; } } const packageJsonContent = await FileOperations.readFile(packageJsonPath); const packageJson = JSON.parse(packageJsonContent); let libScore = 0; const hasExports = !!packageJson.exports; const hasMain = !!packageJson.main; const hasModule = !!packageJson.module; const hasTypes = !!(packageJson.types ?? packageJson.typings); const hasFiles = !!packageJson.files; if (hasExports) libScore += 3; if (hasMain) libScore += 2; if (hasModule) libScore += 2; if (hasTypes) libScore += 2; if (hasFiles) libScore += 1; const keywords = packageJson.keywords ?? []; const libKeywords = [ "library", "component", "util", "tool", "helper", "sdk", "api", "framework", "module", "package", "plugin", "extension", "widget", "ui", "esm", "cjs" ]; const hasLibKeywords = keywords.some( (keyword) => libKeywords.includes(keyword.toLowerCase()) ); if (hasLibKeywords) libScore += 2; const devDependencies = packageJson.devDependencies ?? {}; const libDevDependencies = ["jest", "vitest", "mocha", "karma", "vite-plugin-dts"]; const hasLibDevDeps = Object.keys(devDependencies).some( (dep) => libDevDependencies.some((libDep) => dep.includes(libDep)) ); if (hasLibDevDeps) libScore += 2; const hasPublishConfig = !!packageJson.publishConfig; if (hasPublishConfig) libScore += 1; if (libScore >= 5) { return "lib"; } return "app"; } catch (error) { console.warn("⚠️ 项目类型检测失败,使用默认类型:", error); return "app"; } } /** * 更新package.json脚本 */ static async updatePackageJsonScripts(projectRoot) { const packageJsonPath = join(projectRoot, "package.json"); try { const packageJson = JSON.parse(await FileOperations.readFile(packageJsonPath)); const defaultScripts = { build: "vue-tsc --noEmit && vite build", preview: "vite preview" }; const envBuildScripts = await Discovery.generateEnvBuildScripts(projectRoot); packageJson.scripts = { ...defaultScripts, ...packageJson.scripts, ...envBuildScripts }; await FileOperations.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); } catch (error) { console.warn("⚠️ package.json脚本更新失败:", error); } } /** * 根据环境变量文件生成构建脚本 */ static async generateEnvBuildScripts(projectRoot) { const envBuildScripts = {}; try { const envDir = join(projectRoot, "env"); const envFiles = await Discovery.scanEnvFilesInDirectory( await FileOperations.exists(envDir) ? envDir : projectRoot ); for (const envFile of envFiles) { const mode = Discovery.extractModeFromEnvFile(envFile); if (mode) { envBuildScripts[`build:${mode}`] = `vite build --mode ${mode}`; } } } catch (error) { console.warn("⚠️ 生成环境构建脚本失败:", error); } return envBuildScripts; } /** * 扫描指定目录中的环境变量文件 */ static async scanEnvFilesInDirectory(directory) { try { const files = await FileOperations.readDir(directory); return files.filter( (file) => file.startsWith(".env.") && !file.endsWith(".example") && !file.endsWith(".local") ); } catch { return []; } } /** * 从环境变量文件名中提取模式名称 */ static extractModeFromEnvFile(envFile) { const mode = envFile.replace(/^\.env\./, ""); if (/^[a-zA-Z0-9.-]+$/.test(mode) && mode.length > 0) { return mode; } return null; } } export { Discovery as D }; //# sourceMappingURL=core-discovery-DU1aPJk0.js.map