From a58f8e671054e0b9138aea6abd36e49ea3514915 Mon Sep 17 00:00:00 2001 From: Dillon Kearns Date: Tue, 16 May 2023 11:13:05 -0700 Subject: [PATCH] Use built-in netlify adapter in examples. --- examples/blog-engine/adapter.mjs | 310 --------------------- examples/blog-engine/elm-pages.config.mjs | 2 +- examples/hackernews/adapter.mjs | 312 --------------------- examples/hackernews/elm-pages.config.mjs | 3 +- examples/pokedex/adapter.mjs | 210 -------------- examples/pokedex/elm-pages.config.mjs | 3 +- examples/smoothies/adapter.mjs | 316 ---------------------- examples/smoothies/elm-pages.config.mjs | 3 +- examples/todos/adapter.mjs | 309 --------------------- examples/todos/elm-pages.config.mjs | 2 +- examples/trails/adapter.mjs | 312 --------------------- 11 files changed, 5 insertions(+), 1777 deletions(-) delete mode 100644 examples/blog-engine/adapter.mjs delete mode 100644 examples/hackernews/adapter.mjs delete mode 100644 examples/pokedex/adapter.mjs delete mode 100644 examples/smoothies/adapter.mjs delete mode 100644 examples/todos/adapter.mjs delete mode 100644 examples/trails/adapter.mjs diff --git a/examples/blog-engine/adapter.mjs b/examples/blog-engine/adapter.mjs deleted file mode 100644 index 3ffd88f3..00000000 --- a/examples/blog-engine/adapter.mjs +++ /dev/null @@ -1,310 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; - -export default async function run({ - renderFunctionFilePath, - routePatterns, - apiRoutePatterns, - portsFilePath, - htmlTemplate, -}) { - console.log("Running adapter script", portsFilePath); - ensureDirSync("functions/render"); - ensureDirSync("functions/server-render"); - - fs.copyFileSync( - renderFunctionFilePath, - "./functions/render/elm-pages-cli.cjs" - ); - fs.copyFileSync( - renderFunctionFilePath, - "./functions/server-render/elm-pages-cli.cjs" - ); - - fs.writeFileSync( - "./functions/render/index.mjs", - rendererCode(true, htmlTemplate) - ); - fs.writeFileSync( - "./functions/server-render/index.mjs", - rendererCode(false, htmlTemplate) - ); - // TODO rename functions/render to functions/fallback-render - // TODO prepend instead of writing file - - const apiServerRoutes = apiRoutePatterns.filter(isServerSide); - - ensureValidRoutePatternsForNetlify(apiServerRoutes); - - // TODO filter apiRoutePatterns on is server side - // TODO need information on whether api route is odb or serverless - const apiRouteRedirects = apiServerRoutes - .map((apiRoute) => { - if (apiRoute.kind === "prerender-with-fallback") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/builders/render 200`; - } else if (apiRoute.kind === "serverless") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/functions/server-render 200`; - } else { - throw "Unhandled 2"; - } - }) - .join("\n"); - - const redirectsFile = - routePatterns - .filter(isServerSide) - .map((route) => { - if (route.kind === "prerender-with-fallback") { - return `${route.pathPattern} /.netlify/builders/render 200 -${route.pathPattern}/content.dat /.netlify/builders/render 200`; - } else { - return `${route.pathPattern} /.netlify/functions/server-render 200 -${route.pathPattern}/content.dat /.netlify/functions/server-render 200`; - } - }) - .join("\n") + - "\n" + - apiRouteRedirects + - "\n"; - - fs.writeFileSync("dist/_redirects", redirectsFile); -} - -function ensureValidRoutePatternsForNetlify(apiRoutePatterns) { - const invalidNetlifyRoutes = apiRoutePatterns.filter((apiRoute) => - apiRoute.pathPattern.some(({ kind }) => kind === "hybrid") - ); - if (invalidNetlifyRoutes.length > 0) { - throw ( - "Invalid Netlify routes!\n" + - invalidNetlifyRoutes - .map((value) => JSON.stringify(value, null, 2)) - .join(", ") - ); - } -} - -function isServerSide(route) { - return ( - route.kind === "prerender-with-fallback" || route.kind === "serverless" - ); -} - -/** - * @param {boolean} isOnDemand - * @param {string} htmlTemplate - */ -function rendererCode(isOnDemand, htmlTemplate) { - console.log({ portsFilePath }); - return `import * as path from "path"; -import * as busboy from "busboy"; -import { fileURLToPath } from "url"; -import * as renderer from "../../../../generator/src/render.js"; -import * as preRenderHtml from "../../../../generator/src/pre-render-html.js"; -import * as customBackendTask from "${path.resolve(portsFilePath)}"; -const htmlTemplate = ${JSON.stringify(htmlTemplate)}; - -${ - isOnDemand - ? `import { builder } from "@netlify/functions"; - -export const handler = builder(render);` - : ` - -export const handler = render;` -} - - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} event - * @param {any} context - */ -async function render(event, context) { - const requestTime = new Date(); - console.log(JSON.stringify(event)); - - try { - const basePath = "/"; - const mode = "build"; - const addWatcher = () => {}; - - const renderResult = await renderer.render( - customBackendTask, - basePath, - (await import("./elm-pages-cli.cjs")).default, - mode, - event.path, - await reqToJson(event, requestTime), - addWatcher, - false - ); - console.log("@@@renderResult", JSON.stringify(renderResult, null, 2)); - - const statusCode = renderResult.is404 ? 404 : renderResult.statusCode; - - if (renderResult.kind === "bytes") { - return { - body: Buffer.from(renderResult.contentDatPayload.buffer).toString("base64"), - isBase64Encoded: true, - headers: { - "Content-Type": "application/octet-stream", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } else if (renderResult.kind === "api-response") { - const serverResponse = renderResult.body; - return { - body: serverResponse.body, - multiValueHeaders: serverResponse.headers, - statusCode: serverResponse.statusCode, - isBase64Encoded: serverResponse.isBase64Encoded, - }; - } else { - console.log('@rendering', preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString)) - return { - body: preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString), - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } - } catch (error) { - console.log('ERROR') - console.error(error); - console.error(JSON.stringify(error, null, 2)); - return { - body: \`

Error

\${error.toString()}
\`, - statusCode: 500, - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - }, - }; - } -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {Promise<{ method: string; hostname: string; query: Record; headers: Record; host: string; pathname: string; port: number | null; protocol: string; rawUrl: string; }>} - */ -function reqToJson(req, requestTime) { - return new Promise((resolve, reject) => { - if ( - req.httpMethod && req.httpMethod.toUpperCase() === "POST" && - req.headers["content-type"] && - req.headers["content-type"].includes("multipart/form-data") && - req.body - ) { - try { - console.log('@@@1'); - const bb = busboy({ - headers: req.headers, - }); - let fields = {}; - - bb.on("file", (fieldname, file, info) => { - console.log('@@@2'); - const { filename, encoding, mimeType } = info; - - file.on("data", (data) => { - fields[fieldname] = { - filename, - mimeType, - body: data.toString(), - }; - }); - }); - - bb.on("field", (fieldName, value) => { - console.log("@@@field", fieldName, value); - fields[fieldName] = value; - }); - - // TODO skip parsing JSON and form data body if busboy doesn't run - bb.on("close", () => { - console.log('@@@3'); - console.log("@@@close", fields); - resolve(toJsonHelper(req, requestTime, fields)); - }); - console.log('@@@4'); - - if (req.isBase64Encoded) { - bb.write(Buffer.from(req.body, 'base64').toString('utf8')); - } else { - bb.write(req.body); - } - } catch (error) { - console.error('@@@5', error); - resolve(toJsonHelper(req, requestTime, null)); - } - } else { - console.log('@@@6'); - resolve(toJsonHelper(req, requestTime, null)); - } - }); -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {{method: string; rawUrl: string; body: string?; headers: Record; requestTime: number; multiPartFormData: unknown }} - */ -function toJsonHelper(req, requestTime, multiPartFormData) { - return { - method: req.httpMethod, - headers: req.headers, - rawUrl: req.rawUrl, - body: req.body, - requestTime: Math.round(requestTime.getTime()), - multiPartFormData: multiPartFormData, - }; -} -`; -} - -/** - * @param {fs.PathLike} dirpath - */ -function ensureDirSync(dirpath) { - try { - fs.mkdirSync(dirpath, { recursive: true }); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } -} - -/** @typedef {{kind: 'dynamic'} | {kind: 'literal', value: string}} ApiSegment */ - -/** - * @param {ApiSegment[]} pathPattern - */ -function apiPatternToRedirectPattern(pathPattern) { - return ( - "/" + - pathPattern - .map((segment, index) => { - switch (segment.kind) { - case "literal": { - return segment.value; - } - case "dynamic": { - return `:dynamic${index}`; - } - default: { - throw "Unhandled segment: " + JSON.stringify(segment); - } - } - }) - .join("/") - ); -} diff --git a/examples/blog-engine/elm-pages.config.mjs b/examples/blog-engine/elm-pages.config.mjs index 3e3a34b6..d4d55336 100644 --- a/examples/blog-engine/elm-pages.config.mjs +++ b/examples/blog-engine/elm-pages.config.mjs @@ -1,5 +1,5 @@ import { defineConfig } from "vite"; -import adapter from "./adapter.mjs"; +import adapter from "../../adapter/netlify.js"; export default { vite: defineConfig({}), diff --git a/examples/hackernews/adapter.mjs b/examples/hackernews/adapter.mjs deleted file mode 100644 index 7be8f940..00000000 --- a/examples/hackernews/adapter.mjs +++ /dev/null @@ -1,312 +0,0 @@ -import fs from "fs"; - -export default async function run({ - renderFunctionFilePath, - routePatterns, - apiRoutePatterns, - portsFilePath, - htmlTemplate, -}) { - console.log("Running adapter script"); - ensureDirSync("functions/render"); - ensureDirSync("functions/server-render"); - - fs.copyFileSync( - renderFunctionFilePath, - "./functions/render/elm-pages-cli.js" - ); - fs.copyFileSync( - renderFunctionFilePath, - "./functions/server-render/elm-pages-cli.js" - ); - fs.copyFileSync(portsFilePath, "./functions/render/custom-backend-task.mjs"); - fs.copyFileSync( - portsFilePath, - "./functions/server-render/custom-backend-task.mjs" - ); - - fs.writeFileSync( - "./functions/render/index.js", - rendererCode(true, htmlTemplate) - ); - fs.writeFileSync( - "./functions/server-render/index.js", - rendererCode(false, htmlTemplate) - ); - // TODO rename functions/render to functions/fallback-render - // TODO prepend instead of writing file - - const apiServerRoutes = apiRoutePatterns.filter(isServerSide); - - ensureValidRoutePatternsForNetlify(apiServerRoutes); - - // TODO filter apiRoutePatterns on is server side - // TODO need information on whether api route is odb or serverless - const apiRouteRedirects = apiServerRoutes - .map((apiRoute) => { - if (apiRoute.kind === "prerender-with-fallback") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/builders/render 200`; - } else if (apiRoute.kind === "serverless") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/functions/server-render 200`; - } else { - throw "Unhandled 2"; - } - }) - .join("\n"); - - const redirectsFile = - routePatterns - .filter(isServerSide) - .map((route) => { - if (route.kind === "prerender-with-fallback") { - return `${route.pathPattern} /.netlify/builders/render 200 -${route.pathPattern}/content.dat /.netlify/builders/render 200`; - } else { - return `${route.pathPattern} /.netlify/functions/server-render 200 -${route.pathPattern}/content.dat /.netlify/functions/server-render 200`; - } - }) - .join("\n") + - "\n" + - apiRouteRedirects + - "\n"; - - fs.writeFileSync("dist/_redirects", redirectsFile); -} - -function ensureValidRoutePatternsForNetlify(apiRoutePatterns) { - const invalidNetlifyRoutes = apiRoutePatterns.filter((apiRoute) => - apiRoute.pathPattern.some(({ kind }) => kind === "hybrid") - ); - if (invalidNetlifyRoutes.length > 0) { - throw ( - "Invalid Netlify routes!\n" + - invalidNetlifyRoutes - .map((value) => JSON.stringify(value, null, 2)) - .join(", ") - ); - } -} - -function isServerSide(route) { - return ( - route.kind === "prerender-with-fallback" || route.kind === "serverless" - ); -} - -/** - * @param {boolean} isOnDemand - * @param {string} htmlTemplate - */ -function rendererCode(isOnDemand, htmlTemplate) { - return `const path = require("path"); -const busboy = require("busboy"); -const htmlTemplate = ${JSON.stringify(htmlTemplate)}; - -${ - isOnDemand - ? `const { builder } = require("@netlify/functions"); - -exports.handler = builder(render);` - : ` - -exports.handler = render;` -} - - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} event - * @param {any} context - */ -async function render(event, context) { - const requestTime = new Date(); - console.log(JSON.stringify(event)); - global.staticHttpCache = {}; - - const compiledElmPath = path.join(__dirname, "elm-pages-cli.js"); - const compiledPortsFile = path.join(__dirname, "custom-backend-task.mjs"); - const renderer = require("../../../../generator/src/render"); - const preRenderHtml = require("../../../../generator/src/pre-render-html"); - try { - const basePath = "/"; - const mode = "build"; - const addWatcher = () => {}; - - const renderResult = await renderer.render( - compiledPortsFile, - basePath, - require(compiledElmPath), - mode, - event.path, - await reqToJson(event, requestTime), - addWatcher, - false - ); - console.log("@@@renderResult", JSON.stringify(renderResult, null, 2)); - - const statusCode = renderResult.is404 ? 404 : renderResult.statusCode; - - if (renderResult.kind === "bytes") { - return { - body: Buffer.from(renderResult.contentDatPayload.buffer).toString("base64"), - isBase64Encoded: true, - headers: { - "Content-Type": "application/octet-stream", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } else if (renderResult.kind === "api-response") { - const serverResponse = renderResult.body; - return { - body: serverResponse.body, - multiValueHeaders: serverResponse.headers, - statusCode: serverResponse.statusCode, - isBase64Encoded: serverResponse.isBase64Encoded, - }; - } else { - console.log('@rendering', preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString)) - return { - body: preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString), - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } - } catch (error) { - console.error(error); - return { - body: \`

Error

\${error.toString()}
\`, - statusCode: 500, - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - }, - }; - } -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {Promise<{ method: string; hostname: string; query: Record; headers: Record; host: string; pathname: string; port: number | null; protocol: string; rawUrl: string; }>} - */ -function reqToJson(req, requestTime) { - return new Promise((resolve, reject) => { - if ( - req.httpMethod && req.httpMethod.toUpperCase() === "POST" && - req.headers["content-type"] && - req.headers["content-type"].includes("multipart/form-data") && - req.body - ) { - try { - console.log('@@@1'); - const bb = busboy({ - headers: req.headers, - }); - let fields = {}; - - bb.on("file", (fieldname, file, info) => { - console.log('@@@2'); - const { filename, encoding, mimeType } = info; - - file.on("data", (data) => { - fields[fieldname] = { - filename, - mimeType, - body: data.toString(), - }; - }); - }); - - bb.on("field", (fieldName, value) => { - console.log("@@@field", fieldName, value); - fields[fieldName] = value; - }); - - // TODO skip parsing JSON and form data body if busboy doesn't run - bb.on("close", () => { - console.log('@@@3'); - console.log("@@@close", fields); - resolve(toJsonHelper(req, requestTime, fields)); - }); - console.log('@@@4'); - - if (req.isBase64Encoded) { - bb.write(Buffer.from(req.body, 'base64').toString('utf8')); - } else { - bb.write(req.body); - } - } catch (error) { - console.error('@@@5', error); - resolve(toJsonHelper(req, requestTime, null)); - } - } else { - console.log('@@@6'); - resolve(toJsonHelper(req, requestTime, null)); - } - }); -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {{method: string; rawUrl: string; body: string?; headers: Record; requestTime: number; multiPartFormData: unknown }} - */ -function toJsonHelper(req, requestTime, multiPartFormData) { - return { - method: req.httpMethod, - headers: req.headers, - rawUrl: req.rawUrl, - body: req.body, - requestTime: Math.round(requestTime.getTime()), - multiPartFormData: multiPartFormData, - }; -} -`; -} - -/** - * @param {fs.PathLike} dirpath - */ -function ensureDirSync(dirpath) { - try { - fs.mkdirSync(dirpath, { recursive: true }); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } -} - -/** @typedef {{kind: 'dynamic'} | {kind: 'literal', value: string}} ApiSegment */ - -/** - * @param {ApiSegment[]} pathPattern - */ -function apiPatternToRedirectPattern(pathPattern) { - return ( - "/" + - pathPattern - .map((segment, index) => { - switch (segment.kind) { - case "literal": { - return segment.value; - } - case "dynamic": { - return `:dynamic${index}`; - } - default: { - throw "Unhandled segment: " + JSON.stringify(segment); - } - } - }) - .join("/") - ); -} diff --git a/examples/hackernews/elm-pages.config.mjs b/examples/hackernews/elm-pages.config.mjs index dfb90f16..d4d55336 100644 --- a/examples/hackernews/elm-pages.config.mjs +++ b/examples/hackernews/elm-pages.config.mjs @@ -1,6 +1,5 @@ import { defineConfig } from "vite"; - -import adapter from "./adapter.mjs"; +import adapter from "../../adapter/netlify.js"; export default { vite: defineConfig({}), diff --git a/examples/pokedex/adapter.mjs b/examples/pokedex/adapter.mjs deleted file mode 100644 index 3ab0f895..00000000 --- a/examples/pokedex/adapter.mjs +++ /dev/null @@ -1,210 +0,0 @@ -import * as fs from "fs"; - -export default async function run({ - renderFunctionFilePath, - routePatterns, - apiRoutePatterns, -}) { - console.log("Running adapter script"); - ensureDirSync("functions/render"); - ensureDirSync("functions/server-render"); - - fs.copyFileSync( - renderFunctionFilePath, - "./functions/render/elm-pages-cli.mjs" - ); - fs.copyFileSync( - renderFunctionFilePath, - "./functions/server-render/elm-pages-cli.mjs" - ); - - fs.writeFileSync("./functions/render/index.mjs", rendererCode(true)); - fs.writeFileSync("./functions/server-render/index.mjs", rendererCode(false)); - // TODO rename functions/render to functions/fallback-render - // TODO prepend instead of writing file - - const apiServerRoutes = apiRoutePatterns.filter(isServerSide); - - ensureValidRoutePatternsForNetlify(apiServerRoutes); - - const apiRouteRedirects = apiServerRoutes - .map((apiRoute) => { - if (apiRoute.kind === "prerender-with-fallback") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/builders/render 200`; - } else if (apiRoute.kind === "serverless") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/functions/server-render 200`; - } else { - throw "Unhandled 2"; - } - }) - .join("\n"); - - const redirectsFile = - routePatterns - .filter(isServerSide) - .map((route) => { - if (route.kind === "prerender-with-fallback") { - return `${route.pathPattern} /.netlify/builders/render 200 -${route.pathPattern}/content.dat /.netlify/builders/render 200`; - } else { - return `${route.pathPattern} /.netlify/functions/server-render 200 -${route.pathPattern}/content.dat /.netlify/functions/server-render 200`; - } - }) - .join("\n") + - "\n" + - apiRouteRedirects + - "\n"; - - fs.writeFileSync("dist/_redirects", redirectsFile); -} - -function ensureValidRoutePatternsForNetlify(apiRoutePatterns) { - const invalidNetlifyRoutes = apiRoutePatterns.filter((apiRoute) => - apiRoute.pathPattern.some(({ kind }) => kind === "hybrid") - ); - if (invalidNetlifyRoutes.length > 0) { - throw ( - "Invalid Netlify routes!\n" + - invalidNetlifyRoutes - .map((value) => JSON.stringify(value, null, 2)) - .join(", ") - ); - } -} - -function isServerSide(route) { - return ( - route.kind === "prerender-with-fallback" || route.kind === "serverless" - ); -} - -/** - * @param {boolean} isOnDemand - */ -function rendererCode(isOnDemand) { - return `import * as elmPages from "./elm-pages-cli.mjs"; -import * as busboy from "busboy"; - -${ - isOnDemand - ? `import { builder } from "@netlify/functions"; - -export const handler = builder(render);` - : ` - -export const handler = render;` -} - - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} event - * @param {any} context - */ -async function render(event, context) { - try { - const renderResult = await elmPages.render(await reqToJson(event)); - console.log("@@@renderResult", JSON.stringify(renderResult, null, 2)); - - const statusCode = renderResult.statusCode; - const headers = renderResult.headers; - - if (renderResult.kind === "bytes") { - return { - body: Buffer.from(renderResult.body).toString("base64"), - isBase64Encoded: true, - multiValueHeaders: { - "Content-Type": "application/octet-stream", - "x-powered-by": "elm-pages", - ...headers, - }, - statusCode, - }; - } else if (renderResult.kind === "api-response") { - return { - body: renderResult.body, - multiValueHeaders: headers, - statusCode, - isBase64Encoded: renderResult.isBase64Encoded, - }; - } else { - return { - body: renderResult.body, - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - ...headers, - }, - statusCode, - }; - } - } catch (error) { - console.error(error); - console.error(JSON.stringify(error, null, 2)); - return { - body: \`

Error

\${JSON.stringify(error, null, 2)}
\`, - statusCode: 500, - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - }, - }; - } -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @returns {{method: string; rawUrl: string; body: string?; headers: Record; requestTime: number; multiPartFormData: unknown }} - */ -function reqToJson(req) { - return { - method: req.httpMethod, - headers: req.headers, - rawUrl: req.rawUrl, - body: req.body, - multiPartFormData: null, - }; -} -`; -} - -/** - * @param {fs.PathLike} dirpath - */ -function ensureDirSync(dirpath) { - try { - fs.mkdirSync(dirpath, { recursive: true }); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } -} - -/** @typedef {{kind: 'dynamic'} | {kind: 'literal', value: string}} ApiSegment */ - -/** - * @param {ApiSegment[]} pathPattern - */ -function apiPatternToRedirectPattern(pathPattern) { - return ( - "/" + - pathPattern - .map((segment, index) => { - switch (segment.kind) { - case "literal": { - return segment.value; - } - case "dynamic": { - return `:dynamic${index}`; - } - default: { - throw "Unhandled segment: " + JSON.stringify(segment); - } - } - }) - .join("/") - ); -} diff --git a/examples/pokedex/elm-pages.config.mjs b/examples/pokedex/elm-pages.config.mjs index dfb90f16..d4d55336 100644 --- a/examples/pokedex/elm-pages.config.mjs +++ b/examples/pokedex/elm-pages.config.mjs @@ -1,6 +1,5 @@ import { defineConfig } from "vite"; - -import adapter from "./adapter.mjs"; +import adapter from "../../adapter/netlify.js"; export default { vite: defineConfig({}), diff --git a/examples/smoothies/adapter.mjs b/examples/smoothies/adapter.mjs deleted file mode 100644 index 3c6d8d90..00000000 --- a/examples/smoothies/adapter.mjs +++ /dev/null @@ -1,316 +0,0 @@ -import fs from "fs"; -import path from "path"; - -export default async function run({ - renderFunctionFilePath, - routePatterns, - apiRoutePatterns, - portsFilePath, - htmlTemplate, -}) { - console.log("Running adapter script"); - ensureDirSync("functions/render"); - ensureDirSync("functions/server-render"); - - fs.copyFileSync( - renderFunctionFilePath, - "./functions/render/elm-pages-cli.js" - ); - fs.copyFileSync( - renderFunctionFilePath, - "./functions/server-render/elm-pages-cli.js" - ); - fs.copyFileSync(portsFilePath, "./functions/render/custom-backend-task.js"); - fs.copyFileSync( - portsFilePath, - "./functions/server-render/custom-backend-task.js" - ); - - fs.writeFileSync( - "./functions/render/index.js", - rendererCode(true, htmlTemplate) - ); - fs.writeFileSync( - "./functions/server-render/index.js", - rendererCode(false, htmlTemplate) - ); - // TODO rename functions/render to functions/fallback-render - // TODO prepend instead of writing file - - const apiServerRoutes = apiRoutePatterns.filter(isServerSide); - - ensureValidRoutePatternsForNetlify(apiServerRoutes); - - // TODO filter apiRoutePatterns on is server side - // TODO need information on whether api route is odb or serverless - const apiRouteRedirects = apiServerRoutes - .map((apiRoute) => { - if (apiRoute.kind === "prerender-with-fallback") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/builders/render 200`; - } else if (apiRoute.kind === "serverless") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/functions/server-render 200`; - } else { - throw "Unhandled 2"; - } - }) - .join("\n"); - - const redirectsFile = - routePatterns - .filter(isServerSide) - .map((route) => { - if (route.kind === "prerender-with-fallback") { - return `${route.pathPattern} /.netlify/builders/render 200 -${route.pathPattern}/content.dat /.netlify/builders/render 200`; - } else { - return `${route.pathPattern} /.netlify/functions/server-render 200 -${path.join( - route.pathPattern, - "/content.dat" -)} /.netlify/functions/server-render 200`; - } - }) - .join("\n") + - "\n" + - apiRouteRedirects + - "\n"; - - fs.writeFileSync("dist/_redirects", redirectsFile); -} - -function ensureValidRoutePatternsForNetlify(apiRoutePatterns) { - const invalidNetlifyRoutes = apiRoutePatterns.filter((apiRoute) => - apiRoute.pathPattern.some(({ kind }) => kind === "hybrid") - ); - if (invalidNetlifyRoutes.length > 0) { - throw ( - "Invalid Netlify routes!\n" + - invalidNetlifyRoutes - .map((value) => JSON.stringify(value, null, 2)) - .join(", ") - ); - } -} - -function isServerSide(route) { - return ( - route.kind === "prerender-with-fallback" || route.kind === "serverless" - ); -} - -/** - * @param {boolean} isOnDemand - * @param {string} htmlTemplate - */ -function rendererCode(isOnDemand, htmlTemplate) { - return `const path = require("path"); -const busboy = require("busboy"); -const htmlTemplate = ${JSON.stringify(htmlTemplate)}; - -${ - isOnDemand - ? `const { builder } = require("@netlify/functions"); - -exports.handler = builder(render);` - : ` - -exports.handler = render;` -} - - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} event - * @param {any} context - */ -async function render(event, context) { - const requestTime = new Date(); - console.log(JSON.stringify(event)); - global.staticHttpCache = {}; - - const compiledElmPath = path.join(__dirname, "elm-pages-cli.js"); - const compiledPortsFile = path.join(__dirname, "custom-backend-task.js"); - const renderer = require("../../../../generator/src/render"); - const preRenderHtml = require("../../../../generator/src/pre-render-html"); - try { - const basePath = "/"; - const mode = "build"; - const addWatcher = () => {}; - - const renderResult = await renderer.render( - compiledPortsFile, - basePath, - require(compiledElmPath), - mode, - event.path, - await reqToJson(event, requestTime), - addWatcher, - false - ); - console.log("@@@renderResult", JSON.stringify(renderResult, null, 2)); - - const statusCode = renderResult.is404 ? 404 : renderResult.statusCode; - - if (renderResult.kind === "bytes") { - return { - body: Buffer.from(renderResult.contentDatPayload.buffer).toString("base64"), - isBase64Encoded: true, - headers: { - "Content-Type": "application/octet-stream", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } else if (renderResult.kind === "api-response") { - const serverResponse = renderResult.body; - return { - body: serverResponse.body, - multiValueHeaders: serverResponse.headers, - statusCode: serverResponse.statusCode, - isBase64Encoded: serverResponse.isBase64Encoded, - }; - } else { - console.log('@rendering', preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString)) - return { - body: preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString), - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } - } catch (error) { - console.error(error); - return { - body: \`

Error

\${error.toString()}
\`, - statusCode: 500, - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - }, - }; - } -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {Promise<{ method: string; hostname: string; query: Record; headers: Record; host: string; pathname: string; port: number | null; protocol: string; rawUrl: string; }>} - */ -function reqToJson(req, requestTime) { - return new Promise((resolve, reject) => { - if ( - req.httpMethod && req.httpMethod.toUpperCase() === "POST" && - req.headers["content-type"] && - req.headers["content-type"].includes("multipart/form-data") && - req.body - ) { - try { - console.log('@@@1'); - const bb = busboy({ - headers: req.headers, - }); - let fields = {}; - - bb.on("file", (fieldname, file, info) => { - console.log('@@@2'); - const { filename, encoding, mimeType } = info; - - file.on("data", (data) => { - fields[fieldname] = { - filename, - mimeType, - body: data.toString(), - }; - }); - }); - - bb.on("field", (fieldName, value) => { - console.log("@@@field", fieldName, value); - fields[fieldName] = value; - }); - - // TODO skip parsing JSON and form data body if busboy doesn't run - bb.on("close", () => { - console.log('@@@3'); - console.log("@@@close", fields); - resolve(toJsonHelper(req, requestTime, fields)); - }); - console.log('@@@4'); - - if (req.isBase64Encoded) { - bb.write(Buffer.from(req.body, 'base64').toString('utf8')); - } else { - bb.write(req.body); - } - } catch (error) { - console.error('@@@5', error); - resolve(toJsonHelper(req, requestTime, null)); - } - } else { - console.log('@@@6'); - resolve(toJsonHelper(req, requestTime, null)); - } - }); -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {{method: string; rawUrl: string; body: string?; headers: Record; requestTime: number; multiPartFormData: unknown }} - */ -function toJsonHelper(req, requestTime, multiPartFormData) { - return { - method: req.httpMethod, - headers: req.headers, - rawUrl: req.rawUrl, - body: req.body, - requestTime: Math.round(requestTime.getTime()), - multiPartFormData: multiPartFormData, - }; -} -`; -} - -/** - * @param {fs.PathLike} dirpath - */ -function ensureDirSync(dirpath) { - try { - fs.mkdirSync(dirpath, { recursive: true }); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } -} - -/** @typedef {{kind: 'dynamic'} | {kind: 'literal', value: string}} ApiSegment */ - -/** - * @param {ApiSegment[]} pathPattern - */ -function apiPatternToRedirectPattern(pathPattern) { - return ( - "/" + - pathPattern - .map((segment, index) => { - switch (segment.kind) { - case "literal": { - return segment.value; - } - case "dynamic": { - return `:dynamic${index}`; - } - default: { - throw "Unhandled segment: " + JSON.stringify(segment); - } - } - }) - .join("/") - ); -} diff --git a/examples/smoothies/elm-pages.config.mjs b/examples/smoothies/elm-pages.config.mjs index dfb90f16..d4d55336 100644 --- a/examples/smoothies/elm-pages.config.mjs +++ b/examples/smoothies/elm-pages.config.mjs @@ -1,6 +1,5 @@ import { defineConfig } from "vite"; - -import adapter from "./adapter.mjs"; +import adapter from "../../adapter/netlify.js"; export default { vite: defineConfig({}), diff --git a/examples/todos/adapter.mjs b/examples/todos/adapter.mjs deleted file mode 100644 index 87e5db72..00000000 --- a/examples/todos/adapter.mjs +++ /dev/null @@ -1,309 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; - -export default async function run({ - renderFunctionFilePath, - routePatterns, - apiRoutePatterns, - portsFilePath, - htmlTemplate, -}) { - console.log("Running adapter script"); - ensureDirSync("functions/render"); - ensureDirSync("functions/server-render"); - - fs.copyFileSync( - renderFunctionFilePath, - "./functions/render/elm-pages-cli.cjs" - ); - fs.copyFileSync( - renderFunctionFilePath, - "./functions/server-render/elm-pages-cli.cjs" - ); - - fs.writeFileSync( - "./functions/render/index.mjs", - rendererCode(true, htmlTemplate) - ); - fs.writeFileSync( - "./functions/server-render/index.mjs", - rendererCode(false, htmlTemplate) - ); - // TODO rename functions/render to functions/fallback-render - // TODO prepend instead of writing file - - const apiServerRoutes = apiRoutePatterns.filter(isServerSide); - - ensureValidRoutePatternsForNetlify(apiServerRoutes); - - // TODO filter apiRoutePatterns on is server side - // TODO need information on whether api route is odb or serverless - const apiRouteRedirects = apiServerRoutes - .map((apiRoute) => { - if (apiRoute.kind === "prerender-with-fallback") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/builders/render 200`; - } else if (apiRoute.kind === "serverless") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/functions/server-render 200`; - } else { - throw "Unhandled 2"; - } - }) - .join("\n"); - - const redirectsFile = - routePatterns - .filter(isServerSide) - .map((route) => { - if (route.kind === "prerender-with-fallback") { - return `${route.pathPattern} /.netlify/builders/render 200 -${route.pathPattern}/content.dat /.netlify/builders/render 200`; - } else { - return `${route.pathPattern} /.netlify/functions/server-render 200 -${route.pathPattern}/content.dat /.netlify/functions/server-render 200`; - } - }) - .join("\n") + - "\n" + - apiRouteRedirects + - "\n"; - - fs.writeFileSync("dist/_redirects", redirectsFile); -} - -function ensureValidRoutePatternsForNetlify(apiRoutePatterns) { - const invalidNetlifyRoutes = apiRoutePatterns.filter((apiRoute) => - apiRoute.pathPattern.some(({ kind }) => kind === "hybrid") - ); - if (invalidNetlifyRoutes.length > 0) { - throw ( - "Invalid Netlify routes!\n" + - invalidNetlifyRoutes - .map((value) => JSON.stringify(value, null, 2)) - .join(", ") - ); - } -} - -function isServerSide(route) { - return ( - route.kind === "prerender-with-fallback" || route.kind === "serverless" - ); -} - -/** - * @param {boolean} isOnDemand - * @param {string} htmlTemplate - */ -function rendererCode(isOnDemand, htmlTemplate) { - return `import * as path from "path"; -import * as busboy from "busboy"; -import { fileURLToPath } from "url"; -import * as renderer from "../../../../generator/src/render.js"; -import * as preRenderHtml from "../../../../generator/src/pre-render-html.js"; -import * as customBackendTask from "${path.resolve(portsFilePath)}"; -const htmlTemplate = ${JSON.stringify(htmlTemplate)}; - -${ - isOnDemand - ? `import { builder } from "@netlify/functions"; - -export const handler = builder(render);` - : ` - -export const handler = render;` -} - - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} event - * @param {any} context - */ -async function render(event, context) { - const requestTime = new Date(); - console.log(JSON.stringify(event)); - - try { - const basePath = "/"; - const mode = "build"; - const addWatcher = () => {}; - - const renderResult = await renderer.render( - customBackendTask, - basePath, - (await import("./elm-pages-cli.cjs")).default, - mode, - event.path, - await reqToJson(event, requestTime), - addWatcher, - false - ); - console.log("@@@renderResult", JSON.stringify(renderResult, null, 2)); - - const statusCode = renderResult.is404 ? 404 : renderResult.statusCode; - - if (renderResult.kind === "bytes") { - return { - body: Buffer.from(renderResult.contentDatPayload.buffer).toString("base64"), - isBase64Encoded: true, - headers: { - "Content-Type": "application/octet-stream", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } else if (renderResult.kind === "api-response") { - const serverResponse = renderResult.body; - return { - body: serverResponse.body, - multiValueHeaders: serverResponse.headers, - statusCode: serverResponse.statusCode, - isBase64Encoded: serverResponse.isBase64Encoded, - }; - } else { - console.log('@rendering', preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString)) - return { - body: preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString), - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } - } catch (error) { - console.log('ERROR') - console.error(error); - console.error(JSON.stringify(error, null, 2)); - return { - body: \`

Error

\${error.toString()}
\`, - statusCode: 500, - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - }, - }; - } -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {Promise<{ method: string; hostname: string; query: Record; headers: Record; host: string; pathname: string; port: number | null; protocol: string; rawUrl: string; }>} - */ -function reqToJson(req, requestTime) { - return new Promise((resolve, reject) => { - if ( - req.httpMethod && req.httpMethod.toUpperCase() === "POST" && - req.headers["content-type"] && - req.headers["content-type"].includes("multipart/form-data") && - req.body - ) { - try { - console.log('@@@1'); - const bb = busboy({ - headers: req.headers, - }); - let fields = {}; - - bb.on("file", (fieldname, file, info) => { - console.log('@@@2'); - const { filename, encoding, mimeType } = info; - - file.on("data", (data) => { - fields[fieldname] = { - filename, - mimeType, - body: data.toString(), - }; - }); - }); - - bb.on("field", (fieldName, value) => { - console.log("@@@field", fieldName, value); - fields[fieldName] = value; - }); - - // TODO skip parsing JSON and form data body if busboy doesn't run - bb.on("close", () => { - console.log('@@@3'); - console.log("@@@close", fields); - resolve(toJsonHelper(req, requestTime, fields)); - }); - console.log('@@@4'); - - if (req.isBase64Encoded) { - bb.write(Buffer.from(req.body, 'base64').toString('utf8')); - } else { - bb.write(req.body); - } - } catch (error) { - console.error('@@@5', error); - resolve(toJsonHelper(req, requestTime, null)); - } - } else { - console.log('@@@6'); - resolve(toJsonHelper(req, requestTime, null)); - } - }); -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {{method: string; rawUrl: string; body: string?; headers: Record; requestTime: number; multiPartFormData: unknown }} - */ -function toJsonHelper(req, requestTime, multiPartFormData) { - return { - method: req.httpMethod, - headers: req.headers, - rawUrl: req.rawUrl, - body: req.body, - requestTime: Math.round(requestTime.getTime()), - multiPartFormData: multiPartFormData, - }; -} -`; -} - -/** - * @param {fs.PathLike} dirpath - */ -function ensureDirSync(dirpath) { - try { - fs.mkdirSync(dirpath, { recursive: true }); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } -} - -/** @typedef {{kind: 'dynamic'} | {kind: 'literal', value: string}} ApiSegment */ - -/** - * @param {ApiSegment[]} pathPattern - */ -function apiPatternToRedirectPattern(pathPattern) { - return ( - "/" + - pathPattern - .map((segment, index) => { - switch (segment.kind) { - case "literal": { - return segment.value; - } - case "dynamic": { - return `:dynamic${index}`; - } - default: { - throw "Unhandled segment: " + JSON.stringify(segment); - } - } - }) - .join("/") - ); -} diff --git a/examples/todos/elm-pages.config.mjs b/examples/todos/elm-pages.config.mjs index dfb90f16..8c17604c 100644 --- a/examples/todos/elm-pages.config.mjs +++ b/examples/todos/elm-pages.config.mjs @@ -1,6 +1,6 @@ import { defineConfig } from "vite"; -import adapter from "./adapter.mjs"; +import adapter from "../../adapter/netlify.js"; export default { vite: defineConfig({}), diff --git a/examples/trails/adapter.mjs b/examples/trails/adapter.mjs deleted file mode 100644 index 7be8f940..00000000 --- a/examples/trails/adapter.mjs +++ /dev/null @@ -1,312 +0,0 @@ -import fs from "fs"; - -export default async function run({ - renderFunctionFilePath, - routePatterns, - apiRoutePatterns, - portsFilePath, - htmlTemplate, -}) { - console.log("Running adapter script"); - ensureDirSync("functions/render"); - ensureDirSync("functions/server-render"); - - fs.copyFileSync( - renderFunctionFilePath, - "./functions/render/elm-pages-cli.js" - ); - fs.copyFileSync( - renderFunctionFilePath, - "./functions/server-render/elm-pages-cli.js" - ); - fs.copyFileSync(portsFilePath, "./functions/render/custom-backend-task.mjs"); - fs.copyFileSync( - portsFilePath, - "./functions/server-render/custom-backend-task.mjs" - ); - - fs.writeFileSync( - "./functions/render/index.js", - rendererCode(true, htmlTemplate) - ); - fs.writeFileSync( - "./functions/server-render/index.js", - rendererCode(false, htmlTemplate) - ); - // TODO rename functions/render to functions/fallback-render - // TODO prepend instead of writing file - - const apiServerRoutes = apiRoutePatterns.filter(isServerSide); - - ensureValidRoutePatternsForNetlify(apiServerRoutes); - - // TODO filter apiRoutePatterns on is server side - // TODO need information on whether api route is odb or serverless - const apiRouteRedirects = apiServerRoutes - .map((apiRoute) => { - if (apiRoute.kind === "prerender-with-fallback") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/builders/render 200`; - } else if (apiRoute.kind === "serverless") { - return `${apiPatternToRedirectPattern( - apiRoute.pathPattern - )} /.netlify/functions/server-render 200`; - } else { - throw "Unhandled 2"; - } - }) - .join("\n"); - - const redirectsFile = - routePatterns - .filter(isServerSide) - .map((route) => { - if (route.kind === "prerender-with-fallback") { - return `${route.pathPattern} /.netlify/builders/render 200 -${route.pathPattern}/content.dat /.netlify/builders/render 200`; - } else { - return `${route.pathPattern} /.netlify/functions/server-render 200 -${route.pathPattern}/content.dat /.netlify/functions/server-render 200`; - } - }) - .join("\n") + - "\n" + - apiRouteRedirects + - "\n"; - - fs.writeFileSync("dist/_redirects", redirectsFile); -} - -function ensureValidRoutePatternsForNetlify(apiRoutePatterns) { - const invalidNetlifyRoutes = apiRoutePatterns.filter((apiRoute) => - apiRoute.pathPattern.some(({ kind }) => kind === "hybrid") - ); - if (invalidNetlifyRoutes.length > 0) { - throw ( - "Invalid Netlify routes!\n" + - invalidNetlifyRoutes - .map((value) => JSON.stringify(value, null, 2)) - .join(", ") - ); - } -} - -function isServerSide(route) { - return ( - route.kind === "prerender-with-fallback" || route.kind === "serverless" - ); -} - -/** - * @param {boolean} isOnDemand - * @param {string} htmlTemplate - */ -function rendererCode(isOnDemand, htmlTemplate) { - return `const path = require("path"); -const busboy = require("busboy"); -const htmlTemplate = ${JSON.stringify(htmlTemplate)}; - -${ - isOnDemand - ? `const { builder } = require("@netlify/functions"); - -exports.handler = builder(render);` - : ` - -exports.handler = render;` -} - - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} event - * @param {any} context - */ -async function render(event, context) { - const requestTime = new Date(); - console.log(JSON.stringify(event)); - global.staticHttpCache = {}; - - const compiledElmPath = path.join(__dirname, "elm-pages-cli.js"); - const compiledPortsFile = path.join(__dirname, "custom-backend-task.mjs"); - const renderer = require("../../../../generator/src/render"); - const preRenderHtml = require("../../../../generator/src/pre-render-html"); - try { - const basePath = "/"; - const mode = "build"; - const addWatcher = () => {}; - - const renderResult = await renderer.render( - compiledPortsFile, - basePath, - require(compiledElmPath), - mode, - event.path, - await reqToJson(event, requestTime), - addWatcher, - false - ); - console.log("@@@renderResult", JSON.stringify(renderResult, null, 2)); - - const statusCode = renderResult.is404 ? 404 : renderResult.statusCode; - - if (renderResult.kind === "bytes") { - return { - body: Buffer.from(renderResult.contentDatPayload.buffer).toString("base64"), - isBase64Encoded: true, - headers: { - "Content-Type": "application/octet-stream", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } else if (renderResult.kind === "api-response") { - const serverResponse = renderResult.body; - return { - body: serverResponse.body, - multiValueHeaders: serverResponse.headers, - statusCode: serverResponse.statusCode, - isBase64Encoded: serverResponse.isBase64Encoded, - }; - } else { - console.log('@rendering', preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString)) - return { - body: preRenderHtml.replaceTemplate(htmlTemplate, renderResult.htmlString), - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - ...renderResult.headers, - }, - statusCode, - }; - } - } catch (error) { - console.error(error); - return { - body: \`

Error

\${error.toString()}
\`, - statusCode: 500, - headers: { - "Content-Type": "text/html", - "x-powered-by": "elm-pages", - }, - }; - } -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {Promise<{ method: string; hostname: string; query: Record; headers: Record; host: string; pathname: string; port: number | null; protocol: string; rawUrl: string; }>} - */ -function reqToJson(req, requestTime) { - return new Promise((resolve, reject) => { - if ( - req.httpMethod && req.httpMethod.toUpperCase() === "POST" && - req.headers["content-type"] && - req.headers["content-type"].includes("multipart/form-data") && - req.body - ) { - try { - console.log('@@@1'); - const bb = busboy({ - headers: req.headers, - }); - let fields = {}; - - bb.on("file", (fieldname, file, info) => { - console.log('@@@2'); - const { filename, encoding, mimeType } = info; - - file.on("data", (data) => { - fields[fieldname] = { - filename, - mimeType, - body: data.toString(), - }; - }); - }); - - bb.on("field", (fieldName, value) => { - console.log("@@@field", fieldName, value); - fields[fieldName] = value; - }); - - // TODO skip parsing JSON and form data body if busboy doesn't run - bb.on("close", () => { - console.log('@@@3'); - console.log("@@@close", fields); - resolve(toJsonHelper(req, requestTime, fields)); - }); - console.log('@@@4'); - - if (req.isBase64Encoded) { - bb.write(Buffer.from(req.body, 'base64').toString('utf8')); - } else { - bb.write(req.body); - } - } catch (error) { - console.error('@@@5', error); - resolve(toJsonHelper(req, requestTime, null)); - } - } else { - console.log('@@@6'); - resolve(toJsonHelper(req, requestTime, null)); - } - }); -} - -/** - * @param {import('aws-lambda').APIGatewayProxyEvent} req - * @param {Date} requestTime - * @returns {{method: string; rawUrl: string; body: string?; headers: Record; requestTime: number; multiPartFormData: unknown }} - */ -function toJsonHelper(req, requestTime, multiPartFormData) { - return { - method: req.httpMethod, - headers: req.headers, - rawUrl: req.rawUrl, - body: req.body, - requestTime: Math.round(requestTime.getTime()), - multiPartFormData: multiPartFormData, - }; -} -`; -} - -/** - * @param {fs.PathLike} dirpath - */ -function ensureDirSync(dirpath) { - try { - fs.mkdirSync(dirpath, { recursive: true }); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } -} - -/** @typedef {{kind: 'dynamic'} | {kind: 'literal', value: string}} ApiSegment */ - -/** - * @param {ApiSegment[]} pathPattern - */ -function apiPatternToRedirectPattern(pathPattern) { - return ( - "/" + - pathPattern - .map((segment, index) => { - switch (segment.kind) { - case "literal": { - return segment.value; - } - case "dynamic": { - return `:dynamic${index}`; - } - default: { - throw "Unhandled segment: " + JSON.stringify(segment); - } - } - }) - .join("/") - ); -}