From 71c6848acd97f00c84b07ee1a80d92a053d405b5 Mon Sep 17 00:00:00 2001 From: milaninfy <111582375+milaninfy@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:41:42 -0400 Subject: [PATCH] fix(exec): npx to run specified version if multiple version exist globally (#7587) When multiple version of the same package is exist globally either at top level or at any level as a sub dependency, even though the version specified does not exist at top level it was running top level bin since it matches the bin name. This fixes checks for depth of the found node along with already existing specs checks. Fixes: #7486 --- workspaces/libnpmexec/lib/index.js | 8 +++- workspaces/libnpmexec/test/registry.js | 62 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/workspaces/libnpmexec/lib/index.js b/workspaces/libnpmexec/lib/index.js index 79d3cc1512f8e..3d9d24469ac59 100644 --- a/workspaces/libnpmexec/lib/index.js +++ b/workspaces/libnpmexec/lib/index.js @@ -32,7 +32,7 @@ const getManifest = async (spec, flatOptions) => { // Returns the required manifest if the spec is missing from the tree // Returns the found node if it is in the tree -const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree }) => { +const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree, shallow }) => { // If asking for a spec by name only (spec.raw === spec.name): // - In local or global mode go with anything in the tree that matches // - If looking in the npx cache check if a newer version is available @@ -41,6 +41,10 @@ const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree }) => { // registry spec that is not a specific tag. const nodesBySpec = tree.inventory.query('packageName', spec.name) for (const node of nodesBySpec) { + // continue if node is not a top level node + if (shallow && node.depth) { + continue + } if (spec.rawSpec === '*') { return { node } } @@ -202,7 +206,7 @@ const exec = async (opts) => { const globalArb = new Arborist({ ...flatOptions, path: globalPath, global: true }) const globalTree = await globalArb.loadActual() const { manifest: globalManifest } = - await missingFromTree({ spec, tree: globalTree, flatOptions }) + await missingFromTree({ spec, tree: globalTree, flatOptions, shallow: true }) if (!globalManifest && await fileExists(`${globalBin}/${args[0]}`)) { binPaths.push(globalBin) return await run() diff --git a/workspaces/libnpmexec/test/registry.js b/workspaces/libnpmexec/test/registry.js index 8af792018c954..81c5f90bb2b4e 100644 --- a/workspaces/libnpmexec/test/registry.js +++ b/workspaces/libnpmexec/test/registry.js @@ -164,3 +164,65 @@ t.test('run multiple from registry', async t => { value: 'packages-2.0.0', }) }) +t.test('packages with different versions in the global tree', async t => { + const pkgA1 = createPkg({ + localVersion: '1.0.0', + versions: ['1.0.0', '2.0.0'], + name: '@npmcli/A', + }) + + const pkgA2 = createPkg({ + localVersion: '2.0.0', + name: '@npmcli/A', + versions: ['1.0.0', '2.0.0'], + }) + + const pkgB = createPkg({ + localVersion: '1.0.0', + name: '@npmcli/B', + }) + + const pkgBfix = merge(pkgB.fixtures, { + node_modules: { + '@npmcli': { B: { + node_modules: { + '@npmcli': { + A: pkgA2.fixtures.packages['@npmcli-A-2.0.0'], + } }, + 'package.json': { dependencies: { '@npmcli/A': '2.0.0' } }, + }, + }, + } }) + + const { chmod, exec, readOutput, binLinks, registry, path } = setup(t, { + pkg: [pkgA2.pkg, pkgA1.pkg, pkgB.pkg], + global: true, + testdir: merge(pkgA1.fixtures, pkgBfix), + }) + + await chmod() + await binLinks() + + await pkgA2.package({ registry, path, times: 2, tarballs: ['2.0.0'] }) + await pkgA1.package({ registry, path, times: 1, tarballs: [] }) + + await exec({ + args: ['@npmcli/A@2.0.0'], + }) + + t.match(await readOutput('@npmcli-A'), { + value: 'packages-2.0.0', + args: [], + created: 'packages/@npmcli-A-2.0.0/bin-file.js', + }) + + await exec({ + args: ['@npmcli/A@1.0.0'], + }) + + t.match(await readOutput('@npmcli-A'), { + value: 'local-1.0.0', + args: [], + created: 'global/node_modules/@npmcli/A/bin-file.js', + }) +})