diff --git a/lib/install/action/extract.js b/lib/install/action/extract.js index 2c8a995af63..2f577fad867 100644 --- a/lib/install/action/extract.js +++ b/lib/install/action/extract.js @@ -3,7 +3,7 @@ var path = require('path') var iferr = require('iferr') var asyncMap = require('slide').asyncMap var fs = require('graceful-fs') -var rename = require('../../utils/rename.js') +var move = require('../../utils/move.js') var gentlyRm = require('../../utils/gently-rm.js') var updatePackageJson = require('../update-package-json') var npm = require('../../npm.js') @@ -68,7 +68,7 @@ function stageBundledModule (bundler, child, staging, parentPath, next) { function moveModule () { if (child.fromBundle) { - return rename(stageFrom, stageTo, iferr(next, updateMovedPackageJson)) + return move(stageFrom, stageTo, iferr(next, updateMovedPackageJson)) } else { return fs.stat(stageFrom, function (notExists, exists) { if (exists) { diff --git a/lib/install/action/finalize.js b/lib/install/action/finalize.js index acc11cc4a77..03a71f4cc03 100644 --- a/lib/install/action/finalize.js +++ b/lib/install/action/finalize.js @@ -4,7 +4,7 @@ var rimraf = require('rimraf') var fs = require('graceful-fs') var mkdirp = require('mkdirp') var asyncMap = require('slide').asyncMap -var rename = require('../../utils/rename.js') +var move = require('../../utils/move.js') var gentlyRm = require('../../utils/gently-rm') var moduleStagingPath = require('../module-staging-path.js') @@ -26,31 +26,31 @@ module.exports = function (staging, pkg, log, next) { function destStatted (doesNotExist) { if (doesNotExist) { - rename(extractedTo, pkg.path, whenMoved) + move(extractedTo, pkg.path, whenMoved) } else { moveAway() } } - function whenMoved (renameEr) { - if (!renameEr) return next() - if (renameEr.code !== 'ENOTEMPTY') return next(renameEr) + function whenMoved (moveEr) { + if (!moveEr) return next() + if (moveEr.code !== 'ENOTEMPTY' && moveEr.code !== 'EEXIST') return next(moveEr) moveAway() } function moveAway () { - rename(pkg.path, delpath, whenOldMovedAway) + move(pkg.path, delpath, whenOldMovedAway) } - function whenOldMovedAway (renameEr) { - if (renameEr) return next(renameEr) - rename(extractedTo, pkg.path, whenConflictMoved) + function whenOldMovedAway (moveEr) { + if (moveEr) return next(moveEr) + move(extractedTo, pkg.path, whenConflictMoved) } - function whenConflictMoved (renameEr) { + function whenConflictMoved (moveEr) { // if we got an error we'll try to put back the original module back, // succeed or fail though we want the original error that caused this - if (renameEr) return rename(delpath, pkg.path, function () { next(renameEr) }) + if (moveEr) return move(delpath, pkg.path, function () { next(moveEr) }) fs.readdir(path.join(delpath, 'node_modules'), makeTarget) } @@ -65,7 +65,7 @@ module.exports = function (staging, pkg, log, next) { asyncMap(files, function (file, done) { var from = path.join(delpath, 'node_modules', file) var to = path.join(pkg.path, 'node_modules', file) - rename(from, to, done) + move(from, to, done) }, cleanup) } diff --git a/lib/install/action/move.js b/lib/install/action/move.js index 3f29379f2bd..07649c35569 100644 --- a/lib/install/action/move.js +++ b/lib/install/action/move.js @@ -8,7 +8,7 @@ var mkdirp = require('mkdirp') var rmStuff = require('../../unbuild.js').rmStuff var lifecycle = require('../../utils/lifecycle.js') var updatePackageJson = require('../update-package-json.js') -var rename = require('../../utils/rename.js') +var move = require('../../utils/move.js') /* Move a module from one point in the node_modules tree to another. @@ -46,7 +46,7 @@ function moveModuleOnly (from, to, log, done) { log.silly('move', 'move existing destination node_modules away', toModules) - rename(toModules, tempToModules, removeDestination(done)) + move(toModules, tempToModules, removeDestination(done)) function removeDestination (next) { return function (er) { @@ -62,7 +62,7 @@ function moveModuleOnly (from, to, log, done) { function moveToModulesBack (next) { return function () { log.silly('move', 'move existing destination node_modules back', toModules) - rename(tempToModules, toModules, iferr(done, next)) + move(tempToModules, toModules, iferr(done, next)) } } @@ -76,14 +76,14 @@ function moveModuleOnly (from, to, log, done) { function moveNodeModules (next) { return function () { log.silly('move', 'move source node_modules away', fromModules) - rename(fromModules, tempFromModules, iferr(doMove(next), doMove(moveNodeModulesBack(next)))) + move(fromModules, tempFromModules, iferr(doMove(next), doMove(moveNodeModulesBack(next)))) } } function doMove (next) { return function () { log.silly('move', 'move module dir to final dest', from, to) - rename(from, to, iferr(done, next)) + move(from, to, iferr(done, next)) } } @@ -91,7 +91,7 @@ function moveModuleOnly (from, to, log, done) { return function () { mkdirp(from, iferr(done, function () { log.silly('move', 'put source node_modules back', fromModules) - rename(tempFromModules, fromModules, iferr(done, next)) + move(tempFromModules, fromModules, iferr(done, next)) })) } } diff --git a/lib/install/action/remove.js b/lib/install/action/remove.js index 47d5b766f7d..9fe77c35e05 100644 --- a/lib/install/action/remove.js +++ b/lib/install/action/remove.js @@ -6,7 +6,7 @@ var asyncMap = require('slide').asyncMap var mkdirp = require('mkdirp') var npm = require('../../npm.js') var andIgnoreErrors = require('../and-ignore-errors.js') -var rename = require('../../utils/rename.js') +var move = require('../../utils/move.js') // This is weird because we want to remove the module but not it's node_modules folder // allowing for this allows us to not worry about the order of operations @@ -26,11 +26,11 @@ function removeLink (pkg, next) { function removeDir (pkg, log, next) { var modpath = path.join(path.dirname(pkg.path), '.' + path.basename(pkg.path) + '.MODULES') - rename(path.join(pkg.path, 'node_modules'), modpath, unbuildPackage) + move(path.join(pkg.path, 'node_modules'), modpath, unbuildPackage) - function unbuildPackage (renameEr) { + function unbuildPackage (moveEr) { npm.commands.unbuild(pkg.path, true, function () { - rimraf(pkg.path, renameEr ? andRemoveEmptyParents(pkg.path) : moveModulesBack) + rimraf(pkg.path, moveEr ? andRemoveEmptyParents(pkg.path) : moveModulesBack) }) } @@ -58,7 +58,7 @@ function removeDir (pkg, log, next) { var to = path.join(pkg.path, 'node_modules', file) // we ignore errors here, because they can legitimately happen, for instance, // bundled modules will be in both node_modules folders - rename(from, to, andIgnoreErrors(done)) + move(from, to, andIgnoreErrors(done)) }, cleanup) } diff --git a/lib/utils/rename.js b/lib/utils/rename.js deleted file mode 100644 index 8a444289844..00000000000 --- a/lib/utils/rename.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' -var fs = require('graceful-fs') -var SaveStack = require('./save-stack.js') - -module.exports = rename - -function rename (from, to, cb) { - var saved = new SaveStack(rename) - fs.rename(from, to, function (er) { - if (er) { - return cb(saved.completeWith(er)) - } else { - return cb() - } - }) -} diff --git a/node_modules/@npmcorp/move/LICENSE b/node_modules/@npmcorp/move/LICENSE new file mode 100644 index 00000000000..e0040f6659d --- /dev/null +++ b/node_modules/@npmcorp/move/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2017, Rebecca Turner + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/@npmcorp/move/README.md b/node_modules/@npmcorp/move/README.md new file mode 100644 index 00000000000..6a6d9c23e09 --- /dev/null +++ b/node_modules/@npmcorp/move/README.md @@ -0,0 +1,52 @@ +# @npminc/move + +Move files and directories. + +``` +const move = require('@npminc/move') +move('/path/to/thing', '/new/path/thing'), err => { + if (err) throw err + // thing is now moved! +}) +``` + +Uses `rename` to move things as fast as possible. + +If you `move` across devices or on filesystems that don't support renaming +large directories. That is, situations that result in `rename` returning +the `EXDEV` error, then `move` will fallback to copy + delete. + +When recursively copying directories it will first try to rename the +contents before falling back to copying. While this will be slightly slower +in true cross-device scenarios, it is MUCH faster in cases where the +filesystem can't handle directory renames. + +When copying ownership is maintained when running as root. Permissions are +always maintained. On Windows, if symlinks are unavailable then junctions +will be used. + +## INTERFACE + +### move(from, to, options) → Promise + +Recursively moves `from` to `to` and resolves its promise when finished. +If `to` already exists then the promise will be rejected with an `EEXIST` +error. + +Starts by trying to rename `from` to `to`. + +Options are: + +* maxConcurrency – (Default: `1`) The maximum number of concurrent copies to do at once. +* isWindows - (Default: `process.platform === 'win32'`) If true enables Windows symlink semantics. This requires + an extra `stat` to determine if the destination of a symlink is a file or directory. If symlinking a directory + fails then we'll try making a junction instead. + +Options can also include dependency injection: + +* Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. +* fs - (Default: `require('fs')`) The filesystem module to use. Can be used + to use `graceful-fs` or to inject a mock. +* writeStreamAtomic - (Default: `require('fs-write-stream-atomic')`) The + implementation of `writeStreamAtomic` to use. Used to inject a mock. +* getuid - (Default: `process.getuid`) A function that returns the current UID. Used to inject a mock. diff --git a/node_modules/@npmcorp/move/move.js b/node_modules/@npmcorp/move/move.js new file mode 100644 index 00000000000..0a84093d43c --- /dev/null +++ b/node_modules/@npmcorp/move/move.js @@ -0,0 +1,84 @@ +'use strict' +module.exports = move + +var nodeFs = require('fs') +var rimraf = require('rimraf') +var validate = require('aproba') +var copy = require('@npmcorp/copy') +var RunQueue = require('run-queue') +var extend = Object.assign || require('util')._extend + +function promisify (Promise, fn) { + return function () { + var args = [].slice.call(arguments) + return new Promise(function (resolve, reject) { + return fn.apply(null, args.concat(function (err, value) { + if (err) { + reject(err) + } else { + resolve(value) + } + })) + }) + } +} + +function move (from, to, opts) { + validate('SSO|SS', arguments) + opts = extend({}, opts || {}) + + var Promise = opts.Promise || global.Promise + var fs = opts.fs || nodeFs + var rimrafAsync = promisify(Promise, rimraf) + var renameAsync = promisify(Promise, fs.rename) + + opts.top = from + + var queue = new RunQueue({ + maxConcurrency: opts.maxConcurrency, + Promise: Promise + }) + opts.queue = queue + opts.recurseWith = rename + + queue.add(0, rename, [from, to, opts]) + + return queue.run().then(function () { + return remove(from) + }, function (err) { + // if the target already exists don't clobber it + if (err.code === 'EEXIST' || err.code === 'EPERM') { + return passThroughError() + } else { + return remove(to).then(passThroughError, passThroughError) + } + function passThroughError () { + return Promise.reject(err) + } + }) + + function remove (target) { + var opts = { + unlink: fs.unlink, + chmod: fs.chmod, + stat: fs.stat, + lstat: fs.lstat, + rmdir: fs.rmdir, + readdir: fs.readdir, + glob: false + } + return rimrafAsync(target, opts) + } + + function rename (from, to, opts, done) { + return renameAsync(from, to).catch(function (err) { + if (err.code !== 'EXDEV') { + return Promise.reject(err) + } else { + return remove(to).then(function () { + return copy.item(from, to, opts) + }) + } + }) + } +} diff --git a/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/LICENSE b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/LICENSE new file mode 100644 index 00000000000..e0040f6659d --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2017, Rebecca Turner + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/README.md b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/README.md new file mode 100644 index 00000000000..6e89dc64749 --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/README.md @@ -0,0 +1,127 @@ +# @npminc/copy + +Copy files, directories and symlinks + +``` +const copy = require('@npminc/copy') +copy('/path/to/thing', '/new/path/thing').then(() => { + // this is now copied +}).catch(err => { + // oh noooo +}) +``` + +Copies files, directories and symlinks. Ownership is maintained when +running as root, permissions are always maintained. On Windows, if symlinks +are unavailable then junctions will be used. + +## PUBLIC INTERFACE + +### copy(from, to, [options]) → Promise + +Recursively copies `from` to `to` and resolves its promise when finished. +If `to` already exists then the promise will be rejected with an `EEXIST` +error. + +Options are: + +* maxConcurrency – (Default: `1`) The maximum number of concurrent copies to do at once. +* recurseWith - (Default: `copy.item`) The function to call on each file after recursing into a directory. +* isWindows - (Default: `process.platform === 'win32'`) If true enables Windows symlink semantics. This requires + an extra `stat` to determine if the destination of a symlink is a file or directory. If symlinking a directory + fails then we'll try making a junction instead. + +Options can also include dependency injection: + +* Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. +* fs - (Default: `require('fs')`) The filesystem module to use. Can be used + to use `graceful-fs` or to inject a mock. +* writeStreamAtomic - (Default: `require('fs-write-stream-atomic')`) The + implementation of `writeStreamAtomic` to use. Used to inject a mock. +* getuid - (Default: `process.getuid`) A function that returns the current UID. Used to inject a mock. + +## EXTENSION INTERFACE + +Ordinarily you'd only call `copy` above. But it's possible to use it's +component functions directly. This is useful if, say, you're writing +[@npminc/move](https://npmjs.com/package/@npminc/move). + +### copy.file(from, to, options) → Promise + +Copies a ordinary file `from` to destination `to`. Uses +`fs-write-stream-atomic` to ensure that the file is entirely copied or not +at all. + +Options are: + +* uid, gid - (Optional) If `getuid()` is `0` then this and gid will be used to + set the user and group of `to`. If uid is present then gid must be too. +* mode - (Optional) If set then `to` will have its perms set to `mode`. +* fs - (Default: `require('fs')`) The filesystem module to use. Can be used + to use `graceful-fs` or to inject a mock. +* Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. +* writeStreamAtomic - (Default `require('fs-write-stream-atomic')`) The + implementation of `writeStreamAtomic` to use. Used to inject a mock. + +### copy.symlink(from, to, options) → Promise + +Copies a symlink `from` to destination `to`. If on Windows then if +symlinking fails, a junction will be used instead. + +Options are: + +* top - The top level the copy is being run from. This is used to determine + if the symlink destination is within the set of files we're copying or + outside it. +* fs - (Default: `require('fs')`) The filesystem module to use. Can be used + to use `graceful-fs` or to inject a mock. +* Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. +* isWindows - (Default: `process.platform === 'win32'`) If true enables Windows symlink semantics. This requires + an extra `stat` to determine if the destination of a symlink is a file or directory. If symlinking a directory + fails then we'll try making a junction instead. + +### copy.recurse(from, to, options) → Promise + +Reads all of the files in directory `from` and adds them to the `queue` +using `recurseWith` (by default `copy.item`). + +Options are: + +* queue - A [`run-queue`](https://npmjs.com/package/run-queue) object to add files found inside `from` to. +* recurseWith - (Default: `copy.item`) The function to call on each file after recursing into a directory. +* uid, gid - (Optional) If `getuid()` is `0` then this and gid will be used to + set the user and group of `to`. If uid is present then gid must be too. +* mode - (Optional) If set then `to` will have its perms set to `mode`. +* fs - (Default: `require('fs')`) The filesystem module to use. Can be used + to use `graceful-fs` or to inject a mock. +* getuid - (Default: `process.getuid`) A function that returns the current UID. Used to inject a mock. + +### copy.item(from, to, options) → Promise + +Copies some kind of `from` to destination `to`. This looks at the filetype +and calls `copy.file`, `copy.symlink` or `copy.recurse` as appropriate. + +Symlink copies are queued with a priority such that they happen after all +file and directory copies as you can't create a junction on windows to a +file that doesn't exist yet. + +Options are: + +* top - The top level the copy is being run from. This is used to determine + if the symlink destination is within the set of files we're copying or + outside it. +* queue - The [`run-queue`](https://npmjs.com/package/run-queue) object to + pass to `copy.recurse` if `from` is a directory. +* recurseWith - (Default: `copy.item`) The function to call on each file after recursing into a directory. +* uid, gid - (Optional) If `getuid()` is `0` then this and gid will be used to + set the user and group of `to`. If uid is present then gid must be too. +* mode - (Optional) If set then `to` will have its perms set to `mode`. +* fs - (Default: `require('fs')`) The filesystem module to use. Can be used + to use `graceful-fs` or to inject a mock. +* getuid - (Default: `process.getuid`) A function that returns the current UID. Used to inject a mock. +* isWindows - (Default: `process.platform === 'win32'`) If true enables Windows symlink semantics. This requires + an extra `stat` to determine if the destination of a symlink is a file or directory. If symlinking a directory + fails then we'll try making a junction instead. +* Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. +* writeStreamAtomic - (Default `require('fs-write-stream-atomic')`) The + implementation of `writeStreamAtomic` to use. Used to inject a mock. diff --git a/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/copy.js b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/copy.js new file mode 100644 index 00000000000..7455928a98c --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/copy.js @@ -0,0 +1,224 @@ +'use strict' +module.exports = copy +module.exports.item = copyItem +module.exports.recurse = recurseDir +module.exports.symlink = copySymlink +module.exports.file = copyFile + +var nodeFs = require('fs') +var path = require('path') +var validate = require('aproba') +var stockWriteStreamAtomic = require('fs-write-stream-atomic') +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var isWindows = require('./is-windows') +var RunQueue = require('run-queue') +var extend = Object.assign || require('util')._extend + +function promisify (Promise, fn) { + return function () { + var args = [].slice.call(arguments) + return new Promise(function (resolve, reject) { + return fn.apply(null, args.concat(function (err, value) { + if (err) { + reject(err) + } else { + resolve(value) + } + })) + }) + } +} + +function copy (from, to, opts) { + validate('SSO|SS', arguments) + opts = extend({}, opts || {}) + + var Promise = opts.Promise || global.Promise + var fs = opts.fs || nodeFs + + if (opts.isWindows == null) opts.isWindows = isWindows + if (!opts.Promise) opts.Promise = Promise + if (!opts.fs) opts.fs = fs + if (!opts.recurseWith) opts.recurseWith = copyItem + if (!opts.lstat) opts.lstat = promisify(opts.Promise, fs.lstat) + if (!opts.stat) opts.stat = promisify(opts.Promise, fs.stat) + if (!opts.chown) opts.chown = promisify(opts.Promise, fs.chown) + if (!opts.readdir) opts.readdir = promisify(opts.Promise, fs.readdir) + if (!opts.readlink) opts.readlink = promisify(opts.Promise, fs.readlink) + if (!opts.symlink) opts.symlink = promisify(opts.Promise, fs.symlink) + if (!opts.chmod) opts.chmod = promisify(opts.Promise, fs.chmod) + + opts.top = from + opts.mkdirpAsync = promisify(opts.Promise, mkdirp) + var rimrafAsync = promisify(opts.Promise, rimraf) + + var queue = new RunQueue({ + maxConcurrency: opts.maxConcurrency, + Promise: Promise + }) + opts.queue = queue + + queue.add(0, copyItem, [from, to, opts]) + + return queue.run().catch(function (err) { + // if the target already exists don't clobber it + if (err.code === 'EEXIST' || err.code === 'EPERM') { + return passThroughError() + } else { + return remove(to).then(passThroughError, passThroughError) + } + function passThroughError () { + return Promise.reject(err) + } + }) + + function remove (target) { + var opts = { + unlink: fs.unlink, + chmod: fs.chmod, + stat: fs.stat, + lstat: fs.lstat, + rmdir: fs.rmdir, + readdir: fs.readdir, + glob: false + } + return rimrafAsync(target, opts) + } +} + +function copyItem (from, to, opts) { + validate('SSO', [from, to, opts]) + var fs = opts.fs || nodeFs + var Promise = opts.Promise || global.Promise + var lstat = opts.lstat || promisify(Promise, fs.lstat) + + return lstat(to).then(function () { + return Promise.reject(eexists(from, to)) + }, function (err) { + if (err && err.code !== 'ENOENT') return Promise.reject(err) + return lstat(from) + }).then(function (fromStat) { + var cmdOpts = extend(extend({}, opts), fromStat) + if (fromStat.isDirectory()) { + return recurseDir(from, to, cmdOpts) + } else if (fromStat.isSymbolicLink()) { + opts.queue.add(1, copySymlink, [from, to, cmdOpts]) + } else if (fromStat.isFile()) { + return copyFile(from, to, cmdOpts) + } else if (fromStat.isBlockDevice()) { + return Promise.reject(eunsupported(from + " is a block device, and we don't know how to copy those.")) + } else if (fromStat.isCharacterDevice()) { + return Promise.reject(eunsupported(from + " is a character device, and we don't know how to copy those.")) + } else if (fromStat.isFIFO()) { + return Promise.reject(eunsupported(from + " is a FIFO, and we don't know how to copy those.")) + } else if (fromStat.isSocket()) { + return Promise.reject(eunsupported(from + " is a socket, and we don't know how to copy those.")) + } else { + return Promise.reject(eunsupported("We can't tell what " + from + " is and so we can't copy it.")) + } + }) +} + +function recurseDir (from, to, opts) { + validate('SSO', [from, to, opts]) + var recurseWith = opts.recurseWith || copyItem + var fs = opts.fs || nodeFs + var chown = opts.chown || promisify(Promise, fs.chown) + var readdir = opts.readdir || promisify(Promise, fs.readdir) + + return opts.mkdirpAsync(to, {fs: fs, mode: opts.mode}).then(function () { + var getuid = opts.getuid || process.getuid + if (getuid && opts.uid != null && getuid() === 0) { + return chown(to, opts.uid, opts.gid) + } + }).then(function () { + return readdir(from) + }).then(function (files) { + files.forEach(function (file) { + opts.queue.add(0, recurseWith, [path.join(from, file), path.join(to, file), opts]) + }) + }) +} + +function copySymlink (from, to, opts) { + validate('SSO', [from, to, opts]) + var fs = opts.fs || nodeFs + var readlink = opts.readlink || promisify(Promise, fs.readlink) + var stat = opts.stat || promisify(Promise, fs.symlink) + var symlink = opts.symlink || promisify(Promise, fs.symlink) + var Promise = opts.Promise || global.Promise + + return readlink(from).then(function (fromDest) { + var absoluteDest = path.resolve(path.dirname(from), fromDest) + // Treat absolute paths that are inside the tree we're + // copying as relative. This necessary to properly support junctions + // on windows (which are always absolute) but is also DWIM with symlinks. + var relativeDest = path.relative(opts.top, absoluteDest) + var linkFrom = relativeDest.substr(0, 2) === '..' ? fromDest : path.relative(path.dirname(from), absoluteDest) + if (opts.isWindows) { + return stat(absoluteDest).catch(function () { return null }).then(function (destStat) { + var isDir = destStat && destStat.isDirectory() + var type = isDir ? 'dir' : 'file' + return symlink(linkFrom, to, type).catch(function (err) { + if (type === 'dir') { + return symlink(linkFrom, to, 'junction') + } else { + return Promise.reject(err) + } + }) + }) + } else { + return symlink(linkFrom, to) + } + }) +} + +function copyFile (from, to, opts) { + validate('SSO', [from, to, opts]) + var fs = opts.fs || nodeFs + var writeStreamAtomic = opts.writeStreamAtomic || stockWriteStreamAtomic + var Promise = opts.Promise || global.Promise + var chmod = opts.chmod || promisify(Promise, fs.chmod) + + var writeOpts = {} + var getuid = opts.getuid || process.getuid + if (getuid && opts.uid != null && getuid() === 0) { + writeOpts.chown = { + uid: opts.uid, + gid: opts.gid + } + } + + return new Promise(function (resolve, reject) { + var errored = false + function onError (err) { + errored = true + reject(err) + } + fs.createReadStream(from) + .once('error', onError) + .pipe(writeStreamAtomic(to, writeOpts)) + .once('error', onError) + .once('close', function () { + if (errored) return + if (opts.mode != null) { + resolve(chmod(to, opts.mode)) + } else { + resolve() + } + }) + }) +} + +function eexists (from, to) { + var err = new Error('Could not move ' + from + ' to ' + to + ': destination already exists.') + err.code = 'EEXIST' + return err +} + +function eunsupported (msg) { + var err = new Error(msg) + err.code = 'EUNSUPPORTED' + return err +} diff --git a/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/is-windows.js b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/is-windows.js new file mode 100644 index 00000000000..8a991d54f5d --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/is-windows.js @@ -0,0 +1,2 @@ +'use strict' +module.exports = process.platform === 'win32' diff --git a/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/package.json b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/package.json new file mode 100644 index 00000000000..26fbea6e96d --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/@npmcorp/copy/package.json @@ -0,0 +1,103 @@ +{ + "_args": [ + [ + { + "raw": "@npmcorp/copy@^1.0.0", + "scope": "@npmcorp", + "escapedName": "@npmcorp%2fcopy", + "name": "@npmcorp/copy", + "rawSpec": "^1.0.0", + "spec": ">=1.0.0 <2.0.0", + "type": "range" + }, + "/Users/rebecca/code/npm/node_modules/@npmcorp/move" + ] + ], + "_from": "@npmcorp/copy@>=1.0.0 <2.0.0", + "_id": "@npmcorp/copy@1.0.0", + "_inCache": true, + "_location": "/@npmcorp/move/@npmcorp/copy", + "_nodeVersion": "4.6.1", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/copy-1.0.0.tgz_1488166712044_0.09934459743089974" + }, + "_npmUser": { + "name": "iarna", + "email": "me@re-becca.org" + }, + "_npmVersion": "4.4.0", + "_phantomChildren": {}, + "_requested": { + "raw": "@npmcorp/copy@^1.0.0", + "scope": "@npmcorp", + "escapedName": "@npmcorp%2fcopy", + "name": "@npmcorp/copy", + "rawSpec": "^1.0.0", + "spec": ">=1.0.0 <2.0.0", + "type": "range" + }, + "_requiredBy": [ + "/@npmcorp/move" + ], + "_resolved": "https://registry.npmjs.org/@npmcorp/copy/-/copy-1.0.0.tgz", + "_shasum": "ffa8003a144c8340858d9d5998895210d1fa1e12", + "_shrinkwrap": null, + "_spec": "@npmcorp/copy@^1.0.0", + "_where": "/Users/rebecca/code/npm/node_modules/@npmcorp/move", + "author": { + "name": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org/" + }, + "bugs": { + "url": "https://github.com/npm/npmcorp-copy/issues" + }, + "dependencies": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "description": "Copy files, directories and links, preserving ownership and perms", + "devDependencies": { + "standard": "^8.6.0", + "tacks": "^1.2.6", + "tap": "^10.1.1" + }, + "directories": { + "test": "test" + }, + "dist": { + "shasum": "ffa8003a144c8340858d9d5998895210d1fa1e12", + "tarball": "https://registry.npmjs.org/@npmcorp/copy/-/copy-1.0.0.tgz" + }, + "files": [ + "copy.js", + "is-windows.js" + ], + "gitHead": "731abdc0e3fd2f297e9423620524b6be38c0ef20", + "homepage": "https://www.npmjs.com/package/@npmcorp/copy", + "keywords": [], + "license": "ISC", + "main": "copy.js", + "maintainers": [ + { + "name": "iarna", + "email": "me@re-becca.org" + } + ], + "name": "@npmcorp/copy", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+https://github.com/npm/npmcorp-copy.git" + }, + "scripts": { + "test": "standard && tap --coverage test" + }, + "version": "1.0.0" +} diff --git a/node_modules/@npmcorp/move/node_modules/run-queue/README.md b/node_modules/@npmcorp/move/node_modules/run-queue/README.md new file mode 100644 index 00000000000..e84214d1f0a --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/run-queue/README.md @@ -0,0 +1,86 @@ +# run-queue + +A promise based, dynamic priority queue runner, with concurrency limiting. + +```js +const RunQueue = require('run-queue') + +const queue = new RunQueue({ + maxConcurrency: 1 +}) + +queue.add(1, example, [-1]) +for (let ii = 0; ii < 5; ++ii) { + queue.add(0, example, [ii]) +} +const finished = [] +queue.run().then( + console.log(finished) +}) + +function example (num, next) { + setTimeout(() => { + finished.push(num) + next() + }, 5 - Math.abs(num)) +} +``` + +would output + +``` +[ 0, 1, 2, 3, 4, -1 ] +``` + +If you bump concurrency to `2`, then you get: + +``` +[ 1, 0, 3, 2, 4, -1 ] +``` + +The concurrency means that they don't finish in order, because some take +longer than others. Each priority level must finish entirely before the +next priority level is run. See +[PRIORITIES](https://github.com/iarna/run-queue#priorities) below. This is +even true if concurrency is set high enough that all of the regular queue +can execute at once, for instance, with `maxConcurrency: 10`: + +``` +[ 4, 3, 2, 1, 0, -1 ] +``` + +## API + +### const queue = new RunQueue(options) + +Create a new queue. Options may contain: + +* maxConcurrency - (Default: `1`) The maximum number of jobs to execute at once. +* Promise - (Default: global.Promise) The promise implementation to use. + +### queue.add (prio, fn, args) + +Add a new job to the end of the queue at priority `prio` that will run `fn` +with `args`. If `fn` is async then it should return a Promise. + +### queue.run () + +Start running the job queue. Returns a Promise that resolves when either +all the jobs are complete or a job ends in error (throws or returns a +rejected promise). If a job ended in error then this Promise will be rejected +with that error and no further queue running will be done. + +## PRIORITIES + +Priorities are any integer value >= 0. + +Lowest is executed first. + +Priorities essentially represent distinct job queues. All jobs in a queue +must complete before the next highest priority job queue is executed. + +This means that if you have two queues, `0` and `1` then ALL jobs in `0` +must complete before ANY execute in `1`. If you add new `0` level jobs +while `1` level jobs are running then it will switch back processing the `0` +queue and won't execute any more `1` jobs till all of the new `0` jobs +complete. diff --git a/node_modules/@npmcorp/move/node_modules/run-queue/package.json b/node_modules/@npmcorp/move/node_modules/run-queue/package.json new file mode 100644 index 00000000000..541162d05f9 --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/run-queue/package.json @@ -0,0 +1,97 @@ +{ + "_args": [ + [ + { + "raw": "run-queue@^1.0.3", + "scope": null, + "escapedName": "run-queue", + "name": "run-queue", + "rawSpec": "^1.0.3", + "spec": ">=1.0.3 <2.0.0", + "type": "range" + }, + "/Users/rebecca/code/npm/node_modules/@npmcorp/move" + ] + ], + "_from": "run-queue@>=1.0.3 <2.0.0", + "_id": "run-queue@1.0.3", + "_inCache": true, + "_location": "/@npmcorp/move/run-queue", + "_nodeVersion": "0.12.13", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/run-queue-1.0.3.tgz_1488161288446_0.14580746600404382" + }, + "_npmUser": { + "name": "iarna", + "email": "me@re-becca.org" + }, + "_npmVersion": "2.15.0", + "_phantomChildren": {}, + "_requested": { + "raw": "run-queue@^1.0.3", + "scope": null, + "escapedName": "run-queue", + "name": "run-queue", + "rawSpec": "^1.0.3", + "spec": ">=1.0.3 <2.0.0", + "type": "range" + }, + "_requiredBy": [ + "/@npmcorp/move", + "/@npmcorp/move/@npmcorp/copy" + ], + "_resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "_shasum": "e848396f057d223f24386924618e25694161ec47", + "_shrinkwrap": null, + "_spec": "run-queue@^1.0.3", + "_where": "/Users/rebecca/code/npm/node_modules/@npmcorp/move", + "author": { + "name": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org/" + }, + "bugs": { + "url": "https://github.com/iarna/run-queue/issues" + }, + "dependencies": { + "aproba": "^1.1.1" + }, + "description": "A promise based, dynamic priority queue runner, with concurrency limiting.", + "devDependencies": { + "standard": "^8.6.0", + "tap": "^10.2.0" + }, + "directories": { + "test": "test" + }, + "dist": { + "shasum": "e848396f057d223f24386924618e25694161ec47", + "tarball": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz" + }, + "files": [ + "queue.js" + ], + "gitHead": "3c315b738d0578c2c54be2beb0469d00ccf1dc25", + "homepage": "https://npmjs.com/package/run-queue", + "keywords": [], + "license": "ISC", + "main": "queue.js", + "maintainers": [ + { + "name": "iarna", + "email": "me@re-becca.org" + } + ], + "name": "run-queue", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+https://github.com/iarna/run-queue.git" + }, + "scripts": { + "test": "standard && tap -J test" + }, + "version": "1.0.3" +} diff --git a/node_modules/@npmcorp/move/node_modules/run-queue/queue.js b/node_modules/@npmcorp/move/node_modules/run-queue/queue.js new file mode 100644 index 00000000000..500bf293d30 --- /dev/null +++ b/node_modules/@npmcorp/move/node_modules/run-queue/queue.js @@ -0,0 +1,95 @@ +'use strict' +module.exports = RunQueue + +var validate = require('aproba') + +function RunQueue (opts) { + validate('Z|O', [opts]) + if (!opts) opts = {} + this.finished = false + this.inflight = 0 + this.maxConcurrency = opts.maxConcurrency || 1 + this.queued = 0 + this.queue = [] + this.currentPrio = null + this.currentQueue = null + this.Promise = opts.Promise || global.Promise + this.deferred = {} +} + +RunQueue.prototype = {} + +RunQueue.prototype.run = function () { + if (arguments.length !== 0) throw new Error('RunQueue.run takes no arguments') + var self = this + var deferred = this.deferred + if (!deferred.promise) { + deferred.promise = new this.Promise(function (resolve, reject) { + deferred.resolve = resolve + deferred.reject = reject + self._runQueue() + }) + } + return deferred.promise +} + +RunQueue.prototype._runQueue = function () { + var self = this + + while ((this.inflight < this.maxConcurrency) && this.queued) { + if (!this.currentQueue || this.currentQueue.length === 0) { + // wait till the current priority is entirely processed before + // starting a new one + if (this.inflight) return + var prios = Object.keys(this.queue) + for (var ii = 0; ii < prios.length; ++ii) { + var prioQueue = this.queue[prios[ii]] + if (prioQueue.length) { + this.currentQueue = prioQueue + this.currentPrio = prios[ii] + break + } + } + } + + --this.queued + ++this.inflight + var next = this.currentQueue.shift() + var args = next.args || [] + + // we explicitly construct a promise here so that queue items can throw + // or immediately return to resolve + var queueEntry = new this.Promise(function (resolve) { + resolve(next.cmd.apply(null, args)) + }) + + queueEntry.then(function () { + --self.inflight + if (self.finished) return + if (self.queued <= 0 && self.inflight <= 0) { + self.finished = true + self.deferred.resolve() + } + self._runQueue() + }, function (err) { + self.finished = true + self.deferred.reject(err) + }) + } +} + +RunQueue.prototype.add = function (prio, cmd, args) { + if (this.finished) throw new Error("Can't add to a finished queue. Create a new queue.") + if (Math.abs(Math.floor(prio)) !== prio) throw new Error('Priorities must be a positive integer value.') + validate('NFA|NFZ', [prio, cmd, args]) + prio = Number(prio) + if (!this.queue[prio]) this.queue[prio] = [] + ++this.queued + this.queue[prio].push({cmd: cmd, args: args}) + // if this priority is higher than the one we're currently processing, + // switch back to processing its queue. + if (this.currentPrio > prio) { + this.currentQueue = this.queue[prio] + this.currentPrio = prio + } +} diff --git a/node_modules/@npmcorp/move/package.json b/node_modules/@npmcorp/move/package.json new file mode 100644 index 00000000000..603489fbda8 --- /dev/null +++ b/node_modules/@npmcorp/move/package.json @@ -0,0 +1,110 @@ +{ + "_args": [ + [ + { + "raw": "@npmcorp/move", + "scope": "@npmcorp", + "escapedName": "@npmcorp%2fmove", + "name": "@npmcorp/move", + "rawSpec": "", + "spec": "latest", + "type": "tag" + }, + "/Users/rebecca/code/npm" + ] + ], + "_from": "@npmcorp/move@latest", + "_id": "@npmcorp/move@1.0.0", + "_inCache": true, + "_location": "/@npmcorp/move", + "_nodeVersion": "4.6.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/move-1.0.0.tgz_1488167362493_0.06707011582329869" + }, + "_npmUser": { + "name": "iarna", + "email": "me@re-becca.org" + }, + "_npmVersion": "4.4.0", + "_phantomChildren": { + "aproba": "1.1.1", + "fs-write-stream-atomic": "1.0.8", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "rimraf": "2.6.0" + }, + "_requested": { + "raw": "@npmcorp/move", + "scope": "@npmcorp", + "escapedName": "@npmcorp%2fmove", + "name": "@npmcorp/move", + "rawSpec": "", + "spec": "latest", + "type": "tag" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "file:../move", + "_shasum": "eccb118645704056e5cd8d8bfea8100feae7ea47", + "_shrinkwrap": null, + "_spec": "@npmcorp/move", + "_where": "/Users/rebecca/code/npm", + "author": { + "name": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org/" + }, + "bugs": { + "url": "https://github.com/npm/npmcorp-move/issues" + }, + "dependencies": { + "@npmcorp/copy": "^1.0.0", + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "description": "Move files or directories with rename, falling back to recursive rename/copy on EXDEV errors.", + "devDependencies": { + "standard": "^8.6.0", + "tacks": "^1.2.6", + "tap": "^10.1.1" + }, + "directories": { + "test": "test" + }, + "dist": { + "shasum": "eccb118645704056e5cd8d8bfea8100feae7ea47", + "tarball": "https://registry.npmjs.org/@npmcorp/move/-/move-1.0.0.tgz" + }, + "files": [ + "move.js", + "is-windows.js" + ], + "gitHead": "18a3164b47457b8cfea62a91bb20e4401ab2b679", + "homepage": "https://www.npmjs.com/package/@npmcorp/move", + "keywords": [], + "license": "ISC", + "main": "move.js", + "maintainers": [ + { + "name": "iarna", + "email": "me@re-becca.org" + } + ], + "name": "@npmcorp/move", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+https://github.com/npm/npmcorp-move.git" + }, + "scripts": { + "test": "standard && tap --coverage test" + }, + "version": "1.0.0" +} diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 67d25b8084b..76d1b646dfe 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2,6 +2,23 @@ "name": "npm", "version": "4.4.0", "dependencies": { + "@npmcorp/move": { + "version": "1.0.0", + "from": "@npmcorp/move@latest", + "resolved": "file:../move", + "dependencies": { + "@npmcorp/copy": { + "version": "1.0.0", + "from": "@npmcorp/copy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@npmcorp/copy/-/copy-1.0.0.tgz" + }, + "run-queue": { + "version": "1.0.3", + "from": "run-queue@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz" + } + } + }, "abbrev": { "version": "1.1.0", "from": "abbrev@latest", @@ -37,6 +54,11 @@ "from": "asap@2.0.5", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz" }, + "bluebird": { + "version": "3.4.7", + "from": "bluebird@latest", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz" + }, "chownr": { "version": "1.0.1", "from": "chownr@>=1.0.1 <1.1.0", diff --git a/package.json b/package.json index 4f1f35f99f5..2ed133833d9 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "main": "./lib/npm.js", "bin": "./bin/npm-cli.js", "dependencies": { + "@npmcorp/move": "~1.0.0", "JSONStream": "~1.3.0", "abbrev": "~1.1.0", "ansi-regex": "~2.1.1", @@ -38,6 +39,7 @@ "aproba": "~1.1.1", "archy": "~1.0.0", "asap": "~2.0.5", + "bluebird": "~3.4.7", "chownr": "~1.0.1", "cmd-shim": "~2.0.2", "columnify": "~1.5.4", @@ -110,6 +112,7 @@ "write-file-atomic": "~1.3.1" }, "bundleDependencies": [ + "@npmcorp/move", "abbrev", "ansi-regex", "ansicolors", @@ -117,6 +120,7 @@ "aproba", "archy", "asap", + "bluebird", "chownr", "cmd-shim", "columnify",