UNPKG

fs-extender

Version:
1,355 lines (1,354 loc) 53.2 kB
"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;