index.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import type { CAC } from 'cac';
  2. import { extname } from 'node:path';
  3. import { getStagedFiles } from '@vben/node-utils';
  4. import { circularDepsDetect } from 'circular-dependency-scanner';
  5. // 默认配置
  6. const DEFAULT_CONFIG = {
  7. allowedExtensions: ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx', '.vue'],
  8. ignoreDirs: [
  9. 'dist',
  10. '.turbo',
  11. 'output',
  12. '.cache',
  13. 'scripts',
  14. 'internal',
  15. 'packages/effects/request/src/',
  16. 'packages/@core/ui-kit/menu-ui/src/',
  17. 'packages/@core/ui-kit/popup-ui/src/',
  18. ],
  19. threshold: 0, // 循环依赖的阈值
  20. } as const;
  21. // 类型定义
  22. type CircularDependencyResult = string[];
  23. interface CheckCircularConfig {
  24. allowedExtensions?: string[];
  25. ignoreDirs?: string[];
  26. threshold?: number;
  27. }
  28. interface CommandOptions {
  29. config?: CheckCircularConfig;
  30. staged: boolean;
  31. verbose: boolean;
  32. }
  33. // 缓存机制
  34. const cache = new Map<string, CircularDependencyResult[]>();
  35. /**
  36. * 格式化循环依赖的输出
  37. * @param circles - 循环依赖结果
  38. */
  39. function formatCircles(circles: CircularDependencyResult[]): void {
  40. if (circles.length === 0) {
  41. console.log('✅ No circular dependencies found');
  42. return;
  43. }
  44. console.log('⚠️ Circular dependencies found:');
  45. circles.forEach((circle, index) => {
  46. console.log(`\nCircular dependency #${index + 1}:`);
  47. circle.forEach((file) => console.log(` → ${file}`));
  48. });
  49. }
  50. /**
  51. * 检查项目中的循环依赖
  52. * @param options - 检查选项
  53. * @param options.staged - 是否只检查暂存区文件
  54. * @param options.verbose - 是否显示详细信息
  55. * @param options.config - 自定义配置
  56. * @returns Promise<void>
  57. */
  58. async function checkCircular({
  59. config = {},
  60. staged,
  61. verbose,
  62. }: CommandOptions): Promise<void> {
  63. try {
  64. // 合并配置
  65. const finalConfig = {
  66. ...DEFAULT_CONFIG,
  67. ...config,
  68. };
  69. // 生成忽略模式
  70. const ignorePattern = `**/{${finalConfig.ignoreDirs.join(',')}}/**`;
  71. // 检查缓存
  72. const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`;
  73. if (cache.has(cacheKey)) {
  74. const cachedResults = cache.get(cacheKey);
  75. if (cachedResults) {
  76. verbose && formatCircles(cachedResults);
  77. }
  78. return;
  79. }
  80. // 检测循环依赖
  81. const results = await circularDepsDetect({
  82. absolute: staged,
  83. cwd: process.cwd(),
  84. ignore: [ignorePattern],
  85. });
  86. if (staged) {
  87. let files = await getStagedFiles();
  88. const allowedExtensions = new Set(finalConfig.allowedExtensions);
  89. // 过滤文件列表
  90. files = files.filter((file) => allowedExtensions.has(extname(file)));
  91. const circularFiles: CircularDependencyResult[] = [];
  92. for (const file of files) {
  93. for (const result of results) {
  94. const resultFiles = result.flat();
  95. if (resultFiles.includes(file)) {
  96. circularFiles.push(result);
  97. }
  98. }
  99. }
  100. // 更新缓存
  101. cache.set(cacheKey, circularFiles);
  102. verbose && formatCircles(circularFiles);
  103. } else {
  104. // 更新缓存
  105. cache.set(cacheKey, results);
  106. verbose && formatCircles(results);
  107. }
  108. // 如果发现循环依赖,只输出警告信息
  109. if (results.length > 0) {
  110. console.log(
  111. '\n⚠️ Warning: Circular dependencies found, please check and fix',
  112. );
  113. }
  114. } catch (error) {
  115. console.error(
  116. '❌ Error checking circular dependencies:',
  117. error instanceof Error ? error.message : error,
  118. );
  119. }
  120. }
  121. /**
  122. * 定义检查循环依赖的命令
  123. * @param cac - CAC实例
  124. */
  125. function defineCheckCircularCommand(cac: CAC): void {
  126. cac
  127. .command('check-circular')
  128. .option('--staged', 'Only check staged files')
  129. .option('--verbose', 'Show detailed information')
  130. .option('--threshold <number>', 'Threshold for circular dependencies', {
  131. default: 0,
  132. })
  133. .option('--ignore-dirs <dirs>', 'Directories to ignore, comma separated')
  134. .usage('Analyze project circular dependencies')
  135. .action(async ({ ignoreDirs, staged, threshold, verbose }) => {
  136. const config: CheckCircularConfig = {
  137. threshold: Number(threshold),
  138. ...(ignoreDirs && { ignoreDirs: ignoreDirs.split(',') }),
  139. };
  140. await checkCircular({
  141. config,
  142. staged,
  143. verbose: verbose ?? true,
  144. });
  145. });
  146. }
  147. export { type CheckCircularConfig, defineCheckCircularCommand };