diff --git a/.changeset/shiny-ghosts-flash.md b/.changeset/shiny-ghosts-flash.md new file mode 100644 index 000000000000..ce3b6a2bc522 --- /dev/null +++ b/.changeset/shiny-ghosts-flash.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: stop getPlatformProxy crashing when internal DOs are present + +Internal DOs still do not work with getPlatformProxy, but warn instead of crashing. diff --git a/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts b/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts index 4fb22fb13e35..ac243cef576e 100644 --- a/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts +++ b/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts @@ -1,6 +1,14 @@ import path from "path"; import { D1Database, R2Bucket } from "@cloudflare/workers-types"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + MockInstance, + vi, +} from "vitest"; import { getPlatformProxy } from "./shared"; import type { Hyperdrive, KVNamespace } from "@cloudflare/workers-types"; import type { Unstable_DevWorker } from "wrangler"; @@ -197,6 +205,52 @@ describe("getPlatformProxy - env", () => { } }); + describe("DO warnings", () => { + let warn = {} as MockInstance; + beforeEach(() => { + warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + }); + afterEach(() => { + warn.mockRestore(); + }); + + it("warns about internal DOs and doesn't crash", async () => { + await getPlatformProxy({ + configPath: path.join(__dirname, "..", "wrangler_internal_do.jsonc"), + }); + expect(warn).toMatchInlineSnapshot(` + [MockFunction warn] { + "calls": [ + [ + "▲ [WARNING]  You have defined bindings to the following internal Durable Objects: + + - {"class_name":"MyDurableObject","name":"MY_DURABLE_OBJECT"} + These will not work in local development, but they should work in production. + + If you want to develop these locally, you can define your DO in a separate Worker, with a separate configuration file. + For detailed instructions, refer to the Durable Objects section here: https://developers.cloudflare.com/workers/wrangler/api#supported-bindings + + ", + ], + ], + "results": [ + { + "type": "return", + "value": undefined, + }, + ], + } + `); + }); + + it("doesn't warn about external DOs and doesn't crash", async () => { + await getPlatformProxy({ + configPath: path.join(__dirname, "..", "wrangler_external_do.jsonc"), + }); + expect(warn).not.toHaveBeenCalled(); + }); + }); + describe("with a target environment", () => { it("should provide bindings targeting a specified environment and also inherit top-level ones", async () => { const { env, dispose } = await getPlatformProxy({ diff --git a/fixtures/get-platform-proxy/wrangler_external_do.jsonc b/fixtures/get-platform-proxy/wrangler_external_do.jsonc new file mode 100644 index 000000000000..597fd3bc7087 --- /dev/null +++ b/fixtures/get-platform-proxy/wrangler_external_do.jsonc @@ -0,0 +1,24 @@ +{ + "name": "external-do", + "main": "src/index.ts", + // external durable object = binding has script_name. This indicates that + // the DO is exported in a separate Worker called `do-worker`. For the + // purposes of testing, we don't need to set that up because + // getPlatformProxy would not be involved in running that Worker. + + "durable_objects": { + "bindings": [ + { + "class_name": "MyDurableObject", + "name": "MY_DURABLE_OBJECT", + "script_name": "do-worker", + }, + ], + }, + "migrations": [ + { + "new_sqlite_classes": ["MyDurableObject"], + "tag": "v1", + }, + ], +} diff --git a/fixtures/get-platform-proxy/wrangler_internal_do.jsonc b/fixtures/get-platform-proxy/wrangler_internal_do.jsonc new file mode 100644 index 000000000000..f36328c7adb2 --- /dev/null +++ b/fixtures/get-platform-proxy/wrangler_internal_do.jsonc @@ -0,0 +1,23 @@ +{ + "name": "internal-do", + "main": "src/index.ts", + // Internal durable object = the binding does not specify a script name. + // This implies the DO is exported alongside this worker in `index.ts`, + // which it isn't actually. However we don't care about this here because + // getPlatformProxy will discard all user code anyway. We are simply making + // sure the warning shows up. + "durable_objects": { + "bindings": [ + { + "class_name": "MyDurableObject", + "name": "MY_DURABLE_OBJECT", + }, + ], + }, + "migrations": [ + { + "new_sqlite_classes": ["MyDurableObject"], + "tag": "v1", + }, + ], +} diff --git a/packages/wrangler/e2e/__snapshots__/pages-dev.test.ts.snap b/packages/wrangler/e2e/__snapshots__/pages-dev.test.ts.snap index 16e127c1b285..69f29cf5a467 100644 --- a/packages/wrangler/e2e/__snapshots__/pages-dev.test.ts.snap +++ b/packages/wrangler/e2e/__snapshots__/pages-dev.test.ts.snap @@ -2,11 +2,6 @@ exports[`Pages 'wrangler pages dev' > should merge (with override) \`wrangler.toml\` configuration with configuration provided via the command line, with command line args taking precedence 1`] = ` "✨ Compiled Worker successfully -▲ [WARNING] WARNING: You have Durable Object bindings that are not defined locally in the worker being developed. - Be aware that changes to the data stored in these Durable Objects will be permanent and affect the live instances. - Remote Durable Objects that are affected: - - {"name":"DO_BINDING_1_TOML","class_name":"DO_1_TOML","script_name":"DO_SCRIPT_1_TOML"} - - {"name":"DO_BINDING_2_TOML","class_name":"DO_2_TOML","script_name":"DO_SCRIPT_2_TOML"} Your Worker and resources are simulated locally via Miniflare. For more information, see: https://developers.cloudflare.com/workers/testing/local-development. Your worker has access to the following bindings: - Durable Objects: diff --git a/packages/wrangler/src/__tests__/dev.test.ts b/packages/wrangler/src/__tests__/dev.test.ts index 5d142e37f387..84f389dc0b7e 100644 --- a/packages/wrangler/src/__tests__/dev.test.ts +++ b/packages/wrangler/src/__tests__/dev.test.ts @@ -1288,15 +1288,6 @@ describe.sequential("wrangler dev", () => { https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ for more details. - - ▲ [WARNING] WARNING: You have Durable Object bindings that are not defined locally in the worker being developed. - - Be aware that changes to the data stored in these Durable Objects will be permanent and affect the - live instances. - Remote Durable Objects that are affected: - - {\\"name\\":\\"NAME_2\\",\\"class_name\\":\\"CLASS_2\\",\\"script_name\\":\\"SCRIPT_A\\"} - - {\\"name\\":\\"NAME_4\\",\\"class_name\\":\\"CLASS_4\\",\\"script_name\\":\\"SCRIPT_B\\"} - " `); }); diff --git a/packages/wrangler/src/api/integrations/platform/index.ts b/packages/wrangler/src/api/integrations/platform/index.ts index dad23f82f2a2..5f2f8105875a 100644 --- a/packages/wrangler/src/api/integrations/platform/index.ts +++ b/packages/wrangler/src/api/integrations/platform/index.ts @@ -1,6 +1,7 @@ import { kCurrentWorker, Miniflare } from "miniflare"; import { getAssetsOptions } from "../../../assets"; import { readConfig } from "../../../config"; +import { partitionDurableObjectBindings } from "../../../deployment-bundle/entry"; import { DEFAULT_MODULE_RULES } from "../../../deployment-bundle/rules"; import { getBindings } from "../../../dev"; import { getBoundRegisteredWorkers } from "../../../dev-registry"; @@ -11,7 +12,9 @@ import { buildSitesOptions, } from "../../../dev/miniflare"; import { run } from "../../../experimental-flags"; +import { logger } from "../../../logger"; import { getSiteAssetPaths } from "../../../sites"; +import { dedent } from "../../../utils/dedent"; import { CacheStorage } from "./caches"; import { ExecutionContext } from "./executionContext"; import { getServiceBindings } from "./services"; @@ -129,6 +132,7 @@ export async function getPlatformProxy< }; } +// this is only used by getPlatformProxy async function getMiniflareOptionsFromConfig( rawConfig: Config, env: string | undefined, @@ -136,6 +140,19 @@ async function getMiniflareOptionsFromConfig( ): Promise> { const bindings = getBindings(rawConfig, env, true, {}); + if (rawConfig["durable_objects"]) { + const { localBindings } = partitionDurableObjectBindings(rawConfig); + if (localBindings.length > 0) { + logger.warn(dedent` + You have defined bindings to the following internal Durable Objects: + ${localBindings.map((b) => `- ${JSON.stringify(b)}`).join("\n")} + These will not work in local development, but they should work in production. + + If you want to develop these locally, you can define your DO in a separate Worker, with a separate configuration file. + For detailed instructions, refer to the Durable Objects section here: https://developers.cloudflare.com/workers/wrangler/api#supported-bindings + `); + } + } const workerDefinitions = await getBoundRegisteredWorkers({ name: rawConfig.name, services: bindings.services, @@ -143,7 +160,7 @@ async function getMiniflareOptionsFromConfig( }); const { bindingOptions, externalWorkers } = buildMiniflareBindingOptions({ - name: undefined, + name: rawConfig.name, bindings, workerDefinitions, queueConsumers: undefined, @@ -162,6 +179,7 @@ async function getMiniflareOptionsFromConfig( { script: "", modules: true, + name: rawConfig.name, ...bindingOptions, serviceBindings: { ...serviceBindings, diff --git a/packages/wrangler/src/deployment-bundle/entry.ts b/packages/wrangler/src/deployment-bundle/entry.ts index 39cb20ef3c03..907ded2bcaa0 100644 --- a/packages/wrangler/src/deployment-bundle/entry.ts +++ b/packages/wrangler/src/deployment-bundle/entry.ts @@ -2,7 +2,6 @@ import path from "node:path"; import dedent from "ts-dedent"; import { configFileName, formatConfigSnippet } from "../config"; import { UserError } from "../errors"; -import { logger } from "../logger"; import { sniffUserAgent } from "../package-manager"; import guessWorkerFormat from "./guess-worker-format"; import { @@ -130,17 +129,7 @@ export async function getEntry( config.tsconfig ); - const { localBindings, remoteBindings } = - partitionDurableObjectBindings(config); - - if (command === "dev" && remoteBindings.length > 0) { - logger.warn( - "WARNING: You have Durable Object bindings that are not defined locally in the worker being developed.\n" + - "Be aware that changes to the data stored in these Durable Objects will be permanent and affect the live instances.\n" + - "Remote Durable Objects that are affected:\n" + - remoteBindings.map((b) => `- ${JSON.stringify(b)}`).join("\n") - ); - } + const { localBindings } = partitionDurableObjectBindings(config); if (format === "service-worker" && localBindings.length > 0) { const errorMessage = @@ -171,14 +160,17 @@ export async function getEntry( * Groups the durable object bindings into two lists: * those that are defined locally and those that refer to a durable object defined in another script. */ -function partitionDurableObjectBindings(config: Config): { +export function partitionDurableObjectBindings(config: Config): { localBindings: DurableObjectBindings; remoteBindings: DurableObjectBindings; } { const localBindings: DurableObjectBindings = []; const remoteBindings: DurableObjectBindings = []; for (const binding of config.durable_objects.bindings) { - if (binding.script_name === undefined) { + if ( + binding.script_name === undefined || + binding.script_name === config.name + ) { localBindings.push(binding); } else { remoteBindings.push(binding);