index.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import type { CAC } from 'cac';
  2. import { getPackages } from '@vben/node-utils';
  3. import depcheck from 'depcheck';
  4. // 默认配置
  5. const DEFAULT_CONFIG = {
  6. // 需要忽略的依赖匹配
  7. ignoreMatches: [
  8. 'vite',
  9. 'vitest',
  10. 'unbuild',
  11. '@vben/tsconfig',
  12. '@vben/vite-config',
  13. '@vben/tailwind-config',
  14. '@types/*',
  15. '@vben-core/design',
  16. ],
  17. // 需要忽略的包
  18. ignorePackages: [
  19. '@vben/backend-mock',
  20. '@vben/commitlint-config',
  21. '@vben/eslint-config',
  22. '@vben/node-utils',
  23. '@vben/prettier-config',
  24. '@vben/stylelint-config',
  25. '@vben/tailwind-config',
  26. '@vben/tsconfig',
  27. '@vben/vite-config',
  28. '@vben/vsh',
  29. ],
  30. // 需要忽略的文件模式
  31. ignorePatterns: ['dist', 'node_modules', 'public'],
  32. };
  33. interface DepcheckResult {
  34. dependencies: string[];
  35. devDependencies: string[];
  36. missing: Record<string, string[]>;
  37. }
  38. interface DepcheckConfig {
  39. ignoreMatches?: string[];
  40. ignorePackages?: string[];
  41. ignorePatterns?: string[];
  42. }
  43. interface PackageInfo {
  44. dir: string;
  45. packageJson: {
  46. name: string;
  47. };
  48. }
  49. /**
  50. * 清理依赖检查结果
  51. * @param unused - 依赖检查结果
  52. */
  53. function cleanDepcheckResult(unused: DepcheckResult): void {
  54. // 删除file:前缀的依赖提示,该依赖是本地依赖
  55. Reflect.deleteProperty(unused.missing, 'file:');
  56. // 清理路径依赖
  57. Object.keys(unused.missing).forEach((key) => {
  58. unused.missing[key] = (unused.missing[key] || []).filter(
  59. (item: string) => !item.startsWith('/'),
  60. );
  61. if (unused.missing[key].length === 0) {
  62. Reflect.deleteProperty(unused.missing, key);
  63. }
  64. });
  65. }
  66. /**
  67. * 格式化依赖检查结果
  68. * @param pkgName - 包名
  69. * @param unused - 依赖检查结果
  70. */
  71. function formatDepcheckResult(pkgName: string, unused: DepcheckResult): void {
  72. const hasIssues =
  73. Object.keys(unused.missing).length > 0 ||
  74. unused.dependencies.length > 0 ||
  75. unused.devDependencies.length > 0;
  76. if (!hasIssues) {
  77. return;
  78. }
  79. console.log('\n📦 Package:', pkgName);
  80. if (Object.keys(unused.missing).length > 0) {
  81. console.log('❌ Missing dependencies:');
  82. Object.entries(unused.missing).forEach(([dep, files]) => {
  83. console.log(` - ${dep}:`);
  84. files.forEach((file) => console.log(` → ${file}`));
  85. });
  86. }
  87. if (unused.dependencies.length > 0) {
  88. console.log('⚠️ Unused dependencies:');
  89. unused.dependencies.forEach((dep) => console.log(` - ${dep}`));
  90. }
  91. if (unused.devDependencies.length > 0) {
  92. console.log('⚠️ Unused devDependencies:');
  93. unused.devDependencies.forEach((dep) => console.log(` - ${dep}`));
  94. }
  95. }
  96. /**
  97. * 运行依赖检查
  98. * @param config - 配置选项
  99. */
  100. async function runDepcheck(config: DepcheckConfig = {}): Promise<void> {
  101. try {
  102. const finalConfig = {
  103. ...DEFAULT_CONFIG,
  104. ...config,
  105. };
  106. const { packages } = await getPackages();
  107. let hasIssues = false;
  108. await Promise.all(
  109. packages.map(async (pkg: PackageInfo) => {
  110. // 跳过需要忽略的包
  111. if (finalConfig.ignorePackages.includes(pkg.packageJson.name)) {
  112. return;
  113. }
  114. const unused = await depcheck(pkg.dir, {
  115. ignoreMatches: finalConfig.ignoreMatches,
  116. ignorePatterns: finalConfig.ignorePatterns,
  117. });
  118. cleanDepcheckResult(unused);
  119. const pkgHasIssues =
  120. Object.keys(unused.missing).length > 0 ||
  121. unused.dependencies.length > 0 ||
  122. unused.devDependencies.length > 0;
  123. if (pkgHasIssues) {
  124. hasIssues = true;
  125. formatDepcheckResult(pkg.packageJson.name, unused);
  126. }
  127. }),
  128. );
  129. if (!hasIssues) {
  130. console.log('\n✅ Dependency check completed, no issues found');
  131. }
  132. } catch (error) {
  133. console.error(
  134. '❌ Dependency check failed:',
  135. error instanceof Error ? error.message : error,
  136. );
  137. }
  138. }
  139. /**
  140. * 定义依赖检查命令
  141. * @param cac - CAC实例
  142. */
  143. function defineDepcheckCommand(cac: CAC): void {
  144. cac
  145. .command('check-dep')
  146. .option(
  147. '--ignore-packages <packages>',
  148. 'Packages to ignore, comma separated',
  149. )
  150. .option(
  151. '--ignore-matches <matches>',
  152. 'Dependency patterns to ignore, comma separated',
  153. )
  154. .option(
  155. '--ignore-patterns <patterns>',
  156. 'File patterns to ignore, comma separated',
  157. )
  158. .usage('Analyze project dependencies')
  159. .action(async ({ ignoreMatches, ignorePackages, ignorePatterns }) => {
  160. const config: DepcheckConfig = {
  161. ...(ignorePackages && { ignorePackages: ignorePackages.split(',') }),
  162. ...(ignoreMatches && { ignoreMatches: ignoreMatches.split(',') }),
  163. ...(ignorePatterns && { ignorePatterns: ignorePatterns.split(',') }),
  164. };
  165. await runDepcheck(config);
  166. });
  167. }
  168. export { defineDepcheckCommand, type DepcheckConfig };