Quiet reporter routing discarded lifecycle events before failures could render their buffered output. Consume the lifecycle stream at warn/error, group by package, stage, and working directory, discard successful blocks, and flush complete failed blocks. Keep optional failures at warn without changing exit status. Show ignored-build warnings at warn and failed supply-chain verification at error, while leaving progress, successful verdicts, and successful lifecycle output hidden. Preserve info, silent, NDJSON, and direct script-run behavior. Capture TypeScript root install hook output only for quiet default and append-only reporters. Separate the CLI reporter name from the installer's reporter callback options at both forwarding boundaries. Cover both TypeScript and Rust reporters plus resolved/frozen root and dependency installs, optional failures, real parallel builds, more than ten output lines, ignored builds, and failed supply-chain verification routing. --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Zoltan Kochan <z@kochan.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
179 lines
8.4 KiB
TypeScript
179 lines
8.4 KiB
TypeScript
import { expect, test } from '@jest/globals'
|
|
import { lifecycleLogger } from '@pnpm/core-loggers'
|
|
|
|
import { captureOutput } from './utils/captureOutput.js'
|
|
|
|
test.each([
|
|
['warn', true],
|
|
['error', false],
|
|
] as const)('prints complete failed lifecycle output at %s level with append-only=%s', async (logLevel, appendOnly) => {
|
|
const log = { depPath: 'packages/foo', stage: 'postinstall', wd: 'packages/foo' }
|
|
const script = `node fail.js ${'argument'.repeat(20)}`
|
|
const lines = Array.from({ length: 12 }, (_, index) => `line ${index + 1} ${'x'.repeat(100)}`)
|
|
const output = await captureOutput({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { appendOnly, logLevel, outputMaxWidth: 40 },
|
|
}, () => {
|
|
lifecycleLogger.debug({ ...log, optional: false, script })
|
|
for (const line of lines) {
|
|
lifecycleLogger.debug({ ...log, line: `stdout ${line}`, stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...log, line: `stderr ${line}`, stdio: 'stderr' })
|
|
}
|
|
lifecycleLogger.debug({ ...log, exitCode: 1, optional: false })
|
|
})
|
|
const rendered = appendOnly ? output : output.at(-1)?.split('\n')
|
|
expect(rendered).toEqual([
|
|
`packages/foo postinstall$ ${script}`,
|
|
...lines.flatMap((line) => [
|
|
`packages/foo postinstall: stdout ${line}`,
|
|
`packages/foo postinstall: stderr ${line}`,
|
|
]),
|
|
'packages/foo postinstall: Failed',
|
|
])
|
|
})
|
|
|
|
test.each(['warn', 'error'] as const)('discards successful lifecycle output at %s level', async (logLevel) => {
|
|
const log = { depPath: 'packages/foo', stage: 'postinstall', wd: 'packages/foo' }
|
|
const output = await captureOutput({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { appendOnly: true, logLevel },
|
|
}, () => {
|
|
lifecycleLogger.debug({ ...log, optional: false, script: 'node success.js' })
|
|
lifecycleLogger.debug({ ...log, line: 'successful stdout', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...log, line: 'successful stderr', stdio: 'stderr' })
|
|
lifecycleLogger.debug({ ...log, exitCode: 0, optional: false })
|
|
})
|
|
expect(output).toEqual([])
|
|
})
|
|
|
|
test.each([
|
|
['warn', true],
|
|
['error', false],
|
|
] as const)('respects hidden lifecycle line prefixes at %s level with append-only=%s', async (logLevel, appendOnly) => {
|
|
const log = { depPath: 'packages/foo', stage: 'postinstall', wd: 'packages/foo' }
|
|
const output = await captureOutput({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { appendOnly, logLevel, hideLifecyclePrefix: true },
|
|
}, () => {
|
|
lifecycleLogger.debug({ ...log, optional: false, script: 'node fail.js' })
|
|
lifecycleLogger.debug({ ...log, line: 'failed stdout', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...log, line: 'failed stderr', stdio: 'stderr' })
|
|
lifecycleLogger.debug({ ...log, exitCode: 1, optional: false })
|
|
})
|
|
const rendered = appendOnly ? output : output.at(-1)?.split('\n')
|
|
expect(rendered).toEqual([
|
|
'packages/foo postinstall$ node fail.js',
|
|
'failed stdout',
|
|
'failed stderr',
|
|
'packages/foo postinstall: Failed',
|
|
])
|
|
})
|
|
|
|
test.each(['warn', 'error'] as const)('reports optional failures only at warn and clears ignored output at %s level', async (logLevel) => {
|
|
const log = { depPath: 'packages/foo', stage: 'postinstall', wd: 'packages/foo' }
|
|
const output = await captureOutput({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { appendOnly: true, logLevel },
|
|
}, () => {
|
|
lifecycleLogger.debug({ ...log, optional: true, script: 'node optional.js' })
|
|
lifecycleLogger.debug({ ...log, line: 'optional stdout', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...log, line: 'optional stderr', stdio: 'stderr' })
|
|
lifecycleLogger.debug({ ...log, exitCode: 1, optional: true })
|
|
if (logLevel === 'error') {
|
|
lifecycleLogger.debug({ ...log, optional: false, script: 'node fatal.js' })
|
|
lifecycleLogger.debug({ ...log, line: 'fatal stderr', stdio: 'stderr' })
|
|
lifecycleLogger.debug({ ...log, exitCode: 1, optional: false })
|
|
}
|
|
})
|
|
expect(output).toEqual(logLevel === 'warn' ? [
|
|
'packages/foo postinstall$ node optional.js',
|
|
'packages/foo postinstall: optional stdout',
|
|
'packages/foo postinstall: optional stderr',
|
|
'packages/foo postinstall: Failed (skipped as optional)',
|
|
] : [
|
|
'packages/foo postinstall$ node fatal.js',
|
|
'packages/foo postinstall: fatal stderr',
|
|
'packages/foo postinstall: Failed',
|
|
])
|
|
})
|
|
|
|
test.each(['warn', 'error'] as const)('groups concurrent packages and stages and clears successful buffers at %s level', async (logLevel) => {
|
|
const fooBuild = { depPath: 'packages/foo', stage: 'build', wd: 'packages/foo' }
|
|
const fooPostinstall = { ...fooBuild, stage: 'postinstall' }
|
|
const barPostinstall = { depPath: 'packages/bar', stage: 'postinstall', wd: 'packages/bar' }
|
|
const output = await captureOutput({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { appendOnly: true, logLevel },
|
|
}, () => {
|
|
lifecycleLogger.debug({ ...fooBuild, optional: false, script: 'node build.js' })
|
|
lifecycleLogger.debug({ ...fooBuild, line: 'build I', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...fooPostinstall, optional: false, script: 'node success.js' })
|
|
lifecycleLogger.debug({ ...fooPostinstall, line: 'discarded output', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...barPostinstall, optional: false, script: 'node bar.js' })
|
|
lifecycleLogger.debug({ ...barPostinstall, line: 'bar stderr', stdio: 'stderr' })
|
|
lifecycleLogger.debug({ ...fooBuild, line: 'build II', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...fooPostinstall, exitCode: 0, optional: false })
|
|
lifecycleLogger.debug({ ...barPostinstall, exitCode: 1, optional: false })
|
|
lifecycleLogger.debug({ ...fooBuild, exitCode: 1, optional: false })
|
|
lifecycleLogger.debug({ ...fooPostinstall, optional: false, script: 'node retry.js' })
|
|
lifecycleLogger.debug({ ...fooPostinstall, line: 'retry stderr', stdio: 'stderr' })
|
|
lifecycleLogger.debug({ ...fooPostinstall, exitCode: 1, optional: false })
|
|
})
|
|
expect(output).toEqual([
|
|
'packages/bar postinstall$ node bar.js',
|
|
'packages/bar postinstall: bar stderr',
|
|
'packages/bar postinstall: Failed',
|
|
'packages/foo build$ node build.js',
|
|
'packages/foo build: build I',
|
|
'packages/foo build: build II',
|
|
'packages/foo build: Failed',
|
|
'packages/foo postinstall$ node retry.js',
|
|
'packages/foo postinstall: retry stderr',
|
|
'packages/foo postinstall: Failed',
|
|
])
|
|
})
|
|
|
|
test.each([false, true])('retains default info optional success and failure once with aggregate-output=%s', async (aggregateOutput) => {
|
|
const log = { depPath: 'packages/foo', stage: 'postinstall', wd: 'packages/foo' }
|
|
const output = await captureOutput({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { appendOnly: true, aggregateOutput },
|
|
}, () => {
|
|
lifecycleLogger.debug({ ...log, optional: true, script: 'node success.js' })
|
|
lifecycleLogger.debug({ ...log, line: 'successful output', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...log, exitCode: 0, optional: true })
|
|
lifecycleLogger.debug({ ...log, optional: true, script: 'node optional.js' })
|
|
lifecycleLogger.debug({ ...log, line: 'optional output', stdio: 'stderr' })
|
|
lifecycleLogger.debug({ ...log, exitCode: 1, optional: true })
|
|
})
|
|
expect(output).toEqual([
|
|
'packages/foo postinstall$ node success.js',
|
|
'packages/foo postinstall: successful output',
|
|
'packages/foo postinstall: Done',
|
|
'packages/foo postinstall$ node optional.js',
|
|
'packages/foo postinstall: optional output',
|
|
'packages/foo postinstall: Failed',
|
|
])
|
|
})
|
|
|
|
test('buffers same-named projects separately at warn level', async () => {
|
|
const failed = { depPath: 'same-name', stage: '(exec)', wd: 'packages/a' }
|
|
const succeeded = { depPath: 'same-name', stage: '(exec)', wd: 'packages/b' }
|
|
const output = await captureOutput({
|
|
context: { argv: ['exec'] },
|
|
reportingOptions: { appendOnly: true, logLevel: 'warn' },
|
|
}, () => {
|
|
lifecycleLogger.debug({ ...failed, optional: false, script: 'node fail.js' })
|
|
lifecycleLogger.debug({ ...failed, line: 'failed stdout', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...succeeded, optional: false, script: 'node ok.js' })
|
|
lifecycleLogger.debug({ ...succeeded, line: 'successful stdout', stdio: 'stdout' })
|
|
lifecycleLogger.debug({ ...succeeded, exitCode: 0, optional: false })
|
|
lifecycleLogger.debug({ ...failed, exitCode: 1, optional: false })
|
|
})
|
|
expect(output).toEqual([
|
|
'packages/a (exec)$ node fail.js',
|
|
'packages/a (exec): failed stdout',
|
|
'packages/a (exec): Failed',
|
|
])
|
|
})
|