1const { resolve } = require('path') 2const libexec = require('libnpmexec') 3const BaseCommand = require('../base-command.js') 4 5class Exec extends BaseCommand { 6 static description = 'Run a command from a local or remote npm package' 7 static params = [ 8 'package', 9 'call', 10 'workspace', 11 'workspaces', 12 'include-workspace-root', 13 ] 14 15 static name = 'exec' 16 static usage = [ 17 '-- <pkg>[@<version>] [args...]', 18 '--package=<pkg>[@<version>] -- <cmd> [args...]', 19 '-c \'<cmd> [args...]\'', 20 '--package=foo -c \'<cmd> [args...]\'', 21 ] 22 23 static workspaces = true 24 static ignoreImplicitWorkspace = false 25 static isShellout = true 26 27 async exec (args) { 28 return this.callExec(args) 29 } 30 31 async execWorkspaces (args) { 32 await this.setWorkspaces() 33 34 for (const [name, path] of this.workspaces) { 35 const locationMsg = 36 `in workspace ${this.npm.chalk.green(name)} at location:\n${this.npm.chalk.dim(path)}` 37 await this.callExec(args, { locationMsg, runPath: path }) 38 } 39 } 40 41 async callExec (args, { locationMsg, runPath } = {}) { 42 // This is where libnpmexec will look for locally installed packages 43 const localPrefix = this.npm.localPrefix 44 45 // This is where libnpmexec will actually run the scripts from 46 if (!runPath) { 47 runPath = process.cwd() 48 } 49 50 const call = this.npm.config.get('call') 51 let globalPath 52 const { 53 flatOptions, 54 localBin, 55 globalBin, 56 globalDir, 57 chalk, 58 } = this.npm 59 const output = this.npm.output.bind(this.npm) 60 const scriptShell = this.npm.config.get('script-shell') || undefined 61 const packages = this.npm.config.get('package') 62 const yes = this.npm.config.get('yes') 63 // --prefix sets both of these to the same thing, meaning the global prefix 64 // is invalid (i.e. no lib/node_modules). This is not a trivial thing to 65 // untangle and fix so we work around it here. 66 if (this.npm.localPrefix !== this.npm.globalPrefix) { 67 globalPath = resolve(globalDir, '..') 68 } 69 70 if (call && args.length) { 71 throw this.usageError() 72 } 73 74 return libexec({ 75 ...flatOptions, 76 // we explicitly set packageLockOnly to false because if it's true 77 // when we try to install a missing package, we won't actually install it 78 packageLockOnly: false, 79 // copy args so they dont get mutated 80 args: [...args], 81 call, 82 localBin, 83 locationMsg, 84 globalBin, 85 globalPath, 86 output, 87 chalk, 88 packages, 89 path: localPrefix, 90 runPath, 91 scriptShell, 92 yes, 93 }) 94 } 95} 96 97module.exports = Exec 98