Skip to content

Commit f3a7380

Browse files
authoredNov 14, 2023
fix: look in workspace for exec commands (#6973)
Closes #6765
1 parent d11496b commit f3a7380

File tree

2 files changed

+108
-10
lines changed

2 files changed

+108
-10
lines changed
 

‎lib/commands/exec.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,33 @@ class Exec extends BaseCommand {
3434
for (const [name, path] of this.workspaces) {
3535
const locationMsg =
3636
`in workspace ${this.npm.chalk.green(name)} at location:\n${this.npm.chalk.dim(path)}`
37-
await this.callExec(args, { locationMsg, runPath: path })
37+
await this.callExec(args, { name, locationMsg, runPath: path })
3838
}
3939
}
4040

41-
async callExec (args, { locationMsg, runPath } = {}) {
42-
// This is where libnpmexec will look for locally installed packages
41+
async callExec (args, { name, locationMsg, runPath } = {}) {
42+
// This is where libnpmexec will look for locally installed packages at the project level
4343
const localPrefix = this.npm.localPrefix
44+
// This is where libnpmexec will look for locally installed packages at the workspace level
45+
let localBin = this.npm.localBin
46+
let path = localPrefix
4447

4548
// This is where libnpmexec will actually run the scripts from
4649
if (!runPath) {
4750
runPath = process.cwd()
51+
} else {
52+
// We have to consider if the workspace has its own separate versions
53+
// libnpmexec will walk up to localDir after looking here
54+
localBin = resolve(this.npm.localDir, name, 'node_modules', '.bin')
55+
// We also need to look for `bin` entries in the workspace package.json
56+
// libnpmexec will NOT look in the project root for the bin entry
57+
path = runPath
4858
}
4959

5060
const call = this.npm.config.get('call')
5161
let globalPath
5262
const {
5363
flatOptions,
54-
localBin,
5564
globalBin,
5665
globalDir,
5766
chalk,
@@ -79,14 +88,14 @@ class Exec extends BaseCommand {
7988
// copy args so they dont get mutated
8089
args: [...args],
8190
call,
82-
localBin,
83-
locationMsg,
91+
chalk,
8492
globalBin,
8593
globalPath,
94+
localBin,
95+
locationMsg,
8696
output,
87-
chalk,
8897
packages,
89-
path: localPrefix,
98+
path,
9099
runPath,
91100
scriptShell,
92101
yes,

‎test/lib/commands/exec.js

+91-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ t.test('--prefix', async t => {
8888
t.ok(exists.isFile(), 'bin ran, creating file')
8989
})
9090

91-
t.test('workspaces', async t => {
91+
t.test('runs in workspace path', async t => {
9292
const registry = new MockRegistry({
9393
tap: t,
9494
registry: 'https://registry.npmjs.org/',
@@ -124,12 +124,101 @@ t.test('workspaces', async t => {
124124
await registry.package({ manifest,
125125
tarballs: {
126126
'1.0.0': path.join(npm.prefix, 'npm-exec-test'),
127-
} })
127+
},
128+
})
128129
await npm.exec('exec', ['@npmcli/npx-test'])
129130
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
130131
t.ok(exists.isFile(), 'bin ran, creating file inside workspace')
131132
})
132133

134+
t.test('finds workspace bin first', async t => {
135+
const { npm } = await loadMockNpm(t, {
136+
config: {
137+
workspace: ['workspace-a'],
138+
},
139+
prefixDir: {
140+
'package.json': JSON.stringify({
141+
name: '@npmcli/npx-workspace-root-test',
142+
bin: { 'npx-test': 'index.js' },
143+
workspaces: ['workspace-a'],
144+
}),
145+
'index.js': `#!/usr/bin/env node
146+
require('fs').writeFileSync('npm-exec-test-fail', '')`,
147+
'workspace-a': {
148+
'package.json': JSON.stringify({
149+
name: '@npmcli/npx-workspace-test',
150+
bin: { 'npx-test': 'index.js' },
151+
}),
152+
'index.js': `#!/usr/bin/env node
153+
require('fs').writeFileSync('npm-exec-test-success', '')`,
154+
},
155+
},
156+
})
157+
158+
await npm.exec('install', []) // reify
159+
await npm.exec('exec', ['npx-test'])
160+
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
161+
t.ok(exists.isFile(), 'bin ran, creating file inside workspace')
162+
t.rejects(fs.stat(path.join(npm.prefix, 'npm-exec-test-fail')))
163+
})
164+
165+
t.test('finds workspace dep first', async t => {
166+
const registry = new MockRegistry({
167+
tap: t,
168+
registry: 'https://registry.npmjs.org/',
169+
})
170+
171+
const manifest = registry.manifest({ name: '@npmcli/subdep', versions: ['1.0.0', '2.0.0'] })
172+
manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' }
173+
manifest.versions['2.0.0'].bin = { 'npx-test': 'index.js' }
174+
175+
const { npm } = await loadMockNpm(t, {
176+
prefixDir: {
177+
subdep: {
178+
one: {
179+
'package.json': JSON.stringify(manifest.versions['1.0.0']),
180+
'index.js': `#!/usr/bin/env node
181+
require('fs').writeFileSync('npm-exec-test-one', '')`,
182+
},
183+
two: {
184+
'package.json': JSON.stringify(manifest.versions['2.0.0']),
185+
'index.js': `#!/usr/bin/env node
186+
require('fs').writeFileSync('npm-exec-test-two', '')`,
187+
},
188+
},
189+
'package.json': JSON.stringify({
190+
name: '@npmcli/npx-workspace-root-test',
191+
dependencies: { '@npmcli/subdep': '1.0.0' },
192+
bin: { 'npx-test': 'index.js' },
193+
workspaces: ['workspace-a'],
194+
}),
195+
'index.js': `#!/usr/bin/env node
196+
require('fs').writeFileSync('npm-exec-test-fail', '')`,
197+
'workspace-a': {
198+
'package.json': JSON.stringify({
199+
name: '@npmcli/npx-workspace-test',
200+
dependencies: { '@npmcli/subdep': '2.0.0' },
201+
bin: { 'npx-test': 'index.js' },
202+
}),
203+
'index.js': `#!/usr/bin/env node
204+
require('fs').writeFileSync('npm-exec-test-success', '')`,
205+
},
206+
},
207+
})
208+
209+
await registry.package({ manifest,
210+
tarballs: {
211+
'1.0.0': path.join(npm.prefix, 'subdep', 'one'),
212+
'2.0.0': path.join(npm.prefix, 'subdep', 'two'),
213+
},
214+
})
215+
await npm.exec('install', [])
216+
npm.config.set('workspace', ['workspace-a'])
217+
await npm.exec('exec', ['npx-test'])
218+
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
219+
t.ok(exists.isFile(), 'bin ran, creating file')
220+
})
221+
133222
t.test('npx --no-install @npmcli/npx-test', async t => {
134223
const registry = new MockRegistry({
135224
tap: t,

0 commit comments

Comments
 (0)
Please sign in to comment.