explore.js 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. const pkgJson = require('@npmcli/package-json')
  2. const runScript = require('@npmcli/run-script')
  3. const { join, relative } = require('node:path')
  4. const { log, output } = require('proc-log')
  5. const completion = require('../utils/installed-shallow.js')
  6. const BaseCommand = require('../base-cmd.js')
  7. // npm explore <pkg>[@<version>]
  8. // open a subshell to the package folder.
  9. class Explore extends BaseCommand {
  10. static description = 'Browse an installed package'
  11. static name = 'explore'
  12. static usage = ['<pkg> [ -- <command>]']
  13. static params = ['shell']
  14. static ignoreImplicitWorkspace = false
  15. static async completion (opts, npm) {
  16. return completion(npm, opts)
  17. }
  18. async exec (args) {
  19. if (args.length < 1 || !args[0]) {
  20. throw this.usageError()
  21. }
  22. const pkgname = args.shift()
  23. // detect and prevent any .. shenanigans
  24. const path = join(this.npm.dir, join('/', pkgname))
  25. if (relative(path, this.npm.dir) === '') {
  26. throw this.usageError()
  27. }
  28. // run as if running a script named '_explore', which we set to either the set of arguments, or the shell config, and let @npmcli/run-script handle all the escaping and PATH setup stuff.
  29. const { content: pkg } = await pkgJson.normalize(path).catch(er => {
  30. log.error('explore', `It doesn't look like ${pkgname} is installed.`)
  31. throw er
  32. })
  33. const { shell } = this.npm.flatOptions
  34. pkg.scripts = {
  35. ...(pkg.scripts || {}),
  36. _explore: args.join(' ').trim() || shell,
  37. }
  38. if (!args.length) {
  39. output.standard(`\nExploring ${path}\nType 'exit' or ^D when finished\n`)
  40. }
  41. return runScript({
  42. ...this.npm.flatOptions,
  43. pkg,
  44. path,
  45. event: '_explore',
  46. stdio: 'inherit',
  47. }).catch(er => {
  48. process.exitCode = typeof er.code === 'number' && er.code !== 0 ? er.code
  49. : 1
  50. // if it's not an exit error, or non-interactive, throw it
  51. const isProcExit = er.message === 'command failed' &&
  52. (typeof er.code === 'number' || /^SIG/.test(er.signal || ''))
  53. if (args.length || !isProcExit) {
  54. throw er
  55. }
  56. })
  57. }
  58. }
  59. module.exports = Explore