Skip to content

Commit fead1dc

Browse files
nicohrubecclaude
andcommitted
fix(nextjs): Serialize RegExp filePath for Turbopack orchestrion loader
Turbopack requires loader options to be JSON-serializable, so a RegExp `module.filePath` (used by the firestore config) was silently dropped and no firestore channels were injected under Turbopack — while webpack, which passes the config in-process, worked. Encode via `serializeInstrumentations` at the Turbopack boundary; the loader revives the RegExp on the other side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ffea8e1 commit fead1dc

3 files changed

Lines changed: 53 additions & 3 deletions

File tree

packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { debug } from '@sentry/core';
22
import * as path from 'path';
3-
import { getOrchestrionLoaderPath, getSentryInstrumentations } from '@sentry/server-utils/orchestrion/webpack';
3+
import {
4+
getOrchestrionLoaderPath,
5+
getSentryInstrumentations,
6+
serializeInstrumentations,
7+
} from '@sentry/server-utils/orchestrion/webpack';
48
import type { VercelCronsConfig } from '../../common/types';
59
import type { RouteManifest } from '../manifest/types';
610
import type {
@@ -141,8 +145,10 @@ function maybeAddOrchestrionRule(
141145
loaders: [
142146
{
143147
loader: getOrchestrionLoaderPath(),
144-
// `instrumentations` is JSON-serializable
145-
options: { instrumentations: getSentryInstrumentations() as unknown as JSONValue[] },
148+
// Turbopack JSON-serializes loader options, so a RegExp `filePath` must be encoded first.
149+
options: {
150+
instrumentations: serializeInstrumentations(getSentryInstrumentations()) as unknown as JSONValue[],
151+
},
146152
},
147153
],
148154
},

packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,47 @@ describe('componentAnnotation with turbopackReactComponentAnnotation', () => {
13071307
});
13081308
});
13091309

1310+
describe('orchestrion diagnostics-channel injection', () => {
1311+
function getOrchestrionOptions(result: ReturnType<typeof constructTurbopackConfig>): {
1312+
instrumentations: Array<{ module: { name: string; filePath: unknown } }>;
1313+
} {
1314+
const rule = result.rules!['*.{js,mjs,cjs}'] as {
1315+
loaders: Array<{ options: { instrumentations: Array<{ module: { name: string; filePath: unknown } }> } }>;
1316+
};
1317+
return rule.loaders[0]!.options;
1318+
}
1319+
1320+
it('serializes a RegExp filePath so it survives Turbopack JSON loader options', () => {
1321+
const result = constructTurbopackConfig({
1322+
userNextConfig: {},
1323+
userSentryOptions: { _experimental: { useDiagnosticsChannelInjection: true } },
1324+
nextJsVersion: '16.0.0',
1325+
});
1326+
1327+
const firestore = getOrchestrionOptions(result).instrumentations.find(i => i.module.name === '@firebase/firestore');
1328+
1329+
expect(firestore).toBeDefined();
1330+
expect(firestore!.module.filePath).toEqual({
1331+
type: 'RegExp',
1332+
source: expect.any(String),
1333+
flags: expect.any(String),
1334+
});
1335+
expect(firestore!.module.filePath).not.toBeInstanceOf(RegExp);
1336+
// A raw RegExp would `JSON.stringify` to `{}`, dropping the match entirely.
1337+
expect(JSON.parse(JSON.stringify(firestore!.module.filePath))).not.toEqual({});
1338+
});
1339+
1340+
it('does not add the orchestrion rule when injection is not opted in', () => {
1341+
const result = constructTurbopackConfig({
1342+
userNextConfig: {},
1343+
userSentryOptions: {},
1344+
nextJsVersion: '16.0.0',
1345+
});
1346+
1347+
expect(result.rules!['*.{js,mjs,cjs}']).toBeUndefined();
1348+
});
1349+
});
1350+
13101351
describe('safelyAddTurbopackRule', () => {
13111352
const mockRule = {
13121353
loaders: [

packages/server-utils/src/orchestrion/bundler/webpack.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type { InstrumentationConfig } from '..';
88
import { instrumentedModuleNames, SENTRY_INSTRUMENTATIONS } from '../config';
99
import codeTransformerWebpack from '@apm-js-collab/code-transformer-bundler-plugins/webpack';
1010
import type { PluginOptions } from './options';
11+
12+
export { serializeInstrumentations } from '@apm-js-collab/code-transformer-bundler-plugins/core';
13+
export type { SerializableInstrumentationConfig } from '@apm-js-collab/code-transformer-bundler-plugins/core';
1114
import { externalEntryMatchesModule, externalizedModulesWarning, orchestrionTransformOptions } from './options';
1215

1316
// Both branches use `createRequire` (never alias the CJS `require`) so bundlers consuming this

0 commit comments

Comments
 (0)