Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/server-utils/src/orchestrion/runtime/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ export function registerDiagnosticsChannelInjection(_options?: RegisterDiagnosti
// Our own bundled copy of the tracing-hooks async hooks (see
// `src/orchestrion/runtime/hook.mjs`) — the dependency itself is bundled into this package's
// build and no longer resolvable as a bare specifier at runtime.
// Deployments can omit the package marker that tells Node these bundled hook files are ESM.
const esmDirUrl = new URL('../../../esm/', parentURL).href;
mod.register(
`data:text/javascript,${encodeURIComponent(
`const d=${JSON.stringify(esmDirUrl)};export async function load(u,c,n){return n(u,u.startsWith(d)?{...c,format:'module'}:c)}`,
)}`,
);

mod.register('@sentry/server-utils/orchestrion/hook', {
parentURL,
data: { instrumentations: SENTRY_INSTRUMENTATIONS, diagnosticsPort },
Expand Down
45 changes: 45 additions & 0 deletions packages/server-utils/test/orchestrion/esmMarker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { spawnSync } from 'node:child_process';
import { cpSync, mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { afterAll, describe, expect, it } from 'vitest';

/**
* Deployments can drop `build/esm/package.json` (the only `"type": "module"` marker for the hook),
* and Vercel runs Node with syntax detection disabled, so the loader thread then parses the hook as
* CommonJS. Runs against the built files, so it needs the package built.
*
* The copy sits inside this package so `@sentry/*` still resolves from its `node_modules`.
*/
const copyDir = mkdtempSync(join(__dirname, '.esm-marker-'));
cpSync(join(__dirname, '../..', 'build'), join(copyDir, 'build'), { recursive: true });
cpSync(join(__dirname, '../..', 'package.json'), join(copyDir, 'package.json'));
rmSync(join(copyDir, 'build', 'esm', 'package.json'));

afterAll(() => {
rmSync(copyDir, { recursive: true, force: true });
});

describe('without the build/esm package.json marker', () => {
it('registers the ESM loader hook', { timeout: 30_000 }, () => {
// Removing `registerHooks` forces the `Module.register()` path, the one that loads the hook on
// Node < 24.13, whatever Node version runs the test.
const script = `
require('node:module').registerHooks = undefined;
require('@sentry/core').debug.warn = (...args) => console.error(...args);
const { registerDiagnosticsChannelInjection } = require(${JSON.stringify(join(copyDir, 'build', 'cjs', 'orchestrion', 'runtime', 'register.js'))});
registerDiagnosticsChannelInjection();
import('data:text/javascript,export default 1').then(() => {
const marker = globalThis.__SENTRY__?.__SENTRY_ORCHESTRION__ ?? globalThis.__SENTRY_ORCHESTRION__;
process.stdout.write(JSON.stringify(marker));
});
`;

const { stdout, stderr, status } = spawnSync(process.execPath, ['--no-experimental-detect-module', '-e', script], {
encoding: 'utf8',
});

expect(status, stderr).toBe(0);

Check failure on line 41 in packages/server-utils/test/orchestrion/esmMarker.test.ts

View workflow job for this annotation

GitHub Actions / Node (18) Unit Tests

test/orchestrion/esmMarker.test.ts > without the build/esm package.json marker > registers the ESM loader hook

AssertionError: /opt/hostedtoolcache/node/18.20.8/x64/bin/node: bad option: --no-experimental-detect-module : expected 9 to be +0 // Object.is equality - Expected + Received - 0 + 9 ❯ test/orchestrion/esmMarker.test.ts:41:28
expect(stderr).not.toContain('Failed to register diagnostics-channel injection hooks');
expect(JSON.parse(stdout)).toEqual({ runtime: [] });
});
});
Loading