22import { nodeFileTrace } from '@vercel/nft' ;
33import * as childProcess from 'child_process' ;
44import * as fs from 'fs' ;
5- import * as os from 'os' ;
65import * as path from 'path' ;
76import { version } from '../package.json' ;
87
@@ -22,21 +21,14 @@ function run(cmd: string, options?: childProcess.ExecSyncOptions): string {
2221 */
2322async function buildLambdaLayer ( ) : Promise < void > {
2423 console . log ( 'Building Lambda layer.' ) ;
25- buildPackageJson ( ) ;
26- console . log ( 'Installing local @sentry/aws-serverless into build/aws/dist-serverless/nodejs.' ) ;
27- // Use a temporary cache folder to avoid stale cache references to local file: packages.
28- // Yarn's global cache can contain outdated references to build artifacts from other
29- // @sentry /* packages (e.g., build/node_modules paths that no longer exist), causing
30- // ENOENT errors during file copying.
31- // The cache folder must be outside the monorepo to avoid recursive nesting when Yarn
32- // follows file: links and copies package directories.
33- const cacheFolder = path . join ( os . tmpdir ( ) , `sentry-lambda-build-cache-${ Date . now ( ) } ` ) ;
34- run ( `yarn install --prod --cwd ./build/aws/dist-serverless/nodejs --cache-folder "${ cacheFolder } "` ) ;
24+ const tarballDir = buildTarballsAndPackageJson ( ) ;
25+ console . log ( 'Installing @sentry/aws-serverless from tarballs into build/aws/dist-serverless/nodejs.' ) ;
26+ run ( 'yarn install --prod --cwd ./build/aws/dist-serverless/nodejs' ) ;
3527
3628 await pruneNodeModules ( ) ;
3729 fs . rmSync ( './build/aws/dist-serverless/nodejs/package.json' , { force : true } ) ;
3830 fs . rmSync ( './build/aws/dist-serverless/nodejs/yarn.lock' , { force : true } ) ;
39- fs . rmSync ( cacheFolder , { recursive : true , force : true } ) ;
31+ fs . rmSync ( tarballDir , { recursive : true , force : true } ) ;
4032
4133 // The layer also includes `awslambda-auto.js`, a helper file which calls `Sentry.init()` and wraps the lambda
4234 // handler. It gets run when Node is launched inside the lambda, using the environment variable
@@ -170,43 +162,90 @@ function getAllFiles(dir: string): string[] {
170162 return files ;
171163}
172164
173- function buildPackageJson ( ) : void {
174- console . log ( 'Building package.json' ) ;
165+ /**
166+ * Pack @sentry/* packages into tarballs and generate a package.json that references them.
167+ *
168+ * Using tarballs instead of `file:` directory references avoids stale yarn cache issues
169+ * (where yarn's global cache retains outdated build artifact paths from previous builds)
170+ * and allows us to use yarn's global cache for faster installs.
171+ *
172+ * Only packs packages that are transitive dependencies of @sentry/aws-serverless to keep
173+ * the packing step fast.
174+ */
175+ function buildTarballsAndPackageJson ( ) : string {
176+ const tarballDir = path . resolve ( './build/aws/tarballs' ) ;
177+ fsForceMkdirSync ( tarballDir ) ;
178+
175179 const packagesDir = path . resolve ( __dirname , '../..' ) ;
176- const packageDirs = fs
177- . readdirSync ( packagesDir , { withFileTypes : true } )
178- . filter ( dirent => dirent . isDirectory ( ) )
179- . map ( dirent => dirent . name )
180- . filter ( name => ! name . startsWith ( '.' ) ) // Skip hidden directories
181- . sort ( ) ;
180+ const sentryPackages = collectSentryDependencies ( packagesDir ) ;
181+
182+ console . log ( `Packing ${ sentryPackages . size } @sentry/* packages into tarballs.` ) ;
182183
183184 const resolutions : Record < string , string > = { } ;
184185
185- for ( const packageDir of packageDirs ) {
186- const packageJsonPath = path . join ( packagesDir , packageDir , 'package.json' ) ;
187- if ( fs . existsSync ( packageJsonPath ) ) {
188- try {
189- const packageContent = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf-8' ) ) as { name ?: string } ;
190- const packageName = packageContent . name ;
191- if ( typeof packageName === 'string' && packageName ) {
192- resolutions [ packageName ] = `file:../../../../../../packages/${ packageDir } ` ;
193- }
194- } catch {
195- console . warn ( `Warning: Could not read package.json for ${ packageDir } ` ) ;
196- }
197- }
186+ for ( const [ packageName , packageDir ] of sentryPackages ) {
187+ run ( `npm pack --pack-destination "${ tarballDir } "` , {
188+ cwd : path . join ( packagesDir , packageDir ) ,
189+ stdio : 'pipe' ,
190+ } ) ;
191+ const tarballName = `${ packageName . replace ( '@' , '' ) . replace ( '/' , '-' ) } -${ version } .tgz` ;
192+ resolutions [ packageName ] = `file:${ path . join ( tarballDir , tarballName ) } ` ;
193+ }
194+
195+ const awsServerlessTarball = resolutions [ '@sentry/aws-serverless' ] ;
196+ if ( ! awsServerlessTarball ) {
197+ throw new Error ( 'Failed to pack @sentry/aws-serverless' ) ;
198198 }
199199
200200 const packageJson = {
201201 dependencies : {
202- '@sentry/aws-serverless' : 'file:../../../../../../packages/aws-serverless' ,
202+ '@sentry/aws-serverless' : awsServerlessTarball ,
203203 } ,
204204 resolutions,
205205 } ;
206206
207207 fsForceMkdirSync ( './build/aws/dist-serverless/nodejs' ) ;
208- const packageJsonPath = './build/aws/dist-serverless/nodejs/package.json' ;
209- fs . writeFileSync ( packageJsonPath , JSON . stringify ( packageJson , null , 2 ) ) ;
208+ fs . writeFileSync ( './build/aws/dist-serverless/nodejs/package.json' , JSON . stringify ( packageJson , null , 2 ) ) ;
209+
210+ return tarballDir ;
211+ }
212+
213+ /**
214+ * Collect all @sentry/* and @sentry-internal/* packages that are transitive
215+ * dependencies of @sentry/aws-serverless.
216+ * Returns a Map of packageName -> directory name.
217+ */
218+ function collectSentryDependencies ( packagesDir : string ) : Map < string , string > {
219+ const result = new Map < string , string > ( ) ;
220+
221+ // Build a lookup of package name -> directory name
222+ const nameToDir = new Map < string , string > ( ) ;
223+ for ( const dirent of fs . readdirSync ( packagesDir , { withFileTypes : true } ) ) {
224+ if ( ! dirent . isDirectory ( ) || dirent . name . startsWith ( '.' ) ) continue ;
225+ const pkgPath = path . join ( packagesDir , dirent . name , 'package.json' ) ;
226+ if ( ! fs . existsSync ( pkgPath ) ) continue ;
227+ const pkg = JSON . parse ( fs . readFileSync ( pkgPath , 'utf-8' ) ) as { name ?: string } ;
228+ if ( pkg . name ) {
229+ nameToDir . set ( pkg . name , dirent . name ) ;
230+ }
231+ }
232+
233+ function collect ( packageName : string ) : void {
234+ if ( result . has ( packageName ) ) return ;
235+ const dir = nameToDir . get ( packageName ) ;
236+ if ( ! dir ) return ;
237+ result . set ( packageName , dir ) ;
238+ const pkgPath = path . join ( packagesDir , dir , 'package.json' ) ;
239+ const pkg = JSON . parse ( fs . readFileSync ( pkgPath , 'utf-8' ) ) as { dependencies ?: Record < string , string > } ;
240+ for ( const dep of Object . keys ( pkg . dependencies || { } ) ) {
241+ if ( dep . startsWith ( '@sentry/' ) || dep . startsWith ( '@sentry-internal/' ) ) {
242+ collect ( dep ) ;
243+ }
244+ }
245+ }
246+
247+ collect ( '@sentry/aws-serverless' ) ;
248+ return result ;
210249}
211250
212251function replaceSDKSource ( ) : void {
0 commit comments