Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
transport: loggingTransport,
});

// `test.ts` writes these packages in the folders where the isolated layouts of pnpm and Bun keep them
require('pnpm-isolated-package');
require('@pnpm-isolated/scoped-package');
require('bun-isolated-package');
require('@bun-isolated/scoped-package');
// Another version of `pnpm-isolated-package`, loaded last as a dependency of the app would load it
require('./node_modules/.pnpm/pnpm-isolated-package@1.0.0/node_modules/pnpm-isolated-package');

Sentry.captureException(new Error('error_1'));
43 changes: 42 additions & 1 deletion dev-packages/node-integration-tests/suites/modules/test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SDK_VERSION } from '@sentry/core';
import { join } from 'path';
import { mkdirSync, rmSync, symlinkSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';
import { afterAll, describe, expect, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../utils/runner';

Expand Down Expand Up @@ -27,6 +28,46 @@ describe('modulesIntegration', () => {
await runner.completed();
});

test('CJS with the isolated layouts of pnpm and Bun', async () => {
const nodeModulesDir = join(__dirname, 'node_modules');
for (const [storeDir, name, version, linked] of [
['.pnpm', 'pnpm-isolated-package', '1.2.3', true],
// Only a dependency of the app uses this version, so it is not linked into `node_modules`
['.pnpm', 'pnpm-isolated-package', '1.0.0', false],
['.pnpm', '@pnpm-isolated/scoped-package', '2.3.4', true],
['.bun', 'bun-isolated-package', '4.5.6', true],
['.bun', '@bun-isolated/scoped-package', '5.6.7', true],
] as const) {
const packageDir = join(nodeModulesDir, storeDir, `${name.replace('/', '+')}@${version}`, 'node_modules', name);
mkdirSync(packageDir, { recursive: true });
writeFileSync(join(packageDir, 'package.json'), JSON.stringify({ name, version }));
writeFileSync(join(packageDir, 'index.js'), 'module.exports = {};');

if (linked) {
mkdirSync(dirname(join(nodeModulesDir, name)), { recursive: true });
symlinkSync(packageDir, join(nodeModulesDir, name), 'junction');
}
}

try {
await createRunner(__dirname, 'scenario-isolated.js')
.expect({
event: {
modules: {
'pnpm-isolated-package': '1.2.3',
'@pnpm-isolated/scoped-package': '2.3.4',
'bun-isolated-package': '4.5.6',
'@bun-isolated/scoped-package': '5.6.7',
},
},
})
.start()
.completed();
} finally {
rmSync(nodeModulesDir, { recursive: true, force: true });
}
});

test('ESM', async () => {
const runner = createRunner(__dirname, 'server.mjs')
.withInstrument(join(__dirname, 'instrument.mjs'))
Expand Down
42 changes: 38 additions & 4 deletions packages/node/src/integrations/modules.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { existsSync, readFileSync, realpathSync } from 'node:fs';
import { basename, dirname, join } from 'node:path';
import { GLOBAL_OBJ, type IntegrationFn } from '@sentry/core';

type ModuleInfo = Record<string, string>;
Expand Down Expand Up @@ -73,6 +73,34 @@ function collectModules(): ModuleInfo {
};
}

/**
* Whether `dir` is a node_modules folder of the main module, a folder
* `<that node_modules>/<.pnpm or .bun>/<package id>/node_modules`, where the isolated layouts of pnpm and Bun keep
* each package, or a scope folder (`@scope`) in one of these folders.
*/
function isMainModulesDir(dir: string, mainPaths: string[]): boolean {
Comment thread
isaacs marked this conversation as resolved.
if (basename(dir).startsWith('@')) {
return isMainModulesDir(dirname(dir), mainPaths);
}

if (mainPaths.includes(dir)) {
return true;
}

const storeDir = dirname(dirname(dir));
Comment thread
isaacs marked this conversation as resolved.
return (
basename(dir) === 'node_modules' &&
['.pnpm', '.bun'].includes(basename(storeDir)) &&
mainPaths.includes(dirname(storeDir))
);
}

/** Whether `require(name)` in the main module loads the package in `packageDir`. */
function isResolvedFromMain(name: string, packageDir: string, mainPaths: string[]): boolean {
const resolvedDir = mainPaths.map(dir => join(dir, name)).find(dir => existsSync(dir));
return !!resolvedDir && realpathSync(resolvedDir) === realpathSync(packageDir);
}

/** Extract information about package.json modules from require.cache */
function collectRequireModules(): ModuleInfo {
const mainPaths = require.main?.paths || [];
Expand All @@ -81,6 +109,7 @@ function collectRequireModules(): ModuleInfo {
// We start with the modules from package.json (if possible)
// These may be overwritten by more specific versions from the require.cache
const infos: ModuleInfo = {};
const packageDirs: Record<string, string> = {};
const seen = new Set<string>();

paths.forEach(path => {
Expand All @@ -94,7 +123,7 @@ function collectRequireModules(): ModuleInfo {
if (!dir || orig === dir || seen.has(orig)) {
return undefined;
}
if (mainPaths.indexOf(dir) < 0) {
if (!isMainModulesDir(dir, mainPaths)) {
return updir();
}

Expand All @@ -110,7 +139,12 @@ function collectRequireModules(): ModuleInfo {
name: string;
version: string;
};
infos[info.name] = info.version;
// The isolated layouts of pnpm and Bun keep every version of a package. Keep the one that the main module loads.
const previousDir = packageDirs[info.name];
if (!previousDir || !isResolvedFromMain(info.name, previousDir, mainPaths)) {
infos[info.name] = info.version;
packageDirs[info.name] = orig;
}
} catch {
// no-empty
}
Expand Down
Loading