From ed7d1f6b51b786f91adf399aa60197eef674c742 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 11 Feb 2021 15:36:58 -0800 Subject: [PATCH] Allow --force to override conflicted peerOptional With a dependency graph like this: ``` root -> (a, b@1) a -> PEEROPTIONAL(b@2) ``` We do not install the peerOptional dependency by default, so even though `b@2` is included in the peerSet of `a`, it is not added to the tree. Then, the `b@1` dependency is added to satisfy root's direct dependency on it, causing the `a -> b@2` edge to become invalid. We then try to resolve the `a -> b@2` edge, and find that we cannot place it anywhere, causing an `ERESOLVE` error. However, because `b@2` is no longer a part of a peerSet sourced on the `root` node, we miss the chance to detect that it should be overridden, resulting in an `ERESOLVE` failure even when `--force` is used. This commit adds the check for `this[_force]` prior to crashing with ERESOLVE, so that cases that avoid our earlier heuristics still accept the invalid resolution when `--force` is in effect. Fix: #226 Fix: https://github.com/npm/cli/issues/2504 --- lib/arborist/build-ideal-tree.js | 29 +- ...t-arborist-build-ideal-tree.js-TAP.test.js | 736 ++++++++++++++++++ test/arborist/build-ideal-tree.js | 105 +++ .../b/package.json | 12 + .../peer/1/package.json | 4 + .../peer/2/package.json | 4 + ...nflicted-peer-optional-from-dev-dep-b.json | 61 ++ ...cted-peer-optional-from-dev-dep-b.min.json | 29 + ...icted-peer-optional-from-dev-dep-peer.json | 86 ++ ...d-peer-optional-from-dev-dep-peer.min.json | 33 + 10 files changed, 1092 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/conflicted-peer-optional-from-dev-dep/b/package.json create mode 100644 test/fixtures/conflicted-peer-optional-from-dev-dep/peer/1/package.json create mode 100644 test/fixtures/conflicted-peer-optional-from-dev-dep/peer/2/package.json create mode 100644 test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.json create mode 100644 test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.min.json create mode 100644 test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.json create mode 100644 test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.min.json diff --git a/lib/arborist/build-ideal-tree.js b/lib/arborist/build-ideal-tree.js index de7bd658a..be508ed6d 100644 --- a/lib/arborist/build-ideal-tree.js +++ b/lib/arborist/build-ideal-tree.js @@ -805,6 +805,7 @@ This is a one-time fix-up, please be patient... // a virtual root of whatever brought in THIS node. // so we VR the node itself if the edge is not a peer const source = edge.peer ? peerSource : node + const virtualRoot = this[_virtualRoot](source, true) // reuse virtual root if we already have one, but don't // try to do the override ahead of time, since we MAY be able @@ -826,8 +827,7 @@ This is a one-time fix-up, please be patient... // +-- z@1 // But if x and y are loaded in the same virtual root, then they will // be forced to agree on a version of z. - const required = edge.type === 'peerOptional' ? new Set() - : new Set([edge.from]) + const required = new Set([edge.from]) const parent = edge.peer ? virtualRoot : null const dep = vrDep && vrDep.satisfies(edge) ? vrDep : await this[_nodeFromEdge](edge, parent, null, required) @@ -1217,8 +1217,25 @@ This is a one-time fix-up, please be patient... break } - if (!target) - this[_failPeerConflict](edge) + // if we can't find a target, that means that the last placed checked + // (and all the places before it) had a copy already. if we're in + // --force mode, then the user has explicitly said that they're ok + // with conflicts. This can only occur in --force mode in the case + // when a node was added to the tree with a peerOptional dep that we + // ignored, and then later, that edge became invalid, and we fail to + // resolve it. We will warn about it in a moment. + if (!target) { + if (this[_force]) { + // we know that there is a dep (not the root) which is the target + // of this edge, or else it wouldn't have been a conflict. + target = edge.to.resolveParent + canPlace = KEEP + } else + this[_failPeerConflict](edge) + } else { + // it worked, so we clearly have no peer conflicts at this point. + this[_peerConflict] = null + } this.log.silly( 'placeDep', @@ -1229,9 +1246,6 @@ This is a one-time fix-up, please be patient... `want: ${edge.spec || '*'}` ) - // it worked, so we clearly have no peer conflicts at this point. - this[_peerConflict] = null - // Can only get KEEP here if the original edge was valid, // and we're checking for an update but it's already up to date. if (canPlace === KEEP) { @@ -1417,6 +1431,7 @@ This is a one-time fix-up, please be patient... }) const entryEdge = peerEntryEdge || edge const source = this[_peerSetSource].get(dep) + isSource = isSource || target === source // if we're overriding the source, then we care if the *target* is // ours, even if it wasn't actually the original source, since we diff --git a/tap-snapshots/test-arborist-build-ideal-tree.js-TAP.test.js b/tap-snapshots/test-arborist-build-ideal-tree.js-TAP.test.js index b566a72ff..c53eaeb11 100644 --- a/tap-snapshots/test-arborist-build-ideal-tree.js-TAP.test.js +++ b/tap-snapshots/test-arborist-build-ideal-tree.js-TAP.test.js @@ -315,6 +315,742 @@ ArboristNode { } ` +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source both direct and peer of the same type dependencies > use the force 1`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "prod", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-dependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "prod", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-dependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "prod", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "prod", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-dependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-dependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source both direct and peer of the same type devDependencies > use the force 1`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "dev": true, + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "dev", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-devDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "dev": true, + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "dev", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-devDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "dev", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "dev", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-devDependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-devDependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source both direct and peer of the same type optionalDependencies > use the force 1`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "optional", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "optional": true, + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-optionalDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "optional", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "optional": true, + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-optionalDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "optional", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "optional", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-optionalDependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-optionalDependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source both direct and peer of the same type peerDependencies > use the force 1`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "peer", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-peerDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "peer", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-peerDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "peer", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peer", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-peerDependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-both-direct-and-peer-of-the-same-type-peerDependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source peer is peer, b is some other type dependencies > use the force 1`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "peer", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "prod", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "peer", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "prod", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source peer is peer, b is some other type dependencies > use the force 2`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "prod", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "peer", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "prod", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peer", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-dependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source peer is peer, b is some other type devDependencies > use the force 1`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "peer", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "devOptional": true, + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "dev", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "peer", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "dev", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source peer is peer, b is some other type devDependencies > use the force 2`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "dev": true, + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "dev", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "peer", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "dev", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peer", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-devDependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source peer is peer, b is some other type optionalDependencies > use the force 1`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "peer", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "optional", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "optional": true, + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "peer", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "optional", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies", + "version": "1.2.3", +} +` + +exports[`test/arborist/build-ideal-tree.js TAP allow ERESOLVE to be forced when not in the source peer is peer, b is some other type optionalDependencies > use the force 2`] = ` +ArboristNode { + "children": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "type": "optional", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "error": "INVALID", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "optional": true, + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "version": "1.0.0", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => ArboristNode { + "edgesIn": Set { + EdgeIn { + "from": "", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "type": "peer", + }, + EdgeIn { + "error": "INVALID", + "from": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "2", + "type": "peerOptional", + }, + }, + "location": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies/node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "peer": true, + "resolved": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "version": "1.0.0", + }, + }, + "edgesOut": Map { + "@isaacs/conflicted-peer-optional-from-dev-dep-b" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "spec": "*", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-b", + "type": "optional", + }, + "@isaacs/conflicted-peer-optional-from-dev-dep-peer" => EdgeOut { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "spec": "1", + "to": "node_modules/@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "type": "peer", + }, + }, + "location": "", + "name": "build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies", + "packageName": "@isaacs/conflicted-peer-optional-from-dev-dep", + "path": "{CWD}/test/arborist/build-ideal-tree-allow-ERESOLVE-to-be-forced-when-not-in-the-source-peer-is-peer-b-is-some-other-type-optionalDependencies", + "version": "1.2.3", +} +` + exports[`test/arborist/build-ideal-tree.js TAP allow updating when peer outside of explicit update set conflict, but resolves appropriately with --force > succeed if force applied 1`] = ` ArboristNode { "children": Map { diff --git a/test/arborist/build-ideal-tree.js b/test/arborist/build-ideal-tree.js index 7abf2c35d..de0779861 100644 --- a/test/arborist/build-ideal-tree.js +++ b/test/arborist/build-ideal-tree.js @@ -1545,6 +1545,7 @@ t.test('more peer dep conflicts', t => { }, error: true, }, + 'prod dep directly on conflicted peer, older': { pkg: { dependencies: { @@ -1554,6 +1555,7 @@ t.test('more peer dep conflicts', t => { }, error: true, }, + 'prod dep directly on conflicted peer, full peer set, newer': { pkg: { dependencies: { @@ -1566,6 +1568,7 @@ t.test('more peer dep conflicts', t => { }, error: true, }, + 'prod dep directly on conflicted peer, full peer set, older': { pkg: { dependencies: { @@ -1578,6 +1581,7 @@ t.test('more peer dep conflicts', t => { }, error: true, }, + 'prod dep directly on conflicted peer, meta peer set, older': { pkg: { dependencies: { @@ -1589,6 +1593,7 @@ t.test('more peer dep conflicts', t => { }, error: true, }, + 'dep indirectly on conflicted peer': { pkg: { dependencies: { @@ -1598,6 +1603,7 @@ t.test('more peer dep conflicts', t => { }, error: true, }, + 'collision forcing duplication, order 1': { pkg: { dependencies: { @@ -1608,6 +1614,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'collision forcing duplication, order 2': { pkg: { dependencies: { @@ -1618,6 +1625,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'collision forcing duplication via add, order 1': { pkg: { dependencies: { @@ -1628,6 +1636,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'collision forcing duplication via add, order 2': { pkg: { dependencies: { @@ -1638,6 +1647,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'collision forcing metadep duplication, order 1': { pkg: { dependencies: { @@ -1648,6 +1658,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'collision forcing metadep duplication, order 2': { pkg: { dependencies: { @@ -1658,6 +1669,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'direct collision forcing metadep duplication, order 1': { pkg: { dependencies: { @@ -1668,6 +1680,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'direct collision forcing metadep duplication, order 2': { pkg: { dependencies: { @@ -1678,6 +1691,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: true, }, + 'dep with conflicting peers': { pkg: { dependencies: { @@ -1688,6 +1702,7 @@ t.test('more peer dep conflicts', t => { // but it is a conflict in a peerSet that the root is sourcing. error: true, }, + 'metadeps with conflicting peers': { pkg: { dependencies: { @@ -1696,6 +1711,7 @@ t.test('more peer dep conflicts', t => { }, error: false, }, + 'metadep conflict that warns because source is target': { pkg: { dependencies: { @@ -1706,6 +1722,7 @@ t.test('more peer dep conflicts', t => { error: false, resolvable: false, }, + 'metadep conflict triggering the peerConflict code path': { pkg: { dependencies: { @@ -1717,6 +1734,7 @@ t.test('more peer dep conflicts', t => { resolvable: false, }, }) + t.jobs = cases.length t.plan(cases.length) @@ -2468,3 +2486,90 @@ t.test('do not ERESOLVE on peerOptionals that are ignored anyway', t => { }) } }) + +t.test('allow ERESOLVE to be forced when not in the source', async t => { + const types = [ + 'peerDependencies', + 'optionalDependencies', + 'devDependencies', + 'dependencies', + ] + + // in these tests, the deps are both of the same type. b has a peerOptional + // dep on peer, and peer is a direct dependency of the root. + t.test('both direct and peer of the same type', t => { + t.plan(types.length) + const pj = type => ({ + name: '@isaacs/conflicted-peer-optional-from-dev-dep', + version: '1.2.3', + [type]: { + '@isaacs/conflicted-peer-optional-from-dev-dep-peer': '1', + '@isaacs/conflicted-peer-optional-from-dev-dep-b': '', + }, + }) + + for (const type of types) { + t.test(type, async t => { + const path = t.testdir({ + 'package.json': JSON.stringify(pj(type)), + }) + t.matchSnapshot(await printIdeal(path, { force: true }), 'use the force') + t.rejects(printIdeal(path), { code: 'ERESOLVE' }, 'no force') + }) + } + }) + + // in these, the peer is a peer dep of the root, and b is a different type + t.test('peer is peer, b is some other type', t => { + t.plan(types.length - 1) + const pj = type => ({ + name: '@isaacs/conflicted-peer-optional-from-dev-dep', + version: '1.2.3', + peerDependencies: { + '@isaacs/conflicted-peer-optional-from-dev-dep-b': '', + }, + [type]: { + '@isaacs/conflicted-peer-optional-from-dev-dep-peer': '1', + }, + }) + for (const type of types) { + if (type === 'peerDependencies') + continue + t.test(type, async t => { + const path = t.testdir({ + 'package.json': JSON.stringify(pj(type)), + }) + t.matchSnapshot(await printIdeal(path, { force: true }), 'use the force') + t.rejects(printIdeal(path), { code: 'ERESOLVE' }, 'no force') + }) + } + }) + + // in these, b is a peer dep, and peer is some other type + t.test('peer is peer, b is some other type', t => { + t.plan(types.length - 1) + const pj = type => ({ + name: '@isaacs/conflicted-peer-optional-from-dev-dep', + version: '1.2.3', + peerDependencies: { + '@isaacs/conflicted-peer-optional-from-dev-dep-peer': '1', + }, + [type]: { + '@isaacs/conflicted-peer-optional-from-dev-dep-b': '', + }, + }) + for (const type of types) { + if (type === 'peerDependencies') + continue + t.test(type, async t => { + const path = t.testdir({ + 'package.json': JSON.stringify(pj(type)), + }) + t.matchSnapshot(await printIdeal(path, { force: true }), 'use the force') + t.rejects(printIdeal(path), { code: 'ERESOLVE' }, 'no force') + }) + } + }) + + t.end() +}) diff --git a/test/fixtures/conflicted-peer-optional-from-dev-dep/b/package.json b/test/fixtures/conflicted-peer-optional-from-dev-dep/b/package.json new file mode 100644 index 000000000..4d82ed5f5 --- /dev/null +++ b/test/fixtures/conflicted-peer-optional-from-dev-dep/b/package.json @@ -0,0 +1,12 @@ +{ + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "version": "1.0.0", + "peerDependencies": { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer": "2" + }, + "peerDependenciesMeta": { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer": { + "optional": true + } + } +} diff --git a/test/fixtures/conflicted-peer-optional-from-dev-dep/peer/1/package.json b/test/fixtures/conflicted-peer-optional-from-dev-dep/peer/1/package.json new file mode 100644 index 000000000..2352a7779 --- /dev/null +++ b/test/fixtures/conflicted-peer-optional-from-dev-dep/peer/1/package.json @@ -0,0 +1,4 @@ +{ + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "version": "1.0.0" +} diff --git a/test/fixtures/conflicted-peer-optional-from-dev-dep/peer/2/package.json b/test/fixtures/conflicted-peer-optional-from-dev-dep/peer/2/package.json new file mode 100644 index 000000000..c28e4fc0f --- /dev/null +++ b/test/fixtures/conflicted-peer-optional-from-dev-dep/peer/2/package.json @@ -0,0 +1,4 @@ +{ + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "version": "2.0.0" +} diff --git a/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.json b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.json new file mode 100644 index 000000000..25b17f062 --- /dev/null +++ b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.json @@ -0,0 +1,61 @@ +{ + "_id": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "version": "1.0.0", + "peerDependencies": { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer": "2" + }, + "peerDependenciesMeta": { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer": { + "optional": true + } + }, + "_id": "@isaacs/conflicted-peer-optional-from-dev-dep-b@1.0.0", + "_nodeVersion": "15.3.0", + "_npmVersion": "7.5.3", + "dist": { + "integrity": "sha512-2rStqBkn1QSXBjweS4lHUPH9jt4ToFqkw2nUbWA4fZBxOaYS1nkS/ST70xRlQxRW+3UdTFGc+5xk187sHlMT/A==", + "shasum": "63f188dba7ec8a3e23b09540230ba8014ee760d2", + "tarball": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "fileCount": 1, + "unpackedSize": 299, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJI1ACRA9TVsSAnZWagAAbrMP+QHkDuGH+7Upc7vgCNGn\nh/BJ9oweduRP3C8DW4xviYVqUVYJdRtfN9NjA8DOA6RroecCaRONhiMPybz3\nD53u7QfKMAowj9z5KckriR8/RJ6v9EbFZWA3re/c3O6lNR8sZ0fPlw3VeOgq\nxuofI21+n3SQNDlD2N/fK8YRGkqgx4QI90IF0gb5q1k56cFP7DAqAHnRaxk9\nZ60SA8iv/lYu60+bGrnozv4H1qn6VM9m2hm/284H+HkGMOCBXSLpPLu6N1oJ\nNBy0rrp+5nhHHizWxEtTeI7xMds59p9IjMXMHSyVjnKakPYqLXeyda/dceCM\naqx5KFVyiJV+fcnqJgjgjbOLnE4+Xaz3PcSrYOfThSPgAvQBeOWk9vh0Wmuf\nMHpoONZt7NSI3ZBEbJuELpaFRO94bqaZgLqJIxDd8CiYmnbBQnOTWdzWFmCU\nQxFkwkOdQFE2oGkRRocRuWK2oD+aNegW48/tR2ckIp8apKOz9xGyL7suWmdO\n6RjCoq+9LFQJLb4q3J+ebRKD4GP0vXWHbNr/pEEPdgJI3X3q0s3jn5ig2gvU\nLj/oEkXoiPgeOvqX2n53tRtWen8Zw/hRtk06tMBUPzdg5LbGON24F7aklIrU\nCdMZwtbk/LwKXU+8bJlhTl63YB8+qoYKC7WnCqxcTvFyxZobT7FPTnGSO0wE\nHGT/\r\n=wnoR\r\n-----END PGP SIGNATURE-----\r\n" + }, + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "directories": {}, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/conflicted-peer-optional-from-dev-dep-b_1.0.0_1613008191679_0.24514239505031288" + }, + "_hasShrinkwrap": false + } + }, + "time": { + "created": "2021-02-11T01:49:51.620Z", + "1.0.0": "2021-02-11T01:49:51.832Z", + "modified": "2021-02-11T01:49:54.757Z" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "readme": "ERROR: No README data found!", + "readmeFilename": "" +} diff --git a/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.min.json b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.min.json new file mode 100644 index 000000000..34b16a3cf --- /dev/null +++ b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-b.min.json @@ -0,0 +1,29 @@ +{ + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-b", + "version": "1.0.0", + "peerDependencies": { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer": "2" + }, + "dist": { + "integrity": "sha512-2rStqBkn1QSXBjweS4lHUPH9jt4ToFqkw2nUbWA4fZBxOaYS1nkS/ST70xRlQxRW+3UdTFGc+5xk187sHlMT/A==", + "shasum": "63f188dba7ec8a3e23b09540230ba8014ee760d2", + "tarball": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-b/-/conflicted-peer-optional-from-dev-dep-b-1.0.0.tgz", + "fileCount": 1, + "unpackedSize": 299, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJI1ACRA9TVsSAnZWagAAbrMP+QHkDuGH+7Upc7vgCNGn\nh/BJ9oweduRP3C8DW4xviYVqUVYJdRtfN9NjA8DOA6RroecCaRONhiMPybz3\nD53u7QfKMAowj9z5KckriR8/RJ6v9EbFZWA3re/c3O6lNR8sZ0fPlw3VeOgq\nxuofI21+n3SQNDlD2N/fK8YRGkqgx4QI90IF0gb5q1k56cFP7DAqAHnRaxk9\nZ60SA8iv/lYu60+bGrnozv4H1qn6VM9m2hm/284H+HkGMOCBXSLpPLu6N1oJ\nNBy0rrp+5nhHHizWxEtTeI7xMds59p9IjMXMHSyVjnKakPYqLXeyda/dceCM\naqx5KFVyiJV+fcnqJgjgjbOLnE4+Xaz3PcSrYOfThSPgAvQBeOWk9vh0Wmuf\nMHpoONZt7NSI3ZBEbJuELpaFRO94bqaZgLqJIxDd8CiYmnbBQnOTWdzWFmCU\nQxFkwkOdQFE2oGkRRocRuWK2oD+aNegW48/tR2ckIp8apKOz9xGyL7suWmdO\n6RjCoq+9LFQJLb4q3J+ebRKD4GP0vXWHbNr/pEEPdgJI3X3q0s3jn5ig2gvU\nLj/oEkXoiPgeOvqX2n53tRtWen8Zw/hRtk06tMBUPzdg5LbGON24F7aklIrU\nCdMZwtbk/LwKXU+8bJlhTl63YB8+qoYKC7WnCqxcTvFyxZobT7FPTnGSO0wE\nHGT/\r\n=wnoR\r\n-----END PGP SIGNATURE-----\r\n" + }, + "peerDependenciesMeta": { + "@isaacs/conflicted-peer-optional-from-dev-dep-peer": { + "optional": true + } + } + } + }, + "modified": "2021-02-11T01:49:54.757Z" +} diff --git a/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.json b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.json new file mode 100644 index 000000000..e07a5e338 --- /dev/null +++ b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.json @@ -0,0 +1,86 @@ +{ + "_id": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "_rev": "1-1a8e917429566dc7ed1fb9b8fd70bb6f", + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "version": "1.0.0", + "_id": "@isaacs/conflicted-peer-optional-from-dev-dep-peer@1.0.0", + "_nodeVersion": "15.3.0", + "_npmVersion": "7.5.3", + "dist": { + "integrity": "sha512-aigW1kh3zkxch1C45Fkl4Xzb5pScrV3NoXefGffNArG+S/etd2vmrPXm4G6SFwwPgIcmlygsVgp4TkrAqGjqvg==", + "shasum": "2aae82d451c3683c60779fdf10d437d5425fd363", + "tarball": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "fileCount": 1, + "unpackedSize": 89, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJI1ECRA9TVsSAnZWagAAecUP/jgRv7bsxpJEQPHhDxEc\n0zcya5koI2VYrgVWbE9kmO0sXg8lyQCMJKLq29gjXsN1O9jiaYsgi0xVsB/G\nWfL8/MyqFGP+WRlnGsWes63WNHa9IKOVnFDbzkwhDjLId4ZFKUuDQ6N3ZJTQ\nqy/jeXrdz2tdT0Y4rfjdTGBnNJWZyf78A3ESjDmf5K0g7hhEtdU1B1bLfOOb\nY7uS8ab7lR5SsW65vn7aoaL8kxQC9Q4a7KNb/PpvxchIUMrAyVQAx85tK9ct\nPuE7Sd6x0JyHV/y5gjeJqzwjA7FqgRS6stdu1287YXqy7RKDvdbHdXM4TAqY\nlUbB+Kp7dMzoQM+OwP9NEny+axBilAYLHIHTgF995YveBodWUr/4o+ZA7RQp\ncUdjz6hJCNvQHpPNHVfTxvn2tHa/Gd9Rk7Lk52MhLcREDnKvL6lktgZ/cJOy\nOW2pBeoOEEhPQWzVTp2ugYNHeFDVqqUP647qZ1nu8qfhzrCo38WR6enGJpgs\n0NJtxwtxLkTOU7j7mnd0D8SPZIK3NI9ackKb6RhJ6mzURwPjTsmMrxWpCIoV\n+aMNt9ugfYOx3wi4z5tHxjkjS3lFvFJCWdR9N3Et8qWMoTeDZFLkQKihrxdT\na9YG58zvQyRLvtE3OrN2lCZw36ftSWVDCKCIM8VqGn4s64KE8LzRph3mW5EI\nkd2m\r\n=Q9LN\r\n-----END PGP SIGNATURE-----\r\n" + }, + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "directories": {}, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/conflicted-peer-optional-from-dev-dep-peer_1.0.0_1613008195871_0.9488799085098012" + }, + "_hasShrinkwrap": false + }, + "2.0.0": { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "version": "2.0.0", + "_id": "@isaacs/conflicted-peer-optional-from-dev-dep-peer@2.0.0", + "_nodeVersion": "15.3.0", + "_npmVersion": "7.5.3", + "dist": { + "integrity": "sha512-N/Bg11JKWel/PTOVDt89oQ3fw569d4h5bLkElozWaFkJ9qH7m05VY1L1Kfjik8sB+Bi8HnBS4JkP8F8VC1QHsw==", + "shasum": "cf64ce16c0cfe78c3c7b97c38a00d459b88d89a3", + "tarball": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-2.0.0.tgz", + "fileCount": 1, + "unpackedSize": 89, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJI1HCRA9TVsSAnZWagAAi94P/3sAxX8bVi5N+F+ZFY3W\nz9berGe2gT6Nt0vT6DHlMuIVqoW8EyrH+ZzB7hY1Ez/ws1D8doR/au17dMyQ\nSP0+gZHi3Jb1PAHdOlhh9mq8uc7GHKxoHRBvK9P763TPXwbLE0Rhb/2jqpP2\nLultq3+xM1GpmIKxPAf5bZ2v1StmfgKFww/FV+5UgAyumnz+lFlhDOoj3NoK\nGAFdBmiEnVnJ0lhMesv4iNBa+KYItR4y7EmLIHDaLfvumqT14tOSavhnSvRt\nTS7TL+UcKuru8fR2ZWrkms+zA6kjOF1nYzZDOq/zJ2LNJa9H7idoOyXQHPNR\nXieOWjq0AnfS8X23ogRBwKkf1wyfSnZe2VVXB7fF/T+l2WBEXI+b1jEb+oGE\n5ix1EGZJBwi0jYimUmaBVgjgNGy8+d365i22ymvM7P++HDi4w4Kk+LtG+gK8\nqFuloNfHBFvNFw5/wvmnVhQxx3bRGSwWLwhP2sgquH+BcXa+C+W0MAa3WF+A\n1hQmP6T/rfdPo8CKmuHrPVHrFslrZC81zLwWynb0ea6zLvHdwx+f+9oENcrp\n4WirMDZPwQvDlsIvQiRPbPinADomAkdDpmOfUn1O9N4o3E3ckgldbyzL9vOb\nPR4bO04F9eNo9jXx2pqeBLKB/V+0BhLz1a/uoi+Pk1s7wJq7IJ7iPw60vzld\nNOEm\r\n=vQNY\r\n-----END PGP SIGNATURE-----\r\n" + }, + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "directories": {}, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/conflicted-peer-optional-from-dev-dep-peer_2.0.0_1613008199324_0.776370650758518" + }, + "_hasShrinkwrap": false + } + }, + "time": { + "created": "2021-02-11T01:49:55.832Z", + "1.0.0": "2021-02-11T01:49:55.989Z", + "modified": "2021-02-11T01:50:01.651Z", + "2.0.0": "2021-02-11T01:49:59.462Z" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "readme": "ERROR: No README data found!", + "readmeFilename": "" +} diff --git a/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.min.json b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.min.json new file mode 100644 index 000000000..1a75c9b1d --- /dev/null +++ b/test/fixtures/registry-mocks/content/isaacs/conflicted-peer-optional-from-dev-dep-peer.min.json @@ -0,0 +1,33 @@ +{ + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "version": "1.0.0", + "dist": { + "integrity": "sha512-aigW1kh3zkxch1C45Fkl4Xzb5pScrV3NoXefGffNArG+S/etd2vmrPXm4G6SFwwPgIcmlygsVgp4TkrAqGjqvg==", + "shasum": "2aae82d451c3683c60779fdf10d437d5425fd363", + "tarball": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-1.0.0.tgz", + "fileCount": 1, + "unpackedSize": 89, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJI1ECRA9TVsSAnZWagAAecUP/jgRv7bsxpJEQPHhDxEc\n0zcya5koI2VYrgVWbE9kmO0sXg8lyQCMJKLq29gjXsN1O9jiaYsgi0xVsB/G\nWfL8/MyqFGP+WRlnGsWes63WNHa9IKOVnFDbzkwhDjLId4ZFKUuDQ6N3ZJTQ\nqy/jeXrdz2tdT0Y4rfjdTGBnNJWZyf78A3ESjDmf5K0g7hhEtdU1B1bLfOOb\nY7uS8ab7lR5SsW65vn7aoaL8kxQC9Q4a7KNb/PpvxchIUMrAyVQAx85tK9ct\nPuE7Sd6x0JyHV/y5gjeJqzwjA7FqgRS6stdu1287YXqy7RKDvdbHdXM4TAqY\nlUbB+Kp7dMzoQM+OwP9NEny+axBilAYLHIHTgF995YveBodWUr/4o+ZA7RQp\ncUdjz6hJCNvQHpPNHVfTxvn2tHa/Gd9Rk7Lk52MhLcREDnKvL6lktgZ/cJOy\nOW2pBeoOEEhPQWzVTp2ugYNHeFDVqqUP647qZ1nu8qfhzrCo38WR6enGJpgs\n0NJtxwtxLkTOU7j7mnd0D8SPZIK3NI9ackKb6RhJ6mzURwPjTsmMrxWpCIoV\n+aMNt9ugfYOx3wi4z5tHxjkjS3lFvFJCWdR9N3Et8qWMoTeDZFLkQKihrxdT\na9YG58zvQyRLvtE3OrN2lCZw36ftSWVDCKCIM8VqGn4s64KE8LzRph3mW5EI\nkd2m\r\n=Q9LN\r\n-----END PGP SIGNATURE-----\r\n" + } + }, + "2.0.0": { + "name": "@isaacs/conflicted-peer-optional-from-dev-dep-peer", + "version": "2.0.0", + "dist": { + "integrity": "sha512-N/Bg11JKWel/PTOVDt89oQ3fw569d4h5bLkElozWaFkJ9qH7m05VY1L1Kfjik8sB+Bi8HnBS4JkP8F8VC1QHsw==", + "shasum": "cf64ce16c0cfe78c3c7b97c38a00d459b88d89a3", + "tarball": "https://registry.npmjs.org/@isaacs/conflicted-peer-optional-from-dev-dep-peer/-/conflicted-peer-optional-from-dev-dep-peer-2.0.0.tgz", + "fileCount": 1, + "unpackedSize": 89, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJI1HCRA9TVsSAnZWagAAi94P/3sAxX8bVi5N+F+ZFY3W\nz9berGe2gT6Nt0vT6DHlMuIVqoW8EyrH+ZzB7hY1Ez/ws1D8doR/au17dMyQ\nSP0+gZHi3Jb1PAHdOlhh9mq8uc7GHKxoHRBvK9P763TPXwbLE0Rhb/2jqpP2\nLultq3+xM1GpmIKxPAf5bZ2v1StmfgKFww/FV+5UgAyumnz+lFlhDOoj3NoK\nGAFdBmiEnVnJ0lhMesv4iNBa+KYItR4y7EmLIHDaLfvumqT14tOSavhnSvRt\nTS7TL+UcKuru8fR2ZWrkms+zA6kjOF1nYzZDOq/zJ2LNJa9H7idoOyXQHPNR\nXieOWjq0AnfS8X23ogRBwKkf1wyfSnZe2VVXB7fF/T+l2WBEXI+b1jEb+oGE\n5ix1EGZJBwi0jYimUmaBVgjgNGy8+d365i22ymvM7P++HDi4w4Kk+LtG+gK8\nqFuloNfHBFvNFw5/wvmnVhQxx3bRGSwWLwhP2sgquH+BcXa+C+W0MAa3WF+A\n1hQmP6T/rfdPo8CKmuHrPVHrFslrZC81zLwWynb0ea6zLvHdwx+f+9oENcrp\n4WirMDZPwQvDlsIvQiRPbPinADomAkdDpmOfUn1O9N4o3E3ckgldbyzL9vOb\nPR4bO04F9eNo9jXx2pqeBLKB/V+0BhLz1a/uoi+Pk1s7wJq7IJ7iPw60vzld\nNOEm\r\n=vQNY\r\n-----END PGP SIGNATURE-----\r\n" + } + } + }, + "modified": "2021-02-11T01:50:01.651Z" +}