diff --git a/.gitignore b/.gitignore index 2f24c57..909c219 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ coverage/ .nyc_output/ +test/temp-files-*/ diff --git a/.travis.yml b/.travis.yml index 1d38b47..c10d2c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,11 @@ node_js: - 12 - 10 - 8 - - 6 os: - - linux - windows + - osx + - linux cache: directories: diff --git a/README.md b/README.md index 5273a50..b88c9c6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ resilient to errors. * Queues up `open` and `readdir` calls, and retries them once something closes if there is an EMFILE error from too many file descriptors. -* fixes `lchmod` for Node versions prior to 0.6.2. * implements `fs.lutimes` if possible. Otherwise it becomes a noop. * ignores `EINVAL` and `EPERM` errors in `chown`, `fchown` or `lchown` if the user isn't root. @@ -79,8 +78,7 @@ also imposes the challenge of keeping in sync with the core module. The current approach loads the `fs` module, and then creates a lookalike object that has all the same methods, except a few that are -patched. It is safe to use in all versions of Node from 0.8 through -7.0. +patched. ### v4 diff --git a/check-for-callback.js b/check-for-callback.js new file mode 100644 index 0000000..54a2785 --- /dev/null +++ b/check-for-callback.js @@ -0,0 +1,42 @@ +'use strict' + +const nodeMajor = process.versions.node.split('.')[0] + +/* This function emulates the functionality of node.js 7, 8 and 9 by emitting a + * deprecation warning. In node.js 10+ a TypeError is produced. */ +function checkForCallback (cb, ctor) { + if (typeof cb === 'function') { + return true + } + + if (nodeMajor >= 10) { + /* It's possible that the caller provided something incorrect for the callback + * but more likely they omitted the argument. + * + * The error is technically wrong if someone provides something for the callback + * argument which is not a function, for example `fs.mkdir('/path', {}, 'cb')` + * should say that a string was provided. Providing the correct exception for + * this off nominal case would require argument processing to be specific to each + * patched function. */ + const error = new TypeError('Callback must be a function. Received undefined') + error.code = 'ERR_INVALID_CALLBACK' + + /* The next 4 lines are copied from node.js, lib/internal/errors.js:addCodeToName() */ + error.name = `${error.name} [${error.code}]` + Error.captureStackTrace(error, ctor) + error.stack // eslint-disable-line no-unused-expressions + delete error.name + + throw error + } + + process.emitWarning( + 'Calling an asynchronous function without callback is deprecated.', + 'DeprecationWarning', + 'DEP0013', + ctor + ) + return false +} + +module.exports = checkForCallback diff --git a/chown-er-filter.js b/chown-er-filter.js new file mode 100644 index 0000000..804d5d6 --- /dev/null +++ b/chown-er-filter.js @@ -0,0 +1,28 @@ +'use strict' + +// 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. +function chownErFilter (er) { + if (!er || er.code === 'ENOSYS') { + return + } + + const nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot && (er.code === 'EINVAL' || er.code === 'EPERM')) { + return + } + + return er +} + +module.exports = chownErFilter diff --git a/clone.js b/clone.js index 028356c..5aaa57a 100644 --- a/clone.js +++ b/clone.js @@ -3,17 +3,11 @@ module.exports = clone function clone (obj) { - if (obj === null || typeof obj !== 'object') - return obj + const copy = Object.create(Object.getPrototypeOf(obj)) - if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } - else - var copy = Object.create(null) - - Object.getOwnPropertyNames(obj).forEach(function (key) { + for (const key of Object.getOwnPropertyNames(obj)) { Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) - }) + } return copy } diff --git a/graceful-fs.js b/graceful-fs.js index ac20675..f73ada8 100644 --- a/graceful-fs.js +++ b/graceful-fs.js @@ -1,279 +1,128 @@ -var fs = require('fs') -var polyfills = require('./polyfills.js') -var legacy = require('./legacy-streams.js') -var clone = require('./clone.js') +'use strict' -var queue = [] +const fs = require('fs') -var util = require('util') +const polyfills = require('./polyfills.js') +const clone = require('./clone.js') +const normalizeArgs = require('./normalize-args.js') +const {initQueue, retry, enqueue} = require('./retry-queue.js') +const readdirSort = require('./readdir-sort.js') -function noop () {} +const gracefulPatched = Symbol.for('graceful-fs.patched') -var debug = noop -if (util.debuglog) - debug = util.debuglog('gfs4') -else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) - debug = function() { - var m = util.format.apply(util, arguments) - m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') - console.error(m) - } - -if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { - process.on('exit', function() { - debug(queue) - require('assert').equal(queue.length, 0) - }) -} +initQueue() module.exports = patch(clone(fs)) -if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { - module.exports = patch(fs) - fs.__patched = true; -} - -// Always patch fs.close/closeSync, because we want to -// retry() whenever a close happens *anywhere* in the program. -// This is essential when multiple graceful-fs instances are -// in play at the same time. -module.exports.close = (function (fs$close) { return function (fd, cb) { - return fs$close.call(fs, fd, function (err) { - if (!err) - retry() - - if (typeof cb === 'function') - cb.apply(this, arguments) - }) -}})(fs.close) - -module.exports.closeSync = (function (fs$closeSync) { return function (fd) { - // Note that graceful-fs also retries when fs.closeSync() fails. - // Looks like a bug to me, although it's probably a harmless one. - var rval = fs$closeSync.apply(fs, arguments) - retry() - return rval -}})(fs.closeSync) - -// Only patch fs once, otherwise we'll run into a memory leak if -// graceful-fs is loaded multiple times, such as in test environments that -// reset the loaded modules between tests. -// We look for the string `graceful-fs` from the comment above. This -// way we are not adding any extra properties and it will detect if older -// versions of graceful-fs are installed. -if (!/\bgraceful-fs\b/.test(fs.closeSync.toString())) { - fs.closeSync = module.exports.closeSync; - fs.close = module.exports.close; -} - -function patch (fs) { - // Everything that references the open() function needs to be in here - polyfills(fs) - fs.gracefulify = patch - fs.FileReadStream = ReadStream; // Legacy name. - fs.FileWriteStream = WriteStream; // Legacy name. - fs.createReadStream = createReadStream - fs.createWriteStream = createWriteStream - var fs$readFile = fs.readFile - fs.readFile = readFile - function readFile (path, options, cb) { - if (typeof options === 'function') - cb = options, options = null - - return go$readFile(path, options, cb) - - function go$readFile (path, options, cb) { - return fs$readFile(path, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readFile, [path, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } - - var fs$writeFile = fs.writeFile - fs.writeFile = writeFile - function writeFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null - - return go$writeFile(path, data, options, cb) - - function go$writeFile (path, data, options, cb) { - return fs$writeFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$writeFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } - - var fs$appendFile = fs.appendFile - if (fs$appendFile) - fs.appendFile = appendFile - function appendFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null - - return go$appendFile(path, data, options, cb) - - function go$appendFile (path, data, options, cb) { - return fs$appendFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$appendFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } - - var fs$readdir = fs.readdir - fs.readdir = readdir - function readdir (path, options, cb) { - var args = [path] - if (typeof options !== 'function') { - args.push(options) - } else { - cb = options - } - args.push(go$readdir$cb) - - return go$readdir(args) - - function go$readdir$cb (err, files) { - if (files && files.sort) - files.sort() - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readdir, [args]]) - - else { - if (typeof cb === 'function') - cb.apply(this, arguments) +function patchENFILE (origImpl, setupArgs) { + function internalImpl (implArgs, cb) { + return origImpl(...implArgs, (err, ...args) => { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) { + enqueue([internalImpl, [implArgs, cb]]) + } else { + cb(err, ...args) retry() } - } - } - - function go$readdir (args) { - return fs$readdir.apply(fs, args) + }) } - if (process.version.substr(0, 4) === 'v0.8') { - var legStreams = legacy(fs) - ReadStream = legStreams.ReadStream - WriteStream = legStreams.WriteStream + if (setupArgs) { + return (...args) => internalImpl(...setupArgs(...normalizeArgs(args))) } - var fs$ReadStream = fs.ReadStream - if (fs$ReadStream) { - ReadStream.prototype = Object.create(fs$ReadStream.prototype) - ReadStream.prototype.open = ReadStream$open - } + return (...args) => internalImpl(...normalizeArgs(args)) +} - var fs$WriteStream = fs.WriteStream - if (fs$WriteStream) { - WriteStream.prototype = Object.create(fs$WriteStream.prototype) - WriteStream.prototype.open = WriteStream$open - } +function patchStream (fs, isRead) { + const name = isRead ? 'ReadStream' : 'WriteStream' + const origImpl = fs[name] - fs.ReadStream = ReadStream - fs.WriteStream = WriteStream + function PatchedStream (...args) { + if (this instanceof PatchedStream) { + origImpl.apply(this, args) + return this + } - function ReadStream (path, options) { - if (this instanceof ReadStream) - return fs$ReadStream.apply(this, arguments), this - else - return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + return new PatchedStream(...args) } - function ReadStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { + PatchedStream.prototype = Object.create(origImpl.prototype) + PatchedStream.prototype.open = PatchedStream$open + + function PatchedStream$open () { + fs.open(this.path, this.flags, this.mode, (err, fd) => { if (err) { - if (that.autoClose) - that.destroy() + if (this.autoClose) { + this.destroy() + } - that.emit('error', err) + this.emit('error', err) } else { - that.fd = fd - that.emit('open', fd) - that.read() + this.fd = fd + this.emit('open', fd) + this.emit('ready') + if (isRead) { + this.read() + } } }) } - function WriteStream (path, options) { - if (this instanceof WriteStream) - return fs$WriteStream.apply(this, arguments), this - else - return WriteStream.apply(Object.create(WriteStream.prototype), arguments) - } + let Klass = PatchedStream + + fs[`create${name}`] = (...args) => new Klass(...args) + + Object.defineProperties(fs, { + [name]: { + get () { + return Klass + }, + set (value) { + Klass = value + }, + enumerable: true + }, + // Legacy name + [`File${name}`]: { + value: PatchedStream, + writable: true, + enumerable: true + } + }) +} - function WriteStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - that.destroy() - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - } - }) +function patch (fs) { + if (fs[gracefulPatched]) { + return fs } - function createReadStream (path, options) { - return new ReadStream(path, options) - } + Object.defineProperty(fs, gracefulPatched, { + value: true + }) - function createWriteStream (path, options) { - return new WriteStream(path, options) - } + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch - var fs$open = fs.open - fs.open = open - function open (path, flags, mode, cb) { - if (typeof mode === 'function') - cb = mode, mode = null + fs.open = patchENFILE(fs.open) + fs.readFile = patchENFILE(fs.readFile) + fs.writeFile = patchENFILE(fs.writeFile) + fs.appendFile = patchENFILE(fs.appendFile) + fs.readdir = patchENFILE(fs.readdir, (args, cb) => [ + args, + (err, files) => { + cb(err, readdirSort(files)) + } + ]) - return go$open(path, flags, mode, cb) + patchStream(fs, true) + patchStream(fs, false) - function go$open (path, flags, mode, cb) { - return fs$open(path, flags, mode, function (err, fd) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$open, [path, flags, mode, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } + const promises = Object.getOwnPropertyDescriptor(fs, 'promises') + /* istanbul ignore next */ + if (promises) { + require('./promises.js')(fs, promises) } return fs } - -function enqueue (elem) { - debug('ENQUEUE', elem[0].name, elem[1]) - queue.push(elem) -} - -function retry () { - var elem = queue.shift() - if (elem) { - debug('RETRY', elem[0].name, elem[1]) - elem[0].apply(null, elem[1]) - } -} diff --git a/legacy-streams.js b/legacy-streams.js deleted file mode 100644 index d617b50..0000000 --- a/legacy-streams.js +++ /dev/null @@ -1,118 +0,0 @@ -var Stream = require('stream').Stream - -module.exports = legacy - -function legacy (fs) { - return { - ReadStream: ReadStream, - WriteStream: WriteStream - } - - function ReadStream (path, options) { - if (!(this instanceof ReadStream)) return new ReadStream(path, options); - - Stream.call(this); - - var self = this; - - this.path = path; - this.fd = null; - this.readable = true; - this.paused = false; - - this.flags = 'r'; - this.mode = 438; /*=0666*/ - this.bufferSize = 64 * 1024; - - options = options || {}; - - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } - - if (this.encoding) this.setEncoding(this.encoding); - - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.end === undefined) { - this.end = Infinity; - } else if ('number' !== typeof this.end) { - throw TypeError('end must be a Number'); - } - - if (this.start > this.end) { - throw new Error('start must be <= end'); - } - - this.pos = this.start; - } - - if (this.fd !== null) { - process.nextTick(function() { - self._read(); - }); - return; - } - - fs.open(this.path, this.flags, this.mode, function (err, fd) { - if (err) { - self.emit('error', err); - self.readable = false; - return; - } - - self.fd = fd; - self.emit('open', fd); - self._read(); - }) - } - - function WriteStream (path, options) { - if (!(this instanceof WriteStream)) return new WriteStream(path, options); - - Stream.call(this); - - this.path = path; - this.fd = null; - this.writable = true; - - this.flags = 'w'; - this.encoding = 'binary'; - this.mode = 438; /*=0666*/ - this.bytesWritten = 0; - - options = options || {}; - - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } - - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.start < 0) { - throw new Error('start must be >= zero'); - } - - this.pos = this.start; - } - - this.busy = false; - this._queue = []; - - if (this.fd === null) { - this._open = fs.open; - this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); - this.flush(); - } - } -} diff --git a/lutimes-polyfill.js b/lutimes-polyfill.js new file mode 100644 index 0000000..34d0671 --- /dev/null +++ b/lutimes-polyfill.js @@ -0,0 +1,53 @@ +'use strict' + +const {constants} = require('fs') +const {noop, noopSync} = require('./noop.js') +const normalizeArgs = require('./normalize-args.js') + +function patchLutimes (fs) { + /* istanbul ignore if: coverage for this file is ignored if O_SYMLINK is not defined. */ + if (typeof constants.O_SYMLINK === 'undefined') { + fs.lutimes = noop + fs.lutimesSync = noopSync + return + } + + fs.lutimes = (path, at, mt, cb) => { + cb = normalizeArgs([cb])[1] + + fs.open(path, constants.O_SYMLINK, (er, fd) => { + if (er) { + cb(er) + return + } + + fs.futimes(fd, at, mt, er => { + fs.close(fd, er2 => { + cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = (path, at, mt) => { + const fd = fs.openSync(path, constants.O_SYMLINK) + let ret + let threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + + return ret + } +} + +module.exports = patchLutimes diff --git a/noop.js b/noop.js new file mode 100644 index 0000000..96b54e7 --- /dev/null +++ b/noop.js @@ -0,0 +1,16 @@ +'use strict' + +const normalizeArgs = require('./normalize-args.js') + +function noop (...args) { + const cb = normalizeArgs(args)[1] + process.nextTick(cb) +} + +function noopSync () { +} + +module.exports = { + noop, + noopSync +} diff --git a/normalize-args.js b/normalize-args.js new file mode 100644 index 0000000..a04e11c --- /dev/null +++ b/normalize-args.js @@ -0,0 +1,17 @@ +'use strict' + +const checkForCallback = require('./check-for-callback.js') + +function normalizeArgs (args) { + let cb = args.slice(-1)[0] + if (checkForCallback(cb, normalizeArgs)) { + args.splice(-1, 1) + } else { + /* This is for node.js < 10 only, newer versions will throw in checkForCallback. */ + cb = () => {} + } + + return [args, cb] +} + +module.exports = normalizeArgs diff --git a/nyc.config.js b/nyc.config.js new file mode 100644 index 0000000..9333085 --- /dev/null +++ b/nyc.config.js @@ -0,0 +1,31 @@ +'use strict' + +const fs = require('fs') +const glob = require('glob') + +const ignore = [] + +if (!('O_SYMLINK' in fs.constants)) { + ignore.push('lutimes-polyfill.js') +} + +if (!fs.Dirent) { + // Unavailable in node.js 8 + ignore.push('readdir-sort.js') +} + +if (!Object.getOwnPropertyDescriptor(fs, 'promises')) { + ignore.push('promises.js', 'promise-windows-rename-polyfill.js') +} + +module.exports = { + all: true, + lines: 100, + statements: 100, + functions: 100, + branches: 100, + include: glob.sync('*.js', { + cwd: __dirname, + ignore + }) +} diff --git a/package-lock.json b/package-lock.json index be5c67b..473934a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,14 @@ "lodash": "^4.17.13", "source-map": "^0.5.0", "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "@babel/helper-function-name": { @@ -72,6 +80,15 @@ "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", "dev": true }, + "@babel/runtime": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", + "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, "@babel/template": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", @@ -124,9 +141,9 @@ } }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "ansi-styles": { @@ -138,6 +155,16 @@ "color-convert": "^1.9.0" } }, + "anymatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", + "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "append-transform": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", @@ -183,6 +210,15 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, + "async-hook-domain": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/async-hook-domain/-/async-hook-domain-1.1.0.tgz", + "integrity": "sha512-NH7V97d1yCbIanu2oDLyPT2GFNct0esPeJyRfkk8J5hTztHVSQp4UiNfL2O42sCA9XZPU8OgHvzOmt9ewBhVqA==", + "dev": true, + "requires": { + "source-map-support": "^0.5.11" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -216,6 +252,12 @@ "tweetnacl": "^0.14.3" } }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, "bind-obj-methods": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bind-obj-methods/-/bind-obj-methods-2.0.0.tgz", @@ -232,6 +274,15 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -254,30 +305,25 @@ "make-dir": "^2.0.0", "package-hash": "^3.0.0", "write-file-atomic": "^2.4.2" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "requires": { - "caller-callsite": "^2.0.0" + }, + "dependencies": { + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } } }, "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "camelcase": { @@ -309,23 +355,39 @@ "supports-color": "^5.3.0" } }, - "clean-yaml-object": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", - "integrity": "sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g=", - "dev": true + "chokidar": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "dev": true, + "requires": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "fsevents": "^2.0.6", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", + "normalize-path": "^3.0.0", + "readdirp": "^3.1.1" + } }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -399,9 +461,9 @@ "dev": true }, "coveralls": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.5.tgz", - "integrity": "sha512-/KD7PGfZv/tjKB6LoW97jzIgFqem0Tu9tZL9/iwBnBd8zkIZp7vT1ZSHNvnr0GSQMV/LTMxUstWg8WcDDUVQKg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.6.tgz", + "integrity": "sha512-Pgh4v3gCI4T/9VijVrm8Ym5v0OgjvGLKj3zTUwkvsCiwqae/p6VLzpsFNjQS2i6ewV7ef+DjFJ5TSKxYt/mCrA==", "dev": true, "requires": { "growl": "~> 1.10.0", @@ -483,15 +545,9 @@ "dev": true }, "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", - "dev": true - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", "dev": true }, "ecc-jsbn": { @@ -579,6 +635,15 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -599,6 +664,29 @@ "locate-path": "^3.0.0" } }, + "findit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findit/-/findit-2.0.0.tgz", + "integrity": "sha1-ZQnwEmr0wXhVHPqZOU4DLhOk1W4=", + "dev": true + }, + "flow-parser": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.104.0.tgz", + "integrity": "sha512-S2VGfM/qU4g9NUf2hA5qH/QmQsZIflxFO7victnYN1LR5SoOUsn3JtMhXLKHm2QlnZwwJKIdLt/uYyPr4LiQAA==", + "dev": true + }, + "flow-remove-types": { + "version": "2.104.0", + "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.104.0.tgz", + "integrity": "sha512-4M132BBfZmURXSoN24VPqUn4Q1rxymNG/G8u/l/hDKkDjzfrM3ZiQeHaCBgu8h/qekQ1QNiZp9gfMV0DURq0tA==", + "dev": true, + "requires": { + "flow-parser": "^0.104.0", + "pirates": "^3.0.2", + "vlq": "^0.2.1" + } + }, "foreground-child": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", @@ -638,6 +726,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "dev": true, + "optional": true + }, "function-loop": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-1.0.2.tgz", @@ -673,6 +768,15 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -680,9 +784,9 @@ "dev": true }, "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", "dev": true }, "growl": { @@ -701,14 +805,6 @@ "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "har-schema": { @@ -743,10 +839,30 @@ } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.2.tgz", + "integrity": "sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } }, "http-signature": { "version": "1.2.0", @@ -760,13 +876,13 @@ } }, "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", "dev": true, "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, "imurmurhash": { @@ -797,12 +913,42 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -872,6 +1018,34 @@ } } }, + "istanbul-lib-processinfo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-1.0.0.tgz", + "integrity": "sha512-FY0cPmWa4WoQNlvB8VOcafiRoB5nB+l2Pz2xGuXHRSy1KM8QFOYfz/rN+bGMCAeejrY3mrpF5oJHcN0s/garCg==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^6.0.5", + "istanbul-lib-coverage": "^2.0.3", + "rimraf": "^2.6.3", + "uuid": "^3.3.2" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, "istanbul-lib-report": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", @@ -905,14 +1079,6 @@ "make-dir": "^2.1.0", "rimraf": "^2.6.3", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "istanbul-reports": { @@ -924,6 +1090,15 @@ "handlebars": "^4.1.2" } }, + "jackspeak": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-1.4.0.tgz", + "integrity": "sha512-VDcSunT+wcccoG46FtzuBAyQKlzhHjli4q31e1fIHGOsRspqNUFjVzGb+7eIFDlTvqLygxapDHPHS0ouT2o/tw==", + "dev": true, + "requires": { + "cliui": "^4.1.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1075,14 +1250,6 @@ "dev": true, "requires": { "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "mime-db": { @@ -1160,6 +1327,18 @@ "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -1172,6 +1351,18 @@ "validate-npm-package-license": "^3.0.1" } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "nyc": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", @@ -1203,14 +1394,6 @@ "uuid": "^3.3.2", "yargs": "^13.2.2", "yargs-parser": "^13.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } } }, "oauth-sign": { @@ -1301,6 +1484,15 @@ "release-zalgo": "^1.0.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -1323,6 +1515,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -1352,12 +1550,27 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, + "pirates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-3.0.2.tgz", + "integrity": "sha512-c5CgUJq6H2k6MJz72Ak1F5sN9n9wlSlJyEnwvpm9/y3WB4E3pHBDT2c6PEiS1vyJvq2bUxUAIu0EGf8Cx4Ic7Q==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -1444,6 +1657,21 @@ } } }, + "readdirp": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz", + "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -1503,9 +1731,9 @@ } }, "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "rimraf": { @@ -1541,6 +1769,21 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -1548,9 +1791,9 @@ "dev": true }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-support": { @@ -1561,14 +1804,6 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "spawn-wrap": { @@ -1647,14 +1882,13 @@ "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "strip-ansi": "^4.0.0" } }, "string_decoder": { @@ -1677,12 +1911,12 @@ } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { @@ -1701,108 +1935,1169 @@ } }, "tap": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/tap/-/tap-12.7.0.tgz", - "integrity": "sha512-SjglJmRv0pqrQQ7d5ZBEY8ZOqv3nYDBXEX51oyycOH7piuhn82JKT/yDNewwmOsodTD/RZL9MccA96EjDgK+Eg==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/tap/-/tap-14.6.1.tgz", + "integrity": "sha512-zxENP78NlaF4guEyNPvSs7iKWOJUSjqRYcfN+ERo42OaSyA0hDm27U4yuliQjuBJlW35vvAk9dIfocPVa0eYMw==", "dev": true, "requires": { + "async-hook-domain": "^1.1.0", "bind-obj-methods": "^2.0.0", "browser-process-hrtime": "^1.0.0", "capture-stack-trace": "^1.0.0", - "clean-yaml-object": "^0.1.0", + "chokidar": "^3.0.2", "color-support": "^1.1.0", - "coveralls": "^3.0.2", - "domain-browser": "^1.2.0", - "esm": "^3.2.5", + "coveralls": "^3.0.5", + "diff": "^4.0.1", + "esm": "^3.2.25", + "findit": "^2.0.0", + "flow-remove-types": "^2.101.0", "foreground-child": "^1.3.3", "fs-exists-cached": "^1.0.0", - "function-loop": "^1.0.1", - "glob": "^7.1.3", + "function-loop": "^1.0.2", + "glob": "^7.1.4", + "import-jsx": "^2.0.0", + "ink": "^2.3.0", "isexe": "^2.0.0", - "js-yaml": "^3.13.1", + "istanbul-lib-processinfo": "^1.0.0", + "jackspeak": "^1.4.0", "minipass": "^2.3.5", "mkdirp": "^0.5.1", - "nyc": "^14.0.0", + "nyc": "^14.1.1", "opener": "^1.5.1", - "os-homedir": "^1.0.2", "own-or": "^1.0.0", "own-or-env": "^1.0.1", + "react": "^16.8.6", "rimraf": "^2.6.3", "signal-exit": "^3.0.0", - "source-map-support": "^0.5.10", + "source-map-support": "^0.5.12", "stack-utils": "^1.0.2", - "tap-mocha-reporter": "^3.0.9", - "tap-parser": "^7.0.0", - "tmatch": "^4.0.0", + "tap-mocha-reporter": "^4.0.1", + "tap-parser": "^9.3.2", + "tap-yaml": "^1.0.0", + "tcompare": "^2.3.0", + "treport": "^0.4.0", "trivial-deferred": "^1.0.1", - "ts-node": "^8.0.2", - "tsame": "^2.0.1", - "typescript": "^3.3.3", - "write-file-atomic": "^2.4.2", + "ts-node": "^8.3.0", + "typescript": "^3.5.3", + "which": "^1.3.1", + "write-file-atomic": "^3.0.0", + "yaml": "^1.6.0", "yapool": "^1.0.0" - } - }, - "tap-mocha-reporter": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-3.0.9.tgz", - "integrity": "sha512-VO07vhC9EG27EZdOe7bWBj1ldbK+DL9TnRadOgdQmiQOVZjFpUEQuuqO7+rNSO2kfmkq5hWeluYXDWNG/ytXTQ==", - "dev": true, - "requires": { - "color-support": "^1.1.0", - "debug": "^2.1.3", - "diff": "^1.3.2", - "escape-string-regexp": "^1.0.3", - "glob": "^7.0.5", - "js-yaml": "^3.3.1", - "readable-stream": "^2.1.5", - "tap-parser": "^5.1.0", - "unicode-length": "^1.0.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "@babel/runtime": { + "version": "7.4.5", + "bundled": true, "dev": true, "requires": { - "ms": "2.0.0" + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.2", + "bundled": true, + "dev": true + } } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "@types/prop-types": { + "version": "15.7.1", + "bundled": true, "dev": true }, - "tap-parser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.4.0.tgz", - "integrity": "sha512-BIsIaGqv7uTQgTW1KLTMNPSEQf4zDDPgYOBRdgOfuB+JFOLRBfEu6cLa/KvMvmqggu1FKXDfitjLwsq4827RvA==", + "@types/react": { + "version": "16.8.22", + "bundled": true, "dev": true, "requires": { - "events-to-array": "^1.0.1", - "js-yaml": "^3.2.7", - "readable-stream": "^2" + "@types/prop-types": "*", + "csstype": "^2.2.0" } - } - } - }, - "tap-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-7.0.0.tgz", - "integrity": "sha512-05G8/LrzqOOFvZhhAk32wsGiPZ1lfUrl+iV7+OkKgfofZxiceZWMHkKmow71YsyVQ8IvGBP2EjcIjE5gL4l5lA==", - "dev": true, - "requires": { - "events-to-array": "^1.0.1", - "js-yaml": "^3.2.7", - "minipass": "^2.2.0" - } - }, - "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", - "dev": true, + }, + "ansi-escapes": { + "version": "3.2.0", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "auto-bind": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "@types/react": "^16.8.12" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + } + } + }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "esutils": "^2.0.2" + } + }, + "babel-helpers": { + "version": "6.24.1", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "bundled": true, + "dev": true + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" + } + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "bundled": true, + "dev": true, + "requires": { + "babel-helper-builder-react-jsx": "^6.24.1", + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-register": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "bundled": true, + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "caller-callsite": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cardinal": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "ci-info": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-truncate": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "slice-ansi": "^1.0.0", + "string-width": "^2.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "bundled": true, + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-js": { + "version": "2.6.5", + "bundled": true, + "dev": true + }, + "csstype": { + "version": "2.6.5", + "bundled": true, + "dev": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "bundled": true, + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esprima": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "events-to-array": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "home-or-tmp": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "import-jsx": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "babel-core": "^6.25.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-object-rest-spread": "^6.23.0", + "babel-plugin-transform-react-jsx": "^6.24.1", + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "ink": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "@types/react": "^16.8.6", + "arrify": "^1.0.1", + "auto-bind": "^2.0.0", + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "cli-truncate": "^1.1.0", + "is-ci": "^2.0.0", + "lodash.throttle": "^4.1.1", + "log-update": "^3.0.0", + "prop-types": "^15.6.2", + "react-reconciler": "^0.20.0", + "scheduler": "^0.13.2", + "signal-exit": "^3.0.2", + "slice-ansi": "^1.0.0", + "string-length": "^2.0.0", + "widest-line": "^2.0.0", + "wrap-ansi": "^5.0.0", + "yoga-layout-prebuilt": "^1.9.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } + } + }, + "invariant": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-ci": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "json5": { + "version": "0.5.1", + "bundled": true, + "dev": true + }, + "lodash": { + "version": "4.17.14", + "bundled": true, + "dev": true + }, + "lodash.throttle": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "log-update": { + "version": "3.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "cli-cursor": "^2.1.0", + "wrap-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } + } + }, + "loose-envify": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "onetime": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "private": { + "version": "0.1.8", + "bundled": true, + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "punycode": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "react": { + "version": "16.8.6", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.13.6" + } + }, + "react-is": { + "version": "16.8.6", + "bundled": true, + "dev": true + }, + "react-reconciler": { + "version": "0.20.4", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.13.6" + } + }, + "redeyed": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "scheduler": { + "version": "0.13.6", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slash": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "string-length": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "tap-parser": { + "version": "9.3.2", + "bundled": true, + "dev": true, + "requires": { + "events-to-array": "^1.0.1", + "minipass": "^2.2.0", + "tap-yaml": "^1.0.0" + } + }, + "tap-yaml": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "yaml": "^1.5.0" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "treport": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "cardinal": "^2.1.1", + "chalk": "^2.4.2", + "import-jsx": "^2.0.0", + "ink": "^2.1.1", + "ms": "^2.1.1", + "react": "^16.8.6", + "string-length": "^2.0.0", + "tap-parser": "^9.3.2", + "unicode-length": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ms": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "unicode-length": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "punycode": "^2.0.0", + "strip-ansi": "^3.0.1" + } + } + } + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "widest-line": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "yaml": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "@babel/runtime": "^7.4.5" + } + }, + "yoga-layout-prebuilt": { + "version": "1.9.3", + "bundled": true, + "dev": true + } + } + }, + "tap-mocha-reporter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-4.0.1.tgz", + "integrity": "sha512-/KfXaaYeSPn8qBi5Be8WSIP3iKV83s2uj2vzImJAXmjNu22kzqZ+1Dv1riYWa53sPCiyo1R1w1jbJrftF8SpcQ==", + "dev": true, + "requires": { + "color-support": "^1.1.0", + "debug": "^2.1.3", + "diff": "^1.3.2", + "escape-string-regexp": "^1.0.3", + "glob": "^7.0.5", + "readable-stream": "^2.1.5", + "tap-parser": "^8.0.0", + "tap-yaml": "0 || 1", + "unicode-length": "^1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "tap-parser": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-8.1.0.tgz", + "integrity": "sha512-GgOzgDwThYLxhVR83RbS1JUR1TxcT+jfZsrETgPAvFdr12lUOnuvrHOBaUQgpkAp6ZyeW6r2Nwd91t88M0ru3w==", + "dev": true, + "requires": { + "events-to-array": "^1.0.1", + "minipass": "^2.2.0", + "tap-yaml": "0 || 1" + } + }, + "tap-yaml": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-1.0.0.tgz", + "integrity": "sha512-Rxbx4EnrWkYk0/ztcm5u3/VznbyFJpyXO12dDBHKWiDVxy7O2Qw6MRrwO5H6Ww0U5YhRY/4C/VzWmFPhBQc4qQ==", + "dev": true, + "requires": { + "yaml": "^1.5.0" + } + }, + "tcompare": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-2.3.0.tgz", + "integrity": "sha512-fAfA73uFtFGybWGt4+IYT6UPLYVZQ4NfsP+IXEZGY0vh8e2IF7LVKafcQNMRBLqP0wzEA65LM9Tqj+FSmO8GLw==", + "dev": true + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, "requires": { "glob": "^7.1.3", "minimatch": "^3.0.4", @@ -1810,18 +3105,21 @@ "require-main-filename": "^2.0.0" } }, - "tmatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-4.0.0.tgz", - "integrity": "sha512-Ynn2Gsp+oCvYScQXeV+cCs7citRDilq0qDXA6tuvFwDgiYyyaq7D5vKUlAPezzZR5NDobc/QMeN6e5guOYmvxg==", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -1863,22 +3161,8 @@ "make-error": "^1.1.1", "source-map-support": "^0.5.6", "yn": "^3.0.0" - }, - "dependencies": { - "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", - "dev": true - } } }, - "tsame": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tsame/-/tsame-2.0.1.tgz", - "integrity": "sha512-jxyxgKVKa4Bh5dPcO42TJL22lIvfd9LOVJwdovKOnJa4TLLrHxquK+DlGm4rkGmrcur+GRx+x4oW00O2pY/fFw==", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1894,6 +3178,15 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", @@ -1909,15 +3202,6 @@ "requires": { "commander": "~2.20.0", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } } }, "unicode-length": { @@ -1996,6 +3280,12 @@ "extsprintf": "^1.2.0" } }, + "vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -2018,14 +3308,50 @@ "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "wrappy": { @@ -2035,14 +3361,15 @@ "dev": true }, "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.0.tgz", + "integrity": "sha512-EIgkf60l2oWsffja2Sf2AL384dx328c0B+cIYPTQq5q2rOYuDV00/iPFBOUiDKKwKMOhkymH8AidPaRvzfxY+Q==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, "y18n": { @@ -2057,6 +3384,15 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, + "yaml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.6.0.tgz", + "integrity": "sha512-iZfse3lwrJRoSlfs/9KQ9iIXxs9++RvBFVzAqbbBiFT+giYtyanevreF9r61ZTbGMgWQBxAua3FzJiniiJXWWw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.5" + } + }, "yapool": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/yapool/-/yapool-1.0.0.tgz", @@ -2079,6 +3415,56 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^13.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } } }, "yargs-parser": { diff --git a/package.json b/package.json index ebf1efc..d28f686 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "preversion": "npm test", "postversion": "npm publish", "postpublish": "git push origin --follow-tags", - "test": "node test.js | tap -" + "pretest": "rimraf test/temp-files-*", + "test": "nyc -r none node test.js | tap -", + "posttest": "nyc report -r text --check-coverage" }, "keywords": [ "fs", @@ -34,17 +36,29 @@ ], "license": "ISC", "devDependencies": { - "import-fresh": "^2.0.0", - "mkdirp": "^0.5.0", - "rimraf": "^2.2.8", - "tap": "^12.7.0" + "glob": "^7.1.4", + "import-fresh": "^3.1.0", + "nyc": "^14.1.1", + "rimraf": "^2.6.3", + "tap": "^14.6.1" + }, + "engines": { + "node": ">=8" }, "files": [ - "fs.js", + "check-for-callback.js", + "chown-er-filter.js", + "clone.js", "graceful-fs.js", - "legacy-streams.js", + "lutimes-polyfill.js", + "noop.js", + "normalize-args.js", "polyfills.js", - "clone.js" + "promises.js", + "promise-windows-rename-polyfill.js", + "readdir-sort.js", + "retry-queue.js", + "windows-rename-polyfill.js" ], "dependencies": {} } diff --git a/polyfills.js b/polyfills.js index a5808d2..dd15ba9 100644 --- a/polyfills.js +++ b/polyfills.js @@ -1,40 +1,47 @@ -var constants = require('constants') +'use strict' -var origCwd = process.cwd -var cwd = null +const normalizeArgs = require('./normalize-args.js') +const {noop, noopSync} = require('./noop.js') +const chownErFilter = require('./chown-er-filter.js') -var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform +function patchProcess () { + if (/graceful-fs replacement/.test(process.cwd.toString())) { + // Don't patch more than once + return + } -process.cwd = function() { - if (!cwd) - cwd = origCwd.call(process) - return cwd -} -try { - process.cwd() -} catch (er) {} + const {cwd, chdir} = process + let pwd = null + + process.cwd = () => { + /* graceful-fs replacement */ + if (pwd === null) { + pwd = cwd() + } + + return pwd + } + + process.chdir = dir => { + pwd = null + chdir(dir) + } -var chdir = process.chdir -process.chdir = function(d) { - cwd = null - chdir.call(process, d) + try { + process.cwd() + } catch (er) {} } +patchProcess() + module.exports = patch function patch (fs) { // (re-)implement some things that are known busted or missing. - // lchmod, broken prior to 0.6.2 - // back-port the fix here. - if (constants.hasOwnProperty('O_SYMLINK') && - process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { - patchLchmod(fs) - } - // lutimes implementation, or no-op if (!fs.lutimes) { - patchLutimes(fs) + require('./lutimes-polyfill.js')(fs) } // https://github.com/isaacs/node-graceful-fs/issues/4 @@ -42,42 +49,33 @@ function patch (fs) { // It should not fail on enosys ever, as this just indicates // that a fs doesn't support the intended operation. - fs.chown = chownFix(fs.chown) - fs.fchown = chownFix(fs.fchown) - fs.lchown = chownFix(fs.lchown) - - fs.chmod = chmodFix(fs.chmod) - fs.fchmod = chmodFix(fs.fchmod) - fs.lchmod = chmodFix(fs.lchmod) + fs.chown = patchChownErFilter(fs.chown) + fs.fchown = patchChownErFilter(fs.fchown) + fs.lchown = patchChownErFilter(fs.lchown) - fs.chownSync = chownFixSync(fs.chownSync) - fs.fchownSync = chownFixSync(fs.fchownSync) - fs.lchownSync = chownFixSync(fs.lchownSync) + fs.chmod = patchChownErFilter(fs.chmod) + fs.fchmod = patchChownErFilter(fs.fchmod) + fs.lchmod = patchChownErFilter(fs.lchmod) - fs.chmodSync = chmodFixSync(fs.chmodSync) - fs.fchmodSync = chmodFixSync(fs.fchmodSync) - fs.lchmodSync = chmodFixSync(fs.lchmodSync) + fs.chownSync = patchChownSyncErFilter(fs.chownSync) + fs.fchownSync = patchChownSyncErFilter(fs.fchownSync) + fs.lchownSync = patchChownSyncErFilter(fs.lchownSync) - fs.stat = statFix(fs.stat) - fs.fstat = statFix(fs.fstat) - fs.lstat = statFix(fs.lstat) - - fs.statSync = statFixSync(fs.statSync) - fs.fstatSync = statFixSync(fs.fstatSync) - fs.lstatSync = statFixSync(fs.lstatSync) + fs.chmodSync = patchChownSyncErFilter(fs.chmodSync) + fs.fchmodSync = patchChownSyncErFilter(fs.fchmodSync) + fs.lchmodSync = patchChownSyncErFilter(fs.lchmodSync) // if lchmod/lchown do not exist, then make them no-ops + /* istanbul ignore next */ if (!fs.lchmod) { - fs.lchmod = function (path, mode, cb) { - if (cb) process.nextTick(cb) - } - fs.lchmodSync = function () {} + fs.lchmod = noop + fs.lchmodSync = noopSync } + + /* istanbul ignore next */ if (!fs.lchown) { - fs.lchown = function (path, uid, gid, cb) { - if (cb) process.nextTick(cb) - } - fs.lchownSync = function () {} + fs.lchown = noop + fs.lchownSync = noopSync } // on Windows, A/V software can lock the directory, causing this @@ -89,254 +87,72 @@ function patch (fs) { // 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. - if (platform === "win32") { - fs.rename = (function (fs$rename) { return function (from, to, cb) { - var start = Date.now() - var backoff = 0; - fs$rename(from, to, function CB (er) { - if (er - && (er.code === "EACCES" || er.code === "EPERM") - && Date.now() - start < 60000) { - setTimeout(function() { - fs.stat(to, function (stater, st) { - if (stater && stater.code === "ENOENT") - fs$rename(from, to, CB); - else - cb(er) - }) - }, backoff) - if (backoff < 100) - backoff += 10; - return; - } - if (cb) cb(er) - }) - }})(fs.rename) + /* istanbul ignore next */ + if (process.platform === 'win32') { + require('./windows-rename-polyfill.js')(fs) } + const {read, readSync} = fs // if read() returns EAGAIN, then just try it again. - fs.read = (function (fs$read) { - function read (fd, buffer, offset, length, position, callback_) { - var callback - if (callback_ && typeof callback_ === 'function') { - var eagCounter = 0 - callback = function (er, _, __) { - if (er && er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } - callback_.apply(this, arguments) - } + fs.read = (fd, buffer, offset, length, position, cb) => { + cb = normalizeArgs([cb])[1] + + let eagCounter = 0 + read(fd, buffer, offset, length, position, function CB (er, ...args) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + read(fd, buffer, offset, length, position, CB) + return } - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } - // This ensures `util.promisify` works as it does for native `fs.read`. - read.__proto__ = fs$read - return read - })(fs.read) + cb(er, ...args) + }) + } + // This ensures `util.promisify` works as it does for native `fs.read`. + Object.setPrototypeOf(fs.read, read) - fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { - var eagCounter = 0 + fs.readSync = (...args) => { + let eagCounter = 0 while (true) { try { - return fs$readSync.call(fs, fd, buffer, offset, length, position) + return readSync(...args) } catch (er) { if (er.code === 'EAGAIN' && eagCounter < 10) { eagCounter ++ continue } - throw er - } - } - }})(fs.readSync) - - function patchLchmod (fs) { - fs.lchmod = function (path, mode, callback) { - fs.open( path - , constants.O_WRONLY | constants.O_SYMLINK - , mode - , function (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. - fs.fchmod(fd, mode, function (err) { - fs.close(fd, function(err2) { - if (callback) callback(err || err2) - }) - }) - }) - } - - fs.lchmodSync = function (path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) - - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - var threw = true - var ret - try { - ret = fs.fchmodSync(fd, mode) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret - } - } - function patchLutimes (fs) { - if (constants.hasOwnProperty("O_SYMLINK")) { - fs.lutimes = function (path, at, mt, cb) { - fs.open(path, constants.O_SYMLINK, function (er, fd) { - if (er) { - if (cb) cb(er) - return - } - fs.futimes(fd, at, mt, function (er) { - fs.close(fd, function (er2) { - if (cb) cb(er || er2) - }) - }) - }) - } - - fs.lutimesSync = function (path, at, mt) { - var fd = fs.openSync(path, constants.O_SYMLINK) - var ret - var threw = true - try { - ret = fs.futimesSync(fd, at, mt) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret + throw er } - - } else { - fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } - fs.lutimesSync = function () {} } } - function chmodFix (orig) { - if (!orig) return orig - return function (target, mode, cb) { - return orig.call(fs, target, mode, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) + function patchChownErFilter (orig) { + /* istanbul ignore if */ + if (!orig) { + return orig } - } - function chmodFixSync (orig) { - if (!orig) return orig - return function (target, mode) { - try { - return orig.call(fs, target, mode) - } catch (er) { - if (!chownErOk(er)) throw er - } + return (...userArgs) => { + const [args, cb] = normalizeArgs(userArgs) + return orig(...args, (er, ...cbArgs) => cb(chownErFilter(er), ...cbArgs)) } } - - function chownFix (orig) { - if (!orig) return orig - return function (target, uid, gid, cb) { - return orig.call(fs, target, uid, gid, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) + function patchChownSyncErFilter (orig) { + /* istanbul ignore if */ + if (!orig) { + return orig } - } - function chownFixSync (orig) { - if (!orig) return orig - return function (target, uid, gid) { + return (...args) => { try { - return orig.call(fs, target, uid, gid) + return orig(...args) } catch (er) { - if (!chownErOk(er)) throw er - } - } - } - - function statFix (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, options, cb) { - if (typeof options === 'function') { - cb = options - options = null - } - function callback (er, stats) { - if (stats) { - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 + if (chownErFilter(er)) { + throw er } - if (cb) cb.apply(this, arguments) } - return options ? orig.call(fs, target, options, callback) - : orig.call(fs, target, callback) - } - } - - function statFixSync (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, options) { - var stats = options ? orig.call(fs, target, options) - : orig.call(fs, target) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - return stats; } } - - // 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. - function chownErOk (er) { - if (!er) - return true - - if (er.code === "ENOSYS") - return true - - var nonroot = !process.getuid || process.getuid() !== 0 - if (nonroot) { - if (er.code === "EINVAL" || er.code === "EPERM") - return true - } - - return false - } } diff --git a/promise-windows-rename-polyfill.js b/promise-windows-rename-polyfill.js new file mode 100644 index 0000000..fa1a8d6 --- /dev/null +++ b/promise-windows-rename-polyfill.js @@ -0,0 +1,46 @@ +'use strict' + +const {promisify} = require('util') + +const accessErrors = new Set(['EACCES', 'EPERM']) + +const delay = promisify(setTimeout) + +function promiseWindowsRenamePolyfill (fs) { + const {rename} = fs + + fs.rename = async (from, to) => { + const start = Date.now() + let backoff = 0 + + while (true) { + try { + return await rename(from, to) + } catch (er) { + if (!accessErrors.has(er.code) || Date.now() - start >= 60000) { + throw er + } + + await delay(backoff) + + // The only way this `await` resolves is if fs.stat throws ENOENT. + await fs.stat(to).then( + () => { + throw er + }, + stater => { + if (stater.code !== 'ENOENT') { + throw er + } + } + ) + + if (backoff < 100) { + backoff += 10 + } + } + } + } +} + +module.exports = promiseWindowsRenamePolyfill diff --git a/promises.js b/promises.js new file mode 100644 index 0000000..96e4fe9 --- /dev/null +++ b/promises.js @@ -0,0 +1,162 @@ +'use strict' + +const clone = require('./clone.js') +const chownErFilter = require('./chown-er-filter.js') +const {retry, enqueue} = require('./retry-queue.js') +const readdirSort = require('./readdir-sort.js') + +function patchChown (orig) { + return (...args) => orig(...args).catch(er => { + if (chownErFilter(er)) { + throw er + } + }) +} + +function patchAsyncENFILE (origImpl, next = res => res) { + const attempt = (args, resolve, reject) => { + origImpl(...args) + .then(res => { + process.nextTick(retry) + resolve(next(res)) + }) + .catch(err => { + if (err.code === 'EMFILE' || err.code === 'ENFILE') { + enqueue([attempt, [args, resolve, reject]]) + } else { + process.nextTick(retry) + reject(err) + } + }) + } + + return (...args) => new Promise((resolve, reject) => attempt(args, resolve, reject)) +} + +async function patchFileHandleClose (promises) { + const filehandle = await promises.open(__filename, 'r') + const Klass = Object.getPrototypeOf(filehandle) + await filehandle.close() + + const {close} = Klass + if (/graceful-fs replacement/.test(close.toString())) { + return + } + + Klass.close = async function (...args) { + /* graceful-fs replacement */ + await close.apply(this, args) + retry() + } + + // Just in case a promises FileHandle closed before we could monkey-patch it + retry() +} + +function initPromises (orig) { + // Checking `orig.value` handles situations where a user does + // something like require('graceful-fs).gracefulify({...require('fs')}) + const origPromises = orig.value || orig.get() + patchFileHandleClose(origPromises).catch( + /* istanbul ignore next: this should never happen */ + console.error + ) + + const promises = clone(origPromises) + promises.open = patchAsyncENFILE(promises.open, filehandle => { + /* It's too bad node.js makes it impossible to extend the + * actual filehandle class. */ + const replacementFns = { + async chmod (...args) { + try { + await filehandle.chmod(...args) + } catch (er) { + if (chownErFilter(er)) { + throw er + } + } + }, + async chown (...args) { + try { + await filehandle.chown(...args) + } catch (er) { + if (chownErFilter(er)) { + throw er + } + } + }, + async read (...args) { + let eagCounter = 0 + while (true) { + try { + return await filehandle.read(...args) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + + throw er + } + } + } + } + + return new Proxy(filehandle, { + get (filehandle, prop) { + if (!(prop in replacementFns)) { + const original = filehandle[prop] + if (typeof original !== 'function') { + return original + } + + replacementFns[prop] = original.bind(filehandle) + } + + return replacementFns[prop] + } + }) + }) + + promises.readFile = patchAsyncENFILE(promises.readFile) + promises.writeFile = patchAsyncENFILE(promises.writeFile) + promises.appendFile = patchAsyncENFILE(promises.appendFile) + promises.readdir = patchAsyncENFILE(promises.readdir, readdirSort) + + promises.chmod = patchChown(promises.chmod) + promises.lchmod = patchChown(promises.lchmod) + promises.chown = patchChown(promises.chown) + promises.lchown = patchChown(promises.lchown) + + /* istanbul ignore next */ + if (process.platform === 'win32') { + require('./promise-windows-rename-polyfill.js')(promises) + } + + return promises +} + +function patchPromises (fs, orig) { + let promises + /* istanbul ignore next: ignoring the version specific branch, initPromises is covered */ + if (orig.enumerable) { + // If enumerable is enabled fs.promises is not experimental, no warning + promises = initPromises(orig) + } + + Object.defineProperty(fs, 'promises', { + // enumerable is true in node.js 11+ where fs.promises is stable + enumerable: orig.enumerable, + configurable: true, + get () { + /* istanbul ignore next: ignoring the version specific branch, initPromises is covered */ + if (!promises) { + promises = initPromises(orig) + } + + return promises + } + }) +} + +module.exports = patchPromises diff --git a/readdir-sort.js b/readdir-sort.js new file mode 100644 index 0000000..90a171d --- /dev/null +++ b/readdir-sort.js @@ -0,0 +1,19 @@ +'use strict' + +const {Dirent} = require('fs') + +module.exports = files => { + if (!files || files.length === 0) { + return files + } + + if (typeof files[0] === 'object' && files[0].constructor === Dirent) { + if (typeof files[0].name === 'string') { + return files.sort((a, b) => a.name.localeCompare(b.name)) + } + + return files.sort((a, b) => a.name.toString().localeCompare(b.name.toString())) + } + + return files.sort() +} diff --git a/retry-queue.js b/retry-queue.js new file mode 100644 index 0000000..c495e5d --- /dev/null +++ b/retry-queue.js @@ -0,0 +1,92 @@ +'use strict' + +const fs = require('fs') +const util = require('util') +const normalizeArgs = require('./normalize-args.js') + +const debug = util.debuglog('gfs4') + +const gracefulQueue = Symbol.for('graceful-fs.queue') +const gracefulResetQueue = Symbol.for('graceful-fs.reset-queue') + +// Once time initialization +function initQueue () { + if (!global[gracefulQueue] || global[gracefulResetQueue]) { + delete global[gracefulResetQueue] + + /* istanbul ignore next: nyc already created this variable, this is untestable */ + if (!global[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + const queue = [] + Object.defineProperty(global, gracefulQueue, { + get () { + return queue + } + }) + } + + const previous = Symbol.for('graceful-fs.previous') + /* istanbul ignore else: this is always true when running under nyc */ + if (fs.close[previous]) { + fs.close = fs.close[previous] + } + + /* istanbul ignore else: this is always true when running under nyc */ + if (fs.closeSync[previous]) { + fs.closeSync = fs.closeSync[previous] + } + + // 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 graceful-fs instances are + // in play at the same time. + const {close, closeSync} = fs + fs.close = (fd, cb) => { + cb = normalizeArgs([cb])[1] + + close(fd, err => { + // This function uses the graceful-fs shared queue + if (!err) { + retry() + } + + cb(err) + }) + } + Object.defineProperty(fs.close, previous, { + value: close + }) + + fs.closeSync = fd => { + // This function uses the graceful-fs shared queue + closeSync(fd) + retry() + } + Object.defineProperty(fs.closeSync, previous, { + value: closeSync + }) + + /* istanbul ignore next */ + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', () => { + debug(global[gracefulQueue]) + require('assert').strictEqual(global[gracefulQueue].length, 0) + }) + } + } +} + +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + global[gracefulQueue].push(elem) +} + +function retry () { + const elem = global[gracefulQueue].shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0](...elem[1]) + } +} + +module.exports = {initQueue, enqueue, retry} diff --git a/test.js b/test.js index 54da6b5..05d2e02 100644 --- a/test.js +++ b/test.js @@ -1,24 +1,19 @@ -var fs = require('fs') -var tap = require('tap') -var dir = __dirname + '/test' -var node = process.execPath +'use strict' -var files = fs.readdirSync(dir) -var env = Object.keys(process.env).reduce(function (env, k) { - env[k] = process.env[k] - return env -}, { - TEST_GRACEFUL_FS_GLOBAL_PATCH: 1 -}) +const path = require('path') +const tap = require('tap') +const glob = require('glob') -files.filter(function (f) { - if (/\.js$/.test(f) && fs.statSync(dir + '/' + f).isFile()) { - // expose-gc is so we can check for memory leaks - tap.spawn(node, ['--expose-gc', 'test/' + f]) - return true - } -}).forEach(function (f) { - tap.spawn(node, ['--expose-gc', 'test/' + f], { - env: env - }, '🐵 test/' + f) -}) +const node = process.execPath +const env = { + ...process.env, + TEST_GFS_GLOBAL_PATCH: '1' +} +const files = glob.sync('*.js', {cwd: path.join(__dirname, 'test')}) + .map(f => path.join('test', f)) + +for (const f of files) { + const args = ['--no-warnings', '--expose-gc', f] + tap.spawn(node, args, {}, f) + tap.spawn(node, args, {env}, `${f} [🐵]`) +} diff --git a/test/avoid-memory-leak.js b/test/avoid-memory-leak.js index d30392f..c1bd789 100644 --- a/test/avoid-memory-leak.js +++ b/test/avoid-memory-leak.js @@ -1,41 +1,73 @@ -var importFresh = require('import-fresh'); -var t = require('tap') -var v8 -try { - v8 = require('v8') -} catch (er) {} +'use strict' -var previousHeapStats +const path = require('path') +const v8 = require('v8') +const importFresh = require('import-fresh') +const t = require('tap') +const {promisify} = require('util') + +const delay = promisify(setTimeout) +let previousHeapStats function checkHeap (t) { - var v8stats = v8 ? v8.getHeapStatistics() : {} - var stats = process.memoryUsage() - if (typeof v8stats.number_of_detached_contexts === 'number') + const v8stats = v8.getHeapStatistics() + const stats = process.memoryUsage() + if (typeof v8stats.number_of_detached_contexts === 'number') { t.equal(v8stats.number_of_detached_contexts, 0, 'no detached contexts') - else { - const memoryUsage = stats.heapUsed - previousHeapStats.heapUsed - const memoryUsageMB = Math.round(memoryUsage / Math.pow(1024, 2)) - t.ok(memoryUsageMB < 2, 'expect less than 2MB difference, ' - + memoryUsageMB + 'MB difference found.'); } + + const memoryUsage = stats.heapUsed - previousHeapStats.heapUsed + const memoryUsageKB = Math.round(memoryUsage / 1024) + t.ok( + memoryUsageKB < 2048, + `expect less than 2048KB difference, ${memoryUsageKB}KB difference found.` + ) } -t.test('no memory leak when loading multiple times', function(t) { - t.plan(1); - importFresh(process.cwd() + '/graceful-fs.js') // node 0.10-5 were getting: Cannot find module '../' +async function gfsinit () { + const gfsPath = path.resolve(__dirname, '../graceful-fs.js') + const gfsHelper = path.join(__dirname, './helpers/graceful-fs.js') + + delete require.cache[gfsPath] + const fs = importFresh(gfsHelper) + // Force initialization of `fs.promises` if available + if (fs.promises) { + // fs.promises.open has an async initialization, this ensures it's fully loaded + const handle = await fs.promises.open(__filename, 'r') + await handle.close() + } else { + // For node.js 8 + await delay(1) + } +} + +t.test('no memory leak when loading multiple times', async t => { + t.ok(true, 'tap measures the time between first and last test') + await gfsinit() + global.gc() + previousHeapStats = process.memoryUsage() + // simulate project with 4000 tests - var i = 0; - function importFreshGracefulFs() { - importFresh(process.cwd() + '/graceful-fs.js'); - if (i < 4000) { - i++; - process.nextTick(() => importFreshGracefulFs()); - } else { - global.gc() - checkHeap(t); - t.end(); - } + for (let i = 0; i < 4000; i++) { + await gfsinit() } - importFreshGracefulFs(); + + // Two gc cycles seems to help with node.js 8 + global.gc() + global.gc() + + checkHeap(t) +}) + +t.test('process is not repeatedly patched', t => { + const polyfills = path.resolve(__dirname, '../polyfills.js') + importFresh(polyfills) + + const {cwd, chdir} = process + + importFresh(polyfills) + t.is(cwd, process.cwd, 'process.cwd not repeatedly patched') + t.is(chdir, process.chdir, 'process.chdir not repeatedly patched') + t.end() }) diff --git a/test/chdir.js b/test/chdir.js new file mode 100644 index 0000000..a1e97d9 --- /dev/null +++ b/test/chdir.js @@ -0,0 +1,55 @@ +'use strict' + +const path = require('path') +const t = require('tap') + +const {chdir, cwd} = process +const hits = { + chdir: 0, + cwd: 0 +} + +/* nyc has already caused graceful-fs to be loaded from node_modules. + * If that version of graceful-fs contains the multiple load protection + * it will prevent our polyfills.js from replacing process.chdir and + * process.cwd. Replacing these functions cause our polyfills.js to + * still perform process function replacements. */ +process.chdir = dir => { + hits.chdir++ + chdir(dir) +} + +process.cwd = () => { + hits.cwd++ + return cwd() +} + +// Side-effect only to force replacement of process.chdir / process.cwd +require('./helpers/graceful-fs.js') + +const project = path.resolve(__dirname, '..') + +// Ignore any calls that happen during initialization +hits.chdir = 0 +hits.cwd = 0 + +process.chdir(__dirname) +t.is(hits.chdir, 1, 'our chdir was called') +t.is(hits.cwd, 0, 'no calls to cwd') + +t.is(process.cwd(), __dirname, 'chdir(__dirname) worked') +t.is(hits.cwd, 1, 'our cwd was called') + +t.is(process.cwd(), __dirname, 'cwd twice in a row') +t.is(hits.cwd, 1, 'repeat calls to cwd use cached value') +t.is(hits.chdir, 1, 'no unexpected calls to chdir') + +process.chdir(project) +t.is(hits.chdir, 2, 'our chdir was called') +t.is(hits.cwd, 1, 'no unexpected calls to cwd') + +t.is(process.cwd(), project, 'chdir(project) worked') +t.is(hits.cwd, 2) +t.is(hits.chdir, 2, 'no unexpected calls to chdir') + +t.end() diff --git a/test/chown-er-ok.js b/test/chown-er-ok.js index aad7815..a89363c 100644 --- a/test/chown-er-ok.js +++ b/test/chown-er-ok.js @@ -1,53 +1,97 @@ -var realFs = require('fs') +'use strict' -var methods = ['chown', 'chownSync', 'chmod', 'chmodSync'] -methods.forEach(function (method) { - causeErr(method, realFs[method]) -}) +const realFs = require('fs') +const {promisify} = require('util') + +const realPromises = realFs.promises +// This test depends on chown-er-filter.js not seeing us as root. +process.getuid = () => 1000 + +// For the fchown / fchmod do not accept `path` as the first parameter but +// gfs doesn't duplicate this check so we can still verify that the errors +// are ignored without added complexity in this test +const methods = ['chown', 'fchown', 'lchown', 'chmod', 'fchmod', 'lchmod'] +methods.forEach(method => { + realFs[`${method}Sync`] = path => { + throw makeErr(path, `${method}Sync`) + } -function causeErr (method, original) { - realFs[method] = function (path) { - var err = makeErr(path, method) - if (!/Sync$/.test(method)) { - var cb = arguments[arguments.length - 1] - process.nextTick(cb.bind(null, err)) - } else { - throw err + realFs[method] = (path, ...args) => { + const cb = args.pop() + const err = makeErr(path, method) + process.nextTick(() => cb(err)) + } + + if (realFs.promises && !method.startsWith('f')) { + realFs.promises[method] = async path => { + throw makeErr(path, method) } } -} +}) function makeErr (path, method) { - var err = new Error('this is fine') - err.syscall = method.replace(/Sync$/, '') - err.code = path.toUpperCase() - return err + return Object.assign(new Error('this is fine'), { + syscall: method.replace(/Sync$/, ''), + code: path.toUpperCase() + }) } -var fs = require('../') -var t = require('tap') +const fs = require('./helpers/graceful-fs.js') +const {test} = require('tap') -var errs = ['ENOSYS', 'EINVAL', 'EPERM'] -t.plan(errs.length * methods.length) +const errs = ['ENOSYS', 'EINVAL', 'EPERM'] -errs.forEach(function (err) { - methods.forEach(function (method) { - var args = [err] - if (/chmod/.test(method)) { - args.push('some mode') - } else { - args.push('some uid', 'some gid') - } +async function helper (t, err, method) { + const args = [err] + if (/chmod/.test(method)) { + args.push('some mode') + } else { + args.push('some uid', 'some gid') + } - if (method.match(/Sync$/)) { - t.doesNotThrow(function () { - fs[method].apply(fs, args) - }) - } else { - args.push(function (err) { - t.notOk(err) - }) - fs[method].apply(fs, args) - } + t.doesNotThrow(() => fs[`${method}Sync`](...args), `${method}Sync does not throw ${err}`) + await promisify(fs[method])(...args) + if (fs.promises && !method.startsWith('f')) { + await fs.promises[method](...args) + } +} + +errs.forEach(err => { + methods.forEach(method => { + test(`${method} ${err}`, t => helper(t, err, method)) }) }) + +if (fs.promises) { + test('setup for promises', async () => { + const filehandle = await realPromises.open(__filename, 'r') + const PromisesFileHandle = Object.getPrototypeOf(filehandle) + ;['chmod', 'chown'].forEach(method => { + PromisesFileHandle[method] = async path => { + throw makeErr(path, method) + } + }) + await filehandle.close() + }) + + test('FileHandle.chown / FileHandle.chmod', async t => { + const filehandle = await fs.promises.open(__filename, 'r') + + for (const err of errs) { + await filehandle.chmod(err, 'some mode') + await filehandle.chown(err, 'some uid', 'some gid') + } + + await filehandle.close() + + await t.rejects( + filehandle.chmod('EBADF', 'some mode'), + {code: 'EBADF'} + ) + + await t.rejects( + filehandle.chown('EBADF', 'some uid', 'some gid'), + {code: 'EBADF'} + ) + }) +} diff --git a/test/close.js b/test/close.js index dd1f002..f0315f7 100644 --- a/test/close.js +++ b/test/close.js @@ -1,10 +1,36 @@ -var fs$close = require('fs').close; -var fs$closeSync = require('fs').closeSync; -var fs = require('../'); -var test = require('tap').test - -test('`close` is patched correctly', function(t) { - t.notEqual(fs.close, fs$close, 'patch close'); - t.notEqual(fs.closeSync, fs$closeSync, 'patch closeSync'); - t.end(); +'use strict' + +const fs = require('fs') +const path = require('path') +const importFresh = require('import-fresh') +const gfs = require('./helpers/graceful-fs.js') +const {test} = require('tap') + +const {close, closeSync} = fs +const gfsPath = path.resolve(__dirname, '..', 'graceful-fs.js') + +test('`close` is patched correctly', t => { + t.match(close.toString(), /graceful-fs shared queue/, 'patch fs.close') + t.match(closeSync.toString(), /graceful-fs shared queue/, 'patch fs.closeSync') + t.match(gfs.close.toString(), /graceful-fs shared queue/, 'patch gfs.close') + t.match(gfs.closeSync.toString(), /graceful-fs shared queue/, 'patch gfs.closeSync') + + const newGFS = importFresh(gfsPath) + t.equal(fs.close, close) + t.equal(fs.closeSync, closeSync) + t.equal(newGFS.close, close) + t.equal(newGFS.closeSync, closeSync) + t.end() +}) + +test('close error', t => { + /* Open and close an fd to test fs.close / fs.closeSync errors */ + const fd = fs.openSync(__filename, 'r') + gfs.closeSync(fd) + + t.throws(() => gfs.closeSync(fd), {code: 'EBADF'}) + gfs.close(fd, err => { + t.ok(err && err.code === 'EBADF') + t.end() + }) }) diff --git a/test/eagain.js b/test/eagain.js new file mode 100644 index 0000000..95005b0 --- /dev/null +++ b/test/eagain.js @@ -0,0 +1,125 @@ +'use strict' + +const {test} = require('tap') + +const eagain = () => Object.assign(new Error('EAGAIN'), {code: 'EAGAIN'}) + +let readPing +let readSyncPing +let cbArgs = [] + +const realPromises = require('fs').promises + +const gfs = require('../graceful-fs.js') +// We can't hijack the actual `fs` module so we have to fake it +const fs = gfs.gracefulify({ + ...require('fs'), + read (...args) { + const cb = args.slice(-1)[0] + readPing(args) + + cb(...cbArgs) + }, + readSync (...args) { + return readSyncPing(...args) + } +}) + +test('read unresolved EAGAIN', t => { + let counter = 0 + readPing = () => { + counter++ + } + cbArgs = [eagain()] + + fs.read(null, null, 0, 0, 0, err => { + t.ok(err && err.code === 'EAGAIN', 'unresolved eagain') + t.is(counter, 11) + t.end() + }) +}) + +test('read EAGAIN loop', t => { + let counter = 0 + + cbArgs = [eagain()] + readPing = () => { + counter++ + if (counter === 5) { + cbArgs = [null, 'success'] + } + } + + fs.read(null, null, 0, 0, 0, (err, msg) => { + t.is(counter, 5, 'retried 5 times') + t.notOk(err) + t.is(msg, 'success', 'resolved after retries') + t.end() + }) +}) + +test('readSync unresolved EAGAIN', t => { + let counter = 0 + readSyncPing = () => { + counter++ + throw eagain() + } + + t.throws(() => fs.readSync(), {code: 'EAGAIN'}) + t.is(counter, 11) + t.end() +}) + +test('readSync unresolved EAGAIN', t => { + let counter = 0 + readSyncPing = () => { + counter++ + if (counter !== 5) { + throw eagain() + } + + return 'success' + } + + t.is(fs.readSync(), 'success') + t.is(counter, 5) + t.end() +}) + +if (gfs.promises) { + let PromisesFileHandle + test('find PromisesFileHandle', async () => { + const filehandle = await realPromises.open(__filename, 'r') + PromisesFileHandle = Object.getPrototypeOf(filehandle) + await filehandle.close() + }) + + test('promises read', async t => { + t.ok(true) + const filehandle = await gfs.promises.open(__filename, 'r') + let counter = 0 + PromisesFileHandle.read = async () => { + counter++ + throw eagain() + } + + await t.rejects( + filehandle.read(null, 0, 0, 0), + {code: 'EAGAIN'}, + 'unresolve eagain' + ) + t.is(counter, 11) + + counter = 0 + PromisesFileHandle.read = async () => { + counter++ + if (counter !== 5) { + throw eagain() + } + } + + await filehandle.read(null, 0, 0, 0) + t.is(counter, 5, 'retried 5 times') + await filehandle.close() + }) +} diff --git a/test/enoent.js b/test/enoent.js index 98d5c1d..9358dbd 100644 --- a/test/enoent.js +++ b/test/enoent.js @@ -1,77 +1,40 @@ +'use strict' + // this test makes sure that various things get enoent, instead of // some other kind of throw. -var g = require('../') - -var NODE_VERSION_MAJOR_WITH_BIGINT = 10 -var NODE_VERSION_MINOR_WITH_BIGINT = 5 -var NODE_VERSION_PATCH_WITH_BIGINT = 0 -var nodeVersion = process.versions.node.split('.') -var nodeVersionMajor = Number.parseInt(nodeVersion[0], 10) -var nodeVersionMinor = Number.parseInt(nodeVersion[1], 10) -var nodeVersionPatch = Number.parseInt(nodeVersion[2], 10) - -function nodeSupportsBigInt () { - if (nodeVersionMajor > NODE_VERSION_MAJOR_WITH_BIGINT) { - return true - } else if (nodeVersionMajor === NODE_VERSION_MAJOR_WITH_BIGINT) { - if (nodeVersionMinor > NODE_VERSION_MINOR_WITH_BIGINT) { - return true - } else if (nodeVersionMinor === NODE_VERSION_MINOR_WITH_BIGINT) { - if (nodeVersionPatch >= NODE_VERSION_PATCH_WITH_BIGINT) { - return true - } - } - } - return false -} +const {promisify} = require('util') +const g = require('./helpers/graceful-fs.js') +const t = require('tap') -var t = require('tap') -var file = 'this file does not exist even a little bit' -var methods = [ +const file = 'this file does not exist even a little bit' +const methods = [ ['open', 'r'], ['readFile'], - ['stat'], - ['lstat'], ['utimes', new Date(), new Date()], - ['readdir'] + ['readdir'], + ['readdir', {}], + ['chmod', 0] ] -// any version > v6 can do readdir(path, options, cb) -if (process.version.match(/^v([6-9]|[1-9][0-9])\./)) { - methods.push(['readdir', {}]) -} - -// any version > v10.5 can do stat(path, options, cb) -if (nodeSupportsBigInt()) { - methods.push(['stat', {}]) - methods.push(['lstat', {}]) +if (process.platform !== 'win32') { + // fs.chown does nothing on win32 (doesn't even check if the file exists) + methods.push(['chown', 0, 0]) } - t.plan(methods.length) -methods.forEach(function (method) { - t.test(method[0], runTest(method)) -}) +methods.forEach(([method, ...args]) => { + t.test(method, async t => { + const methodSync = `${method}Sync` + t.isa(g[method], 'function') + t.isa(g[methodSync], 'function') + + args.unshift(file) + t.throws(() => g[methodSync](...args), {code: 'ENOENT'}) + if (g.promises) { + await t.rejects(g.promises[method](...args), {code: 'ENOENT'}) + } -function runTest (args) { return function (t) { - var method = args.shift() - args.unshift(file) - var methodSync = method + 'Sync' - t.isa(g[methodSync], 'function') - t.throws(function () { - g[methodSync].apply(g, args) - }, { code: 'ENOENT' }) - // add the callback - args.push(verify(t)) - t.isa(g[method], 'function') - t.doesNotThrow(function () { - g[method].apply(g, args) + await t.rejects(promisify(g[method])(...args), {code: 'ENOENT'}) }) -}} - -function verify (t) { return function (er) { - t.isa(er, Error) - t.equal(er.code, 'ENOENT') - t.end() -}} +}) diff --git a/test/helpers/graceful-fs.js b/test/helpers/graceful-fs.js new file mode 100644 index 0000000..ab33684 --- /dev/null +++ b/test/helpers/graceful-fs.js @@ -0,0 +1,9 @@ +'use strict' + +/* This is to force use of *our* fs.close / fs.closeSync even if + * nyc is using a version of graceful-fs with the shared queue */ +global[Symbol.for('graceful-fs.reset-queue')] = true + +const gfs = require('../../graceful-fs.js') + +module.exports = process.env.TEST_GFS_GLOBAL_PATCH ? gfs.gracefulify(require('fs')) : gfs diff --git a/test/lutimes.js b/test/lutimes.js new file mode 100644 index 0000000..2886a91 --- /dev/null +++ b/test/lutimes.js @@ -0,0 +1,72 @@ +'use strict' + +const path = require('path') +const rimraf = require('rimraf') +const {test} = require('tap') +const fs = require('./helpers/graceful-fs.js') + +if (!('O_SYMLINK' in fs.constants)) { + test('stubs', t => { + fs.lutimes('ln', 0, 0, () => {}) + fs.lutimesSync('ln', 0, 0) + t.end() + }) + + // nothing to test on this platform + process.exit(0) +} + +const dir = fs.mkdtempSync(path.join(__dirname, 'temp-files-')) +const ln = path.resolve(dir, 'symlink') + +test('lutimes', t => { + fs.symlinkSync(__filename, ln) + fs.lutimesSync(ln, 0, 0) + + let stat = fs.lstatSync(ln) + t.is(stat.atimeMs, 0) + t.is(stat.mtimeMs, 0) + + fs.lutimes(ln, 1, 1, er => { + t.notOk(er) + stat = fs.lstatSync(ln) + t.is(stat.atimeMs, 1000) + t.is(stat.mtimeMs, 1000) + + fs.unlinkSync(ln) + t.end() + }) +}) + +test('futimes error', t => { + const error = new Error('test error') + + fs.symlinkSync(__filename, ln) + + fs.futimes = (fd, at, mt, cb) => cb(error) + fs.futimesSync = () => { + throw error + } + + t.throws(() => fs.lutimesSync(ln, 0, 0), error, 'futimesSync error') + + fs.lutimes(ln, 1, 1, er => { + t.is(er, error) + + fs.unlinkSync(ln) + t.end() + }) +}) + +test('lutimes open error', t => { + fs.lutimes(ln, 1, 1, er => { + t.match(er, {code: 'ENOENT'}) + + t.end() + }) +}) + +test('cleanup', t => { + rimraf.sync(dir) + t.end() +}) diff --git a/test/max-open.js b/test/max-open.js index 743331f..d603558 100644 --- a/test/max-open.js +++ b/test/max-open.js @@ -1,42 +1,51 @@ -var fs = require('../') -var test = require('tap').test +'use strict' -test('open lots of stuff', function (t) { +const fs = require('./helpers/graceful-fs.js') +const {test} = require('tap') + +test('open lots of stuff', t => { // Get around EBADF from libuv by making sure that stderr is opened // Otherwise Darwin will refuse to give us a FD for stderr! process.stderr.write('') // How many parallel open()'s to do - var n = 1024 - var opens = 0 - var fds = [] - var going = true - var closing = false - var doneCalled = 0 + const n = 1024 + let opens = 0 + const fds = [] + let going = true + let closing = false + let doneCalled = 0 - for (var i = 0; i < n; i++) { + for (let i = 0; i < n; i++) { go() } - function go() { + function go () { opens++ - fs.open(__filename, 'r', function (er, fd) { - if (er) throw er + fs.open(__filename, 'r', (er, fd) => { + if (er) { + throw er + } + fds.push(fd) - if (going) go() + if (going) { + go() + } }) } // should hit ulimit pretty fast - setTimeout(function () { + setTimeout(() => { going = false t.equal(opens - fds.length, n) done() }, 100) - function done () { - if (closing) return + if (closing) { + return + } + doneCalled++ if (fds.length === 0) { @@ -50,18 +59,20 @@ test('open lots of stuff', function (t) { } closing = true - setTimeout(function () { + setTimeout(() => { // console.error('do closing again') closing = false done() }, 100) // console.error('closing time') - var closes = fds.slice(0) + const closes = fds.slice(0) fds.length = 0 - closes.forEach(function (fd) { - fs.close(fd, function (er) { - if (er) throw er + closes.forEach(fd => { + fs.close(fd, er => { + if (er) { + throw er + } }) }) } diff --git a/test/noop.js b/test/noop.js new file mode 100644 index 0000000..4ae929d --- /dev/null +++ b/test/noop.js @@ -0,0 +1,46 @@ +'use strict' + +const {test} = require('tap') +const {noop, noopSync} = require('../noop.js') + +test('noopSync', t => { + noopSync() + noopSync('a', 'b', 'c', 'd', 'e', () => t.fail('should not run callback')) + + // Wait a few ticks + setTimeout(() => { + t.end() + }, 50) +}) + +test('noop', t => { + const data = { + cbOnly: 0, + withArgs: 0 + } + + t.doesNotThrow(() => { + noop((er, ...args) => { + t.notOk(er) + t.is(args.length, 0) + data.cbOnly++ + }) + }, 'cb only') + t.is(data.cbOnly, 0, 'callback is not sync') + + t.doesNotThrow(() => { + noop('a', 'b', 'c', 'd', (er, ...args) => { + t.notOk(er) + t.is(args.length, 0) + data.withArgs++ + }) + }, 'with args') + t.is(data.withArgs, 0, 'callback is not sync') + + // Wait a few ticks + setTimeout(() => { + t.is(data.cbOnly, 1) + t.is(data.withArgs, 1) + t.end() + }, 50) +}) diff --git a/test/normalize-args-v10.js b/test/normalize-args-v10.js new file mode 100644 index 0000000..a6f239e --- /dev/null +++ b/test/normalize-args-v10.js @@ -0,0 +1,27 @@ +'use strict' + +Object.defineProperty(process.versions, 'node', {value: '10.0.0'}) + +const {test} = require('tap') +const normalizeArgs = require('../normalize-args.js') + +const stackTracer = (...args) => normalizeArgs(args) + +test('throw on no callback', t => { + const matchError = { + code: 'ERR_INVALID_CALLBACK', + name: 'TypeError', + message: 'Callback must be a function. Received undefined', + stack: /^TypeError \[ERR_INVALID_CALLBACK\]: Callback must be a function. Received undefined\n\s*at stackTracer/ + } + + t.throws(() => stackTracer([]), matchError) + t.throws(() => stackTracer(['blue', 'green']), matchError) + + const newCB = () => {} + const result = stackTracer('blue', 'green', newCB) + t.deepEqual(result[0], ['blue', 'green']) + t.is(result[1], newCB) + + t.end() +}) diff --git a/test/normalize-args-v8.js b/test/normalize-args-v8.js new file mode 100644 index 0000000..051c741 --- /dev/null +++ b/test/normalize-args-v8.js @@ -0,0 +1,41 @@ +'use strict' + +Object.defineProperty(process.versions, 'node', {value: '8.0.0'}) + +const {test} = require('tap') +const normalizeArgs = require('../normalize-args.js') + +const stackTracer = (...args) => normalizeArgs(args) + +test('warns on no callback', t => { + let hits = 0 + process.on('warning', warning => { + hits++ + t.match(warning, { + name: 'DeprecationWarning', + message: 'Calling an asynchronous function without callback is deprecated.', + code: 'DEP0013', + stack: /^DeprecationWarning: Calling an asynchronous function without callback is deprecated\.\n\s*at stackTracer/ + }, `warning ${hits}`) + }) + + let result = stackTracer() + t.deepEqual(result[0], []) + t.type(result[1], 'function') + t.notThrow(() => result[1]()) + t.notThrow(() => result[1](new Error('ignored'))) + + result = stackTracer('blue', 'green') + t.deepEqual(result[0], ['blue', 'green']) + t.type(result[1], 'function') + + const newCB = () => {} + result = stackTracer('blue', 'green', newCB) + t.deepEqual(result[0], ['blue', 'green']) + t.is(result[1], newCB) + + process.nextTick(() => { + t.is(hits, 2) + t.end() + }) +}) diff --git a/test/open.js b/test/open.js index ee0d8bc..df817ad 100644 --- a/test/open.js +++ b/test/open.js @@ -1,34 +1,40 @@ -var fs = require('../') -var test = require('tap').test +'use strict' -test('open an existing file works', function (t) { - var fd = fs.openSync(__filename, 'r') +const fs = require('./helpers/graceful-fs.js') +const {test} = require('tap') + +test('open an existing file works', t => { + const fd = fs.openSync(__filename, 'r') fs.closeSync(fd) - fs.open(__filename, 'r', function (er, fd) { - if (er) throw er - fs.close(fd, function (er) { - if (er) throw er + fs.open(__filename, 'r', (er, fd) => { + if (er) { + throw er + } + + fs.close(fd, er => { + if (er) { + throw er + } + t.pass('works') t.end() }) }) }) -test('open a non-existing file throws', function (t) { - var er - try { - var fd = fs.openSync('this file does not exist', 'r') - } catch (x) { - er = x - } - t.ok(er, 'should throw') - t.notOk(fd, 'should not get an fd') - t.equal(er.code, 'ENOENT') - - fs.open('neither does this file', 'r', function (er, fd) { - t.ok(er, 'should throw') - t.notOk(fd, 'should not get an fd') - t.equal(er.code, 'ENOENT') - t.end() +if (fs.promises) { + test('fs.promises.open an existing file works', async t => { + const stats = await fs.promises.stat(__filename) + const filehandle = await fs.promises.open(__filename, 'r') + + t.type(filehandle.getAsyncId, 'function') + t.type(filehandle.read, 'function') + t.type(filehandle.fd, 'number') + + const result = await filehandle.readFile('utf8') + t.type(result, 'string') + t.is(result.length, stats.size); + + await filehandle.close() }) -}) +} diff --git a/test/promise-close.js b/test/promise-close.js new file mode 100644 index 0000000..350df76 --- /dev/null +++ b/test/promise-close.js @@ -0,0 +1,70 @@ +'use strict' + +const fs = require('fs') +const {promisify} = require('util') +const t = require('tap') +const {enqueue} = require('../retry-queue.js') + +const promises = Object.getOwnPropertyDescriptor(fs, 'promises') +const delay = promisify(setTimeout) + +if (!promises) { + t.pass('nothing to do') + process.exit(0) +} + +const gfs = require('./helpers/graceful-fs.js') + +let hit = false +const retryHit = () => { + hit = true +} + +const enqueueHit = () => enqueue([retryHit, []]) + +function testHits (msg) { + t.ok(hit, msg) + hit = false +} + +async function testFunction () { + await delay(0) + enqueueHit() + + if (!promises.enumerable) { + hit = false + // Force initialization of promises + gfs.promises + } + + await delay(50) + testHits('gfs.promises close initialization did retry') + + enqueueHit() + let filehandle = await gfs.promises.open(__filename) + t.notOk(hit, 'gfs.promises.open delays retry') + await delay(0) + testHits('gfs.promises.open retry on nextTick') + + enqueueHit() + await filehandle.close() + testHits('filehandle.close caused immediate retry') + + if (!process.env.TEST_GFS_GLOBAL_PATCH) { + enqueueHit() + filehandle = await fs.promises.open(__filename) + t.notOk(hit, 'no retry from fs.promises.open') + await delay(0) + t.notOk(hit, 'fs.promises.open no retry on nextTick') + + await filehandle.close() + testHits('filehandle.close caused immediate retry') + } +} + +t.resolves(testFunction(), 'test function resolves') + .then(() => t.end()) + .catch(error => { + console.error(error) + t.fail() + }) diff --git a/test/read-write-stream.js b/test/read-write-stream.js index c6511cb..ce1d87a 100644 --- a/test/read-write-stream.js +++ b/test/read-write-stream.js @@ -1,51 +1,144 @@ 'use strict' -var fs = require('../') -var rimraf = require('rimraf') -var mkdirp = require('mkdirp') -var test = require('tap').test -var p = require('path').resolve(__dirname, 'files') - -process.chdir(__dirname) +const path = require('path') +const fs = require('./helpers/graceful-fs.js') +const rimraf = require('rimraf') +const {test} = require('tap') // Make sure to reserve the stderr fd process.stderr.write('') -var num = 4097 -var paths = new Array(num) +const p = fs.mkdtempSync(path.join(__dirname, 'temp-files-')) +const paths = new Array(4097).fill().map((_, i) => `${p}/file-${i}`) -test('write files', function (t) { - rimraf.sync(p) - mkdirp.sync(p) - - t.plan(num) - for (var i = 0; i < num; ++i) { - paths[i] = 'files/file-' + i - var stream = fs.createWriteStream(paths[i]) - stream.on('finish', function () { - t.pass('success') - }) +test('write files', t => { + t.plan(paths.length * 4) + for (const i in paths) { + let stream + switch (i % 3) { + case 0: + stream = fs.createWriteStream(paths[i]) + break + case 1: + stream = fs.WriteStream(paths[i]) + break + case 2: + stream = new fs.WriteStream(paths[i]) + break + } + + t.type(stream, fs.WriteStream) + stream.on('open', fd => t.type(fd, 'number')) + stream.on('ready', () => t.pass('ready')) + stream.on('finish', () => t.pass('success')) stream.write('content') stream.end() } }) -test('read files', function (t) { +test('read files', t => { // now read them - t.plan(num) - for (var i = 0; i < num; ++i) (function (i) { - var stream = fs.createReadStream(paths[i]) - var data = '' - stream.on('data', function (c) { + t.plan(paths.length * 4) + for (const i in paths) { + let stream + switch (i % 3) { + case 0: + stream = fs.createReadStream(paths[i]) + break + case 1: + stream = fs.ReadStream(paths[i]) + break + case 2: + stream = new fs.ReadStream(paths[i]) + break + } + + t.type(stream, fs.ReadStream) + stream.on('open', fd => t.type(fd, 'number')) + stream.on('ready', () => t.pass('ready')) + let data = '' + stream.on('data', c => { data += c }) - stream.on('end', function () { - t.equal(data, 'content') - }) - })(i) + stream.on('end', () => t.equal(data, 'content')) + } +}) + +function streamErrors (t, read, autoClose) { + const events = [] + const initializer = read ? 'createReadStream' : 'createWriteStream' + const stream = fs[initializer]( + path.join(__dirname, 'this dir does not exist', 'filename'), + {autoClose} + ) + const matchDestroy = autoClose ? ['destroy'] : ['error', 'destroy'] + const matchError = autoClose ? ['destroy', 'error'] : ['error'] + const {destroy} = stream + stream.on('open', () => t.fail('unexpected open')) + stream.on('ready', () => t.fail('unexpected ready')) + stream.destroy = () => { + events.push('destroy') + t.deepEqual(events, matchDestroy, 'got destroy') + destroy.call(stream) + } + + stream.on('error', () => { + events.push('error') + t.deepEqual(events, matchError, 'got error') + if (!autoClose) { + stream.destroy() + } + + setTimeout(() => t.end(), 50) + }) +} + +test('read error autoClose', t => streamErrors(t, true, true)) +test('read error no autoClose', t => streamErrors(t, true, false)) +test('write error autoClose', t => streamErrors(t, false, true)) +test('write error no autoClose', t => streamErrors(t, false, false)) + +test('ReadStream replacement', t => { + const testArgs = [__filename, {}] + let called = 0 + + class FakeReplacement { + constructor (...args) { + t.deepEqual(args, testArgs) + called++ + } + } + + const {ReadStream} = fs + fs.ReadStream = FakeReplacement + const rs = fs.createReadStream(...testArgs) + fs.ReadStream = ReadStream + t.type(rs, FakeReplacement) + t.is(called, 1) + t.end() +}) + +test('WriteStream replacement', t => { + const testArgs = [__filename, {}] + let called = 0 + + class FakeReplacement { + constructor (...args) { + t.deepEqual(args, testArgs) + called++ + } + } + + const {WriteStream} = fs + fs.WriteStream = FakeReplacement + const rs = fs.createWriteStream(...testArgs) + fs.WriteStream = WriteStream + t.type(rs, FakeReplacement) + t.is(called, 1) + t.end() }) -test('cleanup', function (t) { +test('cleanup', t => { rimraf.sync(p) t.end() }) diff --git a/test/readdir-options.js b/test/readdir-options.js index 07c2530..f2c91d0 100644 --- a/test/readdir-options.js +++ b/test/readdir-options.js @@ -1,34 +1,45 @@ -var fs = require("fs") -var t = require("tap") +'use strict' -var currentTest +const fs = require('fs') +const t = require('tap') +const {promisify} = require('util') -var strings = ['b', 'z', 'a'] -var buffs = strings.map(function (s) { return Buffer.from(s) }) -var hexes = buffs.map(function (b) { return b.toString('hex') }) +let currentTest -function getRet (encoding) { +function getRet (encoding, withFileTypes) { + const strings = ['b', 'z', 'a'] + const buffs = strings.map(s => Buffer.from(s)) + const hexes = buffs.map(b => b.toString('hex')) + + let results switch (encoding) { case 'hex': - return hexes + results = hexes + break case 'buffer': - return buffs + results = buffs + break default: - return strings + results = strings + break + } + + if (withFileTypes) { + return results.map(name => new fs.Dirent(name)) } + + return results } -var readdir = fs.readdir -var failed = false -fs.readdir = function(path, options, cb) { +const emfile = () => Object.assign(new Error('synthetic emfile'), {code: 'EMFILE'}) +let failed = false +fs.readdir = (path, options, cb) => { if (!failed) { // simulate an EMFILE and then open and close a thing to retry failed = true - process.nextTick(function () { - var er = new Error('synthetic emfile') - er.code = 'EMFILE' - cb(er) - process.nextTick(function () { + process.nextTick(() => { + cb(emfile()) + process.nextTick(() => { g.closeSync(fs.openSync(__filename, 'r')) }) }) @@ -39,23 +50,51 @@ fs.readdir = function(path, options, cb) { currentTest.isa(cb, 'function') currentTest.isa(options, 'object') currentTest.ok(options) - process.nextTick(function() { - var ret = getRet(options.encoding) - cb(null, ret) + process.nextTick(() => { + cb(null, getRet(options.encoding, options.withFileTypes)) }) } -var g = require("../") +if (fs.promises) { + fs.promises.readdir = async (path, options) => { + if (!failed) { + // simulate an EMFILE and then open and close a thing to retry + failed = true + process.nextTick(() => { + g.closeSync(fs.openSync(__filename, 'r')) + }) + throw emfile() + } + + failed = false + currentTest.isa(options, 'object') + currentTest.ok(options) + return getRet(options.encoding, options.withFileTypes) + } +} + +const g = require('./helpers/graceful-fs.js') -var encodings = ['buffer', 'hex', 'utf8', null] -encodings.forEach(function (enc) { - t.test('encoding=' + enc, function (t) { +const sortDirEnts = (a, b) => a.name.toString().localeCompare(b.name.toString()) +const encodings = ['buffer', 'hex', 'utf8', null] +encodings.forEach(encoding => { + const readdir = promisify(g.readdir) + t.test('encoding=' + encoding, async t => { currentTest = t - g.readdir("whatevers", { encoding: enc }, function (er, files) { - if (er) - throw er - t.same(files, getRet(enc).sort()) - t.end() - }) + let files = await readdir('whatevers', {encoding}) + t.same(files, getRet(encoding, false).sort()) + + if (fs.Dirent) { + files = await readdir('whatevers', {encoding, withFileTypes: true}) + t.same(files, getRet(encoding, true).sort(sortDirEnts)) + } + + if (g.promises) { + files = await g.promises.readdir('whatevers', {encoding}) + t.same(files, getRet(encoding).sort()) + + files = await g.promises.readdir('whatevers', {encoding, withFileTypes: true}) + t.same(files, getRet(encoding, true).sort(sortDirEnts)) + } }) }) diff --git a/test/readdir-sort.js b/test/readdir-sort.js index 6d3ea28..168d03b 100644 --- a/test/readdir-sort.js +++ b/test/readdir-sort.js @@ -1,20 +1,29 @@ -var fs = require("fs") +'use strict' -var readdir = fs.readdir -fs.readdir = function(path, cb) { - process.nextTick(function() { - cb(null, ["b", "z", "a"]) +const fs = require('fs') +const {promisify} = require('util') + +fs.readdir = (path, cb) => { + process.nextTick(() => { + cb(null, ['b', 'z', 'a']) }) } -var g = require("../") -var test = require("tap").test +if (fs.promises) { + fs.promises.readdir = async () => { + return ['b', 'z', 'a'] + } +} -test("readdir reorder", function (t) { - g.readdir("whatevers", function (er, files) { - if (er) - throw er - t.same(files, [ "a", "b", "z" ]) - t.end() - }) +const g = require('./helpers/graceful-fs.js') +const {test} = require('tap') + +test('readdir reorder', async t => { + let files = await promisify(g.readdir)('whatevers') + t.same(files, ['a', 'b', 'z']) + + if (g.promises) { + files = await g.promises.readdir('whatevers') + t.same(files, ['a', 'b', 'z']) + } }) diff --git a/test/readfile.js b/test/readfile.js index ce4f04f..81ad334 100644 --- a/test/readfile.js +++ b/test/readfile.js @@ -1,47 +1,88 @@ 'use strict' -var fs = require('../') -var rimraf = require('rimraf') -var mkdirp = require('mkdirp') -var test = require('tap').test -var p = require('path').resolve(__dirname, 'files') - -process.chdir(__dirname) +const path = require('path') +const fs = require('./helpers/graceful-fs.js') +const rimraf = require('rimraf') +const {test} = require('tap') // Make sure to reserve the stderr fd process.stderr.write('') -var num = 4097 -var paths = new Array(num) - -test('write files', function (t) { - rimraf.sync(p) - mkdirp.sync(p) +const tmpdir = fs.mkdtempSync(path.join(__dirname, 'temp-files-')) +const dir = path.join(tmpdir, 'tmp') +const paths = new Array(4097).fill().map((_, i) => `${dir}/file-${i}`) - t.plan(num) - for (var i = 0; i < num; ++i) { - paths[i] = 'files/file-' + i - fs.writeFile(paths[i], 'content', 'ascii', function (er) { - if (er) - throw er +test('write files', t => { + t.plan(paths.length * 2) + fs.mkdirSync(dir) + // write files + for (const i in paths) { + fs.writeFile(paths[i], 'content', 'ascii', er => { + t.error(er) t.pass('written') }) } }) -test('read files', function (t) { +test('read files', t => { + t.plan(paths.length * 2) // now read them - t.plan(num) - for (var i = 0; i < num; ++i) { - fs.readFile(paths[i], 'ascii', function (er, data) { - if (er) - throw er + for (const i in paths) { + fs.readFile(paths[i], 'ascii', (er, data) => { + t.error(er) t.equal(data, 'content') }) } }) -test('cleanup', function (t) { - rimraf.sync(p) +test('cleanup', t => { + rimraf.sync(dir) t.end() }) + +if (fs.promises) { + test('promise write then read files', async t => { + t.ok(true) + await fs.promises.mkdir(dir) + // write files + await Promise.all(paths.map(async (p, i) => { + // Alternate between the three methods + if (i % 3 === 0) { + await fs.promises.writeFile(p, 'content', 'ascii') + } else { + const filehandle = await fs.promises.open(p, 'wx') + if (i % 3 === 1) { + await filehandle.writeFile('content', 'ascii') + } else { + await fs.promises.writeFile(filehandle, 'content', 'ascii') + } + + await filehandle.close() + } + })) + + // now read them + const results = await Promise.all(paths.map(async (p, i) => { + // Alternate between the three methods + if (i % 3 === 0) { + return fs.promises.readFile(p, 'ascii') + } + + let result + const filehandle = await fs.promises.open(p, 'r') + + if (i % 3 === 1) { + result = await filehandle.readFile('ascii') + } else { + result = await fs.promises.readFile(filehandle, 'ascii') + } + + await filehandle.close() + return result + })) + + t.is(results.length, paths.length) + t.ok(results.every(r => r === 'content')) + rimraf.sync(tmpdir) + }) +} diff --git a/test/stats-uid-gid.js b/test/stats-uid-gid.js deleted file mode 100644 index 7422e5d..0000000 --- a/test/stats-uid-gid.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; -var util = require('util') -var fs = require('fs') -var test = require('tap').test - -// mock fs.statSync to return signed uids/gids -var realStatSync = fs.statSync -fs.statSync = function(path) { - var stats = realStatSync.call(fs, path) - stats.uid = -2 - stats.gid = -2 - return stats -} - -var gfs = require('../graceful-fs.js') - -test('graceful fs uses same stats constructor as fs', function (t) { - t.equal(gfs.Stats, fs.Stats, 'should reference the same constructor') - - if (!process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { - t.equal(fs.statSync(__filename).uid, -2) - t.equal(fs.statSync(__filename).gid, -2) - } - - t.equal(gfs.statSync(__filename).uid, 0xfffffffe) - t.equal(gfs.statSync(__filename).gid, 0xfffffffe) - - t.end() -}) - -test('does not throw when async stat fails', function (t) { - gfs.stat(__filename + ' this does not exist', function (er, stats) { - t.ok(er) - t.notOk(stats) - t.end() - }) -}) - -test('throws ENOENT when sync stat fails', function (t) { - t.throws(function() { - gfs.statSync(__filename + ' this does not exist') - }, /ENOENT/) - t.end() -}) diff --git a/test/stats.js b/test/stats.js deleted file mode 100644 index b6c45b9..0000000 --- a/test/stats.js +++ /dev/null @@ -1,12 +0,0 @@ -var fs = require('fs') -var gfs = require('../graceful-fs.js') -var test = require('tap').test - -test('graceful fs uses same stats constructor as fs', function (t) { - t.equal(gfs.Stats, fs.Stats, 'should reference the same constructor') - t.ok(fs.statSync(__filename) instanceof fs.Stats, - 'should be instance of fs.Stats') - t.ok(gfs.statSync(__filename) instanceof fs.Stats, - 'should be instance of fs.Stats') - t.end() -}) diff --git a/test/windows-rename-polyfill.js b/test/windows-rename-polyfill.js index f48ba74..a1efc24 100644 --- a/test/windows-rename-polyfill.js +++ b/test/windows-rename-polyfill.js @@ -1,35 +1,141 @@ -process.env.GRACEFUL_FS_PLATFORM = 'win32' - -var fs = require('fs') -fs.rename = function (a, b, cb) { - setTimeout(function () { - var er = new Error('EPERM blerg') - er.code = 'EPERM' - cb(er) - }) +'use strict' + +const path = require('path') +const fs = require('fs') +const {promisify} = require('util') + +const windowsRenamePolyfill = require('../windows-rename-polyfill.js') +const promiseWindowsRenamePolyfill = require('../promise-windows-rename-polyfill.js') + +function createPolyfilledObject (code) { + const pfs = { + stat: fs.stat, + rename (a, b, cb) { + /* original rename */ + cb(Object.assign(new Error(code), {code})) + } + } + + windowsRenamePolyfill(pfs) + if (fs.promises) { + pfs.promises = { + stat: fs.promises.stat, + async rename () { + /* original rename */ + throw Object.assign(new Error(code), {code}) + } + } + + promiseWindowsRenamePolyfill(pfs.promises) + } + + return pfs } -var gfs = require('../') -var t = require('tap') -var a = __dirname + '/a' -var b = __dirname + '/b' +const t = require('tap') + +const a = path.join(__dirname, 'a') +const b = path.join(__dirname, 'b') + +t.test('setup', t => { + const pfs = createPolyfilledObject('EPERM') + t.notMatch(pfs.rename.toString(), /original rename/) + if (pfs.promises) { + t.notMatch(pfs.promises.rename.toString(), /original rename/) + } + + try { + fs.mkdirSync(a) + } catch (e) {} + + try { + fs.mkdirSync(b) + } catch (e) {} -t.test('setup', function (t) { - try { fs.mkdirSync(a) } catch (e) {} - try { fs.mkdirSync(b) } catch (e) {} t.end() }) -t.test('rename', { timeout: 100 }, function (t) { - t.plan(1) +t.test('rename EPERM', {timeout: 100}, async t => { + const pfs = createPolyfilledObject('EPERM') + console.log('orig rename') + await t.rejects(promisify(pfs.rename)(a, b), {code: 'EPERM'}) - gfs.rename(a, b, function (er) { - t.ok(er) - }) + if (pfs.promises) { + console.log('promise rename') + await t.rejects(pfs.promises.rename(a, b), {code: 'EPERM'}) + } }) -t.test('cleanup', function (t) { - try { fs.rmdirSync(a) } catch (e) {} - try { fs.rmdirSync(b) } catch (e) {} +t.test('rename EACCES', {timeout: 100}, async t => { + const pfs = createPolyfilledObject('EACCES') + await t.rejects(promisify(pfs.rename)(a, b), {code: 'EACCES'}) + + if (pfs.promises) { + await t.rejects(pfs.promises.rename(a, b), {code: 'EACCES'}) + } +}) + +t.test('rename ENOENT', {timeout: 100}, async t => { + const pfs = createPolyfilledObject('ENOENT') + await t.rejects(promisify(pfs.rename)(a, b), {code: 'ENOENT'}) + + if (pfs.promises) { + await t.rejects(pfs.promises.rename(a, b), {code: 'ENOENT'}) + } +}) + +t.test('rename EPERM then stat ENOENT', {timeout: 2000}, async t => { + const pfs = createPolyfilledObject('EPERM') + let enoent = 12 + pfs.stat = (p, cb) => { + if (--enoent) { + cb(Object.assign(new Error('ENOENT'), {code: 'ENOENT'})) + } else { + fs.stat(p, cb) + } + } + + await t.rejects(promisify(pfs.rename)(a, b), {code: 'EPERM'}) + + if (pfs.promises) { + enoent = 12 + pfs.promises.stat = async p => { + if (--enoent) { + throw Object.assign(new Error('ENOENT'), {code: 'ENOENT'}) + } + + return fs.promises.stat(p) + } + + await t.rejects(pfs.promises.rename(a, b), {code: 'EPERM'}) + } +}) + +t.test('rename EPERM then stat EACCES', {timeout: 2000}, async t => { + const pfs = createPolyfilledObject('EPERM') + pfs.stat = (p, cb) => { + cb(Object.assign(new Error('EACCES'), {code: 'EACCES'})) + } + + await t.rejects(promisify(pfs.rename)(a, b), {code: 'EPERM'}) + + if (pfs.promises) { + pfs.promises.stat = async () => { + throw Object.assign(new Error('EACCES'), {code: 'EACCES'}) + } + + await t.rejects(pfs.promises.rename(a, b), {code: 'EPERM'}) + } +}) + +t.test('cleanup', t => { + try { + fs.rmdirSync(a) + } catch (e) {} + + try { + fs.rmdirSync(b) + } catch (e) {} + t.end() }) diff --git a/test/write-then-read.js b/test/write-then-read.js deleted file mode 100644 index 3a66df3..0000000 --- a/test/write-then-read.js +++ /dev/null @@ -1,43 +0,0 @@ -var fs = require('../'); -var rimraf = require('rimraf'); -var mkdirp = require('mkdirp'); -var test = require('tap').test; -var p = require('path').resolve(__dirname, 'files'); - -process.chdir(__dirname) - -// Make sure to reserve the stderr fd -process.stderr.write(''); - -var num = 4097; -var paths = new Array(num); - -test('make files', function (t) { - rimraf.sync(p); - mkdirp.sync(p); - - for (var i = 0; i < num; ++i) { - paths[i] = 'files/file-' + i; - fs.writeFileSync(paths[i], 'content'); - } - - t.end(); -}) - -test('read files', function (t) { - // now read them - t.plan(num) - for (var i = 0; i < num; ++i) { - fs.readFile(paths[i], 'ascii', function(err, data) { - if (err) - throw err; - - t.equal(data, 'content') - }); - } -}); - -test('cleanup', function (t) { - rimraf.sync(p); - t.end(); -}); diff --git a/windows-rename-polyfill.js b/windows-rename-polyfill.js new file mode 100644 index 0000000..ca356c2 --- /dev/null +++ b/windows-rename-polyfill.js @@ -0,0 +1,39 @@ +'use strict' + +const normalizeArgs = require('./normalize-args.js') + +const accessErrors = new Set(['EACCES', 'EPERM']) + +function windowsRenamePolyfill (fs) { + const {rename} = fs + + fs.rename = (from, to, cb) => { + const start = Date.now() + let backoff = 0 + cb = normalizeArgs([cb])[1] + + rename(from, to, function CB (er) { + if (er && accessErrors.has(er.code) && Date.now() - start < 60000) { + setTimeout(() => { + fs.stat(to, stater => { + if (stater && stater.code === 'ENOENT') { + rename(from, to, CB) + } else { + cb(er) + } + }) + }, backoff) + + if (backoff < 100) { + backoff += 10 + } + + return + } + + cb(er) + }) + } +} + +module.exports = windowsRenamePolyfill