rimraf-windows.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. // This is the same as rimrafPosix, with the following changes:
  2. //
  3. // 1. EBUSY, ENFILE, EMFILE trigger retries and/or exponential backoff
  4. // 2. All non-directories are removed first and then all directories are
  5. // removed in a second sweep.
  6. // 3. If we hit ENOTEMPTY in the second sweep, fall back to move-remove on
  7. // the that folder.
  8. //
  9. // Note: "move then remove" is 2-10 times slower, and just as unreliable.
  10. import { parse, resolve } from 'path';
  11. import { fixEPERM, fixEPERMSync } from './fix-eperm.js';
  12. import { lstatSync, promises, rmdirSync, unlinkSync } from './fs.js';
  13. import { ignoreENOENT, ignoreENOENTSync } from './ignore-enoent.js';
  14. import { readdirOrError, readdirOrErrorSync } from './readdir-or-error.js';
  15. import { retryBusy, retryBusySync } from './retry-busy.js';
  16. import { rimrafMoveRemove, rimrafMoveRemoveSync } from './rimraf-move-remove.js';
  17. const { unlink, rmdir, lstat } = promises;
  18. const rimrafWindowsFile = retryBusy(fixEPERM(unlink));
  19. const rimrafWindowsFileSync = retryBusySync(fixEPERMSync(unlinkSync));
  20. const rimrafWindowsDirRetry = retryBusy(fixEPERM(rmdir));
  21. const rimrafWindowsDirRetrySync = retryBusySync(fixEPERMSync(rmdirSync));
  22. const rimrafWindowsDirMoveRemoveFallback = async (path, opt) => {
  23. /* c8 ignore start */
  24. if (opt?.signal?.aborted) {
  25. throw opt.signal.reason;
  26. }
  27. /* c8 ignore stop */
  28. // already filtered, remove from options so we don't call unnecessarily
  29. const { filter, ...options } = opt;
  30. try {
  31. return await rimrafWindowsDirRetry(path, options);
  32. }
  33. catch (er) {
  34. if (er?.code === 'ENOTEMPTY') {
  35. return await rimrafMoveRemove(path, options);
  36. }
  37. throw er;
  38. }
  39. };
  40. const rimrafWindowsDirMoveRemoveFallbackSync = (path, opt) => {
  41. if (opt?.signal?.aborted) {
  42. throw opt.signal.reason;
  43. }
  44. // already filtered, remove from options so we don't call unnecessarily
  45. const { filter, ...options } = opt;
  46. try {
  47. return rimrafWindowsDirRetrySync(path, options);
  48. }
  49. catch (er) {
  50. const fer = er;
  51. if (fer?.code === 'ENOTEMPTY') {
  52. return rimrafMoveRemoveSync(path, options);
  53. }
  54. throw er;
  55. }
  56. };
  57. const START = Symbol('start');
  58. const CHILD = Symbol('child');
  59. const FINISH = Symbol('finish');
  60. export const rimrafWindows = async (path, opt) => {
  61. if (opt?.signal?.aborted) {
  62. throw opt.signal.reason;
  63. }
  64. try {
  65. return await rimrafWindowsDir(path, opt, await lstat(path), START);
  66. }
  67. catch (er) {
  68. if (er?.code === 'ENOENT')
  69. return true;
  70. throw er;
  71. }
  72. };
  73. export const rimrafWindowsSync = (path, opt) => {
  74. if (opt?.signal?.aborted) {
  75. throw opt.signal.reason;
  76. }
  77. try {
  78. return rimrafWindowsDirSync(path, opt, lstatSync(path), START);
  79. }
  80. catch (er) {
  81. if (er?.code === 'ENOENT')
  82. return true;
  83. throw er;
  84. }
  85. };
  86. const rimrafWindowsDir = async (path, opt, ent, state = START) => {
  87. if (opt?.signal?.aborted) {
  88. throw opt.signal.reason;
  89. }
  90. const entries = ent.isDirectory() ? await readdirOrError(path) : null;
  91. if (!Array.isArray(entries)) {
  92. // this can only happen if lstat/readdir lied, or if the dir was
  93. // swapped out with a file at just the right moment.
  94. /* c8 ignore start */
  95. if (entries) {
  96. if (entries.code === 'ENOENT') {
  97. return true;
  98. }
  99. if (entries.code !== 'ENOTDIR') {
  100. throw entries;
  101. }
  102. }
  103. /* c8 ignore stop */
  104. if (opt.filter && !(await opt.filter(path, ent))) {
  105. return false;
  106. }
  107. // is a file
  108. await ignoreENOENT(rimrafWindowsFile(path, opt));
  109. return true;
  110. }
  111. const s = state === START ? CHILD : state;
  112. const removedAll = (await Promise.all(entries.map(ent => rimrafWindowsDir(resolve(path, ent.name), opt, ent, s)))).reduce((a, b) => a && b, true);
  113. if (state === START) {
  114. return rimrafWindowsDir(path, opt, ent, FINISH);
  115. }
  116. else if (state === FINISH) {
  117. if (opt.preserveRoot === false && path === parse(path).root) {
  118. return false;
  119. }
  120. if (!removedAll) {
  121. return false;
  122. }
  123. if (opt.filter && !(await opt.filter(path, ent))) {
  124. return false;
  125. }
  126. await ignoreENOENT(rimrafWindowsDirMoveRemoveFallback(path, opt));
  127. }
  128. return true;
  129. };
  130. const rimrafWindowsDirSync = (path, opt, ent, state = START) => {
  131. const entries = ent.isDirectory() ? readdirOrErrorSync(path) : null;
  132. if (!Array.isArray(entries)) {
  133. // this can only happen if lstat/readdir lied, or if the dir was
  134. // swapped out with a file at just the right moment.
  135. /* c8 ignore start */
  136. if (entries) {
  137. if (entries.code === 'ENOENT') {
  138. return true;
  139. }
  140. if (entries.code !== 'ENOTDIR') {
  141. throw entries;
  142. }
  143. }
  144. /* c8 ignore stop */
  145. if (opt.filter && !opt.filter(path, ent)) {
  146. return false;
  147. }
  148. // is a file
  149. ignoreENOENTSync(() => rimrafWindowsFileSync(path, opt));
  150. return true;
  151. }
  152. let removedAll = true;
  153. for (const ent of entries) {
  154. const s = state === START ? CHILD : state;
  155. const p = resolve(path, ent.name);
  156. removedAll = rimrafWindowsDirSync(p, opt, ent, s) && removedAll;
  157. }
  158. if (state === START) {
  159. return rimrafWindowsDirSync(path, opt, ent, FINISH);
  160. }
  161. else if (state === FINISH) {
  162. if (opt.preserveRoot === false && path === parse(path).root) {
  163. return false;
  164. }
  165. if (!removedAll) {
  166. return false;
  167. }
  168. if (opt.filter && !opt.filter(path, ent)) {
  169. return false;
  170. }
  171. ignoreENOENTSync(() => {
  172. rimrafWindowsDirMoveRemoveFallbackSync(path, opt);
  173. });
  174. }
  175. return true;
  176. };
  177. //# sourceMappingURL=rimraf-windows.js.map