explore.js 2.6 KB

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