diff --git a/workspaces/libnpmexec/lib/index.js b/workspaces/libnpmexec/lib/index.js index 79d3cc1512f8e..99460250977a9 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, depth }) => { // 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 at specified depth, no need to check further + if (depth !== undefined && node.depth !== depth) { + continue + } if (spec.rawSpec === '*') { return { node } } @@ -201,9 +205,11 @@ const exec = async (opts) => { // See if the package is installed globally, and run the translated bin const globalArb = new Arborist({ ...flatOptions, path: globalPath, global: true }) const globalTree = await globalArb.loadActual() - const { manifest: globalManifest } = - await missingFromTree({ spec, tree: globalTree, flatOptions }) - if (!globalManifest && await fileExists(`${globalBin}/${args[0]}`)) { + const { node: globalNode } = + await missingFromTree({ spec, tree: globalTree, flatOptions, depth: 0 }) + + // if only found at top level + if (globalNode && 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', + }) +})