149 lines
3.9 KiB
TypeScript
149 lines
3.9 KiB
TypeScript
// 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.css';
|
||
|
||
${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();
|