Files
defgov/packages/ui-react/scripts/gen-index.ts
2026-06-08 13:04:13 +08:00

255 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as fs from "fs";
import * as path from "path";
/**
* 默认测试 / story / example / fixture / type 文件匹配规则
* 仅用于“识别”,不直接决定是否排除
*/
const DEFAULT_TEST_FILE_PATTERNS: RegExp[] = [
// =========================
// Test / Spec
// =========================
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
/\.(test|spec)\.(ts|tsx|js|jsx)\?.*$/, // query param safe
// =========================
// E2E / Playwright / Cypress
// =========================
/\.(cy|playwright|e2e)\.(ts|tsx|js|jsx)$/,
// =========================
// Storybook
// =========================
/\.(story|stories)\.(ts|tsx|js|jsx|mdx)$/,
// =========================
// Snapshot / Mock / Fixture
// =========================
/\.snap$/,
/\.mock\.(ts|tsx|js|jsx)$/,
/\.fixture\.(ts|tsx|js|jsx)$/,
// =========================
// Type declarations
// =========================
/\.d\.ts$/,
// =========================
// Examples / Demos / Docs
// =========================
/\.(example|demo|docs)\.(ts|tsx|js|jsx)$/,
];
/**
* 测试 / 辅助目录(目录级排除)
*/
const DEFAULT_TEST_DIR_PATTERNS: RegExp[] = [
/[/\\](__tests__|tests|test|spec|__mocks__|__fixtures__|__snapshots__)[\/\\]/,
/[/\\](cypress|playwright|e2e|__e2e__|__playwright__)[\/\\]/,
/[/\\](story|stories|\.storybook)[\/\\]/,
/[/\\](types|type|typings|interfaces)[\/\\]/,
/[/\\](examples|example|demo|demos|docs)[\/\\]/,
];
type Config = {
outputDir: string;
outputFile: string;
scanDirs: string[];
preamble?: string[];
importPrefix?: string;
entryFilePatterns?: RegExp[];
barrelFirstMode?: boolean;
includeFilePatterns?: RegExp[];
excludeFilePatterns?: RegExp[];
excludeDirPatterns?: RegExp[];
};
const tsConfig: Config = {
outputDir: "src",
outputFile: "index.ts",
scanDirs: ["src"],
importPrefix: "export * from",
barrelFirstMode: true,
preamble: ["import './index.css';"],
entryFilePatterns: [/^index\.(js|ts|jsx|tsx)$/],
includeFilePatterns: [/\.(js|ts|jsx|tsx)$/],
excludeFilePatterns: DEFAULT_TEST_FILE_PATTERNS,
excludeDirPatterns: DEFAULT_TEST_DIR_PATTERNS,
};
const cssConfig: Config = {
outputDir: "src",
outputFile: "index.css",
scanDirs: ["src"],
importPrefix: "@import",
barrelFirstMode: false,
entryFilePatterns: [/^index\.(css)$/],
includeFilePatterns: [/\.(css)$/],
excludeFilePatterns: DEFAULT_TEST_FILE_PATTERNS,
excludeDirPatterns: DEFAULT_TEST_DIR_PATTERNS,
};
function isEntryFile(fileName: string, config: Config): boolean {
const regExps = config.entryFilePatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(fileName));
}
function isIncludeFile(fileName: string, config: Config): boolean {
const regExps = config.includeFilePatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(fileName));
}
function isExcludeFile(fileName: string, config: Config): boolean {
const regExps = config.excludeFilePatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(fileName));
}
function isExcludeDir(filePath: string, config: Config): boolean {
const regExps = config.excludeDirPatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(filePath));
}
function isValidDir(filePath: string, config: Config): boolean {
if (isExcludeDir(filePath, config)) {
return false;
}
return true;
}
function isValidFile(fileName: string, config: Config): boolean {
if (isIncludeFile(fileName, config) && !isExcludeFile(fileName, config)) {
return true;
}
return false;
}
const buildExportStatement = (filePath: string, config: Config) => {
// 文件夹(不是文件)与目标文件的相对路径
let exportFilePath = path.relative(config.outputDir, filePath);
// 去除扩展名,并且转化成正斜杠
const exportFilePathWithoutExt = path
.join(
path.dirname(exportFilePath),
path.basename(exportFilePath, path.extname(exportFilePath)),
)
.replace(/\\/g, "/");
// 加上 ./ 前缀
return `${config.importPrefix} './${exportFilePathWithoutExt}';`;
};
function isOutputEntry(filePath: string, config: Config) {
const outputFilePath = path.resolve(config.outputDir, config.outputFile);
const targetFilePath = path.resolve(filePath);
return outputFilePath === targetFilePath;
}
function generateExports(dirPath: string, config: Config): string[] {
const fileNames = fs.readdirSync(dirPath);
const exports: string[] = [];
// =========================
// 逻辑分支Barrel First Mode
// =========================
if (config.barrelFirstMode) {
// 查找当前目录有没有 index.ts
const entryFileName = fileNames.find((fileName) => {
const filePath = path.join(dirPath, fileName);
return fs.statSync(filePath).isFile() && isEntryFile(fileName, config);
});
// 如果有
if (entryFileName) {
const entryFilePath = path.join(dirPath, entryFileName);
// 如果是 outputFile 自身,则跳过
if (isOutputEntry(entryFilePath, config)) {
console.log(`⏭️ 跳过自身入口文件: ${entryFilePath}`);
} else {
exports.push(buildExportStatement(entryFilePath, config));
}
}
}
// =========================
// 逻辑分支index.ts 不存在,则继续正常遍历与递归
// =========================
fileNames.forEach((fileName) => {
const filePath = path.join(dirPath, fileName);
const stat = fs.statSync(filePath);
// 情况1是文件且通过了校验
if (
stat.isFile() &&
isValidFile(fileName, config) &&
!isEntryFile(fileName, config)
) {
exports.push(buildExportStatement(filePath, config));
}
// 情况2是文件夹且通过了校验递归扫描子文件夹
else if (stat.isDirectory() && isValidDir(filePath, config)) {
const subExports = generateExports(filePath, config);
exports.push(...subExports);
}
});
return exports;
}
async function genIndexFile(config: Config) {
const allExports: string[] = [];
// 遍历所有需要扫描的根目录
config.scanDirs.forEach((scanDir) => {
// 只有当目录真实存在时才进行扫描
if (fs.existsSync(scanDir)) {
const exports = generateExports(scanDir, config);
allExports.push(...exports);
} else {
console.warn(`⚠️ 警告:扫描目录不存在,已跳过 -> ${scanDir}`);
}
});
// 拼接最终的文件内容:前言 + 导出语句(使用 Set 自动去重)
const fileContent = [
...(config.preamble ?? []),
...Array.from(new Set(allExports)),
].join("\n");
// 写入之前,确保输出目录存在,如果不存在就递归创建
if (!fs.existsSync(config.outputDir)) {
fs.mkdirSync(config.outputDir, { recursive: true });
}
// 将内容写入到最终的 index.ts 文件中
const outputFilePath = path.join(config.outputDir, config.outputFile);
fs.writeFileSync(outputFilePath, fileContent, "utf-8");
console.log(`✨ 成功生成入口文件: ${outputFilePath}`);
}
// ================= 脚本执行入口 =================
try {
console.log("🚀 开始扫描并生成入口文件...");
await genIndexFile(cssConfig);
await genIndexFile(tsConfig);
console.log("✅ 脚本执行完毕!");
} catch (error) {
console.error("❌ 脚本执行失败:", error);
process.exit(1); // 如果报错,让进程以非 0 状态码退出
}