From 7202b1a3fc921a1a14a0054604b36226484d6d1e Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 4 Sep 2026 10:38:49 +0200 Subject: [PATCH 1/4] test(e2e): Avoid network installs in AWS serverless tests Co-Authored-By: OpenAI Codex --- .../test-applications/aws-serverless/src/stack.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts index 87b5d2b7f8c3..d270b8b581ea 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts @@ -52,7 +52,7 @@ export class LocalLambdaStack extends Stack { const functionName = `Npm${lambdaDir}`; const lambdaPath = path.resolve(LAMBDA_FUNCTIONS_DIR, lambdaDir); - const packageLockPath = path.join(lambdaPath, 'package-lock.json'); + const lockfilePath = path.join(lambdaPath, 'pnpm-lock.yaml'); const nodeModulesPath = path.join(lambdaPath, 'node_modules'); // `dir` is the package directory under `packages/`; `name` is the published npm name. @@ -81,8 +81,8 @@ export class LocalLambdaStack extends Stack { console.log(`[LocalLambdaStack] Install dependencies for ${functionName}`); - if (fs.existsSync(packageLockPath)) { - fs.rmSync(packageLockPath); + if (fs.existsSync(lockfilePath)) { + fs.rmSync(lockfilePath); } if (fs.existsSync(nodeModulesPath)) { @@ -91,10 +91,16 @@ export class LocalLambdaStack extends Stack { const packageJson = { dependencies, + pnpm: { + overrides: dependencies, + }, }; fs.writeFileSync(path.join(lambdaPath, 'package.json'), JSON.stringify(packageJson, null, 2)); - execFileSync('npm', ['install', '--install-links', '--prefix', lambdaPath], { stdio: 'inherit' }); + execFileSync('pnpm', ['install', '--offline', '--prod', '--ignore-scripts', '--no-frozen-lockfile'], { + cwd: lambdaPath, + stdio: 'inherit', + }); if (!process.env.NODE_VERSION) { throw new Error('[LocalLambdaStack] NODE_VERSION is not set'); From 28613a7b4d15d220f5d3df9ca16de3fecd522b00 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 4 Sep 2026 11:09:17 +0200 Subject: [PATCH 2/4] test(e2e): Make pnpm Lambda install self-contained Co-Authored-By: OpenAI Codex --- .../aws-serverless/src/stack.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts index d270b8b581ea..a3874fc3f585 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts @@ -27,6 +27,14 @@ function resolvePackagesDir(): string { return path.resolve(__dirname, '../../../../../packages'); } +function materializePackage(lambdaPath: string, packageName: string): void { + const packagePath = path.join(lambdaPath, 'node_modules', ...packageName.split('/')); + const resolvedPackagePath = fs.realpathSync(packagePath); + + fs.rmSync(packagePath, { recursive: true, force: true }); + fs.cpSync(resolvedPackagePath, packagePath, { recursive: true, dereference: true }); +} + export class LocalLambdaStack extends Stack { constructor(scope: Construct, id: string, props: StackProps, hostIp: string) { console.log('[LocalLambdaStack] Creating local SAM Lambda Stack'); @@ -79,6 +87,15 @@ export class LocalLambdaStack extends Stack { dependencies[name] = `file:${relativePath.replace(/\\/g, '/')}`; } + const awsServerlessPackageJson = JSON.parse( + fs.readFileSync(path.join(packagesDir, 'aws-serverless', 'package.json'), 'utf8'), + ) as { dependencies: Record }; + const conventionsVersion = awsServerlessPackageJson.dependencies['@sentry/conventions']; + if (!conventionsVersion) { + throw new Error('[LocalLambdaStack] @sentry/aws-serverless does not declare @sentry/conventions'); + } + dependencies['@sentry/conventions'] = conventionsVersion; + console.log(`[LocalLambdaStack] Install dependencies for ${functionName}`); if (fs.existsSync(lockfilePath)) { @@ -102,6 +119,9 @@ export class LocalLambdaStack extends Stack { stdio: 'inherit', }); + // Lambda handler names cannot contain pnpm's `.pnpm` virtual-store path. + materializePackage(lambdaPath, '@sentry/aws-serverless'); + if (!process.env.NODE_VERSION) { throw new Error('[LocalLambdaStack] NODE_VERSION is not set'); } From aa9dc13880c1f2a6543f1fefd444bfda96e532a8 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 4 Sep 2026 11:24:10 +0200 Subject: [PATCH 3/4] fix(aws-serverless): Support pnpm handler paths Co-Authored-By: OpenAI Codex --- .../aws-serverless/src/stack.ts | 20 ------------- .../src/integration/awslambda.ts | 22 +++++++++++++- .../test/awslambda-integration.test.ts | 30 ++++++++++++++++++- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts index a3874fc3f585..d270b8b581ea 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts @@ -27,14 +27,6 @@ function resolvePackagesDir(): string { return path.resolve(__dirname, '../../../../../packages'); } -function materializePackage(lambdaPath: string, packageName: string): void { - const packagePath = path.join(lambdaPath, 'node_modules', ...packageName.split('/')); - const resolvedPackagePath = fs.realpathSync(packagePath); - - fs.rmSync(packagePath, { recursive: true, force: true }); - fs.cpSync(resolvedPackagePath, packagePath, { recursive: true, dereference: true }); -} - export class LocalLambdaStack extends Stack { constructor(scope: Construct, id: string, props: StackProps, hostIp: string) { console.log('[LocalLambdaStack] Creating local SAM Lambda Stack'); @@ -87,15 +79,6 @@ export class LocalLambdaStack extends Stack { dependencies[name] = `file:${relativePath.replace(/\\/g, '/')}`; } - const awsServerlessPackageJson = JSON.parse( - fs.readFileSync(path.join(packagesDir, 'aws-serverless', 'package.json'), 'utf8'), - ) as { dependencies: Record }; - const conventionsVersion = awsServerlessPackageJson.dependencies['@sentry/conventions']; - if (!conventionsVersion) { - throw new Error('[LocalLambdaStack] @sentry/aws-serverless does not declare @sentry/conventions'); - } - dependencies['@sentry/conventions'] = conventionsVersion; - console.log(`[LocalLambdaStack] Install dependencies for ${functionName}`); if (fs.existsSync(lockfilePath)) { @@ -119,9 +102,6 @@ export class LocalLambdaStack extends Stack { stdio: 'inherit', }); - // Lambda handler names cannot contain pnpm's `.pnpm` virtual-store path. - materializePackage(lambdaPath, '@sentry/aws-serverless'); - if (!process.env.NODE_VERSION) { throw new Error('[LocalLambdaStack] NODE_VERSION is not set'); } diff --git a/packages/aws-serverless/src/integration/awslambda.ts b/packages/aws-serverless/src/integration/awslambda.ts index 94ddb92d00f6..9fc4f6a9e15a 100644 --- a/packages/aws-serverless/src/integration/awslambda.ts +++ b/packages/aws-serverless/src/integration/awslambda.ts @@ -1,18 +1,38 @@ import type { IntegrationFn } from '@sentry/core'; import { debug, defineIntegration, getCurrentScope, safeSetSpanJSONAttributes } from '@sentry/core'; +import { existsSync } from 'node:fs'; import { createRequire } from 'node:module'; +import { sep, join } from 'node:path'; import { DEBUG_BUILD } from '../debug-build'; import { parseHandlerString, resolveHandlerFile } from '../handlerResolution'; const SHIM_MODULE_ID = '@sentry/aws-serverless/run-lambda-handler'; +export function findHandlerShimFile(resolvedShimFile: string, nodeModulesPaths: string[] | null): string | undefined { + const nodeModulesMarker = `${sep}node_modules${sep}`; + const lastNodeModulesIndex = resolvedShimFile.lastIndexOf(nodeModulesMarker); + const packageRelativePath = + lastNodeModulesIndex < 0 ? undefined : resolvedShimFile.slice(lastNodeModulesIndex + nodeModulesMarker.length); + + const logicalShimFiles = packageRelativePath + ? (nodeModulesPaths ?? []).map(nodeModulesPath => join(nodeModulesPath, packageRelativePath)) + : []; + + return [...logicalShimFiles, resolvedShimFile].find(shimFile => !shimFile.includes('..') && existsSync(shimFile)); +} + function resolveShimFile(): string | undefined { try { // In the CJS build `require` exists; in the ESM build (and when running the TS source // directly, e.g. in tests) we create one. Rollup converts `import.meta.url` to an // equivalent for the CJS build, so both branches are always syntactically valid. const resolve = typeof require === 'function' ? require.resolve : createRequire(import.meta.url).resolve; - return resolve(SHIM_MODULE_ID); + const resolvedShimFile = resolve(SHIM_MODULE_ID); + const shimFile = findHandlerShimFile(resolvedShimFile, resolve.paths(SHIM_MODULE_ID)); + if (!shimFile) { + DEBUG_BUILD && debug.warn(`Could not find a Lambda-compatible path for ${SHIM_MODULE_ID}.`); + } + return shimFile; } catch (error) { DEBUG_BUILD && debug.warn(`Could not resolve ${SHIM_MODULE_ID}, not instrumenting the Lambda handler.`, error); return undefined; diff --git a/packages/aws-serverless/test/awslambda-integration.test.ts b/packages/aws-serverless/test/awslambda-integration.test.ts index a1a45fd656b9..b0639a8b76d1 100644 --- a/packages/aws-serverless/test/awslambda-integration.test.ts +++ b/packages/aws-serverless/test/awslambda-integration.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { awsLambdaIntegration, redirectLambdaHandler } from '../src/integration/awslambda'; +import { awsLambdaIntegration, findHandlerShimFile, redirectLambdaHandler } from '../src/integration/awslambda'; const mockGetScopeData = vi.fn(); @@ -45,6 +45,34 @@ describe('redirectLambdaHandler', () => { expect(process.env._HANDLER).not.toContain('..'); }); + test('uses a logical package path when pnpm resolves the shim to an invalid virtual-store path', () => { + const nodeModulesPath = path.join(taskRoot, 'node_modules'); + const logicalShimFile = path.join( + nodeModulesPath, + '@sentry', + 'aws-serverless', + 'build', + 'npm', + 'run-lambda-handler.mjs', + ); + fs.mkdirSync(path.dirname(logicalShimFile), { recursive: true }); + fs.writeFileSync(logicalShimFile, 'export const handler = () => {};'); + + const resolvedShimFile = path.join( + nodeModulesPath, + '.pnpm', + '@sentry+aws-serverless@file+..+..+packages+aws-serverless', + 'node_modules', + '@sentry', + 'aws-serverless', + 'build', + 'npm', + 'run-lambda-handler.mjs', + ); + + expect(findHandlerShimFile(resolvedShimFile, [nodeModulesPath])).toBe(logicalShimFile); + }); + test('does nothing when LAMBDA_TASK_ROOT or _HANDLER are not set', () => { process.env._HANDLER = 'index.handler'; From bbf1a2299eb8e7ff579240b62c49b00d6ab72a7a Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 4 Sep 2026 12:23:13 +0200 Subject: [PATCH 4/4] test(e2e): Use deployable pnpm Lambda layout Co-Authored-By: OpenAI Codex --- .../aws-serverless/src/stack.ts | 12 +++++--- .../src/integration/awslambda.ts | 22 +------------- .../test/awslambda-integration.test.ts | 30 +------------------ 3 files changed, 10 insertions(+), 54 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts index d270b8b581ea..b01f79c5bb48 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts @@ -97,10 +97,14 @@ export class LocalLambdaStack extends Stack { }; fs.writeFileSync(path.join(lambdaPath, 'package.json'), JSON.stringify(packageJson, null, 2)); - execFileSync('pnpm', ['install', '--offline', '--prod', '--ignore-scripts', '--no-frozen-lockfile'], { - cwd: lambdaPath, - stdio: 'inherit', - }); + execFileSync( + 'pnpm', + ['install', '--offline', '--prod', '--ignore-scripts', '--no-frozen-lockfile', '--config.node-linker=hoisted'], + { + cwd: lambdaPath, + stdio: 'inherit', + }, + ); if (!process.env.NODE_VERSION) { throw new Error('[LocalLambdaStack] NODE_VERSION is not set'); diff --git a/packages/aws-serverless/src/integration/awslambda.ts b/packages/aws-serverless/src/integration/awslambda.ts index 9fc4f6a9e15a..94ddb92d00f6 100644 --- a/packages/aws-serverless/src/integration/awslambda.ts +++ b/packages/aws-serverless/src/integration/awslambda.ts @@ -1,38 +1,18 @@ import type { IntegrationFn } from '@sentry/core'; import { debug, defineIntegration, getCurrentScope, safeSetSpanJSONAttributes } from '@sentry/core'; -import { existsSync } from 'node:fs'; import { createRequire } from 'node:module'; -import { sep, join } from 'node:path'; import { DEBUG_BUILD } from '../debug-build'; import { parseHandlerString, resolveHandlerFile } from '../handlerResolution'; const SHIM_MODULE_ID = '@sentry/aws-serverless/run-lambda-handler'; -export function findHandlerShimFile(resolvedShimFile: string, nodeModulesPaths: string[] | null): string | undefined { - const nodeModulesMarker = `${sep}node_modules${sep}`; - const lastNodeModulesIndex = resolvedShimFile.lastIndexOf(nodeModulesMarker); - const packageRelativePath = - lastNodeModulesIndex < 0 ? undefined : resolvedShimFile.slice(lastNodeModulesIndex + nodeModulesMarker.length); - - const logicalShimFiles = packageRelativePath - ? (nodeModulesPaths ?? []).map(nodeModulesPath => join(nodeModulesPath, packageRelativePath)) - : []; - - return [...logicalShimFiles, resolvedShimFile].find(shimFile => !shimFile.includes('..') && existsSync(shimFile)); -} - function resolveShimFile(): string | undefined { try { // In the CJS build `require` exists; in the ESM build (and when running the TS source // directly, e.g. in tests) we create one. Rollup converts `import.meta.url` to an // equivalent for the CJS build, so both branches are always syntactically valid. const resolve = typeof require === 'function' ? require.resolve : createRequire(import.meta.url).resolve; - const resolvedShimFile = resolve(SHIM_MODULE_ID); - const shimFile = findHandlerShimFile(resolvedShimFile, resolve.paths(SHIM_MODULE_ID)); - if (!shimFile) { - DEBUG_BUILD && debug.warn(`Could not find a Lambda-compatible path for ${SHIM_MODULE_ID}.`); - } - return shimFile; + return resolve(SHIM_MODULE_ID); } catch (error) { DEBUG_BUILD && debug.warn(`Could not resolve ${SHIM_MODULE_ID}, not instrumenting the Lambda handler.`, error); return undefined; diff --git a/packages/aws-serverless/test/awslambda-integration.test.ts b/packages/aws-serverless/test/awslambda-integration.test.ts index b0639a8b76d1..a1a45fd656b9 100644 --- a/packages/aws-serverless/test/awslambda-integration.test.ts +++ b/packages/aws-serverless/test/awslambda-integration.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { awsLambdaIntegration, findHandlerShimFile, redirectLambdaHandler } from '../src/integration/awslambda'; +import { awsLambdaIntegration, redirectLambdaHandler } from '../src/integration/awslambda'; const mockGetScopeData = vi.fn(); @@ -45,34 +45,6 @@ describe('redirectLambdaHandler', () => { expect(process.env._HANDLER).not.toContain('..'); }); - test('uses a logical package path when pnpm resolves the shim to an invalid virtual-store path', () => { - const nodeModulesPath = path.join(taskRoot, 'node_modules'); - const logicalShimFile = path.join( - nodeModulesPath, - '@sentry', - 'aws-serverless', - 'build', - 'npm', - 'run-lambda-handler.mjs', - ); - fs.mkdirSync(path.dirname(logicalShimFile), { recursive: true }); - fs.writeFileSync(logicalShimFile, 'export const handler = () => {};'); - - const resolvedShimFile = path.join( - nodeModulesPath, - '.pnpm', - '@sentry+aws-serverless@file+..+..+packages+aws-serverless', - 'node_modules', - '@sentry', - 'aws-serverless', - 'build', - 'npm', - 'run-lambda-handler.mjs', - ); - - expect(findHandlerShimFile(resolvedShimFile, [nodeModulesPath])).toBe(logicalShimFile); - }); - test('does nothing when LAMBDA_TASK_ROOT or _HANDLER are not set', () => { process.env._HANDLER = 'index.handler';