fs-extender
Version:
Extras suite for node fs module
1,355 lines (1,354 loc) • 53.2 kB
JavaScript
"use strict";
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.existAccessSync = exports.existAccess = exports.exists = exports.promises = exports.createWriteStream = exports.createReadStream = exports.WriteStream = exports.ReadStream = exports.writeFile = exports.unlinkSync = exports.unlink = exports.symlinkSync = exports.symlink = exports.lstatSync = exports.fstatSync = exports.statSync = exports.lstat = exports.fstat = exports.stat = exports.rmdirSync = exports.rmdir = exports.renameSync = exports.rename = exports.readFile = exports.readDirSync = exports.readdirSync = exports.readDir = exports.readdir = exports.readSync = exports.read = exports.open = exports.lutimesSync = exports.lutimes = exports.copyFile = exports.lchmodSync = exports.fchmodSync = exports.chmodSync = exports.lchownSync = exports.fchownSync = exports.chownSync = exports.lchmod = exports.fchmod = exports.chmod = exports.lchown = exports.fchown = exports.chown = exports.appendFile = exports.getQueue = exports.fsExtenderQueueSymbol = exports.originalFn = void 0;
exports.isEmptySync = exports.isEmpty = exports.statIsSymbolicLinkSync = exports.statIsSymbolicLink = exports.statIsFileSync = exports.statIsFile = exports.statIsDirectorySync = exports.statIsDirectory = void 0;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const NodeFsAux = require("fs");
const fs = process.env["FS_EXTENDER_FS_OVERRIDE"]
? // eslint-disable-next-line @typescript-eslint/no-var-requires
require(process.env["FS_EXTENDER_FS_OVERRIDE"])
: NodeFsAux;
if (process.env["FS_EXTENDER_FS_OVERRIDE"]) {
/* istanbul ignore next */
for (const key of Object.keys(fs)) {
module.exports[key] = fs[key];
}
}
const utils_1 = require("@n3okill/utils");
const NodeOs = __importStar(require("os"));
const _rm = __importStar(require("../rm/index"));
const util_1 = require("../util");
const path_extender_1 = __importDefault(require("path-extender"));
/**@internal */
const IsWindows = /^win/.test(NodeOs.platform());
__exportStar(require("fs"), exports);
exports.originalFn = Symbol.for("fsExtender.original.fn");
exports.fsExtenderQueueSymbol = Symbol.for("fsExtender.Queue");
const MaxTimeout = process.env["FS_EXTENDER_TIMEOUT"] ? parseInt(process.env["FS_EXTENDER_TIMEOUT"]) : 60000;
const MaxTimeoutSync = process.env["FS_EXTENDER_TIMEOUT_SYNC"]
? parseInt(process.env["FS_EXTENDER_TIMEOUT_SYNC"])
: 60000;
const RenameMaxTime = process.env["FS_EXTENDER_WIN32_TIMEOUT"]
? parseInt(process.env["FS_EXTENDER_WIN32_TIMEOUT"], 10)
: MaxTimeout;
const RenameMaxTimeoutSync = process.env["FS_EXTENDER_WIN32_TIMEOUT_SYNC"]
? parseInt(process.env["FS_EXTENDER_WIN32_TIMEOUT_SYNC"], 10)
: MaxTimeoutSync;
const IgnorePatch = process.env["FS_EXTENDER_IGNORE_PATCH"]
? (0, util_1.parseBoolean)(process.env["FS_EXTENDER_IGNORE_PATCH"])
: false;
const platform = process.env["FS_EXTENDER_TEST_PLATFORM"] || NodeOs.platform();
const IgnorePathClose = process.env["FS_EXTENDER_IGNORE_PATCH_CLOSE"]
? (0, util_1.parseBoolean)(process.env["FS_EXTENDER_IGNORE_PATCH_CLOSE"])
: IgnorePatch;
/**@internal */
let cwd = null;
/**@internal */
process.cwd = ((cwdFn) => {
return () => {
if (!cwd) {
cwd = cwdFn.call(process);
}
return cwd;
};
})(process.cwd);
try {
process.cwd();
}
catch (er) {
//Intentionally left blank
}
if (utils_1.Type.isFunction(process.chdir)) {
const chdir = process.chdir;
process.chdir = function (dir) {
cwd = null;
chdir.call(process, dir);
};
if (Object.setPrototypeOf) {
Object.setPrototypeOf(process.chdir, chdir);
}
}
/**@internal */
function publishQueue(context, queue) {
Object.defineProperty(context, exports.fsExtenderQueueSymbol, {
get: function () {
return queue;
},
});
}
/**@internal */
function getQueue() {
/* istanbul ignore next */
return fs[exports.fsExtenderQueueSymbol];
}
exports.getQueue = getQueue;
if (utils_1.Type.isUndefined(fs[exports.fsExtenderQueueSymbol])) {
if (!IgnorePatch) {
const queue = global[exports.fsExtenderQueueSymbol] || [];
publishQueue(fs, queue);
patchClose(fs);
}
}
/**@internal */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function patchClose(_fs) {
if (IgnorePathClose) {
return;
}
// Patch fs.close/closeSync to shared queue version, because we need
// to retry() whenever a close happens *anywhere* in the program.
// This is essential when multiple fs-extender instances are
// in play at the same time.
_fs.close = (function (close) {
function closeFsExtender(fd, cb) {
return Reflect.apply(close, _fs, [
fd,
(err) => {
if (!err) {
processQueue();
}
Reflect.apply(cb, _fs, [err]);
},
]);
}
return closeFsExtender;
})(_fs.close);
_fs.closeSync = (function (close) {
function closeSyncFsExtender(fd) {
Reflect.apply(close, _fs, [fd]);
processQueue();
}
return closeSyncFsExtender;
})(_fs.closeSync);
}
if (utils_1.Type.isUndefined(global[exports.fsExtenderQueueSymbol])) {
if (!IgnorePatch) {
publishQueue(global, fs[exports.fsExtenderQueueSymbol]);
}
}
/**@internal */
/* istanbul ignore next */
function addQueue(elem) {
fs[exports.fsExtenderQueueSymbol].push(elem);
retry();
}
// reset the startTime and lastTime to now
// this resets the start of the 60 second overall timeout as well as the
// delay between attempts so that we'll retry these jobs sooner
/**@internal */
/* istanbul ignore next */
function processQueue() {
const now = Date.now();
const queue = fs[exports.fsExtenderQueueSymbol];
if (utils_1.Type.isArray(queue)) {
for (let i = 0; i < queue.length; ++i) {
queue[i][3] = now; //startTime
queue[i][4] = now; //stopTime
}
// call retry to make sure we're actively processing the queue
retry();
}
}
// keep track of the timeout between retry() calls
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let retryTimer;
/**@internal */
/* istanbul ignore next */
function retry() {
clearImmediate(retryTimer);
retryTimer = undefined;
if (fs[exports.fsExtenderQueueSymbol].length === 0) {
return;
}
const elem = fs[exports.fsExtenderQueueSymbol].shift();
const fn = elem[0];
const args = elem[1];
const err = elem[2];
const startTime = elem[3];
const lastTime = elem[4];
// if we don't have a startTime we have no way of knowing if we've waited
// long enough, so go ahead and retry this item now
if (utils_1.Type.isUndefined(startTime)) {
Reflect.apply(fn, null, args);
}
else if (Date.now() - startTime >= 60000) {
// it's been more than 60 seconds total, bail now
const cb = args.pop();
if (utils_1.Type.isFunction(cb)) {
Reflect.apply(cb, null, [err]);
}
}
else {
// the amount of time between the last attempt and right now
const sinceAttempt = Date.now() - lastTime;
// the amount of time between when we first tried, and when we last tried
// rounded up to at least 1
const sinceStart = Math.max(lastTime - startTime, 1);
// backoff. wait longer than the total time we've been retrying, but only
// up to a maximum of 100ms
const desiredDelay = Math.min(sinceStart * 1.2, 100);
// it's been long enough since the last retry, do it again
if (sinceAttempt >= desiredDelay) {
Reflect.apply(fn, null, [...args, startTime]);
}
else {
// if we can't do this job yet, push it to the end of the queue
// and let the next iteration check again
fs[exports.fsExtenderQueueSymbol].push(elem);
}
}
if (utils_1.Type.isUndefined(retryTimer)) {
retryTimer = setImmediate(retry);
}
}
/**@internal */
function errorEnqueueOrCallback(fn, args, argsResult, callback, startTime) {
const err = argsResult[0];
if (err && (err.code === "EMFILE" || err.code === "ENFILE")) {
/* istanbul ignore next */
addQueue([fn, args, err, startTime || Date.now(), Date.now()]);
}
else {
Reflect.apply(callback, this, argsResult);
}
}
function appendFile(path, data, options, callback) {
/* istanbul ignore next */
if (!IgnorePatch) {
if (utils_1.Type.isFunction(options)) {
callback = options;
options = null;
}
function fsAppendFile(path, data, options, callback, startTime) {
return fs.appendFile(path, data, options, (...args) => {
errorEnqueueOrCallback(fsAppendFile, [path, data, options, callback], args, callback, startTime);
});
}
return fsAppendFile(path, data, options, callback);
}
else {
Reflect.apply(fs.appendFile, fs, [path, data, options, callback]);
}
}
exports.appendFile = appendFile;
// Chown should not fail on einval or eperm if non-root.
// It should not fail on enosys ever, as this just indicates
// that a fs doesn't support the intended operation.
// ENOSYS means that the fs doesn't support the op. Just ignore
// that, because it doesn't matter.
//
// if there's no getuid, or if getuid() is something other
// than 0, and the error is EINVAL or EPERM, then just ignore
// it.
//
// This specific case is a silent failure in cp, install, tar,
// and most other unix tools that manage permissions.
//
// When running as root, or if other types of errors are
// encountered, then it's strict.
/* istanbul ignore next */
function chownErOk(err) {
if (!err) {
return true;
}
if (err.code === "ENOSYS") {
return true;
}
const nonroot = !process.getuid || process.getuid() !== 0;
if (nonroot) {
if (err.code === "EINVAL" || err.code === "EPERM")
return true;
}
return false;
}
/** @internal*/
/* istanbul ignore next */
function chownFix(orig, path, uid, gid, callback) {
if (!IgnorePatch) {
return Reflect.apply(orig, fs, [
path,
uid,
gid,
(err) => callback(chownErOk(err) ? null : err),
]);
}
else {
Reflect.apply(orig, fs, [path, uid, gid, callback]);
}
}
/** @internal*/
/* istanbul ignore next */
function chmodFix(orig, path, mode, callback) {
if (!IgnorePatch) {
return Reflect.apply(orig, fs, [
path,
mode,
(err) => callback(chownErOk(err) ? null : err),
]);
}
else {
return Reflect.apply(orig, fs, [path, mode, callback]);
}
}
/** @internal*/
/* istanbul ignore next */
function chownFixSync(orig, path, uid, gid) {
if (!IgnorePatch) {
try {
return Reflect.apply(orig, fs, [path, uid, gid]);
}
catch (err) {
if (!chownErOk(err)) {
throw err;
}
}
}
else {
return Reflect.apply(orig, fs, [path, uid, gid]);
}
}
/** @internal*/
/* istanbul ignore next */
function chmodFixSync(orig, path, mode) {
if (!IgnorePatch) {
try {
return Reflect.apply(orig, fs, [path, mode]);
}
catch (err) {
if (!chownErOk(err)) {
throw err;
}
}
}
else {
return Reflect.apply(orig, fs, [path, mode]);
}
}
/**
* Asynchronously changes owner and group of a file. No arguments other than a
* possible exception are given to the completion callback.
*
* See the POSIX [`chown(2)`](http://man7.org/linux/man-pages/man2/chown.2.html) documentation for more detail.
*/
function chown(path, uid, gid, callback) {
/* istanbul ignore next */
return chownFix(fs.chown, path, uid, gid, callback);
}
exports.chown = chown;
/**
* Sets the owner of the file. No arguments other than a possible exception are
* given to the completion callback.
*
* See the POSIX [`fchown(2)`](http://man7.org/linux/man-pages/man2/fchown.2.html) documentation for more detail.
*/
function fchown(fd, uid, gid, callback) {
/* istanbul ignore next */
return chownFix(fs.fchown, fd, uid, gid, callback);
}
exports.fchown = fchown;
/**
* Set the owner of the symbolic link. No arguments other than a possible
* exception are given to the completion callback.
*
* See the POSIX [`lchown(2)`](http://man7.org/linux/man-pages/man2/lchown.2.html) documentation for more detail.
*/
function lchown(path, uid, gid, callback) {
/* istanbul ignore next */
if (!fs.lchown) {
process.nextTick(callback);
return;
}
/* istanbul ignore next */
return chownFix(fs.lchown, path, uid, gid, callback);
}
exports.lchown = lchown;
/**
* Asynchronously changes the permissions of a file. No arguments other than a
* possible exception are given to the completion callback.
*
* See the POSIX [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html) documentation for more detail.
*
* ```js
* import { chmod } from 'fs-extender';
*
* chmod('my_file.txt', 0o775, (err) => {
* if (err) throw err;
* console.log('The permissions for file "my_file.txt" have been changed!');
* });
* ```
*/
function chmod(path, mode, callback) {
return chmodFix(fs.chmod, path, mode, callback);
}
exports.chmod = chmod;
/**
* Sets the permissions on the file. No arguments other than a possible exception
* are given to the completion callback.
*
* See the POSIX [`fchmod(2)`](http://man7.org/linux/man-pages/man2/fchmod.2.html) documentation for more detail.
*/
function fchmod(fd, mode, callback) {
/* istanbul ignore next */
return chmodFix(fs.fchmod, fd, mode, callback);
}
exports.fchmod = fchmod;
/**
* Changes the permissions on a symbolic link. No arguments other than a possible
* exception are given to the completion callback.
*
* This method is only implemented on macOS.
*
* See the POSIX [`lchmod(2)`](https://www.freebsd.org/cgi/man.cgi?query=lchmod&sektion=2) documentation for more detail.
*/
function lchmod(path, mode, callback) {
/* istanbul ignore next */
if (!IgnorePatch) {
let lchmod = fs.lchmod;
// lchmod, broken prior to 0.6.2
// back-port the fix here.
if (fs.constants.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
lchmod = function (path, mode, callback) {
open(path, fs.constants.O_WRONLY | fs.constants.O_SYMLINK, mode, (err, fd) => {
if (err) {
if (callback) {
callback(err);
}
return;
}
// prefer to return the chmod error, if one occurs,
// but still try to close, and report closing errors if they occur.
fchmod(fd, mode, (err) => {
fs.close(fd, function (err2) {
if (callback) {
callback(err || err2);
}
});
});
});
};
}
if (!lchmod) {
process.nextTick(callback);
return;
}
return chmodFix(lchmod, path, mode, callback);
}
else {
return chmodFix(fs.lchmod, path, mode, callback);
}
}
exports.lchmod = lchmod;
/**
* Synchronously changes owner and group of a file. Returns `undefined`.
* This is the synchronous version of {@link chown}.
*
* See the POSIX [`chown(2)`](http://man7.org/linux/man-pages/man2/chown.2.html) documentation for more detail.
*/
function chownSync(path, uid, gid) {
Reflect.apply(chownFixSync, fs, [fs.chownSync, path, uid, gid]);
}
exports.chownSync = chownSync;
/**
* Sets the owner of the file. Returns `undefined`.
*
* See the POSIX [`fchown(2)`](http://man7.org/linux/man-pages/man2/fchown.2.html) documentation for more detail.
* @param uid The file's new owner's user id.
* @param gid The file's new group's group id.
*/
function fchownSync(fd, uid, gid) {
/* istanbul ignore next */
Reflect.apply(chownFixSync, fs, [fs.fchownSync, fd, uid, gid]);
}
exports.fchownSync = fchownSync;
/**
* Set the owner for the path. Returns `undefined`.
*
* See the POSIX [`lchown(2)`](http://man7.org/linux/man-pages/man2/lchown.2.html) documentation for more details.
* @param uid The file's new owner's user id.
* @param gid The file's new group's group id.
*/
function lchownSync(path, uid, gid) {
/* istanbul ignore next */
if (!fs.lchownSync) {
return;
}
/* istanbul ignore next */
Reflect.apply(chownFixSync, fs, [fs.lchownSync, path, uid, gid]);
}
exports.lchownSync = lchownSync;
/**
* For detailed information, see the documentation of the asynchronous version of
* this API: {@link chmod}.
*
* See the POSIX [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html) documentation for more detail.
*/
function chmodSync(path, mode) {
Reflect.apply(chmodFixSync, fs, [fs.chmodSync, path, mode]);
}
exports.chmodSync = chmodSync;
/**
* Sets the permissions on the file. Returns `undefined`.
*
* See the POSIX [`fchmod(2)`](http://man7.org/linux/man-pages/man2/fchmod.2.html) documentation for more detail.
*/
function fchmodSync(fd, mode) {
/* istanbul ignore next */
Reflect.apply(chmodFixSync, fs, [fs.fchmodSync, fd, mode]);
}
exports.fchmodSync = fchmodSync;
/**
* Changes the permissions on a symbolic link. Returns `undefined`.
*
* This method is only implemented on macOS.
*
* See the POSIX [`lchmod(2)`](https://www.freebsd.org/cgi/man.cgi?query=lchmod&sektion=2) documentation for more detail.
*/
function lchmodSync(path, mode) {
/* istanbul ignore next */
if (!IgnorePatch) {
// lchmod, broken prior to 0.6.2
// back-port the fix here.
let lchmodSync = fs.lchmodSync;
if (fs.constants.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
lchmodSync = function (path, mode) {
const fd = fs.openSync(path, fs.constants.O_WRONLY | fs.constants.O_SYMLINK, mode);
let threw = true;
// prefer to return the chmod error, if one occurs,
// but still try to close, and report closing errors if they occur.
try {
fchmodSync(fd, mode);
threw = false;
}
finally {
if (threw) {
try {
fs.closeSync(fd);
}
catch (err) { }
}
else {
fs.closeSync(fd);
}
}
};
}
if (!lchmodSync) {
return;
}
return Reflect.apply(chmodFixSync, fs, [lchmodSync, path, mode]);
}
else {
return Reflect.apply(chmodFixSync, fs, [fs.lchownSync, path, mode]);
}
}
exports.lchmodSync = lchmodSync;
function copyFile(src, dest, mode, callback) {
/* istanbul ignore next */
if (!IgnorePatch) {
if (utils_1.Type.isFunction(mode)) {
callback = mode;
mode = 0;
}
function fsCopyFile(src, dest, mode, callback, startTime) {
return fs.copyFile(src, dest, mode, function (...args) {
errorEnqueueOrCallback(fsCopyFile, [src, dest, mode, callback], args, callback, startTime);
});
}
return fsCopyFile(src, dest, mode, callback);
}
else {
return Reflect.apply(fs.copyFile, fs, [src, dest, mode, callback]);
}
}
exports.copyFile = copyFile;
/**
* Changes the access and modification times of a file in the same way as {@link utimes}, with the difference that if the path refers to a symbolic
* link, then the link is not dereferenced: instead, the timestamps of the
* symbolic link itself are changed.
*
* No arguments other than a possible exception are given to the completion
* callback.
*/
function lutimes(path, atime, mtime, callback) {
/* istanbul ignore next */
if (!IgnorePatch) {
if (!fs.lutimes) {
if (fs.constants.hasOwnProperty("O_SYMLINK")) {
open(path, fs.constants.O_SYMLINK, (err, fd) => {
if (err) {
if (callback) {
callback(err);
}
return;
}
fs.futimes(fd, atime, mtime, (errFutimes) => {
fs.close(fd, (errClose) => {
if (callback) {
callback(errFutimes || errClose);
}
});
});
});
}
else {
process.nextTick(callback);
}
}
else {
fs.lutimes(path, atime, mtime, callback);
}
}
else {
if (!fs.lutimes) {
process.nextTick(callback);
}
else {
Reflect.apply(fs.lutimes, fs, [path, atime, mtime, callback]);
}
}
}
exports.lutimes = lutimes;
/**
* Change the file system timestamps of the symbolic link referenced by `path`.
* Returns `undefined`, or throws an exception when parameters are incorrect or
* the operation fails. This is the synchronous version of {@link lutimes}.
*/
function lutimesSync(path, atime, mtime) {
/* istanbul ignore next */
if (!IgnorePatch) {
if (!fs.lutimesSync) {
if (fs.constants.hasOwnProperty("O_SYMLINK")) {
const fd = fs.openSync(path, fs.constants.O_SYMLINK);
let threw = true;
try {
fs.futimesSync(fd, atime, mtime);
threw = false;
}
finally {
if (threw) {
try {
fs.closeSync(fd);
}
catch (err) { }
}
else {
fs.closeSync(fd);
}
}
}
else {
return;
}
}
else {
fs.lutimesSync(path, atime, mtime);
}
}
else {
if (!fs.lutimes) {
return;
}
else {
return Reflect.apply(fs.lutimesSync, fs, [path, atime, mtime]);
}
}
}
exports.lutimesSync = lutimesSync;
function open(path, flags, mode, callback) {
if (!IgnorePatch) {
if (utils_1.Type.isFunction(mode)) {
callback = mode;
mode = null;
}
function fsOpen(path, flags, mode, callback, startTime) {
return fs.open(path, flags, mode, (...args) => {
errorEnqueueOrCallback(fsOpen, [path, flags, mode, callback], args, callback, startTime);
});
}
return fsOpen(path, flags, mode, callback);
}
else {
/* istanbul ignore next */
return Reflect.apply(fs.open, fs, [path, flags, mode, callback]);
}
}
exports.open = open;
// if read() returns EAGAIN, then just try it again.
/**
* Read data from the file specified by `fd`.
*
* The callback is given the three arguments, `(err, bytesRead, buffer)`.
*
* If the file is not modified concurrently, the end-of-file is reached when the
* number of bytes read is zero.
*
* If this method is invoked as its `util.promisify()` ed version, it returns
* a promise for an `Object` with `bytesRead` and `buffer` properties.
* @param buffer The buffer that the data will be written to.
* @param offset The position in `buffer` to write the data to.
* @param length The number of bytes to read.
* @param position Specifies where to begin reading from in the file. If `position` is `null` or `-1 `, data will be read from the current file position, and the file position will be updated. If
* `position` is an integer, the file position will be unchanged.
*/
function read(fd, buffer, offset, length, position, callback) {
if (!IgnorePatch) {
let eagCounter = 0;
const cb = (err, bytesRead, buffer) => {
/* istanbul ignore next */
if (err && err.code === "EAGAIN" && eagCounter < 10) {
eagCounter++;
return Reflect.apply(fs.read, fs, [fd, buffer, offset, length, position, cb]);
}
callback(err, bytesRead, buffer);
};
return Reflect.apply(fs.read, fs, [fd, buffer, offset, length, position, cb]);
}
else {
/* istanbul ignore next */
Reflect.apply(fs.read, fs, [fd, buffer, offset, length, position, callback]);
}
}
exports.read = read;
function readSync(...args) {
/* istanbul ignore next */
if (!IgnorePatch) {
let eagCounter = 0;
while (true) {
try {
return Reflect.apply(fs.readSync, fs, args);
}
catch (e) {
const err = e;
if (err.code === "EAGAIN" && eagCounter < 10) {
eagCounter++;
continue;
}
throw err;
}
}
}
else {
return Reflect.apply(fs.readSync, fs, args);
}
}
exports.readSync = readSync;
function readdir(path, options, callback) {
if (!IgnorePatch) {
if (utils_1.Type.isFunction(options)) {
callback = options;
options = null;
}
function fsReaddir(path,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options, callback, startTime) {
return fs.readdir(path, options, (...args) => {
const files = args[1];
if (files && files.sort) {
files.sort();
args[1] = files;
}
errorEnqueueOrCallback(fsReaddir, [path, options, callback], args, callback, startTime);
});
}
return fsReaddir(path, options, callback);
}
else {
/* istanbul ignore next */
Reflect.apply(fs.readdir, fs, [path, options, callback]);
}
}
exports.readdir = readdir;
function readDir(path, options, callback) {
/* istanbul ignore next */
return readdir(path, options, callback);
}
exports.readDir = readDir;
function readdirSync(path, options) {
if (!IgnorePatch) {
return Reflect.apply(fs.readdirSync, fs, [path, options]).sort();
}
else {
/* istanbul ignore next */
return Reflect.apply(fs.readdirSync, fs, [path, options]);
}
}
exports.readdirSync = readdirSync;
function readDirSync(path, options) {
/* istanbul ignore next */
return readdirSync(path, options);
}
exports.readDirSync = readDirSync;
function readFile(path, options, callback) {
if (!IgnorePatch) {
/* istanbul ignore next */
if (utils_1.Type.isFunction(options)) {
callback = options;
options = null;
}
function fsReadFile(path, options, callback, startTime) {
return fs.readFile(path, options, (...args) => {
errorEnqueueOrCallback(fsReadFile, [path, options, callback], args, callback, startTime);
});
}
return fsReadFile(path, options, callback);
}
else {
/* istanbul ignore next */
return Reflect.apply(fs.readFile, fs, [path, options, callback]);
}
}
exports.readFile = readFile;
/**
* Asynchronously rename file at `oldPath` to the pathname provided
* as `newPath`. In the case that `newPath` already exists, it will
* be overwritten. If there is a directory at `newPath`, an error will
* be raised instead. No arguments other than a possible exception are
* given to the completion callback.
*
* See also: [`rename(2)`](http://man7.org/linux/man-pages/man2/rename.2.html).
*
* ```js
* import { rename } from 'fs-extender';
*
* rename('oldFile.txt', 'newFile.txt', (err) => {
* if (err) throw err;
* console.log('Rename complete!');
* });
* ```
*
* on Windows, A/V software can lock the directory, causing this
* to fail with an EACCES or EPERM if the directory contains newly
* created files. Try again on failure, for up to `process.env["FS-FS_EXTENDER_WIN32_TIMEOUT"]` (60 seconds default).
*
* Set the timeout this long because some Windows Anti-Virus, such as Parity
* bit9, may lock files for up to a minute, causing npm package install
* failures. Also, take care to yield the scheduler. Windows scheduling gives
* CPU to a busy looping process, which can cause the program causing the lock
* contention to be starved of CPU by node, so the contention doesn't resolve.
*
* Change max time with `process.env["FS-FS_EXTENDER_WIN32_TIMEOUT"]`
*/
/* istanbul ignore next */
function rename(oldPath, newPath, callback) {
if (platform === "win32" && !IgnorePatch) {
const startTime = Date.now();
let backOff = 0;
const stopTime = startTime + RenameMaxTime;
const CB = (err) => {
if (err && (err.code === "EACCES" || err.code === "EPERM") && Date.now() < stopTime) {
setTimeout(() => {
stat(oldPath, (errOldPath, statOldPath) => {
stat(newPath, (errNewPath, statNewPath) => {
if (errOldPath && !errNewPath) {
//if the source no longer exists we can probably assume it was moved
/* istanbul ignore next */
callback(null);
}
else if (statOldPath &&
statNewPath &&
statOldPath.size === statNewPath.size &&
statOldPath.ctime === statNewPath.ctime) {
//if the source and target have the same size and ctime, we can assume it was moved
/* istanbul ignore next */
callback(null);
}
else {
fs.rename(oldPath, newPath, CB);
}
});
});
}, backOff);
if (backOff < 100) {
backOff += 10;
}
}
else if (backOff && stopTime > startTime && err && err.code === "ENOENT") {
//the source no longer exists so we can assume it was moved during one of the tries
/* istanbul ignore next */
return callback(null);
}
else {
return callback(err);
}
};
fs.rename(oldPath, newPath, CB);
}
else {
fs.rename(oldPath, newPath, callback);
}
}
exports.rename = rename;
/**
* Renames the file from `oldPath` to `newPath`. Returns `undefined`.
*
* **ATTENTION**: In win32 platform this function will block the event loop until the file is renamed or timeout, use with care
*
* See the POSIX [`rename(2)`](http://man7.org/linux/man-pages/man2/rename.2.html) documentation for more details.
*
* on Windows, A/V software can lock the directory, causing this
* to fail with an EACCES or EPERM if the directory contains newly
* created files. Try again on failure, for up to `process.env["FS_EXTENDER_WIN32_TIMEOUTSYNC"]` (60 seconds default).
*
* Set the timeout this long because some Windows Anti-Virus, such as Parity
* bit9, may lock files for up to a minute, causing npm package install
* failures. Also, take care to yield the scheduler. Windows scheduling gives
* CPU to a busy looping process, which can cause the program causing the lock
* contention to be starved of CPU by node, so the contention doesn't resolve.
*
* Change max time with `process.env["FS_EXTENDER_WIN32_TIMEOUT_SYNC"]`
*/
/* istanbul ignore next */
function renameSync(oldPath, newPath) {
if (platform === "win32" && !IgnorePatch) {
const startTime = Date.now();
const stopTime = startTime + RenameMaxTimeoutSync;
let callAgain = 0;
const tryRename = () => {
try {
return fs.renameSync(oldPath, newPath);
}
catch (er) {
const err = er;
if ((err.code === "EACCES" || err.code === "EPERM") && Date.now() < stopTime) {
try {
statSync(newPath);
}
catch (erStat) {
const errStat = erStat;
if (errStat.code === "ENOENT") {
if (callAgain < 100) {
callAgain += 10;
}
const waitUntil = Date.now() + callAgain;
while (waitUntil < Date.now()) { }
return tryRename();
}
}
/* istanbul ignore next */
throw err;
}
else if (callAgain > 0 && err.code === "ENOENT") {
//the source no longer exists because so we can assume it was moved
}
else {
throw err;
}
//wait until target exists and source no longer exists or that we've reached the backoff limit
/* istanbul ignore next */
while ((fs.existsSync(oldPath) || !fs.existsSync(newPath)) && Date.now() < stopTime) { }
}
};
tryRename();
}
else {
fs.renameSync(oldPath, newPath);
}
}
exports.renameSync = renameSync;
function rmdir(path, options, callback) {
if (!IgnorePatch) {
let cb;
/* istanbul ignore next */
if (utils_1.Type.isFunction(options) && !callback) {
cb = options;
options = {};
}
else {
cb = callback;
}
const opt = {
maxRetries: (0, util_1.getObjectOption)(options, "maxRetries", 0),
retryDelay: (0, util_1.getObjectOption)(options, "retryDelay", 100),
recursive: (0, util_1.getObjectOption)(options, "recursive", false),
};
if ("maxBusyTries" in (options || {})) {
/* istanbul ignore next */
opt.maxRetries = options["maxBusyTries"];
}
stat(path, (err, stats) => {
if (err) {
/* istanbul ignore next */
return cb(err);
}
/* istanbul ignore next */
if (!stats.isDirectory()) {
const e = new Error(`'${path}' is not a directory.`);
e.code = IsWindows ? "ENOENT" : "ENOTDIR";
return cb(e);
}
else {
if ("recursive" in opt && opt.recursive) {
_rm.rm(path, { force: true, recursive: true }, cb);
}
else {
let retries = 0;
function retry() {
fs.rmdir(path, (err) => {
if (err) {
if (err.code === "ENOENT") {
/* istanbul ignore next */
return cb(null);
}
else if (["EBUSY", "EMFILE", "ENFILE", "ENOTEMPTY", "EPERM"].indexOf(err.code) !== -1 &&
retries < opt.maxRetries) {
retries++;
if (err.code === "EPERM") {
/* istanbul ignore next */
return chmod(path, 0o666, (errChMod) => {
if (errChMod && errChMod.code === "ENOENT") {
return cb(null);
}
return setTimeout(retry, retries * opt.retryDelay);
});
}
else {
return setTimeout(retry, retries * opt.retryDelay);
}
}
else if (retries >= opt.maxRetries) {
return cb(err);
}
}
cb(null);
});
}
retry();
}
}
});
}
else {
/* istanbul ignore next */
return Reflect.apply(fs.rmdir, fs, [path, options, callback]);
}
}
exports.rmdir = rmdir;
/**
* Synchronous [`rmdir(2)`](http://man7.org/linux/man-pages/man2/rmdir.2.html). Returns `undefined`.
*
* Using `fs.rmdirSync()` on a file (not a directory) results in an `ENOENT` error
* on Windows and an `ENOTDIR` error on POSIX.
*
* To get a behavior similar to the `rm -rf` Unix command, use {@link rmSync} with options `{ recursive: true, force: true }`.
*/
function rmdirSync(path, options) {
if (!IgnorePatch) {
const opt = {
maxRetries: (0, util_1.getObjectOption)(options, "maxRetries", 0),
retryDelay: (0, util_1.getObjectOption)(options, "retryDelay", 100),
recursive: (0, util_1.getObjectOption)(options, "recursive", false),
};
if ("maxBusyTries" in (options || {})) {
/* istanbul ignore next */
opt.maxRetries = options["maxBusyTries"];
}
const stats = statSync(path);
/* istanbul ignore next */
if (!stats.isDirectory()) {
const e = new Error(`'${path}' is not a directory.`);
e.code = IsWindows ? "ENOENT" : "ENOTDIR";
throw e;
}
else {
if ("recursive" in opt && opt.recursive) {
return _rm.rmSync(path, { force: true, recursive: true });
}
else {
const tries = opt.maxRetries + 1;
for (let i = 1; i <= tries; i++) {
try {
fs.rmdirSync(path, opt);
}
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.
/* istanbul ignore next */
if (["EBUSY", "EMFILE", "ENFILE", "ENOTEMPTY", "EPERM"].indexOf(err.code) !== -1 &&
i < tries &&
opt.retryDelay > 0) {
//poor sleeping
const stop = Date.now() + i * opt.retryDelay;
while (stop < Date.now()) { }
//sleep(i * options.retryDelay);
}
else if (err.code === "ENOENT") {
// The file is already gone.
return;
}
else if (i === tries) {
throw err;
}
}
}
}
}
}
else {
/* istanbul ignore next */
return Reflect.apply(fs.rmdirSync, fs, [path, options]);
}
}
exports.rmdirSync = rmdirSync;
/** @internal*/
function statFix(orig, path, options, callback) {
if (!IgnorePatch) {
// Older versions of Node erroneously returned signed integers for
// uid + gid.
if (utils_1.Type.isUndefined(options) && utils_1.NumberUtil.nodeVersionGTE("10.0.0") && utils_1.NumberUtil.nodeVersionLT("11")) {
/* istanbul ignore next */
options = { BigInt: false };
}
return Reflect.apply(orig, this, [
path,
options,
(err, stats) => {
if (stats) {
if (stats.uid < 0) {
stats.uid += 0x100000000;
}
if (stats.gid < 0) {
stats.gid += 0x100000000;
}
}
callback(err, stats);
},
]);
}
else {
/* istanbul ignore next */
Reflect.apply(orig, fs, [path, options, callback]);
}
}
/** @internal*/
function statFixSync(orig, path, options) {
if (!IgnorePatch) {
// Older versions of Node erroneously returned signed integers for
// uid + gid.
if (utils_1.Type.isUndefined(options) && utils_1.NumberUtil.nodeVersionGTE("10.0.0") && utils_1.NumberUtil.nodeVersionLT("11")) {
/* istanbul ignore next */
options = { BigInt: false };
}
const stats = Reflect.apply(orig, fs, [path, options]);
if (stats.uid < 0) {
stats.uid += 0x100000000;
}
if (stats.gid < 0) {
stats.gid += 0x100000000;
}
return stats;
}
else {
/* istanbul ignore next */
return Reflect.apply(orig, fs, [path, options]);
}
}
function stat(path, options, callback) {
if (utils_1.Type.isFunction(options)) {
callback = options;
options = undefined;
}
Reflect.apply(statFix, fs, [fs.stat, path, options, callback]);
}
exports.stat = stat;
function fstat(fd, options, callback) {
/* istanbul ignore next */
if (utils_1.Type.isFunction(options)) {
callback = options;
options = undefined;
}
/* istanbul ignore next */
return Reflect.apply(statFix, fs, [fs.fstat, fd, options, callback]);
}
exports.fstat = fstat;
function lstat(path, options, callback) {
if (utils_1.Type.isFunction(options)) {
callback = options;
options = undefined;
}
return Reflect.apply(statFix, fs, [fs.lstat, path, options, callback]);
}
exports.lstat = lstat;
function statSync(path, options) {
return Reflect.apply(statFixSync, fs, [fs.statSync, path, options]);
}
exports.statSync = statSync;
function fstatSync(fd, options) {
/* istanbul ignore next */
return Reflect.apply(statFixSync, fs, [fs.fstatSync, fd, options]);
}
exports.fstatSync = fstatSync;
function lstatSync(path, options) {
return Reflect.apply(statFixSync, fs, [fs.lstatSync, path, options]);
}
exports.lstatSync = lstatSync;
/* istanbul ignore next */
function symlink(target, path, type, callback) {
if (utils_1.Type.isFunction(type)) {
callback = type;
type = null;
}
//this patch is only applied in node v12.0.0
if (!IgnorePatch && utils_1.Type.isNullOrUndefined(type) && IsWindows && utils_1.NumberUtil.nodeVersionLT("12.0.0")) {
const absoluteTarget = path_extender_1.default.resolve(path, "..", target);
if (!utils_1.Type.isUndefined(absoluteTarget)) {
return Reflect.apply(fs.stat, fs, [
absoluteTarget,
(err, stats) => {
type = stats && stats.isDirectory() ? "dir" : "file";
Reflect.apply(fs.symlink, fs, [target, path, type, callback]);
},
]);
}
}
Reflect.apply(fs.symlink, fs, [target, path, type, callback]);
}
exports.symlink = symlink;
/**
* Returns `undefined`.
*
* For detailed information, see the documentation of the asynchronous version of
* this API: {@link symlink}.
* @since v0.1.31
*/
/* istanbul ignore next */
function symlinkSync(target, path, type) {
if (!IgnorePatch && utils_1.Type.isNullOrUndefined(type) && IsWindows && utils_1.NumberUtil.nodeVersionLT("12.0.0")) {
const absoluteTarget = path_extender_1.default.resolve(path, "..", target);
try {
const stats = fs.statSync(absoluteTarget);
type = stats.isDirectory() ? "dir" : "file";
}
catch (err) { }
}
return Reflect.apply(fs.symlinkSync, fs, [target, path, type]);
}
exports.symlinkSync = symlinkSync;
/**
* Asynchronously removes a file or symbolic link. No arguments other than a
* possible exception are given to the completion callback.
*
* ```js
* import { unlink } from 'fs-extender';
* // Assuming that 'path/file.txt' is a regular file.
* unlink('path/file.txt', (err) => {
* if (err) throw err;
* console.log('path/file.txt was deleted');
* });
* ```
*
* `fs.unlink()` will not work on a directory, empty or otherwise. To remove a
* directory, use {@link rmdir}.
*
* See the POSIX [`unlink(2)`](http://man7.org/linux/man-pages/man2/unlink.2.html) documentation for more details.
*/
function unlink(path, callback) {
if (!IgnorePatch) {
const startTime = Date.now();
let backOff = -10;
const stopTime = startTime + MaxTimeout;
function retry() {
fs.unlink(path, (err) => {
if (err) {
if (err.code === "ENOENT") {
/* istanbul ignore next */
return callback(null);
}
else if (["EBUSY", "EPERM"].indexOf(err.code) !== -1 && stopTime > Date.now()) {
/* istanbul ignore next */
if (err.code === "EPERM" && IsWindows) {
return chmod(path, 0o666, (errChmod) => {
if (errChmod) {
if (errChmod.code === "ENOENT") {
return callback(null);
}
return callback(err);
}
retry();
});
}
else {
setTimeout(retry, backOff);
if (backOff < 100) {
backOff += 10;
}
}
}
else {
return callback(err);
}
}
else {
return callback(null);
}
});
}
retry();
}
else {
/* istanbul ignore next */
Reflect.apply(fs.unlink, fs, [path, callback]);
}
}
exports.unlink = unlink;
/**
* Synchronous [`unlink(2)`](http://man7.org/linux/man-pages/man2/unlink.2.html). Returns `undefined`.
*
* **ATTENTION**: This function will block the event loop until the file is removed or timeout, use with care
*/
function unlinkSync(path) {
if (!IgnorePatch) {
const startTime = Date.now();
let backOff = -10;
const stopTime = startTime + MaxTimeout;
function retry() {
try {
fs.unlinkSync(path);
}
catch (er) {
const err = er;
if (err.code === "ENOENT") {
/* istanbul ignore next */
return;
}
else if (["EBUSY", "EPERM"].indexOf(err.code) !== -1 && stopTime > Date.now()) {
/* istanbul ignore next */
if (err.code === "EPERM" && IsWindows) {
try {
chmodSync(path, 0o666);
}
catch (errChmod) {
if (errChmod.code === "ENOENT") {
return;
}
throw err;
}
return retry();
}
else {
if (backOff < 100) {
backOff += 10;