diff options
Diffstat (limited to 'packages/integrations/cloudflare/src')
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); -} |