summaryrefslogtreecommitdiff
path: root/packages/integrations/cloudflare/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/cloudflare/src')
-rw-r--r--packages/integrations/cloudflare/src/entrypoints/server.advanced.ts79
-rw-r--r--packages/integrations/cloudflare/src/entrypoints/server.directory.ts72
-rw-r--r--packages/integrations/cloudflare/src/getAdapter.ts40
-rw-r--r--packages/integrations/cloudflare/src/index.ts615
-rw-r--r--packages/integrations/cloudflare/src/util.ts19
-rw-r--r--packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts26
-rw-r--r--packages/integrations/cloudflare/src/utils/getCFObject.ts70
-rw-r--r--packages/integrations/cloudflare/src/utils/parser.ts191
-rw-r--r--packages/integrations/cloudflare/src/utils/prependForwardSlash.ts3
-rw-r--r--packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts29
-rw-r--r--packages/integrations/cloudflare/src/utils/wasm-module-loader.ts119
11 files changed, 0 insertions, 1263 deletions
diff --git a/packages/integrations/cloudflare/src/entrypoints/server.advanced.ts b/packages/integrations/cloudflare/src/entrypoints/server.advanced.ts
deleted file mode 100644
index c7c8e8466..000000000
--- a/packages/integrations/cloudflare/src/entrypoints/server.advanced.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import type {
- Request as CFRequest,
- CacheStorage,
- ExecutionContext,
-} from '@cloudflare/workers-types';
-import type { SSRManifest } from 'astro';
-import { App } from 'astro/app';
-import { getProcessEnvProxy, isNode } from '../util.js';
-
-if (!isNode) {
- process.env = getProcessEnvProxy();
-}
-
-type Env = {
- ASSETS: { fetch: (req: Request) => Promise<Response> };
-};
-
-export interface AdvancedRuntime<T extends object = object> {
- runtime: {
- waitUntil: (promise: Promise<any>) => void;
- env: Env & T;
- cf: CFRequest['cf'];
- caches: CacheStorage;
- };
-}
-
-export function createExports(manifest: SSRManifest) {
- const app = new App(manifest);
-
- const fetch = async (request: Request & CFRequest, env: Env, context: ExecutionContext) => {
- // TODO: remove this any cast in the future
- // REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
- process.env = env as any;
-
- const { pathname } = new URL(request.url);
-
- // static assets fallback, in case default _routes.json is not used
- if (manifest.assets.has(pathname)) {
- return env.ASSETS.fetch(request);
- }
-
- let routeData = app.match(request, { matchNotFound: true });
- if (routeData) {
- Reflect.set(
- request,
- Symbol.for('astro.clientAddress'),
- request.headers.get('cf-connecting-ip')
- );
-
- const locals: AdvancedRuntime = {
- runtime: {
- waitUntil: (promise: Promise<any>) => {
- context.waitUntil(promise);
- },
- env: env,
- cf: request.cf,
- caches: caches as unknown as CacheStorage,
- },
- };
-
- let response = await app.render(request, routeData, locals);
-
- if (app.setCookieHeaders) {
- for (const setCookieHeader of app.setCookieHeaders(response)) {
- response.headers.append('Set-Cookie', setCookieHeader);
- }
- }
-
- return response;
- }
-
- return new Response(null, {
- status: 404,
- statusText: 'Not found',
- });
- };
-
- return { default: { fetch } };
-}
diff --git a/packages/integrations/cloudflare/src/entrypoints/server.directory.ts b/packages/integrations/cloudflare/src/entrypoints/server.directory.ts
deleted file mode 100644
index 6f573fe71..000000000
--- a/packages/integrations/cloudflare/src/entrypoints/server.directory.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { Request as CFRequest, CacheStorage, EventContext } from '@cloudflare/workers-types';
-import type { SSRManifest } from 'astro';
-import { App } from 'astro/app';
-import { getProcessEnvProxy, isNode } from '../util.js';
-
-if (!isNode) {
- process.env = getProcessEnvProxy();
-}
-export interface DirectoryRuntime<T extends object = object> {
- runtime: {
- waitUntil: (promise: Promise<any>) => void;
- env: EventContext<unknown, string, unknown>['env'] & T;
- cf: CFRequest['cf'];
- caches: CacheStorage;
- };
-}
-
-export function createExports(manifest: SSRManifest) {
- const app = new App(manifest);
-
- const onRequest = async (context: EventContext<unknown, string, unknown>) => {
- const request = context.request as CFRequest & Request;
- const { env } = context;
-
- // TODO: remove this any cast in the future
- // REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
- process.env = env as any;
-
- const { pathname } = new URL(request.url);
- // static assets fallback, in case default _routes.json is not used
- if (manifest.assets.has(pathname)) {
- return env.ASSETS.fetch(request);
- }
-
- let routeData = app.match(request, { matchNotFound: true });
- if (routeData) {
- Reflect.set(
- request,
- Symbol.for('astro.clientAddress'),
- request.headers.get('cf-connecting-ip')
- );
-
- const locals: DirectoryRuntime = {
- runtime: {
- waitUntil: (promise: Promise<any>) => {
- context.waitUntil(promise);
- },
- env: context.env,
- cf: request.cf,
- caches: caches as unknown as CacheStorage,
- },
- };
-
- let response = await app.render(request, routeData, locals);
-
- if (app.setCookieHeaders) {
- for (const setCookieHeader of app.setCookieHeaders(response)) {
- response.headers.append('Set-Cookie', setCookieHeader);
- }
- }
-
- return response;
- }
-
- return new Response(null, {
- status: 404,
- statusText: 'Not found',
- });
- };
-
- return { onRequest, manifest };
-}
diff --git a/packages/integrations/cloudflare/src/getAdapter.ts b/packages/integrations/cloudflare/src/getAdapter.ts
deleted file mode 100644
index 0cc1263a1..000000000
--- a/packages/integrations/cloudflare/src/getAdapter.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { AstroAdapter, AstroFeatureMap } from 'astro';
-
-export function getAdapter({
- isModeDirectory,
- functionPerRoute,
-}: {
- isModeDirectory: boolean;
- functionPerRoute: boolean;
-}): AstroAdapter {
- const astroFeatures = {
- hybridOutput: 'stable',
- staticOutput: 'unsupported',
- serverOutput: 'stable',
- assets: {
- supportKind: 'stable',
- isSharpCompatible: false,
- isSquooshCompatible: false,
- },
- } satisfies AstroFeatureMap;
-
- if (isModeDirectory) {
- return {
- name: '@astrojs/cloudflare',
- serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.directory.js',
- exports: ['onRequest', 'manifest'],
- adapterFeatures: {
- functionPerRoute,
- edgeMiddleware: false,
- },
- supportedAstroFeatures: astroFeatures,
- };
- }
-
- return {
- name: '@astrojs/cloudflare',
- serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.advanced.js',
- exports: ['default'],
- supportedAstroFeatures: astroFeatures,
- };
-}
diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts
deleted file mode 100644
index 36da696bb..000000000
--- a/packages/integrations/cloudflare/src/index.ts
+++ /dev/null
@@ -1,615 +0,0 @@
-import type { AstroConfig, AstroIntegration, RouteData } from 'astro';
-
-import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
-import { AstroError } from 'astro/errors';
-import esbuild from 'esbuild';
-import { Miniflare } from 'miniflare';
-import * as fs from 'node:fs';
-import * as os from 'node:os';
-import { dirname, relative, sep } from 'node:path';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import glob from 'tiny-glob';
-import { getAdapter } from './getAdapter.js';
-import { deduplicatePatterns } from './utils/deduplicatePatterns.js';
-import { getCFObject } from './utils/getCFObject.js';
-import {
- getD1Bindings,
- getDOBindings,
- getEnvVars,
- getKVBindings,
- getR2Bindings,
-} from './utils/parser.js';
-import { prependForwardSlash } from './utils/prependForwardSlash.js';
-import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js';
-import { wasmModuleLoader } from './utils/wasm-module-loader.js';
-
-export type { AdvancedRuntime } from './entrypoints/server.advanced.js';
-export type { DirectoryRuntime } from './entrypoints/server.directory.js';
-
-type Options = {
- mode?: 'directory' | 'advanced';
- functionPerRoute?: boolean;
- /** Configure automatic `routes.json` generation */
- routes?: {
- /** Strategy for generating `include` and `exclude` patterns
- * - `auto`: Will use the strategy that generates the least amount of entries.
- * - `include`: For each page or endpoint in your application that is not prerendered, an entry in the `include` array will be generated. For each page that is prerendered and whoose path is matched by an `include` entry, an entry in the `exclude` array will be generated.
- * - `exclude`: One `"/*"` entry in the `include` array will be generated. For each page that is prerendered, an entry in the `exclude` array will be generated.
- * */
- strategy?: 'auto' | 'include' | 'exclude';
- /** Additional `include` patterns */
- include?: string[];
- /** Additional `exclude` patterns */
- exclude?: string[];
- };
- /**
- * 'off': current behaviour (wrangler is needed)
- * 'local': use a static req.cf object, and env vars defined in wrangler.toml & .dev.vars (astro dev is enough)
- * 'remote': use a dynamic real-live req.cf object, and env vars defined in wrangler.toml & .dev.vars (astro dev is enough)
- */
- runtime?: 'off' | 'local' | 'remote';
- wasmModuleImports?: boolean;
-};
-
-interface BuildConfig {
- server: URL;
- client: URL;
- assets: string;
- serverEntry: string;
- split?: boolean;
-}
-
-export default function createIntegration(args?: Options): AstroIntegration {
- let _config: AstroConfig;
- let _buildConfig: BuildConfig;
- let _mf: Miniflare;
- let _entryPoints = new Map<RouteData, URL>();
-
- const SERVER_BUILD_FOLDER = '/$server_build/';
-
- const isModeDirectory = args?.mode === 'directory';
- const functionPerRoute = args?.functionPerRoute ?? false;
- const runtimeMode = args?.runtime ?? 'off';
-
- return {
- name: '@astrojs/cloudflare',
- hooks: {
- 'astro:config:setup': ({ config, updateConfig }) => {
- updateConfig({
- build: {
- client: new URL(`.${config.base}`, config.outDir),
- server: new URL(`.${SERVER_BUILD_FOLDER}`, config.outDir),
- serverEntry: '_worker.mjs',
- redirects: false,
- },
- vite: {
- // load .wasm files as WebAssembly modules
- plugins: [
- wasmModuleLoader({
- disabled: !args?.wasmModuleImports,
- assetsDirectory: config.build.assets,
- }),
- ],
- },
- });
- },
- 'astro:config:done': ({ setAdapter, config }) => {
- setAdapter(getAdapter({ isModeDirectory, functionPerRoute }));
- _config = config;
- _buildConfig = config.build;
-
- if (_config.output === 'static') {
- throw new AstroError(
- '[@astrojs/cloudflare] `output: "server"` or `output: "hybrid"` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare.'
- );
- }
-
- if (_config.base === SERVER_BUILD_FOLDER) {
- throw new AstroError(
- '[@astrojs/cloudflare] `base: "${SERVER_BUILD_FOLDER}"` is not allowed. Please change your `base` config to something else.'
- );
- }
- },
- 'astro:server:setup': ({ server }) => {
- if (runtimeMode !== 'off') {
- server.middlewares.use(async function middleware(req, res, next) {
- try {
- const cf = await getCFObject(runtimeMode);
- const vars = await getEnvVars();
- const D1Bindings = await getD1Bindings();
- const R2Bindings = await getR2Bindings();
- const KVBindings = await getKVBindings();
- const DOBindings = await getDOBindings();
- let bindingsEnv = new Object({});
-
- // fix for the error "kj/filesystem-disk-unix.c++:1709: warning: PWD environment variable doesn't match current directory."
- // note: This mismatch might be primarily due to the test runner.
- const originalPWD = process.env.PWD;
- process.env.PWD = process.cwd();
-
- _mf = new Miniflare({
- modules: true,
- script: '',
- cache: true,
- cachePersist: true,
- cacheWarnUsage: true,
- d1Databases: D1Bindings,
- d1Persist: true,
- r2Buckets: R2Bindings,
- r2Persist: true,
- kvNamespaces: KVBindings,
- kvPersist: true,
- durableObjects: DOBindings,
- durableObjectsPersist: true,
- });
- await _mf.ready;
-
- for (const D1Binding of D1Bindings) {
- const db = await _mf.getD1Database(D1Binding);
- Reflect.set(bindingsEnv, D1Binding, db);
- }
- for (const R2Binding of R2Bindings) {
- const bucket = await _mf.getR2Bucket(R2Binding);
- Reflect.set(bindingsEnv, R2Binding, bucket);
- }
- for (const KVBinding of KVBindings) {
- const namespace = await _mf.getKVNamespace(KVBinding);
- Reflect.set(bindingsEnv, KVBinding, namespace);
- }
- for (const key in DOBindings) {
- if (Object.prototype.hasOwnProperty.call(DOBindings, key)) {
- const DO = await _mf.getDurableObjectNamespace(key);
- Reflect.set(bindingsEnv, key, DO);
- }
- }
- const mfCache = await _mf.getCaches();
-
- process.env.PWD = originalPWD;
- const clientLocalsSymbol = Symbol.for('astro.locals');
- Reflect.set(req, clientLocalsSymbol, {
- runtime: {
- env: {
- // default binding for static assets will be dynamic once we support mocking of bindings
- ASSETS: {},
- // this is just a VAR for CF to change build behavior, on dev it should be 0
- CF_PAGES: '0',
- // will be fetched from git dynamically once we support mocking of bindings
- CF_PAGES_BRANCH: 'TBA',
- // will be fetched from git dynamically once we support mocking of bindings
- CF_PAGES_COMMIT_SHA: 'TBA',
- CF_PAGES_URL: `http://${req.headers.host}`,
- ...bindingsEnv,
- ...vars,
- },
- cf: cf,
- waitUntil: (_promise: Promise<any>) => {
- return;
- },
- caches: mfCache,
- },
- });
- next();
- } catch {
- next();
- }
- });
- }
- },
- 'astro:server:done': async ({ logger }) => {
- if (_mf) {
- logger.info('Cleaning up the Miniflare instance, and shutting down the workerd server.');
- await _mf.dispose();
- }
- },
- 'astro:build:setup': ({ vite, target }) => {
- if (target === 'server') {
- vite.resolve ||= {};
- vite.resolve.alias ||= {};
-
- const aliases = [{ find: 'react-dom/server', replacement: 'react-dom/server.browser' }];
-
- if (Array.isArray(vite.resolve.alias)) {
- vite.resolve.alias = [...vite.resolve.alias, ...aliases];
- } else {
- for (const alias of aliases) {
- (vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
- }
- }
- vite.ssr ||= {};
- vite.ssr.target = 'webworker';
-
- // Cloudflare env is only available per request. This isn't feasible for code that access env vars
- // in a global way, so we shim their access as `process.env.*`. We will populate `process.env` later
- // in its fetch handler.
- vite.define = {
- 'process.env': 'process.env',
- ...vite.define,
- };
- }
- },
- 'astro:build:ssr': ({ entryPoints }) => {
- _entryPoints = entryPoints;
- },
- 'astro:build:done': async ({ pages, routes, dir }) => {
- const functionsUrl = new URL('functions/', _config.root);
- const assetsUrl = new URL(_buildConfig.assets, _buildConfig.client);
-
- if (isModeDirectory) {
- await fs.promises.mkdir(functionsUrl, { recursive: true });
- }
-
- // TODO: remove _buildConfig.split in Astro 4.0
- if (isModeDirectory && (_buildConfig.split || functionPerRoute)) {
- const entryPointsURL = [..._entryPoints.values()];
- const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
- const outputUrl = new URL('$astro', _buildConfig.server);
- const outputDir = fileURLToPath(outputUrl);
- //
- // Sadly, when wasmModuleImports is enabled, this needs to build esbuild for each depth of routes/entrypoints
- // independently so that relative import paths to the assets are the correct depth of '../' traversals
- // This is inefficient, so wasmModuleImports is opt-in. This could potentially be improved in the future by
- // taking advantage of the esbuild "onEnd" hook to rewrite import code per entry point relative to where the final
- // destination of the entrypoint is
- const entryPathsGroupedByDepth = !args.wasmModuleImports
- ? [entryPaths]
- : entryPaths
- .reduce((sum, thisPath) => {
- const depthFromRoot = thisPath.split(sep).length;
- sum.set(depthFromRoot, (sum.get(depthFromRoot) || []).concat(thisPath));
- return sum;
- }, new Map<number, string[]>())
- .values();
-
- for (const pathsGroup of entryPathsGroupedByDepth) {
- // for some reason this exports to "entry.pages" on windows instead of "pages" on unix environments.
- // This deduces the name of the "pages" build directory
- const pagesDirname = relative(fileURLToPath(_buildConfig.server), pathsGroup[0]).split(
- sep
- )[0];
- const absolutePagesDirname = fileURLToPath(new URL(pagesDirname, _buildConfig.server));
- const urlWithinFunctions = new URL(
- relative(absolutePagesDirname, pathsGroup[0]),
- functionsUrl
- );
- const relativePathToAssets = relative(
- dirname(fileURLToPath(urlWithinFunctions)),
- fileURLToPath(assetsUrl)
- );
- await esbuild.build({
- target: 'es2022',
- platform: 'browser',
- conditions: ['workerd', 'worker', 'browser'],
- external: [
- 'node:assert',
- 'node:async_hooks',
- 'node:buffer',
- 'node:crypto',
- 'node:diagnostics_channel',
- 'node:events',
- 'node:path',
- 'node:process',
- 'node:stream',
- 'node:string_decoder',
- 'node:util',
- 'cloudflare:*',
- ],
- entryPoints: pathsGroup,
- outbase: absolutePagesDirname,
- outdir: outputDir,
- allowOverwrite: true,
- format: 'esm',
- bundle: true,
- minify: _config.vite?.build?.minify !== false,
- banner: {
- js: `globalThis.process = {
- argv: [],
- env: {},
- };`,
- },
- logOverride: {
- 'ignored-bare-import': 'silent',
- },
- plugins: !args?.wasmModuleImports
- ? []
- : [rewriteWasmImportPath({ relativePathToAssets })],
- });
- }
-
- const outputFiles: Array<string> = await glob(`**/*`, {
- cwd: outputDir,
- filesOnly: true,
- });
-
- // move the files into the functions folder
- // & make sure the file names match Cloudflare syntax for routing
- for (const outputFile of outputFiles) {
- const path = outputFile.split(sep);
-
- const finalSegments = path.map((segment) =>
- segment
- .replace(/(\_)(\w+)(\_)/g, (_, __, prop) => {
- return `[${prop}]`;
- })
- .replace(/(\_\-\-\-)(\w+)(\_)/g, (_, __, prop) => {
- return `[[${prop}]]`;
- })
- );
-
- finalSegments[finalSegments.length - 1] = finalSegments[finalSegments.length - 1]
- .replace('entry.', '')
- .replace(/(.*)\.(\w+)\.(\w+)$/g, (_, fileName, __, newExt) => {
- return `${fileName}.${newExt}`;
- });
-
- const finalDirPath = finalSegments.slice(0, -1).join(sep);
- const finalPath = finalSegments.join(sep);
-
- const newDirUrl = new URL(finalDirPath, functionsUrl);
- await fs.promises.mkdir(newDirUrl, { recursive: true });
-
- const oldFileUrl = new URL(`$astro/${outputFile}`, outputUrl);
- const newFileUrl = new URL(finalPath, functionsUrl);
- await fs.promises.rename(oldFileUrl, newFileUrl);
- }
- } else {
- const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
- const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
- const buildPath = fileURLToPath(entryUrl);
- // A URL for the final build path after renaming
- const finalBuildUrl = pathToFileURL(buildPath.replace(/\.mjs$/, '.js'));
-
- await esbuild.build({
- target: 'es2022',
- platform: 'browser',
- conditions: ['workerd', 'worker', 'browser'],
- external: [
- 'node:assert',
- 'node:async_hooks',
- 'node:buffer',
- 'node:crypto',
- 'node:diagnostics_channel',
- 'node:events',
- 'node:path',
- 'node:process',
- 'node:stream',
- 'node:string_decoder',
- 'node:util',
- 'cloudflare:*',
- ],
- entryPoints: [entryPath],
- outfile: buildPath,
- allowOverwrite: true,
- format: 'esm',
- bundle: true,
- minify: _config.vite?.build?.minify !== false,
- banner: {
- js: `globalThis.process = {
- argv: [],
- env: {},
- };`,
- },
- logOverride: {
- 'ignored-bare-import': 'silent',
- },
- plugins: !args?.wasmModuleImports
- ? []
- : [
- rewriteWasmImportPath({
- relativePathToAssets: isModeDirectory
- ? relative(fileURLToPath(functionsUrl), fileURLToPath(assetsUrl))
- : relative(fileURLToPath(_buildConfig.client), fileURLToPath(assetsUrl)),
- }),
- ],
- });
-
- // Rename to worker.js
- await fs.promises.rename(buildPath, finalBuildUrl);
-
- if (isModeDirectory) {
- const directoryUrl = new URL('[[path]].js', functionsUrl);
- await fs.promises.rename(finalBuildUrl, directoryUrl);
- }
- }
-
- // throw the server folder in the bin
- const serverUrl = new URL(_buildConfig.server);
- await fs.promises.rm(serverUrl, { recursive: true, force: true });
-
- // move cloudflare specific files to the root
- const cloudflareSpecialFiles = ['_headers', '_redirects', '_routes.json'];
-
- if (_config.base !== '/') {
- for (const file of cloudflareSpecialFiles) {
- try {
- await fs.promises.rename(
- new URL(file, _buildConfig.client),
- new URL(file, _config.outDir)
- );
- } catch (e) {
- // ignore
- }
- }
- }
-
- // Add also the worker file so it's excluded from the _routes.json generation
- if (!isModeDirectory) {
- cloudflareSpecialFiles.push('_worker.js');
- }
-
- const routesExists = await fs.promises
- .stat(new URL('./_routes.json', _config.outDir))
- .then((stat) => stat.isFile())
- .catch(() => false);
-
- // this creates a _routes.json, in case there is none present to enable
- // cloudflare to handle static files and support _redirects configuration
- if (!routesExists) {
- /**
- * These route types are candiates for being part of the `_routes.json` `include` array.
- */
- const potentialFunctionRouteTypes = ['endpoint', 'page'];
-
- const functionEndpoints = routes
- // Certain route types, when their prerender option is set to false, run on the server as function invocations
- .filter((route) => potentialFunctionRouteTypes.includes(route.type) && !route.prerender)
- .map((route) => {
- const includePattern =
- '/' +
- route.segments
- .flat()
- .map((segment) => (segment.dynamic ? '*' : segment.content))
- .join('/');
-
- const regexp = new RegExp(
- '^\\/' +
- route.segments
- .flat()
- .map((segment) => (segment.dynamic ? '(.*)' : segment.content))
- .join('\\/') +
- '$'
- );
-
- return {
- includePattern,
- regexp,
- };
- });
-
- const staticPathList: Array<string> = (
- await glob(`${fileURLToPath(_buildConfig.client)}/**/*`, {
- cwd: fileURLToPath(_config.outDir),
- filesOnly: true,
- dot: true,
- })
- )
- .filter((file: string) => cloudflareSpecialFiles.indexOf(file) < 0)
- .map((file: string) => `/${file.replace(/\\/g, '/')}`);
-
- for (let page of pages) {
- let pagePath = prependForwardSlash(page.pathname);
- if (_config.base !== '/') {
- const base = _config.base.endsWith('/') ? _config.base.slice(0, -1) : _config.base;
- pagePath = `${base}${pagePath}`;
- }
- staticPathList.push(pagePath);
- }
-
- const redirectsExists = await fs.promises
- .stat(new URL('./_redirects', _config.outDir))
- .then((stat) => stat.isFile())
- .catch(() => false);
-
- // convert all redirect source paths into a list of routes
- // and add them to the static path
- if (redirectsExists) {
- const redirects = (
- await fs.promises.readFile(new URL('./_redirects', _config.outDir), 'utf-8')
- )
- .split(os.EOL)
- .map((line) => {
- const parts = line.split(' ');
- if (parts.length < 2) {
- return null;
- } else {
- // convert /products/:id to /products/*
- return (
- parts[0]
- .replace(/\/:.*?(?=\/|$)/g, '/*')
- // remove query params as they are not supported by cloudflare
- .replace(/\?.*$/, '')
- );
- }
- })
- .filter(
- (line, index, arr) => line !== null && arr.indexOf(line) === index
- ) as string[];
-
- if (redirects.length > 0) {
- staticPathList.push(...redirects);
- }
- }
-
- const redirectRoutes: [RouteData, string][] = routes
- .filter((r) => r.type === 'redirect')
- .map((r) => {
- return [r, ''];
- });
- const trueRedirects = createRedirectsFromAstroRoutes({
- config: _config,
- routeToDynamicTargetMap: new Map(Array.from(redirectRoutes)),
- dir,
- });
- if (!trueRedirects.empty()) {
- await fs.promises.appendFile(
- new URL('./_redirects', _config.outDir),
- trueRedirects.print()
- );
- }
-
- staticPathList.push(...routes.filter((r) => r.type === 'redirect').map((r) => r.route));
-
- const strategy = args?.routes?.strategy ?? 'auto';
-
- // Strategy `include`: include all function endpoints, and then exclude static paths that would be matched by an include pattern
- const includeStrategy =
- strategy === 'exclude'
- ? undefined
- : {
- include: deduplicatePatterns(
- functionEndpoints
- .map((endpoint) => endpoint.includePattern)
- .concat(args?.routes?.include ?? [])
- ),
- exclude: deduplicatePatterns(
- staticPathList
- .filter((file: string) =>
- functionEndpoints.some((endpoint) => endpoint.regexp.test(file))
- )
- .concat(args?.routes?.exclude ?? [])
- ),
- };
-
- // Cloudflare requires at least one include pattern:
- // https://developers.cloudflare.com/pages/platform/functions/routing/#limits
- // So we add a pattern that we immediately exclude again
- if (includeStrategy?.include.length === 0) {
- includeStrategy.include = ['/'];
- includeStrategy.exclude = ['/'];
- }
-
- // Strategy `exclude`: include everything, and then exclude all static paths
- const excludeStrategy =
- strategy === 'include'
- ? undefined
- : {
- include: ['/*'],
- exclude: deduplicatePatterns(staticPathList.concat(args?.routes?.exclude ?? [])),
- };
-
- const includeStrategyLength = includeStrategy
- ? includeStrategy.include.length + includeStrategy.exclude.length
- : Infinity;
-
- const excludeStrategyLength = excludeStrategy
- ? excludeStrategy.include.length + excludeStrategy.exclude.length
- : Infinity;
-
- const winningStrategy =
- includeStrategyLength <= excludeStrategyLength ? includeStrategy : excludeStrategy;
-
- await fs.promises.writeFile(
- new URL('./_routes.json', _config.outDir),
- JSON.stringify(
- {
- version: 1,
- ...winningStrategy,
- },
- null,
- 2
- )
- );
- }
- },
- },
- };
-}
diff --git a/packages/integrations/cloudflare/src/util.ts b/packages/integrations/cloudflare/src/util.ts
deleted file mode 100644
index 120cb7334..000000000
--- a/packages/integrations/cloudflare/src/util.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export const isNode =
- typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]';
-
-export function getProcessEnvProxy() {
- return new Proxy(
- {},
- {
- get: (target, prop) => {
- console.warn(
- // NOTE: \0 prevents Vite replacement
- `Unable to access \`import.meta\0.env.${prop.toString()}\` on initialization ` +
- `as the Cloudflare platform only provides the environment variables per request. ` +
- `Please move the environment variable access inside a function ` +
- `that's only called after a request has been received.`
- );
- },
- }
- );
-}
diff --git a/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts b/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts
deleted file mode 100644
index 37743fe55..000000000
--- a/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Remove duplicates and redundant patterns from an `include` or `exclude` list.
- * Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries.
- * E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']`
- * @param patterns a list of `include` or `exclude` patterns
- * @returns a deduplicated list of patterns
- */
-export function deduplicatePatterns(patterns: string[]) {
- const openPatterns: RegExp[] = [];
-
- // A value in the set may only occur once; it is unique in the set's collection.
- // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
- return [...new Set(patterns)]
- .sort((a, b) => a.length - b.length)
- .filter((pattern) => {
- if (openPatterns.some((p) => p.test(pattern))) {
- return false;
- }
-
- if (pattern.endsWith('*')) {
- openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`));
- }
-
- return true;
- });
-}
diff --git a/packages/integrations/cloudflare/src/utils/getCFObject.ts b/packages/integrations/cloudflare/src/utils/getCFObject.ts
deleted file mode 100644
index 7a4cd8a0c..000000000
--- a/packages/integrations/cloudflare/src/utils/getCFObject.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
-
-export async function getCFObject(
- runtimeMode: string
-): Promise<IncomingRequestCfProperties | void> {
- const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json';
- const CF_FALLBACK: IncomingRequestCfProperties = {
- asOrganization: '',
- asn: 395747,
- colo: 'DFW',
- city: 'Austin',
- region: 'Texas',
- regionCode: 'TX',
- metroCode: '635',
- postalCode: '78701',
- country: 'US',
- continent: 'NA',
- timezone: 'America/Chicago',
- latitude: '30.27130',
- longitude: '-97.74260',
- clientTcpRtt: 0,
- httpProtocol: 'HTTP/1.1',
- requestPriority: 'weight=192;exclusive=0',
- tlsCipher: 'AEAD-AES128-GCM-SHA256',
- tlsVersion: 'TLSv1.3',
- tlsClientAuth: {
- certPresented: '0',
- certVerified: 'NONE',
- certRevoked: '0',
- certIssuerDN: '',
- certSubjectDN: '',
- certIssuerDNRFC2253: '',
- certSubjectDNRFC2253: '',
- certIssuerDNLegacy: '',
- certSubjectDNLegacy: '',
- certSerial: '',
- certIssuerSerial: '',
- certSKI: '',
- certIssuerSKI: '',
- certFingerprintSHA1: '',
- certFingerprintSHA256: '',
- certNotBefore: '',
- certNotAfter: '',
- },
- edgeRequestKeepAliveStatus: 0,
- hostMetadata: undefined,
- clientTrustScore: 99,
- botManagement: {
- corporateProxy: false,
- verifiedBot: false,
- ja3Hash: '25b4882c2bcb50cd6b469ff28c596742',
- staticResource: false,
- detectionIds: [],
- score: 99,
- },
- };
-
- if (runtimeMode === 'local') {
- return CF_FALLBACK;
- } else if (runtimeMode === 'remote') {
- try {
- const res = await fetch(CF_ENDPOINT);
- const cfText = await res.text();
- const storedCf = JSON.parse(cfText);
- return storedCf;
- } catch (e: any) {
- return CF_FALLBACK;
- }
- }
-}
diff --git a/packages/integrations/cloudflare/src/utils/parser.ts b/packages/integrations/cloudflare/src/utils/parser.ts
deleted file mode 100644
index 4045a0e72..000000000
--- a/packages/integrations/cloudflare/src/utils/parser.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-/**
- * This file is a derivative work of wrangler by Cloudflare
- * An upstream request for exposing this API was made here:
- * https://github.com/cloudflare/workers-sdk/issues/3897
- *
- * Until further notice, we will be using this file as a workaround
- * TODO: Tackle this file, once their is an decision on the upstream request
- */
-
-import type {} from '@cloudflare/workers-types/experimental';
-import TOML from '@iarna/toml';
-import dotenv from 'dotenv';
-import { findUpSync } from 'find-up';
-import * as fs from 'node:fs';
-import { dirname, resolve } from 'node:path';
-let _wrangler: any;
-
-function findWranglerToml(
- referencePath: string = process.cwd(),
- preferJson = false
-): string | undefined {
- if (preferJson) {
- return (
- findUpSync(`wrangler.json`, { cwd: referencePath }) ??
- findUpSync(`wrangler.toml`, { cwd: referencePath })
- );
- }
- return findUpSync(`wrangler.toml`, { cwd: referencePath });
-}
-type File = {
- file?: string;
- fileText?: string;
-};
-type Location = File & {
- line: number;
- column: number;
- length?: number;
- lineText?: string;
- suggestion?: string;
-};
-type Message = {
- text: string;
- location?: Location;
- notes?: Message[];
- kind?: 'warning' | 'error';
-};
-class ParseError extends Error implements Message {
- readonly text: string;
- readonly notes: Message[];
- readonly location?: Location;
- readonly kind: 'warning' | 'error';
-
- constructor({ text, notes, location, kind }: Message) {
- super(text);
- this.name = this.constructor.name;
- this.text = text;
- this.notes = notes ?? [];
- this.location = location;
- this.kind = kind ?? 'error';
- }
-}
-const TOML_ERROR_NAME = 'TomlError';
-const TOML_ERROR_SUFFIX = ' at row ';
-type TomlError = Error & {
- line: number;
- col: number;
-};
-function parseTOML(input: string, file?: string): TOML.JsonMap | never {
- try {
- // Normalize CRLF to LF to avoid hitting https://github.com/iarna/iarna-toml/issues/33.
- const normalizedInput = input.replace(/\r\n/g, '\n');
- return TOML.parse(normalizedInput);
- } catch (err) {
- const { name, message, line, col } = err as TomlError;
- if (name !== TOML_ERROR_NAME) {
- throw err;
- }
- const text = message.substring(0, message.lastIndexOf(TOML_ERROR_SUFFIX));
- const lineText = input.split('\n')[line];
- const location = {
- lineText,
- line: line + 1,
- column: col - 1,
- file,
- fileText: input,
- };
- throw new ParseError({ text, location });
- }
-}
-
-export interface DotEnv {
- path: string;
- parsed: dotenv.DotenvParseOutput;
-}
-function tryLoadDotEnv(path: string): DotEnv | undefined {
- try {
- const parsed = dotenv.parse(fs.readFileSync(path));
- return { path, parsed };
- } catch (e) {
- // logger.debug(`Failed to load .env file "${path}":`, e);
- }
-}
-/**
- * Loads a dotenv file from <path>, preferring to read <path>.<environment> if
- * <environment> is defined and that file exists.
- */
-
-export function loadDotEnv(path: string): DotEnv | undefined {
- return tryLoadDotEnv(path);
-}
-function getVarsForDev(config: any, configPath: string | undefined): any {
- const configDir = resolve(dirname(configPath ?? '.'));
- const devVarsPath = resolve(configDir, '.dev.vars');
- const loaded = loadDotEnv(devVarsPath);
- if (loaded !== undefined) {
- return {
- ...config.vars,
- ...loaded.parsed,
- };
- } else {
- return config.vars;
- }
-}
-
-function parseConfig() {
- if (_wrangler) return _wrangler;
- let rawConfig;
- const configPath = findWranglerToml(process.cwd(), false); // false = args.experimentalJsonConfig
- if (!configPath) {
- throw new Error('Could not find wrangler.toml');
- }
- // Load the configuration from disk if available
- if (configPath?.endsWith('toml')) {
- rawConfig = parseTOML(fs.readFileSync(configPath).toString(), configPath);
- }
- _wrangler = { rawConfig, configPath };
- return { rawConfig, configPath };
-}
-
-export async function getEnvVars() {
- const { rawConfig, configPath } = parseConfig();
- const vars = getVarsForDev(rawConfig, configPath);
- return vars;
-}
-
-export async function getD1Bindings() {
- const { rawConfig } = parseConfig();
- if (!rawConfig) return [];
- if (!rawConfig?.d1_databases) return [];
- const bindings = (rawConfig?.d1_databases as []).map(
- (binding: { binding: string }) => binding.binding
- );
- return bindings;
-}
-
-export async function getR2Bindings() {
- const { rawConfig } = parseConfig();
- if (!rawConfig) return [];
- if (!rawConfig?.r2_buckets) return [];
- const bindings = (rawConfig?.r2_buckets as []).map(
- (binding: { binding: string }) => binding.binding
- );
- return bindings;
-}
-
-export async function getKVBindings() {
- const { rawConfig } = parseConfig();
- if (!rawConfig) return [];
- if (!rawConfig?.kv_namespaces) return [];
- const bindings = (rawConfig?.kv_namespaces as []).map(
- (binding: { binding: string }) => binding.binding
- );
- return bindings;
-}
-
-export function getDOBindings(): Record<
- string,
- { scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
-> {
- const { rawConfig } = parseConfig();
- if (!rawConfig) return {};
- if (!rawConfig?.durable_objects) return {};
- const output = new Object({}) as Record<
- string,
- { scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
- >;
- for (const binding of rawConfig?.durable_objects.bindings) {
- Reflect.set(output, binding.name, { className: binding.class_name });
- }
- return output;
-}
diff --git a/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts b/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts
deleted file mode 100644
index b66b588f3..000000000
--- a/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function prependForwardSlash(path: string) {
- return path[0] === '/' ? path : '/' + path;
-}
diff --git a/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts b/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts
deleted file mode 100644
index ada19bb56..000000000
--- a/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import esbuild from 'esbuild';
-import { basename } from 'node:path';
-
-/**
- *
- * @param relativePathToAssets - relative path from the final location for the current esbuild output bundle, to the assets directory.
- */
-export function rewriteWasmImportPath({
- relativePathToAssets,
-}: {
- relativePathToAssets: string;
-}): esbuild.Plugin {
- return {
- name: 'wasm-loader',
- setup(build) {
- build.onResolve({ filter: /.*\.wasm.mjs$/ }, (args) => {
- const updatedPath = [
- relativePathToAssets.replaceAll('\\', '/'),
- basename(args.path).replace(/\.mjs$/, ''),
- ].join('/');
-
- return {
- path: updatedPath,
- external: true, // mark it as external in the bundle
- };
- });
- },
- };
-}
diff --git a/packages/integrations/cloudflare/src/utils/wasm-module-loader.ts b/packages/integrations/cloudflare/src/utils/wasm-module-loader.ts
deleted file mode 100644
index 7d34d48c3..000000000
--- a/packages/integrations/cloudflare/src/utils/wasm-module-loader.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as fs from 'node:fs';
-import * as path from 'node:path';
-import { type Plugin } from 'vite';
-
-/**
- * Loads '*.wasm?module' imports as WebAssembly modules, which is the only way to load WASM in cloudflare workers.
- * Current proposal for WASM modules: https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration
- * Cloudflare worker WASM from javascript support: https://developers.cloudflare.com/workers/runtime-apis/webassembly/javascript/
- * @param disabled - if true throws a helpful error message if wasm is encountered and wasm imports are not enabled,
- * otherwise it will error obscurely in the esbuild and vite builds
- * @param assetsDirectory - the folder name for the assets directory in the build directory. Usually '_astro'
- * @returns Vite plugin to load WASM tagged with '?module' as a WASM modules
- */
-export function wasmModuleLoader({
- disabled,
- assetsDirectory,
-}: {
- disabled: boolean;
- assetsDirectory: string;
-}): Plugin {
- const postfix = '.wasm?module';
- let isDev = false;
-
- return {
- name: 'vite:wasm-module-loader',
- enforce: 'pre',
- configResolved(config) {
- isDev = config.command === 'serve';
- },
- config(_, __) {
- // let vite know that file format and the magic import string is intentional, and will be handled in this plugin
- return {
- assetsInclude: ['**/*.wasm?module'],
- build: { rollupOptions: { external: /^__WASM_ASSET__.+\.wasm\.mjs$/i } },
- };
- },
-
- load(id, _) {
- if (!id.endsWith(postfix)) {
- return;
- }
- if (disabled) {
- throw new Error(
- `WASM module's cannot be loaded unless you add \`wasmModuleImports: true\` to your astro config.`
- );
- }
-
- const filePath = id.slice(0, -1 * '?module'.length);
-
- const data = fs.readFileSync(filePath);
- const base64 = data.toString('base64');
-
- const base64Module = `
-const wasmModule = new WebAssembly.Module(Uint8Array.from(atob("${base64}"), c => c.charCodeAt(0)));
-export default wasmModule
-`;
- if (isDev) {
- // no need to wire up the assets in dev mode, just rewrite
- return base64Module;
- } else {
- // just some shared ID
- let hash = hashString(base64);
- // emit the wasm binary as an asset file, to be picked up later by the esbuild bundle for the worker.
- // give it a shared deterministic name to make things easy for esbuild to switch on later
- const assetName = path.basename(filePath).split('.')[0] + '.' + hash + '.wasm';
- this.emitFile({
- type: 'asset',
- // put it explicitly in the _astro assets directory with `fileName` rather than `name` so that
- // vite doesn't give it a random id in its name. We need to be able to easily rewrite from
- // the .mjs loader and the actual wasm asset later in the ESbuild for the worker
- fileName: path.join(assetsDirectory, assetName),
- source: fs.readFileSync(filePath),
- });
-
- // however, by default, the SSG generator cannot import the .wasm as a module, so embed as a base64 string
- const chunkId = this.emitFile({
- type: 'prebuilt-chunk',
- fileName: assetName + '.mjs',
- code: base64Module,
- });
-
- return `
-import wasmModule from "__WASM_ASSET__${chunkId}.wasm.mjs";
-export default wasmModule;
- `;
- }
- },
-
- // output original wasm file relative to the chunk
- renderChunk(code, chunk, _) {
- if (isDev) return;
-
- if (!/__WASM_ASSET__/g.test(code)) return;
-
- const final = code.replaceAll(/__WASM_ASSET__([a-z\d]+).wasm.mjs/g, (s, assetId) => {
- const fileName = this.getFileName(assetId);
- const relativePath = path
- .relative(path.dirname(chunk.fileName), fileName)
- .replaceAll('\\', '/'); // fix windows paths for import
- return `./${relativePath}`;
- });
-
- return { code: final };
- },
- };
-}
-
-/**
- * Returns a deterministic 32 bit hash code from a string
- */
-function hashString(str: string): string {
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- const char = str.charCodeAt(i);
- hash = (hash << 5) - hash + char;
- hash &= hash; // Convert to 32bit integer
- }
- return new Uint32Array([hash])[0].toString(36);
-}