Skip to content

Commit ac9c49d

Browse files
authored
feat(cloudflare): Add wranglerConfigPath to Vite options (#22800)
In the Cloudflare Vite plugin there is the `configPath` option that lets you define the wrangler config path. When this is set we can't find the wrangler config, as it is set somewhere else. Unfortunately we can't retrieve the options directly from their plugin, so it has to be set in our plugin too in order to get the correct wrangler config. This adds the option `wranglerConfigPath` and would be used as the following: ```js export default defineConfig({ plugins: [ cloudflare({ configPath: "./wrangler.agent.jsonc" }), sentryCloudflareVitePlugin({ wranglerConfigPath: "./wrangler.agent.jsonc", _experimental: { autoInstrumentation: true, }, }), ], }); ```
1 parent c08b2fe commit ac9c49d

3 files changed

Lines changed: 87 additions & 4 deletions

File tree

packages/cloudflare/src/vite/autoInstrument.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { basename } from 'node:path';
12
import { collectAgentCandidates, detectAgentClasses, type ModuleResolver } from './agentClass';
23
import { buildOptionsImport, ENV_FALLBACK_OPTIONS_FN, resolveInstrumentFile } from './instrumentFile';
34
import { applyAutoInstrumentTransforms, type ClassWrapperKind, type ProgramBody } from './transform';
@@ -14,7 +15,7 @@ function normalizePath(path: string): string {
1415
// `.html`, … — sharing the entry's basename must never be treated as the entry.
1516
const JS_EXTENSION_REGEX = /\.[cm]?[jt]sx?$/;
1617

17-
export function sentryCloudflareAutoInstrumentPlugin() {
18+
export function sentryCloudflareAutoInstrumentPlugin(options: { wranglerConfigPath?: string } = {}) {
1819
let wranglerConfig: WranglerConfig | undefined;
1920
let entryFilePath: string | undefined;
2021

@@ -25,9 +26,18 @@ export function sentryCloudflareAutoInstrumentPlugin() {
2526
name: 'sentry-cloudflare-auto-instrument',
2627

2728
configResolved(config: { root: string; logger?: { warn(msg: string): void } }): void {
28-
const result = resolveWranglerConfig(config.root);
29+
const result = resolveWranglerConfig(config.root, options.wranglerConfigPath);
2930
if (!result) {
30-
config.logger?.warn('[sentry] No parseable wrangler config found — auto-instrumentation disabled.');
31+
// An explicit path that fails is a misconfiguration worth naming;
32+
// without one, hint at the option so custom-named configs (e.g. a
33+
// `configPath` handed to @cloudflare/vite-plugin) are discoverable.
34+
config.logger?.warn(
35+
options.wranglerConfigPath
36+
? `[sentry] Could not find or parse the wrangler config "${basename(options.wranglerConfigPath)}" ` +
37+
'(resolved against the Vite root) — auto-instrumentation disabled.'
38+
: '[sentry] No parseable wrangler config found — auto-instrumentation disabled. ' +
39+
'Set `wranglerConfigPath` if your config uses a custom name.',
40+
);
3141
return;
3242
}
3343

packages/cloudflare/src/vite/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ import { sentryCloudflareAutoInstrumentPlugin } from './autoInstrument';
1010
* Options for {@link sentryCloudflareVitePlugin}.
1111
*/
1212
export interface SentryCloudflareVitePluginOptions {
13+
/**
14+
* Path to the wrangler config, relative to the Vite root (or absolute).
15+
* Set this when your config doesn't use a default name — e.g. when you
16+
* pass `configPath: './wrangler.agent.jsonc'` to `@cloudflare/vite-plugin`,
17+
* which the Sentry plugin cannot see. When set, only this file is read
18+
* (no default-name probing), and a warning is emitted if it is missing or
19+
* unparseable.
20+
*
21+
* @default undefined (probes `wrangler.json`, `wrangler.jsonc`, `wrangler.toml` at the Vite root)
22+
*/
23+
wranglerConfigPath?: string;
1324
/**
1425
* Experimental options that may change or be removed without notice.
1526
*/
@@ -80,6 +91,8 @@ export function sentryCloudflareVitePlugin(options: SentryCloudflareVitePluginOp
8091
...(options._experimental?.useDiagnosticsChannelInjection
8192
? [sentryOrchestrionPlugin({ injectChannelSubscribers: true })]
8293
: []),
83-
...(options._experimental?.autoInstrumentation ? [sentryCloudflareAutoInstrumentPlugin()] : []),
94+
...(options._experimental?.autoInstrumentation
95+
? [sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: options.wranglerConfigPath })]
96+
: []),
8497
];
8598
}

packages/cloudflare/test/vite/autoInstrument.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,66 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => {
354354
});
355355
});
356356

357+
describe('wranglerConfigPath option', () => {
358+
it('reads a custom-named wrangler config (e.g. wrangler.agent.jsonc)', async () => {
359+
const dir = writeTempDir({ 'wrangler.agent.jsonc': '{ "main": "src/agent.ts" }' });
360+
const plugin = sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: './wrangler.agent.jsonc' });
361+
plugin.configResolved({ root: dir });
362+
363+
const code = 'export default { fetch() { return new Response("ok"); } };';
364+
const result = await plugin.transform.call({ parse: (c: string) => parseJS(c) }, code, join(dir, 'src/agent.ts'));
365+
366+
expect(result).toBeDefined();
367+
expect(result.code).toBe(
368+
[
369+
"import * as __SENTRY__ from '@sentry/cloudflare';",
370+
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };',
371+
'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
372+
'',
373+
].join('\n'),
374+
);
375+
});
376+
377+
it('prefers the explicit path over default-name configs', async () => {
378+
const dir = writeTempDir({
379+
'wrangler.toml': 'main = "src/default.ts"',
380+
'wrangler.agent.jsonc': '{ "main": "src/agent.ts" }',
381+
});
382+
const plugin = sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: 'wrangler.agent.jsonc' });
383+
plugin.configResolved({ root: dir });
384+
385+
const code = 'export default { fetch() { return new Response("ok"); } };';
386+
const tx = (id: string) => plugin.transform.call({ parse: (c: string) => parseJS(c) }, code, id);
387+
388+
// The probed default config's entry must not be treated as the worker entry…
389+
expect(await tx(join(dir, 'src/default.ts'))).toBeUndefined();
390+
// …while the explicit config's entry is.
391+
expect(await tx(join(dir, 'src/agent.ts'))).toBeDefined();
392+
});
393+
394+
it('warns with only the basename when the explicit path cannot be read', () => {
395+
const dir = writeTempDir({});
396+
const warnings: string[] = [];
397+
const plugin = sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: 'nested/dir/wrangler.agent.jsonc' });
398+
plugin.configResolved({ root: dir, logger: { warn: msg => warnings.push(msg) } });
399+
400+
expect(warnings).toHaveLength(1);
401+
expect(warnings[0]).toContain('wrangler.agent.jsonc');
402+
// The full path may leak a location the user doesn't want in build logs.
403+
expect(warnings[0]).not.toContain('nested/dir');
404+
});
405+
406+
it('hints at the option when no default-named config is found', () => {
407+
const dir = writeTempDir({});
408+
const warnings: string[] = [];
409+
const plugin = sentryCloudflareAutoInstrumentPlugin();
410+
plugin.configResolved({ root: dir, logger: { warn: msg => warnings.push(msg) } });
411+
412+
expect(warnings).toHaveLength(1);
413+
expect(warnings[0]).toContain('`wranglerConfigPath`');
414+
});
415+
});
416+
357417
// ---------------------------------------------------------------------------
358418
// instrument.server.* auto-detection (config from a conventional module)
359419
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)