11/* eslint-disable no-console */
2+ // @ts -expect-error - no types
3+ import { terserOptions } from '@sentry-internal/rollup-utils' ;
4+ import { nodeFileTrace } from '@vercel/nft' ;
25import * as childProcess from 'child_process' ;
36import * as fs from 'fs' ;
7+ import * as path from 'path' ;
8+ import { minify } from 'terser' ;
49import { version } from '../package.json' ;
510
611/**
@@ -11,21 +16,19 @@ function run(cmd: string, options?: childProcess.ExecSyncOptions): string {
1116 return String ( childProcess . execSync ( cmd , { stdio : 'inherit' , ...options } ) ) ;
1217}
1318
19+ /**
20+ * Build the AWS lambda layer by first installing the local package into `build/aws/dist-serverless/nodejs`.
21+ * Then, prune the node_modules directory to remove unused files by first getting all necessary files with
22+ * `@vercel/nft` and then deleting all other files inside `node_modules`.
23+ * Finally, minify the files and create a zip file of the layer.
24+ */
1425async function buildLambdaLayer ( ) : Promise < void > {
15- // Create the main SDK bundle
16- run ( 'yarn rollup --config rollup.aws.config.mjs' ) ;
17-
18- // We build a minified bundle, but it's standing in for the regular `index.js` file listed in `package.json`'s `main`
19- // property, so we have to rename it so it's findable.
20- fs . renameSync (
21- 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.debug.min.js' ,
22- 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.js' ,
23- ) ;
26+ console . log ( 'Installing local @sentry/aws-serverless into build/aws/dist-serverless/nodejs.' ) ;
27+ run ( 'npm install . --prefix ./build/aws/dist-serverless/nodejs --install-links --silent' ) ;
2428
25- // We're creating a bundle for the SDK, but still using it in a Node context, so we need to copy in `package.json`,
26- // purely for its `main` property.
27- console . log ( 'Copying `package.json` into lambda layer.' ) ;
28- fs . copyFileSync ( 'package.json' , 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/package.json' ) ;
29+ await pruneNodeModules ( ) ;
30+ fs . unlinkSync ( './build/aws/dist-serverless/nodejs/package.json' ) ;
31+ fs . unlinkSync ( './build/aws/dist-serverless/nodejs/package-lock.json' ) ;
2932
3033 // The layer also includes `awslambda-auto.js`, a helper file which calls `Sentry.init()` and wraps the lambda
3134 // handler. It gets run when Node is launched inside the lambda, using the environment variable
@@ -61,3 +64,113 @@ function fsForceMkdirSync(path: string): void {
6164 fs . rmSync ( path , { recursive : true , force : true } ) ;
6265 fs . mkdirSync ( path ) ;
6366}
67+
68+ async function pruneNodeModules ( ) : Promise < void > {
69+ const entrypoints = [
70+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/esm/index.js' ,
71+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.js' ,
72+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/awslambda-auto.js' ,
73+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/esm/awslambda-auto.js' ,
74+ ] ;
75+
76+ const { fileList } = await nodeFileTrace ( entrypoints ) ;
77+
78+ const allFiles = getAllFiles ( './build/aws/dist-serverless/nodejs/node_modules' ) ;
79+
80+ const filesToDelete = allFiles . filter ( file => ! fileList . has ( file ) ) ;
81+ console . log ( `Removing ${ filesToDelete . length } unused files from node_modules.` ) ;
82+
83+ for ( const file of filesToDelete ) {
84+ try {
85+ fs . unlinkSync ( file ) ;
86+ } catch {
87+ console . error ( `Error deleting ${ file } ` ) ;
88+ }
89+ }
90+
91+ console . log ( 'Cleaning up empty directories.' ) ;
92+
93+ removeEmptyDirs ( './build/aws/dist-serverless/nodejs/node_modules' ) ;
94+
95+ await minifyJavaScriptFiles ( fileList ) ;
96+ }
97+
98+ function removeEmptyDirs ( dir : string ) : void {
99+ try {
100+ const entries = fs . readdirSync ( dir ) ;
101+
102+ for ( const entry of entries ) {
103+ const fullPath = path . join ( dir , entry ) ;
104+ const stat = fs . statSync ( fullPath ) ;
105+ if ( stat . isDirectory ( ) ) {
106+ removeEmptyDirs ( fullPath ) ;
107+ }
108+ }
109+
110+ const remainingEntries = fs . readdirSync ( dir ) ;
111+
112+ if ( remainingEntries . length === 0 ) {
113+ fs . rmdirSync ( dir ) ;
114+ }
115+ } catch ( error ) {
116+ // Directory might not exist or might not be empty, that's ok
117+ }
118+ }
119+
120+ function getAllFiles ( dir : string ) : string [ ] {
121+ const files : string [ ] = [ ] ;
122+
123+ function walkDirectory ( currentPath : string ) : void {
124+ try {
125+ const entries = fs . readdirSync ( currentPath , { withFileTypes : true } ) ;
126+
127+ for ( const entry of entries ) {
128+ const fullPath = path . join ( currentPath , entry . name ) ;
129+ const relativePath = path . relative ( process . cwd ( ) , fullPath ) ;
130+
131+ if ( entry . isDirectory ( ) ) {
132+ walkDirectory ( fullPath ) ;
133+ } else {
134+ files . push ( relativePath ) ;
135+ }
136+ }
137+ } catch {
138+ console . log ( `Skipping directory ${ currentPath } ` ) ;
139+ }
140+ }
141+
142+ walkDirectory ( dir ) ;
143+ return files ;
144+ }
145+
146+ async function minifyJavaScriptFiles ( fileList : Set < string > ) : Promise < void > {
147+ console . log ( 'Minifying JavaScript files.' ) ;
148+ let minifiedCount = 0 ;
149+
150+ for ( const file of fileList ) {
151+ if ( ! file . endsWith ( '.js' ) && ! file . endsWith ( '.mjs' ) && ! file . endsWith ( '.cjs' ) ) {
152+ continue ;
153+ }
154+
155+ // Skip minification for OpenTelemetry files to avoid CommonJS/ESM interop issues
156+ if ( file . includes ( '@opentelemetry' ) ) {
157+ continue ;
158+ }
159+
160+ try {
161+ const fullPath = path . resolve ( file ) ;
162+ const code = fs . readFileSync ( fullPath , 'utf-8' ) ;
163+
164+ const result = await minify ( code , terserOptions ) ;
165+
166+ if ( result . code ) {
167+ fs . writeFileSync ( fullPath , result . code , 'utf-8' ) ;
168+ minifiedCount ++ ;
169+ }
170+ } catch ( error ) {
171+ console . error ( `Error minifying ${ file } ` , error ) ;
172+ }
173+ }
174+
175+ console . log ( `Minified ${ minifiedCount } files.` ) ;
176+ }
0 commit comments