netlify-cli
Version:
Netlify command line tool
132 lines • 5.58 kB
JavaScript
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