From b99f4371be6ffbadad7e60bd01941cda62f53bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Wed, 29 Jul 2026 11:42:20 +0300 Subject: [PATCH] feat(v10/cloudflare): Add wranglerConfigPath to Vite options Backport of: #22800 --- .../cloudflare/src/vite/autoInstrument.ts | 16 ++++- packages/cloudflare/src/vite/index.ts | 15 ++++- .../test/vite/autoInstrument.test.ts | 60 +++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/packages/cloudflare/src/vite/autoInstrument.ts b/packages/cloudflare/src/vite/autoInstrument.ts index 1136cd48b4da..aa410ef98fa4 100644 --- a/packages/cloudflare/src/vite/autoInstrument.ts +++ b/packages/cloudflare/src/vite/autoInstrument.ts @@ -1,3 +1,4 @@ +import { basename } from 'node:path'; import { collectAgentCandidates, detectAgentClasses, type ModuleResolver } from './agentClass'; import { buildOptionsImport, ENV_FALLBACK_OPTIONS_FN, resolveInstrumentFile } from './instrumentFile'; import { applyAutoInstrumentTransforms, type ClassWrapperKind, type ProgramBody } from './transform'; @@ -14,7 +15,7 @@ function normalizePath(path: string): string { // `.html`, … — sharing the entry's basename must never be treated as the entry. const JS_EXTENSION_REGEX = /\.[cm]?[jt]sx?$/; -export function sentryCloudflareAutoInstrumentPlugin() { +export function sentryCloudflareAutoInstrumentPlugin(options: { wranglerConfigPath?: string } = {}) { let wranglerConfig: WranglerConfig | undefined; let entryFilePath: string | undefined; @@ -25,9 +26,18 @@ export function sentryCloudflareAutoInstrumentPlugin() { name: 'sentry-cloudflare-auto-instrument', configResolved(config: { root: string; logger?: { warn(msg: string): void } }): void { - const result = resolveWranglerConfig(config.root); + const result = resolveWranglerConfig(config.root, options.wranglerConfigPath); if (!result) { - config.logger?.warn('[sentry] No parseable wrangler config found — auto-instrumentation disabled.'); + // An explicit path that fails is a misconfiguration worth naming; + // without one, hint at the option so custom-named configs (e.g. a + // `configPath` handed to @cloudflare/vite-plugin) are discoverable. + config.logger?.warn( + options.wranglerConfigPath + ? `[sentry] Could not find or parse the wrangler config "${basename(options.wranglerConfigPath)}" ` + + '(resolved against the Vite root) — auto-instrumentation disabled.' + : '[sentry] No parseable wrangler config found — auto-instrumentation disabled. ' + + 'Set `wranglerConfigPath` if your config uses a custom name.', + ); return; } diff --git a/packages/cloudflare/src/vite/index.ts b/packages/cloudflare/src/vite/index.ts index 094c7df03081..035ce9b3e4ec 100644 --- a/packages/cloudflare/src/vite/index.ts +++ b/packages/cloudflare/src/vite/index.ts @@ -10,6 +10,17 @@ import { sentryCloudflareAutoInstrumentPlugin } from './autoInstrument'; * Options for {@link sentryCloudflareVitePlugin}. */ export interface SentryCloudflareVitePluginOptions { + /** + * Path to the wrangler config, relative to the Vite root (or absolute). + * Set this when your config doesn't use a default name — e.g. when you + * pass `configPath: './wrangler.agent.jsonc'` to `@cloudflare/vite-plugin`, + * which the Sentry plugin cannot see. When set, only this file is read + * (no default-name probing), and a warning is emitted if it is missing or + * unparseable. + * + * @default undefined (probes `wrangler.json`, `wrangler.jsonc`, `wrangler.toml` at the Vite root) + */ + wranglerConfigPath?: string; /** * Experimental options that may change or be removed without notice. */ @@ -80,6 +91,8 @@ export function sentryCloudflareVitePlugin(options: SentryCloudflareVitePluginOp ...(options._experimental?.useDiagnosticsChannelInjection ? [sentryOrchestrionPlugin({ injectChannelSubscribers: true })] : []), - ...(options._experimental?.autoInstrumentation ? [sentryCloudflareAutoInstrumentPlugin()] : []), + ...(options._experimental?.autoInstrumentation + ? [sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: options.wranglerConfigPath })] + : []), ]; } diff --git a/packages/cloudflare/test/vite/autoInstrument.test.ts b/packages/cloudflare/test/vite/autoInstrument.test.ts index f28d8a9b508c..703120daaf7e 100644 --- a/packages/cloudflare/test/vite/autoInstrument.test.ts +++ b/packages/cloudflare/test/vite/autoInstrument.test.ts @@ -354,6 +354,66 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => { }); }); +describe('wranglerConfigPath option', () => { + it('reads a custom-named wrangler config (e.g. wrangler.agent.jsonc)', async () => { + const dir = writeTempDir({ 'wrangler.agent.jsonc': '{ "main": "src/agent.ts" }' }); + const plugin = sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: './wrangler.agent.jsonc' }); + plugin.configResolved({ root: dir }); + + const code = 'export default { fetch() { return new Response("ok"); } };'; + const result = await plugin.transform.call({ parse: (c: string) => parseJS(c) }, code, join(dir, 'src/agent.ts')); + + expect(result).toBeDefined(); + expect(result.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };', + 'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', + '', + ].join('\n'), + ); + }); + + it('prefers the explicit path over default-name configs', async () => { + const dir = writeTempDir({ + 'wrangler.toml': 'main = "src/default.ts"', + 'wrangler.agent.jsonc': '{ "main": "src/agent.ts" }', + }); + const plugin = sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: 'wrangler.agent.jsonc' }); + plugin.configResolved({ root: dir }); + + const code = 'export default { fetch() { return new Response("ok"); } };'; + const tx = (id: string) => plugin.transform.call({ parse: (c: string) => parseJS(c) }, code, id); + + // The probed default config's entry must not be treated as the worker entry… + expect(await tx(join(dir, 'src/default.ts'))).toBeUndefined(); + // …while the explicit config's entry is. + expect(await tx(join(dir, 'src/agent.ts'))).toBeDefined(); + }); + + it('warns with only the basename when the explicit path cannot be read', () => { + const dir = writeTempDir({}); + const warnings: string[] = []; + const plugin = sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: 'nested/dir/wrangler.agent.jsonc' }); + plugin.configResolved({ root: dir, logger: { warn: msg => warnings.push(msg) } }); + + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain('wrangler.agent.jsonc'); + // The full path may leak a location the user doesn't want in build logs. + expect(warnings[0]).not.toContain('nested/dir'); + }); + + it('hints at the option when no default-named config is found', () => { + const dir = writeTempDir({}); + const warnings: string[] = []; + const plugin = sentryCloudflareAutoInstrumentPlugin(); + plugin.configResolved({ root: dir, logger: { warn: msg => warnings.push(msg) } }); + + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain('`wranglerConfigPath`'); + }); +}); + // --------------------------------------------------------------------------- // instrument.server.* auto-detection (config from a conventional module) // ---------------------------------------------------------------------------