exec.js 3.4 KB

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