171 lines
4.3 KiB
TypeScript
171 lines
4.3 KiB
TypeScript
|
import type { CAC } from 'cac';
|
|||
|
|
|||
|
import { extname } from 'node:path';
|
|||
|
|
|||
|
import { getStagedFiles } from '@vben/node-utils';
|
|||
|
|
|||
|
import { circularDepsDetect } from 'circular-dependency-scanner';
|
|||
|
|
|||
|
// 默认配置
|
|||
|
const DEFAULT_CONFIG = {
|
|||
|
allowedExtensions: ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx', '.vue'],
|
|||
|
ignoreDirs: [
|
|||
|
'dist',
|
|||
|
'.turbo',
|
|||
|
'output',
|
|||
|
'.cache',
|
|||
|
'scripts',
|
|||
|
'internal',
|
|||
|
'packages/effects/request/src/',
|
|||
|
'packages/@core/ui-kit/menu-ui/src/',
|
|||
|
'packages/@core/ui-kit/popup-ui/src/',
|
|||
|
],
|
|||
|
threshold: 0, // 循环依赖的阈值
|
|||
|
} as const;
|
|||
|
|
|||
|
// 类型定义
|
|||
|
type CircularDependencyResult = string[];
|
|||
|
|
|||
|
interface CheckCircularConfig {
|
|||
|
allowedExtensions?: string[];
|
|||
|
ignoreDirs?: string[];
|
|||
|
threshold?: number;
|
|||
|
}
|
|||
|
|
|||
|
interface CommandOptions {
|
|||
|
config?: CheckCircularConfig;
|
|||
|
staged: boolean;
|
|||
|
verbose: boolean;
|
|||
|
}
|
|||
|
|
|||
|
// 缓存机制
|
|||
|
const cache = new Map<string, CircularDependencyResult[]>();
|
|||
|
|
|||
|
/**
|
|||
|
* 格式化循环依赖的输出
|
|||
|
* @param circles - 循环依赖结果
|
|||
|
*/
|
|||
|
function formatCircles(circles: CircularDependencyResult[]): void {
|
|||
|
if (circles.length === 0) {
|
|||
|
console.log('✅ No circular dependencies found');
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
console.log('⚠️ Circular dependencies found:');
|
|||
|
circles.forEach((circle, index) => {
|
|||
|
console.log(`\nCircular dependency #${index + 1}:`);
|
|||
|
circle.forEach((file) => console.log(` → ${file}`));
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 检查项目中的循环依赖
|
|||
|
* @param options - 检查选项
|
|||
|
* @param options.staged - 是否只检查暂存区文件
|
|||
|
* @param options.verbose - 是否显示详细信息
|
|||
|
* @param options.config - 自定义配置
|
|||
|
* @returns Promise<void>
|
|||
|
*/
|
|||
|
async function checkCircular({
|
|||
|
config = {},
|
|||
|
staged,
|
|||
|
verbose,
|
|||
|
}: CommandOptions): Promise<void> {
|
|||
|
try {
|
|||
|
// 合并配置
|
|||
|
const finalConfig = {
|
|||
|
...DEFAULT_CONFIG,
|
|||
|
...config,
|
|||
|
};
|
|||
|
|
|||
|
// 生成忽略模式
|
|||
|
const ignorePattern = `**/{${finalConfig.ignoreDirs.join(',')}}/**`;
|
|||
|
|
|||
|
// 检查缓存
|
|||
|
const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`;
|
|||
|
if (cache.has(cacheKey)) {
|
|||
|
const cachedResults = cache.get(cacheKey);
|
|||
|
if (cachedResults) {
|
|||
|
verbose && formatCircles(cachedResults);
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 检测循环依赖
|
|||
|
const results = await circularDepsDetect({
|
|||
|
absolute: staged,
|
|||
|
cwd: process.cwd(),
|
|||
|
ignore: [ignorePattern],
|
|||
|
});
|
|||
|
|
|||
|
if (staged) {
|
|||
|
let files = await getStagedFiles();
|
|||
|
const allowedExtensions = new Set(finalConfig.allowedExtensions);
|
|||
|
|
|||
|
// 过滤文件列表
|
|||
|
files = files.filter((file) => allowedExtensions.has(extname(file)));
|
|||
|
|
|||
|
const circularFiles: CircularDependencyResult[] = [];
|
|||
|
|
|||
|
for (const file of files) {
|
|||
|
for (const result of results) {
|
|||
|
const resultFiles = result.flat();
|
|||
|
if (resultFiles.includes(file)) {
|
|||
|
circularFiles.push(result);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 更新缓存
|
|||
|
cache.set(cacheKey, circularFiles);
|
|||
|
verbose && formatCircles(circularFiles);
|
|||
|
} else {
|
|||
|
// 更新缓存
|
|||
|
cache.set(cacheKey, results);
|
|||
|
verbose && formatCircles(results);
|
|||
|
}
|
|||
|
|
|||
|
// 如果发现循环依赖,只输出警告信息
|
|||
|
if (results.length > 0) {
|
|||
|
console.log(
|
|||
|
'\n⚠️ Warning: Circular dependencies found, please check and fix',
|
|||
|
);
|
|||
|
}
|
|||
|
} catch (error) {
|
|||
|
console.error(
|
|||
|
'❌ Error checking circular dependencies:',
|
|||
|
error instanceof Error ? error.message : error,
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 定义检查循环依赖的命令
|
|||
|
* @param cac - CAC实例
|
|||
|
*/
|
|||
|
function defineCheckCircularCommand(cac: CAC): void {
|
|||
|
cac
|
|||
|
.command('check-circular')
|
|||
|
.option('--staged', 'Only check staged files')
|
|||
|
.option('--verbose', 'Show detailed information')
|
|||
|
.option('--threshold <number>', 'Threshold for circular dependencies', {
|
|||
|
default: 0,
|
|||
|
})
|
|||
|
.option('--ignore-dirs <dirs>', 'Directories to ignore, comma separated')
|
|||
|
.usage('Analyze project circular dependencies')
|
|||
|
.action(async ({ ignoreDirs, staged, threshold, verbose }) => {
|
|||
|
const config: CheckCircularConfig = {
|
|||
|
threshold: Number(threshold),
|
|||
|
...(ignoreDirs && { ignoreDirs: ignoreDirs.split(',') }),
|
|||
|
};
|
|||
|
|
|||
|
await checkCircular({
|
|||
|
config,
|
|||
|
staged,
|
|||
|
verbose: verbose ?? true,
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
export { type CheckCircularConfig, defineCheckCircularCommand };
|