Skip to content

Commit 0ab49df

Browse files
committed
fixup! feat(server-utils): Register orchestrion channel integrations via the global marker
1 parent 87ad6a4 commit 0ab49df

4 files changed

Lines changed: 110 additions & 9 deletions

File tree

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

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,26 @@ export function sentryOrchestrionPlugin(options: SentryOrchestrionPluginOptions
102102
const codeTransformerArray: UnknownPlugin[] = Array.isArray(codeTransformerPlugins)
103103
? codeTransformerPlugins
104104
: [codeTransformerPlugins];
105+
const serverCodeTransformerArray = codeTransformerArray.map(plugin => serverEnvironmentOnly(plugin));
105106
return [
106107
bundlerMarkerPlugin(),
107108
...(options.registerIntegrations ? [registerIntegrationsPlugin()] : []),
108-
...codeTransformerArray,
109+
...serverCodeTransformerArray,
109110
];
110111
}
111112

113+
/** Keeps environment-aware Vite builds from transforming client bundles. */
114+
function serverEnvironmentOnly(plugin: UnknownPlugin): UnknownPlugin {
115+
const applyToEnvironment = plugin.applyToEnvironment;
116+
return {
117+
...plugin,
118+
applyToEnvironment(this: unknown, environment: { config?: { consumer?: string } }): unknown {
119+
if (environment.config?.consumer === 'client') return false;
120+
return applyToEnvironment?.call(this, environment) ?? true;
121+
},
122+
};
123+
}
124+
112125
/**
113126
* Builds the `injectDiagnostics` callback for the code transformer: it records
114127
* the packages the transformer actually transformed — and those whose transform
@@ -193,9 +206,10 @@ function registerIntegrationsPlugin(): UnknownPlugin {
193206

194207
// `serve` (vite dev) vs `build`; drives entry detection in `transform`.
195208
let command = 'build';
196-
// Dev only: environments the registration import has already been injected
197-
// into, so each is injected exactly once (into its first source module).
198-
const injectedServeEnvironments = new Set<string>();
209+
// Dev only: records which source module receives registration in each
210+
// environment. Re-transforming that module must inject again because HMR can
211+
// start a fresh isolate from the newly transformed output.
212+
const injectedServeModules = new Map<string, string>();
199213

200214
function injectRegisterImport(code: string): { code: string; map: unknown } | null {
201215
if (code.includes(REGISTER_MODULE_ID)) return null;
@@ -252,8 +266,9 @@ function registerIntegrationsPlugin(): UnknownPlugin {
252266
// pre-bundled deps, node_modules source, and virtual modules so the import
253267
// lands in the user's entry, not an incidental early module.
254268
const environment = this?.environment?.name ?? '';
255-
if (injectedServeEnvironments.has(environment)) return null;
256269
const cleanId = id.split('?')[0] ?? id;
270+
const injectedModule = injectedServeModules.get(environment);
271+
if (injectedModule && injectedModule !== cleanId) return null;
257272
if (
258273
id.startsWith('\0') ||
259274
cleanId.includes('/node_modules/') ||
@@ -263,7 +278,7 @@ function registerIntegrationsPlugin(): UnknownPlugin {
263278
return null;
264279
}
265280
const result = injectRegisterImport(code);
266-
if (result) injectedServeEnvironments.add(environment);
281+
if (result) injectedServeModules.set(environment, cleanId);
267282
return result;
268283
},
269284
};
@@ -276,9 +291,18 @@ function bundlerMarkerPlugin(): UnknownPlugin {
276291
'',
277292
].join('\n');
278293

294+
let command = 'build';
295+
const injectedServeModules = new Map<string, string>();
296+
279297
return {
280298
name: 'sentry-orchestrion-marker',
281299
enforce: 'pre' as const,
300+
applyToEnvironment(environment: { config?: { consumer?: string } }): boolean {
301+
return environment.config?.consumer !== 'client';
302+
},
303+
configResolved(config: { command: string }): void {
304+
command = config.command;
305+
},
282306
config(): { ssr: { noExternal: string[] } } {
283307
// Force-bundle every instrumented package so the code transform actually
284308
// sees its source. Vite externalizes dependencies in SSR builds by
@@ -321,8 +345,35 @@ function bundlerMarkerPlugin(): UnknownPlugin {
321345
},
322346
};
323347
},
324-
renderChunk(code: string, chunk: { isEntry: boolean }): { code: string; map: unknown } | null {
325-
if (!chunk.isEntry) return null;
348+
transform(
349+
this: { environment?: { name?: string; config?: { consumer?: string } } } | undefined,
350+
code: string,
351+
id: string,
352+
): { code: string; map: unknown } | null {
353+
if (command !== 'serve' || this?.environment?.config?.consumer === 'client') return null;
354+
const cleanId = id.split('?')[0] ?? id;
355+
const environment = this?.environment?.name ?? '';
356+
const injectedModule = injectedServeModules.get(environment);
357+
if (injectedModule && injectedModule !== cleanId) return null;
358+
if (
359+
id.startsWith('\0') ||
360+
cleanId.includes('/node_modules/') ||
361+
cleanId.includes('/.vite/') ||
362+
!/\.[cm]?[jt]sx?$/.test(cleanId)
363+
) {
364+
return null;
365+
}
366+
injectedServeModules.set(environment, cleanId);
367+
const ms = new MagicString(code);
368+
ms.prepend(banner);
369+
return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
370+
},
371+
renderChunk(
372+
this: { environment?: { config?: { consumer?: string } } } | undefined,
373+
code: string,
374+
chunk: { isEntry: boolean },
375+
): { code: string; map: unknown } | null {
376+
if (!chunk.isEntry || this?.environment?.config?.consumer === 'client') return null;
326377
// Prepend via magic-string so the entry chunk's sourcemap stays aligned —
327378
// returning `map: null` here would shift every mapping by the banner's
328379
// line count and misattribute server stack traces.

packages/server-utils/src/orchestrion/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ export const channelIntegrations = {
9393
*
9494
* Names must match the `module.name` values in `SENTRY_INSTRUMENTATIONS` (the
9595
* `config/` files) — e.g. `postgresIntegration` covers both `pg` and `pg-pool`.
96+
* Integrations for channels published natively by a library cannot use this
97+
* transformed-module filter and need an explicit always-active representation
98+
* before they can be added here.
9699
*/
97100
const CHANNEL_INTEGRATION_MODULES: Record<keyof typeof channelIntegrations, string[]> = {
98101
postgresIntegration: ['pg', 'pg-pool'],
@@ -106,7 +109,7 @@ const CHANNEL_INTEGRATION_MODULES: Record<keyof typeof channelIntegrations, stri
106109
vercelAiIntegration: ['ai'],
107110
amqplibIntegration: ['amqplib'],
108111
hapiIntegration: ['@hapi/hapi'],
109-
expressIntegration: ['express'],
112+
expressIntegration: ['express', 'router'],
110113
graphqlIntegration: ['graphql'],
111114
kafkajsIntegration: ['kafkajs'],
112115
};

packages/server-utils/test/orchestrion/vite-plugin.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,39 @@ describe('sentryOrchestrionPlugin', () => {
3838
expect(marker.renderChunk('console.log("chunk");', { isEntry: false })).toBeNull();
3939
});
4040

41+
it('does not prepend the bundler marker to client chunks', () => {
42+
const marker = getMarkerPlugin();
43+
const clientContext = { environment: { config: { consumer: 'client' } } };
44+
45+
expect(marker.renderChunk.call(clientContext, 'console.log("app");', { isEntry: true })).toBeNull();
46+
});
47+
48+
it('applies the marker and code transformer only to server environments', () => {
49+
const plugins = sentryOrchestrionPlugin({ registerIntegrations: true });
50+
const environmentPlugins = plugins.filter(
51+
plugin => plugin.name === 'sentry-orchestrion-marker' || plugin.name === 'code-transformer',
52+
);
53+
54+
expect(environmentPlugins.every(plugin => plugin.applyToEnvironment({ config: { consumer: 'server' } }))).toBe(
55+
true,
56+
);
57+
expect(environmentPlugins.every(plugin => !plugin.applyToEnvironment({ config: { consumer: 'client' } }))).toBe(
58+
true,
59+
);
60+
});
61+
62+
it('prepends the bundler marker during dev and reinjects it after entry HMR', () => {
63+
const marker = getMarkerPlugin();
64+
marker.configResolved({ command: 'serve' });
65+
const context = { environment: { name: 'worker', config: { consumer: 'server' } } };
66+
67+
const initial = marker.transform.call(context, 'export default {};\n', '/app/src/index.ts');
68+
const updated = marker.transform.call(context, 'export default { updated: true };\n', '/app/src/index.ts?t=123');
69+
70+
expect(initial?.code).toContain('globalThis.__SENTRY_ORCHESTRION__.bundler = true;');
71+
expect(updated?.code).toContain('globalThis.__SENTRY_ORCHESTRION__.bundler = true;');
72+
});
73+
4174
describe('configEnvironment (dev dep-optimizer instrumentation)', () => {
4275
it('adds the esbuild transformer for server environments on classic Vite', () => {
4376
// No `meta.rolldownVersion` → esbuild-based optimizer.

packages/server-utils/test/orchestrion/vite-register-integrations.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,20 @@ describe('sentryOrchestrionPlugin — registerIntegrations', () => {
156156
expect(second).toBeNull();
157157
});
158158

159+
it('reinjects when HMR transforms the registered entry again', () => {
160+
const plugin = makeServePlugin();
161+
runTransform(plugin, 'export default {};\n', serveCtx({ name: 'worker' }), '/app/src/index.ts');
162+
163+
const updated = runTransform(
164+
plugin,
165+
'export default { updated: true };\n',
166+
serveCtx({ name: 'worker' }),
167+
'/app/src/index.ts?t=123',
168+
);
169+
170+
expect(updated?.code).toContain(REGISTER_MODULE_ID);
171+
});
172+
159173
it('injects once per distinct environment', () => {
160174
const plugin = makeServePlugin();
161175
const worker = runTransform(plugin, 'export default {};\n', serveCtx({ name: 'worker' }), '/app/src/index.ts');

0 commit comments

Comments
 (0)