Skip to content

Commit 11dfd25

Browse files
authored
fixup! fix(cloudflare): keep default exports intact when auto-instrumenting
1 parent 82697a3 commit 11dfd25

3 files changed

Lines changed: 51 additions & 29 deletions

File tree

‎packages/cloudflare/src/vite/transform.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,22 +402,25 @@ function wrapDefaultExport(node: ExportDefaultNode, ctx: TransformContext, state
402402
? (decl as ClassDeclarationNode).id?.name
403403
: undefined;
404404

405+
// As with `export { handler as default }`, the export is set in two steps: bound as-is where the
406+
// original statement set it, then rebound at the end of the module to the wrapper, which sees
407+
// methods added in between. See `wrapDefaultSpecifier`.
405408
let wrapped = '__SENTRY_DEFAULT_EXPORT__';
406409
if (declaredName) {
407410
// `export default class Handler {}`: the declaration stays, so `Handler` stays bound in the module.
408411
state.ms.remove(node.start, decl.start);
412+
state.ms.appendLeft(node.end, `\nvar __SENTRY_DEFAULT_EXPORT__ = ${declaredName};`);
409413
wrapped = declaredName;
410414
} else if (isAnonymousFunctionOrClass(decl)) {
411415
// As a property value, the class or function gets the name `default`, as `export default` gives it.
412416
const hasSemicolon = source[node.end - 1] === ';';
413-
state.ms.overwrite(node.start, keywordsEnd, 'const __SENTRY_DEFAULT_EXPORT__ = { default:');
417+
state.ms.overwrite(node.start, keywordsEnd, 'var __SENTRY_DEFAULT_EXPORT__ = { default:');
414418
state.ms.appendLeft(hasSemicolon ? node.end - 1 : node.end, hasSemicolon ? ' }.default' : ' }.default;');
415419
} else {
416-
state.ms.overwrite(node.start, keywordsEnd, 'const __SENTRY_DEFAULT_EXPORT__ =');
420+
state.ms.overwrite(node.start, keywordsEnd, 'var __SENTRY_DEFAULT_EXPORT__ =');
417421
}
418-
419-
// Right after the original statement, so the default export is set where it was before.
420-
state.ms.appendLeft(node.end, `\nexport default __SENTRY__.withSentry(${state.optionsFn}, ${wrapped});`);
422+
state.ms.appendLeft(node.end, '\nexport { __SENTRY_DEFAULT_EXPORT__ as default };');
423+
state.ms.append(`\n__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(${state.optionsFn}, ${wrapped});`);
421424
state.needsImport = true;
422425
state.autoWrapped.add(DEFAULT_EXPORT);
423426
}

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => {
7676
expect(resolve).toHaveBeenCalledWith('@sentry/nextjs/cloudflare', join(dir, 'src/index.ts'));
7777
expect(result.code).toBe(
7878
"import * as __SENTRY__ from '@sentry/nextjs/cloudflare';\n" +
79-
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
80-
'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
79+
'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
80+
'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' +
81+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
8182
);
8283
});
8384

@@ -98,8 +99,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => {
9899

99100
expect(result.code).toBe(
100101
"import * as __SENTRY__ from '@sentry/cloudflare';\n" +
101-
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
102-
'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
102+
'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
103+
'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' +
104+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
103105
);
104106
});
105107

@@ -118,8 +120,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => {
118120

119121
expect(result.code).toBe(
120122
"import * as __SENTRY__ from '@sentry/cloudflare';\n" +
121-
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
122-
'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
123+
'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
124+
'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' +
125+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
123126
);
124127
});
125128

@@ -142,8 +145,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => {
142145

143146
expect(result.code).toBe(
144147
"import * as __SENTRY__ from '@sentry/cloudflare';\n" +
145-
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
146-
'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
148+
'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' +
149+
'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' +
150+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
147151
);
148152
});
149153

@@ -518,8 +522,9 @@ describe('wranglerConfigPath option', () => {
518522
expect(result.code).toBe(
519523
[
520524
"import * as __SENTRY__ from '@sentry/cloudflare';",
521-
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };',
522-
'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
525+
'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };',
526+
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
527+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
523528
].join('\n'),
524529
);
525530
});
@@ -595,8 +600,9 @@ describe('package specifier as wrangler `main`', () => {
595600
expect(result.code).toBe(
596601
[
597602
"import * as __SENTRY__ from '@sentry/cloudflare';",
598-
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };',
599-
'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
603+
'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };',
604+
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
605+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);',
600606
].join('\n'),
601607
);
602608
});

‎packages/cloudflare/test/vite/transform.test.ts‎

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('default export wrapping', () => {
5252
const result = transform(code, ctx)!;
5353
expect(result).toBeDefined();
5454
expect(result.code).toContain("import * as __SENTRY__ from '@sentry/cloudflare'");
55-
expect(result.code).toContain('const __SENTRY_DEFAULT_EXPORT__ = handler');
55+
expect(result.code).toContain('var __SENTRY_DEFAULT_EXPORT__ = handler');
5656
expect(result.code).toContain('__SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__)');
5757
expect(result.code).not.toContain('export default handler');
5858
expect(result.map).toBeDefined();
@@ -62,7 +62,7 @@ describe('default export wrapping', () => {
6262
const code = 'export default { fetch() { return new Response("ok"); } };';
6363
const result = transform(code, ctx)!;
6464
expect(result).toBeDefined();
65-
expect(result.code).toContain('const __SENTRY_DEFAULT_EXPORT__ =');
65+
expect(result.code).toContain('var __SENTRY_DEFAULT_EXPORT__ =');
6666
expect(result.code).toContain('__SENTRY__.withSentry(');
6767
});
6868

@@ -287,8 +287,8 @@ describe('default export wrapping', () => {
287287
});
288288

289289
/**
290-
* Writes `files` to a temp dir, with `a.mjs` transformed and `withSentry` stubbed to return the
291-
* handler as-is, and returns what `node <entry>` prints.
290+
* Writes `files` to a temp dir, with `a.mjs` transformed and `withSentry` stubbed to mark each
291+
* method it finds as instrumented, and returns what `node <entry>` prints.
292292
*/
293293
function runWithEntry(files: Record<string, string>, entry: string, transformEntry = true): string {
294294
const dir = mkdtempSync(join(tmpdir(), 'sentry-cf-default-'));
@@ -298,7 +298,7 @@ describe('default export wrapping', () => {
298298
name === 'a.mjs' && transformEntry
299299
? transform(code, ctx)!.code.replace(
300300
"import * as __SENTRY__ from '@sentry/cloudflare';",
301-
'const __SENTRY__ = { withSentry: (_options, handler) => handler };',
301+
'const __SENTRY__ = { withSentry: (_options, handler) => { for (const key of Object.keys(handler)) handler[key].instrumented = true; return handler; } };',
302302
)
303303
: code;
304304
writeFileSync(join(dir, name), out);
@@ -328,8 +328,9 @@ describe('default export wrapping', () => {
328328
[
329329
"import * as __SENTRY__ from '@sentry/cloudflare';",
330330
'const handler = { fetch() {} };',
331-
'const __SENTRY_DEFAULT_EXPORT__ = (handler);',
332-
'export default __SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__);',
331+
'var __SENTRY_DEFAULT_EXPORT__ = (handler);',
332+
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
333+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__);',
333334
].join('\n'),
334335
);
335336
});
@@ -341,12 +342,23 @@ describe('default export wrapping', () => {
341342
[
342343
"import * as __SENTRY__ from '@sentry/cloudflare';",
343344
'class Handler { fetch() {} }',
344-
'export default __SENTRY__.withSentry((env) => ({}), Handler);',
345+
'var __SENTRY_DEFAULT_EXPORT__ = Handler;',
346+
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
345347
'Handler.ready = true;',
348+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), Handler);',
346349
].join('\n'),
347350
);
348351
});
349352

353+
it('wraps a method added to the default export after `export default`', () => {
354+
const files = {
355+
'a.mjs': ['const handler = {};', 'export default handler;', 'handler.fetch = function () {};'].join('\n'),
356+
'main.mjs': "import handler from './a.mjs';\nconsole.log(String(handler.fetch.instrumented));",
357+
};
358+
359+
expect(runWithEntry(files, 'main.mjs')).toBe('true');
360+
});
361+
350362
it('sets the default export where the original statement set it', () => {
351363
// `b` reads the default export of `a` while `a` is still evaluating, after its `export default`.
352364
const files = {
@@ -1008,8 +1020,9 @@ describe('agent class wrapping', () => {
10081020
'const __SENTRY_WRAPPED_MyDO__ = __SENTRY__.instrumentDurableObjectWithSentry((env) => ({ dsn: env.SENTRY_DSN }), MyDO);',
10091021
'export { __SENTRY_WRAPPED_MyDO__ as MyDO };',
10101022
'',
1011-
'const __SENTRY_DEFAULT_EXPORT__ = { fetch() {} };',
1012-
'export default __SENTRY__.withSentry((env) => ({ dsn: env.SENTRY_DSN }), __SENTRY_DEFAULT_EXPORT__);',
1023+
'var __SENTRY_DEFAULT_EXPORT__ = { fetch() {} };',
1024+
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
1025+
'__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({ dsn: env.SENTRY_DSN }), __SENTRY_DEFAULT_EXPORT__);',
10131026
].join('\n'),
10141027
);
10151028
});
@@ -1119,8 +1132,8 @@ describe('combined transforms', () => {
11191132
expect(result.code).toContain('export { __SENTRY_WRAPPED_MyDO__ as MyDO };');
11201133

11211134
// Default export wrapped
1122-
expect(result.code).toContain('const __SENTRY_DEFAULT_EXPORT__ =');
1123-
expect(result.code).toContain('export default __SENTRY__.withSentry(');
1135+
expect(result.code).toContain('var __SENTRY_DEFAULT_EXPORT__ =');
1136+
expect(result.code).toContain('__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(');
11241137

11251138
// Single import
11261139
const importCount = (result.code.match(/import \* as __SENTRY__/g) ?? []).length;

0 commit comments

Comments
 (0)