Skip to content

Commit

Permalink
fix: look in workspace for exec commands (#6973)
Browse files Browse the repository at this point in the history
Closes #6765
  • Loading branch information
wraithgar authored Nov 14, 2023
1 parent d11496b commit f3a7380
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 10 deletions.
25 changes: 17 additions & 8 deletions lib/commands/exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,33 @@ class Exec extends BaseCommand {
for (const [name, path] of this.workspaces) {
const locationMsg =
`in workspace ${this.npm.chalk.green(name)} at location:\n${this.npm.chalk.dim(path)}`
await this.callExec(args, { locationMsg, runPath: path })
await this.callExec(args, { name, locationMsg, runPath: path })
}
}

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

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

const call = this.npm.config.get('call')
let globalPath
const {
flatOptions,
localBin,
globalBin,
globalDir,
chalk,
Expand Down Expand Up @@ -79,14 +88,14 @@ class Exec extends BaseCommand {
// copy args so they dont get mutated
args: [...args],
call,
localBin,
locationMsg,
chalk,
globalBin,
globalPath,
localBin,
locationMsg,
output,
chalk,
packages,
path: localPrefix,
path,
runPath,
scriptShell,
yes,
Expand Down
93 changes: 91 additions & 2 deletions test/lib/commands/exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ t.test('--prefix', async t => {
t.ok(exists.isFile(), 'bin ran, creating file')
})

t.test('workspaces', async t => {
t.test('runs in workspace path', async t => {
const registry = new MockRegistry({
tap: t,
registry: 'https://registry.npmjs.org/',
Expand Down Expand Up @@ -124,12 +124,101 @@ t.test('workspaces', async t => {
await registry.package({ manifest,
tarballs: {
'1.0.0': path.join(npm.prefix, 'npm-exec-test'),
} })
},
})
await npm.exec('exec', ['@npmcli/npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file inside workspace')
})

t.test('finds workspace bin first', async t => {
const { npm } = await loadMockNpm(t, {
config: {
workspace: ['workspace-a'],
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-root-test',
bin: { 'npx-test': 'index.js' },
workspaces: ['workspace-a'],
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-fail', '')`,
'workspace-a': {
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-test',
bin: { 'npx-test': 'index.js' },
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
},
})

await npm.exec('install', []) // reify
await npm.exec('exec', ['npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file inside workspace')
t.rejects(fs.stat(path.join(npm.prefix, 'npm-exec-test-fail')))
})

t.test('finds workspace dep first', async t => {
const registry = new MockRegistry({
tap: t,
registry: 'https://registry.npmjs.org/',
})

const manifest = registry.manifest({ name: '@npmcli/subdep', versions: ['1.0.0', '2.0.0'] })
manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' }
manifest.versions['2.0.0'].bin = { 'npx-test': 'index.js' }

const { npm } = await loadMockNpm(t, {
prefixDir: {
subdep: {
one: {
'package.json': JSON.stringify(manifest.versions['1.0.0']),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-one', '')`,
},
two: {
'package.json': JSON.stringify(manifest.versions['2.0.0']),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-two', '')`,
},
},
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-root-test',
dependencies: { '@npmcli/subdep': '1.0.0' },
bin: { 'npx-test': 'index.js' },
workspaces: ['workspace-a'],
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-fail', '')`,
'workspace-a': {
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-test',
dependencies: { '@npmcli/subdep': '2.0.0' },
bin: { 'npx-test': 'index.js' },
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
},
})

await registry.package({ manifest,
tarballs: {
'1.0.0': path.join(npm.prefix, 'subdep', 'one'),
'2.0.0': path.join(npm.prefix, 'subdep', 'two'),
},
})
await npm.exec('install', [])
npm.config.set('workspace', ['workspace-a'])
await npm.exec('exec', ['npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file')
})

t.test('npx --no-install @npmcli/npx-test', async t => {
const registry = new MockRegistry({
tap: t,
Expand Down

0 comments on commit f3a7380

Please sign in to comment.