@@ -8,7 +8,7 @@ const findUp = require('find-up');
88const packList = require ( 'npm-packlist' ) ;
99const readPkg = require ( 'read-pkg' ) ;
1010
11- const serverlessPackage = require ( '../package.json' ) ;
11+ const serverlessPackageJson = require ( '../package.json' ) ;
1212
1313if ( ! process . env . GITHUB_ACTIONS ) {
1414 console . log ( 'Skipping build-awslambda-layer script in local environment.' ) ;
@@ -36,7 +36,14 @@ if (!process.env.GITHUB_ACTIONS) {
3636// And yet another way is to bundle everything with webpack into a single file. I tried and it seems to be error-prone
3737// so I think it's better to have a classic package directory with node_modules file structure.
3838
39- /** Recursively traverse all the dependencies and collect all the info to the map */
39+ /**
40+ * Recursively traverses all the dependencies of @param pkg and collects all the info to the map
41+ * The map ultimately contains @sentry/serverless itself, its direct dependencies and
42+ * its transitive dependencies.
43+ *
44+ * @param cwd the root directory of the package
45+ * @param packages the map accumulating all packages
46+ */
4047async function collectPackages ( cwd , packages = { } ) {
4148 const packageJson = await readPkg ( { cwd } ) ;
4249
@@ -68,67 +75,89 @@ async function collectPackages(cwd, packages = {}) {
6875}
6976
7077async function main ( ) {
71- const workDir = path . resolve ( __dirname , '..' ) ; // packages/serverless directory
72- const distRequirements = path . resolve ( workDir , 'build' , 'cjs' ) ;
73- if ( ! fs . existsSync ( distRequirements ) ) {
74- console . log ( `The path ${ distRequirements } must exist.` ) ;
78+ const serverlessDir = path . resolve ( __dirname , '..' ) ; // packages/serverless directory
79+
80+ const cjsBuildDir = path . resolve ( serverlessDir , 'build' , 'cjs' ) ;
81+ if ( ! fs . existsSync ( cjsBuildDir ) ) {
82+ console . log ( `The path ${ cjsBuildDir } must exist.` ) ;
7583 return ;
7684 }
77- const packages = await collectPackages ( workDir ) ;
7885
79- const dist = path . resolve ( workDir , 'dist-awslambda-layer' ) ;
86+ const packages = await collectPackages ( serverlessDir ) ;
87+
88+ // the build directory of the Lambda layer
89+ const layerBuildDir = path . resolve ( serverlessDir , 'dist-awslambda-layer' ) ;
90+
91+ // the root directory in which the Lambda layer files + dependencies are copied to
92+ // this structure resembles the structure where Lambda expects to find @sentry /serverless
8093 const destRootRelative = 'nodejs/node_modules/@sentry/serverless' ;
81- const destRoot = path . resolve ( dist , destRootRelative ) ;
82- const destModulesRoot = path . resolve ( destRoot , 'node_modules' ) ;
94+ const destRootDir = path . resolve ( layerBuildDir , destRootRelative ) ;
95+
96+ // this is where all the (transitive) dependencies of @sentry/serverless go
97+ const destRootNodeModulesDir = path . resolve ( destRootDir , 'node_modules' ) ;
8398
8499 try {
85100 // Setting `force: true` ignores exceptions when paths don't exist.
86- fs . rmSync ( destRoot , { force : true , recursive : true , maxRetries : 1 } ) ;
87- fs . mkdirSync ( destRoot , { recursive : true } ) ;
101+ fs . rmSync ( destRootDir , { force : true , recursive : true , maxRetries : 1 } ) ;
102+ fs . mkdirSync ( destRootDir , { recursive : true } ) ;
88103 } catch ( error ) {
89104 // Ignore errors.
90105 }
91106
92107 await Promise . all (
93108 Object . entries ( packages ) . map ( async ( [ name , pkg ] ) => {
94- const isRoot = name == serverlessPackage . name ;
95- const destPath = isRoot ? destRoot : path . resolve ( destModulesRoot , name ) ;
96-
97- // Scan over the distributable files of the module and symlink each of them.
109+ const isServelessPkg = name == serverlessPackageJson . name ;
110+ const destDir = isServelessPkg ? destRootDir : path . resolve ( destRootNodeModulesDir , name ) ;
111+
112+ // Scan over the "distributable" files of `pkg` and symlink all of them.
113+ // `packList` returns all files it deems "distributable" from `pkg.cwd`.
114+ // "Distributable" means in this case that the file would end up in the NPM tarball of `pkg`.
115+ // To find out which files are distributable, packlist scans for NPM file configurations in the following order:
116+ // 1. if `files` section present in package.json, take everything* from there
117+ // 2. if `.npmignore` present, take everything* except what's ignored there
118+ // 3. if `.gitignore` present, take everything* except what's ignored there
119+ // 4. else take everything*
120+ // In our case, rule 2 applies.
121+ // * everything except certain unimportant files similarly to what `npm pack` does when packing a tarball.
122+ // For more information on the rules see: https://github.com/npm/npm-packlist#readme
98123 const sourceFiles = await packList ( { path : pkg . cwd } ) ;
124+
99125 await Promise . all (
100126 sourceFiles . map ( async filename => {
101- const sourceFilename = path . resolve ( pkg . cwd , filename ) ;
102- const destFilename = path . resolve ( destPath , filename ) ;
127+ const sourceFilePath = path . resolve ( pkg . cwd , filename ) ;
128+ const destFilePath = path . resolve ( destDir , filename ) ;
103129
104130 try {
105- fs . mkdirSync ( path . dirname ( destFilename ) , { recursive : true } ) ;
106- fs . symlinkSync ( sourceFilename , destFilename ) ;
131+ fs . mkdirSync ( path . dirname ( destFilePath ) , { recursive : true } ) ;
132+ fs . symlinkSync ( sourceFilePath , destFilePath ) ;
107133 } catch ( error ) {
108134 // Ignore errors.
109135 }
110136 } ) ,
111137 ) ;
112138
113- const sourceModulesRoot = path . resolve ( pkg . cwd , 'node_modules' ) ;
139+ // Now we deal with the `pkg`'s dependencies in its local `node_modules` directory
140+ const pkgNodeModulesDir = path . resolve ( pkg . cwd , 'node_modules' ) ;
141+
142+ // First, check if `pkg` has node modules. If not, we're done with this `pkg`.
114143 // `fs.constants.F_OK` indicates whether the file is visible to the current process, but it doesn't check
115144 // its permissions. For more information, refer to https://nodejs.org/api/fs.html#fs_file_access_constants.
116145 try {
117- fs . accessSync ( path . resolve ( sourceModulesRoot ) , fs . constants . F_OK ) ;
146+ fs . accessSync ( path . resolve ( pkgNodeModulesDir ) , fs . constants . F_OK ) ;
118147 } catch ( error ) {
119148 return ;
120149 }
121150
122- // Scan over local node_modules folder of the package and symlink its non-dev dependencies.
123- const sourceModules = fs . readdirSync ( sourceModulesRoot ) ;
151+ // Then, scan over local node_modules folder of `pkg` and symlink its non-dev dependencies.
152+ const pkgNodeModules = fs . readdirSync ( pkgNodeModulesDir ) ;
124153 await Promise . all (
125- sourceModules . map ( async sourceModule => {
126- if ( ! pkg . packageJson . dependencies || ! pkg . packageJson . dependencies [ sourceModule ] ) {
154+ pkgNodeModules . map ( async nodeModule => {
155+ if ( ! pkg . packageJson . dependencies || ! pkg . packageJson . dependencies [ nodeModule ] ) {
127156 return ;
128157 }
129158
130- const sourceModulePath = path . resolve ( sourceModulesRoot , sourceModule ) ;
131- const destModulePath = path . resolve ( destPath , 'node_modules' , sourceModule ) ;
159+ const sourceModulePath = path . resolve ( pkgNodeModulesDir , nodeModule ) ;
160+ const destModulePath = path . resolve ( destDir , 'node_modules' , nodeModule ) ;
132161
133162 try {
134163 fs . mkdirSync ( path . dirname ( destModulePath ) , { recursive : true } ) ;
@@ -141,25 +170,32 @@ async function main() {
141170 } ) ,
142171 ) ;
143172
144- const version = serverlessPackage . version ;
173+ const version = serverlessPackageJson . version ;
145174 const zipFilename = `sentry-node-serverless-${ version } .zip` ;
146175
176+ // link from `./build/cjs` to `./dist`
177+ // This needs to be done to satisfy the NODE_OPTIONS environment variable path that is set in
178+ // AWS lambda functions when connecting them to Sentry. On initialization, the layer preloads a js
179+ // file specified in NODE_OPTIONS to initialize the SDK.
180+ // Hence we symlink everything from `.build/cjs` to `.dist`.
181+ // This creates duplication but it's not too bad file size wise.
147182 try {
148- fs . symlinkSync ( path . resolve ( destRoot , 'build' , 'dist' ) , path . resolve ( destRoot , 'dist' ) ) ;
149- fs . symlinkSync ( path . resolve ( destRoot , 'build' , 'esm' ) , path . resolve ( destRoot , 'esm' ) ) ;
183+ fs . symlinkSync ( path . resolve ( destRootDir , 'build' , 'cjs' ) , path . resolve ( destRootDir , 'dist' ) ) ;
150184 } catch ( error ) {
151185 console . error ( error ) ;
152186 }
153187
188+ // remove previously created layer zip
154189 try {
155- fs . unlinkSync ( path . resolve ( dist , zipFilename ) ) ;
190+ fs . unlinkSync ( path . resolve ( layerBuildDir , zipFilename ) ) ;
156191 } catch ( error ) {
157192 // If the ZIP file hasn't been previously created (e.g. running this script for the first time),
158193 // `unlinkSync` will try to delete a non-existing file. This error is ignored.
159194 }
160195
196+ // create new layer zip
161197 try {
162- childProcess . execSync ( `zip -r ${ zipFilename } ${ destRootRelative } ` , { cwd : dist } ) ;
198+ childProcess . execSync ( `zip -r ${ zipFilename } ${ destRootRelative } ` , { cwd : layerBuildDir } ) ;
163199 } catch ( error ) {
164200 // The child process timed out or had non-zero exit code.
165201 // The error contains the entire result from `childProcess.spawnSync`.
0 commit comments