diff options
author | Ruy Adorno <ruyadorno@hotmail.com> | 2021-01-15 22:48:21 +0300 |
---|---|---|
committer | Ruy Adorno <ruyadorno@hotmail.com> | 2021-01-15 22:50:08 +0300 |
commit | e5ce6bbbad82b85c8e74a4558503513e4f337476 (patch) | |
tree | 0c4e3edef62d9e85749c46dd2a952154c6543c10 /node_modules | |
parent | 653769de359b8d24f0d17b8e7e426708f49cadb8 (diff) |
chore: update missing files from node_modules
- @npmcli/arborist@2.0.5
- @npmcli/metavuln-calculator@1.0.1
- move-file@1.1.0
Diffstat (limited to 'node_modules')
-rw-r--r-- | node_modules/@npmcli/arborist/lib/arborist/index.js | 1 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/lib/arborist/load-actual.js | 15 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/lib/arborist/reify.js | 2 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/lib/diff.js | 5 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/lib/edge.js | 24 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/lib/index.js | 1 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/lib/node.js | 74 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/lib/shrinkwrap.js | 8 | ||||
-rw-r--r-- | node_modules/@npmcli/arborist/package.json | 9 | ||||
-rw-r--r-- | node_modules/@npmcli/metavuln-calculator/lib/advisory.js | 131 | ||||
-rw-r--r-- | node_modules/@npmcli/metavuln-calculator/lib/load-worker.js | 29 | ||||
-rw-r--r-- | node_modules/@npmcli/metavuln-calculator/lib/test-version.js | 59 | ||||
-rw-r--r-- | node_modules/@npmcli/metavuln-calculator/package.json | 2 | ||||
-rw-r--r-- | node_modules/@npmcli/move-file/README.md | 9 | ||||
-rw-r--r-- | node_modules/@npmcli/move-file/index.js | 83 | ||||
-rw-r--r-- | node_modules/@npmcli/move-file/package.json | 5 |
16 files changed, 421 insertions, 36 deletions
diff --git a/node_modules/@npmcli/arborist/lib/arborist/index.js b/node_modules/@npmcli/arborist/lib/arborist/index.js index 4c7e96da4..6c46656eb 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/index.js +++ b/node_modules/@npmcli/arborist/lib/arborist/index.js @@ -53,6 +53,7 @@ class Arborist extends Base { ...options, path: options.path || '.', cache: options.cache || `${homedir()}/.npm/_cacache`, + packumentCache: new Map(), } this.cache = resolve(this.options.cache) this.path = resolve(this.options.path) diff --git a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js index abf39e5dc..49e76e265 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js +++ b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js @@ -111,7 +111,7 @@ module.exports = cls => class ActualLoader extends cls { pkg: {}, global, }) - return this[_loadActualActually]({root, ignoreMissing}) + return this[_loadActualActually]({root, ignoreMissing, global}) } // not in global mode, hidden lockfile is allowed, load root pkg too @@ -154,7 +154,7 @@ module.exports = cls => class ActualLoader extends cls { return this[_actualTree] } - async [_loadActualActually] ({ root, ignoreMissing }) { + async [_loadActualActually] ({ root, ignoreMissing, global }) { await this[_loadFSTree](this[_actualTree]) if (!ignoreMissing) await this[_findMissingEdges]() @@ -162,6 +162,17 @@ module.exports = cls => class ActualLoader extends cls { this[_transplant](root) await this[_loadWorkspaces](this[_actualTree]) + if (global) { + // need to depend on the children, or else all of them + // will end up being flagged as extraneous, since the + // global root isn't a "real" project + const tree = this[_actualTree] + const actualRoot = tree.isLink ? tree.target : tree + const { dependencies = {} } = actualRoot.package + for (const name of actualRoot.children.keys()) + dependencies[name] = dependencies[name] || '*' + actualRoot.package = { ...actualRoot.package, dependencies } + } // only reset root flags if we're not re-rooting, otherwise leave as-is calcDepFlags(this[_actualTree], !root) return this[_actualTree] diff --git a/node_modules/@npmcli/arborist/lib/arborist/reify.js b/node_modules/@npmcli/arborist/lib/arborist/reify.js index 661d879eb..5375b6df4 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/node_modules/@npmcli/arborist/lib/arborist/reify.js @@ -136,7 +136,7 @@ module.exports = cls => class Reifier extends cls { async [_validatePath] () { // don't create missing dirs on dry runs - if (this[_packageLockOnly] || this[_dryRun] || this[_global]) + if (this[_packageLockOnly] || this[_dryRun]) return await mkdirp(resolve(this.path)) diff --git a/node_modules/@npmcli/arborist/lib/diff.js b/node_modules/@npmcli/arborist/lib/diff.js index 1864a3ea1..ada67f816 100644 --- a/node_modules/@npmcli/arborist/lib/diff.js +++ b/node_modules/@npmcli/arborist/lib/diff.js @@ -72,6 +72,11 @@ const allChildren = node => { if (!node) return new Map() + // if the node is a global root, and also a link, then what we really + // want is to traverse the target's children + if (node.global && node.isRoot && node.isLink) + return allChildren(node.target) + const kids = new Map() for (const n of [node, ...node.fsChildren]) { for (const kid of n.children.values()) diff --git a/node_modules/@npmcli/arborist/lib/edge.js b/node_modules/@npmcli/arborist/lib/edge.js index 0e30f4633..c5f00faff 100644 --- a/node_modules/@npmcli/arborist/lib/edge.js +++ b/node_modules/@npmcli/arborist/lib/edge.js @@ -1,6 +1,7 @@ // An edge in the dependency graph // Represents a dependency relationship of some kind +const util = require('util') const npa = require('npm-package-arg') const depValid = require('./dep-valid.js') const _from = Symbol('_from') @@ -24,6 +25,21 @@ const types = new Set([ 'workspace', ]) +class ArboristEdge {} +const printableEdge = (edge) => { + const edgeFrom = edge.from && edge.from.location + const edgeTo = edge.to && edge.to.location + + return Object.assign(new ArboristEdge(), { + name: edge.name, + spec: edge.spec, + type: edge.type, + ...(edgeFrom != null ? { from: edgeFrom } : {}), + ...(edgeTo ? { to: edgeTo } : {}), + ...(edge.error ? { error: edge.error } : {}), + }) +} + class Edge { constructor (options) { const { type, name, spec, accept, from } = options @@ -185,6 +201,14 @@ class Edge { get to () { return this[_to] } + + toJSON () { + return printableEdge(this) + } + + [util.inspect.custom] () { + return this.toJSON() + } } Edge.types = [...types] diff --git a/node_modules/@npmcli/arborist/lib/index.js b/node_modules/@npmcli/arborist/lib/index.js index 830a88a5f..fd7d88172 100644 --- a/node_modules/@npmcli/arborist/lib/index.js +++ b/node_modules/@npmcli/arborist/lib/index.js @@ -2,5 +2,6 @@ module.exports = require('./arborist/index.js') module.exports.Arborist = module.exports module.exports.Node = require('./node.js') module.exports.Link = require('./link.js') +module.exports.Edge = require('./edge.js') // XXX export the other classes, too. shrinkwrap, diff, etc. // they're handy! diff --git a/node_modules/@npmcli/arborist/lib/node.js b/node_modules/@npmcli/arborist/lib/node.js index 6e243c049..396bcb58a 100644 --- a/node_modules/@npmcli/arborist/lib/node.js +++ b/node_modules/@npmcli/arborist/lib/node.js @@ -40,6 +40,7 @@ const treeCheck = require('./tree-check.js') const walkUp = require('walk-up-path') const {resolve, relative, dirname, basename} = require('path') +const util = require('util') const _package = Symbol('_package') const _parent = Symbol('_parent') const _target = Symbol.for('_target') @@ -63,6 +64,71 @@ const _meta = Symbol('_meta') const relpath = require('./relpath.js') const consistentResolve = require('./consistent-resolve.js') +// helper function to output a clearer visualization +// of the current node and its descendents +class ArboristNode {} + +const printableTree = (tree, path = []) => + (path.includes(tree) ? { location: tree.location } : (path.push(tree), Object.assign(new ArboristNode(), { + name: tree.name, + ...(tree.package && tree.package.version + ? { version: tree.package.version } + : {}), + location: tree.location, + path: tree.path, + realpath: tree.realpath, + ...(tree.isLink ? { target: printableTree(tree.target, path) } : {}), + ...(tree.resolved != null ? { resolved: tree.resolved } : {}), + ...(tree.extraneous ? { extraneous: true } : { + ...(tree.dev ? { dev: true } : {}), + ...(tree.optional ? { optional: true } : {}), + ...(tree.devOptional && !tree.dev && !tree.optional + ? { devOptional: true } : {}), + ...(tree.peer ? { peer: true } : {}), + }), + ...(tree.inBundle ? { bundled: true } : {}), + // handle top-level tree error + ...(tree.error + ? { + error: { + code: tree.error.code, + ...(tree.error.path + ? { path: tree.error.path } + : {}), + }, + } : {}), + // handle errors for each node + ...(tree.errors && tree.errors.length + ? { + errors: tree.errors.map(error => ({ + code: error.code, + ...(error.path + ? { path: error.path } + : {}), + })), + } : {}), + ...(tree.edgesIn && tree.edgesIn.size ? { + edgesIn: new Set([...tree.edgesIn] + .sort((a, b) => a.from.location.localeCompare(b.from.location))), + } : {}), + ...(tree.edgesOut && tree.edgesOut.size ? { + edgesOut: new Map([...tree.edgesOut.entries()] + .sort((a, b) => a[0].localeCompare(b[0]))), + } : {}), + ...(tree.fsChildren && tree.fsChildren.size ? { + fsChildren: new Set([...tree.fsChildren] + .sort((a, b) => a.path.localeCompare(b.path)) + .map(tree => printableTree(tree, path))), + } : {}), + ...(tree.target || !tree.children || !tree.children.size + ? {} + : { + children: new Map([...tree.children.entries()] + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([name, tree]) => [name, printableTree(tree, path)])), + }), + }))) + class Node { constructor (options) { // NB: path can be null if it's a link target @@ -1145,6 +1211,14 @@ class Node { const base = scoped ? `${basename(d)}/${basename(rp)}` : basename(rp) return base === name && basename(nm) === 'node_modules' ? dir : false } + + toJSON () { + return printableTree(this) + } + + [util.inspect.custom] () { + return this.toJSON() + } } module.exports = Node diff --git a/node_modules/@npmcli/arborist/lib/shrinkwrap.js b/node_modules/@npmcli/arborist/lib/shrinkwrap.js index a454320a3..9254531e4 100644 --- a/node_modules/@npmcli/arborist/lib/shrinkwrap.js +++ b/node_modules/@npmcli/arborist/lib/shrinkwrap.js @@ -200,9 +200,9 @@ class Shrinkwrap { return s[_maybeStat]().then(([sw, lock]) => { s.filename = resolve(s.path, (s.hiddenLockfile ? 'node_modules/.package-lock' - : s.shrinkwrapOnly || sw && !lock ? 'npm-shrinkwrap' + : s.shrinkwrapOnly || sw ? 'npm-shrinkwrap' : 'package-lock') + '.json') - s.loadedFromDisk = sw || lock + s.loadedFromDisk = !!(sw || lock) s.type = basename(s.filename) return s }) @@ -353,14 +353,14 @@ class Shrinkwrap { // we don't need to load package-lock.json except for top of tree nodes, // only npm-shrinkwrap.json. return this[_maybeRead]().then(([sw, lock, yarn]) => { - const data = lock || sw || '' + const data = sw || lock || '' // use shrinkwrap only for deps, otherwise prefer package-lock // and ignore npm-shrinkwrap if both are present. // TODO: emit a warning here or something if both are present. this.filename = resolve(this.path, (this.hiddenLockfile ? 'node_modules/.package-lock' - : this.shrinkwrapOnly || sw && !lock ? 'npm-shrinkwrap' + : this.shrinkwrapOnly || sw ? 'npm-shrinkwrap' : 'package-lock') + '.json') this.type = basename(this.filename) diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index 6300a5e86..1a46daa19 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/node_modules/@npmcli/arborist/package.json @@ -1,12 +1,12 @@ { "name": "@npmcli/arborist", - "version": "2.0.3", + "version": "2.0.5", "description": "Manage node_modules trees", "dependencies": { "@npmcli/installed-package-contents": "^1.0.5", "@npmcli/map-workspaces": "^1.0.1", - "@npmcli/metavuln-calculator": "^1.0.0", - "@npmcli/move-file": "^1.0.1", + "@npmcli/metavuln-calculator": "^1.0.1", + "@npmcli/move-file": "^1.1.0", "@npmcli/name-from-folder": "^1.0.1", "@npmcli/node-gyp": "^1.0.1", "@npmcli/run-script": "^1.8.1", @@ -19,7 +19,8 @@ "npm-install-checks": "^4.0.0", "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", - "pacote": "^11.1.14", + "npm-registry-fetch": "^9.0.0", + "pacote": "^11.2.1", "parse-conflict-json": "^1.1.1", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", diff --git a/node_modules/@npmcli/metavuln-calculator/lib/advisory.js b/node_modules/@npmcli/metavuln-calculator/lib/advisory.js index 95bcc6755..15340f5dc 100644 --- a/node_modules/@npmcli/metavuln-calculator/lib/advisory.js +++ b/node_modules/@npmcli/metavuln-calculator/lib/advisory.js @@ -65,7 +65,7 @@ class Advisory { // load up the data from a cache entry and a fetched packument load (cached, packument) { - // basic data integrity gutchecks + // basic data integrity gutcheck if (!cached || typeof cached !== 'object') { throw new TypeError('invalid cached data, expected object') } @@ -148,7 +148,42 @@ class Advisory { } [_calculateRange] () { - const metavuln = this.vulnerableVersions.join(' || ').trim() + // calling semver.simplifyRange with a massive list of versions, and those + // versions all concatenated with `||` is a geometric CPU explosion! + // we can try to be a *little* smarter up front by doing x-y for all + // contiguous version sets in the list + const ranges = [] + this.versions = semver.sort(this.versions) + this.vulnerableVersions = semver.sort(this.vulnerableVersions) + for (let v = 0, vulnVer = 0; v < this.versions.length; v++) { + // figure out the vulnerable subrange + const vr = [this.versions[v]] + while (v < this.versions.length) { + if (this.versions[v] !== this.vulnerableVersions[vulnVer]) { + // we don't test prerelease versions, so just skip past it + if (/-/.test(this.versions[v])) { + v++ + continue + } + break + } + if (vr.length > 1) + vr[1] = this.versions[v] + else + vr.push(this.versions[v]) + v++ + vulnVer++ + } + // it'll either be just the first version, which means no overlap, + // or the start and end versions, which might be the same version + if (vr.length > 1) { + const tail = this.versions[this.versions.length - 1] + ranges.push(vr[1] === tail ? `>=${vr[0]}` + : vr[0] === vr[1] ? vr[0] + : vr.join(' - ')) + } + } + const metavuln = ranges.join(' || ').trim() this.range = !metavuln ? '<0.0.0-0' : semver.simplifyRange(this.versions, metavuln, semverOpt) } @@ -271,25 +306,99 @@ class Advisory { } for (const list of versionSets) { - const headVuln = this.testVersion(list[0]) - const tailVuln = this.testVersion(list[list.length - 1]) + // it's common to have version lists like: + // 1.0.0 + // 1.0.1-alpha.0 + // 1.0.1-alpha.1 + // ... + // 1.0.1-alpha.999 + // 1.0.1 + // 1.0.2-alpha.0 + // ... + // 1.0.2-alpha.99 + // 1.0.2 + // with a huge number of prerelease versions that are not installable + // anyway. + // If mid has a prerelease tag, and list[0] does not, then walk it + // back until we hit a non-prerelease version + // If mid has a prerelease tag, and list[list.length-1] does not, + // then walk it forward until we hit a version without a prerelease tag + // Similarly, if the head/tail is a prerelease, but there is a non-pr + // version in the list, then start there instead. + let h = 0 + const origHeadVuln = this.testVersion(list[h]) + while (h < list.length && /-/.test(String(list[h]))) + h++ + + // don't filter out the whole list! they might all be pr's + if (h === list.length) + h = 0 + else if (origHeadVuln) { + // if the original was vulnerable, assume so are all of these + for (let hh = 0; hh < h; hh++) + this[_markVulnerable](list[hh]) + } + + let t = list.length - 1 + const origTailVuln = this.testVersion(list[t]) + while (t > h && /-/.test(String(list[t]))) + t-- + + // don't filter out the whole list! might all be pr's + if (t === h) + t = list.length - 1 + else if (origTailVuln) { + // if original tail was vulnerable, assume these are as well + for (let tt = list.length - 1; tt > t; tt--) + this[_markVulnerable](list[tt]) + } + + const headVuln = h === 0 ? origHeadVuln + : this.testVersion(list[h]) + + const tailVuln = t === list.length - 1 ? origTailVuln + : this.testVersion(list[t]) + // if head and tail both vulnerable, whole list is thrown out if (headVuln && tailVuln) { - for (const v of list.slice(1, -1)) { - this[_markVulnerable](v) - } + for (let v = h; v < t; v++) + this[_markVulnerable](list[v]) continue } // if length is 2 or 1, then we marked them all already - if (list.length <= 2) + if (t < h + 2) continue const mid = Math.floor(list.length / 2) - // leave out the ends, since we tested those already - versionSets.add(list.slice(0, mid)) - versionSets.add(list.slice(mid)) + const pre = list.slice(0, mid) + const post = list.slice(mid) + + // if the parent list wasn't prereleases, then drop pr tags + // from end of the pre list, and beginning of the post list, + // marking as vulnerable if the midpoint item we picked is. + if (!/-/.test(String(pre[0]))) { + const midVuln = this.testVersion(pre[pre.length - 1]) + while (/-/.test(String(pre[pre.length-1]))) { + const v = pre.pop() + if (midVuln) + this[_markVulnerable](v) + } + } + + if (!/-/.test(String(post[post.length-1]))) { + const midVuln = this.testVersion(post[0]) + while (/-/.test(String(post[0]))) { + const v = post.shift() + if (midVuln) + this[_markVulnerable](v) + } + } + + versionSets.add(pre) + versionSets.add(post) } } } + module.exports = Advisory diff --git a/node_modules/@npmcli/metavuln-calculator/lib/load-worker.js b/node_modules/@npmcli/metavuln-calculator/lib/load-worker.js new file mode 100644 index 000000000..5c5797b4f --- /dev/null +++ b/node_modules/@npmcli/metavuln-calculator/lib/load-worker.js @@ -0,0 +1,29 @@ +// When Advisory.load() is called in the main thread, it spins up +// a worker thread to do the actual loading, because this can be +// a CPU-intensive operation which blocks the main thread otherwise. + +const { + Worker, + isMainThread, + parentPort, + workerData, +} = require('worker_threads') + +const Advisory = require('./advisory.js') +const load = async ({ name, source, options, cached, packument }) => { + const advisory = new Advisory(name, source, options) + // guard against infinite recursion, mostly for testing + advisory.inWorkerThread = true + await advisory.load(cached, packument) + parentPort.postMessage(advisory) +} + +if (isMainThread) + module.exports = load +else + load(workerData).catch(er => parentPort.postMessage({ + error: { + message: er.message, + stack: er.stack, + }, + })) diff --git a/node_modules/@npmcli/metavuln-calculator/lib/test-version.js b/node_modules/@npmcli/metavuln-calculator/lib/test-version.js new file mode 100644 index 000000000..211a46ddd --- /dev/null +++ b/node_modules/@npmcli/metavuln-calculator/lib/test-version.js @@ -0,0 +1,59 @@ +// called by the Advisory[_testVersion] method, and the test-versions worker +const semver = require('semver') +const semverOpt = { includePrerelease: true, loose: true } +const getDepSpec = require('./get-dep-spec.js') + +module.exports = opts => { + const { + version, + spec, + vulnerableVersions, + type, + range, + packument, + dependency, + source, + } = opts + + const sv = String(version) + if (vulnerableVersions.includes(sv)) + return true + + if (type === 'advisory') { + // advisory, just test range + return semver.satisfies(version, range, semverOpt) + } + + // check the dependency of version on the vulnerable dep + // if we got a version that's not in the packument, fall back on + // the spec provided, if possible. + const mani = packument.versions[version] || { + dependencies: { + [dependency]: spec, + }, + } + + if (!spec) + spec = getDepSpec(mani, dependency) + + // no dep, no vuln + if (spec === null) + return false + + // not a semver range, nothing we can hope to do about it + if (!semver.validRange(spec, semverOpt)) + return true + + const bd = mani.bundleDependencies + const bundled = bd && bd.includes(source.name) + // XXX if bundled, then semver.intersects() means vulnerable + // else, pick a manifest and see if it can't be avoided + // try to pick a version of the dep that isn't vulnerable + const avoid = source.range + + if (bundled) { + return semver.intersects(spec, avoid, semverOpt) + } + + return this[_source].testSpec(spec) +} diff --git a/node_modules/@npmcli/metavuln-calculator/package.json b/node_modules/@npmcli/metavuln-calculator/package.json index ec5eea5a4..7f643cbc5 100644 --- a/node_modules/@npmcli/metavuln-calculator/package.json +++ b/node_modules/@npmcli/metavuln-calculator/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/metavuln-calculator", - "version": "1.0.0", + "version": "1.0.1", "main": "lib/index.js", "files": [ "lib" diff --git a/node_modules/@npmcli/move-file/README.md b/node_modules/@npmcli/move-file/README.md index da682ebd5..8a5a57f0f 100644 --- a/node_modules/@npmcli/move-file/README.md +++ b/node_modules/@npmcli/move-file/README.md @@ -3,7 +3,7 @@ A fork of [move-file](https://github.com/sindresorhus/move-file) with compatibility with all node 10.x versions. -> Move a file +> Move a file (or directory) The built-in [`fs.rename()`](https://nodejs.org/api/fs.html#fs_fs_rename_oldpath_newpath_callback) @@ -18,6 +18,7 @@ would have expected `fs.rename()` to be. - Optionally prevent overwriting an existing file. - Creates non-existent destination directories for you. - Support for Node versions that lack built-in recursive `fs.mkdir()` +- Automatically recurses when source is a directory. ## Install @@ -48,13 +49,13 @@ Returns a `Promise` that resolves when the file has been moved. Type: `string` -File you want to move. +File, or directory, you want to move. #### destination Type: `string` -Where you want the file moved. +Where you want the file or directory moved. #### options @@ -65,4 +66,4 @@ Type: `object` Type: `boolean`\ Default: `true` -Overwrite existing destination file. +Overwrite existing destination file(s). diff --git a/node_modules/@npmcli/move-file/index.js b/node_modules/@npmcli/move-file/index.js index d1567d1f6..51f9535d3 100644 --- a/node_modules/@npmcli/move-file/index.js +++ b/node_modules/@npmcli/move-file/index.js @@ -1,4 +1,5 @@ -const { dirname } = require('path') +const { dirname, join, resolve, relative, isAbsolute } = require('path') +const rimraf_ = require('rimraf') const { promisify } = require('util') const { access: access_, @@ -7,14 +8,31 @@ const { copyFileSync, unlink: unlink_, unlinkSync, + readdir: readdir_, + readdirSync, rename: rename_, renameSync, + stat: stat_, + statSync, + lstat: lstat_, + lstatSync, + symlink: symlink_, + symlinkSync, + readlink: readlink_, + readlinkSync } = require('fs') const access = promisify(access_) const copyFile = promisify(copyFile_) const unlink = promisify(unlink_) +const readdir = promisify(readdir_) const rename = promisify(rename_) +const stat = promisify(stat_) +const lstat = promisify(lstat_) +const symlink = promisify(symlink_) +const readlink = promisify(readlink_) +const rimraf = promisify(rimraf_) +const rimrafSync = rimraf_.sync const mkdirp = require('mkdirp') @@ -36,7 +54,7 @@ const pathExistsSync = path => { } } -module.exports = async (source, destination, options = {}) => { +const moveFile = async (source, destination, options = {}, root = true, symlinks = []) => { if (!source || !destination) { throw new TypeError('`source` and `destination` file required') } @@ -56,15 +74,38 @@ module.exports = async (source, destination, options = {}) => { await rename(source, destination) } catch (error) { if (error.code === 'EXDEV') { - await copyFile(source, destination) - await unlink(source) + const sourceStat = await lstat(source) + if (sourceStat.isDirectory()) { + const files = await readdir(source) + await Promise.all(files.map((file) => moveFile(join(source, file), join(destination, file), options, false, symlinks))) + } else if (sourceStat.isSymbolicLink()) { + symlinks.push({ source, destination }) + } else { + await copyFile(source, destination) + } } else { throw error } } + + if (root) { + await Promise.all(symlinks.map(async ({ source, destination }) => { + let target = await readlink(source) + // junction symlinks in windows will be absolute paths, so we need to make sure they point to the destination + if (isAbsolute(target)) + target = resolve(destination, relative(source, target)) + // try to determine what the actual file is so we can create the correct type of symlink in windows + let targetStat + try { + targetStat = await stat(resolve(dirname(source), target)) + } catch (err) {} + await symlink(target, destination, targetStat && targetStat.isDirectory() ? 'junction' : 'file') + })) + await rimraf(source) + } } -module.exports.sync = (source, destination, options = {}) => { +const moveFileSync = (source, destination, options = {}, root = true, symlinks = []) => { if (!source || !destination) { throw new TypeError('`source` and `destination` file required') } @@ -84,10 +125,38 @@ module.exports.sync = (source, destination, options = {}) => { renameSync(source, destination) } catch (error) { if (error.code === 'EXDEV') { - copyFileSync(source, destination) - unlinkSync(source) + const sourceStat = lstatSync(source) + if (sourceStat.isDirectory()) { + const files = readdirSync(source) + for (const file of files) { + moveFileSync(join(source, file), join(destination, file), options, false, symlinks) + } + } else if (sourceStat.isSymbolicLink()) { + symlinks.push({ source, destination }) + } else { + copyFileSync(source, destination) + } } else { throw error } } + + if (root) { + for (const { source, destination } of symlinks) { + let target = readlinkSync(source) + // junction symlinks in windows will be absolute paths, so we need to make sure they point to the destination + if (isAbsolute(target)) + target = resolve(destination, relative(source, target)) + // try to determine what the actual file is so we can create the correct type of symlink in windows + let targetStat + try { + targetStat = statSync(resolve(dirname(source), target)) + } catch (err) {} + symlinkSync(target, destination, targetStat && targetStat.isDirectory() ? 'junction' : 'file') + } + rimrafSync(source) + } } + +module.exports = moveFile +module.exports.sync = moveFileSync diff --git a/node_modules/@npmcli/move-file/package.json b/node_modules/@npmcli/move-file/package.json index 476bc76ba..46b42c9e9 100644 --- a/node_modules/@npmcli/move-file/package.json +++ b/node_modules/@npmcli/move-file/package.json @@ -1,12 +1,13 @@ { "name": "@npmcli/move-file", - "version": "1.0.1", + "version": "1.1.0", "files": [ "index.js" ], "description": "move a file (fork of move-file)", "dependencies": { - "mkdirp": "^1.0.4" + "mkdirp": "^1.0.4", + "rimraf": "^2.7.1" }, "devDependencies": { "require-inject": "^1.4.4", |