From 8a006f7c6b2304df5564ae9659d9991d1602cb25 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 15 Jul 2026 21:53:03 +0200 Subject: [PATCH 1/5] feat(cloudflare): add support for Cloudflare Containers with new configuration and E2E tests --- .github/workflows/local.yml | 6 ++ examples-cloudflare/common/config-e2e.ts | 33 +++++++--- .../e2e/app-router/e2e/container.test.ts | 14 +++++ .../e2e/playwright.container.config.ts | 8 +++ .../app-router/open-next.container.config.ts | 5 ++ .../e2e/app-router/package.json | 3 + .../e2e/app-router/wrangler.container.jsonc | 48 ++++++++++++++ packages/cloudflare/package.json | 1 + packages/cloudflare/src/api/config.spec.ts | 47 ++++++++++++++ packages/cloudflare/src/api/config.ts | 61 +++++++++++++++--- packages/cloudflare/src/api/container.ts | 18 ++++++ packages/cloudflare/src/cli/adapter.ts | 41 ++++++++---- .../utils/copy-package-cli-files.spec.ts | 29 +++++++++ .../cli/build/utils/copy-package-cli-files.ts | 14 ++++- .../cli/build/utils/ensure-cf-config.spec.ts | 18 ++++++ .../src/cli/build/utils/ensure-cf-config.ts | 62 ++++++++++++++++++- .../cloudflare/src/cli/templates/container.ts | 53 ++++++++++++++++ packages/core/src/build/createServerBundle.ts | 2 +- pnpm-lock.yaml | 8 +++ 19 files changed, 437 insertions(+), 34 deletions(-) create mode 100644 examples-cloudflare/e2e/app-router/e2e/container.test.ts create mode 100644 examples-cloudflare/e2e/app-router/e2e/playwright.container.config.ts create mode 100644 examples-cloudflare/e2e/app-router/open-next.container.config.ts create mode 100644 examples-cloudflare/e2e/app-router/wrangler.container.jsonc create mode 100644 packages/cloudflare/src/api/config.spec.ts create mode 100644 packages/cloudflare/src/api/container.ts create mode 100644 packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts create mode 100644 packages/cloudflare/src/cli/build/utils/ensure-cf-config.spec.ts create mode 100644 packages/cloudflare/src/cli/templates/container.ts diff --git a/.github/workflows/local.yml b/.github/workflows/local.yml index 62a97855..b26b7fb3 100644 --- a/.github/workflows/local.yml +++ b/.github/workflows/local.yml @@ -91,3 +91,9 @@ jobs: - name: Run E2E Test in Cloudflare Environment shell: bash run: pnpm turbo e2e:cf + + - name: Run Cloudflare Container E2E Test + shell: bash + run: | + pnpm --filter examples-cloudflare/e2e-app-router build:worker:container + pnpm --filter examples-cloudflare/e2e-app-router e2e:container diff --git a/examples-cloudflare/common/config-e2e.ts b/examples-cloudflare/common/config-e2e.ts index 1a02c65d..9ecc095d 100644 --- a/examples-cloudflare/common/config-e2e.ts +++ b/examples-cloudflare/common/config-e2e.ts @@ -4,9 +4,21 @@ import { getAppPort, getInspectorPort, type AppName } from "./apps"; declare const process: typeof nodeProcess; -export function configurePlaywright( - app: AppName, - { +type ConfigurePlaywrightOptions = { + /** Whether the Playwright run is executing in CI. */ + isCI?: boolean; + /** Whether the app runs in a Worker instead of through `next dev`. */ + isWorker?: boolean; + multipleBrowsers?: boolean; + parallel?: boolean; + useTurbopack?: boolean; + workerBuildScript?: string; + workerPreviewScript?: string; + testMatch?: string | string[]; +}; + +export function configurePlaywright(app: AppName, options: ConfigurePlaywrightOptions = {}) { + const { // Do we run on CI? isCI = Boolean(process.env.CI), // Do we run on workers (`wrangler dev`) or on Node (`next dev`) @@ -17,8 +29,13 @@ export function configurePlaywright( parallel = true, // Use the turbopack runtime useTurbopack = false, - } = {} -) { + // Script used to build the Worker before starting it + workerBuildScript = "build:worker", + // Script used to start the Worker + workerPreviewScript = "preview:worker", + // Test file or glob to run + testMatch, + } = options; const port = getAppPort(app, { isWorker }); const inspectorPort = getInspectorPort(app); const baseURL = `http://localhost:${port}`; @@ -26,10 +43,11 @@ export function configurePlaywright( let timeout: number; if (isWorker) { // Do not build on CI - there is a preceding build step - command = isCI ? "" : `pnpm ${useTurbopack ? "build:worker-turbopack" : "build:worker"} && `; + const buildScript = useTurbopack ? "build:worker-turbopack" : workerBuildScript; + command = isCI ? "" : `pnpm ${buildScript} && `; const env = app === "r2-incremental-cache" ? "--env e2e" : ""; - command += `pnpm preview:worker -- --port ${port} --inspector-port ${inspectorPort} ${env}`; + command += `pnpm ${workerPreviewScript} -- --port ${port} --inspector-port ${inspectorPort} ${env}`; timeout = 800_000; } else { timeout = 100_000; @@ -63,6 +81,7 @@ export function configurePlaywright( testIgnore: isWorker ? "*next.spec.ts" : "*cloudflare.spec.ts", /* Run tests in files in parallel */ fullyParallel: parallel, + ...(testMatch ? { testMatch } : {}), /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: isCI, /* Retry on CI only */ diff --git a/examples-cloudflare/e2e/app-router/e2e/container.test.ts b/examples-cloudflare/e2e/app-router/e2e/container.test.ts new file mode 100644 index 00000000..29719773 --- /dev/null +++ b/examples-cloudflare/e2e/app-router/e2e/container.test.ts @@ -0,0 +1,14 @@ +import { expect, test } from "@playwright/test"; + +test("serves SSR from the Node.js container", async ({ page }) => { + await page.goto("/ssr"); + + await expect(page.getByText("Time:")).toBeVisible(); +}); + +test("runs external middleware before forwarding to the container", async ({ page }) => { + await page.goto("/rewrite"); + + await expect(page).toHaveURL(/\/rewrite$/); + await expect(page.getByText("Rewritten Destination", { exact: true })).toBeVisible(); +}); diff --git a/examples-cloudflare/e2e/app-router/e2e/playwright.container.config.ts b/examples-cloudflare/e2e/app-router/e2e/playwright.container.config.ts new file mode 100644 index 00000000..c441e1bf --- /dev/null +++ b/examples-cloudflare/e2e/app-router/e2e/playwright.container.config.ts @@ -0,0 +1,8 @@ +import { configurePlaywright } from "../../../common/config-e2e"; + +export default configurePlaywright("app-router", { + useTurbopack: false, + workerBuildScript: "build:worker:container", + workerPreviewScript: "preview:container", + testMatch: "container.test.ts", +}); diff --git a/examples-cloudflare/e2e/app-router/open-next.container.config.ts b/examples-cloudflare/e2e/app-router/open-next.container.config.ts new file mode 100644 index 00000000..891cb65b --- /dev/null +++ b/examples-cloudflare/e2e/app-router/open-next.container.config.ts @@ -0,0 +1,5 @@ +import { defineCloudflareConfig } from "@opennextjs/cloudflare"; + +export default defineCloudflareConfig({ + container: true, +}); diff --git a/examples-cloudflare/e2e/app-router/package.json b/examples-cloudflare/e2e/app-router/package.json index d1924fbb..8358cfb0 100644 --- a/examples-cloudflare/e2e/app-router/package.json +++ b/examples-cloudflare/e2e/app-router/package.json @@ -10,9 +10,12 @@ "lint": "next lint", "clean": "rm -rf .turbo node_modules .next .open-next", "build:worker:cf": "pnpm opennextjs-cloudflare build", + "build:worker:container": "pnpm opennextjs-cloudflare build --openNextConfigPath open-next.container.config.ts --config wrangler.container.jsonc", "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview:container": "pnpm opennextjs-cloudflare preview --config wrangler.container.jsonc", "preview": "pnpm build:worker && pnpm preview:worker", "e2e:cf": "playwright test -c e2e/playwright.config.ts", + "e2e:container": "playwright test -c e2e/playwright.container.config.ts", "build:worker-turbopack": "pnpm build:worker --openNextConfigPath open-next.turbopack.config.ts", "e2e-turbopack": "playwright test -c e2e/playwright.turbopack.config.ts" }, diff --git a/examples-cloudflare/e2e/app-router/wrangler.container.jsonc b/examples-cloudflare/e2e/app-router/wrangler.container.jsonc new file mode 100644 index 00000000..dd38797f --- /dev/null +++ b/examples-cloudflare/e2e/app-router/wrangler.container.jsonc @@ -0,0 +1,48 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "main": ".open-next/worker.js", + "name": "app-router-container", + "compatibility_date": "2024-12-30", + "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], + "assets": { + "directory": ".open-next/assets", + "binding": "ASSETS", + }, + "containers": [ + { + "class_name": "OpenNextContainer", + "image": ".open-next/server-functions/default/Dockerfile", + "max_instances": 1, + "instance_type": "lite", + }, + ], + "durable_objects": { + "bindings": [ + { + "name": "OPEN_NEXT_CONTAINER", + "class_name": "OpenNextContainer", + }, + ], + }, + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "cache", + }, + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "app-router", + }, + ], + "vars": { + "OPEN_NEXT_REQUEST_ID_HEADER": "true", + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["OpenNextContainer", "DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"], + }, + ], +} diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 71bcd44b..a24f2c05 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -51,6 +51,7 @@ }, "dependencies": { "@ast-grep/napi": "0.40.5", + "@cloudflare/containers": "^0.3.7", "@dotenvx/dotenvx": "catalog:", "@opennextjs/core": "workspace:*", "cloudflare": "^4.4.1", diff --git a/packages/cloudflare/src/api/config.spec.ts b/packages/cloudflare/src/api/config.spec.ts new file mode 100644 index 00000000..a571874c --- /dev/null +++ b/packages/cloudflare/src/api/config.spec.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "vitest"; + +import { defineCloudflareConfig } from "./config.js"; + +describe("defineCloudflareConfig", () => { + test("uses the Worker defaults by default", () => { + const config = defineCloudflareConfig(); + + expect(config.cloudflare?.container).toBe(false); + expect(config.default.override).toMatchObject({ + wrapper: "cloudflare-node", + converter: "edge", + proxyExternalRequest: "fetch", + }); + }); + + test("configures a Node.js container default function", () => { + const config = defineCloudflareConfig({ container: true }); + + expect(config.cloudflare?.container).toBe(true); + expect(config.default.override).toEqual({ + wrapper: "node", + converter: "node", + generateDockerfile: true, + incrementalCache: "dummy", + tagCache: "dummy", + queue: "dummy", + }); + expect(config.middleware).toMatchObject({ + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + }, + }); + }); + + test("rejects Cloudflare binding-backed overrides for containers", () => { + expect(() => + defineCloudflareConfig({ + container: true, + incrementalCache: () => ({ name: "unsupported" }) as never, + }) + ).toThrow("Container mode"); + }); +}); diff --git a/packages/cloudflare/src/api/config.ts b/packages/cloudflare/src/api/config.ts index 32d9be73..9fe26773 100644 --- a/packages/cloudflare/src/api/config.ts +++ b/packages/cloudflare/src/api/config.ts @@ -22,6 +22,17 @@ export type Override = "dummy" | T | LazyLoadedOverride< * See the [Caching documentation](https://opennext.js.org/cloudflare/caching)) */ export type CloudflareOverrides = { + /** + * Run the default Next.js server in a Cloudflare Container. + * + * The Cloudflare Worker continues to run the external middleware and forwards + * requests that reach the default server to one container instance. + * + * Cloudflare binding-backed caches are not available from the Node.js + * container in this mode. + */ + container?: true; + /** * Sets the incremental cache implementation. */ @@ -57,25 +68,51 @@ export type CloudflareOverrides = { * @returns the OpenNext configuration object */ export function defineCloudflareConfig(config: CloudflareOverrides = {}): OpenNextConfig { - const { incrementalCache, tagCache, queue, cachePurge, routePreloadingBehavior = "none" } = config; - - return { - default: { - override: { - wrapper: "cloudflare-node", - converter: "edge", - proxyExternalRequest: "fetch", + const { + container = false, + incrementalCache, + tagCache, + queue, + cachePurge, + routePreloadingBehavior = "none", + } = config; + if ( + container && + [incrementalCache, tagCache, queue, cachePurge].some((value) => value !== undefined && value !== "dummy") + ) { + throw new Error( + "Cloudflare Container mode only supports the default dummy cache, tag cache, queue, and cache purge overrides." + ); + } + const defaultOverride = container + ? { + wrapper: "node" as const, + converter: "node" as const, + generateDockerfile: true, + incrementalCache: "dummy" as const, + tagCache: "dummy" as const, + queue: "dummy" as const, + } + : { + wrapper: "cloudflare-node" as const, + converter: "edge" as const, + proxyExternalRequest: "fetch" as const, incrementalCache: resolveIncrementalCache(incrementalCache), tagCache: resolveTagCache(tagCache), queue: resolveQueue(queue), cdnInvalidation: resolveCdnInvalidation(cachePurge), - }, + }; + + return { + default: { + override: defaultOverride, routePreloadingBehavior, }, // node:crypto is used to compute cache keys edgeExternals: ["node:crypto"], cloudflare: { useWorkerdCondition: true, + container, }, middleware: { external: true, @@ -126,6 +163,12 @@ function resolveCdnInvalidation(value: CloudflareOverrides["cachePurge"] = "dumm interface OpenNextConfig extends AwsOpenNextConfig { cloudflare?: { + /** + * Whether the default function runs in a Cloudflare Container. + * @default false + */ + container?: boolean; + /** * Whether to use the "workerd" build conditions when bundling the server. * It is recommended to set it to `true` so that code specifically targeted to the diff --git a/packages/cloudflare/src/api/container.ts b/packages/cloudflare/src/api/container.ts new file mode 100644 index 00000000..c61d4b90 --- /dev/null +++ b/packages/cloudflare/src/api/container.ts @@ -0,0 +1,18 @@ +import { Container, getContainer } from "@cloudflare/containers"; + +export const OPEN_NEXT_CONTAINER_BINDING = "OPEN_NEXT_CONTAINER"; +export const OPEN_NEXT_CONTAINER_NAME = "default"; + +/** + * The Durable Object controller for the Node.js OpenNext server container. + * + * The matching Wrangler configuration must declare this class as a container + * and bind it as `OPEN_NEXT_CONTAINER`. + */ +export class OpenNextContainer extends Container { + override defaultPort = 3000; +} + +export function getOpenNextContainer(containerNamespace: DurableObjectNamespace) { + return getContainer(containerNamespace, OPEN_NEXT_CONTAINER_NAME); +} diff --git a/packages/cloudflare/src/cli/adapter.ts b/packages/cloudflare/src/cli/adapter.ts index b3079449..18550376 100644 --- a/packages/cloudflare/src/cli/adapter.ts +++ b/packages/cloudflare/src/cli/adapter.ts @@ -1,6 +1,7 @@ /* oxlint-disable @typescript-eslint/no-explicit-any */ import fs from "node:fs"; import path from "node:path"; +import { fileURLToPath } from "node:url"; import { buildAdapter } from "@opennextjs/core/build/adapter.js"; import type { BuildOptions } from "@opennextjs/core/build/helper.js"; @@ -23,8 +24,11 @@ import { inlineLoadManifest } from "./build/patches/plugins/load-manifest.js"; import { patchResRevalidate } from "./build/patches/plugins/res-revalidate.js"; import { patchTurbopackRuntime } from "./build/patches/plugins/turbopack.js"; import { patchUseCacheIO } from "./build/patches/plugins/use-cache.js"; +import { copyPackageCliFiles } from "./build/utils/copy-package-cli-files.js"; export default buildAdapter((config: OpenNextConfig, buildOpts: BuildOptions) => { + const isContainer = + (config as OpenNextConfig & { cloudflare?: { container?: boolean } }).cloudflare?.container === true; const packagePath = buildHelper.getPackagePath(buildOpts); return { skipRevalidation: true, @@ -47,7 +51,7 @@ export default buildAdapter((config: OpenNextConfig, buildOpts: BuildOptions) => await compileSkewProtection(buildOpts, openNextConfig); }, serverBundle: { - useEdgeConfig: true, + useEdgeConfig: !isContainer, externals: ["./middleware.mjs"], banner: (name: string) => [ `globalThis.monorepoPackagePath = "${normalizePath(packagePath)}";`, @@ -56,21 +60,32 @@ export default buildAdapter((config: OpenNextConfig, buildOpts: BuildOptions) => additionalPlugins: (updater: ContentUpdater, outputs: NextAdapterOutputs) => [ inlineRouteHandler(updater, outputs, packagePath), inlineLoadManifest(updater, buildOpts), - ...(config.middleware?.external - ? [ - openNextExternalMiddlewarePlugin( - path.join(buildOpts.openNextDistDir, "core/edgeFunctionHandler.js") - ), - ] - : []), - openNextEdgePlugins({ - nextDir: path.join(buildOpts.appBuildOutputPath, ".next"), - isInCloudflare: true, - }), + ...(isContainer + ? [] + : [ + ...(config.middleware?.external + ? [ + openNextExternalMiddlewarePlugin( + path.join(buildOpts.openNextDistDir, "core/edgeFunctionHandler.js") + ), + ] + : []), + openNextEdgePlugins({ + nextDir: path.join(buildOpts.appBuildOutputPath, ".next"), + isInCloudflare: true, + }), + ]), ], - additionalCodePatches: [patchResRevalidate, patchUseCacheIO, patchTurbopackRuntime], + additionalCodePatches: isContainer + ? [patchUseCacheIO, patchTurbopackRuntime] + : [patchResRevalidate, patchUseCacheIO, patchTurbopackRuntime], }, afterServerBundle: async (buildOpts, _config) => { + if (isContainer) { + const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); + copyPackageCliFiles(packageDistDir, buildOpts, "container"); + return; + } compileDurableObjects(buildOpts); await bundleServer(buildOpts, { minify: false } as any); }, diff --git a/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts new file mode 100644 index 00000000..ec095a9a --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts @@ -0,0 +1,29 @@ +import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; + +import { afterEach, describe, expect, test } from "vitest"; + +import { copyPackageCliFiles } from "./copy-package-cli-files.js"; + +const tempDirectories: string[] = []; + +afterEach(async () => { + await Promise.all(tempDirectories.splice(0).map((dir) => rm(dir, { force: true, recursive: true }))); +}); + +describe("copyPackageCliFiles", () => { + test("writes the container Worker entrypoint when requested", async () => { + const root = await mkdtemp(path.join(tmpdir(), "opennext-container-")); + tempDirectories.push(root); + const templatesDir = path.join(root, "cli", "templates"); + const outputDir = path.join(root, "output"); + await mkdir(templatesDir, { recursive: true }); + await writeFile(path.join(templatesDir, "container.js"), "container worker"); + await writeFile(path.join(templatesDir, "worker.js"), "standard worker"); + + copyPackageCliFiles(root, { outputDir } as never, "container"); + + expect(await readFile(path.join(outputDir, "worker.container.js"), "utf8")).toBe("container worker"); + }); +}); diff --git a/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts index 10b57618..e6dabdb6 100644 --- a/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts +++ b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts @@ -5,12 +5,18 @@ import type { BuildOptions } from "@opennextjs/core/build/helper.js"; import { getOutputWorkerPath } from "../bundle-server.js"; +type WorkerTemplate = "container" | "worker"; + /** * Copies * - the template files present in the cloudflare adapter package to `.open-next/cloudflare-templates` * - `worker.js` to `.open-next/` */ -export function copyPackageCliFiles(packageDistDir: string, buildOpts: BuildOptions) { +export function copyPackageCliFiles( + packageDistDir: string, + buildOpts: BuildOptions, + workerTemplate: WorkerTemplate = "worker" +) { console.log("# copyPackageTemplateFiles"); const sourceDir = path.join(packageDistDir, "cli/templates"); @@ -19,5 +25,9 @@ export function copyPackageCliFiles(packageDistDir: string, buildOpts: BuildOpti fs.mkdirSync(destinationDir, { recursive: true }); fs.cpSync(sourceDir, destinationDir, { recursive: true }); - fs.copyFileSync(path.join(packageDistDir, "cli/templates/worker.js"), getOutputWorkerPath(buildOpts)); + const outputPath = + workerTemplate === "container" + ? path.join(buildOpts.outputDir, "worker.container.js") + : getOutputWorkerPath(buildOpts); + fs.copyFileSync(path.join(packageDistDir, `cli/templates/${workerTemplate}.js`), outputPath); } diff --git a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.spec.ts b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.spec.ts new file mode 100644 index 00000000..b90f5432 --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.spec.ts @@ -0,0 +1,18 @@ +import { describe, expect, test } from "vitest"; + +import { defineCloudflareConfig } from "../../../api/config.js"; + +import { ensureCloudflareConfig } from "./ensure-cf-config.js"; + +describe("ensureCloudflareConfig", () => { + test("accepts the container topology", () => { + expect(() => ensureCloudflareConfig(defineCloudflareConfig({ container: true }))).not.toThrow(); + }); + + test("rejects binding-backed caches in container mode", () => { + const config = defineCloudflareConfig({ container: true }); + config.default.override!.incrementalCache = () => ({ name: "unsupported" }) as never; + + expect(() => ensureCloudflareConfig(config)).toThrow("Cloudflare Containers"); + }); +}); diff --git a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts index fb461018..d3756aba 100644 --- a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts +++ b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts @@ -11,6 +11,7 @@ import type { OpenNextConfig } from "../../../api/config.js"; export function ensureCloudflareConfig(config: OpenNextConfig) { const mwIsMiddlewareExternal = config.middleware?.external === true; const mwConfig = mwIsMiddlewareExternal ? (config.middleware as ExternalMiddlewareConfig) : undefined; + const isContainer = config.cloudflare?.container === true; const requirements = { // Check for the default function @@ -27,6 +28,12 @@ export function ensureCloudflareConfig(config: OpenNextConfig) { config.default?.override?.queue === "dummy" || config.default?.override?.queue === "direct" || typeof config.default?.override?.queue === "function", + dftUseNodeWrapper: config.default?.override?.wrapper === "node", + dftUseNodeConverter: config.default?.override?.converter === "node", + dftGenerateDockerfile: config.default?.override?.generateDockerfile === true, + dftUseDummyCache: config.default?.override?.incrementalCache === "dummy", + dftUseDummyTagCache: config.default?.override?.tagCache === "dummy", + dftUseDummyQueue: config.default?.override?.queue === "dummy", // Check for the middleware function mwIsMiddlewareExternal, mwUseCloudflareWrapper: mwConfig?.override?.wrapper === "cloudflare-edge", @@ -39,8 +46,34 @@ export function ensureCloudflareConfig(config: OpenNextConfig) { logger.warn("The direct mode queue is not recommended for use in production."); } - if (Object.values(requirements).some((satisfied) => !satisfied)) { - const errorMessage = + const workerRequirements = [ + requirements.dftUseCloudflareWrapper, + requirements.dftUseEdgeConverter, + requirements.dftUseFetchProxy, + requirements.dftMaybeUseCache, + requirements.dftMaybeUseTagCache, + requirements.dftMaybeUseQueue, + ]; + const containerRequirements = [ + requirements.dftUseNodeWrapper, + requirements.dftUseNodeConverter, + requirements.dftGenerateDockerfile, + requirements.dftUseDummyCache, + requirements.dftUseDummyTagCache, + requirements.dftUseDummyQueue, + ]; + const commonRequirements = [ + requirements.mwIsMiddlewareExternal, + requirements.mwUseCloudflareWrapper, + requirements.mwUseEdgeConverter, + requirements.mwUseFetchProxy, + requirements.hasCryptoExternal, + ]; + + if ( + ![...(isContainer ? containerRequirements : workerRequirements), ...commonRequirements].every(Boolean) + ) { + const workerErrorMessage = "The `open-next.config.ts` should have a default export like this:\n\n" + `{ default: { @@ -66,6 +99,31 @@ export function ensureCloudflareConfig(config: OpenNextConfig) { }, }, }\n\n`.replace(/^ {8}/gm, ""); + const containerErrorMessage = + "The `open-next.config.ts` should use this configuration for Cloudflare Containers:\n\n" + + `{ + default: { + override: { + wrapper: "node", + converter: "node", + generateDockerfile: true, + incrementalCache: "dummy", + tagCache: "dummy", + queue: "dummy", + }, + }, + edgeExternals: ["node:crypto"], + cloudflare: { container: true }, + middleware: { + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + }, + }, + }\n\n`.replace(/^ {8}/gm, ""); + const errorMessage = isContainer ? containerErrorMessage : workerErrorMessage; if (config.cloudflare?.dangerousDisableConfigValidation) { logger.warn(errorMessage); return; diff --git a/packages/cloudflare/src/cli/templates/container.ts b/packages/cloudflare/src/cli/templates/container.ts new file mode 100644 index 00000000..427555f8 --- /dev/null +++ b/packages/cloudflare/src/cli/templates/container.ts @@ -0,0 +1,53 @@ +// @ts-expect-error: This public package entrypoint is resolved by Wrangler from the application. +import { getOpenNextContainer, OpenNextContainer } from "@opennextjs/cloudflare/container"; + +//@ts-expect-error: Will be resolved by wrangler build +import { handleCdnCgiImageRequest, handleImageRequest } from "./cloudflare/images.js"; +//@ts-expect-error: Will be resolved by wrangler build +import { runWithCloudflareRequestContext } from "./cloudflare/init.js"; +//@ts-expect-error: Will be resolved by wrangler build +import { maybeGetSkewProtectionResponse } from "./cloudflare/skew-protection.js"; +// @ts-expect-error: Will be resolved by wrangler build +import { handler as middlewareHandler } from "./middleware/handler.mjs"; + +export { OpenNextContainer }; + +type ContainerEnv = CloudflareEnv & { + OPEN_NEXT_CONTAINER: DurableObjectNamespace; +}; + +export default { + async fetch(request, env, ctx) { + return runWithCloudflareRequestContext(request, env, ctx, async () => { + const response = maybeGetSkewProtectionResponse(request); + if (response) { + return response; + } + + const url = new URL(request.url); + if (url.pathname.startsWith("/cdn-cgi/image/")) { + return handleCdnCgiImageRequest(url, env); + } + + if ( + url.pathname === + `${globalThis.__NEXT_BASE_PATH__}/_next/image${globalThis.__TRAILING_SLASH__ ? "/" : ""}` + ) { + return await handleImageRequest(url, request.headers, env); + } + + const reqOrResp = await middlewareHandler(request, env, ctx); + if (reqOrResp instanceof Response) { + return reqOrResp; + } + + if ("initialResponse" in reqOrResp) { + return new Response("Partial prerendering is not supported in Cloudflare container mode.", { + status: 501, + }); + } + + return getOpenNextContainer(env.OPEN_NEXT_CONTAINER).fetch(reqOrResp); + }); + }, +} satisfies ExportedHandler; diff --git a/packages/core/src/build/createServerBundle.ts b/packages/core/src/build/createServerBundle.ts index d22ba6cb..acb1e13a 100644 --- a/packages/core/src/build/createServerBundle.ts +++ b/packages/core/src/build/createServerBundle.ts @@ -286,7 +286,7 @@ async function generateBundle( typeof shouldGenerateDocker === "string" ? shouldGenerateDocker : ` -FROM node:18-alpine +FROM node:24-alpine WORKDIR /app COPY . /app EXPOSE 3000 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb571a6d..b8b65e23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -902,6 +902,9 @@ importers: '@ast-grep/napi': specifier: 0.40.5 version: 0.40.5 + '@cloudflare/containers': + specifier: ^0.3.7 + version: 0.3.7 '@dotenvx/dotenvx': specifier: 'catalog:' version: 1.31.0 @@ -1899,6 +1902,9 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@cloudflare/containers@0.3.7': + resolution: {integrity: sha512-DM9dm3FnIBSyiSJ1FLavKwl/lk3oAmTaynCzZQ9pZR0ncRPquSxkxd8Nu2MFILxmDDsPkxKsSNEh9mHHMty4Fw==} + '@cloudflare/kv-asset-handler@0.4.2': resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} @@ -10324,6 +10330,8 @@ snapshots: human-id: 4.1.2 prettier: 2.8.8 + '@cloudflare/containers@0.3.7': {} + '@cloudflare/kv-asset-handler@0.4.2': {} '@cloudflare/unenv-preset@2.11.0(unenv@2.0.0-rc.24)(workerd@1.20260120.0)': From ce704dedbc8fed109c3024589cdf950cf6267e08 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 15 Jul 2026 22:08:03 +0200 Subject: [PATCH 2/5] feat(cloudflare): implement container support with configuration updates and new compile logic --- .../e2e/app-router/wrangler.container.jsonc | 17 +------------- packages/cloudflare/src/cli/adapter.ts | 2 ++ .../cli/build/open-next/compileContainer.ts | 22 +++++++++++++++++++ .../utils/copy-package-cli-files.spec.ts | 2 +- .../cli/build/utils/copy-package-cli-files.ts | 11 +++++----- packages/cloudflare/src/cli/commands/build.ts | 8 ++++++- .../cloudflare/src/cli/templates/container.ts | 5 ++--- packages/core/src/build/adapter.spec.ts | 21 ++++++++++++++++++ packages/core/src/build/adapter.ts | 7 +++--- 9 files changed, 65 insertions(+), 30 deletions(-) create mode 100644 packages/cloudflare/src/cli/build/open-next/compileContainer.ts diff --git a/examples-cloudflare/e2e/app-router/wrangler.container.jsonc b/examples-cloudflare/e2e/app-router/wrangler.container.jsonc index dd38797f..5fb02a69 100644 --- a/examples-cloudflare/e2e/app-router/wrangler.container.jsonc +++ b/examples-cloudflare/e2e/app-router/wrangler.container.jsonc @@ -24,25 +24,10 @@ }, ], }, - "r2_buckets": [ - { - "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "cache", - }, - ], - "services": [ - { - "binding": "WORKER_SELF_REFERENCE", - "service": "app-router", - }, - ], - "vars": { - "OPEN_NEXT_REQUEST_ID_HEADER": "true", - }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["OpenNextContainer", "DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"], + "new_sqlite_classes": ["OpenNextContainer"], }, ], } diff --git a/packages/cloudflare/src/cli/adapter.ts b/packages/cloudflare/src/cli/adapter.ts index 18550376..d6c23b50 100644 --- a/packages/cloudflare/src/cli/adapter.ts +++ b/packages/cloudflare/src/cli/adapter.ts @@ -19,6 +19,7 @@ import { compileEnvFiles } from "./build/open-next/compile-env-files.js"; import { compileImages } from "./build/open-next/compile-images.js"; import { compileInit } from "./build/open-next/compile-init.js"; import { compileSkewProtection } from "./build/open-next/compile-skew-protection.js"; +import { compileContainer } from "./build/open-next/compileContainer.js"; import { compileDurableObjects } from "./build/open-next/compileDurableObjects.js"; import { inlineLoadManifest } from "./build/patches/plugins/load-manifest.js"; import { patchResRevalidate } from "./build/patches/plugins/res-revalidate.js"; @@ -83,6 +84,7 @@ export default buildAdapter((config: OpenNextConfig, buildOpts: BuildOptions) => afterServerBundle: async (buildOpts, _config) => { if (isContainer) { const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); + compileContainer(buildOpts); copyPackageCliFiles(packageDistDir, buildOpts, "container"); return; } diff --git a/packages/cloudflare/src/cli/build/open-next/compileContainer.ts b/packages/cloudflare/src/cli/build/open-next/compileContainer.ts new file mode 100644 index 00000000..54f2e932 --- /dev/null +++ b/packages/cloudflare/src/cli/build/open-next/compileContainer.ts @@ -0,0 +1,22 @@ +import { createRequire } from "node:module"; +import path from "node:path"; + +import { type BuildOptions, esbuildSync } from "@opennextjs/core/build/helper.js"; + +/** Compiles the Container Durable Object used by the generated Worker entrypoint. */ +export function compileContainer(buildOpts: BuildOptions) { + const require = createRequire(import.meta.url); + const entryPoint = require.resolve("@opennextjs/cloudflare/container"); + + return esbuildSync( + { + entryPoints: [entryPoint], + bundle: true, + platform: "node", + format: "esm", + outfile: path.join(buildOpts.buildDir, "open-next-container.js"), + external: ["cloudflare:workers"], + }, + buildOpts + ); +} diff --git a/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts index ec095a9a..d44586e9 100644 --- a/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts +++ b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.spec.ts @@ -24,6 +24,6 @@ describe("copyPackageCliFiles", () => { copyPackageCliFiles(root, { outputDir } as never, "container"); - expect(await readFile(path.join(outputDir, "worker.container.js"), "utf8")).toBe("container worker"); + expect(await readFile(path.join(outputDir, "worker.js"), "utf8")).toBe("container worker"); }); }); diff --git a/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts index e6dabdb6..eb1bd865 100644 --- a/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts +++ b/packages/cloudflare/src/cli/build/utils/copy-package-cli-files.ts @@ -10,7 +10,7 @@ type WorkerTemplate = "container" | "worker"; /** * Copies * - the template files present in the cloudflare adapter package to `.open-next/cloudflare-templates` - * - `worker.js` to `.open-next/` + * - the selected Worker template as `.open-next/worker.js` */ export function copyPackageCliFiles( packageDistDir: string, @@ -25,9 +25,8 @@ export function copyPackageCliFiles( fs.mkdirSync(destinationDir, { recursive: true }); fs.cpSync(sourceDir, destinationDir, { recursive: true }); - const outputPath = - workerTemplate === "container" - ? path.join(buildOpts.outputDir, "worker.container.js") - : getOutputWorkerPath(buildOpts); - fs.copyFileSync(path.join(packageDistDir, `cli/templates/${workerTemplate}.js`), outputPath); + fs.copyFileSync( + path.join(packageDistDir, `cli/templates/${workerTemplate}.js`), + getOutputWorkerPath(buildOpts) + ); } diff --git a/packages/cloudflare/src/cli/commands/build.ts b/packages/cloudflare/src/cli/commands/build.ts index c7b76fc5..3b2a4e84 100644 --- a/packages/cloudflare/src/cli/commands/build.ts +++ b/packages/cloudflare/src/cli/commands/build.ts @@ -1,4 +1,5 @@ import { createRequire } from "node:module"; +import path from "node:path"; import logger from "@opennextjs/core/logger.js"; import type yargs from "yargs"; @@ -39,6 +40,7 @@ export async function buildCommand( const require = createRequire(import.meta.url); process.env.NEXT_ADAPTER_PATH = require.resolve("../adapter.js"); + process.env.OPEN_NEXT_CONFIG_PATH = path.resolve(args.openNextConfigPath ?? "open-next.config.ts"); // Ask whether a `wrangler.jsonc` should be created when no config file exists. // Note: We don't ask when a custom config file is specified via `--config` @@ -56,7 +58,11 @@ export async function buildCommand( } } - await buildImpl(options, projectOpts); + try { + await buildImpl(options, projectOpts); + } finally { + delete process.env.OPEN_NEXT_CONFIG_PATH; + } } /** diff --git a/packages/cloudflare/src/cli/templates/container.ts b/packages/cloudflare/src/cli/templates/container.ts index 427555f8..ede5d438 100644 --- a/packages/cloudflare/src/cli/templates/container.ts +++ b/packages/cloudflare/src/cli/templates/container.ts @@ -1,6 +1,5 @@ -// @ts-expect-error: This public package entrypoint is resolved by Wrangler from the application. -import { getOpenNextContainer, OpenNextContainer } from "@opennextjs/cloudflare/container"; - +// @ts-expect-error: Generated by the Cloudflare adapter before this Worker is bundled by Wrangler. +import { getOpenNextContainer, OpenNextContainer } from "./.build/open-next-container.js"; //@ts-expect-error: Will be resolved by wrangler build import { handleCdnCgiImageRequest, handleImageRequest } from "./cloudflare/images.js"; //@ts-expect-error: Will be resolved by wrangler build diff --git a/packages/core/src/build/adapter.spec.ts b/packages/core/src/build/adapter.spec.ts index bda407af..00c9ca6a 100644 --- a/packages/core/src/build/adapter.spec.ts +++ b/packages/core/src/build/adapter.spec.ts @@ -196,6 +196,27 @@ describe("buildAdapter", () => { expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ default: {} }), expect.any(Object)); }); + test("modifyConfig uses the config path selected by the build command", async () => { + const previousConfigPath = process.env.OPEN_NEXT_CONFIG_PATH; + process.env.OPEN_NEXT_CONFIG_PATH = "open-next.container.config.ts"; + + try { + const adapter = buildAdapter(() => ({})); + const nextConfig = { experimental: {}, images: {} } as BuildCompleteContext["config"]; + await adapter.modifyConfig(nextConfig, { phase: "production" }); + + expect(compileOpenNextConfig).toHaveBeenCalledWith("open-next.container.config.ts", { + compileEdge: true, + }); + } finally { + if (previousConfigPath === undefined) { + delete process.env.OPEN_NEXT_CONFIG_PATH; + } else { + process.env.OPEN_NEXT_CONFIG_PATH = previousConfigPath; + } + } + }); + test("modifyConfig returns nextConfig with cacheHandler, cacheHandlers, cacheMaxMemorySize, and trustHostHeader", async () => { const adapter = buildAdapter(() => ({})); diff --git a/packages/core/src/build/adapter.ts b/packages/core/src/build/adapter.ts index 9c3488f7..2af941e0 100644 --- a/packages/core/src/build/adapter.ts +++ b/packages/core/src/build/adapter.ts @@ -104,16 +104,17 @@ export function buildAdapter( name: "OpenNext", async modifyConfig(nextConfig, { phase: _phase }) { + const openNextConfigPath = process.env.OPEN_NEXT_CONFIG_PATH ?? "open-next.config.ts"; // Step 1: Compile OpenNext config with edge support, fallback on failure let result: { config: OpenNextConfig; buildDir: string }; try { - result = await compileOpenNextConfig("open-next.config.ts", { compileEdge: true }); + result = await compileOpenNextConfig(openNextConfigPath, { compileEdge: true }); } catch (error) { console.warn( - "Failed to compile open-next.config.ts for edge runtime, falling back to node-only compilation.", + `Failed to compile ${openNextConfigPath} for edge runtime, falling back to node-only compilation.`, error instanceof Error ? error.message : error ); - result = await compileOpenNextConfig("open-next.config.ts", { compileEdge: false }); + result = await compileOpenNextConfig(openNextConfigPath, { compileEdge: false }); } config = result.config; From b9b7b4f0e91f9d99d25e3c9c377472d3c9943612 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 15 Jul 2026 22:17:15 +0200 Subject: [PATCH 3/5] feat(cloudflare): enhance container support with request normalization and updated banner logic --- packages/cloudflare/src/api/container.ts | 19 +++++++++++++++++++ packages/cloudflare/src/cli/adapter.ts | 22 ++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/cloudflare/src/api/container.ts b/packages/cloudflare/src/api/container.ts index c61d4b90..7aa79048 100644 --- a/packages/cloudflare/src/api/container.ts +++ b/packages/cloudflare/src/api/container.ts @@ -11,6 +11,25 @@ export const OPEN_NEXT_CONTAINER_NAME = "default"; */ export class OpenNextContainer extends Container { override defaultPort = 3000; + + /** + * Normalize the Durable Object request before forwarding it to the Container. + * + * In local workerd, the request received by a Durable Object can originate + * from another runtime realm. The Container base class checks it with + * `instanceof Request`, which then fails and coerces it to "[object Request]". + */ + override fetch(request: Request): Promise { + return this.containerFetch( + request.url, + { + method: request.method, + headers: request.headers, + body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body, + }, + this.defaultPort + ); + } } export function getOpenNextContainer(containerNamespace: DurableObjectNamespace) { diff --git a/packages/cloudflare/src/cli/adapter.ts b/packages/cloudflare/src/cli/adapter.ts index d6c23b50..7eef247b 100644 --- a/packages/cloudflare/src/cli/adapter.ts +++ b/packages/cloudflare/src/cli/adapter.ts @@ -54,10 +54,24 @@ export default buildAdapter((config: OpenNextConfig, buildOpts: BuildOptions) => serverBundle: { useEdgeConfig: !isContainer, externals: ["./middleware.mjs"], - banner: (name: string) => [ - `globalThis.monorepoPackagePath = "${normalizePath(packagePath)}";`, - name === "default" ? "" : `globalThis.fnName = "${name}";`, - ], + banner: (name: string) => { + const cloudflareBanner = [`globalThis.monorepoPackagePath = "${normalizePath(packagePath)}";`]; + + if (isContainer) { + cloudflareBanner.push( + "import process from 'node:process';", + "import { Buffer } from 'node:buffer';", + "import { createRequire as topLevelCreateRequire } from 'module';", + "const require = topLevelCreateRequire(import.meta.url);", + "import bannerUrl from 'url';", + "const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));", + "const __filename = bannerUrl.fileURLToPath(import.meta.url);" + ); + } + + cloudflareBanner.push(name === "default" ? "" : `globalThis.fnName = "${name}";`); + return cloudflareBanner; + }, additionalPlugins: (updater: ContentUpdater, outputs: NextAdapterOutputs) => [ inlineRouteHandler(updater, outputs, packagePath), inlineLoadManifest(updater, buildOpts), From 75461d8fafa1d9c48b1b4af81d7498159e812122 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 15 Jul 2026 22:17:23 +0200 Subject: [PATCH 4/5] chore: update wrangler dependency to version 4.110.0 --- pnpm-lock.yaml | 439 ++++++++++++++++++++++++++++++++++++++++++-- pnpm-workspace.yaml | 2 +- 2 files changed, 426 insertions(+), 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8b65e23..c7ae39e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,8 +80,8 @@ catalogs: specifier: ^2.1.1 version: 2.1.3 wrangler: - specifier: ^4.59.2 - version: 4.60.0 + specifier: ^4.110.0 + version: 4.111.0 yargs: specifier: ^18.0.0 version: 18.0.0 @@ -224,7 +224,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/e2e/app-router: dependencies: @@ -270,7 +270,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/e2e/experimental: dependencies: @@ -304,7 +304,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/e2e/pages-router: dependencies: @@ -350,7 +350,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/e2e/shared: dependencies: @@ -403,7 +403,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/overrides/kv-tag-next: dependencies: @@ -437,7 +437,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/overrides/memory-queue: dependencies: @@ -471,7 +471,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/overrides/r2-incremental-cache: dependencies: @@ -505,7 +505,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/overrides/static-assets-incremental-cache: dependencies: @@ -539,7 +539,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/playground16: dependencies: @@ -576,7 +576,7 @@ importers: version: 4.1.18 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples-cloudflare/prisma: dependencies: @@ -616,7 +616,7 @@ importers: version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) examples/app-pages-router: dependencies: @@ -928,7 +928,7 @@ importers: version: 0.8.6 wrangler: specifier: 'catalog:' - version: 4.60.0(@cloudflare/workers-types@4.20260123.0) + version: 4.111.0(@cloudflare/workers-types@4.20260123.0) yargs: specifier: 'catalog:' version: 18.0.0 @@ -1909,6 +1909,10 @@ packages: resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} + '@cloudflare/unenv-preset@2.11.0': resolution: {integrity: sha512-z3hxFajL765VniNPGV0JRStZolNz63gU3B3AktwoGdDlnQvz5nP+Ah4RL04PONlZQjwmDdGHowEStJ94+RsaJg==} peerDependencies: @@ -1918,36 +1922,75 @@ packages: workerd: optional: true + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + '@cloudflare/workerd-darwin-64@1.20260120.0': resolution: {integrity: sha512-JLHx3p5dpwz4wjVSis45YNReftttnI3ndhdMh5BUbbpdreN/g0jgxNt5Qp9tDFqEKl++N63qv+hxJiIIvSLR+Q==} engines: {node: '>=16'} cpu: [x64] os: [darwin] + '@cloudflare/workerd-darwin-64@1.20260710.1': + resolution: {integrity: sha512-OqJl2eWF5+y9jarMm3YqqCTUe7Hd4ihogX5jyRU8iaAgOVyDr/Bk6aXpPCVUi1/MHzO93a18R/TmSTtzmB0sQw==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + '@cloudflare/workerd-darwin-arm64@1.20260120.0': resolution: {integrity: sha512-1Md2tCRhZjwajsZNOiBeOVGiS3zbpLPzUDjHr4+XGTXWOA6FzzwScJwQZLa0Doc28Cp4Nr1n7xGL0Dwiz1XuOA==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] + '@cloudflare/workerd-darwin-arm64@1.20260710.1': + resolution: {integrity: sha512-MYBqWgUblO+VlGvO73zYsH3hB9tdRj+yLyt5IHDFWryipb2l1efmNiWtAOkIhSRfypqLYGFrfpaDm2Hg00XVKw==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + '@cloudflare/workerd-linux-64@1.20260120.0': resolution: {integrity: sha512-O0mIfJfvU7F8N5siCoRDaVDuI12wkz2xlG4zK6/Ct7U9c9FiE0ViXNFWXFQm5PPj+qbkNRyhjUwhP+GCKTk5EQ==} engines: {node: '>=16'} cpu: [x64] os: [linux] + '@cloudflare/workerd-linux-64@1.20260710.1': + resolution: {integrity: sha512-lVWUgqI8qrkqvaCBGElu1kdaUFdAvaS2RD8K4qkCFP9hI3f5TCXumEs5qWSeZkvKum0+X/uJZ5hBFWsYI5SmoQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + '@cloudflare/workerd-linux-arm64@1.20260120.0': resolution: {integrity: sha512-aRHO/7bjxVpjZEmVVcpmhbzpN6ITbFCxuLLZSW0H9O0C0w40cDCClWSi19T87Ax/PQcYjFNT22pTewKsupkckA==} engines: {node: '>=16'} cpu: [arm64] os: [linux] + '@cloudflare/workerd-linux-arm64@1.20260710.1': + resolution: {integrity: sha512-kDwDPItBjAI4JL0df9Fma2N+Qggbm77IB/DnroAkEGQ79fpR80sYMyuB/ZQKyjEk9f48Ocq7HCCLq59qVSyNqA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + '@cloudflare/workerd-windows-64@1.20260120.0': resolution: {integrity: sha512-ASZIz1E8sqZQqQCgcfY1PJbBpUDrxPt8NZ+lqNil0qxnO4qX38hbCsdDF2/TDAuq0Txh7nu8ztgTelfNDlb4EA==} engines: {node: '>=16'} cpu: [x64] os: [win32] + '@cloudflare/workerd-windows-64@1.20260710.1': + resolution: {integrity: sha512-GcLHy1oN1dfK6g1Z7UDV9f5xMGyTfPwcjWQ0sfWKH31IsoEVCRapnj3IC0PoIrDbnoo6irGPP0CwVs3WzdTajw==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + '@cloudflare/workers-types@4.20250214.0': resolution: {integrity: sha512-+M8oOFVbyXT5GeJrYLWMUGyPf5wGB4+k59PPqdedtOig7NjZ5r4S79wMdaZ/EV5IV8JPtZBSNjTKpDnNmfxjaQ==} @@ -2009,6 +2052,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.13': resolution: {integrity: sha512-j7NhycJUoUAG5kAzGf4fPWfd17N6SM3o1X6MlXVqfHvs2buFraCJzos9vbeWjLxOyBKHyPOnuCuipbhvbYtTAg==} engines: {node: '>=12'} @@ -2033,6 +2082,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.13': resolution: {integrity: sha512-KwqFhxRFMKZINHzCqf8eKxE0XqWlAVPRxwy6rc7CbVFxzUWB2sA/s3hbMZeemPdhN3fKBkqOaFhTbS8xJXYIWQ==} engines: {node: '>=12'} @@ -2057,6 +2112,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.13': resolution: {integrity: sha512-M2eZkRxR6WnWfVELHmv6MUoHbOqnzoTVSIxgtsyhm/NsgmL+uTmag/VVzdXvmahak1I6sOb1K/2movco5ikDJg==} engines: {node: '>=12'} @@ -2081,6 +2142,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.13': resolution: {integrity: sha512-f5goG30YgR1GU+fxtaBRdSW3SBG9pZW834Mmhxa6terzcboz7P2R0k4lDxlkP7NYRIIdBbWp+VgwQbmMH4yV7w==} engines: {node: '>=12'} @@ -2105,6 +2172,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.13': resolution: {integrity: sha512-RIrxoKH5Eo+yE5BtaAIMZaiKutPhZjw+j0OCh8WdvKEKJQteacq0myZvBDLU+hOzQOZWJeDnuQ2xgSScKf1Ovw==} engines: {node: '>=12'} @@ -2129,6 +2202,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.13': resolution: {integrity: sha512-AfRPhHWmj9jGyLgW/2FkYERKmYR+IjYxf2rtSLmhOrPGFh0KCETFzSjx/JX/HJnvIqHt/DRQD/KAaVsUKoI3Xg==} engines: {node: '>=12'} @@ -2153,6 +2232,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.13': resolution: {integrity: sha512-pGzWWZJBInhIgdEwzn8VHUBang8UvFKsvjDkeJ2oyY5gZtAM6BaxK0QLCuZY+qoj/nx/lIaItH425rm/hloETA==} engines: {node: '>=12'} @@ -2177,6 +2262,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.13': resolution: {integrity: sha512-hCzZbVJEHV7QM77fHPv2qgBcWxgglGFGCxk6KfQx6PsVIdi1u09X7IvgE9QKqm38OpkzaAkPnnPqwRsltvLkIQ==} engines: {node: '>=12'} @@ -2201,6 +2292,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.13': resolution: {integrity: sha512-4iMxLRMCxGyk7lEvkkvrxw4aJeC93YIIrfbBlUJ062kilUUnAiMb81eEkVvCVoh3ON283ans7+OQkuy1uHW+Hw==} engines: {node: '>=12'} @@ -2225,6 +2322,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.13': resolution: {integrity: sha512-I3OKGbynl3AAIO6onXNrup/ttToE6Rv2XYfFgLK/wnr2J+1g+7k4asLrE+n7VMhaqX+BUnyWkCu27rl+62Adug==} engines: {node: '>=12'} @@ -2249,6 +2352,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.13': resolution: {integrity: sha512-8pcKDApAsKc6WW51ZEVidSGwGbebYw2qKnO1VyD8xd6JN0RN6EUXfhXmDk9Vc4/U3Y4AoFTexQewQDJGsBXBpg==} engines: {node: '>=12'} @@ -2273,6 +2382,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.13': resolution: {integrity: sha512-6GU+J1PLiVqWx8yoCK4Z0GnfKyCGIH5L2KQipxOtbNPBs+qNDcMJr9euxnyJ6FkRPyMwaSkjejzPSISD9hb+gg==} engines: {node: '>=12'} @@ -2297,6 +2412,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.13': resolution: {integrity: sha512-pfn/OGZ8tyR8YCV7MlLl5hAit2cmS+j/ZZg9DdH0uxdCoJpV7+5DbuXrR+es4ayRVKIcfS9TTMCs60vqQDmh+w==} engines: {node: '>=12'} @@ -2321,6 +2442,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.13': resolution: {integrity: sha512-aIbhU3LPg0lOSCfVeGHbmGYIqOtW6+yzO+Nfv57YblEK01oj0mFMtvDJlOaeAZ6z0FZ9D13oahi5aIl9JFphGg==} engines: {node: '>=12'} @@ -2345,6 +2472,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.13': resolution: {integrity: sha512-Pct1QwF2sp+5LVi4Iu5Y+6JsGaV2Z2vm4O9Dd7XZ5tKYxEHjFtb140fiMcl5HM1iuv6xXO8O1Vrb1iJxHlv8UA==} engines: {node: '>=12'} @@ -2369,6 +2502,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.13': resolution: {integrity: sha512-zTrIP0KzYP7O0+3ZnmzvUKgGtUvf4+piY8PIO3V8/GfmVd3ZyHJGz7Ht0np3P1wz+I8qJ4rjwJKqqEAbIEPngA==} engines: {node: '>=12'} @@ -2393,6 +2532,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.4': resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} engines: {node: '>=18'} @@ -2405,6 +2550,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.18.13': resolution: {integrity: sha512-I6zs10TZeaHDYoGxENuksxE1sxqZpCp+agYeW039yqFwh3MgVvdmXL5NMveImOC6AtpLvE4xG5ujVic4NWFIDQ==} engines: {node: '>=12'} @@ -2429,6 +2580,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.4': resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} engines: {node: '>=18'} @@ -2441,6 +2598,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.13': resolution: {integrity: sha512-W5C5nczhrt1y1xPG5bV+0M12p2vetOGlvs43LH8SopQ3z2AseIROu09VgRqydx5qFN7y9qCbpgHLx0kb0TcW7g==} engines: {node: '>=12'} @@ -2465,12 +2628,24 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.27.0': resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.18.13': resolution: {integrity: sha512-X/xzuw4Hzpo/yq3YsfBbIsipNgmsm8mE/QeWbdGdTTeZ77fjxI2K0KP3AlhZ6gU3zKTw1bKoZTuKLnqcJ537qw==} engines: {node: '>=12'} @@ -2495,6 +2670,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.13': resolution: {integrity: sha512-4CGYdRQT/ILd+yLLE5i4VApMPfGE0RPc/wFQhlluDQCK09+b4JDbxzzjpgQqTPrdnP7r5KUtGVGZYclYiPuHrw==} engines: {node: '>=12'} @@ -2519,6 +2700,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.13': resolution: {integrity: sha512-D+wKZaRhQI+MUGMH+DbEr4owC2D7XnF+uyGiZk38QbgzLcofFqIOwFs7ELmIeU45CQgfHNy9Q+LKW3cE8g37Kg==} engines: {node: '>=12'} @@ -2543,6 +2730,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.13': resolution: {integrity: sha512-iVl6lehAfJS+VmpF3exKpNQ8b0eucf5VWfzR8S7xFve64NBNz2jPUgx1X93/kfnkfgP737O+i1k54SVQS7uVZA==} engines: {node: '>=12'} @@ -2567,6 +2760,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} @@ -5228,6 +5427,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -6185,6 +6389,11 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + miniflare@4.20260710.0: + resolution: {integrity: sha512-x1LLRkU6o1p7hiKrB0TRnL0MJn6xFOT+/vrlEQINz5cRDKLP8ru4hBqWTIvXAetzr1acKAnmAaG84pQ4W/K14g==} + engines: {node: '>=22.0.0'} + hasBin: true + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -7503,6 +7712,10 @@ packages: resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} engines: {node: '>=20.18.1'} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} @@ -7736,6 +7949,21 @@ packages: engines: {node: '>=16'} hasBin: true + workerd@1.20260710.1: + resolution: {integrity: sha512-U2sBPPrb9U97sBKnnMN6Kv8p65903P35nwMkPE9vSH/bRuRqkZ3a1EjUw3jV28RhiyXpkLF77Evzw8XimFxyTw==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.111.0: + resolution: {integrity: sha512-bffpI9EyrnpKkF/1S+RaIv8oRD93GtbsA7TlfWwOsGJGB7VO3jVbdGzpC9TU7Bqom3z7jUxcte4Z9MPhaQ4HoQ==} + engines: {node: '>=22.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^5.20260710.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrangler@4.60.0: resolution: {integrity: sha512-n4kibm/xY0Qd5G2K/CbAQeVeOIlwPNVglmFjlDRCCYk3hZh8IggO/rg8AXt/vByK2Sxsugl5Z7yvgWxrUbmS6g==} engines: {node: '>=20.0.0'} @@ -7785,6 +8013,18 @@ packages: utf-8-validate: optional: true + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -10334,27 +10574,50 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} + '@cloudflare/kv-asset-handler@0.5.0': {} + '@cloudflare/unenv-preset@2.11.0(unenv@2.0.0-rc.24)(workerd@1.20260120.0)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: workerd: 1.20260120.0 + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260710.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260710.1 + '@cloudflare/workerd-darwin-64@1.20260120.0': optional: true + '@cloudflare/workerd-darwin-64@1.20260710.1': + optional: true + '@cloudflare/workerd-darwin-arm64@1.20260120.0': optional: true + '@cloudflare/workerd-darwin-arm64@1.20260710.1': + optional: true + '@cloudflare/workerd-linux-64@1.20260120.0': optional: true + '@cloudflare/workerd-linux-64@1.20260710.1': + optional: true + '@cloudflare/workerd-linux-arm64@1.20260120.0': optional: true + '@cloudflare/workerd-linux-arm64@1.20260710.1': + optional: true + '@cloudflare/workerd-windows-64@1.20260120.0': optional: true + '@cloudflare/workerd-windows-64@1.20260710.1': + optional: true + '@cloudflare/workers-types@4.20250214.0': {} '@cloudflare/workers-types@4.20260123.0': {} @@ -10418,6 +10681,9 @@ snapshots: '@esbuild/aix-ppc64@0.27.0': optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/android-arm64@0.18.13': optional: true @@ -10430,6 +10696,9 @@ snapshots: '@esbuild/android-arm64@0.27.0': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm@0.18.13': optional: true @@ -10442,6 +10711,9 @@ snapshots: '@esbuild/android-arm@0.27.0': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-x64@0.18.13': optional: true @@ -10454,6 +10726,9 @@ snapshots: '@esbuild/android-x64@0.27.0': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.18.13': optional: true @@ -10466,6 +10741,9 @@ snapshots: '@esbuild/darwin-arm64@0.27.0': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.18.13': optional: true @@ -10478,6 +10756,9 @@ snapshots: '@esbuild/darwin-x64@0.27.0': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.18.13': optional: true @@ -10490,6 +10771,9 @@ snapshots: '@esbuild/freebsd-arm64@0.27.0': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.18.13': optional: true @@ -10502,6 +10786,9 @@ snapshots: '@esbuild/freebsd-x64@0.27.0': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.18.13': optional: true @@ -10514,6 +10801,9 @@ snapshots: '@esbuild/linux-arm64@0.27.0': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm@0.18.13': optional: true @@ -10526,6 +10816,9 @@ snapshots: '@esbuild/linux-arm@0.27.0': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-ia32@0.18.13': optional: true @@ -10538,6 +10831,9 @@ snapshots: '@esbuild/linux-ia32@0.27.0': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-loong64@0.18.13': optional: true @@ -10550,6 +10846,9 @@ snapshots: '@esbuild/linux-loong64@0.27.0': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.18.13': optional: true @@ -10562,6 +10861,9 @@ snapshots: '@esbuild/linux-mips64el@0.27.0': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.18.13': optional: true @@ -10574,6 +10876,9 @@ snapshots: '@esbuild/linux-ppc64@0.27.0': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.18.13': optional: true @@ -10586,6 +10891,9 @@ snapshots: '@esbuild/linux-riscv64@0.27.0': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-s390x@0.18.13': optional: true @@ -10598,6 +10906,9 @@ snapshots: '@esbuild/linux-s390x@0.27.0': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-x64@0.18.13': optional: true @@ -10610,12 +10921,18 @@ snapshots: '@esbuild/linux-x64@0.27.0': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + '@esbuild/netbsd-arm64@0.25.4': optional: true '@esbuild/netbsd-arm64@0.27.0': optional: true + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.18.13': optional: true @@ -10628,12 +10945,18 @@ snapshots: '@esbuild/netbsd-x64@0.27.0': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.25.4': optional: true '@esbuild/openbsd-arm64@0.27.0': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.18.13': optional: true @@ -10646,9 +10969,15 @@ snapshots: '@esbuild/openbsd-x64@0.27.0': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + '@esbuild/openharmony-arm64@0.27.0': optional: true + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.18.13': optional: true @@ -10661,6 +10990,9 @@ snapshots: '@esbuild/sunos-x64@0.27.0': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.18.13': optional: true @@ -10673,6 +11005,9 @@ snapshots: '@esbuild/win32-arm64@0.27.0': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-ia32@0.18.13': optional: true @@ -10685,6 +11020,9 @@ snapshots: '@esbuild/win32-ia32@0.27.0': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-x64@0.18.13': optional: true @@ -10697,6 +11035,9 @@ snapshots: '@esbuild/win32-x64@0.27.0': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@fastify/busboy@2.1.1': {} '@graphql-tools/executor@0.0.18(graphql@16.9.0)': @@ -13697,6 +14038,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.0 '@esbuild/win32-x64': 0.27.0 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -14717,6 +15087,18 @@ snapshots: - bufferutil - utf-8-validate + miniflare@4.20260710.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.28.0 + workerd: 1.20260710.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + minimalistic-assert@1.0.1: {} minimatch@10.1.1: @@ -16358,6 +16740,8 @@ snapshots: undici@7.18.2: {} + undici@7.28.0: {} + unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 @@ -16597,6 +16981,31 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260120.0 '@cloudflare/workerd-windows-64': 1.20260120.0 + workerd@1.20260710.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260710.1 + '@cloudflare/workerd-darwin-arm64': 1.20260710.1 + '@cloudflare/workerd-linux-64': 1.20260710.1 + '@cloudflare/workerd-linux-arm64': 1.20260710.1 + '@cloudflare/workerd-windows-64': 1.20260710.1 + + wrangler@4.111.0(@cloudflare/workers-types@4.20260123.0): + dependencies: + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260710.1) + blake3-wasm: 2.1.5 + esbuild: 0.28.1 + miniflare: 4.20260710.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260710.1 + optionalDependencies: + '@cloudflare/workers-types': 4.20260123.0 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrangler@4.60.0(@cloudflare/workers-types@4.20260123.0): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 @@ -16638,6 +17047,8 @@ snapshots: ws@8.18.0: {} + ws@8.21.0: {} + xml-name-validator@4.0.0: {} xml2js@0.6.2: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 41ba1627..fa1376c9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -30,7 +30,7 @@ catalog: tsx: ^4.19.2 typescript: ^6.0.3 vitest: ^2.1.1 - wrangler: ^4.59.2 + wrangler: ^4.110.0 yargs: ^18.0.0 catalogs: From bf347d057b704767233a2a9e94aeaf199ba23502 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 15 Jul 2026 22:26:05 +0200 Subject: [PATCH 5/5] feat(cloudflare): add AsyncLocalStorage import for improved context management --- packages/cloudflare/src/cli/adapter.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cloudflare/src/cli/adapter.ts b/packages/cloudflare/src/cli/adapter.ts index 7eef247b..bab34717 100644 --- a/packages/cloudflare/src/cli/adapter.ts +++ b/packages/cloudflare/src/cli/adapter.ts @@ -61,6 +61,8 @@ export default buildAdapter((config: OpenNextConfig, buildOpts: BuildOptions) => cloudflareBanner.push( "import process from 'node:process';", "import { Buffer } from 'node:buffer';", + "import { AsyncLocalStorage as NodeAsyncLocalStorage } from 'node:async_hooks';", + "globalThis.AsyncLocalStorage = NodeAsyncLocalStorage;", "import { createRequire as topLevelCreateRequire } from 'module';", "const require = topLevelCreateRequire(import.meta.url);", "import bannerUrl from 'url';",