nodejs/deps/npm/test/fixtures/mock-logs.js

109 lines
3.4 KiB
JavaScript
Raw Permalink Normal View History

const { log: { LEVELS } } = require('proc-log')
const { stripVTControlCharacters: stripAnsi } = require('node:util')
const logPrefix = new RegExp(`^npm (${LEVELS.join('|')})\\s`)
const isLog = (str) => logPrefix.test(stripAnsi(str))
// We only strip trailing newlines since some output will
// have significant tabs and spaces
const trimTrailingNewline = (str) => str.replace(/\n$/, '')
const joinAndTrimTrailingNewlines = (arr) =>
trimTrailingNewline(arr.map(trimTrailingNewline).join('\n'))
const logsByTitle = (logs) => ({
byTitle: {
value: (title) => {
return logs
.filter((l) => stripAnsi(l.message).startsWith(`${title} `))
.map((l) => l.message)
},
},
})
module.exports = () => {
const outputs = []
const outputErrors = []
const fullOutput = []
const levelLogs = []
const logs = Object.defineProperties([], {
...logsByTitle(levelLogs),
...LEVELS.reduce((acc, level) => {
acc[level] = {
get () {
const byLevel = levelLogs.filter((l) => l.level === level)
return Object.defineProperties(byLevel.map((l) => l.message), logsByTitle(byLevel))
},
}
return acc
}, {}),
})
const streams = {
stderr: {
cursorTo: () => {},
clearLine: () => {},
write: (str) => {
str = trimTrailingNewline(str)
// Use the beginning of each line to determine if its a log
// or an output error since we write both of those to stderr.
// This couples logging format to this test but we only need
// to do it in a single place so hopefully its easy to change
// in the future if/when we refactor what logs look like.
if (!isLog(str)) {
outputErrors.push(str)
fullOutput.push(str)
return
}
// Split on spaces for the heading and level/label. We know that
// none of those have spaces but could be colorized so there's no
// other good way to get each of those including control chars
const [rawHeading, rawLevel] = str.split(' ')
const rawPrefix = `${rawHeading} ${rawLevel} `
// If message is colorized we can just replaceAll with the string since
// it will be unique due to control chars. Otherwise we create a regex
// that will only match the beginning of each line.
const prefix = stripAnsi(str) !== str ? rawPrefix : new RegExp(`^${rawPrefix}`, 'gm')
// The level needs color stripped always because we use it to filter logs
const level = stripAnsi(rawLevel)
logs.push(str.replaceAll(prefix, `${level} `))
fullOutput.push(str.replaceAll(prefix, `${level} `))
levelLogs.push({ level, message: str.replaceAll(prefix, '') })
},
},
stdout: {
write: (str) => {
outputs.push(trimTrailingNewline(str))
fullOutput.push(trimTrailingNewline(str))
},
},
}
return {
streams,
logs: {
outputs,
joinedOutput: () => joinAndTrimTrailingNewlines(outputs),
clearOutput: () => {
outputs.length = 0
outputErrors.length = 0
fullOutput.length = 0
},
outputErrors,
joinedOutputError: () => joinAndTrimTrailingNewlines(outputs),
fullOutput,
joinedFullOutput: () => joinAndTrimTrailingNewlines(fullOutput),
logs,
clearLogs: () => {
levelLogs.length = 0
logs.length = 0
},
},
}
}