mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-12 02:57:44 -04:00
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:
5
.changeset/tidy-dingos-happen.md
Normal file
5
.changeset/tidy-dingos-happen.md
Normal 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.
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user