UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

283 lines (235 loc) 7.36 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/fs/rimraf.js import { Buffer } from "nstdlib/lib/buffer"; import * as fs from "nstdlib/lib/fs"; import { sep } from "nstdlib/lib/path"; import { setTimeout } from "nstdlib/lib/timers"; import { sleep, isWindows } from "nstdlib/lib/internal/util"; // This file is a modified version of the rimraf module on npm. It has been // modified in the following ways: // - Use of the assert module has been replaced with core's error system. // - All code related to the glob dependency has been removed. // - Bring your own custom fs module is not currently supported. // - Some basic code cleanup. const { chmod, chmodSync, lstat, lstatSync, readdir, readdirSync, rmdir, rmdirSync, stat, statSync, unlink, unlinkSync, } = fs; const notEmptyErrorCodes = new Set(["ENOTEMPTY", "EEXIST", "EPERM"]); const retryErrorCodes = new Set([ "EBUSY", "EMFILE", "ENFILE", "ENOTEMPTY", "EPERM", ]); const epermHandler = isWindows ? fixWinEPERM : _rmdir; const epermHandlerSync = isWindows ? fixWinEPERMSync : _rmdirSync; const readdirEncoding = "buffer"; const separator = Buffer.from(sep); function rimraf(path, options, callback) { let retries = 0; _rimraf(path, options, function CB(err) { if (err) { if (retryErrorCodes.has(err.code) && retries < options.maxRetries) { retries++; const delay = retries * options.retryDelay; return setTimeout(_rimraf, delay, path, options, CB); } // The file is already gone. if (err.code === "ENOENT") err = null; } callback(err); }); } function _rimraf(path, options, callback) { // SunOS lets the root user unlink directories. Use lstat here to make sure // it's not a directory. lstat(path, (err, stats) => { if (err) { if (err.code === "ENOENT") return callback(null); // Windows can EPERM on stat. if (isWindows && err.code === "EPERM") return fixWinEPERM(path, options, err, callback); } else if (stats.isDirectory()) { return _rmdir(path, options, err, callback); } unlink(path, (err) => { if (err) { if (err.code === "ENOENT") return callback(null); if (err.code === "EISDIR") return _rmdir(path, options, err, callback); if (err.code === "EPERM") { return epermHandler(path, options, err, callback); } } return callback(err); }); }); } function fixWinEPERM(path, options, originalErr, callback) { chmod(path, 0o666, (err) => { if (err) return callback(err.code === "ENOENT" ? null : originalErr); stat(path, (err, stats) => { if (err) return callback(err.code === "ENOENT" ? null : originalErr); if (stats.isDirectory()) _rmdir(path, options, originalErr, callback); else unlink(path, callback); }); }); } function _rmdir(path, options, originalErr, callback) { rmdir(path, (err) => { if (err) { if (notEmptyErrorCodes.has(err.code)) return _rmchildren(path, options, callback); if (err.code === "ENOTDIR") return callback(originalErr); } callback(err); }); } function _rmchildren(path, options, callback) { const pathBuf = Buffer.from(path); readdir(pathBuf, readdirEncoding, (err, files) => { if (err) return callback(err); let numFiles = files.length; if (numFiles === 0) return rmdir(path, callback); let done = false; Array.prototype.forEach.call(files, (child) => { const childPath = Buffer.concat([pathBuf, separator, child]); rimraf(childPath, options, (err) => { if (done) return; if (err) { done = true; return callback(err); } numFiles--; if (numFiles === 0) rmdir(path, callback); }); }); }); } function rimrafPromises(path, options) { return new Promise((resolve, reject) => { rimraf(path, options, (err) => { if (err) return reject(err); resolve(); }); }); } function rimrafSync(path, options) { let stats; try { stats = lstatSync(path); } catch (err) { if (err.code === "ENOENT") return; // Windows can EPERM on stat. if (isWindows && err.code === "EPERM") fixWinEPERMSync(path, options, err); } try { // SunOS lets the root user unlink directories. if (stats?.isDirectory()) _rmdirSync(path, options, null); else _unlinkSync(path, options); } catch (err) { if (err.code === "ENOENT") return; if (err.code === "EPERM") return epermHandlerSync(path, options, err); if (err.code !== "EISDIR") throw err; _rmdirSync(path, options, err); } } function _unlinkSync(path, options) { const tries = options.maxRetries + 1; for (let i = 1; i <= tries; i++) { try { return unlinkSync(path); } catch (err) { // Only sleep if this is not the last try, and the delay is greater // than zero, and an error was encountered that warrants a retry. if ( retryErrorCodes.has(err.code) && i < tries && options.retryDelay > 0 ) { sleep(i * options.retryDelay); } else if (err.code === "ENOENT") { // The file is already gone. return; } else if (i === tries) { throw err; } } } } function _rmdirSync(path, options, originalErr) { try { rmdirSync(path); } catch (err) { if (err.code === "ENOENT") return; if (err.code === "ENOTDIR") { throw originalErr || err; } if (notEmptyErrorCodes.has(err.code)) { // Removing failed. Try removing all children and then retrying the // original removal. Windows has a habit of not closing handles promptly // when files are deleted, resulting in spurious ENOTEMPTY failures. Work // around that issue by retrying on Windows. const pathBuf = Buffer.from(path); Array.prototype.forEach.call( readdirSync(pathBuf, readdirEncoding), (child) => { const childPath = Buffer.concat([pathBuf, separator, child]); rimrafSync(childPath, options); }, ); const tries = options.maxRetries + 1; for (let i = 1; i <= tries; i++) { try { return fs.rmdirSync(path); } catch (err) { // Only sleep if this is not the last try, and the delay is greater // than zero, and an error was encountered that warrants a retry. if ( retryErrorCodes.has(err.code) && i < tries && options.retryDelay > 0 ) { sleep(i * options.retryDelay); } else if (err.code === "ENOENT") { // The file is already gone. return; } else if (i === tries) { throw err; } } } } throw originalErr || err; } } function fixWinEPERMSync(path, options, originalErr) { try { chmodSync(path, 0o666); } catch (err) { if (err.code === "ENOENT") return; throw originalErr; } let stats; try { stats = statSync(path, { throwIfNoEntry: false }); } catch { throw originalErr; } if (stats === undefined) return; if (stats.isDirectory()) _rmdirSync(path, options, originalErr); else _unlinkSync(path, options); } export { rimraf }; export { rimrafPromises }; export { rimrafSync };