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