summaryrefslogtreecommitdiff
path: root/packages/integrations/netlify/src
diff options
context:
space:
mode:
authorGravatar alexanderniebuhr <alexanderniebuhr@users.noreply.github.com> 2023-10-16 14:10:41 +0000
committerGravatar astrobot-houston <fred+astrobot@astro.build> 2023-10-16 14:10:41 +0000
commit3d1329414e507345b19e1dee81fe1bbff4eb3ae0 (patch)
tree69c822b1dc12f177b4919337109e6300a04df91d /packages/integrations/netlify/src
parent63977f5e438062b7e9302b8feff41b662bfa6231 (diff)
downloadastro-3d1329414e507345b19e1dee81fe1bbff4eb3ae0.tar.gz
astro-3d1329414e507345b19e1dee81fe1bbff4eb3ae0.tar.zst
astro-3d1329414e507345b19e1dee81fe1bbff4eb3ae0.zip
[ci] format
Diffstat (limited to 'packages/integrations/netlify/src')
-rw-r--r--packages/integrations/netlify/src/env.d.ts2
-rw-r--r--packages/integrations/netlify/src/index.ts7
-rw-r--r--packages/integrations/netlify/src/integration-functions.ts273
-rw-r--r--packages/integrations/netlify/src/integration-static.ts54
-rw-r--r--packages/integrations/netlify/src/middleware.ts101
-rw-r--r--packages/integrations/netlify/src/netlify-functions.ts413
-rw-r--r--packages/integrations/netlify/src/shared.ts171
7 files changed, 541 insertions, 480 deletions
diff --git a/packages/integrations/netlify/src/env.d.ts b/packages/integrations/netlify/src/env.d.ts
index 8c34fb45e..f964fe0cf 100644
--- a/packages/integrations/netlify/src/env.d.ts
+++ b/packages/integrations/netlify/src/env.d.ts
@@ -1 +1 @@
-/// <reference types="astro/client" /> \ No newline at end of file
+/// <reference types="astro/client" />
diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts
index a374020f9..f9a974d61 100644
--- a/packages/integrations/netlify/src/index.ts
+++ b/packages/integrations/netlify/src/index.ts
@@ -1,2 +1,5 @@
-export { netlifyFunctions as default, netlifyFunctions } from './integration-functions.js';
-export { netlifyStatic } from './integration-static.js';
+export {
+ netlifyFunctions as default,
+ netlifyFunctions,
+} from "./integration-functions.js";
+export { netlifyStatic } from "./integration-static.js";
diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts
index ed16e0200..e4586d654 100644
--- a/packages/integrations/netlify/src/integration-functions.ts
+++ b/packages/integrations/netlify/src/integration-functions.ts
@@ -1,144 +1,165 @@
-import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
-import { writeFile } from 'node:fs/promises';
-import { extname, join } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import { generateEdgeMiddleware } from './middleware.js';
-import type { Args } from './netlify-functions.js';
-import { createRedirects } from './shared.js';
+import type {
+ AstroAdapter,
+ AstroConfig,
+ AstroIntegration,
+ RouteData,
+} from "astro";
+import { writeFile } from "node:fs/promises";
+import { extname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { generateEdgeMiddleware } from "./middleware.js";
+import type { Args } from "./netlify-functions.js";
+import { createRedirects } from "./shared.js";
-export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware';
-export const ASTRO_LOCALS_HEADER = 'x-astro-locals';
+export const NETLIFY_EDGE_MIDDLEWARE_FILE = "netlify-edge-middleware";
+export const ASTRO_LOCALS_HEADER = "x-astro-locals";
-export function getAdapter({ functionPerRoute, edgeMiddleware, ...args }: Args): AstroAdapter {
- return {
- name: '@astrojs/netlify/functions',
- serverEntrypoint: '@astrojs/netlify/netlify-functions.js',
- exports: ['handler'],
- args,
- adapterFeatures: {
- functionPerRoute,
- edgeMiddleware,
- },
- supportedAstroFeatures: {
- hybridOutput: 'stable',
- staticOutput: 'stable',
- serverOutput: 'stable',
- assets: {
- supportKind: 'stable',
- isSharpCompatible: true,
- isSquooshCompatible: true,
- },
- },
- };
+export function getAdapter({
+ functionPerRoute,
+ edgeMiddleware,
+ ...args
+}: Args): AstroAdapter {
+ return {
+ name: "@astrojs/netlify/functions",
+ serverEntrypoint: "@astrojs/netlify/netlify-functions.js",
+ exports: ["handler"],
+ args,
+ adapterFeatures: {
+ functionPerRoute,
+ edgeMiddleware,
+ },
+ supportedAstroFeatures: {
+ hybridOutput: "stable",
+ staticOutput: "stable",
+ serverOutput: "stable",
+ assets: {
+ supportKind: "stable",
+ isSharpCompatible: true,
+ isSquooshCompatible: true,
+ },
+ },
+ };
}
interface NetlifyFunctionsOptions {
- dist?: URL;
- builders?: boolean;
- binaryMediaTypes?: string[];
- edgeMiddleware?: boolean;
- functionPerRoute?: boolean;
+ dist?: URL;
+ builders?: boolean;
+ binaryMediaTypes?: string[];
+ edgeMiddleware?: boolean;
+ functionPerRoute?: boolean;
}
function netlifyFunctions({
- dist,
- builders,
- binaryMediaTypes,
- functionPerRoute = false,
- edgeMiddleware = false,
+ dist,
+ builders,
+ binaryMediaTypes,
+ functionPerRoute = false,
+ edgeMiddleware = false,
}: NetlifyFunctionsOptions = {}): AstroIntegration {
- let _config: AstroConfig;
- let _entryPoints: Map<RouteData, URL>;
- let ssrEntryFile: string;
- let _middlewareEntryPoint: URL;
- return {
- name: '@astrojs/netlify',
- hooks: {
- 'astro:config:setup': ({ config, updateConfig }) => {
- const outDir = dist ?? new URL('./dist/', config.root);
- updateConfig({
- outDir,
- build: {
- redirects: false,
- client: outDir,
- server: new URL('./.netlify/functions-internal/', config.root),
- },
- });
- },
- 'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => {
- if (middlewareEntryPoint) {
- _middlewareEntryPoint = middlewareEntryPoint;
- }
- _entryPoints = entryPoints;
- },
- 'astro:config:done': ({ config, setAdapter }) => {
- setAdapter(getAdapter({ binaryMediaTypes, builders, functionPerRoute, edgeMiddleware }));
- _config = config;
- ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, '');
+ let _config: AstroConfig;
+ let _entryPoints: Map<RouteData, URL>;
+ let ssrEntryFile: string;
+ let _middlewareEntryPoint: URL;
+ return {
+ name: "@astrojs/netlify",
+ hooks: {
+ "astro:config:setup": ({ config, updateConfig }) => {
+ const outDir = dist ?? new URL("./dist/", config.root);
+ updateConfig({
+ outDir,
+ build: {
+ redirects: false,
+ client: outDir,
+ server: new URL("./.netlify/functions-internal/", config.root),
+ },
+ });
+ },
+ "astro:build:ssr": async ({ entryPoints, middlewareEntryPoint }) => {
+ if (middlewareEntryPoint) {
+ _middlewareEntryPoint = middlewareEntryPoint;
+ }
+ _entryPoints = entryPoints;
+ },
+ "astro:config:done": ({ config, setAdapter }) => {
+ setAdapter(
+ getAdapter({
+ binaryMediaTypes,
+ builders,
+ functionPerRoute,
+ edgeMiddleware,
+ }),
+ );
+ _config = config;
+ ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, "");
- if (config.output === 'static') {
- // eslint-disable-next-line no-console
- console.warn(
- `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`
- );
- // eslint-disable-next-line no-console
- console.warn(
- `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`
- );
- }
- },
- 'astro:build:done': async ({ routes, dir }) => {
- const functionsConfig = {
- version: 1,
- config: {
- nodeModuleFormat: 'esm',
- },
- };
- const functionsConfigPath = join(fileURLToPath(_config.build.server), 'entry.json');
- await writeFile(functionsConfigPath, JSON.stringify(functionsConfig));
+ if (config.output === "static") {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`,
+ );
+ // eslint-disable-next-line no-console
+ console.warn(
+ `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`,
+ );
+ }
+ },
+ "astro:build:done": async ({ routes, dir }) => {
+ const functionsConfig = {
+ version: 1,
+ config: {
+ nodeModuleFormat: "esm",
+ },
+ };
+ const functionsConfigPath = join(
+ fileURLToPath(_config.build.server),
+ "entry.json",
+ );
+ await writeFile(functionsConfigPath, JSON.stringify(functionsConfig));
- const type = builders ? 'builders' : 'functions';
- const kind = type ?? 'functions';
+ const type = builders ? "builders" : "functions";
+ const kind = type ?? "functions";
- if (_entryPoints.size) {
- const routeToDynamicTargetMap = new Map();
- for (const [route, entryFile] of _entryPoints) {
- const wholeFileUrl = fileURLToPath(entryFile);
+ if (_entryPoints.size) {
+ const routeToDynamicTargetMap = new Map();
+ for (const [route, entryFile] of _entryPoints) {
+ const wholeFileUrl = fileURLToPath(entryFile);
- const extension = extname(wholeFileUrl);
- const relative = wholeFileUrl
- .replace(fileURLToPath(_config.build.server), '')
- .replace(extension, '')
- .replaceAll('\\', '/');
- const dynamicTarget = `/.netlify/${kind}/${relative}`;
+ const extension = extname(wholeFileUrl);
+ const relative = wholeFileUrl
+ .replace(fileURLToPath(_config.build.server), "")
+ .replace(extension, "")
+ .replaceAll("\\", "/");
+ const dynamicTarget = `/.netlify/${kind}/${relative}`;
- routeToDynamicTargetMap.set(route, dynamicTarget);
- }
- await createRedirects(_config, routeToDynamicTargetMap, dir);
- } else {
- const dynamicTarget = `/.netlify/${kind}/${ssrEntryFile}`;
- const map: [RouteData, string][] = routes.map((route) => {
- return [route, dynamicTarget];
- });
- const routeToDynamicTargetMap = new Map(Array.from(map));
+ routeToDynamicTargetMap.set(route, dynamicTarget);
+ }
+ await createRedirects(_config, routeToDynamicTargetMap, dir);
+ } else {
+ const dynamicTarget = `/.netlify/${kind}/${ssrEntryFile}`;
+ const map: [RouteData, string][] = routes.map((route) => {
+ return [route, dynamicTarget];
+ });
+ const routeToDynamicTargetMap = new Map(Array.from(map));
- await createRedirects(_config, routeToDynamicTargetMap, dir);
- }
- if (_middlewareEntryPoint) {
- const outPath = fileURLToPath(new URL('./.netlify/edge-functions/', _config.root));
- const netlifyEdgeMiddlewareHandlerPath = new URL(
- NETLIFY_EDGE_MIDDLEWARE_FILE,
- _config.srcDir
- );
- await generateEdgeMiddleware(
- _middlewareEntryPoint,
- outPath,
- netlifyEdgeMiddlewareHandlerPath
- );
- }
- },
- },
- };
+ await createRedirects(_config, routeToDynamicTargetMap, dir);
+ }
+ if (_middlewareEntryPoint) {
+ const outPath = fileURLToPath(
+ new URL("./.netlify/edge-functions/", _config.root),
+ );
+ const netlifyEdgeMiddlewareHandlerPath = new URL(
+ NETLIFY_EDGE_MIDDLEWARE_FILE,
+ _config.srcDir,
+ );
+ await generateEdgeMiddleware(
+ _middlewareEntryPoint,
+ outPath,
+ netlifyEdgeMiddlewareHandlerPath,
+ );
+ }
+ },
+ },
+ };
}
export { netlifyFunctions as default, netlifyFunctions };
diff --git a/packages/integrations/netlify/src/integration-static.ts b/packages/integrations/netlify/src/integration-static.ts
index af2849867..beebdef05 100644
--- a/packages/integrations/netlify/src/integration-static.ts
+++ b/packages/integrations/netlify/src/integration-static.ts
@@ -1,30 +1,30 @@
-import type { AstroIntegration, RouteData } from 'astro';
-import { createRedirects } from './shared.js';
+import type { AstroIntegration, RouteData } from "astro";
+import { createRedirects } from "./shared.js";
export function netlifyStatic(): AstroIntegration {
- let _config: any;
- return {
- name: '@astrojs/netlify',
- hooks: {
- 'astro:config:setup': ({ updateConfig }) => {
- updateConfig({
- build: {
- // Do not output HTML redirects because we are building a `_redirects` file.
- redirects: false,
- },
- });
- },
- 'astro:config:done': ({ config }) => {
- _config = config;
- },
- 'astro:build:done': async ({ dir, routes }) => {
- const mappedRoutes: [RouteData, string][] = routes.map((route) => [
- route,
- `/.netlify/static/`,
- ]);
- const routesToDynamicTargetMap = new Map(Array.from(mappedRoutes));
- await createRedirects(_config, routesToDynamicTargetMap, dir);
- },
- },
- };
+ let _config: any;
+ return {
+ name: "@astrojs/netlify",
+ hooks: {
+ "astro:config:setup": ({ updateConfig }) => {
+ updateConfig({
+ build: {
+ // Do not output HTML redirects because we are building a `_redirects` file.
+ redirects: false,
+ },
+ });
+ },
+ "astro:config:done": ({ config }) => {
+ _config = config;
+ },
+ "astro:build:done": async ({ dir, routes }) => {
+ const mappedRoutes: [RouteData, string][] = routes.map((route) => [
+ route,
+ `/.netlify/static/`,
+ ]);
+ const routesToDynamicTargetMap = new Map(Array.from(mappedRoutes));
+ await createRedirects(_config, routesToDynamicTargetMap, dir);
+ },
+ },
+ };
}
diff --git a/packages/integrations/netlify/src/middleware.ts b/packages/integrations/netlify/src/middleware.ts
index 3c2f4f697..c07dd04fa 100644
--- a/packages/integrations/netlify/src/middleware.ts
+++ b/packages/integrations/netlify/src/middleware.ts
@@ -1,55 +1,68 @@
-import { existsSync } from 'node:fs';
-import { join } from 'node:path';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import { ASTRO_LOCALS_HEADER } from './integration-functions.js';
-import { DENO_SHIM } from './shared.js';
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+import { fileURLToPath, pathToFileURL } from "node:url";
+import { ASTRO_LOCALS_HEADER } from "./integration-functions.js";
+import { DENO_SHIM } from "./shared.js";
/**
* It generates a Netlify edge function.
*
*/
export async function generateEdgeMiddleware(
- astroMiddlewareEntryPointPath: URL,
- outPath: string,
- netlifyEdgeMiddlewareHandlerPath: URL
+ astroMiddlewareEntryPointPath: URL,
+ outPath: string,
+ netlifyEdgeMiddlewareHandlerPath: URL,
): Promise<URL> {
- const entryPointPathURLAsString = JSON.stringify(
- fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/')
- );
+ const entryPointPathURLAsString = JSON.stringify(
+ fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, "/"),
+ );
- const code = edgeMiddlewareTemplate(entryPointPathURLAsString, netlifyEdgeMiddlewareHandlerPath);
- const bundledFilePath = join(outPath, 'edgeMiddleware.js');
- const esbuild = await import('esbuild');
- await esbuild.build({
- stdin: {
- contents: code,
- resolveDir: process.cwd(),
- },
- target: 'es2020',
- platform: 'browser',
- outfile: bundledFilePath,
- allowOverwrite: true,
- format: 'esm',
- bundle: true,
- minify: false,
- banner: {
- js: DENO_SHIM,
- },
- });
- return pathToFileURL(bundledFilePath);
+ const code = edgeMiddlewareTemplate(
+ entryPointPathURLAsString,
+ netlifyEdgeMiddlewareHandlerPath,
+ );
+ const bundledFilePath = join(outPath, "edgeMiddleware.js");
+ const esbuild = await import("esbuild");
+ await esbuild.build({
+ stdin: {
+ contents: code,
+ resolveDir: process.cwd(),
+ },
+ target: "es2020",
+ platform: "browser",
+ outfile: bundledFilePath,
+ allowOverwrite: true,
+ format: "esm",
+ bundle: true,
+ minify: false,
+ banner: {
+ js: DENO_SHIM,
+ },
+ });
+ return pathToFileURL(bundledFilePath);
}
-function edgeMiddlewareTemplate(middlewarePath: string, netlifyEdgeMiddlewareHandlerPath: URL) {
- const filePathEdgeMiddleware = fileURLToPath(netlifyEdgeMiddlewareHandlerPath);
- let handlerTemplateImport = '';
- let handlerTemplateCall = '{}';
- if (existsSync(filePathEdgeMiddleware + '.js') || existsSync(filePathEdgeMiddleware + '.ts')) {
- const stringified = JSON.stringify(filePathEdgeMiddleware.replace(/\\/g, '/'));
- handlerTemplateImport = `import handler from ${stringified}`;
- handlerTemplateCall = `handler({ request, context })`;
- } else {
- }
- return `
+function edgeMiddlewareTemplate(
+ middlewarePath: string,
+ netlifyEdgeMiddlewareHandlerPath: URL,
+) {
+ const filePathEdgeMiddleware = fileURLToPath(
+ netlifyEdgeMiddlewareHandlerPath,
+ );
+ let handlerTemplateImport = "";
+ let handlerTemplateCall = "{}";
+ if (
+ existsSync(filePathEdgeMiddleware + ".js") ||
+ existsSync(filePathEdgeMiddleware + ".ts")
+ ) {
+ const stringified = JSON.stringify(
+ filePathEdgeMiddleware.replace(/\\/g, "/"),
+ );
+ handlerTemplateImport = `import handler from ${stringified}`;
+ handlerTemplateCall = `handler({ request, context })`;
+ } else {
+ }
+ return `
${handlerTemplateImport}
import { onRequest } from ${middlewarePath};
import { createContext, trySerializeLocals } from 'astro/middleware';
@@ -61,7 +74,9 @@ export default async function middleware(request, context) {
});
ctx.locals = ${handlerTemplateCall};
const next = async () => {
- request.headers.set(${JSON.stringify(ASTRO_LOCALS_HEADER)}, trySerializeLocals(ctx.locals));
+ request.headers.set(${JSON.stringify(
+ ASTRO_LOCALS_HEADER,
+ )}, trySerializeLocals(ctx.locals));
return await context.next();
};
diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts
index 8c051d9f6..54a9ea7d3 100644
--- a/packages/integrations/netlify/src/netlify-functions.ts
+++ b/packages/integrations/netlify/src/netlify-functions.ts
@@ -1,144 +1,157 @@
-import { builder, type Handler } from '@netlify/functions';
-import type { SSRManifest } from 'astro';
-import { App } from 'astro/app';
-import { applyPolyfills } from 'astro/app/node';
-import { ASTRO_LOCALS_HEADER } from './integration-functions.js';
+import { builder, type Handler } from "@netlify/functions";
+import type { SSRManifest } from "astro";
+import { App } from "astro/app";
+import { applyPolyfills } from "astro/app/node";
+import { ASTRO_LOCALS_HEADER } from "./integration-functions.js";
applyPolyfills();
export interface Args {
- builders?: boolean;
- binaryMediaTypes?: string[];
- edgeMiddleware: boolean;
- functionPerRoute: boolean;
+ builders?: boolean;
+ binaryMediaTypes?: string[];
+ edgeMiddleware: boolean;
+ functionPerRoute: boolean;
}
function parseContentType(header?: string) {
- return header?.split(';')[0] ?? '';
+ return header?.split(";")[0] ?? "";
}
-const clientAddressSymbol = Symbol.for('astro.clientAddress');
+const clientAddressSymbol = Symbol.for("astro.clientAddress");
export const createExports = (manifest: SSRManifest, args: Args) => {
- const app = new App(manifest);
-
- const builders = args.builders ?? false;
- const binaryMediaTypes = args.binaryMediaTypes ?? [];
- const knownBinaryMediaTypes = new Set([
- 'audio/3gpp',
- 'audio/3gpp2',
- 'audio/aac',
- 'audio/midi',
- 'audio/mpeg',
- 'audio/ogg',
- 'audio/opus',
- 'audio/wav',
- 'audio/webm',
- 'audio/x-midi',
- 'image/avif',
- 'image/bmp',
- 'image/gif',
- 'image/vnd.microsoft.icon',
- 'image/heif',
- 'image/jpeg',
- 'image/png',
- 'image/svg+xml',
- 'image/tiff',
- 'image/webp',
- 'video/3gpp',
- 'video/3gpp2',
- 'video/mp2t',
- 'video/mp4',
- 'video/mpeg',
- 'video/ogg',
- 'video/x-msvideo',
- 'video/webm',
- ...binaryMediaTypes,
- ]);
-
- const myHandler: Handler = async (event) => {
- const { httpMethod, headers, rawUrl, body: requestBody, isBase64Encoded } = event;
- const init: RequestInit = {
- method: httpMethod,
- headers: new Headers(headers as any),
- };
- // Attach the event body the request, with proper encoding.
- if (httpMethod !== 'GET' && httpMethod !== 'HEAD') {
- const encoding = isBase64Encoded ? 'base64' : 'utf-8';
- init.body =
- typeof requestBody === 'string' ? Buffer.from(requestBody, encoding) : requestBody;
- }
-
- const request = new Request(rawUrl, init);
-
- const routeData = app.match(request);
- const ip = headers['x-nf-client-connection-ip'];
- Reflect.set(request, clientAddressSymbol, ip);
-
- let locals: Record<string, unknown> = {};
-
- if (request.headers.has(ASTRO_LOCALS_HEADER)) {
- let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER);
- if (localsAsString) {
- locals = JSON.parse(localsAsString);
- }
- }
-
- let responseTtl = undefined;
-
- locals.runtime = builders
- ? {
- setBuildersTtl(ttl: number) {
- responseTtl = ttl;
- },
- }
- : {};
-
- const response: Response = await app.render(request, routeData, locals);
- const responseHeaders = Object.fromEntries(response.headers.entries());
-
- const responseContentType = parseContentType(responseHeaders['content-type']);
- const responseIsBase64Encoded = knownBinaryMediaTypes.has(responseContentType);
-
- let responseBody: string;
- if (responseIsBase64Encoded) {
- const ab = await response.arrayBuffer();
- responseBody = Buffer.from(ab).toString('base64');
- } else {
- responseBody = await response.text();
- }
-
- const fnResponse: any = {
- statusCode: response.status,
- headers: responseHeaders,
- body: responseBody,
- isBase64Encoded: responseIsBase64Encoded,
- ttl: responseTtl,
- };
-
- const cookies = response.headers.get('set-cookie');
- if (cookies) {
- fnResponse.multiValueHeaders = {
- 'set-cookie': Array.isArray(cookies) ? cookies : splitCookiesString(cookies),
- };
- }
-
- // Apply cookies set via Astro.cookies.set/delete
- if (app.setCookieHeaders) {
- const setCookieHeaders = Array.from(app.setCookieHeaders(response));
- fnResponse.multiValueHeaders = fnResponse.multiValueHeaders || {};
- if (!fnResponse.multiValueHeaders['set-cookie']) {
- fnResponse.multiValueHeaders['set-cookie'] = [];
- }
- fnResponse.multiValueHeaders['set-cookie'].push(...setCookieHeaders);
- }
-
- return fnResponse;
- };
-
- const handler = builders ? builder(myHandler) : myHandler;
-
- return { handler };
+ const app = new App(manifest);
+
+ const builders = args.builders ?? false;
+ const binaryMediaTypes = args.binaryMediaTypes ?? [];
+ const knownBinaryMediaTypes = new Set([
+ "audio/3gpp",
+ "audio/3gpp2",
+ "audio/aac",
+ "audio/midi",
+ "audio/mpeg",
+ "audio/ogg",
+ "audio/opus",
+ "audio/wav",
+ "audio/webm",
+ "audio/x-midi",
+ "image/avif",
+ "image/bmp",
+ "image/gif",
+ "image/vnd.microsoft.icon",
+ "image/heif",
+ "image/jpeg",
+ "image/png",
+ "image/svg+xml",
+ "image/tiff",
+ "image/webp",
+ "video/3gpp",
+ "video/3gpp2",
+ "video/mp2t",
+ "video/mp4",
+ "video/mpeg",
+ "video/ogg",
+ "video/x-msvideo",
+ "video/webm",
+ ...binaryMediaTypes,
+ ]);
+
+ const myHandler: Handler = async (event) => {
+ const {
+ httpMethod,
+ headers,
+ rawUrl,
+ body: requestBody,
+ isBase64Encoded,
+ } = event;
+ const init: RequestInit = {
+ method: httpMethod,
+ headers: new Headers(headers as any),
+ };
+ // Attach the event body the request, with proper encoding.
+ if (httpMethod !== "GET" && httpMethod !== "HEAD") {
+ const encoding = isBase64Encoded ? "base64" : "utf-8";
+ init.body =
+ typeof requestBody === "string"
+ ? Buffer.from(requestBody, encoding)
+ : requestBody;
+ }
+
+ const request = new Request(rawUrl, init);
+
+ const routeData = app.match(request);
+ const ip = headers["x-nf-client-connection-ip"];
+ Reflect.set(request, clientAddressSymbol, ip);
+
+ let locals: Record<string, unknown> = {};
+
+ if (request.headers.has(ASTRO_LOCALS_HEADER)) {
+ let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER);
+ if (localsAsString) {
+ locals = JSON.parse(localsAsString);
+ }
+ }
+
+ let responseTtl = undefined;
+
+ locals.runtime = builders
+ ? {
+ setBuildersTtl(ttl: number) {
+ responseTtl = ttl;
+ },
+ }
+ : {};
+
+ const response: Response = await app.render(request, routeData, locals);
+ const responseHeaders = Object.fromEntries(response.headers.entries());
+
+ const responseContentType = parseContentType(
+ responseHeaders["content-type"],
+ );
+ const responseIsBase64Encoded =
+ knownBinaryMediaTypes.has(responseContentType);
+
+ let responseBody: string;
+ if (responseIsBase64Encoded) {
+ const ab = await response.arrayBuffer();
+ responseBody = Buffer.from(ab).toString("base64");
+ } else {
+ responseBody = await response.text();
+ }
+
+ const fnResponse: any = {
+ statusCode: response.status,
+ headers: responseHeaders,
+ body: responseBody,
+ isBase64Encoded: responseIsBase64Encoded,
+ ttl: responseTtl,
+ };
+
+ const cookies = response.headers.get("set-cookie");
+ if (cookies) {
+ fnResponse.multiValueHeaders = {
+ "set-cookie": Array.isArray(cookies)
+ ? cookies
+ : splitCookiesString(cookies),
+ };
+ }
+
+ // Apply cookies set via Astro.cookies.set/delete
+ if (app.setCookieHeaders) {
+ const setCookieHeaders = Array.from(app.setCookieHeaders(response));
+ fnResponse.multiValueHeaders = fnResponse.multiValueHeaders || {};
+ if (!fnResponse.multiValueHeaders["set-cookie"]) {
+ fnResponse.multiValueHeaders["set-cookie"] = [];
+ }
+ fnResponse.multiValueHeaders["set-cookie"].push(...setCookieHeaders);
+ }
+
+ return fnResponse;
+ };
+
+ const handler = builders ? builder(myHandler) : myHandler;
+
+ return { handler };
};
/*
@@ -152,74 +165,74 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
*/
function splitCookiesString(cookiesString: string): string[] {
- if (Array.isArray(cookiesString)) {
- return cookiesString;
- }
- if (typeof cookiesString !== 'string') {
- return [];
- }
-
- let cookiesStrings = [];
- let pos = 0;
- let start;
- let ch;
- let lastComma;
- let nextStart;
- let cookiesSeparatorFound;
-
- function skipWhitespace() {
- while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
- pos += 1;
- }
- return pos < cookiesString.length;
- }
-
- function notSpecialChar() {
- ch = cookiesString.charAt(pos);
-
- return ch !== '=' && ch !== ';' && ch !== ',';
- }
-
- while (pos < cookiesString.length) {
- start = pos;
- cookiesSeparatorFound = false;
-
- while (skipWhitespace()) {
- ch = cookiesString.charAt(pos);
- if (ch === ',') {
- // ',' is a cookie separator if we have later first '=', not ';' or ','
- lastComma = pos;
- pos += 1;
-
- skipWhitespace();
- nextStart = pos;
-
- while (pos < cookiesString.length && notSpecialChar()) {
- pos += 1;
- }
-
- // currently special character
- if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') {
- // we found cookies separator
- cookiesSeparatorFound = true;
- // pos is inside the next cookie, so back up and return it.
- pos = nextStart;
- cookiesStrings.push(cookiesString.substring(start, lastComma));
- start = pos;
- } else {
- // in param ',' or param separator ';',
- // we continue from that comma
- pos = lastComma + 1;
- }
- } else {
- pos += 1;
- }
- }
-
- if (!cookiesSeparatorFound || pos >= cookiesString.length) {
- cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
- }
- }
-
- return cookiesStrings;
+ if (Array.isArray(cookiesString)) {
+ return cookiesString;
+ }
+ if (typeof cookiesString !== "string") {
+ return [];
+ }
+
+ let cookiesStrings = [];
+ let pos = 0;
+ let start;
+ let ch;
+ let lastComma;
+ let nextStart;
+ let cookiesSeparatorFound;
+
+ function skipWhitespace() {
+ while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
+ pos += 1;
+ }
+ return pos < cookiesString.length;
+ }
+
+ function notSpecialChar() {
+ ch = cookiesString.charAt(pos);
+
+ return ch !== "=" && ch !== ";" && ch !== ",";
+ }
+
+ while (pos < cookiesString.length) {
+ start = pos;
+ cookiesSeparatorFound = false;
+
+ while (skipWhitespace()) {
+ ch = cookiesString.charAt(pos);
+ if (ch === ",") {
+ // ',' is a cookie separator if we have later first '=', not ';' or ','
+ lastComma = pos;
+ pos += 1;
+
+ skipWhitespace();
+ nextStart = pos;
+
+ while (pos < cookiesString.length && notSpecialChar()) {
+ pos += 1;
+ }
+
+ // currently special character
+ if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
+ // we found cookies separator
+ cookiesSeparatorFound = true;
+ // pos is inside the next cookie, so back up and return it.
+ pos = nextStart;
+ cookiesStrings.push(cookiesString.substring(start, lastComma));
+ start = pos;
+ } else {
+ // in param ',' or param separator ';',
+ // we continue from that comma
+ pos = lastComma + 1;
+ }
+ } else {
+ pos += 1;
+ }
+ }
+
+ if (!cookiesSeparatorFound || pos >= cookiesString.length) {
+ cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
+ }
+ }
+
+ return cookiesStrings;
}
diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts
index 175b9d04f..4003f812f 100644
--- a/packages/integrations/netlify/src/shared.ts
+++ b/packages/integrations/netlify/src/shared.ts
@@ -1,9 +1,9 @@
-import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
-import type { AstroConfig, RouteData } from 'astro';
-import esbuild from 'esbuild';
-import fs from 'node:fs';
-import npath from 'node:path';
-import { fileURLToPath } from 'node:url';
+import { createRedirectsFromAstroRoutes } from "@astrojs/underscore-redirects";
+import type { AstroConfig, RouteData } from "astro";
+import esbuild from "esbuild";
+import fs from "node:fs";
+import npath from "node:path";
+import { fileURLToPath } from "node:url";
export const DENO_SHIM = `globalThis.process = {
argv: [],
@@ -11,104 +11,113 @@ export const DENO_SHIM = `globalThis.process = {
};`;
export interface NetlifyEdgeFunctionsOptions {
- dist?: URL;
+ dist?: URL;
}
export interface NetlifyEdgeFunctionManifestFunctionPath {
- function: string;
- path: string;
+ function: string;
+ path: string;
}
export interface NetlifyEdgeFunctionManifestFunctionPattern {
- function: string;
- pattern: string;
+ function: string;
+ pattern: string;
}
export type NetlifyEdgeFunctionManifestFunction =
- | NetlifyEdgeFunctionManifestFunctionPath
- | NetlifyEdgeFunctionManifestFunctionPattern;
+ | NetlifyEdgeFunctionManifestFunctionPath
+ | NetlifyEdgeFunctionManifestFunctionPattern;
export interface NetlifyEdgeFunctionManifest {
- functions: NetlifyEdgeFunctionManifestFunction[];
- version: 1;
+ functions: NetlifyEdgeFunctionManifestFunction[];
+ version: 1;
}
export async function createRedirects(
- config: AstroConfig,
- routeToDynamicTargetMap: Map<RouteData, string>,
- dir: URL
+ config: AstroConfig,
+ routeToDynamicTargetMap: Map<RouteData, string>,
+ dir: URL,
) {
- const _redirectsURL = new URL('./_redirects', dir);
+ const _redirectsURL = new URL("./_redirects", dir);
- const _redirects = createRedirectsFromAstroRoutes({
- config,
- routeToDynamicTargetMap,
- dir,
- });
- const content = _redirects.print();
+ const _redirects = createRedirectsFromAstroRoutes({
+ config,
+ routeToDynamicTargetMap,
+ dir,
+ });
+ const content = _redirects.print();
- // Always use appendFile() because the redirects file could already exist,
- // e.g. due to a `/public/_redirects` file that got copied to the output dir.
- // If the file does not exist yet, appendFile() automatically creates it.
- await fs.promises.appendFile(_redirectsURL, content, 'utf-8');
+ // Always use appendFile() because the redirects file could already exist,
+ // e.g. due to a `/public/_redirects` file that got copied to the output dir.
+ // If the file does not exist yet, appendFile() automatically creates it.
+ await fs.promises.appendFile(_redirectsURL, content, "utf-8");
}
-export async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: URL) {
- const functions: NetlifyEdgeFunctionManifestFunction[] = [];
- for (const route of routes) {
- if (route.pathname) {
- functions.push({
- function: entryFile,
- path: route.pathname,
- });
- } else {
- functions.push({
- function: entryFile,
- // Make route pattern serializable to match expected
- // Netlify Edge validation format. Mirrors Netlify's own edge bundler:
- // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34
- pattern: route.pattern.source.replace(/\\\//g, '/').toString(),
- });
- }
- }
+export async function createEdgeManifest(
+ routes: RouteData[],
+ entryFile: string,
+ dir: URL,
+) {
+ const functions: NetlifyEdgeFunctionManifestFunction[] = [];
+ for (const route of routes) {
+ if (route.pathname) {
+ functions.push({
+ function: entryFile,
+ path: route.pathname,
+ });
+ } else {
+ functions.push({
+ function: entryFile,
+ // Make route pattern serializable to match expected
+ // Netlify Edge validation format. Mirrors Netlify's own edge bundler:
+ // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34
+ pattern: route.pattern.source.replace(/\\\//g, "/").toString(),
+ });
+ }
+ }
- const manifest: NetlifyEdgeFunctionManifest = {
- functions,
- version: 1,
- };
+ const manifest: NetlifyEdgeFunctionManifest = {
+ functions,
+ version: 1,
+ };
- const baseDir = new URL('./.netlify/edge-functions/', dir);
- await fs.promises.mkdir(baseDir, { recursive: true });
+ const baseDir = new URL("./.netlify/edge-functions/", dir);
+ await fs.promises.mkdir(baseDir, { recursive: true });
- const manifestURL = new URL('./manifest.json', baseDir);
- const _manifest = JSON.stringify(manifest, null, ' ');
- await fs.promises.writeFile(manifestURL, _manifest, 'utf-8');
+ const manifestURL = new URL("./manifest.json", baseDir);
+ const _manifest = JSON.stringify(manifest, null, " ");
+ await fs.promises.writeFile(manifestURL, _manifest, "utf-8");
}
-export async function bundleServerEntry(entryUrl: URL, serverUrl?: URL, vite?: any | undefined) {
- const pth = fileURLToPath(entryUrl);
- await esbuild.build({
- target: 'es2020',
- platform: 'browser',
- entryPoints: [pth],
- outfile: pth,
- allowOverwrite: true,
- format: 'esm',
- bundle: true,
- external: ['@astrojs/markdown-remark', 'astro/middleware'],
- banner: {
- js: DENO_SHIM,
- },
- });
+export async function bundleServerEntry(
+ entryUrl: URL,
+ serverUrl?: URL,
+ vite?: any | undefined,
+) {
+ const pth = fileURLToPath(entryUrl);
+ await esbuild.build({
+ target: "es2020",
+ platform: "browser",
+ entryPoints: [pth],
+ outfile: pth,
+ allowOverwrite: true,
+ format: "esm",
+ bundle: true,
+ external: ["@astrojs/markdown-remark", "astro/middleware"],
+ banner: {
+ js: DENO_SHIM,
+ },
+ });
- // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash.
- if (vite && serverUrl) {
- try {
- const chunkFileNames =
- vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`;
- const chunkPath = npath.dirname(chunkFileNames);
- const chunksDirUrl = new URL(chunkPath + '/', serverUrl);
- await fs.promises.rm(chunksDirUrl, { recursive: true, force: true });
- } catch {}
- }
+ // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash.
+ if (vite && serverUrl) {
+ try {
+ const chunkFileNames =
+ vite?.build?.rollupOptions?.output?.chunkFileNames ??
+ `chunks/chunk.[hash].mjs`;
+ const chunkPath = npath.dirname(chunkFileNames);
+ const chunksDirUrl = new URL(chunkPath + "/", serverUrl);
+ await fs.promises.rm(chunksDirUrl, { recursive: true, force: true });
+ } catch {}
+ }
}