From ab1b79b76210888560a72cfca540d65226bfec20 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Mon, 9 Jun 2025 17:12:26 +0100 Subject: [PATCH 01/12] Initial support for VSCode Javascript Debug Terminals --- fixtures/worker-app/src/index.js | 2 +- fixtures/worker-ts/src/index.ts | 4 +- fixtures/worker-ts/wrangler.jsonc | 1 + packages/miniflare/src/index.ts | 5 +- packages/miniflare/src/runtime/index.ts | 67 ++++++- .../src/api/startDevWorker/ProxyController.ts | 174 ++++++++++-------- .../wrangler/src/api/startDevWorker/events.ts | 2 +- .../wrangler/src/api/startDevWorker/types.ts | 2 +- packages/wrangler/src/dev/hotkeys.ts | 14 +- 9 files changed, 179 insertions(+), 92 deletions(-) diff --git a/fixtures/worker-app/src/index.js b/fixtures/worker-app/src/index.js index f8d72b34abed..fc77eedeffaa 100644 --- a/fixtures/worker-app/src/index.js +++ b/fixtures/worker-app/src/index.js @@ -20,7 +20,7 @@ function hexEncode(array) { export default { async fetch(request, env) { - console.log("request log"); + console.log("request log 5"); const { pathname, origin, hostname, host } = new URL(request.url); if (pathname.startsWith("/fav")) diff --git a/fixtures/worker-ts/src/index.ts b/fixtures/worker-ts/src/index.ts index 6a010a461e9d..e3ae37925011 100644 --- a/fixtures/worker-ts/src/index.ts +++ b/fixtures/worker-ts/src/index.ts @@ -28,7 +28,9 @@ export default { ctx: ExecutionContext ): Promise { const url = new URL(request.url); + const a = 29; + console.log(url.hash); if (url.pathname === "/error") throw new Error("Hello Error"); - return new Response("Hello World!"); + return env.APP.fetch(request); }, }; diff --git a/fixtures/worker-ts/wrangler.jsonc b/fixtures/worker-ts/wrangler.jsonc index e373588ae2c7..c854a63b343c 100644 --- a/fixtures/worker-ts/wrangler.jsonc +++ b/fixtures/worker-ts/wrangler.jsonc @@ -2,4 +2,5 @@ "name": "worker-ts", "main": "src/index.ts", "compatibility_date": "2023-05-04", + "services": [{ "binding": "APP", "service": "worker-app" }], } diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index 38ccd0cadd72..2e722e67089c 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -2010,7 +2010,10 @@ export class Miniflare { }; const maybeSocketPorts = await this.#runtime.updateConfig( configBuffer, - runtimeOpts + runtimeOpts, + this.#workerOpts + .filter((w) => w.core.name) + .map((w) => w.core.name) as string[] ); if (this.#disposeController.signal.aborted) return; if (maybeSocketPorts === undefined) { diff --git a/packages/miniflare/src/runtime/index.ts b/packages/miniflare/src/runtime/index.ts index cd5070e5ab09..6c42af168275 100644 --- a/packages/miniflare/src/runtime/index.ts +++ b/packages/miniflare/src/runtime/index.ts @@ -1,6 +1,8 @@ import assert from "assert"; -import childProcess from "child_process"; +import childProcess, { spawn } from "child_process"; +import { randomBytes } from "crypto"; import { Abortable, once } from "events"; +import path from "path"; import rl from "readline"; import { Readable } from "stream"; import { $ as $colors, red } from "kleur/colors"; @@ -119,13 +121,35 @@ function getRuntimeArgs(options: RuntimeOptions) { return args; } +function getInspectorOptions() { + const value = process.env.VSCODE_INSPECTOR_OPTIONS; + if (!value) { + return undefined; + } + + const ownOptions = value + .split(":::") + .reverse() + .find((v) => !!v); + if (!ownOptions) { + return; + } + + try { + return JSON.parse(ownOptions); + } catch { + return undefined; + } +} + export class Runtime { #process?: childProcess.ChildProcess; #processExitPromise?: Promise; async updateConfig( configBuffer: Buffer, - options: Abortable & RuntimeOptions + options: Abortable & RuntimeOptions, + workers: string[] | undefined ): Promise { // 1. Stop existing process (if any) and wait for exit await this.dispose(); @@ -156,7 +180,44 @@ export class Runtime { await once(runtimeProcess.stdin, "finish"); // 4. Wait for sockets to start listening - return waitForPorts(controlPipe, options); + const ports = await waitForPorts(controlPipe, options); + if (ports?.has(kInspectorSocket) && process.env.VSCODE_INSPECTOR_OPTIONS) { + // We have an inspector socket and we're in a VSCode Debug Terminal. + // Let's startup a watchdog service to register ourselves as a debuggable target + + // First, we need to _find_ the watchdog script. It's located next to bootloader.js, which should be injected as a require hook + const bootloaderPath = + process.env.NODE_OPTIONS?.match(/--require "(.*?)"/)?.[1]; + + if (!bootloaderPath) { + return ports; + } + const watchdogPath = path.resolve(bootloaderPath, "../watchdog.js"); + + const info = getInspectorOptions(); + + for (const worker of workers ?? []) { + const p = spawn(process.execPath, [watchdogPath], { + env: { + NODE_INSPECTOR_INFO: JSON.stringify({ + ipcAddress: info.inspectorIpc || "", + pid: String(this.#process.pid), + scriptName: worker, + inspectorURL: `ws://127.0.0.1:${ports?.get(kInspectorSocket)}/core:user:${worker}`, + waitForDebugger: true, + ownId: randomBytes(12).toString("hex"), + openerId: info.openerId, + }), + NODE_SKIP_PLATFORM_CHECK: process.env.NODE_SKIP_PLATFORM_CHECK, + ELECTRON_RUN_AS_NODE: "1", + }, + stdio: "ignore", + detached: true, + }); + p.unref(); + } + } + return ports; } dispose(): Awaitable { diff --git a/packages/wrangler/src/api/startDevWorker/ProxyController.ts b/packages/wrangler/src/api/startDevWorker/ProxyController.ts index 52ad0f54f577..162958036736 100644 --- a/packages/wrangler/src/api/startDevWorker/ProxyController.ts +++ b/packages/wrangler/src/api/startDevWorker/ProxyController.ts @@ -41,7 +41,7 @@ import type { } from "./events"; import type { StartDevWorkerOptions } from "./types"; import type { DeferredPromise } from "./utils"; -import type { MiniflareOptions } from "miniflare"; +import type { MiniflareOptions, WorkerOptions } from "miniflare"; type ProxyControllerEventMap = ControllerEventMap & { ready: [ReadyEvent]; @@ -65,6 +65,11 @@ export class ProxyController extends Controller { } assert(this.latestConfig !== undefined); + // If we're in a JavaScript Debug terminal, Miniflare will send the inspector ports directly to VSCode for registration + // As such, we don't need our inspector proxy and in fact including it causes issue with multiple clients connected to the + // inspector endpoint. + const inVscodeJsDebugTerminal = !!process.env.VSCODE_INSPECTOR_OPTIONS; + const cert = this.latestConfig.dev?.server?.secure || this.latestConfig.dev?.inspector?.secure @@ -74,86 +79,89 @@ export class ProxyController extends Controller { ) : undefined; - const proxyWorkerOptions: MiniflareOptions = { - host: this.latestConfig.dev?.server?.hostname, - port: this.latestConfig.dev?.server?.port, - https: this.latestConfig.dev?.server?.secure, - httpsCert: cert?.cert, - httpsKey: cert?.key, - - workers: [ - { - name: "ProxyWorker", - compatibilityDate: "2023-12-18", - compatibilityFlags: ["nodejs_compat"], - modulesRoot: path.dirname(proxyWorkerPath), - modules: [{ type: "ESModule", path: proxyWorkerPath }], - durableObjects: { - DURABLE_OBJECT: { - className: "ProxyWorker", - unsafePreventEviction: true, - }, - }, - // Miniflare will strip CF-Connecting-IP from outgoing fetches from a Worker (to fix https://github.com/cloudflare/workers-sdk/issues/7924) - // However, the proxy worker only makes outgoing requests to the user Worker Miniflare instance, which _should_ receive CF-Connecting-IP - stripCfConnectingIp: false, - serviceBindings: { - PROXY_CONTROLLER: async (req): Promise => { - const message = - (await req.json()) as ProxyWorkerOutgoingRequestBody; - - this.onProxyWorkerMessage(message); - - return new Response(null, { status: 204 }); - }, - }, - bindings: { - PROXY_CONTROLLER_AUTH_SECRET: this.secret, - }, + const inspectorProxyWorkerOptions: WorkerOptions = { + name: "InspectorProxyWorker", + compatibilityDate: "2023-12-18", + compatibilityFlags: ["nodejs_compat", "increase_websocket_message_size"], + modulesRoot: path.dirname(inspectorProxyWorkerPath), + modules: [{ type: "ESModule", path: inspectorProxyWorkerPath }], + durableObjects: { + DURABLE_OBJECT: { + className: "InspectorProxyWorker", + unsafePreventEviction: true, + }, + }, + serviceBindings: { + PROXY_CONTROLLER: async (req): Promise => { + const body = + (await req.json()) as InspectorProxyWorkerOutgoingRequestBody; - // no need to use file-system, so don't - cache: false, - unsafeEphemeralDurableObjects: true, + return this.onInspectorProxyWorkerRequest(body); }, + }, + bindings: { + PROXY_CONTROLLER_AUTH_SECRET: this.secret, + }, + + unsafeDirectSockets: [ { - name: "InspectorProxyWorker", - compatibilityDate: "2023-12-18", - compatibilityFlags: [ - "nodejs_compat", - "increase_websocket_message_size", - ], - modulesRoot: path.dirname(inspectorProxyWorkerPath), - modules: [{ type: "ESModule", path: inspectorProxyWorkerPath }], - durableObjects: { - DURABLE_OBJECT: { - className: "InspectorProxyWorker", - unsafePreventEviction: true, - }, - }, - serviceBindings: { - PROXY_CONTROLLER: async (req): Promise => { - const body = - (await req.json()) as InspectorProxyWorkerOutgoingRequestBody; + host: this.latestConfig.dev?.inspector?.hostname, + port: this.latestConfig.dev?.inspector?.port ?? 0, + }, + ], - return this.onInspectorProxyWorkerRequest(body); - }, - }, - bindings: { - PROXY_CONTROLLER_AUTH_SECRET: this.secret, + // no need to use file-system, so don't + cache: false, + unsafeEphemeralDurableObjects: true, + }; + + const workers: WorkerOptions[] = [ + { + name: "ProxyWorker", + compatibilityDate: "2023-12-18", + compatibilityFlags: ["nodejs_compat"], + modulesRoot: path.dirname(proxyWorkerPath), + modules: [{ type: "ESModule", path: proxyWorkerPath }], + durableObjects: { + DURABLE_OBJECT: { + className: "ProxyWorker", + unsafePreventEviction: true, }, + }, + // Miniflare will strip CF-Connecting-IP from outgoing fetches from a Worker (to fix https://github.com/cloudflare/workers-sdk/issues/7924) + // However, the proxy worker only makes outgoing requests to the user Worker Miniflare instance, which _should_ receive CF-Connecting-IP + stripCfConnectingIp: false, + serviceBindings: { + PROXY_CONTROLLER: async (req): Promise => { + const message = + (await req.json()) as ProxyWorkerOutgoingRequestBody; - unsafeDirectSockets: [ - { - host: this.latestConfig.dev?.inspector?.hostname, - port: this.latestConfig.dev?.inspector?.port ?? 0, - }, - ], + this.onProxyWorkerMessage(message); - // no need to use file-system, so don't - cache: false, - unsafeEphemeralDurableObjects: true, + return new Response(null, { status: 204 }); + }, }, - ], + bindings: { + PROXY_CONTROLLER_AUTH_SECRET: this.secret, + }, + + // no need to use file-system, so don't + cache: false, + unsafeEphemeralDurableObjects: true, + }, + ]; + if (!inVscodeJsDebugTerminal) { + workers.push(inspectorProxyWorkerOptions); + } + + const proxyWorkerOptions: MiniflareOptions = { + host: this.latestConfig.dev?.server?.hostname, + port: this.latestConfig.dev?.server?.port, + https: this.latestConfig.dev?.server?.secure, + httpsCert: cert?.cert, + httpsKey: cert?.key, + + workers, verbose: logger.loggerLevel === "debug", @@ -195,19 +203,23 @@ export class ProxyController extends Controller { if (willInstantiateMiniflareInstance) { void Promise.all([ proxyWorker.ready, - proxyWorker.unsafeGetDirectURL("InspectorProxyWorker"), + !inVscodeJsDebugTerminal + ? proxyWorker.unsafeGetDirectURL("InspectorProxyWorker") + : undefined, ]) .then(([url, inspectorUrl]) => { // Don't connect the inspector proxy worker until we have a valid ready Miniflare instance. // Otherwise, tearing down the ProxyController immediately after setting it up // will result in proxyWorker.ready throwing, but reconnectInspectorProxyWorker hanging for ever, // preventing teardown - return this.reconnectInspectorProxyWorker().then(() => [ - url, - inspectorUrl, - ]); + return ( + !inVscodeJsDebugTerminal + ? this.reconnectInspectorProxyWorker() + : Promise.resolve() + ).then(() => [url, inspectorUrl]); }) .then(([url, inspectorUrl]) => { + assert(url !== undefined); this.emitReadyEvent(proxyWorker, url, inspectorUrl); }) .catch((error) => { @@ -524,7 +536,11 @@ export class ProxyController extends Controller { // Event Dispatchers // ********************* - emitReadyEvent(proxyWorker: Miniflare, url: URL, inspectorUrl: URL) { + emitReadyEvent( + proxyWorker: Miniflare, + url: URL, + inspectorUrl: URL | undefined + ) { const data: ReadyEvent = { type: "ready", proxyWorker, diff --git a/packages/wrangler/src/api/startDevWorker/events.ts b/packages/wrangler/src/api/startDevWorker/events.ts index 5d4ed90528f4..413180dd5fc3 100644 --- a/packages/wrangler/src/api/startDevWorker/events.ts +++ b/packages/wrangler/src/api/startDevWorker/events.ts @@ -87,7 +87,7 @@ export type ReadyEvent = { type: "ready"; proxyWorker: Miniflare; url: URL; - inspectorUrl: URL; + inspectorUrl: URL | undefined; }; // ProxyWorker diff --git a/packages/wrangler/src/api/startDevWorker/types.ts b/packages/wrangler/src/api/startDevWorker/types.ts index f8f5da8086d9..66048e6c78f7 100644 --- a/packages/wrangler/src/api/startDevWorker/types.ts +++ b/packages/wrangler/src/api/startDevWorker/types.ts @@ -49,7 +49,7 @@ type MiniflareWorker = Awaited>; export interface Worker { ready: Promise; url: Promise; - inspectorUrl: Promise; + inspectorUrl: Promise; config: StartDevWorkerOptions; setConfig: ConfigController["set"]; patchConfig: ConfigController["patch"]; diff --git a/packages/wrangler/src/dev/hotkeys.ts b/packages/wrangler/src/dev/hotkeys.ts index 37a71e10772a..e6c1d7b508e1 100644 --- a/packages/wrangler/src/dev/hotkeys.ts +++ b/packages/wrangler/src/dev/hotkeys.ts @@ -23,11 +23,15 @@ export default function registerDevHotKeys( handler: async () => { const { inspectorUrl } = await devEnv.proxy.ready.promise; - // TODO: refactor this function to accept a whole URL (not just .port and assuming .hostname) - await openInspector( - parseInt(inspectorUrl.port), - devEnv.config.latestConfig?.name - ); + if (!inspectorUrl) { + logger.error("Inspector not available"); + } else { + // TODO: refactor this function to accept a whole URL (not just .port and assuming .hostname) + await openInspector( + parseInt(inspectorUrl.port), + devEnv.config.latestConfig?.name + ); + } }, }, { From f094afc36d3bfa48fd97daca918e080f6650517d Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Mon, 9 Jun 2025 17:22:05 +0100 Subject: [PATCH 02/12] turbo env --- turbo.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index d5ac0504699a..52e8eb4ef93c 100644 --- a/turbo.json +++ b/turbo.json @@ -4,7 +4,11 @@ "signature": true }, "globalEnv": ["CI_OS", "NODE_VERSION"], - "globalPassThroughEnv": ["NODE_EXTRA_CA_CERTS", "CI"], + "globalPassThroughEnv": [ + "NODE_EXTRA_CA_CERTS", + "CI", + "VSCODE_INSPECTOR_OPTIONS" + ], "tasks": { "dev": { "persistent": true, From 55650654b41ca8428dbb2c709ba566ea380a6d1c Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Mon, 9 Jun 2025 17:43:13 +0100 Subject: [PATCH 03/12] type tests --- packages/wrangler/e2e/startWorker.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/wrangler/e2e/startWorker.test.ts b/packages/wrangler/e2e/startWorker.test.ts index ae75bf10ebdf..ab797b5833d8 100644 --- a/packages/wrangler/e2e/startWorker.test.ts +++ b/packages/wrangler/e2e/startWorker.test.ts @@ -108,6 +108,8 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { }); const inspectorUrl = await worker.inspectorUrl; + assert(inspectorUrl); + const res = await undici.fetch(`http://${inspectorUrl.host}/json`); await expect(res.json()).resolves.toBeInstanceOf(Array); @@ -177,6 +179,7 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { }); const inspectorUrl = await worker.inspectorUrl; + assert(inspectorUrl); let ws = new WebSocket(inspectorUrl.href, { setHost: false, @@ -236,6 +239,7 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { }); const inspectorUrl = await worker.inspectorUrl; + assert(inspectorUrl); const ws = new WebSocket(inspectorUrl.href); const consoleApiMessages: DevToolsEvent<"Runtime.consoleAPICalled">[] = From be20890ca7fb5f0e36a2b4d4ea2138a015b833e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Somhairle=20MacLe=C3=B2id?= Date: Tue, 10 Jun 2025 16:39:17 +0100 Subject: [PATCH 04/12] Create perfect-plants-compete.md --- .changeset/perfect-plants-compete.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/perfect-plants-compete.md diff --git a/.changeset/perfect-plants-compete.md b/.changeset/perfect-plants-compete.md new file mode 100644 index 000000000000..0b4947af35b1 --- /dev/null +++ b/.changeset/perfect-plants-compete.md @@ -0,0 +1,8 @@ +--- +"miniflare": patch +"wrangler": patch +--- + +In 2023 we announced [breakpoint debugging support](https://blog.cloudflare.com/debugging-cloudflare-workers/) for Workers, which meant that you could easily debug your Worker code in Wrangler's built-in devtools (accessible via the `[d]` hotkey) as well as multiple other devtools clients, [including VSCode](https://developers.cloudflare.com/workers/observability/dev-tools/breakpoints/). For most developers, breakpoint debugging via VSCode is the most natural flow, but until now it's required [manually configuring a `launch.json` file](https://developers.cloudflare.com/workers/observability/dev-tools/breakpoints/#setup-vs-code-to-use-breakpoints), running `wrangler dev`, and connecting via VSCode's built-in debugger. + +Now, using VSCode's built-in [JavaScript Debug Terminals](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_javascript-debug-terminal), there are just two steps: open a JS debug terminal and run `wrangler dev` (or `vite dev`). VSCode will automatically connect to your running Worker (even if you're running multiple Workers at once!) and start a debugging session. From 407d9519c02e6d6513e794d62b99c2e4532589e0 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Tue, 1 Jul 2025 12:56:11 +0100 Subject: [PATCH 05/12] reset fixtures --- fixtures/additional-modules/package.json | 2 +- fixtures/container-app/Dockerfile | 23 +-- .../container/simple-node-app.js | 49 +++++ fixtures/container-app/package.json | 8 +- fixtures/container-app/src/index.ts | 67 +++++-- fixtures/container-app/tsconfig.json | 2 +- fixtures/container-app/wrangler.jsonc | 15 +- .../container-app/wrangler.registry.jsonc | 29 +++ fixtures/d1-read-replication-app/package.json | 2 +- fixtures/d1-worker-app/package.json | 2 +- fixtures/dev-registry/package.json | 2 +- .../dev-registry/wrangler.module-worker.jsonc | 5 + fixtures/email-worker/package.json | 2 +- .../index.test.js | 85 ++++++++ .../package.json | 19 ++ .../remote-worker.js | 7 + fixtures/get-platform-proxy/package.json | 2 +- fixtures/get-platform-proxy/public/test.txt | 1 + .../tests/get-platform-proxy.env.test.ts | 17 +- fixtures/get-platform-proxy/wrangler.jsonc | 6 + fixtures/interactive-dev-tests/Dockerfile | 3 + fixtures/interactive-dev-tests/index.js | 1 + fixtures/interactive-dev-tests/package.json | 2 +- .../interactive-dev-tests/src/container.mjs | 6 + .../interactive-dev-tests/tests/index.test.ts | 36 ++++ .../wrangler.container.jsonc | 29 +++ fixtures/local-mode-tests/package.json | 2 +- fixtures/miniflare-node-test/package.json | 2 +- fixtures/node-app-pages/package.json | 2 +- fixtures/nodejs-als-app/package.json | 2 +- fixtures/nodejs-hybrid-app/package.json | 2 +- .../pages-dev-proxy-with-script/package.json | 2 +- fixtures/pages-functions-app/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- fixtures/pages-simple-assets/package.json | 2 +- fixtures/secrets-store/package.json | 2 +- fixtures/start-worker-node-test/package.json | 2 +- .../src/config-errors.test.js | 26 ++- .../ai-vectorize/src/index.ts | 7 +- .../vitest-pool-workers-examples/package.json | 2 +- .../package.json | 15 ++ .../remote-worker.js | 7 + .../run-tests.mjs | 86 +++++++++ .../src/index.js | 7 + .../test/index.spec.ts | 32 ++++ .../vitest.workers.config.ts | 49 +++++ .../wrangler.json | 12 ++ fixtures/wildcard-modules/package.json | 2 +- fixtures/worker-app/src/index.js | 2 +- fixtures/worker-ts/package.json | 2 +- fixtures/worker-ts/src/index.ts | 4 +- fixtures/worker-ts/wrangler.jsonc | 1 - .../workers-shared-asset-config/package.json | 2 +- .../package.json | 2 +- .../workers-with-assets-only/package.json | 2 +- .../package.json | 2 +- fixtures/workers-with-assets-spa/package.json | 2 +- .../package.json | 26 +++ .../public/static/page.html | 1 + .../public/worker/asset.html | 1 + .../public/worker/worker-runs.html | 1 + .../spa-assets/index.html | 14 ++ .../spa.wrangler.jsonc | 11 ++ .../src/index.ts | 31 +++ .../test/index.test.ts | 181 ++++++++++++++++++ .../test/tsconfig.json | 7 + .../tsconfig.json | 14 ++ .../wrangler.jsonc | 17 ++ fixtures/workers-with-assets/package.json | 2 +- fixtures/workflow-multiple/package.json | 2 +- fixtures/workflow/package.json | 2 +- 72 files changed, 919 insertions(+), 101 deletions(-) create mode 100644 fixtures/container-app/container/simple-node-app.js create mode 100644 fixtures/container-app/wrangler.registry.jsonc create mode 100644 fixtures/get-platform-proxy-remote-bindings-node-test/index.test.js create mode 100644 fixtures/get-platform-proxy-remote-bindings-node-test/package.json create mode 100644 fixtures/get-platform-proxy-remote-bindings-node-test/remote-worker.js create mode 100644 fixtures/get-platform-proxy/public/test.txt create mode 100644 fixtures/interactive-dev-tests/Dockerfile create mode 100644 fixtures/interactive-dev-tests/index.js create mode 100644 fixtures/interactive-dev-tests/src/container.mjs create mode 100644 fixtures/interactive-dev-tests/wrangler.container.jsonc create mode 100644 fixtures/vitest-pool-workers-remote-bindings/package.json create mode 100644 fixtures/vitest-pool-workers-remote-bindings/remote-worker.js create mode 100644 fixtures/vitest-pool-workers-remote-bindings/run-tests.mjs create mode 100644 fixtures/vitest-pool-workers-remote-bindings/src/index.js create mode 100644 fixtures/vitest-pool-workers-remote-bindings/test/index.spec.ts create mode 100644 fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts create mode 100644 fixtures/vitest-pool-workers-remote-bindings/wrangler.json create mode 100644 fixtures/workers-with-assets-static-routing/package.json create mode 100644 fixtures/workers-with-assets-static-routing/public/static/page.html create mode 100644 fixtures/workers-with-assets-static-routing/public/worker/asset.html create mode 100644 fixtures/workers-with-assets-static-routing/public/worker/worker-runs.html create mode 100644 fixtures/workers-with-assets-static-routing/spa-assets/index.html create mode 100644 fixtures/workers-with-assets-static-routing/spa.wrangler.jsonc create mode 100644 fixtures/workers-with-assets-static-routing/src/index.ts create mode 100644 fixtures/workers-with-assets-static-routing/test/index.test.ts create mode 100644 fixtures/workers-with-assets-static-routing/test/tsconfig.json create mode 100644 fixtures/workers-with-assets-static-routing/tsconfig.json create mode 100644 fixtures/workers-with-assets-static-routing/wrangler.jsonc diff --git a/fixtures/additional-modules/package.json b/fixtures/additional-modules/package.json index 6018472e2e2f..1067f46409ab 100644 --- a/fixtures/additional-modules/package.json +++ b/fixtures/additional-modules/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/container-app/Dockerfile b/fixtures/container-app/Dockerfile index 1813da7e3769..c34ae069ae42 100644 --- a/fixtures/container-app/Dockerfile +++ b/fixtures/container-app/Dockerfile @@ -1,20 +1,9 @@ -# syntax=docker/dockerfile:1 +FROM node:22-alpine -FROM golang:1.24 AS build -# Set destination for COPY -WORKDIR /app +WORKDIR /usr/src/app +RUN echo '{"name": "simple-node-app", "version": "1.0.0", "dependencies": {"ws": "^8.0.0"}}' > package.json +RUN npm install -# Download Go modules -COPY container/go.mod container/go.sum ./ -RUN go mod download - -# Copy container src -COPY container/*.go ./ -# Build -RUN CGO_ENABLED=0 GOOS=linux go build -o /server - -FROM debian:latest -COPY --from=build /server /server +COPY ./container/simple-node-app.js app.js EXPOSE 8080 -# Run -CMD ["/server"] +CMD [ "node", "app.js" ] \ No newline at end of file diff --git a/fixtures/container-app/container/simple-node-app.js b/fixtures/container-app/container/simple-node-app.js new file mode 100644 index 000000000000..77fa1879cf88 --- /dev/null +++ b/fixtures/container-app/container/simple-node-app.js @@ -0,0 +1,49 @@ +const { createServer } = require("http"); + +const webSocketEnabled = process.env.WS_ENABLED === "true"; + +// Create HTTP server +const server = createServer(function (req, res) { + if (req.url === "/ws") { + // WebSocket upgrade will be handled by the WebSocket server + return; + } + + res.writeHead(200, { "Content-Type": "text/plain" }); + res.write("Hello World!"); + res.end(); +}); + +// Check if WebSocket functionality is enabled +if (webSocketEnabled) { + const WebSocket = require("ws"); + + // Create WebSocket server + const wss = new WebSocket.Server({ + server: server, + path: "/ws", + }); + + wss.on("connection", function connection(ws) { + console.log("WebSocket connection established"); + + ws.on("message", function message(data) { + console.log("Received:", data.toString()); + // Echo the message back with prefix + ws.send("Echo: " + data.toString()); + }); + + ws.on("close", function close() { + console.log("WebSocket connection closed"); + }); + + ws.on("error", console.error); + }); +} + +server.listen(8080, function () { + console.log("Server listening on port 8080"); + if (webSocketEnabled) { + console.log("WebSocket support enabled"); + } +}); diff --git a/fixtures/container-app/package.json b/fixtures/container-app/package.json index 81050ac6ffb2..f2ba2c952f4f 100644 --- a/fixtures/container-app/package.json +++ b/fixtures/container-app/package.json @@ -6,19 +6,17 @@ "container:build": "wrangler containers build ./ -t container-fixture", "deploy": "wrangler deploy", "dev": "wrangler dev", - "start": "wrangler dev", - "test:ci": "vitest run", - "test:watch": "vitest" + "start": "wrangler dev" }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "ts-dedent": "^2.2.0", "typescript": "catalog:default", - "vitest": "catalog:default", "wrangler": "workspace:*" }, "volta": { + "node": "20.19.2", "extends": "../../package.json" } } diff --git a/fixtures/container-app/src/index.ts b/fixtures/container-app/src/index.ts index dae6f8c78860..6be4c8c34945 100644 --- a/fixtures/container-app/src/index.ts +++ b/fixtures/container-app/src/index.ts @@ -7,33 +7,64 @@ export class Container extends DurableObject { constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); this.container = ctx.container!; - void this.ctx.blockConcurrencyWhile(async () => { - if (!this.container.running) this.container.start(); - }); } async fetch(req: Request) { - try { - return await this.container - .getTcpPort(8080) - .fetch(req.url.replace("https:", "http:"), req); - } catch (err) { - return new Response(`${this.ctx.id.toString()}: ${err.message}`, { - status: 500, - }); + const path = new URL(req.url).pathname; + switch (path) { + case "/status": + return new Response(JSON.stringify(this.container.running)); + + case "/destroy": + if (!this.container.running) { + throw new Error("Container is not running."); + } + await this.container.destroy(); + return new Response(JSON.stringify(this.container.running)); + + case "/start": + this.container.start({ + entrypoint: ["node", "app.js"], + env: { A: "B", C: "D", L: "F" }, + enableInternet: false, + }); + // this doesn't instantly start, so we will need to poll /fetch + return new Response("Container create request sent..."); + + case "/fetch": + const res = await this.container + .getTcpPort(8080) + // actual request doesn't matter + .fetch("http://foo/bar/baz", { method: "POST", body: "hello" }); + return new Response(await res.text()); + + case "/destroy-with-monitor": + // if (!this.container.running) { + // throw new Error("Container is not running."); + // } + const monitor = this.container.monitor(); + await this.container.destroy(); + await monitor; + return new Response("Container destroyed with monitor."); + + default: + return new Response("Hi from Container DO"); } } } export default { async fetch(request, env): Promise { - try { - return await env.CONTAINER.get(env.CONTAINER.idFromName("fetcher")).fetch( - request - ); - } catch (err) { - console.error("Error fetch:", err.message); - return new Response(err.message, { status: 500 }); + const url = new URL(request.url); + if (url.pathname === "/second") { + // This is a second Durable Object that can be used to test multiple DOs + const id = env.CONTAINER.idFromName("second-container"); + const stub = env.CONTAINER.get(id); + const query = url.searchParams.get("req"); + return stub.fetch("http://example.com/" + query); } + const id = env.CONTAINER.idFromName("container"); + const stub = env.CONTAINER.get(id); + return stub.fetch(request); }, } satisfies ExportedHandler; diff --git a/fixtures/container-app/tsconfig.json b/fixtures/container-app/tsconfig.json index 856398634a5e..cee17af62aba 100644 --- a/fixtures/container-app/tsconfig.json +++ b/fixtures/container-app/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "module": "CommonJS", + "module": "ESNext", "lib": ["ES2020"], "types": ["@cloudflare/workers-types"], "moduleResolution": "node", diff --git a/fixtures/container-app/wrangler.jsonc b/fixtures/container-app/wrangler.jsonc index 1a9ad34a335c..48561a309a22 100644 --- a/fixtures/container-app/wrangler.jsonc +++ b/fixtures/container-app/wrangler.jsonc @@ -2,12 +2,6 @@ "name": "container-app", "main": "src/index.ts", "compatibility_date": "2025-04-03", - "migrations": [ - { - "new_sqlite_classes": ["Container"], - "tag": "v1", - }, - ], "containers": [ { "configuration": { @@ -26,7 +20,10 @@ }, ], }, - "observability": { - "enabled": true, - }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["Container"], + }, + ], } diff --git a/fixtures/container-app/wrangler.registry.jsonc b/fixtures/container-app/wrangler.registry.jsonc new file mode 100644 index 000000000000..113b9aa09958 --- /dev/null +++ b/fixtures/container-app/wrangler.registry.jsonc @@ -0,0 +1,29 @@ +{ + "name": "container-app-from-registry", + "main": "src/index.ts", + "compatibility_date": "2025-04-03", + "containers": [ + { + "configuration": { + "image": "registry.cloudflare.com/8d783f274e1f82dc46744c297b015a2f/ci-container-dont-delete:latest", + }, + "class_name": "Container", + "name": "http2", + "max_instances": 2, + }, + ], + "durable_objects": { + "bindings": [ + { + "class_name": "Container", + "name": "CONTAINER", + }, + ], + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["Container"], + }, + ], +} diff --git a/fixtures/d1-read-replication-app/package.json b/fixtures/d1-read-replication-app/package.json index 56abb80aa810..76dca4eb73e4 100644 --- a/fixtures/d1-read-replication-app/package.json +++ b/fixtures/d1-read-replication-app/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/d1-worker-app/package.json b/fixtures/d1-worker-app/package.json index 1a66fdd6c249..456b1913099a 100644 --- a/fixtures/d1-worker-app/package.json +++ b/fixtures/d1-worker-app/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/dev-registry/package.json b/fixtures/dev-registry/package.json index 5bc95bd93d4e..74254a8ef732 100644 --- a/fixtures/dev-registry/package.json +++ b/fixtures/dev-registry/package.json @@ -12,7 +12,7 @@ "devDependencies": { "@cloudflare/vite-plugin": "workspace:*", "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "vite": "catalog:vite-plugin", "vitest": "catalog:default", diff --git a/fixtures/dev-registry/wrangler.module-worker.jsonc b/fixtures/dev-registry/wrangler.module-worker.jsonc index 1b989f951a3b..804f8ba14d3a 100644 --- a/fixtures/dev-registry/wrangler.module-worker.jsonc +++ b/fixtures/dev-registry/wrangler.module-worker.jsonc @@ -20,6 +20,11 @@ "binding": "WORKER_ENTRYPOINT_B", "service": "worker-entrypoint-b", }, + { + "binding": "NAMED_ENTRYPOINT", + "service": "unknown-worker", + "entrypoint": "UnknownEntrypoint", + }, ], "tail_consumers": [ { diff --git a/fixtures/email-worker/package.json b/fixtures/email-worker/package.json index fb5d699ed575..533b6a4e8017 100644 --- a/fixtures/email-worker/package.json +++ b/fixtures/email-worker/package.json @@ -6,7 +6,7 @@ "start": "wrangler dev" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@types/mimetext": "^2.0.4", "mimetext": "^3.0.27", "wrangler": "workspace:*" diff --git a/fixtures/get-platform-proxy-remote-bindings-node-test/index.test.js b/fixtures/get-platform-proxy-remote-bindings-node-test/index.test.js new file mode 100644 index 000000000000..ef3152dfedb2 --- /dev/null +++ b/fixtures/get-platform-proxy-remote-bindings-node-test/index.test.js @@ -0,0 +1,85 @@ +import { execSync } from "child_process"; +import { randomUUID } from "crypto"; +import assert from "node:assert"; +import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import test, { after, before, describe } from "node:test"; +import { getPlatformProxy } from "wrangler"; + +if ( + !process.env.TEST_CLOUDFLARE_API_TOKEN || + !process.env.TEST_CLOUDFLARE_ACCOUNT_ID +) { + console.warn("No credentials provided, skipping test..."); + process.exit(0); +} + +describe("getPlatformProxy remote-bindings", () => { + const remoteWorkerName = `get-platform-proxy-remote-worker-test-${randomUUID().split("-")[0]}`; + + before(async () => { + // Note: ideally we pass the auth data to `getPlatformProxy`, that currently is not + // possible (DEVX-1857) so we need to make sure that the CLOUDFLARE_ACCOUNT_ID + // and CLOUDFLARE_API_TOKEN env variables are set so that `getPlatformProxy` + // can establish the remote proxy connection + process.env.CLOUDFLARE_ACCOUNT_ID = process.env.TEST_CLOUDFLARE_ACCOUNT_ID; + process.env.CLOUDFLARE_API_TOKEN = process.env.TEST_CLOUDFLARE_API_TOKEN; + + const deployOut = execSync( + `pnpm dlx wrangler deploy remote-worker.js --name ${remoteWorkerName} --compatibility-date 2025-06-19`, + { + stdio: "pipe", + } + ); + + if ( + !new RegExp(`Deployed\\s+${remoteWorkerName}\\b`).test(`${deployOut}`) + ) { + throw new Error(`Failed to deploy ${remoteWorkerName}`); + } + + rmSync("./.tmp", { recursive: true, force: true }); + + mkdirSync("./.tmp"); + + writeFileSync( + "./.tmp/wrangler.json", + JSON.stringify( + { + name: "get-platform-proxy-fixture-test", + compatibility_date: "2025-06-01", + services: [ + { + binding: "MY_WORKER", + service: remoteWorkerName, + experimental_remote: true, + }, + ], + }, + undefined, + 2 + ), + "utf8" + ); + }); + + test("getPlatformProxy works with remote bindings", async () => { + const { env, dispose } = await getPlatformProxy({ + configPath: "./.tmp/wrangler.json", + experimental: { remoteBindings: true }, + }); + + try { + assert.strictEqual( + await (await env.MY_WORKER.fetch("http://example.com")).text(), + "Hello from a remote Worker part of the getPlatformProxy remote bindings fixture!" + ); + } finally { + await dispose(); + } + }); + + after(async () => { + execSync(`pnpm dlx wrangler delete --name ${remoteWorkerName}`); + rmSync("./.tmp", { recursive: true, force: true }); + }); +}); diff --git a/fixtures/get-platform-proxy-remote-bindings-node-test/package.json b/fixtures/get-platform-proxy-remote-bindings-node-test/package.json new file mode 100644 index 000000000000..02f5ef743b83 --- /dev/null +++ b/fixtures/get-platform-proxy-remote-bindings-node-test/package.json @@ -0,0 +1,19 @@ +{ + "name": "@fixture/get-platform-proxy-remote-bindings-node-test", + "private": true, + "description": "", + "license": "ISC", + "author": "", + "type": "module", + "scripts": { + "test:ci": "node --test" + }, + "devDependencies": { + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20250617.0", + "wrangler": "workspace:*" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/fixtures/get-platform-proxy-remote-bindings-node-test/remote-worker.js b/fixtures/get-platform-proxy-remote-bindings-node-test/remote-worker.js new file mode 100644 index 000000000000..a88e54f0c7e0 --- /dev/null +++ b/fixtures/get-platform-proxy-remote-bindings-node-test/remote-worker.js @@ -0,0 +1,7 @@ +export default { + fetch() { + return new Response( + "Hello from a remote Worker part of the getPlatformProxy remote bindings fixture!" + ); + }, +}; diff --git a/fixtures/get-platform-proxy/package.json b/fixtures/get-platform-proxy/package.json index c05a9c6a5692..71f1655c1cd1 100644 --- a/fixtures/get-platform-proxy/package.json +++ b/fixtures/get-platform-proxy/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/get-platform-proxy/public/test.txt b/fixtures/get-platform-proxy/public/test.txt new file mode 100644 index 000000000000..2dd981b4d688 --- /dev/null +++ b/fixtures/get-platform-proxy/public/test.txt @@ -0,0 +1 @@ +this is a test text file! 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 74f7f17646b0..2fc44412ffa4 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 @@ -10,7 +10,11 @@ import { vi, } from "vitest"; import { getPlatformProxy } from "./shared"; -import type { Hyperdrive, KVNamespace } from "@cloudflare/workers-types"; +import type { + Fetcher, + Hyperdrive, + KVNamespace, +} from "@cloudflare/workers-types"; import type { Unstable_DevWorker } from "wrangler"; type Env = { @@ -23,6 +27,7 @@ type Env = { MY_BUCKET: R2Bucket; MY_D1: D1Database; MY_HYPERDRIVE: Hyperdrive; + ASSETS: Fetcher; }; const wranglerConfigFilePath = path.join(__dirname, "..", "wrangler.jsonc"); @@ -123,6 +128,16 @@ describe("getPlatformProxy - env", () => { } }); + it("correctly obtains functioning ASSETS bindings", async () => { + const { env, dispose } = await getPlatformProxy({ + configPath: wranglerConfigFilePath, + }); + const res = await env.ASSETS.fetch("https://0.0.0.0/test.txt"); + const text = await res.text(); + expect(text).toEqual("this is a test text file!\n"); + await dispose(); + }); + it("correctly obtains functioning KV bindings", async () => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, diff --git a/fixtures/get-platform-proxy/wrangler.jsonc b/fixtures/get-platform-proxy/wrangler.jsonc index 95f6f63b2427..545d04d6b578 100644 --- a/fixtures/get-platform-proxy/wrangler.jsonc +++ b/fixtures/get-platform-proxy/wrangler.jsonc @@ -2,6 +2,12 @@ "name": "get-bindings-proxy-fixture", "main": "src/index.ts", "compatibility_date": "2023-11-21", + "assets": { + "directory": "./public", + "binding": "ASSETS", + "html_handling": "auto-trailing-slash", + "not_found_handling": "none", + }, "vars": { "MY_VAR": "my-var-value", "MY_VAR_A": "my-var-a", diff --git a/fixtures/interactive-dev-tests/Dockerfile b/fixtures/interactive-dev-tests/Dockerfile new file mode 100644 index 000000000000..fd180e3c9de9 --- /dev/null +++ b/fixtures/interactive-dev-tests/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:latest +CMD ["echo", "hello world"] +EXPOSE 8080 diff --git a/fixtures/interactive-dev-tests/index.js b/fixtures/interactive-dev-tests/index.js new file mode 100644 index 000000000000..ff8b4c56321a --- /dev/null +++ b/fixtures/interactive-dev-tests/index.js @@ -0,0 +1 @@ +export default {}; diff --git a/fixtures/interactive-dev-tests/package.json b/fixtures/interactive-dev-tests/package.json index e59457630e62..d48fa99c9f10 100644 --- a/fixtures/interactive-dev-tests/package.json +++ b/fixtures/interactive-dev-tests/package.json @@ -12,7 +12,7 @@ "vitest": "catalog:default" }, "optionalDependencies": { - "@cdktf/node-pty-prebuilt-multiarch": "0.10.1-pre.11" + "@cdktf/node-pty-prebuilt-multiarch": "0.10.2" }, "volta": { "extends": "../../package.json" diff --git a/fixtures/interactive-dev-tests/src/container.mjs b/fixtures/interactive-dev-tests/src/container.mjs new file mode 100644 index 000000000000..1fa92225854a --- /dev/null +++ b/fixtures/interactive-dev-tests/src/container.mjs @@ -0,0 +1,6 @@ +import { DurableObject } from "cloudflare:workers"; + +export class Container extends DurableObject {} +export default { + async fetch() {}, +}; diff --git a/fixtures/interactive-dev-tests/tests/index.test.ts b/fixtures/interactive-dev-tests/tests/index.test.ts index 572cc5a62491..5b9f2e022827 100644 --- a/fixtures/interactive-dev-tests/tests/index.test.ts +++ b/fixtures/interactive-dev-tests/tests/index.test.ts @@ -227,6 +227,42 @@ describe.each(devScripts)("wrangler $args", ({ args, expectedBody }) => { expect(duringProcesses.length).toBeGreaterThan(beginProcesses.length); } }); + describe("--show-interactive-dev-session", () => { + it("should show hotkeys when interactive", async () => { + const wrangler = await startWranglerDev(args); + wrangler.pty.kill(); + expect(wrangler.stdout).toContain("open a browser"); + expect(wrangler.stdout).toContain("open devtools"); + expect(wrangler.stdout).toContain("clear console"); + expect(wrangler.stdout).toContain("to exit"); + expect(wrangler.stdout).not.toContain("rebuild container"); + }); + it("should not show hotkeys with --show-interactive-dev-session=false", async () => { + const wrangler = await startWranglerDev([ + ...args, + "--show-interactive-dev-session=false", + ]); + wrangler.pty.kill(); + expect(wrangler.stdout).not.toContain("open a browser"); + expect(wrangler.stdout).not.toContain("open devtools"); + expect(wrangler.stdout).not.toContain("clear console"); + expect(wrangler.stdout).not.toContain("to exit"); + expect(wrangler.stdout).not.toContain("rebuild container"); + }); + // docker isn't installed by default on windows/macos runners + it.skipIf(process.env.platform !== "linux" && process.env.CI === "true")( + "should show rebuild containers hotkey if containers are configured", + async () => { + const wrangler = await startWranglerDev([ + "dev", + "-c", + "wrangler.container.jsonc", + ]); + wrangler.pty.kill(); + expect(wrangler.stdout).toContain("rebuild container"); + } + ); + }); }); it.each(exitKeys)("multiworker cleanly exits with $name", async ({ key }) => { diff --git a/fixtures/interactive-dev-tests/wrangler.container.jsonc b/fixtures/interactive-dev-tests/wrangler.container.jsonc new file mode 100644 index 000000000000..004aafda35fd --- /dev/null +++ b/fixtures/interactive-dev-tests/wrangler.container.jsonc @@ -0,0 +1,29 @@ +{ + "name": "container-app", + "main": "src/container.mjs", + "compatibility_date": "2025-04-03", + "containers": [ + { + "configuration": { + "image": "./Dockerfile", + }, + "class_name": "Container", + "name": "http2", + "max_instances": 2, + }, + ], + "durable_objects": { + "bindings": [ + { + "class_name": "Container", + "name": "CONTAINER", + }, + ], + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["Container"], + }, + ], +} diff --git a/fixtures/local-mode-tests/package.json b/fixtures/local-mode-tests/package.json index 9a8cc427f3cb..1184671ffaf0 100644 --- a/fixtures/local-mode-tests/package.json +++ b/fixtures/local-mode-tests/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@types/node": "catalog:default", "buffer": "^6.0.3", "typescript": "catalog:default", diff --git a/fixtures/miniflare-node-test/package.json b/fixtures/miniflare-node-test/package.json index 3057d880c1f1..d52f6aaa5a6c 100644 --- a/fixtures/miniflare-node-test/package.json +++ b/fixtures/miniflare-node-test/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@types/is-even": "^1.0.2", "is-even": "^1.0.0", "miniflare": "workspace:*", diff --git a/fixtures/node-app-pages/package.json b/fixtures/node-app-pages/package.json index a88fd3befbed..724e5e28fa02 100644 --- a/fixtures/node-app-pages/package.json +++ b/fixtures/node-app-pages/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/nodejs-als-app/package.json b/fixtures/nodejs-als-app/package.json index bf39fe7d5950..f3805b4c2818 100644 --- a/fixtures/nodejs-als-app/package.json +++ b/fixtures/nodejs-als-app/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "undici": "catalog:default", "vitest": "catalog:default", "wrangler": "workspace:*" diff --git a/fixtures/nodejs-hybrid-app/package.json b/fixtures/nodejs-hybrid-app/package.json index c9903e5bb146..4aed8629dfee 100644 --- a/fixtures/nodejs-hybrid-app/package.json +++ b/fixtures/nodejs-hybrid-app/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@types/pg": "^8.11.2", "pg": "8.11.3", "pg-cloudflare": "^1.1.1", diff --git a/fixtures/pages-dev-proxy-with-script/package.json b/fixtures/pages-dev-proxy-with-script/package.json index ace80af72300..f7954f388e19 100644 --- a/fixtures/pages-dev-proxy-with-script/package.json +++ b/fixtures/pages-dev-proxy-with-script/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/pages-functions-app/package.json b/fixtures/pages-functions-app/package.json index 1e33b3646761..39a06d92c93c 100644 --- a/fixtures/pages-functions-app/package.json +++ b/fixtures/pages-functions-app/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@fixture/pages-plugin": "workspace:*", "typescript": "catalog:default", "undici": "catalog:default", diff --git a/fixtures/pages-functions-with-routes-app/package.json b/fixtures/pages-functions-with-routes-app/package.json index 97f850015be4..d39285fe7d43 100644 --- a/fixtures/pages-functions-with-routes-app/package.json +++ b/fixtures/pages-functions-with-routes-app/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/pages-plugin-mounted-on-root-app/package.json b/fixtures/pages-plugin-mounted-on-root-app/package.json index 27de891e7149..6ac5ebc8e131 100644 --- a/fixtures/pages-plugin-mounted-on-root-app/package.json +++ b/fixtures/pages-plugin-mounted-on-root-app/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@fixture/pages-plugin": "workspace:*", "typescript": "catalog:default", "undici": "catalog:default", diff --git a/fixtures/pages-simple-assets/package.json b/fixtures/pages-simple-assets/package.json index 5959e0bad9e1..881c67977ec0 100644 --- a/fixtures/pages-simple-assets/package.json +++ b/fixtures/pages-simple-assets/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/secrets-store/package.json b/fixtures/secrets-store/package.json index a12e7c91caf6..02eb76dfe41e 100644 --- a/fixtures/secrets-store/package.json +++ b/fixtures/secrets-store/package.json @@ -6,7 +6,7 @@ "start": "wrangler dev" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "wrangler": "workspace:*" }, "volta": { diff --git a/fixtures/start-worker-node-test/package.json b/fixtures/start-worker-node-test/package.json index a3143980432c..472ea13b9431 100644 --- a/fixtures/start-worker-node-test/package.json +++ b/fixtures/start-worker-node-test/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@types/is-even": "^1.0.2", "get-port": "^7.1.0", "is-even": "^1.0.0", diff --git a/fixtures/start-worker-node-test/src/config-errors.test.js b/fixtures/start-worker-node-test/src/config-errors.test.js index b427bd147ddc..06d4913860d0 100644 --- a/fixtures/start-worker-node-test/src/config-errors.test.js +++ b/fixtures/start-worker-node-test/src/config-errors.test.js @@ -12,9 +12,15 @@ describe("startWorker - configuration errors", () => { }), (err) => { assert(err instanceof Error); - assert.match( + assert.strictEqual( err.message, - /he entry-point file at "not a real entrypoint" was not found./ + "An error occurred when starting the server" + ); + const cause = err.cause; + assert(cause instanceof Error); + assert.match( + cause.message, + /The entry-point file at "not a real entrypoint" was not found./ ); return true; } @@ -26,8 +32,14 @@ describe("startWorker - configuration errors", () => { unstable_startWorker({ config: "non-existing-config" }), (err) => { assert(err instanceof Error); - assert.match( + assert.strictEqual( err.message, + "An error occurred when starting the server" + ); + const cause = err.cause; + assert(cause instanceof Error); + assert.match( + cause.message, /Missing entry-point to Worker script or to assets directory/ ); return true; @@ -42,7 +54,7 @@ describe("startWorker - configuration errors", () => { server: { port: await getPort(), }, - inspector: { port: await getPort() }, + inspector: false, }, }); @@ -58,8 +70,6 @@ describe("startWorker - configuration errors", () => { } ); - // TODO: worker.dispose() should itself await worker.ready - await worker.ready; await worker.dispose(); }); @@ -70,7 +80,7 @@ describe("startWorker - configuration errors", () => { server: { port: await getPort(), }, - inspector: { port: await getPort() }, + inspector: false, }, }); @@ -86,8 +96,6 @@ describe("startWorker - configuration errors", () => { } ); - // TODO: worker.dispose() should itself await worker.ready - await worker.ready; await worker.dispose(); }); }); diff --git a/fixtures/vitest-pool-workers-examples/ai-vectorize/src/index.ts b/fixtures/vitest-pool-workers-examples/ai-vectorize/src/index.ts index fc296dac0947..d693e06c7e7d 100644 --- a/fixtures/vitest-pool-workers-examples/ai-vectorize/src/index.ts +++ b/fixtures/vitest-pool-workers-examples/ai-vectorize/src/index.ts @@ -1,8 +1,3 @@ -interface EmbeddingResponse { - shape: number[]; - data: number[][]; -} - export default { async fetch(request, env, ctx): Promise { const path = new URL(request.url).pathname; @@ -12,7 +7,7 @@ export default { "This is a story about an orange cloud", "This is a story about a llama", ]; - const modelResp: EmbeddingResponse = await env.AI.run( + const modelResp: Ai_Cf_Baai_Bge_Base_En_V1_5_Output = await env.AI.run( "@cf/baai/bge-base-en-v1.5", { text: stories, diff --git a/fixtures/vitest-pool-workers-examples/package.json b/fixtures/vitest-pool-workers-examples/package.json index cda113ade80d..0f0be03467d1 100644 --- a/fixtures/vitest-pool-workers-examples/package.json +++ b/fixtures/vitest-pool-workers-examples/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@cloudflare/vitest-pool-workers": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@microlabs/otel-cf-workers": "1.0.0-rc.45", "@types/node": "catalog:default", "discord-api-types": "0.37.98", diff --git a/fixtures/vitest-pool-workers-remote-bindings/package.json b/fixtures/vitest-pool-workers-remote-bindings/package.json new file mode 100644 index 000000000000..1d7a5d62629a --- /dev/null +++ b/fixtures/vitest-pool-workers-remote-bindings/package.json @@ -0,0 +1,15 @@ +{ + "name": "@fixture/vitest-pool-workers-remote-bindings", + "private": true, + "scripts": { + "test": "vitest run --config vitest.workers.config.ts", + "test:ci": "node run-tests.mjs", + "test:vitest": "vitest run" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "workspace:*", + "typescript": "^5.5.2", + "vitest": "~3.0.7", + "wrangler": "workspace:*" + } +} diff --git a/fixtures/vitest-pool-workers-remote-bindings/remote-worker.js b/fixtures/vitest-pool-workers-remote-bindings/remote-worker.js new file mode 100644 index 000000000000..3e6f023a8dfc --- /dev/null +++ b/fixtures/vitest-pool-workers-remote-bindings/remote-worker.js @@ -0,0 +1,7 @@ +export default { + fetch() { + return new Response( + "Hello from a remote Worker part of the vitest-pool-workers remote bindings fixture!" + ); + }, +}; diff --git a/fixtures/vitest-pool-workers-remote-bindings/run-tests.mjs b/fixtures/vitest-pool-workers-remote-bindings/run-tests.mjs new file mode 100644 index 000000000000..7351dc5593f5 --- /dev/null +++ b/fixtures/vitest-pool-workers-remote-bindings/run-tests.mjs @@ -0,0 +1,86 @@ +/** + * This fixture is particular since it needs to communicate with remote resources, namely + * a remote worker. + * + * This script is used to deploy a remote worker and run the fixture using said worker. + * + * Alternatively you can simply deploy, using your account, the `./remote-worker.js` file as + * a worker named `my-worker-test` and directly run the fixture using vitest. + */ +import { execSync } from "child_process"; +import { randomUUID } from "crypto"; +import { cpSync, readFileSync, rmSync, writeFileSync } from "fs"; + +if ( + !process.env.TEST_CLOUDFLARE_API_TOKEN || + !process.env.TEST_CLOUDFLARE_ACCOUNT_ID +) { + console.warn("No credentials provided, skipping test..."); + process.exit(0); +} + +rmSync("./.tmp", { recursive: true, force: true }); + +cpSync("./src", "./.tmp/src", { recursive: true }); +cpSync("./test", "./.tmp/test", { recursive: true }); +cpSync("./vitest.workers.config.ts", "./.tmp/vitest.workers.config.ts"); + +const remoteWorkerName = `vitest-pool-workers-remote-worker-test-${ + randomUUID().split("-")[0] +}`; + +const wranglerJson = JSON.parse(readFileSync("./wrangler.json", "utf8")); +wranglerJson.services[0].service = remoteWorkerName; + +writeFileSync( + "./.tmp/wrangler.json", + JSON.stringify(wranglerJson, undefined, 2), + "utf8" +); + +writeFileSync( + "./.tmp/remote-wrangler.json", + JSON.stringify( + { + name: remoteWorkerName, + main: "../remote-worker.js", + compatibility_date: "2025-06-01", + }, + undefined, + 2 + ), + "utf8" +); + +const env = { + ...process.env, + CLOUDFLARE_API_TOKEN: process.env.TEST_CLOUDFLARE_API_TOKEN, + CLOUDFLARE_ACCOUNT_ID: process.env.TEST_CLOUDFLARE_ACCOUNT_ID, +}; + +const deployOut = execSync("pnpm dlx wrangler deploy -c remote-wrangler.json", { + stdio: "pipe", + cwd: "./.tmp", + env, +}); + +if (!new RegExp(`Deployed\\s+${remoteWorkerName}\\b`).test(`${deployOut}`)) { + throw new Error(`Failed to deploy ${remoteWorkerName}`); +} + +let errored = false; +try { + execSync("pnpm test:vitest --config ./.tmp/vitest.workers.config.ts", { + env, + }); +} catch { + errored = true; +} + +execSync(`pnpm dlx wrangler delete --name ${remoteWorkerName}`, { env }); + +rmSync("./.tmp", { recursive: true, force: true }); + +if (errored) { + process.exit(1); +} diff --git a/fixtures/vitest-pool-workers-remote-bindings/src/index.js b/fixtures/vitest-pool-workers-remote-bindings/src/index.js new file mode 100644 index 000000000000..96553456353f --- /dev/null +++ b/fixtures/vitest-pool-workers-remote-bindings/src/index.js @@ -0,0 +1,7 @@ +export default { + async fetch(request, env) { + const remoteWorkerResp = await env.MY_WORKER.fetch(request); + const remoteWorkerRespText = await remoteWorkerResp.text(); + return new Response(`Response from remote worker: ${remoteWorkerRespText}`); + }, +}; diff --git a/fixtures/vitest-pool-workers-remote-bindings/test/index.spec.ts b/fixtures/vitest-pool-workers-remote-bindings/test/index.spec.ts new file mode 100644 index 000000000000..384df4de30cd --- /dev/null +++ b/fixtures/vitest-pool-workers-remote-bindings/test/index.spec.ts @@ -0,0 +1,32 @@ +import { + createExecutionContext, + env, + SELF, + waitOnExecutionContext, + // @ts-ignore +} from "cloudflare:test"; +import { describe, expect, it } from "vitest"; + +describe("Hello World worker", () => { + it( + "responds with Hello World! (unit style)", + { timeout: 50_000 }, + async () => { + const request = new Request("http://example.com"); + const ctx = createExecutionContext(); + debugger; + const response = await env.MY_WORKER.fetch(request, env, ctx); + await waitOnExecutionContext(ctx); + expect(await response.text()).toMatchInlineSnapshot( + `"Hello from a remote Worker part of the vitest-pool-workers remote bindings fixture!"` + ); + } + ); + + it("responds with Hello World! (integration style)", async () => { + const response = await SELF.fetch("https://example.com"); + expect(await response.text()).toMatchInlineSnapshot( + `"Response from remote worker: Hello from a remote Worker part of the vitest-pool-workers remote bindings fixture!"` + ); + }); +}); diff --git a/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts b/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts new file mode 100644 index 000000000000..6a476d79b5e1 --- /dev/null +++ b/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts @@ -0,0 +1,49 @@ +import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; + +class FilteredPushArray extends Array { + constructor(private readonly predicate: (item: T) => boolean) { + super(); + } + + push(...items: T[]) { + return super.push(...items.filter(this.predicate)); + } +} + +debugger; +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + experimental_remoteBindings: true, + wrangler: { configPath: "./wrangler.json" }, + }, + }, + + // Configure the `vite-node` server used by Vitest code to import configs, + // custom pools and tests. By default, Vitest effectively applies Vite + // transforms to all files outside `node_modules`. This means by default, + // our custom pool code is transformed by Vite during development, but not + // when published, leading to possible behaviour mismatches. To fix this, + // we ensure file paths containing `packages/vitest-pool-workers/dist` are + // always "externalised", meaning they're imported directly by Node. + server: { + deps: { + // Vitest automatically adds `/^(?!.*node_modules).*\.mjs$/` as an + // `inline` RegExp: https://github.com/vitest-dev/vitest/blob/v2.1.1/packages/vitest/src/constants.ts#L9 + // We'd like `packages/vitest-pool-workers/dist/pool/index.mjs` to be + // externalised though. Unfortunately, `inline`s are checked before + // `external`s, so there's no nice way we can override this. Instead, + // we prevent the extra `inline` being added in the first place. + inline: new FilteredPushArray((item: any) => { + const str = item.toString(); + return str !== "/^(?!.*node_modules).*\\.mjs$/"; + }), + external: [ + /packages\/vitest-pool-workers\/dist/, + /packages\/wrangler\//, + ], + }, + }, + }, +}); diff --git a/fixtures/vitest-pool-workers-remote-bindings/wrangler.json b/fixtures/vitest-pool-workers-remote-bindings/wrangler.json new file mode 100644 index 000000000000..75d57aee81ab --- /dev/null +++ b/fixtures/vitest-pool-workers-remote-bindings/wrangler.json @@ -0,0 +1,12 @@ +{ + "name": "vitest-remote-bindings-fixture-test", + "main": "src/index.js", + "compatibility_date": "2025-06-01", + "services": [ + { + "binding": "MY_WORKER", + "service": "my-worker-test", + "experimental_remote": true + } + ] +} diff --git a/fixtures/wildcard-modules/package.json b/fixtures/wildcard-modules/package.json index 96364dd18da2..8eb022470e51 100644 --- a/fixtures/wildcard-modules/package.json +++ b/fixtures/wildcard-modules/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "undici": "^5.28.4", "wrangler": "workspace:*" }, diff --git a/fixtures/worker-app/src/index.js b/fixtures/worker-app/src/index.js index fc77eedeffaa..f8d72b34abed 100644 --- a/fixtures/worker-app/src/index.js +++ b/fixtures/worker-app/src/index.js @@ -20,7 +20,7 @@ function hexEncode(array) { export default { async fetch(request, env) { - console.log("request log 5"); + console.log("request log"); const { pathname, origin, hostname, host } = new URL(request.url); if (pathname.startsWith("/fav")) diff --git a/fixtures/worker-ts/package.json b/fixtures/worker-ts/package.json index e20108909c66..85cb32f0e6e4 100644 --- a/fixtures/worker-ts/package.json +++ b/fixtures/worker-ts/package.json @@ -6,7 +6,7 @@ "start": "wrangler dev" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "wrangler": "workspace:*" }, "volta": { diff --git a/fixtures/worker-ts/src/index.ts b/fixtures/worker-ts/src/index.ts index e3ae37925011..6a010a461e9d 100644 --- a/fixtures/worker-ts/src/index.ts +++ b/fixtures/worker-ts/src/index.ts @@ -28,9 +28,7 @@ export default { ctx: ExecutionContext ): Promise { const url = new URL(request.url); - const a = 29; - console.log(url.hash); if (url.pathname === "/error") throw new Error("Hello Error"); - return env.APP.fetch(request); + return new Response("Hello World!"); }, }; diff --git a/fixtures/worker-ts/wrangler.jsonc b/fixtures/worker-ts/wrangler.jsonc index c854a63b343c..e373588ae2c7 100644 --- a/fixtures/worker-ts/wrangler.jsonc +++ b/fixtures/worker-ts/wrangler.jsonc @@ -2,5 +2,4 @@ "name": "worker-ts", "main": "src/index.ts", "compatibility_date": "2023-05-04", - "services": [{ "binding": "APP", "service": "worker-app" }], } diff --git a/fixtures/workers-shared-asset-config/package.json b/fixtures/workers-shared-asset-config/package.json index a9ba962d046c..99093ccde6c2 100644 --- a/fixtures/workers-shared-asset-config/package.json +++ b/fixtures/workers-shared-asset-config/package.json @@ -13,7 +13,7 @@ "@cloudflare/vitest-pool-workers": "workspace:*", "@cloudflare/workers-shared": "workspace:*", "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "run-script-os": "^1.1.6", "typescript": "catalog:default", "undici": "catalog:default", diff --git a/fixtures/workers-with-assets-and-service-bindings/package.json b/fixtures/workers-with-assets-and-service-bindings/package.json index e4bc8cad5c88..3cda0f9ba5aa 100644 --- a/fixtures/workers-with-assets-and-service-bindings/package.json +++ b/fixtures/workers-with-assets-and-service-bindings/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/workers-with-assets-only/package.json b/fixtures/workers-with-assets-only/package.json index d8d908d349f8..12ee3af8cd06 100644 --- a/fixtures/workers-with-assets-only/package.json +++ b/fixtures/workers-with-assets-only/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/workers-with-assets-run-worker-first/package.json b/fixtures/workers-with-assets-run-worker-first/package.json index 6c179242f148..5cc090648744 100644 --- a/fixtures/workers-with-assets-run-worker-first/package.json +++ b/fixtures/workers-with-assets-run-worker-first/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/workers-with-assets-spa/package.json b/fixtures/workers-with-assets-spa/package.json index 702cf37ed57f..b723dcc082de 100644 --- a/fixtures/workers-with-assets-spa/package.json +++ b/fixtures/workers-with-assets-spa/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "@types/jest-image-snapshot": "^6.4.0", "@types/node": "catalog:default", "jest-image-snapshot": "^6.4.0", diff --git a/fixtures/workers-with-assets-static-routing/package.json b/fixtures/workers-with-assets-static-routing/package.json new file mode 100644 index 000000000000..ed61b21502e1 --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/package.json @@ -0,0 +1,26 @@ +{ + "name": "@fixture/workers-with-assets-static-routing", + "private": true, + "scripts": { + "check:type": "tsc", + "dev": "wrangler dev", + "dev:spa": "wrangler dev -c spa.wrangler.jsonc", + "pretest:ci": "pnpm playwright install chromium", + "test:ci": "vitest run", + "test:watch": "vitest", + "type:tests": "tsc -p ./test/tsconfig.json" + }, + "devDependencies": { + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20250617.0", + "playwright-chromium": "catalog:default", + "typescript": "catalog:default", + "undici": "catalog:default", + "vitest": "catalog:default", + "wrangler": "workspace:*" + }, + "volta": { + "node": "20.19.2", + "extends": "../../package.json" + } +} diff --git a/fixtures/workers-with-assets-static-routing/public/static/page.html b/fixtures/workers-with-assets-static-routing/public/static/page.html new file mode 100644 index 000000000000..c61edebd1b80 --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/public/static/page.html @@ -0,0 +1 @@ +

A normal asset

diff --git a/fixtures/workers-with-assets-static-routing/public/worker/asset.html b/fixtures/workers-with-assets-static-routing/public/worker/asset.html new file mode 100644 index 000000000000..1052ed12b5af --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/public/worker/asset.html @@ -0,0 +1 @@ +

Hello, I'm an asset!

diff --git a/fixtures/workers-with-assets-static-routing/public/worker/worker-runs.html b/fixtures/workers-with-assets-static-routing/public/worker/worker-runs.html new file mode 100644 index 000000000000..57c660beaf1d --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/public/worker/worker-runs.html @@ -0,0 +1 @@ +

Hello, I'm an asset at /worker/worker-runs.html!

diff --git a/fixtures/workers-with-assets-static-routing/spa-assets/index.html b/fixtures/workers-with-assets-static-routing/spa-assets/index.html new file mode 100644 index 000000000000..b696b5e4cd9a --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/spa-assets/index.html @@ -0,0 +1,14 @@ + + + + I'm an index.html for a SPA + + +

Here I am, at /!

+ + + diff --git a/fixtures/workers-with-assets-static-routing/spa.wrangler.jsonc b/fixtures/workers-with-assets-static-routing/spa.wrangler.jsonc new file mode 100644 index 000000000000..f1472a236ffe --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/spa.wrangler.jsonc @@ -0,0 +1,11 @@ +{ + "name": "workers-with-assets-static-routing", + "main": "src/index.ts", + "compatibility_date": "2025-05-20", + "assets": { + "binding": "ASSETS", + "directory": "./spa-assets", + "not_found_handling": "single-page-application", + "run_worker_first": ["/api/*", "!/api/asset"], + }, +} diff --git a/fixtures/workers-with-assets-static-routing/src/index.ts b/fixtures/workers-with-assets-static-routing/src/index.ts new file mode 100644 index 000000000000..558007744cb2 --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/src/index.ts @@ -0,0 +1,31 @@ +export type Env = { + ASSETS: Fetcher; +}; + +export default { + async fetch(request, env, ctx): Promise { + const { pathname } = new URL(request.url); + + // api routes + if (pathname.startsWith("/api/")) { + return Response.json({ some: ["json", "response"] }); + } + + // asset middleware + const assetResp = await env.ASSETS.fetch(request); + if (assetResp.ok) { + let text = await assetResp.text(); + text = text.replace( + "I'm an asset", + "I'm an asset (and was intercepted by the User Worker)" + ); + return new Response(text, { + headers: assetResp.headers, + status: assetResp.status, + }); + } + + // default handling + return new Response("404 from the User Worker", { status: 404 }); + }, +} satisfies ExportedHandler; diff --git a/fixtures/workers-with-assets-static-routing/test/index.test.ts b/fixtures/workers-with-assets-static-routing/test/index.test.ts new file mode 100644 index 000000000000..3d90b46a3138 --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/test/index.test.ts @@ -0,0 +1,181 @@ +import { resolve } from "node:path"; +import { Browser, chromium } from "playwright-chromium"; +import { afterAll, beforeAll, describe, it } from "vitest"; +import { runWranglerDev } from "../../shared/src/run-wrangler-long-lived"; + +describe("[Workers + Assets] static routing", () => { + describe("static routing behavior", () => { + let ip: string, port: number, stop: (() => Promise) | undefined; + + beforeAll(async () => { + ({ ip, port, stop } = await runWranglerDev(resolve(__dirname, ".."), [ + "--port=0", + "--inspector-port=0", + ])); + }); + + afterAll(async () => { + await stop?.(); + }); + + it("should serve assets when they exist for a path", async ({ expect }) => { + let response = await fetch(`http://${ip}:${port}/static/page`); + expect(response.status).toBe(200); + expect(await response.text()).toContain(`

A normal asset

`); + }); + + it("should run the worker when no assets exist for a path", async ({ + expect, + }) => { + let response = await fetch(`http://${ip}:${port}/`); + expect(response.status).toBe(404); + expect(await response.text()).toContain(`404 from the User Worker`); + }); + + it("should run the worker when a positive run_worker_first rule matches", async ({ + expect, + }) => { + let response = await fetch(`http://${ip}:${port}/worker/worker-runs`); + expect(response.status).toBe(200); + expect(await response.text()).toContain( + `

Hello, I'm an asset (and was intercepted by the User Worker) at /worker/worker-runs.html!

` + ); + }); + + it("should serve an asset when a negative run_worker_first rule matches", async ({ + expect, + }) => { + let response = await fetch(`http://${ip}:${port}/missing-asset`); + expect(response.status).toBe(404); + expect(await response.text()).toEqual(""); + }); + + it("should serve an asset when both a positive and negative (asset) run_worker_first matches", async ({ + expect, + }) => { + let response = await fetch(`http://${ip}:${port}/worker/asset`); + expect(response.status).toBe(200); + expect(await response.text()).toContain(`

Hello, I'm an asset!

`); + }); + }); + + describe("static routing + SPA behavior", async () => { + let ip: string, port: number, stop: (() => Promise) | undefined; + + beforeAll(async () => { + ({ ip, port, stop } = await runWranglerDev(resolve(__dirname, ".."), [ + "-c=spa.wrangler.jsonc", + "--port=0", + "--inspector-port=0", + ])); + }); + + afterAll(async () => { + await stop?.(); + }); + + describe("browser navigation", () => { + let browser: Browser | undefined; + + beforeAll(async () => { + browser = await chromium.launch({ + headless: !process.env.VITE_DEBUG_SERVE, + args: process.env.CI + ? ["--no-sandbox", "--disable-setuid-sandbox"] + : undefined, + }); + }); + + afterAll(async () => { + await browser?.close(); + }); + + it("renders the root with index.html", async ({ expect }) => { + if (!browser) { + throw new Error("Browser couldn't be initialized"); + } + + const page = await browser.newPage({ + baseURL: `http://${ip}:${port}`, + }); + await page.goto("/"); + expect(await page.getByRole("heading").innerText()).toBe( + "Here I am, at /!" + ); + }); + + it("renders another path with index.html", async ({ expect }) => { + if (!browser) { + throw new Error("Browser couldn't be initialized"); + } + + const page = await browser.newPage({ + baseURL: `http://${ip}:${port}`, + }); + await page.goto("/some/page"); + expect(await page.getByRole("heading").innerText()).toBe( + "Here I am, at /some/page!" + ); + }); + + it("renders an include path with the User worker", async ({ expect }) => { + if (!browser) { + throw new Error("Browser couldn't be initialized"); + } + + const page = await browser.newPage({ + baseURL: `http://${ip}:${port}`, + }); + const response = await page.goto("/api/route"); + expect(response?.headers()).toHaveProperty( + "content-type", + "application/json" + ); + expect(await page.content()).toContain(`{"some":["json","response"]}`); + }); + + it("renders an exclude path with index.html", async ({ expect }) => { + if (!browser) { + throw new Error("Browser couldn't be initialized"); + } + + const page = await browser.newPage({ + baseURL: `http://${ip}:${port}`, + }); + await page.goto("/api/asset"); + expect(await page.getByRole("heading").innerText()).toBe( + "Here I am, at /api/asset!" + ); + }); + }); + + describe("non-browser navigation", () => { + it("renders the root with index.html", async ({ expect }) => { + let response = await fetch(`http://${ip}:${port}`); + expect(response.status).toBe(200); + expect(await response.text()).toContain(`I'm an index.html for a SPA`); + }); + + it("renders another path with index.html", async ({ expect }) => { + let response = await fetch(`http://${ip}:${port}/some/page`); + expect(response.status).toBe(200); + expect(await response.text()).toContain(`I'm an index.html for a SPA`); + }); + + it("renders an include path with the User worker", async ({ expect }) => { + let response = await fetch(`http://${ip}:${port}/api/route`); + expect(response.status).toBe(200); + expect(response.headers.get("content-type")).toEqual( + "application/json" + ); + expect(await response.text()).toContain(`{"some":["json","response"]}`); + }); + + it("renders an exclude path with index.html", async ({ expect }) => { + let response = await fetch(`http://${ip}:${port}/api/asset`); + expect(response.status).toBe(200); + expect(await response.text()).toContain(`I'm an index.html for a SPA`); + }); + }); + }); +}); diff --git a/fixtures/workers-with-assets-static-routing/test/tsconfig.json b/fixtures/workers-with-assets-static-routing/test/tsconfig.json new file mode 100644 index 000000000000..d2ce7f144694 --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/test/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/workers-with-assets-static-routing/tsconfig.json b/fixtures/workers-with-assets-static-routing/tsconfig.json new file mode 100644 index 000000000000..4e71d915af3f --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "lib": ["ES2020"], + "types": ["@cloudflare/workers-types"], + "moduleResolution": "node", + "esModuleInterop": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["**/*.ts"], + "exclude": ["tests"] +} diff --git a/fixtures/workers-with-assets-static-routing/wrangler.jsonc b/fixtures/workers-with-assets-static-routing/wrangler.jsonc new file mode 100644 index 000000000000..cc23155dfcbb --- /dev/null +++ b/fixtures/workers-with-assets-static-routing/wrangler.jsonc @@ -0,0 +1,17 @@ +{ + "name": "workers-with-assets-static-routing", + "main": "src/index.ts", + "compatibility_date": "2025-05-20", + "assets": { + "binding": "ASSETS", + "directory": "./public", + "run_worker_first": [ + // The `/oauth/callback` path and anything under `/worker` will be served by the Worker first + "/worker/*", + "/oauth/callback", + // The `/missing-asset` and `worker/asset` paths will not be served by the Worker first + "!/missing-asset", + "!/worker/asset", + ], + }, +} diff --git a/fixtures/workers-with-assets/package.json b/fixtures/workers-with-assets/package.json index b3032f3355f4..851dd19a5a18 100644 --- a/fixtures/workers-with-assets/package.json +++ b/fixtures/workers-with-assets/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/workflow-multiple/package.json b/fixtures/workflow-multiple/package.json index 0997afeeac89..6e591fd6ebfb 100644 --- a/fixtures/workflow-multiple/package.json +++ b/fixtures/workflow-multiple/package.json @@ -7,7 +7,7 @@ "test:ci": "vitest run" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/fixtures/workflow/package.json b/fixtures/workflow/package.json index 1654f522f525..34260cb8f5b8 100644 --- a/fixtures/workflow/package.json +++ b/fixtures/workflow/package.json @@ -8,7 +8,7 @@ }, "devDependencies": { "@cloudflare/workers-tsconfig": "workspace:*", - "@cloudflare/workers-types": "^4.20250604.0", + "@cloudflare/workers-types": "^4.20250617.0", "typescript": "catalog:default", "undici": "catalog:default", "vitest": "catalog:default", From 28d929ee3af3ea938b6f254d6ffb8f9b7a592881 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Tue, 1 Jul 2025 13:35:46 +0100 Subject: [PATCH 06/12] Add comments --- packages/miniflare/src/runtime/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/miniflare/src/runtime/index.ts b/packages/miniflare/src/runtime/index.ts index 6c42af168275..7522bce18e02 100644 --- a/packages/miniflare/src/runtime/index.ts +++ b/packages/miniflare/src/runtime/index.ts @@ -121,6 +121,11 @@ function getRuntimeArgs(options: RuntimeOptions) { return args; } +/** + * Copied from https://github.com/microsoft/vscode-js-debug/blob/0b5e0dade997b3c702a98e1f58989afcb30612d6/src/targets/node/bootloader/environment.ts#L129 + * + * This function returns the segment of process.env.VSCODE_INSPECTOR_OPTIONS that corresponds to the current process (rather than a parent process) + */ function getInspectorOptions() { const value = process.env.VSCODE_INSPECTOR_OPTIONS; if (!value) { @@ -197,6 +202,8 @@ export class Runtime { const info = getInspectorOptions(); for (const worker of workers ?? []) { + // This is copied from https://github.com/microsoft/vscode-js-debug/blob/0b5e0dade997b3c702a98e1f58989afcb30612d6/src/targets/node/bootloader.ts#L284 + // It spawns a detached "watchdog" process for each corresponding (user) Worker in workerd which will maintain the VSCode debug connection const p = spawn(process.execPath, [watchdogPath], { env: { NODE_INSPECTOR_INFO: JSON.stringify({ From 851c71efbb0b911a6003f6be3d73ca89c352901c Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Tue, 1 Jul 2025 14:03:03 +0100 Subject: [PATCH 07/12] fix format --- .changeset/perfect-plants-compete.md | 2 +- packages/wrangler/src/dev/hotkeys.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/perfect-plants-compete.md b/.changeset/perfect-plants-compete.md index 0b4947af35b1..1207542a8fd4 100644 --- a/.changeset/perfect-plants-compete.md +++ b/.changeset/perfect-plants-compete.md @@ -3,6 +3,6 @@ "wrangler": patch --- -In 2023 we announced [breakpoint debugging support](https://blog.cloudflare.com/debugging-cloudflare-workers/) for Workers, which meant that you could easily debug your Worker code in Wrangler's built-in devtools (accessible via the `[d]` hotkey) as well as multiple other devtools clients, [including VSCode](https://developers.cloudflare.com/workers/observability/dev-tools/breakpoints/). For most developers, breakpoint debugging via VSCode is the most natural flow, but until now it's required [manually configuring a `launch.json` file](https://developers.cloudflare.com/workers/observability/dev-tools/breakpoints/#setup-vs-code-to-use-breakpoints), running `wrangler dev`, and connecting via VSCode's built-in debugger. +In 2023 we announced [breakpoint debugging support](https://blog.cloudflare.com/debugging-cloudflare-workers/) for Workers, which meant that you could easily debug your Worker code in Wrangler's built-in devtools (accessible via the `[d]` hotkey) as well as multiple other devtools clients, [including VSCode](https://developers.cloudflare.com/workers/observability/dev-tools/breakpoints/). For most developers, breakpoint debugging via VSCode is the most natural flow, but until now it's required [manually configuring a `launch.json` file](https://developers.cloudflare.com/workers/observability/dev-tools/breakpoints/#setup-vs-code-to-use-breakpoints), running `wrangler dev`, and connecting via VSCode's built-in debugger. Now, using VSCode's built-in [JavaScript Debug Terminals](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_javascript-debug-terminal), there are just two steps: open a JS debug terminal and run `wrangler dev` (or `vite dev`). VSCode will automatically connect to your running Worker (even if you're running multiple Workers at once!) and start a debugging session. diff --git a/packages/wrangler/src/dev/hotkeys.ts b/packages/wrangler/src/dev/hotkeys.ts index 771758eb2e9d..6277df968b51 100644 --- a/packages/wrangler/src/dev/hotkeys.ts +++ b/packages/wrangler/src/dev/hotkeys.ts @@ -1,4 +1,3 @@ -import assert from "assert"; import { randomUUID } from "crypto"; import { LocalRuntimeController } from "../api/startDevWorker/LocalRuntimeController"; import registerHotKeys from "../cli-hotkeys"; From c655618d4f5b0808c95e7648fa41ccd63782c215 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Wed, 2 Jul 2025 13:24:39 +0100 Subject: [PATCH 08/12] fixups --- packages/miniflare/src/index.ts | 4 +--- packages/miniflare/src/runtime/index.ts | 8 ++++---- packages/wrangler/src/dev/hotkeys.ts | 4 +++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index f62b08d47f48..94cca394edbe 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -1990,9 +1990,7 @@ export class Miniflare { const maybeSocketPorts = await this.#runtime.updateConfig( configBuffer, runtimeOpts, - this.#workerOpts - .filter((w) => w.core.name) - .map((w) => w.core.name) as string[] + this.#workerOpts.flatMap((w) => w.core.name ?? []) ); if (this.#disposeController.signal.aborted) return; if (maybeSocketPorts === undefined) { diff --git a/packages/miniflare/src/runtime/index.ts b/packages/miniflare/src/runtime/index.ts index 7522bce18e02..97e02ed4d78a 100644 --- a/packages/miniflare/src/runtime/index.ts +++ b/packages/miniflare/src/runtime/index.ts @@ -154,7 +154,7 @@ export class Runtime { async updateConfig( configBuffer: Buffer, options: Abortable & RuntimeOptions, - workers: string[] | undefined + workerNames: string[] ): Promise { // 1. Stop existing process (if any) and wait for exit await this.dispose(); @@ -201,7 +201,7 @@ export class Runtime { const info = getInspectorOptions(); - for (const worker of workers ?? []) { + for (const name of workerNames) { // This is copied from https://github.com/microsoft/vscode-js-debug/blob/0b5e0dade997b3c702a98e1f58989afcb30612d6/src/targets/node/bootloader.ts#L284 // It spawns a detached "watchdog" process for each corresponding (user) Worker in workerd which will maintain the VSCode debug connection const p = spawn(process.execPath, [watchdogPath], { @@ -209,8 +209,8 @@ export class Runtime { NODE_INSPECTOR_INFO: JSON.stringify({ ipcAddress: info.inspectorIpc || "", pid: String(this.#process.pid), - scriptName: worker, - inspectorURL: `ws://127.0.0.1:${ports?.get(kInspectorSocket)}/core:user:${worker}`, + scriptName: name, + inspectorURL: `ws://127.0.0.1:${ports?.get(kInspectorSocket)}/core:user:${name}`, waitForDebugger: true, ownId: randomBytes(12).toString("hex"), openerId: info.openerId, diff --git a/packages/wrangler/src/dev/hotkeys.ts b/packages/wrangler/src/dev/hotkeys.ts index 6277df968b51..0be5d709ceb4 100644 --- a/packages/wrangler/src/dev/hotkeys.ts +++ b/packages/wrangler/src/dev/hotkeys.ts @@ -22,11 +22,13 @@ export default function registerDevHotKeys( { keys: ["d"], label: "open devtools", + // Don't display this hotkey if we're in a VSCode debug session + disabled: !!process.env.VSCODE_INSPECTOR_OPTIONS, handler: async () => { const { inspectorUrl } = await devEnv.proxy.ready.promise; if (!inspectorUrl) { - logger.error("Inspector not available"); + logger.warn("DevTools is not available while in a debug terminal"); } else { // TODO: refactor this function to accept a whole URL (not just .port and assuming .hostname) await openInspector( From 21403c3d6bde71582acb5b4afc5a75cc4e2e51cb Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Wed, 2 Jul 2025 15:45:18 +0100 Subject: [PATCH 09/12] bump timeout --- packages/vite-plugin-cloudflare/e2e/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite-plugin-cloudflare/e2e/helpers.ts b/packages/vite-plugin-cloudflare/e2e/helpers.ts index 056c36d55649..542f644e9a17 100644 --- a/packages/vite-plugin-cloudflare/e2e/helpers.ts +++ b/packages/vite-plugin-cloudflare/e2e/helpers.ts @@ -64,7 +64,7 @@ export function seed(fixture: string, pm: "pnpm" | "yarn" | "npm") { maxRetries: 10, }); } - }); + }, 40_000); return projectPath; } From 30ad19bbb7db7d0f2469f7bd2c801c9b624f579a Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Wed, 2 Jul 2025 17:43:05 +0100 Subject: [PATCH 10/12] loosen stack trace matching --- .../wrangler/src/__tests__/sentry.test.ts | 400 +++++++++--------- 1 file changed, 200 insertions(+), 200 deletions(-) diff --git a/packages/wrangler/src/__tests__/sentry.test.ts b/packages/wrangler/src/__tests__/sentry.test.ts index 9a92a130f401..0501775707a0 100644 --- a/packages/wrangler/src/__tests__/sentry.test.ts +++ b/packages/wrangler/src/__tests__/sentry.test.ts @@ -227,206 +227,206 @@ describe("sentry", () => { // If more data is included in the Sentry request, we'll need to verify it // couldn't contain PII and update this snapshot - expect(event).toMatchInlineSnapshot(` - Object { - "data": Object { - "breadcrumbs": Array [ - Object { - "level": "log", - "message": "wrangler whoami", - "timestamp": 0, - }, - ], - "contexts": Object { - "app": Object { - "app_memory": 0, - "app_start_time": "", - }, - "cloud_resource": Object {}, - "device": Object {}, - "os": Object {}, - "runtime": Object { - "name": "node", - "version": "", - }, - "trace": Object { - "span_id": "", - "trace_id": "", - }, - }, - "environment": "production", - "event_id": "", - "exception": Object { - "values": Array [ - Object { - "mechanism": Object { - "handled": true, - "type": "generic", - }, - "stacktrace": Object { - "frames": Array [ - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/core/register-yargs-command.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "register-yargs-command.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/user/commands.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "commands.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/user/whoami.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "whoami.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/user/whoami.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "whoami.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/user/whoami.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "whoami.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/cfetch/index.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "index.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/cfetch/internal.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "internal.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/wrangler/packages/wrangler/src/cfetch/internal.ts", - "function": "", - "in_app": false, - "lineno": 0, - "module": "internal.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/project/...", - "function": "", - "in_app": false, - "lineno": 0, - "module": "@mswjs.interceptors.src.interceptors.fetch:index.ts", - "post_context": Array [], - "pre_context": Array [], - }, - Object { - "colno": 0, - "context_line": "", - "filename": "/project/...", - "function": "", - "in_app": false, - "lineno": 0, - "module": "@mswjs.interceptors.src.interceptors.fetch:index.ts", - "post_context": Array [], - "pre_context": Array [], - }, - ], - }, - "type": "TypeError", - "value": "Failed to fetch", - }, - ], - }, - "modules": Object {}, - "platform": "node", - "release": "", - "sdk": Object { - "integrations": Array [ - "InboundFilters", - "FunctionToString", - "LinkedErrors", - "Console", - "OnUncaughtException", - "OnUnhandledRejection", - "ContextLines", - "Context", - "Modules", - ], - "name": "sentry.javascript.node", - "packages": Array [ - Object { - "name": "npm:@sentry/node", - "version": "7.87.0", - }, - ], - "version": "7.87.0", - }, - "timestamp": 0, - }, - "header": Object { - "event_id": "", - "sdk": Object { - "name": "sentry.javascript.node", - "version": "7.87.0", - }, - "sent_at": "", - "trace": Object { - "environment": "production", - "public_key": "9edbb8417b284aa2bbead9b4c318918b", - "release": "", - "trace_id": "", - }, - }, - "type": Object { - "type": "event", - }, - } - `); + expect(event).toStrictEqual({ + data: { + breadcrumbs: [ + { + level: "log", + message: "wrangler whoami", + timestamp: 0, + }, + ], + contexts: { + app: { + app_memory: 0, + app_start_time: "", + }, + cloud_resource: {}, + device: {}, + os: {}, + runtime: { + name: "node", + version: "", + }, + trace: { + span_id: "", + trace_id: "", + }, + }, + environment: "production", + event_id: "", + exception: { + values: [ + { + mechanism: { + handled: true, + type: "generic", + }, + stacktrace: { + frames: [ + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: expect.stringContaining(".ts"), + function: "", + in_app: false, + lineno: 0, + module: expect.stringContaining(".ts"), + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: "/project/...", + function: "", + in_app: false, + lineno: 0, + module: + "@mswjs.interceptors.src.interceptors.fetch:index.ts", + post_context: [], + pre_context: [], + }, + { + colno: 0, + context_line: "", + filename: "/project/...", + function: "", + in_app: false, + lineno: 0, + module: + "@mswjs.interceptors.src.interceptors.fetch:index.ts", + post_context: [], + pre_context: [], + }, + ], + }, + type: "TypeError", + value: "Failed to fetch", + }, + ], + }, + modules: {}, + platform: "node", + release: "", + sdk: { + integrations: [ + "InboundFilters", + "FunctionToString", + "LinkedErrors", + "Console", + "OnUncaughtException", + "OnUnhandledRejection", + "ContextLines", + "Context", + "Modules", + ], + name: "sentry.javascript.node", + packages: [ + { + name: "npm:@sentry/node", + version: "7.87.0", + }, + ], + version: "7.87.0", + }, + timestamp: 0, + }, + header: { + event_id: "", + sdk: { + name: "sentry.javascript.node", + version: "7.87.0", + }, + sent_at: "", + trace: { + environment: "production", + public_key: "9edbb8417b284aa2bbead9b4c318918b", + release: "", + trace_id: "", + }, + }, + type: { + type: "event", + }, + }); }); }); }); From f86dd6eb373dd500dbf9bf2772c2beefd95e4c1e Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Wed, 2 Jul 2025 22:23:27 +0100 Subject: [PATCH 11/12] Support Vite --- packages/miniflare/src/index.ts | 7 ++++++- packages/vite-plugin-cloudflare/src/index.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index 94cca394edbe..14d2aa21b9f4 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -966,7 +966,12 @@ export class Miniflare { this.#log = this.#sharedOpts.core.log ?? new NoOpLog(); - if (enableInspectorProxy) { + // If we're in a JavaScript Debug terminal, Miniflare will send the inspector ports directly to VSCode for registration + // As such, we don't need our inspector proxy and in fact including it causes issue with multiple clients connected to the + // inspector endpoint. + const inVscodeJsDebugTerminal = !!process.env.VSCODE_INSPECTOR_OPTIONS; + + if (enableInspectorProxy && !inVscodeJsDebugTerminal) { if (this.#sharedOpts.core.inspectorPort === undefined) { throw new MiniflareCoreError( "ERR_MISSING_INSPECTOR_PROXY_PORT", diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts index 36ba3cfb0f05..68a546fce8af 100644 --- a/packages/vite-plugin-cloudflare/src/index.ts +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -740,6 +740,13 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] { enforce: "pre", configureServer(viteDevServer) { assertIsNotPreview(resolvedPluginConfig); + // If we're in a JavaScript Debug terminal, Miniflare will send the inspector ports directly to VSCode for registration + // As such, we don't need our inspector proxy and in fact including it causes issue with multiple clients connected to the + // inspector endpoint. + const inVscodeJsDebugTerminal = !!process.env.VSCODE_INSPECTOR_OPTIONS; + if (inVscodeJsDebugTerminal) { + return; + } if ( resolvedPluginConfig.type === "workers" && @@ -772,6 +779,13 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] { }, async configurePreviewServer(vitePreviewServer) { assertIsPreview(resolvedPluginConfig); + // If we're in a JavaScript Debug terminal, Miniflare will send the inspector ports directly to VSCode for registration + // As such, we don't need our inspector proxy and in fact including it causes issue with multiple clients connected to the + // inspector endpoint. + const inVscodeJsDebugTerminal = !!process.env.VSCODE_INSPECTOR_OPTIONS; + if (inVscodeJsDebugTerminal) { + return; + } if ( resolvedPluginConfig.workers.length >= 1 && From ba17331fddacb3da107fdf159264d9762d8dd5f0 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Wed, 2 Jul 2025 22:45:59 +0100 Subject: [PATCH 12/12] fix sentry test --- .../wrangler/src/__tests__/sentry.test.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/wrangler/src/__tests__/sentry.test.ts b/packages/wrangler/src/__tests__/sentry.test.ts index 0501775707a0..9d38af82d14a 100644 --- a/packages/wrangler/src/__tests__/sentry.test.ts +++ b/packages/wrangler/src/__tests__/sentry.test.ts @@ -267,88 +267,88 @@ describe("sentry", () => { { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], }, { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], }, { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], }, { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], }, { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], }, { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], }, { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], }, { colno: 0, context_line: "", - filename: expect.stringContaining(".ts"), + filename: expect.any(String), function: "", in_app: false, lineno: 0, - module: expect.stringContaining(".ts"), + module: expect.any(String), post_context: [], pre_context: [], },