From 6c8f41f8e76890d6027fd97eaf4e88dccb509fc8 Mon Sep 17 00:00:00 2001 From: Skye <46286597+Skye-31@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:10:43 +0000 Subject: [PATCH] Fix: provide proper error message for missing DOs (#4337) --- .changeset/rude-eagles-buy.md | 7 + fixtures/durable-objects-app/package.json | 20 +++ fixtures/durable-objects-app/src/index.js | 5 + .../durable-objects-app/tests/index.test.ts | 29 ++++ .../durable-objects-app/tests/tsconfig.json | 7 + fixtures/durable-objects-app/tsconfig.json | 12 ++ fixtures/durable-objects-app/wrangler.toml | 13 ++ .../wrangler/src/__tests__/deploy.test.ts | 10 +- .../wrangler/src/deployment-bundle/bundle.ts | 12 ++ pnpm-lock.yaml | 134 ++++++++++-------- 10 files changed, 192 insertions(+), 57 deletions(-) create mode 100644 .changeset/rude-eagles-buy.md create mode 100644 fixtures/durable-objects-app/package.json create mode 100644 fixtures/durable-objects-app/src/index.js create mode 100644 fixtures/durable-objects-app/tests/index.test.ts create mode 100644 fixtures/durable-objects-app/tests/tsconfig.json create mode 100644 fixtures/durable-objects-app/tsconfig.json create mode 100644 fixtures/durable-objects-app/wrangler.toml diff --git a/.changeset/rude-eagles-buy.md b/.changeset/rude-eagles-buy.md new file mode 100644 index 000000000000..2b10921b4578 --- /dev/null +++ b/.changeset/rude-eagles-buy.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +Improve the error message when a script isn't exported a Durable Object class + +Previously, wrangler would error with a message like `Uncaught TypeError: Class extends value undefined is not a constructor or null`. This improves that messaging to be more understandable to users. diff --git a/fixtures/durable-objects-app/package.json b/fixtures/durable-objects-app/package.json new file mode 100644 index 000000000000..e40bfb30030f --- /dev/null +++ b/fixtures/durable-objects-app/package.json @@ -0,0 +1,20 @@ +{ + "name": "do-worker-app", + "version": "1.0.0", + "private": true, + "description": "", + "license": "ISC", + "author": "", + "main": "src/index.js", + "scripts": { + "dev": "wrangler deploy --dry-run", + "test": "npx vitest run", + "test:ci": "npx vitest run", + "test:watch": "npx vitest" + }, + "devDependencies": { + "@cloudflare/workers-tsconfig": "workspace:^", + "undici": "^5.23.0", + "wrangler": "workspace:*" + } +} diff --git a/fixtures/durable-objects-app/src/index.js b/fixtures/durable-objects-app/src/index.js new file mode 100644 index 000000000000..80f20d795064 --- /dev/null +++ b/fixtures/durable-objects-app/src/index.js @@ -0,0 +1,5 @@ +export default { + async fetch() { + return new Response("foo"); + }, +}; diff --git a/fixtures/durable-objects-app/tests/index.test.ts b/fixtures/durable-objects-app/tests/index.test.ts new file mode 100644 index 000000000000..1c4862f0fd5c --- /dev/null +++ b/fixtures/durable-objects-app/tests/index.test.ts @@ -0,0 +1,29 @@ +import { resolve } from "node:path"; +import { assert, describe, it } from "vitest"; +import { execSync } from "node:child_process"; + +describe("durable objects", () => { + it("should throw an error when the worker doesn't export a durable object but requires one", ({ + expect, + }) => { + let err: string = ""; + try { + execSync("pnpm run dev", { + cwd: resolve(__dirname, ".."), + }); + assert(false); // Should never reach this + } catch (e) { + err = (e as Error).message + .replaceAll("✘", "X") + .replace(/\\/g, "/") + .replace(/[^\S\n]+\n/g, "\n") + .trimEnd(); + } + expect(err).toMatchInlineSnapshot(` + "Command failed: pnpm run dev + X [ERROR] Your Worker depends on the following Durable Objects, which are not exported in your entrypoint file: FooBar. + + You should export these objects from your entrypoint, src/index.js." + `); + }); +}); diff --git a/fixtures/durable-objects-app/tests/tsconfig.json b/fixtures/durable-objects-app/tests/tsconfig.json new file mode 100644 index 000000000000..d2ce7f144694 --- /dev/null +++ b/fixtures/durable-objects-app/tests/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@cloudflare/workers-tsconfig/tsconfig.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["**/*.ts", "../../../node-types.d.ts"] +} diff --git a/fixtures/durable-objects-app/tsconfig.json b/fixtures/durable-objects-app/tsconfig.json new file mode 100644 index 000000000000..6eb14e3584b7 --- /dev/null +++ b/fixtures/durable-objects-app/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "esModuleInterop": true, + "module": "CommonJS", + "lib": ["ES2020"], + "types": ["node"], + "moduleResolution": "node", + "noEmit": true + }, + "include": ["tests", "../../node-types.d.ts"] +} diff --git a/fixtures/durable-objects-app/wrangler.toml b/fixtures/durable-objects-app/wrangler.toml new file mode 100644 index 000000000000..b324e1cb77d5 --- /dev/null +++ b/fixtures/durable-objects-app/wrangler.toml @@ -0,0 +1,13 @@ +name = "do-worker-app" +compatibility_date = "2022-03-31" + +main = "src/index.js" + +[durable_objects] +bindings = [ + { name = "MY_DO", class_name = "FooBar" } +] + +[[migrations]] +tag = "v1" +new_classes = ["FooBar"] diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 2aaee7f7b779..655910e8789d 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -7654,7 +7654,15 @@ export default{ }, migrations: [{ tag: "v1", new_classes: ["SomeClass"] }], }); - writeWorkerSource(); + fs.writeFileSync( + "index.js", + `export default { + async fetch(request) { + return new Response('Hello' + foo); + }, + }; + export class SomeClass {};` + ); process.env.CLOUDFLARE_ACCOUNT_ID = ""; await runWrangler("deploy index.js --dry-run"); expect(std).toMatchInlineSnapshot(` diff --git a/packages/wrangler/src/deployment-bundle/bundle.ts b/packages/wrangler/src/deployment-bundle/bundle.ts index f848c08e4a5f..4e7e21020e4e 100644 --- a/packages/wrangler/src/deployment-bundle/bundle.ts +++ b/packages/wrangler/src/deployment-bundle/bundle.ts @@ -378,6 +378,18 @@ export async function bundleWorker( } const entryPoint = getEntryPointFromMetafile(entryFile, result.metafile); + const notExportedDOs = doBindings + .filter((x) => !x.script_name && !entryPoint.exports.includes(x.class_name)) + .map((x) => x.class_name); + if (notExportedDOs.length) { + const relativePath = path.relative(process.cwd(), entryFile); + throw new Error( + `Your Worker depends on the following Durable Objects, which are not exported in your entrypoint file: ${notExportedDOs.join( + ", " + )}.\nYou should export these objects from your entrypoint, ${relativePath}.` + ); + } + const bundleType = entryPoint.exports.length > 0 ? "esm" : "commonjs"; const sourceMapPath = Object.keys(result.metafile.outputs).filter((_path) => diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abec3efe3d9d..d0d38189e5d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,6 +106,18 @@ importers: specifier: workspace:* version: link:../../packages/wrangler + fixtures/durable-objects-app: + devDependencies: + '@cloudflare/workers-tsconfig': + specifier: workspace:^ + version: link:../../packages/workers-tsconfig + undici: + specifier: ^5.23.0 + version: 5.23.0 + wrangler: + specifier: workspace:* + version: link:../../packages/wrangler + fixtures/external-durable-objects-app: devDependencies: '@cloudflare/workers-tsconfig': @@ -691,7 +703,7 @@ importers: version: 8.49.0 eslint-config-turbo: specifier: latest - version: 1.10.15(eslint@8.49.0) + version: 1.10.13(eslint@8.49.0) eslint-plugin-import: specifier: 2.26.x version: 2.26.0(@typescript-eslint/parser@6.7.2)(eslint@8.49.0) @@ -3639,51 +3651,6 @@ packages: marked: 0.3.19 dev: false - /@cloudflare/workerd-darwin-64@1.20231030.0: - resolution: {integrity: sha512-J4PQ9utPxLya9yHdMMx3AZeC5M/6FxcoYw6jo9jbDDFTy+a4Gslqf4Im9We3aeOEdPXa3tgQHVQOSelJSZLhIw==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@cloudflare/workerd-darwin-arm64@1.20231030.0: - resolution: {integrity: sha512-WSJJjm11Del4hSneiNB7wTXGtBXI4QMCH9l5qf4iT5PAW8cESGcCmdHtWDWDtGAAGcvmLT04KNvmum92vRKKQQ==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@cloudflare/workerd-linux-64@1.20231030.0: - resolution: {integrity: sha512-2HUeRTvoCC17fxE0qdBeR7J9dO8j4A8ZbdcvY8pZxdk+zERU6+N03RTbk/dQMU488PwiDvcC3zZqS4gwLfVT8g==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@cloudflare/workerd-linux-arm64@1.20231030.0: - resolution: {integrity: sha512-4/GK5zHh+9JbUI6Z5xTCM0ZmpKKHk7vu9thmHjUxtz+o8Ne9DoD7DlDvXQWgMF6XGaTubDWyp3ttn+Qv8jDFuQ==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@cloudflare/workerd-windows-64@1.20231030.0: - resolution: {integrity: sha512-fb/Jgj8Yqy3PO1jLhk7mTrHMkR8jklpbQFud6rL/aMAn5d6MQbaSrYOCjzkKGp0Zng8D2LIzSl+Fc0C9Sggxjg==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@cloudflare/workers-types@3.18.0: resolution: {integrity: sha512-ehKOJVLMeR+tZkYhWEaLYQxl0TaIZu/kE86HF3/RidR8Xv5LuQxpbh+XXAoKVqsaphWLhIgBhgnlN5HGdheXSQ==} @@ -9955,13 +9922,13 @@ packages: eslint: 8.49.0 dev: true - /eslint-config-turbo@1.10.15(eslint@8.49.0): - resolution: {integrity: sha512-76mpx2x818JZE26euen14utYcFDxOahZ9NaWA+6Xa4pY2ezVKVschuOxS96EQz3o3ZRSmcgBOapw/gHbN+EKxQ==} + /eslint-config-turbo@1.10.13(eslint@8.49.0): + resolution: {integrity: sha512-Ffa0SxkRCPMtfUX/HDanEqsWoLwZTQTAXO9W4IsOtycb2MzJDrVcLmoFW5sMwCrg7gjqbrC4ZJoD+1SPPzIVqg==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 8.49.0 - eslint-plugin-turbo: 1.10.15(eslint@8.49.0) + eslint-plugin-turbo: 1.10.13(eslint@8.49.0) dev: false /eslint-import-resolver-node@0.3.7: @@ -10388,8 +10355,8 @@ packages: - typescript dev: true - /eslint-plugin-turbo@1.10.15(eslint@8.49.0): - resolution: {integrity: sha512-Tv4QSKV/U56qGcTqS/UgOvb9HcKFmWOQcVh3HEaj7of94lfaENgfrtK48E2CckQf7amhKs1i+imhCsNCKjkQyA==} + /eslint-plugin-turbo@1.10.13(eslint@8.49.0): + resolution: {integrity: sha512-el4AAmn0zXmvHEyp1h0IQMfse10Vy8g5Vbg4IU3+vD9CSj5sDbX07iFVt8sCKg7og9Q5FAa9mXzlCf7t4vYgzg==} peerDependencies: eslint: '>6.6.0' dependencies: @@ -18707,11 +18674,11 @@ packages: hasBin: true requiresBuild: true optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20231030.0 - '@cloudflare/workerd-darwin-arm64': 1.20231030.0 - '@cloudflare/workerd-linux-64': 1.20231030.0 - '@cloudflare/workerd-linux-arm64': 1.20231030.0 - '@cloudflare/workerd-windows-64': 1.20231030.0 + '@cloudflare/workerd-darwin-64': registry.npmjs.org/@cloudflare/workerd-darwin-64@1.20231030.0 + '@cloudflare/workerd-darwin-arm64': registry.npmjs.org/@cloudflare/workerd-darwin-arm64@1.20231030.0 + '@cloudflare/workerd-linux-64': registry.npmjs.org/@cloudflare/workerd-linux-64@1.20231030.0 + '@cloudflare/workerd-linux-arm64': registry.npmjs.org/@cloudflare/workerd-linux-arm64@1.20231030.0 + '@cloudflare/workerd-windows-64': registry.npmjs.org/@cloudflare/workerd-windows-64@1.20231030.0 dev: false /wrap-ansi@6.2.0: @@ -19010,3 +18977,58 @@ packages: name: yoga-layout version: 2.0.0-beta.1 dev: true + + registry.npmjs.org/@cloudflare/workerd-darwin-64@1.20231030.0: + resolution: {integrity: sha512-J4PQ9utPxLya9yHdMMx3AZeC5M/6FxcoYw6jo9jbDDFTy+a4Gslqf4Im9We3aeOEdPXa3tgQHVQOSelJSZLhIw==, registry: https://registry-gateway.cloudflare-ui.workers.dev/, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20231030.0.tgz} + name: '@cloudflare/workerd-darwin-64' + version: 1.20231030.0 + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + registry.npmjs.org/@cloudflare/workerd-darwin-arm64@1.20231030.0: + resolution: {integrity: sha512-WSJJjm11Del4hSneiNB7wTXGtBXI4QMCH9l5qf4iT5PAW8cESGcCmdHtWDWDtGAAGcvmLT04KNvmum92vRKKQQ==, registry: https://registry-gateway.cloudflare-ui.workers.dev/, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20231030.0.tgz} + name: '@cloudflare/workerd-darwin-arm64' + version: 1.20231030.0 + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + registry.npmjs.org/@cloudflare/workerd-linux-64@1.20231030.0: + resolution: {integrity: sha512-2HUeRTvoCC17fxE0qdBeR7J9dO8j4A8ZbdcvY8pZxdk+zERU6+N03RTbk/dQMU488PwiDvcC3zZqS4gwLfVT8g==, registry: https://registry-gateway.cloudflare-ui.workers.dev/, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20231030.0.tgz} + name: '@cloudflare/workerd-linux-64' + version: 1.20231030.0 + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + registry.npmjs.org/@cloudflare/workerd-linux-arm64@1.20231030.0: + resolution: {integrity: sha512-4/GK5zHh+9JbUI6Z5xTCM0ZmpKKHk7vu9thmHjUxtz+o8Ne9DoD7DlDvXQWgMF6XGaTubDWyp3ttn+Qv8jDFuQ==, registry: https://registry-gateway.cloudflare-ui.workers.dev/, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20231030.0.tgz} + name: '@cloudflare/workerd-linux-arm64' + version: 1.20231030.0 + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + registry.npmjs.org/@cloudflare/workerd-windows-64@1.20231030.0: + resolution: {integrity: sha512-fb/Jgj8Yqy3PO1jLhk7mTrHMkR8jklpbQFud6rL/aMAn5d6MQbaSrYOCjzkKGp0Zng8D2LIzSl+Fc0C9Sggxjg==, registry: https://registry-gateway.cloudflare-ui.workers.dev/, tarball: https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20231030.0.tgz} + name: '@cloudflare/workerd-windows-64' + version: 1.20231030.0 + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true