@@ -8,7 +8,7 @@ const findUp = require('find-up');
8
8
const packList = require ( 'npm-packlist' ) ;
9
9
const readPkg = require ( 'read-pkg' ) ;
10
10
11
- const serverlessPackage = require ( '../package.json' ) ;
11
+ const serverlessPackageJson = require ( '../package.json' ) ;
12
12
13
13
if ( ! process . env . GITHUB_ACTIONS ) {
14
14
console . log ( 'Skipping build-awslambda-layer script in local environment.' ) ;
@@ -36,7 +36,14 @@ if (!process.env.GITHUB_ACTIONS) {
36
36
// And yet another way is to bundle everything with webpack into a single file. I tried and it seems to be error-prone
37
37
// so I think it's better to have a classic package directory with node_modules file structure.
38
38
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
+ */
40
47
async function collectPackages ( cwd , packages = { } ) {
41
48
const packageJson = await readPkg ( { cwd } ) ;
42
49
@@ -68,67 +75,89 @@ async function collectPackages(cwd, packages = {}) {
68
75
}
69
76
70
77
async 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.` ) ;
75
83
return ;
76
84
}
77
- const packages = await collectPackages ( workDir ) ;
78
85
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
80
93
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' ) ;
83
98
84
99
try {
85
100
// 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 } ) ;
88
103
} catch ( error ) {
89
104
// Ignore errors.
90
105
}
91
106
92
107
await Promise . all (
93
108
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
98
123
const sourceFiles = await packList ( { path : pkg . cwd } ) ;
124
+
99
125
await Promise . all (
100
126
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 ) ;
103
129
104
130
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 ) ;
107
133
} catch ( error ) {
108
134
// Ignore errors.
109
135
}
110
136
} ) ,
111
137
) ;
112
138
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`.
114
143
// `fs.constants.F_OK` indicates whether the file is visible to the current process, but it doesn't check
115
144
// its permissions. For more information, refer to https://nodejs.org/api/fs.html#fs_file_access_constants.
116
145
try {
117
- fs . accessSync ( path . resolve ( sourceModulesRoot ) , fs . constants . F_OK ) ;
146
+ fs . accessSync ( path . resolve ( pkgNodeModulesDir ) , fs . constants . F_OK ) ;
118
147
} catch ( error ) {
119
148
return ;
120
149
}
121
150
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 ) ;
124
153
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 ] ) {
127
156
return ;
128
157
}
129
158
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 ) ;
132
161
133
162
try {
134
163
fs . mkdirSync ( path . dirname ( destModulePath ) , { recursive : true } ) ;
@@ -141,25 +170,32 @@ async function main() {
141
170
} ) ,
142
171
) ;
143
172
144
- const version = serverlessPackage . version ;
173
+ const version = serverlessPackageJson . version ;
145
174
const zipFilename = `sentry-node-serverless-${ version } .zip` ;
146
175
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.
147
182
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' ) ) ;
150
184
} catch ( error ) {
151
185
console . error ( error ) ;
152
186
}
153
187
188
+ // remove previously created layer zip
154
189
try {
155
- fs . unlinkSync ( path . resolve ( dist , zipFilename ) ) ;
190
+ fs . unlinkSync ( path . resolve ( layerBuildDir , zipFilename ) ) ;
156
191
} catch ( error ) {
157
192
// If the ZIP file hasn't been previously created (e.g. running this script for the first time),
158
193
// `unlinkSync` will try to delete a non-existing file. This error is ignored.
159
194
}
160
195
196
+ // create new layer zip
161
197
try {
162
- childProcess . execSync ( `zip -r ${ zipFilename } ${ destRootRelative } ` , { cwd : dist } ) ;
198
+ childProcess . execSync ( `zip -r ${ zipFilename } ${ destRootRelative } ` , { cwd : layerBuildDir } ) ;
163
199
} catch ( error ) {
164
200
// The child process timed out or had non-zero exit code.
165
201
// The error contains the entire result from `childProcess.spawnSync`.
0 commit comments