From c0cd0971324ada343c52ca9d1f484ed3b50594e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Mon, 27 Jul 2026 14:56:30 +0300 Subject: [PATCH] fix(v10/server-utils): Do not inject dc into client bundle Backport of: #22668 --- .../client-build/client/main.ts | 7 ++++ .../client-build/index.html | 9 +++++ .../diagnostics-channel/client-build/index.ts | 12 +++++++ .../diagnostics-channel/client-build/test.ts | 33 +++++++++++++++++++ .../client-build/vite.config.mts | 14 ++++++++ .../client-build/wrangler.jsonc | 15 +++++++++ .../src/orchestrion/bundler/vite.ts | 6 ++++ 7 files changed, 96 insertions(+) create mode 100644 dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/client/main.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.html create mode 100644 dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/test.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/vite.config.mts create mode 100644 dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/wrangler.jsonc diff --git a/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/client/main.ts b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/client/main.ts new file mode 100644 index 000000000000..7d443658fd7b --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/client/main.ts @@ -0,0 +1,7 @@ +import { streamText } from 'ai'; + +// The browser bundle also pulls in an orchestrion-instrumented module (`ai`). +// Injected `node:diagnostics_channel` calls only exist server-side, so the +// client bundle must stay free of them (they throw `X is not a function` in +// the browser otherwise). +document.title = `streamText: ${typeof streamText}`; diff --git a/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.html b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.html new file mode 100644 index 000000000000..9241085484ab --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.html @@ -0,0 +1,9 @@ + + + + orchestrion client build + + + + + diff --git a/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.ts b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.ts new file mode 100644 index 000000000000..df01c6e476cb --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/index.ts @@ -0,0 +1,12 @@ +import { streamText } from 'ai'; + +// The worker imports an orchestrion-instrumented module (`ai`), so the server +// bundle is expected to contain `diagnostics_channel` injections. +export default { + async fetch(request: Request): Promise { + if (new URL(request.url).pathname === '/worker') { + return new Response(`streamText: ${typeof streamText}`); + } + return new Response('not found', { status: 404 }); + }, +}; diff --git a/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/test.ts b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/test.ts new file mode 100644 index 000000000000..3d858b0a3068 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/test.ts @@ -0,0 +1,33 @@ +import { readdirSync, readFileSync } from 'fs'; +import { join } from 'path'; +import { expect, it } from 'vitest'; +import { createRunner } from '../../../../runner'; + +function readBundles(dir: string): string { + return readdirSync(dir, { withFileTypes: true, recursive: true }) + .filter(entry => entry.isFile() && /\.m?js$/.test(entry.name)) + .map(entry => readFileSync(join(entry.parentPath, entry.name), 'utf8')) + .join('\n'); +} + +// Regression test: orchestrion splices `node:diagnostics_channel` calls into +// instrumented modules, which only exist server-side. When a worker ships +// browser assets, Vite produces a `client` bundle next to the server (worker) +// bundle — and the injected `tracingChannel` calls used to land in the client +// bundle too, where they throw `X is not a function` in the browser. +it('injects diagnostics_channel calls into the server bundle only, not the client bundle', async ({ signal }) => { + const runner = createRunner(__dirname).start(signal); + + // Waits for `vite build` + wrangler boot and proves the instrumented worker + // still runs. + const response = await runner.makeRequest('get', '/worker'); + expect(response).toBe('streamText: function'); + + // The worker imports `ai`, so the server bundle must actually be + // instrumented — otherwise a plugin that never runs would also pass. + const workerBundle = readBundles(join(__dirname, 'dist', 'cloudflare_vite_dc_client_build')); + expect(workerBundle).toContain('orchestrion:ai:streamText'); + + const clientBundle = readBundles(join(__dirname, 'dist', 'client')); + expect(clientBundle).not.toContain('orchestrion:ai'); +}); diff --git a/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/vite.config.mts b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/vite.config.mts new file mode 100644 index 000000000000..541d36ac0a61 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/vite.config.mts @@ -0,0 +1,14 @@ +import { cloudflare } from '@cloudflare/vite-plugin'; +import { sentryCloudflareVitePlugin } from '@sentry/cloudflare/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + cloudflare(), + sentryCloudflareVitePlugin({ + _experimental: { + useDiagnosticsChannelInjection: true, + }, + }), + ], +}); diff --git a/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/wrangler.jsonc b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/wrangler.jsonc new file mode 100644 index 000000000000..6c4f68500e1e --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/client-build/wrangler.jsonc @@ -0,0 +1,15 @@ +{ + "$schema": "../../../node_modules/wrangler/config-schema.json", + "name": "cloudflare-vite-dc-client-build", + // `main` points at the source entry; the runner detects `vite.config.mts`, runs + // `vite build`, and serves the built output (so the orchestrion transform runs). + "main": "index.ts", + "compatibility_date": "2026-04-26", + "compatibility_flags": ["nodejs_compat"], + // Giving the worker assets makes the Cloudflare Vite plugin produce a browser + // (`client`) bundle next to the server (worker) bundle — the setup where the + // orchestrion plugin must not touch the client output. + "assets": { + "directory": "./dist/client", + }, +} diff --git a/packages/server-utils/src/orchestrion/bundler/vite.ts b/packages/server-utils/src/orchestrion/bundler/vite.ts index 0b586f0b682c..5bbf4171a8e4 100644 --- a/packages/server-utils/src/orchestrion/bundler/vite.ts +++ b/packages/server-utils/src/orchestrion/bundler/vite.ts @@ -21,6 +21,12 @@ import { externalEntryMatchesModule, externalizedModulesWarning, orchestrionTran export function sentryOrchestrionPlugin(options: PluginOptions = {}): ReturnType { return { ...codeTransformer(orchestrionTransformOptions(options)), + applyToEnvironment(environment) { + // Orchestrion splices `node:diagnostics_channel` calls into instrumented modules, which only + // exist server-side. Only apply to server-consumed environments so injected `tracingChannel` + // calls never land in a browser (`client`) bundle (where they'd throw `X is not a function`). + return environment.config.consumer === 'server'; + }, config(): { ssr: { noExternal: string[] } } { // Force-bundle every instrumented package so the code transform actually // sees its source. Vite externalizes dependencies in SSR builds by