Skip to content

Commit 6eba131

Browse files
authored
fix: don't prompt on npm exec [directory] (#5298)
Local directories have to be "installed" so that their bins are linked and set up and callable, the user shouldn't need to be prompted to do that. Note that this does NOT affect anything passed via the `--package` param, because that may also contain non-directory specs so the existing behavior needs to be preserved. This is a small QOL improvement for the isolated use case of "npm exec [directory]" This also updates the hashing method used to come up with the `.npx` directory to resolve the paths to packages first, so that `npm exec .` in different directories don't share the same `.npx` directory.
1 parent e5a9162 commit 6eba131

File tree

2 files changed

+37
-3
lines changed

2 files changed

+37
-3
lines changed

workspaces/libnpmexec/lib/index.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ const missingFromTree = async ({ spec, tree, flatOptions }) => {
6363
// non-registry spec, or a specific tag. Look up manifest and check
6464
// resolved to see if it's in the tree.
6565
const manifest = await getManifest(spec, flatOptions)
66+
if (spec.type === 'directory') {
67+
return { manifest }
68+
}
6669
const nodesByManifest = tree.inventory.query('packageName', manifest.name)
6770
for (const node of nodesByManifest) {
6871
if (node.package.resolved === manifest._resolved) {
@@ -89,10 +92,10 @@ const exec = async (opts) => {
8992
path = '.',
9093
runPath = '.',
9194
scriptShell = isWindows ? process.env.ComSpec || 'cmd' : 'sh',
92-
yes = undefined,
9395
...flatOptions
9496
} = opts
9597

98+
let yes = opts.yes
9699
const run = () => runScript({
97100
args,
98101
call,
@@ -129,6 +132,16 @@ const exec = async (opts) => {
129132
packages.push(args[0])
130133
}
131134

135+
// Resolve any directory specs so that the npx directory is unique to the
136+
// resolved directory, not the potentially relative one (i.e. "npx .")
137+
for (const i in packages) {
138+
const pkg = packages[i]
139+
const spec = npa(pkg)
140+
if (spec.type === 'directory') {
141+
packages[i] = spec.fetchSpec
142+
}
143+
}
144+
132145
const localArb = new Arborist({ ...flatOptions, path })
133146
const localTree = await localArb.loadActual()
134147

@@ -153,6 +166,10 @@ const exec = async (opts) => {
153166
if (needPackageCommandSwap) {
154167
const spec = npa(args[0])
155168

169+
if (spec.type === 'directory') {
170+
yes = true
171+
}
172+
156173
args[0] = getBinFromManifest(commandManifest)
157174

158175
if (needInstall.length > 0 && globalPath) {
@@ -176,7 +193,15 @@ const exec = async (opts) => {
176193
throw new Error('Must provide a valid npxCache path')
177194
}
178195
const hash = crypto.createHash('sha512')
179-
.update(packages.sort((a, b) => a.localeCompare(b, 'en')).join('\n'))
196+
.update(packages.map(p => {
197+
// Keeps the npx directory unique to the resolved directory, not the
198+
// potentially relative one (i.e. "npx .")
199+
const spec = npa(p)
200+
if (spec.type === 'directory') {
201+
return spec.fetchSpec
202+
}
203+
return p
204+
}).sort((a, b) => a.localeCompare(b, 'en')).join('\n'))
180205
.digest('hex')
181206
.slice(0, 16)
182207
const installDir = resolve(npxCache, hash)

workspaces/libnpmexec/test/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,16 @@ require('fs').writeFileSync(process.argv.slice(2)[0], 'LOCAL PKG')`,
482482
const executable = resolve(path, 'a/index.js')
483483
fs.chmodSync(executable, 0o775)
484484

485-
await libexec({
485+
const mockexec = t.mock('../lib/index.js', {
486+
'@npmcli/ci-detect': () => true,
487+
'proc-log': {
488+
warn (title, msg) {
489+
t.fail('should not warn about local file package install')
490+
},
491+
},
492+
})
493+
494+
await mockexec({
486495
...baseOpts,
487496
args: [`file:${resolve(path, 'a')}`, 'resfile'],
488497
cache,

0 commit comments

Comments
 (0)