Skip to content

Commit eff57fa

Browse files
authored
feat(node): Make getModuleFromFilename compatible with ESM (#10061)
This removes usage of `require.main.filename` to bring better ESM compatability. An extra bonus in that now `createGetModuleFromFilename` is ESM compatible, we can use it in the Anr worker when `appRootPath` is supplied (ie. Electron only for now). ### ⚠️ Fingerprinting This may change `module` fingerprinting for ESM since previously when `require.main.filename` was not available, `process.cwd()` was used.
1 parent 5623fd8 commit eff57fa

File tree

6 files changed

+68
-74
lines changed

6 files changed

+68
-74
lines changed

packages/node-experimental/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export {
5252
extractRequestData,
5353
// eslint-disable-next-line deprecation/deprecation
5454
deepReadDirSync,
55+
// eslint-disable-next-line deprecation/deprecation
5556
getModuleFromFilename,
57+
createGetModuleFromFilename,
5658
close,
5759
createTransport,
5860
// eslint-disable-next-line deprecation/deprecation

packages/node/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ export { defaultIntegrations, init, defaultStackParser, getSentryRelease } from
8686
export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils';
8787
// eslint-disable-next-line deprecation/deprecation
8888
export { deepReadDirSync } from './utils';
89-
export { getModuleFromFilename } from './module';
89+
90+
import { createGetModuleFromFilename } from './module';
91+
/**
92+
* @deprecated use `createGetModuleFromFilename` instead.
93+
*/
94+
export const getModuleFromFilename = createGetModuleFromFilename();
95+
export { createGetModuleFromFilename };
96+
9097
// eslint-disable-next-line deprecation/deprecation
9198
export { enableAnrDetection } from './integrations/anr/legacy';
9299

packages/node/src/integrations/anr/worker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
} from '@sentry/utils';
1616
import { Session as InspectorSession } from 'inspector';
1717
import { parentPort, workerData } from 'worker_threads';
18+
19+
import { createGetModuleFromFilename } from '../../module';
1820
import { makeNodeTransport } from '../../transports';
1921
import type { WorkerStartData } from './common';
2022

@@ -158,8 +160,9 @@ if (options.captureStackTrace) {
158160
// copy the frames
159161
const callFrames = [...event.params.callFrames];
160162

163+
const getModuleName = options.appRootPath ? createGetModuleFromFilename(options.appRootPath) : () => undefined;
161164
const stackFrames = callFrames.map(frame =>
162-
callFrameToStackFrame(frame, scripts.get(frame.location.scriptId), () => undefined),
165+
callFrameToStackFrame(frame, scripts.get(frame.location.scriptId), getModuleName),
163166
);
164167

165168
// Evaluate a script in the currently paused context

packages/node/src/module.ts

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,62 +8,51 @@ function normalizeWindowsPath(path: string): string {
88
.replace(/\\/g, '/'); // replace all `\` instances with `/`
99
}
1010

11-
// We cache this so we don't have to recompute it
12-
let basePath: string | undefined;
13-
14-
function getBasePath(): string {
15-
if (!basePath) {
16-
const baseDir =
17-
require && require.main && require.main.filename ? dirname(require.main.filename) : global.process.cwd();
18-
basePath = `${baseDir}/`;
19-
}
20-
21-
return basePath;
22-
}
23-
24-
/** Gets the module from a filename */
25-
export function getModuleFromFilename(
26-
filename: string | undefined,
27-
basePath: string = getBasePath(),
11+
/** Creates a function that gets the module name from a filename */
12+
export function createGetModuleFromFilename(
13+
basePath: string = dirname(process.argv[1]),
2814
isWindows: boolean = sep === '\\',
29-
): string | undefined {
30-
if (!filename) {
31-
return;
32-
}
33-
15+
): (filename: string | undefined) => string | undefined {
3416
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
35-
const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename;
3617

37-
// eslint-disable-next-line prefer-const
38-
let { dir, base: file, ext } = posix.parse(normalizedFilename);
18+
return (filename: string | undefined) => {
19+
if (!filename) {
20+
return;
21+
}
3922

40-
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
41-
file = file.slice(0, ext.length * -1);
42-
}
23+
const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename;
4324

44-
if (!dir) {
45-
// No dirname whatsoever
46-
dir = '.';
47-
}
25+
// eslint-disable-next-line prefer-const
26+
let { dir, base: file, ext } = posix.parse(normalizedFilename);
4827

49-
let n = dir.lastIndexOf('/node_modules');
50-
if (n > -1) {
51-
return `${dir.slice(n + 14).replace(/\//g, '.')}:${file}`;
52-
}
28+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
29+
file = file.slice(0, ext.length * -1);
30+
}
5331

54-
// Let's see if it's a part of the main module
55-
// To be a part of main module, it has to share the same base
56-
n = `${dir}/`.lastIndexOf(normalizedBase, 0);
57-
if (n === 0) {
58-
let moduleName = dir.slice(normalizedBase.length).replace(/\//g, '.');
32+
if (!dir) {
33+
// No dirname whatsoever
34+
dir = '.';
35+
}
5936

60-
if (moduleName) {
61-
moduleName += ':';
37+
let n = dir.lastIndexOf('/node_modules');
38+
if (n > -1) {
39+
return `${dir.slice(n + 14).replace(/\//g, '.')}:${file}`;
6240
}
63-
moduleName += file;
6441

65-
return moduleName;
66-
}
42+
// Let's see if it's a part of the main module
43+
// To be a part of main module, it has to share the same base
44+
n = `${dir}/`.lastIndexOf(normalizedBase, 0);
45+
if (n === 0) {
46+
let moduleName = dir.slice(normalizedBase.length).replace(/\//g, '.');
47+
48+
if (moduleName) {
49+
moduleName += ':';
50+
}
51+
moduleName += file;
52+
53+
return moduleName;
54+
}
6755

68-
return file;
56+
return file;
57+
};
6958
}

packages/node/src/sdk.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
Spotlight,
3535
Undici,
3636
} from './integrations';
37-
import { getModuleFromFilename } from './module';
37+
import { createGetModuleFromFilename } from './module';
3838
import { makeNodeTransport } from './transports';
3939
import type { NodeClientOptions, NodeOptions } from './types';
4040

@@ -240,7 +240,7 @@ export function getSentryRelease(fallback?: string): string | undefined {
240240
}
241241

242242
/** Node.js stack parser */
243-
export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(getModuleFromFilename));
243+
export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(createGetModuleFromFilename()));
244244

245245
/**
246246
* Enable automatic Session Tracking for the node process.

packages/node/test/module.test.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,33 @@
1-
import { getModuleFromFilename } from '../src/module';
1+
import { createGetModuleFromFilename } from '../src/module';
22

3-
describe('getModuleFromFilename', () => {
4-
test('Windows', () => {
5-
expect(
6-
getModuleFromFilename('C:\\Users\\Tim\\node_modules\\some-dep\\module.js', 'C:\\Users\\Tim\\', true),
7-
).toEqual('some-dep:module');
3+
const getModuleFromFilenameWindows = createGetModuleFromFilename('C:\\Users\\Tim\\', true);
4+
const getModuleFromFilenamePosix = createGetModuleFromFilename('/Users/Tim/');
85

9-
expect(getModuleFromFilename('C:\\Users\\Tim\\some\\more\\feature.js', 'C:\\Users\\Tim\\', true)).toEqual(
10-
'some.more:feature',
6+
describe('createGetModuleFromFilename', () => {
7+
test('Windows', () => {
8+
expect(getModuleFromFilenameWindows('C:\\Users\\Tim\\node_modules\\some-dep\\module.js')).toEqual(
9+
'some-dep:module',
1110
);
11+
expect(getModuleFromFilenameWindows('C:\\Users\\Tim\\some\\more\\feature.js')).toEqual('some.more:feature');
1212
});
1313

1414
test('POSIX', () => {
15-
expect(getModuleFromFilename('/Users/Tim/node_modules/some-dep/module.js', '/Users/Tim/')).toEqual(
16-
'some-dep:module',
17-
);
18-
19-
expect(getModuleFromFilename('/Users/Tim/some/more/feature.js', '/Users/Tim/')).toEqual('some.more:feature');
20-
expect(getModuleFromFilename('/Users/Tim/main.js', '/Users/Tim/')).toEqual('main');
15+
expect(getModuleFromFilenamePosix('/Users/Tim/node_modules/some-dep/module.js')).toEqual('some-dep:module');
16+
expect(getModuleFromFilenamePosix('/Users/Tim/some/more/feature.js')).toEqual('some.more:feature');
17+
expect(getModuleFromFilenamePosix('/Users/Tim/main.js')).toEqual('main');
2118
});
2219

2320
test('.mjs', () => {
24-
expect(getModuleFromFilename('/Users/Tim/node_modules/some-dep/module.mjs', '/Users/Tim/')).toEqual(
25-
'some-dep:module',
26-
);
21+
expect(getModuleFromFilenamePosix('/Users/Tim/node_modules/some-dep/module.mjs')).toEqual('some-dep:module');
2722
});
2823

2924
test('.cjs', () => {
30-
expect(getModuleFromFilename('/Users/Tim/node_modules/some-dep/module.cjs', '/Users/Tim/')).toEqual(
31-
'some-dep:module',
32-
);
25+
expect(getModuleFromFilenamePosix('/Users/Tim/node_modules/some-dep/module.cjs')).toEqual('some-dep:module');
3326
});
3427

3528
test('node internal', () => {
36-
expect(getModuleFromFilename('node.js', '/Users/Tim/')).toEqual('node');
37-
expect(getModuleFromFilename('node:internal/process/task_queues', '/Users/Tim/')).toEqual('task_queues');
38-
expect(getModuleFromFilename('node:internal/timers', '/Users/Tim/')).toEqual('timers');
29+
expect(getModuleFromFilenamePosix('node.js')).toEqual('node');
30+
expect(getModuleFromFilenamePosix('node:internal/process/task_queues')).toEqual('task_queues');
31+
expect(getModuleFromFilenamePosix('node:internal/timers')).toEqual('timers');
3932
});
4033
});

0 commit comments

Comments
 (0)