Skip to content

Commit a767aae

Browse files
wraithgarlukekarrys
authored andcommitted
fix(npx): look for bins in local package.json
1 parent 1b29306 commit a767aae

File tree

4 files changed

+38
-31
lines changed

4 files changed

+38
-31
lines changed

workspaces/libnpmexec/lib/index.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ const exec = async (opts) => {
7979
const {
8080
args = [],
8181
call = '',
82-
color = false,
8382
localBin = resolve('./node_modules/.bin'),
8483
locationMsg = undefined,
8584
globalBin = '',
@@ -97,7 +96,6 @@ const exec = async (opts) => {
9796
const run = () => runScript({
9897
args,
9998
call,
100-
color,
10199
flatOptions,
102100
locationMsg,
103101
output,
@@ -114,20 +112,36 @@ const exec = async (opts) => {
114112

115113
const needPackageCommandSwap = (args.length > 0) && (packages.length === 0)
116114
// If they asked for a command w/o specifying a package, see if there is a
117-
// bin that directly matches that name either globally or in the local tree.
115+
// bin that directly matches that name:
116+
// - in the local package itself
117+
// - in the local tree
118+
// - globally
118119
if (needPackageCommandSwap) {
119-
const dir = dirname(dirname(localBin))
120-
const localBinPath = await localFileExists(dir, args[0], '/')
121-
if (localBinPath) {
122-
binPaths.push(localBinPath)
123-
return await run()
124-
} else if (globalPath && await fileExists(`${globalBin}/${args[0]}`)) {
125-
binPaths.push(globalBin)
126-
return await run()
120+
let localManifest
121+
try {
122+
localManifest = await pacote.manifest(path, flatOptions)
123+
} catch {
124+
// no local package.json? no problem, move one.
125+
}
126+
if (localManifest?.bin?.[args[0]]) {
127+
// we have to install the local package into the npx cache so that its
128+
// bin links get set up
129+
packages.push(path)
130+
yes = true
131+
flatOptions.installLinks = false
132+
} else {
133+
const dir = dirname(dirname(localBin))
134+
const localBinPath = await localFileExists(dir, args[0], '/')
135+
if (localBinPath) {
136+
binPaths.push(localBinPath)
137+
return await run()
138+
} else if (globalPath && await fileExists(`${globalBin}/${args[0]}`)) {
139+
binPaths.push(globalBin)
140+
return await run()
141+
}
142+
// We swap out args[0] with the bin from the manifest later
143+
packages.push(args[0])
127144
}
128-
129-
// We swap out args[0] with the bin from the manifest later
130-
packages.push(args[0])
131145
}
132146

133147
// Resolve any directory specs so that the npx directory is unique to the

workspaces/libnpmexec/lib/run-script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const nocolor = {
1515
const run = async ({
1616
args,
1717
call,
18-
color,
1918
flatOptions,
2019
locationMsg,
2120
output = () => {},
@@ -26,6 +25,7 @@ const run = async ({
2625
}) => {
2726
// turn list of args into command string
2827
const script = call || args.shift() || scriptShell
28+
const color = !!flatOptions.color
2929
const colorize = color ? chalk : nocolor
3030

3131
// do the fakey runScript dance

workspaces/libnpmexec/test/index.js

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,31 @@ const baseOpts = {
2424
yes: true,
2525
}
2626

27-
t.test('local pkg', async t => {
27+
t.test('bin in local pkg', async t => {
2828
const pkg = {
29-
name: 'pkg',
29+
name: '@npmcli/local-pkg-bin-test',
3030
bin: {
31-
a: 'index.js',
31+
a: 'local-bin-test.js',
3232
},
3333
}
3434
const path = t.testdir({
3535
cache: {},
3636
npxCache: {},
37-
node_modules: {
38-
'.bin': {},
39-
a: {
40-
'index.js': `#!/usr/bin/env node
37+
'local-bin-test.js': `#!/usr/bin/env node
4138
require('fs').writeFileSync(process.argv.slice(2)[0], 'LOCAL PKG')`,
42-
},
43-
},
4439
'package.json': JSON.stringify(pkg),
4540
})
4641
const localBin = resolve(path, 'node_modules/.bin')
4742
const runPath = path
43+
const npxCache = resolve(path, 'npxCache')
4844

49-
const executable = resolve(path, 'node_modules/a')
45+
const executable = resolve(path, 'local-bin-test.js')
5046
fs.chmodSync(executable, 0o775)
5147

52-
await binLinks({
53-
path: resolve(path, 'node_modules/a'),
54-
pkg,
55-
})
56-
5748
await libexec({
5849
...baseOpts,
5950
args: ['a', 'resfile'],
51+
npxCache,
6052
localBin,
6153
path,
6254
runPath,

workspaces/libnpmexec/test/run-script.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const baseOpts = {
44
args: [],
55
call: '',
66
color: false,
7+
flatOptions: {},
78
path: '',
89
runPath: '',
910
shell: process.platform === 'win32'
@@ -73,7 +74,7 @@ t.test('colorized interactive mode msg', async t => {
7374
OUTPUT.push(msg)
7475
},
7576
runPath: '/foo/',
76-
color: true,
77+
flatOptions: { color: true },
7778
})
7879
t.matchSnapshot(OUTPUT.join('\n'), 'should print colorized output')
7980
})

0 commit comments

Comments
 (0)