diff --git a/lib/arborist/audit.js b/lib/arborist/audit.js index c0cd79bb1..eb4a35655 100644 --- a/lib/arborist/audit.js +++ b/lib/arborist/audit.js @@ -5,6 +5,7 @@ const AuditReport = require('../audit-report.js') // shared with reify const _global = Symbol.for('global') const _workspaces = Symbol.for('workspaces') +const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot') module.exports = cls => class Auditor extends cls { async audit (options = {}) { @@ -23,7 +24,15 @@ module.exports = cls => class Auditor extends cls { process.emit('time', 'audit') const tree = await this.loadVirtual() if (this[_workspaces] && this[_workspaces].length) { - options.filterSet = this.workspaceDependencySet(tree, this[_workspaces]) + options.filterSet = this.workspaceDependencySet( + tree, + this[_workspaces], + this[_includeWorkspaceRoot] + ) + } + if (!options.workspacesEnabled) { + options.filterSet = + this.excludeWorkspacesDependencySet(tree) } this.auditReport = await AuditReport.load(tree, options) const ret = options.fix ? this.reify(options) : this.auditReport diff --git a/lib/arborist/build-ideal-tree.js b/lib/arborist/build-ideal-tree.js index b7876b114..3e6a9838f 100644 --- a/lib/arborist/build-ideal-tree.js +++ b/lib/arborist/build-ideal-tree.js @@ -93,6 +93,7 @@ const _checkEngine = Symbol('checkEngine') const _checkPlatform = Symbol('checkPlatform') const _virtualRoots = Symbol('virtualRoots') const _virtualRoot = Symbol('virtualRoot') +const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot') const _failPeerConflict = Symbol('failPeerConflict') const _explainPeerConflict = Symbol('explainPeerConflict') @@ -115,12 +116,13 @@ module.exports = cls => class IdealTreeBuilder extends cls { options.registry = this.registry = registry.replace(/\/+$/, '') + '/' const { - idealTree = null, - global = false, follow = false, + force = false, + global = false, globalStyle = false, + idealTree = null, + includeWorkspaceRoot = false, legacyPeerDeps = false, - force = false, packageLock = true, strictPeerDeps = false, workspaces = [], @@ -162,6 +164,8 @@ module.exports = cls => class IdealTreeBuilder extends cls { // don't hold onto references for nodes that are garbage collected. this[_peerSetSource] = new WeakMap() this[_virtualRoots] = new Map() + + this[_includeWorkspaceRoot] = includeWorkspaceRoot } get explicitRequests () { @@ -394,8 +398,14 @@ module.exports = cls => class IdealTreeBuilder extends cls { if (!this[_workspaces].length) { await this[_applyUserRequestsToNode](tree, options) } else { - await Promise.all(this.workspaceNodes(tree, this[_workspaces]) - .map(node => this[_applyUserRequestsToNode](node, options))) + const nodes = this.workspaceNodes(tree, this[_workspaces]) + if (this[_includeWorkspaceRoot]) { + nodes.push(tree) + } + const appliedRequests = nodes.map( + node => this[_applyUserRequestsToNode](node, options) + ) + await Promise.all(appliedRequests) } process.emit('timeEnd', 'idealTree:userRequests') diff --git a/lib/arborist/index.js b/lib/arborist/index.js index d8ca67faa..ccfa7cad9 100644 --- a/lib/arborist/index.js +++ b/lib/arborist/index.js @@ -58,6 +58,7 @@ class Arborist extends Base { cache: options.cache || `${homedir()}/.npm/_cacache`, packumentCache: options.packumentCache || new Map(), log: options.log || procLog, + workspacesEnabled: options.workspacesEnabled !== false, } if (options.saveType && !saveTypeMap.get(options.saveType)) { throw new Error(`Invalid saveType ${options.saveType}`) @@ -73,8 +74,15 @@ class Arborist extends Base { } // returns a set of workspace nodes and all their deps - workspaceDependencySet (tree, workspaces) { + workspaceDependencySet (tree, workspaces, includeWorkspaceRoot) { const wsNodes = this.workspaceNodes(tree, workspaces) + if (includeWorkspaceRoot) { + for (const edge of tree.edgesOut.values()) { + if (edge.type !== 'workspace' && edge.to) { + wsNodes.push(edge.to) + } + } + } const set = new Set(wsNodes) const extraneous = new Set() for (const node of set) { @@ -96,6 +104,25 @@ class Arborist extends Base { for (const extra of extraneous) { set.add(extra) } + + return set + } + + excludeWorkspacesDependencySet (tree) { + const set = new Set() + for (const edge of tree.edgesOut.values()) { + if (edge.type !== 'workspace' && edge.to) { + set.add(edge.to) + } + } + for (const node of set) { + for (const edge of node.edgesOut.values()) { + if (edge.to) { + set.add(edge.to) + } + } + } + return set } } diff --git a/lib/arborist/rebuild.js b/lib/arborist/rebuild.js index e48bdd76b..6fa5c0011 100644 --- a/lib/arborist/rebuild.js +++ b/lib/arborist/rebuild.js @@ -34,6 +34,7 @@ const _addToBuildSet = Symbol('addToBuildSet') const _checkBins = Symbol.for('checkBins') const _queues = Symbol('queues') const _scriptShell = Symbol('scriptShell') +const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot') const _force = Symbol.for('force') @@ -77,7 +78,11 @@ module.exports = cls => class Builder extends cls { if (!nodes) { const tree = await this.loadActual() if (this[_workspaces] && this[_workspaces].length) { - const filterSet = this.workspaceDependencySet(tree, this[_workspaces]) + const filterSet = this.workspaceDependencySet( + tree, + this[_workspaces], + this[_includeWorkspaceRoot] + ) nodes = tree.inventory.filter(node => filterSet.has(node)) } else { nodes = tree.inventory.values() diff --git a/lib/arborist/reify.js b/lib/arborist/reify.js index 3a9c47974..a279d8956 100644 --- a/lib/arborist/reify.js +++ b/lib/arborist/reify.js @@ -83,6 +83,7 @@ const _validateNodeModules = Symbol('validateNodeModules') const _nmValidated = Symbol('nmValidated') const _validatePath = Symbol('validatePath') const _reifyPackages = Symbol.for('reifyPackages') +const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot') const _omitDev = Symbol('omitDev') const _omitOptional = Symbol('omitOptional') @@ -340,6 +341,15 @@ module.exports = cls => class Reifier extends cls { filterNodes.push(actual) } } + if (this[_includeWorkspaceRoot] && (this[_workspaces].length > 0)) { + for (const tree of [this.idealTree, this.actualTree]) { + for (const {type, to} of tree.edgesOut.values()) { + if (type !== 'workspace' && to) { + filterNodes.push(to) + } + } + } + } } // find all the nodes that need to change between the actual @@ -901,7 +911,11 @@ module.exports = cls => class Reifier extends cls { // if we're operating on a workspace, only audit the workspace deps if (this[_workspaces] && this[_workspaces].length) { - options.filterSet = this.workspaceDependencySet(tree, this[_workspaces]) + options.filterSet = this.workspaceDependencySet( + tree, + this[_workspaces], + this[_includeWorkspaceRoot] + ) } this.auditReport = AuditReport.load(tree, options) diff --git a/tap-snapshots/test/arborist/build-ideal-tree.js.test.cjs b/tap-snapshots/test/arborist/build-ideal-tree.js.test.cjs index 3142288cf..35d1d17a9 100644 --- a/tap-snapshots/test/arborist/build-ideal-tree.js.test.cjs +++ b/tap-snapshots/test/arborist/build-ideal-tree.js.test.cjs @@ -335,6 +335,352 @@ ArboristNode { } ` +exports[`test/arborist/build-ideal-tree.js TAP add deps and include workspace-root add mkdirp 0.5.0 to b > must match snapshot 1`] = ` +ArboristNode { + "children": Map { + "a" => ArboristLink { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "a", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "type": "workspace", + }, + }, + "isWorkspace": true, + "location": "node_modules/a", + "name": "a", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/a", + "realpath": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "resolved": "file:../packages/a", + "target": ArboristNode { + "location": "packages/a", + }, + "version": "1.2.3", + }, + "b" => ArboristLink { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "b", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "type": "workspace", + }, + }, + "isWorkspace": true, + "location": "node_modules/b", + "name": "b", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/b", + "realpath": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "resolved": "file:../packages/b", + "target": ArboristNode { + "location": "packages/b", + }, + "version": "1.2.3", + }, + "minimist" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "minimist", + "spec": "1", + "type": "prod", + }, + }, + "location": "node_modules/minimist", + "name": "minimist", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/minimist", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "version": "1.2.5", + }, + "mkdirp" => ArboristNode { + "children": Map { + "minimist" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "node_modules/mkdirp", + "name": "minimist", + "spec": "0.0.8", + "type": "prod", + }, + }, + "location": "node_modules/mkdirp/node_modules/minimist", + "name": "minimist", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/mkdirp/node_modules/minimist", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "version": "0.0.8", + }, + }, + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "mkdirp", + "spec": "0.5.0", + "type": "prod", + }, + EdgeIn { + "from": "packages/a", + "name": "mkdirp", + "spec": "^0.5.0", + "type": "prod", + }, + EdgeIn { + "from": "packages/b", + "name": "mkdirp", + "spec": "0.5.0", + "type": "prod", + }, + }, + "edgesOut": Map { + "minimist" => EdgeOut { + "name": "minimist", + "spec": "0.0.8", + "to": "node_modules/mkdirp/node_modules/minimist", + "type": "prod", + }, + }, + "location": "node_modules/mkdirp", + "name": "mkdirp", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/mkdirp", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "version": "0.5.0", + }, + }, + "edgesOut": Map { + "a" => EdgeOut { + "name": "a", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "to": "node_modules/a", + "type": "workspace", + }, + "b" => EdgeOut { + "name": "b", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "to": "node_modules/b", + "type": "workspace", + }, + "minimist" => EdgeOut { + "name": "minimist", + "spec": "1", + "to": "node_modules/minimist", + "type": "prod", + }, + "mkdirp" => EdgeOut { + "name": "mkdirp", + "spec": "0.5.0", + "to": "node_modules/mkdirp", + "type": "prod", + }, + }, + "fsChildren": Set { + ArboristNode { + "edgesOut": Map { + "mkdirp" => EdgeOut { + "name": "mkdirp", + "spec": "^0.5.0", + "to": "node_modules/mkdirp", + "type": "prod", + }, + }, + "isWorkspace": true, + "location": "packages/a", + "name": "a", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "version": "1.2.3", + }, + ArboristNode { + "edgesOut": Map { + "mkdirp" => EdgeOut { + "name": "mkdirp", + "spec": "0.5.0", + "to": "node_modules/mkdirp", + "type": "prod", + }, + }, + "isWorkspace": true, + "location": "packages/b", + "name": "b", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "version": "1.2.3", + }, + }, + "isProjectRoot": true, + "location": "", + "name": "tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root", + "workspaces": Map { + "a" => "packages/a", + "b" => "packages/b", + }, +} +` + +exports[`test/arborist/build-ideal-tree.js TAP add deps and include workspace-root no args > must match snapshot 1`] = ` +ArboristNode { + "children": Map { + "a" => ArboristLink { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "a", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "type": "workspace", + }, + }, + "isWorkspace": true, + "location": "node_modules/a", + "name": "a", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/a", + "realpath": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "resolved": "file:../packages/a", + "target": ArboristNode { + "location": "packages/a", + }, + "version": "1.2.3", + }, + "b" => ArboristLink { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "b", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "type": "workspace", + }, + }, + "isWorkspace": true, + "location": "node_modules/b", + "name": "b", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/b", + "realpath": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "resolved": "file:../packages/b", + "target": ArboristNode { + "location": "packages/b", + }, + "version": "1.2.3", + }, + "minimist" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "minimist", + "spec": "1", + "type": "prod", + }, + EdgeIn { + "from": "packages/a/node_modules/mkdirp", + "name": "minimist", + "spec": "^1.2.5", + "type": "prod", + }, + }, + "location": "node_modules/minimist", + "name": "minimist", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/minimist", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "version": "1.2.5", + }, + "mkdirp" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "mkdirp", + "spec": "^1.0.4", + "type": "prod", + }, + }, + "location": "node_modules/mkdirp", + "name": "mkdirp", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/node_modules/mkdirp", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "version": "1.0.4", + }, + }, + "edgesOut": Map { + "a" => EdgeOut { + "name": "a", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "to": "node_modules/a", + "type": "workspace", + }, + "b" => EdgeOut { + "name": "b", + "spec": "file:{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "to": "node_modules/b", + "type": "workspace", + }, + "minimist" => EdgeOut { + "name": "minimist", + "spec": "1", + "to": "node_modules/minimist", + "type": "prod", + }, + "mkdirp" => EdgeOut { + "name": "mkdirp", + "spec": "^1.0.4", + "to": "node_modules/mkdirp", + "type": "prod", + }, + }, + "fsChildren": Set { + ArboristNode { + "children": Map { + "mkdirp" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "packages/a", + "name": "mkdirp", + "spec": "^0.5.0", + "type": "prod", + }, + }, + "edgesOut": Map { + "minimist" => EdgeOut { + "name": "minimist", + "spec": "^1.2.5", + "to": "node_modules/minimist", + "type": "prod", + }, + }, + "location": "packages/a/node_modules/mkdirp", + "name": "mkdirp", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a/node_modules/mkdirp", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "version": "0.5.5", + }, + }, + "edgesOut": Map { + "mkdirp" => EdgeOut { + "name": "mkdirp", + "spec": "^0.5.0", + "to": "packages/a/node_modules/mkdirp", + "type": "prod", + }, + }, + "isWorkspace": true, + "location": "packages/a", + "name": "a", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/a", + "version": "1.2.3", + }, + ArboristNode { + "isWorkspace": true, + "location": "packages/b", + "name": "b", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root/packages/b", + "version": "1.2.3", + }, + }, + "isProjectRoot": true, + "location": "", + "name": "tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root", + "path": "{CWD}/test/arborist/tap-testdir-build-ideal-tree-add-deps-and-include-workspace-root", + "workspaces": Map { + "a" => "packages/a", + "b" => "packages/b", + }, +} +` + exports[`test/arborist/build-ideal-tree.js TAP add deps to workspaces KEEP in the root, prune out unnecessary dupe > must match snapshot 1`] = ` ArboristNode { "children": Map { diff --git a/tap-snapshots/test/arborist/load-virtual.js.test.cjs b/tap-snapshots/test/arborist/load-virtual.js.test.cjs index ec4d0bbbb..c0210aabc 100644 --- a/tap-snapshots/test/arborist/load-virtual.js.test.cjs +++ b/tap-snapshots/test/arborist/load-virtual.js.test.cjs @@ -16714,6 +16714,29 @@ ArboristNode { }, "version": "1.0.0", }, + "once" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "once", + "spec": "*", + "type": "prod", + }, + }, + "edgesOut": Map { + "wrappy" => EdgeOut { + "name": "wrappy", + "spec": "1", + "to": "node_modules/wrappy", + "type": "prod", + }, + }, + "location": "node_modules/once", + "name": "once", + "path": "{CWD}/test/fixtures/workspaces-shared-deps-virtual/node_modules/once", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "version": "1.4.0", + }, "uuid" => ArboristNode { "edgesIn": Set { EdgeIn { @@ -16729,6 +16752,21 @@ ArboristNode { "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "version": "3.3.3", }, + "wrappy" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "node_modules/once", + "name": "wrappy", + "spec": "1", + "type": "prod", + }, + }, + "location": "node_modules/wrappy", + "name": "wrappy", + "path": "{CWD}/test/fixtures/workspaces-shared-deps-virtual/node_modules/wrappy", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "version": "1.0.2", + }, }, "edgesOut": Map { "a" => EdgeOut { @@ -16749,6 +16787,12 @@ ArboristNode { "to": "node_modules/c", "type": "workspace", }, + "once" => EdgeOut { + "name": "once", + "spec": "*", + "to": "node_modules/once", + "type": "prod", + }, }, "fsChildren": Set { ArboristNode { diff --git a/tap-snapshots/test/arborist/reify.js.test.cjs b/tap-snapshots/test/arborist/reify.js.test.cjs index 109d5829c..fbd6c3b01 100644 --- a/tap-snapshots/test/arborist/reify.js.test.cjs +++ b/tap-snapshots/test/arborist/reify.js.test.cjs @@ -4177,6 +4177,132 @@ ArboristNode { } ` +exports[`test/arborist/reify.js TAP includeWorkspaceRoot in addition to workspace > must match snapshot 1`] = ` +ArboristNode { + "children": Map { + "a" => ArboristLink { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "a", + "spec": "file:{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/packages/a", + "type": "workspace", + }, + }, + "isWorkspace": true, + "location": "node_modules/a", + "name": "a", + "path": "{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/node_modules/a", + "realpath": "{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/packages/a", + "resolved": "file:../packages/a", + "target": ArboristNode { + "location": "packages/a", + }, + "version": "1.0.1", + }, + "abbrev" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "packages/a", + "name": "abbrev", + "spec": "*", + "type": "prod", + }, + }, + "location": "node_modules/abbrev", + "name": "abbrev", + "path": "{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/node_modules/abbrev", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "version": "1.1.1", + }, + "once" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "once", + "spec": "*", + "type": "prod", + }, + }, + "edgesOut": Map { + "wrappy" => EdgeOut { + "name": "wrappy", + "spec": "1", + "to": "node_modules/wrappy", + "type": "prod", + }, + }, + "location": "node_modules/once", + "name": "once", + "path": "{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/node_modules/once", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "version": "1.4.0", + }, + "wrappy" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "node_modules/once", + "name": "wrappy", + "spec": "1", + "type": "prod", + }, + }, + "location": "node_modules/wrappy", + "name": "wrappy", + "path": "{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/node_modules/wrappy", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "version": "1.0.2", + }, + }, + "edgesOut": Map { + "a" => EdgeOut { + "name": "a", + "spec": "file:{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/packages/a", + "to": "node_modules/a", + "type": "workspace", + }, + "b" => EdgeOut { + "error": "MISSING", + "name": "b", + "spec": "file:{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/packages/b", + "to": null, + "type": "workspace", + }, + "once" => EdgeOut { + "name": "once", + "spec": "*", + "to": "node_modules/once", + "type": "prod", + }, + }, + "fsChildren": Set { + ArboristNode { + "edgesOut": Map { + "abbrev" => EdgeOut { + "name": "abbrev", + "spec": "*", + "to": "node_modules/abbrev", + "type": "prod", + }, + }, + "isWorkspace": true, + "location": "packages/a", + "name": "a", + "path": "{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace/packages/a", + "version": "1.0.1", + }, + }, + "isProjectRoot": true, + "location": "", + "name": "tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace", + "path": "{CWD}/test/arborist/tap-testdir-reify-includeWorkspaceRoot-in-addition-to-workspace", + "workspaces": Map { + "a" => "packages/a", + "b" => "packages/b", + }, +} +` + exports[`test/arborist/reify.js TAP just the shrinkwrap cli-750-fresh > must match snapshot 1`] = ` { "name": "monorepo", diff --git a/test/arborist/audit.js b/test/arborist/audit.js index 56dfe992b..2ff5c5341 100644 --- a/test/arborist/audit.js +++ b/test/arborist/audit.js @@ -107,3 +107,48 @@ t.test('audit in a workspace', async t => { t.equal(fixed.children.get('a').target.children.get('mkdirp').version, '0.5.0', 'did not fix a') t.equal(fixed.children.get('b').target.children.get('mkdirp').version, '0.5.5', 'did fix b') }) + +t.test('audit with workspaces disabled', async t => { + const src = resolve(fixtures, 'audit-nyc-mkdirp') + const auditFile = resolve(src, 'advisory-bulk.json') + t.teardown(advisoryBulkResponse(auditFile)) + + const path = t.testdir({ + 'package.json': JSON.stringify({ + workspaces: ['packages/*'], + dependencies: { + mkdirp: '1', + }, + }), + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.2.3', + dependencies: { + mkdirp: '0', + }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.2.3', + dependencies: { + mkdirp: '0', + }, + }), + }, + }, + }) + + // reify it without auditing so that we can put the "bad" versions + // in place and save a lockfile reflecting this. + await newArb(path, { audit: false }).reify() + const bad = 'mkdirp@0.5.0' + await newArb(path, { audit: false, workspaces: ['a'] }).reify({ add: [bad] }) + await newArb(path, { audit: false, workspaces: ['b'] }).reify({ add: [bad] }) + + const auditReport = await newArb(path, { workspacesEnabled: false }).audit() + t.notOk(auditReport.get('mkdirp')) +}) diff --git a/test/arborist/build-ideal-tree.js b/test/arborist/build-ideal-tree.js index 40e0c7d7a..dfb1a77f9 100644 --- a/test/arborist/build-ideal-tree.js +++ b/test/arborist/build-ideal-tree.js @@ -2601,6 +2601,54 @@ t.test('add deps to workspaces', async t => { }) }) +t.test('add deps and include workspace-root', async t => { + const fixtureDef = { + 'package.json': JSON.stringify({ + workspaces: [ + 'packages/*', + ], + dependencies: { + mkdirp: '^1.0.4', + minimist: '1', + }, + }), + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.2.3', + dependencies: { + mkdirp: '^0.5.0', + }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.2.3', + }), + }, + }, + } + const path = t.testdir(fixtureDef) + + t.test('no args', async t => { + const tree = await buildIdeal(path) + t.equal(tree.children.get('mkdirp').version, '1.0.4') + t.equal(tree.children.get('a').target.children.get('mkdirp').version, '0.5.5') + t.equal(tree.children.get('b').target.children.get('mkdirp'), undefined) + t.ok(tree.edgesOut.has('mkdirp')) + t.matchSnapshot(printTree(tree)) + }) + + t.test('add mkdirp 0.5.0 to b', async t => { + const tree = await buildIdeal(path, { workspaces: ['b'], add: ['mkdirp@0.5.0'], includeWorkspaceRoot: true }) + t.equal(tree.children.get('mkdirp').version, '0.5.0') + t.ok(tree.edgesOut.has('mkdirp')) + t.matchSnapshot(printTree(tree)) + }) +}) + t.test('inflates old lockfile with hasInstallScript', async t => { const path = t.testdir({ 'package-lock.json': JSON.stringify({ diff --git a/test/arborist/index.js b/test/arborist/index.js index b7b04c8be..0237c3fe9 100644 --- a/test/arborist/index.js +++ b/test/arborist/index.js @@ -42,6 +42,24 @@ t.test('workspace nodes and deps', async t => { t.equal(wsDepSet.has(tree.children.get('abbrev')), true) } + { + const wsDepSet = arb.workspaceDependencySet(tree, ['b'], true) + t.equal(wsDepSet.size, 4) + t.equal(wsDepSet.has(tree.children.get('b').target), true) + t.equal(wsDepSet.has(tree.children.get('abbrev')), true) + t.equal(wsDepSet.has(tree.children.get('once')), true) + t.equal(wsDepSet.has(tree.children.get('wrappy')), true) + } + + { + const wsDepSet = arb.excludeWorkspacesDependencySet(tree) + t.equal(wsDepSet.size, 2) + t.equal(wsDepSet.has(tree.children.get('b').target), false) + t.equal(wsDepSet.has(tree.children.get('abbrev')), false) + t.equal(wsDepSet.has(tree.children.get('once')), true) + t.equal(wsDepSet.has(tree.children.get('wrappy')), true) + } + const wsNode = wsNodes[0] new Edge({ from: wsNode, @@ -105,3 +123,99 @@ t.test('workspace nodes and deps', async t => { t.equal(wsDepSet.has(tree.children.get('abbrev').target), true) } }) + +t.test('excludeSet includes nonworkspace metadeps', async t => { + const tree = new Node({ + path: '/hi', + pkg: { + workspaces: ['pkgs/*'], + dependencies: { + foo: '', + }, + }, + children: [ + { + pkg: { + name: 'foo', + version: '0.1.1', + dependencies: { + bar: '', + asdf: '', + }, + }, + }, + { + pkg: { + name: 'bar', + version: '0.2.0', + }, + }, + { + pkg: { + name: 'baz', + version: '9.2.0', + }, + }, + { + pkg: { + name: 'fritzy', + version: '2.2.9', + optionalDependencies: { + isaacs: '', + }, + }, + }, + ], + }) + const pkgA = new Node({ + path: tree.path + '/pkgs/a', + pkg: { + name: 'a', + version: '1.0.0', + dependencies: { + baz: '', + }, + }, + root: tree, + }) + new Link({ + name: 'a', + parent: tree, + target: pkgA, + }) + new Edge({ + type: 'workspace', + from: tree, + name: 'a', + spec: 'file:pkgs/a', + }) + const pkgB = new Node({ + path: tree.path + '/pkgs/b', + pkg: { + name: 'b', + version: '1.0.0', + dependencies: { + fritzy: '', + }, + }, + root: tree, + }) + new Link({ + name: 'b', + parent: tree, + target: pkgB, + }) + new Edge({ + type: 'workspace', + from: tree, + name: 'b', + spec: 'file:pkgs/b', + }) + + const arb = new Arborist() + const filter = arb.excludeWorkspacesDependencySet(tree) + + t.equal(filter.size, 2) + t.equal(filter.has(tree.children.get('foo')), true) + t.equal(filter.has(tree.children.get('bar')), true) +}) diff --git a/test/arborist/reify.js b/test/arborist/reify.js index 5a544d7b9..e058fee3f 100644 --- a/test/arborist/reify.js +++ b/test/arborist/reify.js @@ -2324,3 +2324,39 @@ t.test('adding an unresolvable optional dep is OK', async t => { t.strictSame([...tree.children.values()], [], 'nothing actually added') t.matchSnapshot(printTree(tree)) }) + +t.test('includeWorkspaceRoot in addition to workspace', async t => { + const path = t.testdir({ + 'package.json': JSON.stringify({ + dependencies: { + once: '', + }, + workspaces: ['packages/*'], + }), + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.1', + dependencies: { + abbrev: '', + }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '9.8.1', + dependencies: { + semver: '', + }, + }), + }, + }, + }) + const tree = await reify(path, { includeWorkspaceRoot: true, workspaces: ['a'] }) + t.matchSnapshot(printTree(tree)) + t.equal(tree.inventory.query('name', 'semver').size, 0) + t.equal(tree.inventory.query('name', 'abbrev').size, 1) + t.equal(tree.inventory.query('name', 'once').size, 1) +}) diff --git a/test/fixtures/workspaces-shared-deps-virtual/package-lock.json b/test/fixtures/workspaces-shared-deps-virtual/package-lock.json index 3d77c2f42..8dc30e012 100644 --- a/test/fixtures/workspaces-shared-deps-virtual/package-lock.json +++ b/test/fixtures/workspaces-shared-deps-virtual/package-lock.json @@ -7,10 +7,11 @@ "": { "name": "workspaces-shared-deps", "version": "1.0.0", - "workspaces": { - "packages": [ - "packages/*" - ] + "workspaces": [ + "packages/*" + ], + "dependencies": { + "once": "" } }, "node_modules/a": { @@ -18,7 +19,6 @@ "link": true }, "node_modules/abbrev": { - "name": "abbrev", "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" @@ -31,31 +31,41 @@ "resolved": "packages/c", "link": true }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/uuid": { - "name": "uuid", "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "bin": { "uuid": "bin/uuid" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "packages/a": { - "name": "a", "version": "1.0.0", "dependencies": { "abbrev": "^1.1.1" } }, "packages/b": { - "name": "b", "version": "1.0.0", "dependencies": { "abbrev": "^1.1.1" } }, "packages/c": { - "name": "c", "version": "1.0.0", "dependencies": { "uuid": "=3.3.3" @@ -64,7 +74,10 @@ }, "dependencies": { "a": { - "version": "file:packages/a" + "version": "file:packages/a", + "requires": { + "abbrev": "^1.1.1" + } }, "abbrev": { "version": "1.1.1", @@ -72,15 +85,34 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "b": { - "version": "file:packages/b" + "version": "file:packages/b", + "requires": { + "abbrev": "^1.1.1" + } }, "c": { - "version": "file:packages/c" + "version": "file:packages/c", + "requires": { + "uuid": "=3.3.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } }, "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } } diff --git a/test/fixtures/workspaces-shared-deps-virtual/package.json b/test/fixtures/workspaces-shared-deps-virtual/package.json index 4c634a1e0..9cf60d751 100644 --- a/test/fixtures/workspaces-shared-deps-virtual/package.json +++ b/test/fixtures/workspaces-shared-deps-virtual/package.json @@ -3,5 +3,8 @@ "version": "1.0.0", "workspaces": [ "packages/*" - ] + ], + "dependencies": { + "once": "" + } }