@tianyio/quality-helper
Version:
A comprehensive quality helper tool for project scaffolding and management
476 lines (475 loc) • 15.8 kB
JavaScript
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