2024-04-30 23:53:22 -07:00
|
|
|
const { log: { LEVELS } } = require('proc-log')
|
2024-05-30 04:21:05 -07:00
|
|
|
const { stripVTControlCharacters: stripAnsi } = require('node:util')
|
2021-12-02 22:04:46 +00:00
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
const logPrefix = new RegExp(`^npm (${LEVELS.join('|')})\\s`)
|
|
|
|
const isLog = (str) => logPrefix.test(stripAnsi(str))
|
2021-12-02 22:04:46 +00:00
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
// We only strip trailing newlines since some output will
|
|
|
|
// have significant tabs and spaces
|
|
|
|
const trimTrailingNewline = (str) => str.replace(/\n$/, '')
|
2022-12-06 22:18:33 -05:00
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
const joinAndTrimTrailingNewlines = (arr) =>
|
|
|
|
trimTrailingNewline(arr.map(trimTrailingNewline).join('\n'))
|
2021-12-02 22:04:46 +00:00
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
const logsByTitle = (logs) => ({
|
|
|
|
byTitle: {
|
|
|
|
value: (title) => {
|
|
|
|
return logs
|
|
|
|
.filter((l) => stripAnsi(l.message).startsWith(`${title} `))
|
|
|
|
.map((l) => l.message)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2021-12-02 22:04:46 +00:00
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
module.exports = () => {
|
|
|
|
const outputs = []
|
|
|
|
const outputErrors = []
|
2024-05-30 04:21:05 -07:00
|
|
|
const fullOutput = []
|
2024-04-30 23:53:22 -07:00
|
|
|
|
|
|
|
const levelLogs = []
|
|
|
|
const logs = Object.defineProperties([], {
|
|
|
|
...logsByTitle(levelLogs),
|
|
|
|
...LEVELS.reduce((acc, level) => {
|
2022-12-06 22:18:33 -05:00
|
|
|
acc[level] = {
|
|
|
|
get () {
|
2024-04-30 23:53:22 -07:00
|
|
|
const byLevel = levelLogs.filter((l) => l.level === level)
|
|
|
|
return Object.defineProperties(byLevel.map((l) => l.message), logsByTitle(byLevel))
|
2022-12-06 22:18:33 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
return acc
|
2024-04-30 23:53:22 -07:00
|
|
|
}, {}),
|
|
|
|
})
|
2022-12-06 22:18:33 -05:00
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
const streams = {
|
|
|
|
stderr: {
|
|
|
|
cursorTo: () => {},
|
|
|
|
clearLine: () => {},
|
|
|
|
write: (str) => {
|
|
|
|
str = trimTrailingNewline(str)
|
2022-12-06 22:18:33 -05:00
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
// 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)
|
2024-05-30 04:21:05 -07:00
|
|
|
fullOutput.push(str)
|
2024-04-30 23:53:22 -07:00
|
|
|
return
|
2021-12-02 22:04:46 +00:00
|
|
|
}
|
2024-04-30 23:53:22 -07:00
|
|
|
|
|
|
|
// 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} `))
|
2024-05-30 04:21:05 -07:00
|
|
|
fullOutput.push(str.replaceAll(prefix, `${level} `))
|
2024-04-30 23:53:22 -07:00
|
|
|
levelLogs.push({ level, message: str.replaceAll(prefix, '') })
|
|
|
|
},
|
|
|
|
},
|
|
|
|
stdout: {
|
|
|
|
write: (str) => {
|
|
|
|
outputs.push(trimTrailingNewline(str))
|
2024-05-30 04:21:05 -07:00
|
|
|
fullOutput.push(trimTrailingNewline(str))
|
2022-03-17 20:22:31 +00:00
|
|
|
},
|
2024-04-30 23:53:22 -07:00
|
|
|
},
|
2021-12-02 22:04:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-30 23:53:22 -07:00
|
|
|
return {
|
|
|
|
streams,
|
|
|
|
logs: {
|
|
|
|
outputs,
|
|
|
|
joinedOutput: () => joinAndTrimTrailingNewlines(outputs),
|
|
|
|
clearOutput: () => {
|
|
|
|
outputs.length = 0
|
|
|
|
outputErrors.length = 0
|
2024-05-30 04:21:05 -07:00
|
|
|
fullOutput.length = 0
|
2024-04-30 23:53:22 -07:00
|
|
|
},
|
|
|
|
outputErrors,
|
|
|
|
joinedOutputError: () => joinAndTrimTrailingNewlines(outputs),
|
2024-05-30 04:21:05 -07:00
|
|
|
fullOutput,
|
|
|
|
joinedFullOutput: () => joinAndTrimTrailingNewlines(fullOutput),
|
2024-04-30 23:53:22 -07:00
|
|
|
logs,
|
|
|
|
clearLogs: () => {
|
|
|
|
levelLogs.length = 0
|
|
|
|
logs.length = 0
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2021-12-02 22:04:46 +00:00
|
|
|
}
|