import { Buffer } from 'buffer'; import { isStream } from 'is-stream'; import { chalk, logPadded, NETLIFYDEVERR } from '../../utils/command-helpers.js'; import renderErrorTemplate from '../render-error-template.js'; import { detectAwsSdkError } from './utils.js'; /** * @typedef InvocationError * @property {string} errorType * @property {string} errorMessage * @property {Array<string>} stackTrace */ // @ts-expect-error TS(7006) FIXME: Parameter 'headers' implicitly has an 'any' type. const addHeaders = (headers, response) => { if (!headers) { return; } Object.entries(headers).forEach(([key, value]) => { response.setHeader(key, value); }); }; export const handleSynchronousFunction = function ({ // @ts-expect-error TS(7031) FIXME: Binding element 'invocationError' implicitly has a... Remove this comment to see the full error message error: invocationError, // @ts-expect-error TS(7031) FIXME: Binding element 'functionName' implicitly has an '... Remove this comment to see the full error message functionName, // @ts-expect-error TS(7031) FIXME: Binding element 'request' implicitly has an 'any' ... Remove this comment to see the full error message request, // @ts-expect-error TS(7031) FIXME: Binding element 'response' implicitly has an 'any'... Remove this comment to see the full error message response, // @ts-expect-error TS(7031) FIXME: Binding element 'result' implicitly has an 'any' t... Remove this comment to see the full error message 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'))}`); return handleErr(invocationError, request, response); } const { error } = validateLambdaResponse(result); if (error) { logPadded(`${NETLIFYDEVERR} ${error}`); return handleErr(error, request, response); } response.statusCode = result.statusCode; try { addHeaders(result.headers, response); addHeaders(result.multiValueHeaders, response); } catch (headersError) { const normalizedError = getNormalizedError(headersError); logPadded(`${NETLIFYDEVERR} Failed to set header in function ${chalk.yellow(functionName)}: ${normalizedError.errorMessage}`); return handleErr(headersError, request, response); } if (result.body) { if (isStream(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. * * @param {InvocationError|Error} error * @returns {InvocationError} */ // @ts-expect-error TS(7006) FIXME: Parameter 'error' implicitly has an 'any' type. 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. // @ts-expect-error TS(7006) FIXME: Parameter 'line' implicitly has an 'any' type. const stackTrace = error.stackTrace.map((line) => ` at ${line}`); return { errorType: error.errorType, errorMessage: error.errorMessage, stackTrace, }; }; // @ts-expect-error TS(7006) FIXME: Parameter 'rawError' implicitly has an 'any' type. 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')}`; }; // @ts-expect-error TS(7006) FIXME: Parameter 'err' implicitly has an 'any' type. const handleErr = async (err, request, response) => { // @ts-expect-error TS(2345) FIXME: Argument of type '{ err: any; }' is not assignable... Remove this comment to see the full error message detectAwsSdkError({ err }); const acceptsHtml = request.headers && request.headers.accept && request.headers.accept.includes('text/html'); 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); } }; // @ts-expect-error TS(7006) FIXME: Parameter 'lambdaResponse' implicitly has an 'any'... Remove this comment to see the full error message 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: ${lambdaResponse.statusCode}`, }; } if (lambdaResponse.body && typeof lambdaResponse.body !== 'string' && !isStream(lambdaResponse.body)) { return { error: `Your function response must have a string or a stream body. You gave: ${lambdaResponse.body}` }; } return {}; };