astro-sst
Version:
Adapter that allows Astro to deploy your site to AWS utilizing SST.
130 lines (129 loc) • 4.67 kB
JavaScript
import fs from "fs/promises";
import { NodeApp, applyPolyfills } from "astro/app/node";
import { convertFrom, convertTo } from "./lib/event-mapper.js";
import { debug } from "./lib/logger.js";
applyPolyfills();
function createRequest(internalEvent) {
const requestUrl = internalEvent.url;
const requestProps = {
method: internalEvent.method,
headers: internalEvent.headers,
body: ["GET", "HEAD"].includes(internalEvent.method)
? undefined
: internalEvent.body,
};
return new Request(requestUrl, requestProps);
}
export function createExports(manifest, { responseMode }) {
debug("handlerInit", responseMode);
const isStreaming = responseMode === "stream";
const app = new NodeApp(manifest);
function build404Url(url) {
const url404 = new URL(url);
url404.pathname = "/404";
url404.search = "";
url404.hash = "";
return url404.toString();
}
async function streamHandler(event, responseStream) {
debug("event", event);
const internalEvent = convertFrom(event);
let request = createRequest(internalEvent);
let routeData = app.match(request);
if (!routeData) {
// handle prerendered 404
if (await existsAsync("404.html")) {
return streamError(404, await fs.readFile("404.html", "utf-8"), responseStream);
}
// handle server-side 404
request = createRequest({
...internalEvent,
url: build404Url(internalEvent.url),
});
routeData = app.match(request);
if (!routeData) {
return streamError(404, "Not found", responseStream);
}
}
const response = await app.render(request, {
routeData,
clientAddress: internalEvent.headers["x-forwarded-for"] || internalEvent.remoteAddress,
});
// Stream response back to Cloudfront
const convertedResponse = await convertTo({
type: internalEvent.type,
response,
responseStream,
cookies: Array.from(app.setCookieHeaders(response)),
});
debug("response", convertedResponse);
}
async function bufferHandler(event) {
debug("event", event);
const internalEvent = convertFrom(event);
let request = createRequest(internalEvent);
let routeData = app.match(request);
if (!routeData) {
// handle prerendered 404
if (await existsAsync("404.html")) {
return convertTo({
type: internalEvent.type,
response: new Response(await fs.readFile("404.html", "utf-8"), {
status: 404,
headers: {
"Content-Type": "text/html",
},
}),
});
}
// handle server-side 404
request = createRequest({
...internalEvent,
url: build404Url(internalEvent.url),
});
routeData = app.match(request);
if (!routeData) {
return convertTo({
type: internalEvent.type,
response: new Response("Not found", { status: 404 }),
});
}
}
// Process request
const response = await app.render(request, {
routeData,
clientAddress: internalEvent.headers["x-forwarded-for"] || internalEvent.remoteAddress,
});
// Buffer response back to Cloudfront
const convertedResponse = await convertTo({
type: internalEvent.type,
response,
cookies: Array.from(app.setCookieHeaders(response)),
});
debug("response", convertedResponse);
return convertedResponse;
}
return {
// https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html
handler: isStreaming
? awslambda.streamifyResponse(streamHandler)
: bufferHandler,
};
}
export function streamError(statusCode, error, responseStream) {
console.error(error);
responseStream = awslambda.HttpResponseStream.from(responseStream, {
statusCode,
headers: {
"Content-Type": "text/html",
},
});
responseStream.write(error.toString());
responseStream.end();
}
async function existsAsync(input) {
return fs
.access(input)
.then(() => true)
.catch(() => false);
}