diff --git a/.changeset/four-bags-admire.md b/.changeset/four-bags-admire.md new file mode 100644 index 000000000000..227044f6f9ff --- /dev/null +++ b/.changeset/four-bags-admire.md @@ -0,0 +1,12 @@ +--- +"wrangler": patch +--- + +fix: add warning about `wrangler dev` with remote Durable Objects + +Durable Objects that are being bound by `script_name` will not be isolated from the +live data during development with `wrangler dev`. +This change simply warns the developer about this, so that they can back out before +accidentally changing live data. + +Fixes #319 diff --git a/packages/wrangler/src/__tests__/dev.test.tsx b/packages/wrangler/src/__tests__/dev.test.tsx index 51e4ec3f838c..210e2bba84f7 100644 --- a/packages/wrangler/src/__tests__/dev.test.tsx +++ b/packages/wrangler/src/__tests__/dev.test.tsx @@ -9,7 +9,7 @@ import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; import writeWranglerToml from "./helpers/write-wrangler-toml"; -describe("Dev component", () => { +describe("wrangler dev", () => { mockAccountId(); mockApiToken(); runInTempDir(); @@ -480,6 +480,72 @@ describe("Dev component", () => { expect(std.err).toMatchInlineSnapshot(`""`); }); }); + + describe("durable_objects", () => { + it("should warn if there is one or more remote Durable Object", async () => { + writeWranglerToml({ + main: "index.js", + durable_objects: { + bindings: [ + { name: "NAME_1", class_name: "CLASS_1" }, + { + name: "NAME_2", + class_name: "CLASS_2", + script_name: "SCRIPT_A", + }, + { name: "NAME_3", class_name: "CLASS_3" }, + { + name: "NAME_4", + class_name: "CLASS_4", + script_name: "SCRIPT_B", + }, + ], + }, + }); + fs.writeFileSync("index.js", `export default {};`); + await runWrangler("dev"); + expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("localhost"); + expect(std.out).toMatchInlineSnapshot(`""`); + expect(std.warn).toMatchInlineSnapshot(` + "WARNING: You have Durable Object bindings, which 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\\"}" + `); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should use to `ip` from `wrangler.toml`, if available", async () => { + writeWranglerToml({ + main: "index.js", + dev: { + ip: "0.0.0.0", + }, + }); + fs.writeFileSync("index.js", `export default {};`); + await runWrangler("dev"); + expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("0.0.0.0"); + expect(std.out).toMatchInlineSnapshot(`""`); + expect(std.warn).toMatchInlineSnapshot(`""`); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should use --ip command line arg, if provided", async () => { + writeWranglerToml({ + main: "index.js", + dev: { + ip: "1.1.1.1", + }, + }); + fs.writeFileSync("index.js", `export default {};`); + await runWrangler("dev --ip=0.0.0.0"); + expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("0.0.0.0"); + expect(std.out).toMatchInlineSnapshot(`""`); + expect(std.warn).toMatchInlineSnapshot(`""`); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + }); }); function mockGetZones(domain: string, zones: { id: string }[] = []) { diff --git a/packages/wrangler/src/entry.ts b/packages/wrangler/src/entry.ts index 81bc38394a00..37c5c78e29ed 100644 --- a/packages/wrangler/src/entry.ts +++ b/packages/wrangler/src/entry.ts @@ -20,7 +20,7 @@ export type Entry = { file: string; directory: string; format: CfScriptFormat }; export async function getEntry( args: { script?: string; format?: CfScriptFormat | undefined }, config: Config, - command: string + command: "dev" | "publish" ): Promise { let file: string; let directory = process.cwd(); @@ -49,7 +49,19 @@ export async function getEntry( args.format ?? config.build?.upload?.format ); - if (format === "service-worker" && hasDurableObjectImplementations(config)) { + const { localBindings, remoteBindings } = + partitionDurableObjectBindings(config); + + if (command === "dev" && remoteBindings.length > 0) { + console.warn( + "WARNING: You have Durable Object bindings, which 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") + ); + } + + if (format === "service-worker" && localBindings.length > 0) { const errorMessage = "You seem to be trying to use Durable Objects in a Worker written with Service Worker syntax."; const addScriptName = @@ -174,15 +186,26 @@ export function fileExists(filePath: string): boolean { return false; } +type DurableObjectBindings = Config["durable_objects"]["bindings"]; + /** - * Returns true if the given config contains Durable Object bindings that are implemented - * in this worker instead of being implemented elsewhere, and bound via a `script_name` - * property in wrangler.toml + * 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 hasDurableObjectImplementations(config: Config): boolean { - return config.durable_objects.bindings.some( - (binding) => binding.script_name === undefined - ); +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) { + localBindings.push(binding); + } else { + remoteBindings.push(binding); + } + } + return { localBindings, remoteBindings }; } /**