diff --git a/lib/npm.js b/lib/npm.js index 84b22f2db5a80..df2297b215da7 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -93,14 +93,7 @@ class Npm { } async load () { - return time.start('npm:load', async () => { - const { exec } = await this.#load() - return { - exec, - command: this.argv.shift(), - args: this.argv, - } - }) + return time.start('npm:load', () => this.#load()) } get loaded () { @@ -165,7 +158,26 @@ class Npm { await time.start('npm:load:configload', () => this.config.load()) + // npm --versions + if (this.config.get('versions', 'cli')) { + this.argv = ['version'] + this.config.set('usage', false, 'cli') + } else { + this.argv = [...this.config.parsedArgv.remain] + } + + // Remove first argv since that is our command as typed + // Note that this might not be the actual name of the command + // due to aliases, etc. But we use the raw form of it later + // in user output so it must be preserved as is. + const commandArg = this.argv.shift() + + // This is the actual name of the command that will be run or + // undefined if deref could not find a match + const command = deref(commandArg) + await this.#display.load({ + command, loglevel: this.config.get('loglevel'), stdoutColor: this.color, stderrColor: this.logColor, @@ -202,9 +214,10 @@ class Npm { // note: this MUST be shorter than the actual argv length, because it // uses the same memory, so node will truncate it if it's too long. + // We time this because setting process.title is slow sometimes but we + // have to do it for security reasons. But still helpful to know how slow it is. time.start('npm:load:setTitle', () => { const { parsedArgv: { cooked, remain } } = this.config - this.argv = remain // Secrets are mostly in configs, so title is set using only the positional args // to keep those from being leaked. We still do a best effort replaceInfo. this.#title = ['npm'].concat(replaceInfo(remain)).join(' ').trim() @@ -244,13 +257,7 @@ class Npm { log.warn('using --force', 'Recommended protections disabled.') } - // npm --versions - if (this.config.get('versions', 'cli')) { - this.argv = ['version'] - this.config.set('usage', false, 'cli') - } - - return { exec: true } + return { exec: true, command: commandArg, args: this.argv } } get isShellout () { diff --git a/lib/utils/display.js b/lib/utils/display.js index 67c745f310415..299edc797aaf3 100644 --- a/lib/utils/display.js +++ b/lib/utils/display.js @@ -119,6 +119,7 @@ class Display { #progress // options + #command #levelIndex #timing #json @@ -159,6 +160,7 @@ class Display { } async load ({ + command, heading, json, loglevel, @@ -168,6 +170,7 @@ class Display { timing, unicode, }) { + this.#command = command // get createSupportsColor from chalk directly if this lands // https://github.com/chalk/chalk/pull/600 const [{ Chalk }, { createSupportsColor }] = await Promise.all([ @@ -272,10 +275,14 @@ class Display { return } - // HACK: if it looks like the banner and we are silent do not print it. - // There's no other way to do this right now :( - // eslint-disable-next-line max-len - if (this.#silent && args.length === 1 && args[0].startsWith('\n> ') && args[0].endsWith('\n')) { + // HACK: if it looks like the banner and we are in a state where we hide the + // banner then dont write any output. This hack can be replaced with proc-log.META + const isBanner = args.length === 1 && + typeof args[0] === 'string' && + args[0].startsWith('\n> ') && + args[0].endsWith('\n') + const hideBanner = this.#silent || ['exec', 'explore'].includes(this.#command) + if (isBanner && hideBanner) { return } diff --git a/test/lib/docs.js b/test/lib/docs.js index 7ace9b9995701..e074d556bb360 100644 --- a/test/lib/docs.js +++ b/test/lib/docs.js @@ -74,7 +74,7 @@ t.test('basic usage', async t => { // are generated in the following test const { npm } = await loadMockNpm(t, { mocks: { - '{LIB}/utils/cmd-list.js': { commands: [] }, + '{LIB}/utils/cmd-list.js': { ...cmdList, commands: [] }, }, config: { userconfig: '/some/config/file/.npmrc' }, globals: { process: { platform: 'posix' } },