fs-extender
Version:
Extras suite for node fs module
489 lines • 19 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.rmSync = exports.emptyDirSync = exports.promises = exports.emptyDir = exports.rm = void 0;
const path_extender_1 = __importDefault(require("path-extender"));
const fs = __importStar(require("../patch"));
const util = __importStar(require("../util"));
const mkdirp = __importStar(require("../mkdirp"));
const os_1 = require("os");
const list = __importStar(require("../list"));
/** @hidden */
const IsWindows = /^win/.test((0, os_1.platform)());
/**@internal */
function getOptions(opt) {
return {
noPreserveRoot: util.getObjectOption(opt, "noPreserveRoot", false),
force: util.getObjectOption(opt, "force", false),
maxRetries: util.getObjectOption(opt, "maxRetries", 0),
recursive: util.getObjectOption(opt, "recursive", false),
retryDelay: util.getObjectOption(opt, "retryDelay", 100),
stream: util.getObjectOption(opt, "stream", undefined),
};
}
/**@internal */
function getOptionsEmptyDir(opt) {
return {
force: util.getObjectOption(opt, "force", false),
maxRetries: util.getObjectOption(opt, "maxRetries", 0),
recursive: true,
retryDelay: util.getObjectOption(opt, "retryDelay", 100),
stream: util.getObjectOption(opt, "stream", undefined),
};
}
/**@internal */
function writeToStream(options, path, stats, err) {
if (options.stream) {
const obj = {
type: util.getItemTypeName(util.getItemType(stats)),
item: path,
};
if (err) {
obj.error = err;
}
options.stream.push(JSON.stringify(obj));
}
}
/**@internal */
function isRootPath(path, options) {
const root = path_extender_1.default.parse(path).root;
if (util.equal(path, root) && !options.noPreserveRoot) {
const e = new Error(`To remove '${root}' the 'noPreserveRoot' must be specified as true in options.`);
e.code = "EPERM";
return e;
}
}
function rm(path, options, callback) {
const opt = getOptions(options);
const cb = util.getCallback(options, callback);
path = Buffer.isBuffer(path) ? path : util.fileURLToPath(path);
const isRoot = isRootPath(path, opt);
if (isRoot) {
return cb(isRoot);
}
_rm(path, opt)
.then(() => cb(null))
.catch((err) => cb(err));
}
exports.rm = rm;
/**@internal */
function _rm(path, options, ignoreFirst = false) {
return __awaiter(this, void 0, void 0, function* () {
try {
const stat = yield fs.promises.lstat(path);
/* istanbul ignore next */
if (stat.isDirectory() && !options.recursive) {
const err = new Error(`Path is a directory: rm returned EISDIR (is a directory) '${path}'`);
err.code = "EISDIR";
throw err;
}
if (stat.isFile()) {
writeToStream(options, path, stat);
yield fs.promises.unlink(path);
return;
}
}
catch (errStat) {
if (errStat.code === "ENOENT" && options.force) {
return;
}
throw errStat;
}
const items = yield list.promises.list(path);
if (ignoreFirst) {
items.shift();
}
let stackRetry = [];
if (items.length === 0) {
return;
}
const itemsNotDir = items.filter((i) => !i.stats.isDirectory());
const itemsIsDir = items.filter((i) => i.stats.isDirectory());
while (itemsNotDir.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const item = itemsNotDir.pop();
writeToStream(options, item.path, item.stats);
yield fs.promises.unlink(item.path);
}
/* istanbul ignore next */
while (itemsIsDir.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const item = itemsIsDir.pop();
try {
writeToStream(options, item.path, item.stats);
yield fs.promises.rmdir(item.path);
}
catch (er) {
const err = er;
writeToStream(options, item.path, item.stats, err);
if (err.code === "ENOENT") {
const idx = stackRetry.indexOf(item);
if (idx !== -1) {
stackRetry.splice(idx, 1);
}
continue;
}
if (item.stats.isDirectory()) {
if (err.code === "EPERM" && IsWindows) {
yield fs.promises.chmod(item.path, 0o666);
//re-list for removal
itemsIsDir.push(item);
}
else if (err.code === "ENOTEMPTY" || err.code === "EEXIST" || err.code === "EPERM") {
if (stackRetry.indexOf(item) === -1) {
stackRetry.push(item);
}
}
else {
throw err;
}
continue;
}
throw err;
}
}
stackRetry = stackRetry.reverse();
/* istanbul ignore next: only happens when file is blocked by the os system */
if (stackRetry.length > 0) {
let retries = 0;
function retry(item) {
return __awaiter(this, void 0, void 0, function* () {
try {
writeToStream(options, item.path, item.stats);
yield fs.promises.rmdir(item.path);
}
catch (err) {
writeToStream(options, item.path, item.stats, err);
if (err.code === "ENOENT") {
return;
}
else if (["EBUSY", "EMFILE", "ENFILE", "ENOTEMPTY", "EPERM"].indexOf(err.code) &&
retries < options.maxRetries) {
retries++;
setTimeout(retry, retries * options.retryDelay, item);
}
else if (retries >= options.maxRetries) {
throw err;
}
}
});
}
const ps = Array.from(stackRetry).map((p) => retry(p));
yield Promise.all(ps);
}
if (options.stream /* && !options.stickyStream*/) {
options.stream.push(null);
}
});
}
function emptyDir(path, options, callback) {
const opt = getOptionsEmptyDir(options);
const cb = util.getCallback(options, callback);
_emptyDir(path, opt)
.then(() => cb(null))
.catch((err) => cb(err));
}
exports.emptyDir = emptyDir;
/**@internal */
function _emptyDir(path, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield fs.promises.readdir(path);
}
catch (err) {
/* istanbul ignore next */
if (err.code !== "EEXIST") {
yield mkdirp.promises.mkdirp(path);
return;
}
else {
throw err;
}
}
path = Buffer.isBuffer(path) ? path : util.fileURLToPath(path);
return _rm(path, getOptionsEmptyDir(options), true);
});
}
// eslint-disable-next-line @typescript-eslint/no-namespace
var promises;
(function (promises) {
/**
* Emulate rm -rf command in node
*
* ```js
* import * as fs from "fs-extender";
* await fs.promises.rm(path);
* console.log("item removed with success.");
* ```
*
* @param path - path to remove
* @param options - options
* - `noPreserveRoot` - This options prevents accidentally removing the disc root item, default to `false`
* If `true` will allow to remove all data in the drive, if no error occur
* - `force` - When `true`, exceptions will be ignored if path does not exist. Default: `false`.
* - `maxRetries` - If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or `EPERM` error is encountered, Node.js will retry
* the operation with a linear backoff wait of retryDelay milliseconds longer on each try.
* This option represents the number of retries. This option is ignored if the recursive option is not true.
* Default: 0.
* - `recursive` - If `true`, perform a recursive directory removal.
* In recursive mode, operations are retried on failure. Default: `false`.
* - `retryDelay` - The amount of time in milliseconds to wait between retries.
* This option is ignored if the recursive option is not true. Default: `100`.
* - `stream` - if a stream is passed then it's possible to check the rm process with
* ```js
* stream.on("data",(chunk:string)=>{
* const obj:StreamOutType = JSON.parse(chunk);
* });
* ```
* Note: this doesn't work with `rmSync` or `emptyDirSync`
*
*/
function rm(path, options) {
return __awaiter(this, void 0, void 0, function* () {
const opt = getOptions(options);
path = Buffer.isBuffer(path) ? path : util.fileURLToPath(path);
const isRoot = isRootPath(path, opt);
if (isRoot) {
throw isRoot;
}
return _rm(path, opt);
});
}
promises.rm = rm;
/**
* Delete all items inside a directory
*
* ```js
* import * as fs from "fs-extender";
* await fs.promises.emptyDir(path);
* console.log("dir is empty");
* ```
*
* @param path - path to remove
* @param options - options
* - `force` - When `true`, exceptions will be ignored if path does not exist. Default: `false`.
* - `maxRetries` - If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or `EPERM` error is encountered, Node.js will retry
* the operation with a linear backoff wait of retryDelay milliseconds longer on each try.
* This option represents the number of retries. Default: 0.
* - `retryDelay` - The amount of time in milliseconds to wait between retries. Default: `100`.
* - `stream` - if a stream is passed then it's possible to check the rm process with
* ```js
* stream.on("data",(chunk:string)=>{
* const obj:StreamOutType = JSON.parse(chunk);
* });
* ```
* Note: this doesn't work with `rmSync` or `emptyDirSync`
*/
function emptyDir(path, options) {
return __awaiter(this, void 0, void 0, function* () {
const opt = getOptionsEmptyDir(options);
return _emptyDir(path, opt);
});
}
promises.emptyDir = emptyDir;
})(promises = exports.promises || (exports.promises = {}));
/**
* Delete all items inside a directory
*
* ```js
* import * as fs from "fs-extender";
* fs.emptyDirSync(path);
* console.log("dir is empty");
* ```
*
* @param path - path to remove
* @param options - options
* - `force` - When `true`, exceptions will be ignored if path does not exist. Default: `false`.
* - `maxRetries` - If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or `EPERM` error is encountered, Node.js will retry
* the operation with a linear backoff wait of retryDelay milliseconds longer on each try.
* This option represents the number of retries. Default: 0.
* - `retryDelay` - The amount of time in milliseconds to wait between retries. Default: `100`.
*/
function emptyDirSync(path, options) {
const opt = getOptionsEmptyDir(options);
path = Buffer.isBuffer(path) ? path : util.fileURLToPath(path);
try {
fs.readdirSync(path);
}
catch (err) {
/* istanbul ignore next */
if (err.code !== "EEXIST") {
mkdirp.mkdirpSync(path);
return;
}
else {
throw err;
}
}
_rmSync(path, opt, true);
}
exports.emptyDirSync = emptyDirSync;
/**
* Emulate rm -rf command in node
*
* ```js
* import * as fs from "fs-extender";
* fs.rmSync(path);
* console.log("item removed with success.");
* ```
*
* @param path - path to remove
* @param options - options
* - `noPreserveRoot` - This options prevents accidentally removing the disc root item, default to `false`
* If `true` will allow to remove all data in the drive, if no error occur
* - `force` - When `true`, exceptions will be ignored if path does not exist. Default: `false`.
* - `maxRetries` - If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or `EPERM` error is encountered, Node.js will retry
* the operation with a linear backoff wait of retryDelay milliseconds longer on each try.
* This option represents the number of retries. This option is ignored if the recursive option is not true.
* Default: 0.
* - `recursive` - If `true`, perform a recursive directory removal.
* In recursive mode, operations are retried on failure. Default: `false`.
* - `retryDelay` - The amount of time in milliseconds to wait between retries.
* This option is ignored if the recursive option is not true. Default: `100`.
*/
function rmSync(path, options) {
const opt = getOptions(options);
path = Buffer.isBuffer(path) ? path : util.fileURLToPath(path);
const isRoot = isRootPath(path, opt);
if (isRoot) {
throw isRoot;
}
_rmSync(path, opt);
}
exports.rmSync = rmSync;
/**@internal */
function _rmSync(path, options, ignoreFirst = false) {
try {
const stat = fs.lstatSync(path);
/* istanbul ignore next */
if (stat.isDirectory() && !options.recursive) {
const err = new Error(`Path is a directory: rm returned EISDIR (is a directory) '${path}'`);
err.code = "EISDIR";
throw err;
}
/* istanbul ignore next */
if (stat.isFile()) {
fs.unlinkSync(path);
return;
}
}
catch (errStat) {
if (errStat.code === "ENOENT" && options.force) {
return;
}
throw errStat;
}
const items = list.listSync(path);
if (ignoreFirst) {
items.shift();
}
let stackRetry = [];
if (items.length === 0) {
return;
}
const itemsNotDir = items.filter((i) => !i.stats.isDirectory());
const itemsIsDir = items.filter((i) => i.stats.isDirectory());
while (itemsNotDir.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const item = itemsNotDir.pop();
fs.unlinkSync(item.path);
}
/* istanbul ignore next */
while (itemsIsDir.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const item = itemsIsDir.pop();
try {
fs.rmdirSync(item.path);
}
catch (er) {
const err = er;
if (err.code === "ENOENT") {
const idx = stackRetry.indexOf(item);
if (idx !== -1) {
stackRetry.splice(idx, 1);
}
continue;
}
if (item.stats.isDirectory()) {
if (err.code === "EPERM" && IsWindows) {
fs.chmodSync(item.path, 0o666);
//re-list for removal
itemsIsDir.push(item);
}
else if (err.code === "ENOTEMPTY" || err.code === "EEXIST" || err.code === "EPERM") {
if (stackRetry.indexOf(item) === -1) {
stackRetry.push(item);
}
}
else {
throw err;
}
continue;
}
throw err;
}
}
stackRetry = stackRetry.reverse();
/* istanbul ignore next: only happens when file is blocked by the os system */
while (stackRetry.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const p = stackRetry.pop();
//if we enter here we have sticky bastards
const tries = options.maxRetries + 1;
for (let i = 1; i <= tries; i++) {
try {
fs.rmdirSync(p.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 (["EBUSY", "EMFILE", "ENFILE", "ENOTEMPTY", "EPERM"].indexOf(err.code) &&
i < tries &&
options.retryDelay > 0) {
//poor sleeping
const stop = Date.now() + i * options.retryDelay;
while (stop < Date.now()) { }
//sleep(i * options.retryDelay);
}
else if (err.code === "ENOENT") {
// The file is already gone.
continue;
}
else if (i === tries) {
throw err;
}
}
}
}
}
//# sourceMappingURL=index.js.map
;