Skip to content

Commit 2ca2145

Browse files
committed
feat(tanstackstart-react): Add file exclude to configure middleware auto-instrumentation
1 parent 44afa86 commit 2ca2145

File tree

4 files changed

+190
-11
lines changed

4 files changed

+190
-11
lines changed

packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { stringMatchesSomePattern } from '@sentry/core';
12
import type { Plugin } from 'vite';
23

34
type AutoInstrumentMiddlewareOptions = {
45
enabled?: boolean;
56
debug?: boolean;
7+
exclude?: Array<string | RegExp>;
68
};
79

810
type WrapResult = {
@@ -64,13 +66,32 @@ export function wrapServerFnMiddleware(code: string, id: string, debug: boolean)
6466
return wrapMiddlewareArrays(code, id, debug, /(\.middleware\s*\()\s*\[([^\]]*)\]\s*\)/g);
6567
}
6668

69+
/**
70+
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
71+
*/
72+
function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
73+
if (!exclude || exclude.length === 0) {
74+
return false;
75+
}
76+
77+
if (stringMatchesSomePattern(id, exclude)) {
78+
if (debug) {
79+
// eslint-disable-next-line no-console
80+
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);
81+
}
82+
return true;
83+
}
84+
85+
return false;
86+
}
87+
6788
/**
6889
* A Vite plugin that automatically instruments TanStack Start middlewares:
6990
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
7091
* - `middleware` arrays in `createFileRoute()` route definitions
7192
*/
7293
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
73-
const { enabled = true, debug = false } = options;
94+
const { enabled = true, debug = false, exclude } = options;
7495

7596
return {
7697
name: 'sentry-tanstack-middleware-auto-instrument',
@@ -86,6 +107,11 @@ export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddle
86107
return null;
87108
}
88109

110+
// Skip if file matches exclude patterns
111+
if (shouldSkipFile(id, exclude, debug)) {
112+
return null;
113+
}
114+
89115
// Detect file types that should be instrumented
90116
const isStartFile = id.includes('start') && code.includes('createStart(');
91117
const isRouteFile = code.includes('createFileRoute(') && /middleware\s*:\s*\[/.test(code);

packages/tanstackstart-react/src/vite/sentryTanstackStart.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
88
*/
99
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
1010
/**
11-
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
11+
* Configure automatic middleware instrumentation.
1212
*
13-
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
14-
* instrumentation to capture performance data.
13+
* - Set to `false` to disable automatic middleware instrumentation entirely.
14+
* - Set to `true` (default) to enable for all middleware files.
15+
* - Set to an object with `exclude` to enable but exclude specific files.
1516
*
16-
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
17-
* using `wrapMiddlewaresWithSentry`.
17+
* The `exclude` option takes an array of strings or regular expressions matched
18+
* against the full file path. String patterns match as substrings.
1819
*
1920
* @default true
21+
*
22+
* @example
23+
* // Disable completely
24+
* sentryTanstackStart({ autoInstrumentMiddleware: false })
25+
*
26+
* @example
27+
* // Enable with exclusions
28+
* sentryTanstackStart({
29+
* autoInstrumentMiddleware: {
30+
* exclude: ['/routes/admin/', /\.test\.ts$/],
31+
* },
32+
* })
2033
*/
21-
autoInstrumentMiddleware?: boolean;
34+
autoInstrumentMiddleware?:
35+
| boolean
36+
| {
37+
exclude?: Array<string | RegExp>;
38+
};
2239
}
2340

2441
/**
@@ -54,8 +71,18 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
5471
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];
5572

5673
// middleware auto-instrumentation
57-
if (options.autoInstrumentMiddleware !== false) {
58-
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
74+
const autoInstrumentConfig = options.autoInstrumentMiddleware;
75+
const isDisabled = autoInstrumentConfig === false;
76+
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
77+
78+
if (!isDisabled) {
79+
plugins.push(
80+
makeAutoInstrumentMiddlewarePlugin({
81+
enabled: true,
82+
debug: options.debug,
83+
exclude: excludePatterns,
84+
}),
85+
);
5986
}
6087

6188
// source maps

packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,97 @@ describe('addSentryImport', () => {
445445
});
446446
});
447447

448+
describe('exclude option', () => {
449+
const createStartFile = `
450+
import { createStart } from '@tanstack/react-start';
451+
createStart(() => ({ requestMiddleware: [authMiddleware] }));
452+
`;
453+
454+
it('excludes files matching string pattern (substring match)', () => {
455+
const plugin = makeAutoInstrumentMiddlewarePlugin({
456+
exclude: ['/routes/admin/'],
457+
}) as PluginWithTransform;
458+
459+
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
460+
461+
expect(result).toBeNull();
462+
});
463+
464+
it('excludes files matching regex pattern', () => {
465+
const plugin = makeAutoInstrumentMiddlewarePlugin({
466+
exclude: [/\.test\.ts$/],
467+
}) as PluginWithTransform;
468+
469+
const result = plugin.transform(createStartFile, '/app/start.test.ts');
470+
471+
expect(result).toBeNull();
472+
});
473+
474+
it('excludes files matching any of multiple patterns', () => {
475+
const plugin = makeAutoInstrumentMiddlewarePlugin({
476+
exclude: ['/routes/admin/', /\.test\.ts$/],
477+
}) as PluginWithTransform;
478+
479+
// String pattern match
480+
const result1 = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
481+
expect(result1).toBeNull();
482+
483+
// Regex pattern match
484+
const result2 = plugin.transform(createStartFile, '/app/start.test.ts');
485+
expect(result2).toBeNull();
486+
});
487+
488+
it('instruments files not matching exclude patterns', () => {
489+
const plugin = makeAutoInstrumentMiddlewarePlugin({
490+
exclude: ['/routes/admin/', /\.test\.ts$/],
491+
}) as PluginWithTransform;
492+
493+
const result = plugin.transform(createStartFile, '/app/start.ts');
494+
495+
expect(result).not.toBeNull();
496+
expect(result!.code).toContain('wrapMiddlewaresWithSentry');
497+
});
498+
499+
it('instruments all files when exclude is empty array', () => {
500+
const plugin = makeAutoInstrumentMiddlewarePlugin({
501+
exclude: [],
502+
}) as PluginWithTransform;
503+
504+
const result = plugin.transform(createStartFile, '/app/start.ts');
505+
506+
expect(result).not.toBeNull();
507+
expect(result!.code).toContain('wrapMiddlewaresWithSentry');
508+
});
509+
510+
it('instruments all files when exclude is undefined', () => {
511+
const plugin = makeAutoInstrumentMiddlewarePlugin({
512+
exclude: undefined,
513+
}) as PluginWithTransform;
514+
515+
const result = plugin.transform(createStartFile, '/app/start.ts');
516+
517+
expect(result).not.toBeNull();
518+
expect(result!.code).toContain('wrapMiddlewaresWithSentry');
519+
});
520+
521+
it('logs debug message when file is excluded', () => {
522+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
523+
524+
const plugin = makeAutoInstrumentMiddlewarePlugin({
525+
debug: true,
526+
exclude: ['/routes/admin/'],
527+
}) as PluginWithTransform;
528+
529+
plugin.transform(createStartFile, '/app/routes/admin/start.ts');
530+
531+
expect(consoleLogSpy).toHaveBeenCalledWith(
532+
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
533+
);
534+
535+
consoleLogSpy.mockRestore();
536+
});
537+
});
538+
448539
describe('arrayToObjectShorthand', () => {
449540
it('converts single identifier', () => {
450541
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');

packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,48 @@ describe('sentryTanstackStart()', () => {
118118
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
119119
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });
120120

121-
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
121+
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
122+
enabled: true,
123+
debug: true,
124+
exclude: undefined,
125+
});
122126
});
123127

124128
it('passes debug: undefined when not specified', () => {
125129
sentryTanstackStart({ sourcemaps: { disable: true } });
126130

127-
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
131+
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
132+
enabled: true,
133+
debug: undefined,
134+
exclude: undefined,
135+
});
136+
});
137+
138+
it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
139+
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
140+
sentryTanstackStart({
141+
autoInstrumentMiddleware: { exclude: excludePatterns },
142+
sourcemaps: { disable: true },
143+
});
144+
145+
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
146+
enabled: true,
147+
debug: undefined,
148+
exclude: excludePatterns,
149+
});
150+
});
151+
152+
it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
153+
sentryTanstackStart({
154+
autoInstrumentMiddleware: true,
155+
sourcemaps: { disable: true },
156+
});
157+
158+
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
159+
enabled: true,
160+
debug: undefined,
161+
exclude: undefined,
162+
});
128163
});
129164
});
130165
});

0 commit comments

Comments
 (0)