import fs from "fs"; import path from "path"; // 1. 引入 tinyglobby import { globSync } from "tinyglobby"; interface Config { outputDir: string; outputFilenameWithExt: string; scanDirs: string[]; importPrefix: string; predefineStatements: string[]; includeExtensions: string[]; excludeDirs: string[]; excludeFileExtensions: string[]; excludePatterns: RegExp[]; } const cssConfig: Config = { outputDir: "src", outputFilenameWithExt: "index.css", scanDirs: ["src"], importPrefix: "@import", predefineStatements: ['@import "tailwindcss";'], includeExtensions: [".css"], excludeDirs: ["__tests__", "tests", "story", "stories", "types"], excludeFileExtensions: [], excludePatterns: [/^index\.(css)$/, /\.(test|spec)\./, /\.(story|stories)\./], }; const tsConfig: Config = { outputDir: "src", outputFilenameWithExt: "index.ts", scanDirs: ["src"], importPrefix: "export * from", predefineStatements: ["import './index.css'"], includeExtensions: [".ts", "tsx", "js", "jsx"], excludeDirs: ["__tests__", "tests", "story", "stories", "types"], excludeFileExtensions: [".d.ts"], excludePatterns: [ /^index\.(ts|tsx|js|jsx)$/, /\.(test|spec)\./, /\.(story|stories)\./, ], }; const normalizePath = (p: string) => p.replace(/\\/g, "/"); const isExcludeDir = (filePath: string, excludeDirs: string[]) => { const normalized = normalizePath(filePath); return excludeDirs.some((dir) => normalized.includes(`/${dir}/`)); }; const isExcludeFileExtensions = ( filePath: string, excludeFileExtensions: string[], ) => excludeFileExtensions.some((ext) => filePath.endsWith(ext)); const isExcludePattern = (fileName: string, excludePatterns: RegExp[]) => excludePatterns.some((pattern) => pattern.test(fileName)); // ---------------------------------------- function isValidFile(filePath: string, config: Config): boolean { const fileName = filePath.split(/[\\/]/).pop()!; if (isExcludeDir(filePath, config.excludeDirs)) return false; if (isExcludeFileExtensions(filePath, config.excludeFileExtensions)) return false; if (isExcludePattern(fileName, config.excludePatterns)) return false; const ext = path.extname(filePath); return config.includeExtensions.includes(ext); } // ----------------------------------------- function generateIndexFile(config: Config) { const currentPath = process.cwd(); const outputPath = path.resolve(currentPath, config.outputDir); let exportStatements: string[] = []; // ------ scanDirs forEach start ------------------------ config.scanDirs.forEach((dir) => { // 2. 路径模式保持不变,tinyglobby 能够正确处理 const scanPattern = path.resolve(currentPath, dir, "**", "*.*"); const allFilePath = globSync(scanPattern, { absolute: true, // 3. 移除了 windowsPathsNoEscape,tinyglobby 默认处理路径更智能 }); const validFiles = allFilePath.filter((filePath) => { return isValidFile(filePath, config); }); if (validFiles.length === 0) { console.log( `⚠️ 未找到符合条件的文件,跳过生成 ${config.outputFilenameWithExt}`, ); return; } validFiles.sort(); validFiles.forEach((file) => { const relativePath = path.relative(outputPath, file); const importPath = `./${relativePath.replace(/\\/g, "/")}`; exportStatements.push(`${config.importPrefix} '${importPath}';`); }); }); // --------- scanDirs forEach end ---------------- const indexFileContent = ` ${config.predefineStatements.join("\n")} ${exportStatements.join("\n")} `.trim(); const indexFilePath = path.resolve( currentPath, config.outputDir, config.outputFilenameWithExt, ); // ✅ 内容比对,避免重复写入 if (fs.existsSync(indexFilePath)) { const old = fs.readFileSync(indexFilePath, "utf8"); if (old === indexFileContent) { console.log( `✅ ${config.outputFilenameWithExt} 内容无变化,无需重新生成`, ); return; } } fs.writeFileSync(indexFilePath, indexFileContent, "utf8"); console.log(`✅ 成功生成 ${config.outputFilenameWithExt}: ${indexFilePath}`); } // -------------------------------------------------- try { console.log(`🚀 [gen-index] 开始扫描`); generateIndexFile(cssConfig); generateIndexFile(tsConfig); } catch (err) { const msg = err instanceof Error ? err.message : String(err); console.error(`❌ [gen-index] 执行失败: ${msg}`); process.exit(1); }