diff --git a/workspaces/libnpmexec/lib/index.js b/workspaces/libnpmexec/lib/index.js index 502082c6b0dcc..3681653d8217d 100644 --- a/workspaces/libnpmexec/lib/index.js +++ b/workspaces/libnpmexec/lib/index.js @@ -72,12 +72,12 @@ const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree, shallow }) // non-registry spec, or a specific tag, or name only in npx tree. Look up // manifest and check resolved to see if it's in the tree. const manifest = await getManifest(spec, flatOptions) - if (spec.type === 'directory') { + if (spec.type === 'directory' && !isNpxTree) { return { manifest } } const nodesByManifest = tree.inventory.query('packageName', manifest.name) for (const node of nodesByManifest) { - if (node.package.resolved === manifest._resolved) { + if (node.package.resolved === manifest._resolved || node.realpath === manifest._resolved) { // we have a package by the same name and the same resolved destination, nothing to add. return { node } } diff --git a/workspaces/libnpmexec/test/local.js b/workspaces/libnpmexec/test/local.js index 5b0133105393d..23035b01769e3 100644 --- a/workspaces/libnpmexec/test/local.js +++ b/workspaces/libnpmexec/test/local.js @@ -278,6 +278,59 @@ t.test('no npxCache', async t => { }), /Must provide a valid npxCache path/) }) +t.test('local file system path - skips reify on subsequent runs', async t => { + let reifyCount = 0 + const Arborist = require('@npmcli/arborist') + const { exec, chmod, readOutput, rmOutput, path } = setup(t, { + mocks: { + 'ci-info': { isCI: true }, + '@npmcli/arborist': class extends Arborist { + async reify (...args) { + reifyCount++ + return super.reify(...args) + } + }, + }, + testdir: { + a: { + 'package.json': { + name: 'a', + bin: { + a: './index.js', + }, + }, + 'index.js': { key: 'a', value: 'LOCAL PKG' }, + }, + }, + }) + + await chmod('a/index.js') + + // First run should reify (cold cache) + await exec({ + args: [`file:${resolve(path, 'a')}`, 'resfile'], + }) + + t.match(await readOutput('a'), { + value: 'LOCAL PKG', + args: ['resfile'], + }) + t.equal(reifyCount, 1, 'first run should reify') + + await rmOutput('a') + + // Second run should skip reify (cached) + await exec({ + args: [`file:${resolve(path, 'a')}`, 'resfile'], + }) + + t.match(await readOutput('a'), { + value: 'LOCAL PKG', + args: ['resfile'], + }) + t.equal(reifyCount, 1, 'second run should not reify') +}) + t.test('local file system path', async t => { const { exec, chmod, readOutput, path } = setup(t, { mocks: {