clean.mjs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { promises as fs } from 'node:fs';
  2. import { join, normalize } from 'node:path';
  3. const rootDir = process.cwd();
  4. // 控制并发数量,避免创建过多的并发任务
  5. const CONCURRENCY_LIMIT = 10;
  6. // 需要跳过的目录,避免进入这些目录进行清理
  7. const SKIP_DIRS = new Set(['.DS_Store', '.git', '.idea', '.vscode']);
  8. /**
  9. * 处理单个文件/目录项
  10. * @param {string} currentDir - 当前目录路径
  11. * @param {string} item - 文件/目录名
  12. * @param {string[]} targets - 要删除的目标列表
  13. * @param {number} _depth - 当前递归深度
  14. * @returns {Promise<boolean>} - 是否需要进一步递归处理
  15. */
  16. async function processItem(currentDir, item, targets, _depth) {
  17. // 跳过特殊目录
  18. if (SKIP_DIRS.has(item)) {
  19. return false;
  20. }
  21. try {
  22. const itemPath = normalize(join(currentDir, item));
  23. if (targets.includes(item)) {
  24. // 匹配到目标目录或文件时直接删除
  25. await fs.rm(itemPath, { force: true, recursive: true });
  26. console.log(`✅ Deleted: ${itemPath}`);
  27. return false; // 已删除,无需递归
  28. }
  29. // 使用 readdir 的 withFileTypes 选项,避免额外的 lstat 调用
  30. return true; // 可能需要递归,由调用方决定
  31. } catch (error) {
  32. // 更详细的错误信息
  33. if (error.code === 'ENOENT') {
  34. // 文件不存在,可能已被删除,这是正常情况
  35. return false;
  36. } else if (error.code === 'EPERM' || error.code === 'EACCES') {
  37. console.error(`❌ Permission denied: ${item} in ${currentDir}`);
  38. } else {
  39. console.error(
  40. `❌ Error handling item ${item} in ${currentDir}: ${error.message}`,
  41. );
  42. }
  43. return false;
  44. }
  45. }
  46. /**
  47. * 递归查找并删除目标目录(并发优化版本)
  48. * @param {string} currentDir - 当前遍历的目录路径
  49. * @param {string[]} targets - 要删除的目标列表
  50. * @param {number} depth - 当前递归深度,避免过深递归
  51. */
  52. async function cleanTargetsRecursively(currentDir, targets, depth = 0) {
  53. // 限制递归深度,避免无限递归
  54. if (depth > 10) {
  55. console.warn(`Max recursion depth reached at: ${currentDir}`);
  56. return;
  57. }
  58. let dirents;
  59. try {
  60. // 使用 withFileTypes 选项,一次性获取文件类型信息,避免后续 lstat 调用
  61. dirents = await fs.readdir(currentDir, { withFileTypes: true });
  62. } catch (error) {
  63. // 如果无法读取目录,可能已被删除或权限不足
  64. console.warn(`Cannot read directory ${currentDir}: ${error.message}`);
  65. return;
  66. }
  67. // 分批处理,控制并发数量
  68. for (let i = 0; i < dirents.length; i += CONCURRENCY_LIMIT) {
  69. const batch = dirents.slice(i, i + CONCURRENCY_LIMIT);
  70. const tasks = batch.map(async (dirent) => {
  71. const item = dirent.name;
  72. const shouldRecurse = await processItem(currentDir, item, targets, depth);
  73. // 如果是目录且没有被删除,则递归处理
  74. if (shouldRecurse && dirent.isDirectory()) {
  75. const itemPath = normalize(join(currentDir, item));
  76. return cleanTargetsRecursively(itemPath, targets, depth + 1);
  77. }
  78. return null;
  79. });
  80. // 并发执行当前批次的任务
  81. const results = await Promise.allSettled(tasks);
  82. // 检查是否有失败的任务(可选:用于调试)
  83. const failedTasks = results.filter(
  84. (result) => result.status === 'rejected',
  85. );
  86. if (failedTasks.length > 0) {
  87. console.warn(
  88. `${failedTasks.length} tasks failed in batch starting at index ${i} in directory: ${currentDir}`,
  89. );
  90. }
  91. }
  92. }
  93. (async function startCleanup() {
  94. // 要删除的目录及文件名称
  95. const targets = ['node_modules', 'dist', '.turbo', 'dist.zip'];
  96. const deleteLockFile = process.argv.includes('--del-lock');
  97. const cleanupTargets = [...targets];
  98. if (deleteLockFile) {
  99. cleanupTargets.push('pnpm-lock.yaml');
  100. }
  101. console.log(
  102. `🚀 Starting cleanup of targets: ${cleanupTargets.join(', ')} from root: ${rootDir}`,
  103. );
  104. const startTime = Date.now();
  105. try {
  106. // 先统计要删除的目标数量
  107. console.log('📊 Scanning for cleanup targets...');
  108. await cleanTargetsRecursively(rootDir, cleanupTargets);
  109. const endTime = Date.now();
  110. const duration = (endTime - startTime) / 1000;
  111. console.log(
  112. `✨ Cleanup process completed successfully in ${duration.toFixed(2)}s`,
  113. );
  114. } catch (error) {
  115. console.error(`💥 Unexpected error during cleanup: ${error.message}`);
  116. process.exit(1);
  117. }
  118. })();