Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/build/src/core/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { trace, context } from '@opentelemetry/api'

import { handleBuildError } from '../error/handle.js'
import { reportError } from '../error/report.js'
import { getSystemLogger } from '../log/logger.js'
import type { BufferedLogs } from '../log/logger.js'
import { getLogsOutput, getSystemLogger } from '../log/logger.js'
import type { Logs } from '../log/logger.js'
import { logTimer, logBuildSuccess } from '../log/messages/core.js'
import { getGeneratedFunctions } from '../steps/return_values.js'
import { trackBuildComplete } from '../telemetry/main.js'
Expand All @@ -27,7 +27,7 @@ const tracer = trace.getTracer('core')
export async function buildSite(flags: Partial<BuildFlags> = {}): Promise<{
success: boolean
severityCode: number
logs: BufferedLogs | undefined
logs: Logs | undefined
netlifyConfig?: any
configMutations?: any
}> {
Expand Down Expand Up @@ -123,7 +123,7 @@ export async function buildSite(flags: Partial<BuildFlags> = {}): Promise<{
success,
severityCode,
netlifyConfig: netlifyConfigA,
logs,
logs: getLogsOutput(logs),
configMutations,
generatedFunctions: getGeneratedFunctions(returnValues),
}
Expand Down
2 changes: 2 additions & 0 deletions packages/build/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export type BuildCLIFlags = {
export type BuildFlags = BuildCLIFlags & {
env?: Record<string, unknown>
eventHandlers?: EventHandlers
/** Custom logger function to capture build output */
logger?: (message: string) => void
}

type EventHandlers = {
Expand Down
4 changes: 2 additions & 2 deletions packages/build/src/error/monitor/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import Bugsnag from '@bugsnag/js'
import memoizeOne from 'memoize-one'

import type { ResolvedFlags } from '../../core/normalize_flags.js'
import { BufferedLogs, log } from '../../log/logger.js'
import { Logs, log } from '../../log/logger.js'
import { ROOT_PACKAGE_JSON } from '../../utils/json.js'

const projectRoot = fileURLToPath(new URL('../../..', import.meta.url))

// Start a client to monitor errors
export const startErrorMonitor = function (config: { flags: ResolvedFlags; logs?: BufferedLogs; bugsnagKey?: string }) {
export const startErrorMonitor = function (config: { flags: ResolvedFlags; logs?: Logs; bugsnagKey?: string }) {
const {
flags: { mode },
logs,
Expand Down
1 change: 1 addition & 0 deletions packages/build/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { buildSite } from './core/main.js'
export { NetlifyPluginConstants } from './core/constants.js'

export type { BufferedLogs as Logs } from './log/logger.js'
export type { GeneratedFunction } from './steps/return_values.js'
// export the legacy types
export type { NetlifyPlugin } from './types/netlify_plugin.js'
Expand Down
58 changes: 39 additions & 19 deletions packages/build/src/log/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { THEME } from './theme.js'

export type Logs = BufferedLogs | StreamedLogs
export type BufferedLogs = { stdout: string[]; stderr: string[]; outputFlusher?: OutputFlusher }
export type StreamedLogs = { outputFlusher?: OutputFlusher }
export type StreamedLogs = { outputFlusher?: OutputFlusher; logFunction?: (message: string) => void }

export const logsAreBuffered = (logs: Logs | undefined): logs is BufferedLogs => {
return logs !== undefined && 'stdout' in logs
Expand All @@ -31,13 +31,16 @@ 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: { buffer?: boolean }): BufferedLogs | undefined => {
const { buffer = false } = config
if (!buffer) {
return
export const getBufferLogs = (config: { buffer?: boolean; logger?: (message: string) => void }): Logs | undefined => {
const { buffer = false, logger } = config

if (logger) {
return { logFunction: logger }
}

return { stdout: [], stderr: [] }
if (buffer) {
return { stdout: [], stderr: [] }
}
}

// Core logging utility, used by the other methods.
Expand All @@ -64,9 +67,26 @@ export const log = function (
return
}

if (typeof logs?.logFunction === 'function') {
logs.logFunction(stringC)

return
}

console.log(stringC)
}

// Returns a `logs` object to be returned in the public interface,
// always containing a `stderr` and `stdout` arrays, regardless of
// whether the `buffer` input property was used.
export const getLogsOutput = (logs: Logs | undefined): BufferedLogs | undefined => {
if (!logs || logsAreBuffered(logs)) {
return logs
}

return { stdout: [], stderr: [] }
}

const serializeIndentedArray = function (array) {
return serializeArray(array.map(serializeIndentedItem))
}
Expand All @@ -75,61 +95,61 @@ const serializeIndentedItem = function (item) {
return indentString(item, INDENT_SIZE + 1).trimStart()
}

export const logError = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logError = function (logs: Logs | undefined, string: string, opts = {}) {
log(logs, string, { color: THEME.errorLine, ...opts })
}

export const logWarning = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logWarning = function (logs: Logs | undefined, string: 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: BufferedLogs | undefined, string: string, opts = {}) {
export const logMessage = function (logs: Logs | undefined, string: string, opts = {}) {
log(logs, string, { indent: true, ...opts })
}

// Print an object
export const logObject = function (logs: BufferedLogs | undefined, object, opts) {
export const logObject = function (logs: Logs | undefined, object, opts) {
logMessage(logs, serializeObject(object), opts)
}

// Print an array
export const logArray = function (logs: BufferedLogs | undefined, array, opts = {}) {
export const logArray = function (logs: Logs | undefined, array, opts = {}) {
logMessage(logs, serializeIndentedArray(array), { color: THEME.none, ...opts })
}

// Print an array of errors
export const logErrorArray = function (logs: BufferedLogs | undefined, array, opts = {}) {
export const logErrorArray = function (logs: Logs | undefined, array, opts = {}) {
logMessage(logs, serializeIndentedArray(array), { color: THEME.errorLine, ...opts })
}

// Print an array of warnings
export const logWarningArray = function (logs: BufferedLogs | undefined, array, opts = {}) {
export const logWarningArray = function (logs: Logs | undefined, array, opts = {}) {
logMessage(logs, serializeIndentedArray(array), { color: THEME.warningLine, ...opts })
}

// Print a main section header
export const logHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logHeader = function (logs: Logs | undefined, string: 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: BufferedLogs | undefined, string: string, opts = {}) {
export const logErrorHeader = function (logs: Logs | undefined, string: string, opts = {}) {
logHeader(logs, string, { color: THEME.errorHeader, ...opts })
}

// Print a sub-section header
export const logSubHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logSubHeader = function (logs: Logs | undefined, string: 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: BufferedLogs | undefined, string: string, opts = {}) {
export const logErrorSubHeader = function (logs: Logs | undefined, string: string, opts = {}) {
logSubHeader(logs, string, { color: THEME.errorSubHeader, ...opts })
}

// Print a sub-section header, when a warning happened
export const logWarningSubHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logWarningSubHeader = function (logs: Logs | undefined, string: string, opts = {}) {
logSubHeader(logs, string, { color: THEME.warningSubHeader, ...opts })
}

Expand Down Expand Up @@ -162,7 +182,7 @@ export const reduceLogLines = function (lines) {
* the user-facing build logs)
*/
export const getSystemLogger = function (
logs: BufferedLogs | undefined,
logs: Logs | undefined,
debug: boolean,
/** A system log file descriptor, if non is provided it will be a noop logger */
systemLogFile?: number,
Expand Down
3 changes: 2 additions & 1 deletion packages/build/src/log/messages/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ const INTERNAL_FLAGS = [
'enhancedSecretScan',
'edgeFunctionsBootstrapURL',
'eventHandlers',
'logger',
]
const HIDDEN_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS, ...INTERNAL_FLAGS]
const HIDDEN_DEBUG_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS, 'eventHandlers']
const HIDDEN_DEBUG_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS, 'eventHandlers', 'logger']

export const logBuildDir = function (logs, buildDir) {
logSubHeader(logs, 'Current directory')
Expand Down
4 changes: 2 additions & 2 deletions packages/build/src/log/messages/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { serializeLogError } from '../../error/parse/serialize_log.js'
import { roundTimerToMillisecs } from '../../time/measure.js'
import { ROOT_PACKAGE_JSON } from '../../utils/json.js'
import { getLogHeaderFunc } from '../header_func.js'
import { log, logMessage, logWarning, logHeader, logSubHeader, logWarningArray, BufferedLogs } from '../logger.js'
import { log, logMessage, logWarning, logHeader, logSubHeader, logWarningArray, Logs } from '../logger.js'
import { OutputFlusher } from '../output_flusher.js'
import { THEME } from '../theme.js'

import { logConfigOnError } from './config.js'

export const logBuildStart = function (logs?: BufferedLogs) {
export const logBuildStart = function (logs?: Logs) {
logHeader(logs, 'Netlify Build')
logSubHeader(logs, 'Version')
logMessage(logs, `${ROOT_PACKAGE_JSON.name} ${ROOT_PACKAGE_JSON.version}`)
Expand Down
20 changes: 20 additions & 0 deletions packages/build/tests/log/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,23 @@ test('Does not truncate long redirects in logs', async (t) => {
const output = await new Fixture('./fixtures/truncate_redirects').runWithBuild()
t.false(output.includes('999'))
})

test('Accepts a custom log function', async (t) => {
const logs = []
const logger = (message) => {
logs.push(message)
}
const result = await new Fixture('./fixtures/verbose').withFlags({ logger, verbose: true }).runBuildProgrammatic()

t.deepEqual(result.logs.stdout, [])
t.deepEqual(result.logs.stderr, [])
t.true(logs.length > 0, 'logger should have been called with messages')
t.true(
logs.some((log) => log.includes('Netlify Build')),
'logs should contain build header',
)
t.true(
logs.some((log) => log.includes('onPreBuild')),
'logs should contain plugin event',
)
})
7 changes: 4 additions & 3 deletions packages/testing/src/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,10 @@ export class Fixture {

async runWithBuildAndIntrospect(): Promise<Awaited<ReturnType<typeof build>> & { output: string }> {
const buildResult = await build(this.getBuildFlags())
const output = [buildResult.logs?.stdout.join('\n'), buildResult.logs?.stderr.join('\n')]
.filter(Boolean)
.join('\n\n')
const output =
buildResult.logs && 'stdout' in buildResult.logs
? [buildResult.logs.stdout.join('\n'), buildResult.logs.stderr.join('\n')].filter(Boolean).join('\n\n')
: ''

return {
...buildResult,
Expand Down
Loading