perf(reporter): print a warning summary

Sometimes, when installing new dependencies that rely on many peer dependencies,
or when running installation on a huge monorepo, there will be hundreds or thousands of warnings.
Printing many messages to the terminal is expensive and reduces speed,
so pnpm will only print a few warnings and report the total number of the unprinted warnings.

PR #2826
This commit is contained in:
Zoltan Kochan
2020-09-04 13:58:19 +03:00
committed by GitHub
parent 231bedd853
commit af8361946a
3 changed files with 84 additions and 7 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/default-reporter": minor
---
Sometimes, when installing new dependencies that rely on many peer dependencies, or when running installation on a huge monorepo, there will be hundreds or thousands of warnings. Printing many messages to the terminal is expensive and reduces speed, so pnpm will only print a few warnings and report the total number of the unprinted warnings.

View File

@@ -1,6 +1,7 @@
import { Config } from '@pnpm/config'
import { Log, RegistryLog } from '@pnpm/core-loggers'
import { LogLevel } from '@pnpm/logger'
import PushStream from '@zkochan/zen-push'
import reportError from '../reportError'
import formatWarn from './utils/formatWarn'
import { autozoom } from './utils/zooming'
@@ -16,6 +17,8 @@ const LOG_LEVEL_NUMBER: Record<LogLevel, number> = {
}
// eslint-enable:object-literal-sort-keys
const MAX_SHOWN_WARNINGS = 5
export default (
log$: {
registry: most.Stream<RegistryLog>
@@ -29,22 +32,54 @@ export default (
}
) => {
const maxLogLevel = LOG_LEVEL_NUMBER[opts.logLevel ?? 'info'] ?? LOG_LEVEL_NUMBER['info']
const reportWarning = makeWarningReporter(opts)
return most.merge(log$.registry, log$.other)
.filter((obj) => LOG_LEVEL_NUMBER[obj.level] <= maxLogLevel &&
(obj.level !== 'info' || !obj['prefix'] || obj['prefix'] === opts.cwd))
.map((obj) => {
switch (obj.level) {
case 'warn':
return autozoom(opts.cwd, obj.prefix, formatWarn(obj.message), opts)
case 'warn': {
return reportWarning(obj)
}
case 'error':
if (obj['message']?.['prefix'] && obj['message']['prefix'] !== opts.cwd) {
return `${obj['message']['prefix'] as string}:` + os.EOL + reportError(obj, opts.config)
return most.of({
msg: `${obj['message']['prefix'] as string}:` + os.EOL + reportError(obj, opts.config),
})
}
return reportError(obj, opts.config)
return most.of({ msg: reportError(obj, opts.config) })
default:
return obj['message']
return most.of({ msg: obj['message'] })
}
})
.map((msg) => ({ msg }))
.map(most.of)
}
// Sometimes, when installing new dependencies that rely on many peer dependencies,
// or when running installation on a huge monorepo, there will be hundreds or thousands of warnings.
// Printing many messages to the terminal is expensive and reduces speed,
// so pnpm will only print a few warnings and report the total number of the unprinted warnings.
function makeWarningReporter (
opts: {
cwd: string
zoomOutCurrent: boolean
}
) {
let warningsCounter = 0
let collapsedWarnings: PushStream<{ msg: string }>
return (obj: { prefix: string, message: string }) => {
warningsCounter++
if (warningsCounter <= MAX_SHOWN_WARNINGS) {
return most.of({ msg: autozoom(opts.cwd, obj.prefix, formatWarn(obj.message), opts) })
}
const warningMsg = formatWarn(`${warningsCounter - MAX_SHOWN_WARNINGS} other warnings`)
if (!collapsedWarnings) {
collapsedWarnings = new PushStream()
// For some reason, without using setTimeout, the warning summary is printed above the rest of the warnings
// Even though the summary event happens last. Probably a bug in "most".
setTimeout(() => collapsedWarnings.next({ msg: warningMsg }), 0)
return most.from(collapsedWarnings.observable)
}
setTimeout(() => collapsedWarnings!.next({ msg: warningMsg }), 0)
return most.never()
}
}

View File

@@ -1029,3 +1029,40 @@ test('logLevel=error', t => {
},
})
})
test('warnings are collapsed', t => {
const prefix = process.cwd()
const output$ = toOutput$({
context: {
argv: ['install'],
config: { dir: prefix } as Config,
},
reportingOptions: {
logLevel: 'warn',
},
streamParser: createStreamParser(),
})
logger.warn({ message: 'Some issue 1', prefix })
logger.warn({ message: 'Some issue 2', prefix })
logger.warn({ message: 'Some issue 3', prefix })
logger.warn({ message: 'Some issue 4', prefix })
logger.warn({ message: 'Some issue 5', prefix })
logger.warn({ message: 'Some issue 6', prefix })
logger.warn({ message: 'Some issue 7', prefix })
t.plan(1)
output$.skip(6).take(1).subscribe({
complete: () => t.end(),
error: t.end,
next: output => {
t.equal(output, `${WARN} Some issue 1
${WARN} Some issue 2
${WARN} Some issue 3
${WARN} Some issue 4
${WARN} Some issue 5
${WARN} 2 other warnings`)
},
})
})