UNPKG

netlify-cli

Version:

Netlify command line tool

132 lines 5.58 kB
import { Buffer } from 'buffer'; import { inspect } from 'util'; import { isReadableStream as baseIsReadableStream } from 'is-stream'; import { chalk, logPadded, NETLIFYDEVERR } from '../../utils/command-helpers.js'; import renderErrorTemplate from '../render-error-template.js'; import { warnIfAwsSdkError } from './utils.js'; // Annoyingly, `isReadableStream` refines to the `Readable` interface rather than the // `ReadableStream` class. Refining to the class makes further refinements work as expected. const isReadableStream = (value) => baseIsReadableStream(value); const addHeaders = (headers, response) => { if (!headers) { return; } Object.entries(headers).forEach(([key, value]) => { response.setHeader(key, value); }); }; export const handleSynchronousFunction = async function ({ error: invocationError, functionName, request, response, result, }) { if (invocationError) { const error = getNormalizedError(invocationError); logPadded(`${NETLIFYDEVERR} Function ${chalk.yellow(functionName)} has returned an error: ${error.errorMessage}\n${chalk.dim(error.stackTrace.join('\n'))}`); await handleErr(invocationError, request, response); return; } const { error } = validateLambdaResponse(result); if (error) { logPadded(`${NETLIFYDEVERR} ${error}`); await handleErr(error, request, response); return; } // This shouldn't happen (see `InvokeFunctionResult`), but due to type lossiness TS doesn't know this here. if (result == null) { logPadded(`${NETLIFYDEVERR} Unexpected empty function response`); await handleErr('Unexpected empty function response', request, response); return; } if (result.statusCode != null) { response.statusCode = result.statusCode; } try { addHeaders(result.headers, response); addHeaders(result.multiValueHeaders, response); } catch (headersError) { const wrappedHeadersError = headersError instanceof Error ? headersError : new Error(headersError?.toString()); const normalizedError = getNormalizedError(wrappedHeadersError); logPadded(`${NETLIFYDEVERR} Failed to set header in function ${chalk.yellow(functionName)}: ${normalizedError.errorMessage}`); await handleErr(wrappedHeadersError, request, response); return; } if (result.body) { if (isReadableStream(result.body)) { result.body.pipe(response); return; } response.write(result.isBase64Encoded ? Buffer.from(result.body, 'base64') : result.body); } response.end(); }; /** * Accepts an error generated by `lambda-local` or an instance of `Error` and * returns a normalized error that we can treat in the same way. */ const getNormalizedError = (error) => { if (error instanceof Error) { const normalizedError = { errorMessage: error.message, errorType: error.name, stackTrace: error.stack ? error.stack.split('\n') : [], }; if ('code' in error && error.code === 'ERR_REQUIRE_ESM') { return { ...normalizedError, errorMessage: 'a CommonJS file cannot import ES modules. Consider switching your function to ES modules. For more information, refer to https://ntl.fyi/functions-runtime.', }; } return normalizedError; } // Formatting stack trace lines in the same way that Node.js formats native errors. const stackTrace = error.stackTrace.map((line) => ` at ${line}`); return { errorType: error.errorType, errorMessage: error.errorMessage, stackTrace, }; }; const formatLambdaLocalError = (rawError, acceptsHTML) => { const error = getNormalizedError(rawError); if (acceptsHTML) { return JSON.stringify({ ...error, stackTrace: undefined, trace: error.stackTrace, }); } return `${error.errorType}: ${error.errorMessage}\n ${error.stackTrace.join('\n')}`; }; const handleErr = async (err, request, response) => { warnIfAwsSdkError({ error: err }); const acceptsHtml = request.headers.accept?.includes('text/html') ?? false; const errorString = typeof err === 'string' ? err : formatLambdaLocalError(err, acceptsHtml); response.statusCode = 500; if (acceptsHtml) { response.setHeader('Content-Type', 'text/html'); response.end(await renderErrorTemplate(errorString, '../../src/lib/templates/function-error.html', 'function')); } else { response.end(errorString); } }; const validateLambdaResponse = (lambdaResponse) => { if (lambdaResponse === undefined) { return { error: 'lambda response was undefined. check your function code again' }; } if (lambdaResponse === null) { return { error: 'no lambda response. check your function code again. make sure to return a promise or use the callback.', }; } if (!Number(lambdaResponse.statusCode)) { return { error: `Your function response must have a numerical statusCode. You gave: ${inspect(lambdaResponse.statusCode)}`, }; } if (lambdaResponse.body && typeof lambdaResponse.body !== 'string' && !isReadableStream(lambdaResponse.body)) { return { error: `Your function response must have a string or a stream body. You gave: ${inspect(lambdaResponse.body)}`, }; } return {}; }; //# sourceMappingURL=synchronous.js.map