Skip to content

Commit eebe164

Browse files
cursoragentclaude
andcommitted
fix(core): restore patchExpressModule legacy signature compatibility
Add an overload and runtime fallback for the deprecated single-options argument, reintroduce the deprecated ExpressIntegrationOptions.express field, and add a regression test for legacy callers. Co-Authored-By: gpt-5.3-codex-high <noreply@anthropic.com>
1 parent 29b7472 commit eebe164

File tree

3 files changed

+74
-6
lines changed

3 files changed

+74
-6
lines changed

packages/core/src/integrations/express/index.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ import { setSDKProcessingMetadata } from './set-sdk-processing-metadata';
6060
const getExpressExport = (express: ExpressModuleExport): ExpressExport =>
6161
hasDefaultProp(express) ? express.default : (express as ExpressExport);
6262

63+
type LegacyPatchExpressModuleOptions = ExpressIntegrationOptions & {
64+
express: ExpressModuleExport;
65+
};
66+
67+
let _didWarnLegacyPatchExpressModule = false;
68+
6369
/**
6470
* This is a portable instrumentatiton function that works in any environment
6571
* where Express can be loaded, without depending on OpenTelemetry.
@@ -72,7 +78,46 @@ const getExpressExport = (express: ExpressModuleExport): ExpressExport =>
7278
* Sentry.patchExpressModule(express, () => ({}));
7379
* ```
7480
*/
75-
export const patchExpressModule = (moduleExports: ExpressModuleExport, getOptions: () => ExpressIntegrationOptions) => {
81+
export function patchExpressModule(
82+
moduleExports: ExpressModuleExport,
83+
getOptions: () => ExpressIntegrationOptions,
84+
): ExpressExport;
85+
/**
86+
* @deprecated Pass the Express module export as the first argument and options getter as the second argument.
87+
*/
88+
export function patchExpressModule(options: LegacyPatchExpressModuleOptions): ExpressExport;
89+
export function patchExpressModule(
90+
moduleExportsOrOptions: ExpressModuleExport | LegacyPatchExpressModuleOptions,
91+
getOptions?: () => ExpressIntegrationOptions,
92+
): ExpressExport {
93+
const isLegacyOptionsObject =
94+
!getOptions &&
95+
typeof moduleExportsOrOptions === 'object' &&
96+
moduleExportsOrOptions !== null &&
97+
'express' in moduleExportsOrOptions;
98+
99+
let moduleExports: ExpressModuleExport;
100+
let getExpressOptions: () => ExpressIntegrationOptions;
101+
102+
if (isLegacyOptionsObject) {
103+
moduleExports = moduleExportsOrOptions.express;
104+
getExpressOptions = () => moduleExportsOrOptions;
105+
} else {
106+
moduleExports = moduleExportsOrOptions;
107+
if (!getOptions) {
108+
throw new TypeError('`patchExpressModule(moduleExports, getOptions)` requires a `getOptions` callback');
109+
}
110+
getExpressOptions = getOptions;
111+
}
112+
113+
if (isLegacyOptionsObject && !_didWarnLegacyPatchExpressModule) {
114+
_didWarnLegacyPatchExpressModule = true;
115+
DEBUG_BUILD &&
116+
debug.warn(
117+
'[Express] `patchExpressModule(options)` is deprecated. Use `patchExpressModule(moduleExports, getOptions)` instead.',
118+
);
119+
}
120+
76121
// pass in the require() or import() result of express
77122
const express = getExpressExport(moduleExports);
78123
const routerProto: ExpressRouterv4 | ExpressRouterv5 | undefined = isExpressWithRouterPrototype(express)
@@ -94,7 +139,7 @@ export const patchExpressModule = (moduleExports: ExpressModuleExport, getOption
94139
function routeTrace(this: ExpressRouter, ...args: Parameters<typeof originalRouteMethod>[]) {
95140
const route = originalRouteMethod.apply(this, args);
96141
const layer = this.stack[this.stack.length - 1] as ExpressLayer;
97-
patchLayer(getOptions, layer, getLayerPath(args));
142+
patchLayer(getExpressOptions, layer, getLayerPath(args));
98143
return route;
99144
},
100145
);
@@ -114,7 +159,7 @@ export const patchExpressModule = (moduleExports: ExpressModuleExport, getOption
114159
if (!layer) {
115160
return route;
116161
}
117-
patchLayer(getOptions, layer, getLayerPath(args));
162+
patchLayer(getExpressOptions, layer, getLayerPath(args));
118163
return route;
119164
},
120165
);
@@ -142,7 +187,7 @@ export const patchExpressModule = (moduleExports: ExpressModuleExport, getOption
142187
if (router) {
143188
const layer = router.stack[router.stack.length - 1];
144189
if (layer) {
145-
patchLayer(getOptions, layer, getLayerPath(args));
190+
patchLayer(getExpressOptions, layer, getLayerPath(args));
146191
}
147192
}
148193
return route;
@@ -153,7 +198,7 @@ export const patchExpressModule = (moduleExports: ExpressModuleExport, getOption
153198
}
154199

155200
return express;
156-
};
201+
}
157202

158203
/**
159204
* An Express-compatible error handler, used by setupExpressErrorHandler

packages/core/src/integrations/express/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ export type ExpressRouter = {
136136
export type IgnoreMatcher = string | RegExp | ((name: string) => boolean);
137137

138138
export type ExpressIntegrationOptions = {
139+
/**
140+
* @deprecated Pass the Express module export as the first argument to `patchExpressModule` instead.
141+
*/
142+
express?: ExpressModuleExport;
139143
/** Ignore specific based on their name */
140144
ignoreLayers?: IgnoreMatcher[];
141145
/** Ignore specific layers based on their type */

packages/core/test/lib/integrations/express/index.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,22 @@ vi.mock('../../../../src/debug-build', () => ({
5252
DEBUG_BUILD: true,
5353
}));
5454
const debugErrors: [string, Error][] = [];
55+
const debugWarnings: string[] = [];
5556
vi.mock('../../../../src/utils/debug-logger', () => ({
5657
debug: {
5758
error: (msg: string, er: Error) => {
5859
debugErrors.push([msg, er]);
5960
},
61+
warn: (msg: string) => {
62+
debugWarnings.push(msg);
63+
},
6064
},
6165
}));
6266

63-
beforeEach(() => (patchLayerCalls.length = 0));
67+
beforeEach(() => {
68+
patchLayerCalls.length = 0;
69+
debugWarnings.length = 0;
70+
});
6471
const patchLayerCalls: [getOptions: () => ExpressIntegrationOptions, layer: ExpressLayer, layerPath?: string][] = [];
6572

6673
vi.mock('../../../../src/integrations/express/patch-layer', () => ({
@@ -129,6 +136,18 @@ function getExpress5(): ExpressExportv5 & { spies: ExpressSpies } {
129136
}
130137

131138
describe('patchExpressModule', () => {
139+
it('supports deprecated options signature', () => {
140+
const expressv4 = getExpress4();
141+
const options = { express: expressv4, ignoreLayers: [/foo/] };
142+
patchExpressModule(options);
143+
expressv4.Router.use('a');
144+
145+
expect(patchLayerCalls[0]?.[0]()).toStrictEqual(options);
146+
expect(debugWarnings).toStrictEqual([
147+
'[Express] `patchExpressModule(options)` is deprecated. Use `patchExpressModule(moduleExports, getOptions)` instead.',
148+
]);
149+
});
150+
132151
it('throws trying to patch/unpatch the wrong thing', () => {
133152
expect(() => {
134153
patchExpressModule({} as unknown as ExpressModuleExport, () => ({}));

0 commit comments

Comments
 (0)