• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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