UNPKG

@netlify/build

Version:
158 lines (157 loc) 6 kB
import { createWriteStream } from 'fs'; import figures from 'figures'; import indentString from 'indent-string'; import { getHeader } from './header.js'; import { serializeArray, serializeObject } from './serialize.js'; import { THEME } from './theme.js'; export const logsAreBuffered = (logs) => { return logs !== undefined && 'stdout' in logs; }; const INDENT_SIZE = 2; /** * We need to add a zero width space character in empty lines. Otherwise the * buildbot removes those due to a bug: https://github.com/netlify/buildbot/issues/595 */ const EMPTY_LINES_REGEXP = /^\s*$/gm; const EMPTY_LINE = '\u{200B}'; /** * When the `buffer` option is true, we return logs instead of printing them * on the console. The logs are accumulated in a `logs` array variable. */ export const getBufferLogs = (config) => { const { buffer = false } = config; if (!buffer) { return; } return { stdout: [], stderr: [] }; }; // Core logging utility, used by the other methods. // This should be used instead of `console.log()` as it allows us to instrument // how any build logs is being printed. export const log = function (logs, string, config = {}) { const { indent = false, color } = config; const stringA = indent ? indentString(string, INDENT_SIZE) : string; const stringB = String(stringA).replace(EMPTY_LINES_REGEXP, EMPTY_LINE); const stringC = color === undefined ? stringB : color(stringB); if (logs && logs.outputFlusher) { logs.outputFlusher.flush(); } if (logsAreBuffered(logs)) { // `logs` is a stateful variable logs.stdout.push(stringC); return; } console.log(stringC); }; const serializeIndentedArray = function (array) { return serializeArray(array.map(serializeIndentedItem)); }; const serializeIndentedItem = function (item) { return indentString(item, INDENT_SIZE + 1).trimStart(); }; export const logError = function (logs, string, opts = {}) { log(logs, string, { color: THEME.errorLine, ...opts }); }; export const logWarning = function (logs, string, opts = {}) { log(logs, string, { color: THEME.warningLine, ...opts }); }; // Print a message that is under a header/subheader, i.e. indented export const logMessage = function (logs, string, opts = {}) { log(logs, string, { indent: true, ...opts }); }; // Print an object export const logObject = function (logs, object, opts) { logMessage(logs, serializeObject(object), opts); }; // Print an array export const logArray = function (logs, array, opts = {}) { logMessage(logs, serializeIndentedArray(array), { color: THEME.none, ...opts }); }; // Print an array of errors export const logErrorArray = function (logs, array, opts = {}) { logMessage(logs, serializeIndentedArray(array), { color: THEME.errorLine, ...opts }); }; // Print an array of warnings export const logWarningArray = function (logs, array, opts = {}) { logMessage(logs, serializeIndentedArray(array), { color: THEME.warningLine, ...opts }); }; // Print a main section header export const logHeader = function (logs, string, opts = {}) { log(logs, `\n${getHeader(string)}`, { color: THEME.header, ...opts }); }; // Print a main section header, when an error happened export const logErrorHeader = function (logs, string, opts = {}) { logHeader(logs, string, { color: THEME.errorHeader, ...opts }); }; // Print a sub-section header export const logSubHeader = function (logs, string, opts = {}) { log(logs, `\n${figures.pointer} ${string}`, { color: THEME.subHeader, ...opts }); }; // Print a sub-section header, when an error happened export const logErrorSubHeader = function (logs, string, opts = {}) { logSubHeader(logs, string, { color: THEME.errorSubHeader, ...opts }); }; // Print a sub-section header, when a warning happened export const logWarningSubHeader = function (logs, string, opts = {}) { logSubHeader(logs, string, { color: THEME.warningSubHeader, ...opts }); }; // Combines an array of elements into a single string, separated by a space, // and with basic serialization of non-string types export const reduceLogLines = function (lines) { return lines .map((input) => { if (input instanceof Error) { return `${input.message} ${input.stack}`; } if (typeof input === 'object') { try { return JSON.stringify(input); } catch { // Value could not be serialized to JSON, so we return the string // representation. return String(input); } } return String(input); }) .join(' '); }; /** * Builds a function for logging data to the system logger (i.e. hidden from * the user-facing build logs) */ export const getSystemLogger = function (logs, debug, /** A system log file descriptor, if non is provided it will be a noop logger */ systemLogFile) { // If the `debug` flag is used, we return a function that pipes system logs // to the regular logger, as the intention is for them to end up in stdout. if (debug) { return (...args) => log(logs, reduceLogLines(args)); } // If there's not a file descriptor configured for system logs and `debug` // is not set, we return a no-op function that will swallow the errors. if (!systemLogFile) { return () => { // no-op }; } // Return a function that writes to the file descriptor configured for system // logs. const fileDescriptor = createWriteStream('', { fd: systemLogFile }); fileDescriptor.on('error', () => { logError(logs, 'Could not write to system log file'); }); return (...args) => fileDescriptor.write(`${reduceLogLines(args)}\n`); }; export const addOutputFlusher = (logs, outputFlusher) => { if (logsAreBuffered(logs)) { return { ...logs, outputFlusher, }; } return { outputFlusher, }; };