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