summaryrefslogtreecommitdiff
path: root/packages/integrations/netlify/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/netlify/src')
-rw-r--r--packages/integrations/netlify/src/index.ts2
-rw-r--r--packages/integrations/netlify/src/integration-functions.ts142
-rw-r--r--packages/integrations/netlify/src/integration-static.ts30
-rw-r--r--packages/integrations/netlify/src/middleware.ts75
-rw-r--r--packages/integrations/netlify/src/netlify-functions.ts225
-rw-r--r--packages/integrations/netlify/src/shared.ts114
6 files changed, 0 insertions, 588 deletions
diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts
deleted file mode 100644
index a374020f9..000000000
--- a/packages/integrations/netlify/src/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-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
deleted file mode 100644
index 33e7bade3..000000000
--- a/packages/integrations/netlify/src/integration-functions.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-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 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;
-}
-
-function netlifyFunctions({
- 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/, '');
-
- if (config.output === 'static') {
- console.warn(
- `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`
- );
- 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';
-
- 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}`;
-
- 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
- );
- }
- },
- },
- };
-}
-
-export { netlifyFunctions as default, netlifyFunctions };
diff --git a/packages/integrations/netlify/src/integration-static.ts b/packages/integrations/netlify/src/integration-static.ts
deleted file mode 100644
index af2849867..000000000
--- a/packages/integrations/netlify/src/integration-static.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-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);
- },
- },
- };
-}
diff --git a/packages/integrations/netlify/src/middleware.ts b/packages/integrations/netlify/src/middleware.ts
deleted file mode 100644
index 3c2f4f697..000000000
--- a/packages/integrations/netlify/src/middleware.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-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
-): Promise<URL> {
- 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);
-}
-
-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';
-export default async function middleware(request, context) {
- const url = new URL(request.url);
- const ctx = createContext({
- request,
- params: {}
- });
- ctx.locals = ${handlerTemplateCall};
- const next = async () => {
- request.headers.set(${JSON.stringify(ASTRO_LOCALS_HEADER)}, trySerializeLocals(ctx.locals));
- return await context.next();
- };
-
- return onRequest(ctx, next);
-}
-
-export const config = {
- path: "/*"
-}
-`;
-}
diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts
deleted file mode 100644
index 8c051d9f6..000000000
--- a/packages/integrations/netlify/src/netlify-functions.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-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;
-}
-
-function parseContentType(header?: string) {
- return header?.split(';')[0] ?? '';
-}
-
-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 };
-};
-
-/*
- From: https://github.com/nfriedly/set-cookie-parser/blob/5cae030d8ef0f80eec58459e3583d43a07b984cb/lib/set-cookie.js#L144
- Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
- that are within a single set-cookie field-value, such as in the Expires portion.
- This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
- Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
- React Native's fetch does this for *every* header, including set-cookie.
- Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
- 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;
-}
diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts
deleted file mode 100644
index 175b9d04f..000000000
--- a/packages/integrations/netlify/src/shared.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-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: [],
- env: Deno.env.toObject(),
-};`;
-
-export interface NetlifyEdgeFunctionsOptions {
- dist?: URL;
-}
-
-export interface NetlifyEdgeFunctionManifestFunctionPath {
- function: string;
- path: string;
-}
-
-export interface NetlifyEdgeFunctionManifestFunctionPattern {
- function: string;
- pattern: string;
-}
-
-export type NetlifyEdgeFunctionManifestFunction =
- | NetlifyEdgeFunctionManifestFunctionPath
- | NetlifyEdgeFunctionManifestFunctionPattern;
-
-export interface NetlifyEdgeFunctionManifest {
- functions: NetlifyEdgeFunctionManifestFunction[];
- version: 1;
-}
-
-export async function createRedirects(
- config: AstroConfig,
- routeToDynamicTargetMap: Map<RouteData, string>,
- dir: URL
-) {
- const _redirectsURL = new URL('./_redirects', dir);
-
- 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');
-}
-
-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 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');
-}
-
-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 {}
- }
-}