import type { CAC } from 'cac'; import { getPackages } from '@vben/node-utils'; import depcheck from 'depcheck'; // 默认配置 const DEFAULT_CONFIG = { // 需要忽略的依赖匹配 ignoreMatches: [ 'vite', 'vitest', 'unbuild', '@vben/tsconfig', '@vben/vite-config', '@vben/tailwind-config', '@types/*', '@vben-core/design', ], // 需要忽略的包 ignorePackages: [ '@vben/backend-mock', '@vben/commitlint-config', '@vben/eslint-config', '@vben/lint-staged-config', '@vben/node-utils', '@vben/prettier-config', '@vben/stylelint-config', '@vben/tailwind-config', '@vben/tsconfig', '@vben/vite-config', '@vben/vsh', ], // 需要忽略的文件模式 ignorePatterns: ['dist', 'node_modules', 'public'], }; interface DepcheckResult { dependencies: string[]; devDependencies: string[]; missing: Record; } interface DepcheckConfig { ignoreMatches?: string[]; ignorePackages?: string[]; ignorePatterns?: string[]; } interface PackageInfo { dir: string; packageJson: { name: string; }; } /** * 清理依赖检查结果 * @param unused - 依赖检查结果 */ function cleanDepcheckResult(unused: DepcheckResult): void { // 删除file:前缀的依赖提示,该依赖是本地依赖 Reflect.deleteProperty(unused.missing, 'file:'); // 清理路径依赖 Object.keys(unused.missing).forEach((key) => { unused.missing[key] = (unused.missing[key] || []).filter( (item: string) => !item.startsWith('/'), ); if (unused.missing[key].length === 0) { Reflect.deleteProperty(unused.missing, key); } }); } /** * 格式化依赖检查结果 * @param pkgName - 包名 * @param unused - 依赖检查结果 */ function formatDepcheckResult(pkgName: string, unused: DepcheckResult): void { const hasIssues = Object.keys(unused.missing).length > 0 || unused.dependencies.length > 0 || unused.devDependencies.length > 0; if (!hasIssues) { return; } console.log('\n📦 Package:', pkgName); if (Object.keys(unused.missing).length > 0) { console.log('❌ Missing dependencies:'); Object.entries(unused.missing).forEach(([dep, files]) => { console.log(` - ${dep}:`); files.forEach((file) => console.log(` → ${file}`)); }); } if (unused.dependencies.length > 0) { console.log('⚠️ Unused dependencies:'); unused.dependencies.forEach((dep) => console.log(` - ${dep}`)); } if (unused.devDependencies.length > 0) { console.log('⚠️ Unused devDependencies:'); unused.devDependencies.forEach((dep) => console.log(` - ${dep}`)); } } /** * 运行依赖检查 * @param config - 配置选项 */ async function runDepcheck(config: DepcheckConfig = {}): Promise { try { const finalConfig = { ...DEFAULT_CONFIG, ...config, }; const { packages } = await getPackages(); let hasIssues = false; await Promise.all( packages.map(async (pkg: PackageInfo) => { // 跳过需要忽略的包 if (finalConfig.ignorePackages.includes(pkg.packageJson.name)) { return; } const unused = await depcheck(pkg.dir, { ignoreMatches: finalConfig.ignoreMatches, ignorePatterns: finalConfig.ignorePatterns, }); cleanDepcheckResult(unused); const pkgHasIssues = Object.keys(unused.missing).length > 0 || unused.dependencies.length > 0 || unused.devDependencies.length > 0; if (pkgHasIssues) { hasIssues = true; formatDepcheckResult(pkg.packageJson.name, unused); } }), ); if (!hasIssues) { console.log('\n✅ Dependency check completed, no issues found'); } } catch (error) { console.error( '❌ Dependency check failed:', error instanceof Error ? error.message : error, ); } } /** * 定义依赖检查命令 * @param cac - CAC实例 */ function defineDepcheckCommand(cac: CAC): void { cac .command('check-dep') .option( '--ignore-packages ', 'Packages to ignore, comma separated', ) .option( '--ignore-matches ', 'Dependency patterns to ignore, comma separated', ) .option( '--ignore-patterns ', 'File patterns to ignore, comma separated', ) .usage('Analyze project dependencies') .action(async ({ ignoreMatches, ignorePackages, ignorePatterns }) => { const config: DepcheckConfig = { ...(ignorePackages && { ignorePackages: ignorePackages.split(',') }), ...(ignoreMatches && { ignoreMatches: ignoreMatches.split(',') }), ...(ignorePatterns && { ignorePatterns: ignorePatterns.split(',') }), }; await runDepcheck(config); }); } export { defineDepcheckCommand, type DepcheckConfig };