Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: look in workspace for exec commands #6973

Merged
merged 1 commit into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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