diff --git a/dev-packages/node-integration-tests/suites/modules/scenario-isolated.js b/dev-packages/node-integration-tests/suites/modules/scenario-isolated.js new file mode 100644 index 000000000000..ec543d39824e --- /dev/null +++ b/dev-packages/node-integration-tests/suites/modules/scenario-isolated.js @@ -0,0 +1,14 @@ +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('./node_modules/.pnpm/pnpm-isolated-package@1.2.3/node_modules/pnpm-isolated-package'); +require('./node_modules/.bun/bun-isolated-package@4.5.6/node_modules/bun-isolated-package'); + +Sentry.captureException(new Error('error_1')); diff --git a/dev-packages/node-integration-tests/suites/modules/test.ts b/dev-packages/node-integration-tests/suites/modules/test.ts index f47c4c1f8b09..709bc24ddb1a 100644 --- a/dev-packages/node-integration-tests/suites/modules/test.ts +++ b/dev-packages/node-integration-tests/suites/modules/test.ts @@ -1,4 +1,5 @@ import { SDK_VERSION } from '@sentry/core'; +import { mkdirSync, rmSync, writeFileSync } from 'fs'; import { join } from 'path'; import { afterAll, describe, expect, test } from 'vitest'; import { cleanupChildProcesses, createRunner } from '../../utils/runner'; @@ -27,6 +28,28 @@ 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] of [ + ['.pnpm', 'pnpm-isolated-package', '1.2.3'], + ['.bun', 'bun-isolated-package', '4.5.6'], + ] as const) { + const packageDir = join(nodeModulesDir, storeDir, `${name}@${version}`, 'node_modules', name); + mkdirSync(packageDir, { recursive: true }); + writeFileSync(join(packageDir, 'package.json'), JSON.stringify({ name, version })); + writeFileSync(join(packageDir, 'index.js'), 'module.exports = {};'); + } + + try { + await createRunner(__dirname, 'scenario-isolated.js') + .expect({ event: { modules: { 'pnpm-isolated-package': '1.2.3', 'bun-isolated-package': '4.5.6' } } }) + .start() + .completed(); + } finally { + rmSync(nodeModulesDir, { recursive: true, force: true }); + } + }); + test('ESM', async () => { const runner = createRunner(__dirname, 'server.mjs') .withInstrument(join(__dirname, 'instrument.mjs')) diff --git a/packages/node/src/integrations/modules.ts b/packages/node/src/integrations/modules.ts index 508bae1beace..90921f75388b 100644 --- a/packages/node/src/integrations/modules.ts +++ b/packages/node/src/integrations/modules.ts @@ -1,5 +1,5 @@ import { existsSync, readFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; +import { basename, dirname, join } from 'node:path'; import { GLOBAL_OBJ, type IntegrationFn } from '@sentry/core'; type ModuleInfo = Record; @@ -73,6 +73,24 @@ function collectModules(): ModuleInfo { }; } +/** + * Whether `dir` is a node_modules folder of the main module, or a folder + * `/<.pnpm or .bun>//node_modules`, where the isolated layouts of pnpm and Bun keep + * each package. + */ +function isMainModulesDir(dir: string, mainPaths: string[]): boolean { + if (mainPaths.includes(dir)) { + return true; + } + + const storeDir = dirname(dirname(dir)); + return ( + basename(dir) === 'node_modules' && + ['.pnpm', '.bun'].includes(basename(storeDir)) && + mainPaths.includes(dirname(storeDir)) + ); +} + /** Extract information about package.json modules from require.cache */ function collectRequireModules(): ModuleInfo { const mainPaths = require.main?.paths || []; @@ -94,7 +112,7 @@ function collectRequireModules(): ModuleInfo { if (!dir || orig === dir || seen.has(orig)) { return undefined; } - if (mainPaths.indexOf(dir) < 0) { + if (!isMainModulesDir(dir, mainPaths)) { return updir(); }