// scripts/generate-index.ts import { writeFileSync } from "fs"; import { extname, relative, resolve } from "path"; import { globSync } from "glob"; interface Config { targetDirs: string[]; includeExtensions: string[]; excludeKeywords: { dirs: string[]; fileSuffixes: string[]; filePatterns: RegExp[]; }; } const CONFIG: Config = { targetDirs: ["src"], includeExtensions: [".ts", ".tsx", ".vue"], excludeKeywords: { dirs: ["__tests__", "tests", "story", "stories", "types"], fileSuffixes: [".d.ts"], filePatterns: [ /^index\.(ts|tsx|js|jsx)$/, // index文件 /\.(test|spec)\./, // 测试文件 .test.xxx / .spec.xxx /\.(story|stories)\./, // Storybook文件 .story.xxx / .stories.xxx ], }, }; /** * 统一路径分隔符(兼容 Windows/Linux) * @param path 原始路径 * @returns 标准化路径(全部转为 /) */ const normalizePath = (path: string): string => path.replace(/\\/g, "/"); /** * 检查文件是否在排除目录中 * @param filePath 文件路径 * @returns 是否在排除目录 */ const isInExcludeDir = (filePath: string): boolean => { const normalizedPath = normalizePath(filePath); return CONFIG.excludeKeywords.dirs.some((dir) => normalizedPath.includes(`/${dir}/`), ); }; /** * 检查文件是否匹配排除正则 * @param fileName 文件名 * @returns 是否匹配排除规则 */ const isMatchExcludePattern = (fileName: string): boolean => { return CONFIG.excludeKeywords.filePatterns.some((pattern) => pattern.test(fileName), ); }; /** * 检查文件是否为排除后缀(如 .d.ts) * @param filePath 文件路径 * @returns 是否为排除后缀 */ const isExcludeSuffix = (filePath: string): boolean => { return CONFIG.excludeKeywords.fileSuffixes.some((suffix) => filePath.endsWith(suffix), ); }; function isValidFile(filePath: string): boolean { const fileName = filePath.split(/[\\/]/).pop()!; // 按优先级过滤:目录 > 后缀 > 文件名规则 > 扩展名 if (isInExcludeDir(filePath)) return false; if (isExcludeSuffix(filePath)) return false; if (isMatchExcludePattern(fileName)) return false; const ext = extname(filePath); const isValidExt = CONFIG.includeExtensions.includes(ext); return isValidExt; } // ========== 5. 生成索引文件(错误处理 + 语法规范) ========== function generateIndexFile(dirPath: string): void { const searchPattern = resolve(dirPath, "**", "*.*"); const allFiles = globSync(searchPattern, { nodir: true, absolute: true, windowsPathsNoEscape: true, dot: false, follow: true, }); if (allFiles.length === 0) { return; } const validFiles = allFiles.filter(isValidFile); if (validFiles.length === 0) { return; } validFiles.sort(); // 生成导出语句 const exportStatements = validFiles.map((file) => { const relPath = relative(dirPath, file); const importPath = `./${relPath.replace(/\.[^.]+$/, "").replace(/\\/g, "/")}`; return `export * from '${importPath}';`; }); // 生成索引文件内容(模板字符串格式化) const indexContent = ` import './index.scss'; ${exportStatements.join("\n")} `; const indexFilePath = resolve(dirPath, "index.ts"); writeFileSync(indexFilePath, indexContent, "utf-8"); } // ========== 6. 主函数(完善错误处理 + 类型安全) ========== function main(): void { const [targetDir] = CONFIG.targetDirs; if (!targetDir) { console.error(`❌ 未配置目标扫描目录,请检查 CONFIG.targetDirs`); process.exit(1); } const absTargetDir = resolve(process.cwd(), targetDir); try { generateIndexFile(absTargetDir); } catch (error) { // 处理 unknown 类型错误(TypeScript 最佳实践) const errorMsg = error instanceof Error ? error.message : String(error); console.error(`❌ 生成 index.ts 失败: ${errorMsg}`); process.exit(1); } } // 执行主函数 main();