Skip to content

Commit d5ae88c

Browse files
Lms24AbhiPrasad
authored andcommitted
fix(serverless): Adjust v6 Lambda layer hotfix for v7 (#5006)
- ensure that the `s/dist/cjs` change is applied to the .npmignore files added and to build-awslambda-layer.js - add or adjusts .npmignore files in all Sentry packages that are dependencies of @sentry/serverless therefore, the unnecessary source, test or config files that previously bloated the lambda layer zip are no longer present - improve the build-awslambda-layer.js script by improving documentation and changing variable names to be more expressive
1 parent b411e38 commit d5ae88c

File tree

8 files changed

+138
-33
lines changed

8 files changed

+138
-33
lines changed

packages/core/.npmignore

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# The paths in this file are specified so that they align with the file structure in `./build` after this file is copied
2+
# into it by the prepack script `scripts/prepack.ts`.
3+
4+
*
5+
6+
!/cjs/**/*
7+
!/esm/**/*
8+
!/types/**/*
9+
10+
# These paths are necessary for Node AWS Lambda layer creation
11+
# This package is a (transitive) dependency of @sentry/serverless and thus it is pulled into
12+
# the lambda layer zip file.
13+
!/build/cjs/**/*
14+
!/build/esm/**/*
15+
!/build/types/**/*

packages/hub/.npmignore

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# The paths in this file are specified so that they align with the file structure in `./build` after this file is copied
2+
# into it by the prepack script `scripts/prepack.ts`.
3+
4+
*
5+
6+
!/cjs/**/*
7+
!/esm/**/*
8+
!/types/**/*
9+
10+
# These paths are necessary for Node AWS Lambda layer creation
11+
# This package is a (transitive) dependency of @sentry/serverless and thus it is pulled into
12+
# the lambda layer zip file.
13+
!/build/cjs/**/*
14+
!/build/esm/**/*
15+
!/build/types/**/*

packages/node/.npmignore

+7
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@
66
!/cjs/**/*
77
!/esm/**/*
88
!/types/**/*
9+
10+
# These paths are necessary for Node AWS Lambda creation
11+
# This package is a (transitive) dependency of @sentry/serverless and thus it is pulled into
12+
# the lambda layer zip file. By specifying these paths, we
13+
!/build/cjs/**/*
14+
!/build/esm/**/*
15+
!/build/types/**/*

packages/serverless/.npmignore

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# The paths in this file are specified so that they align with the file structure in `./build` after this file is copied
2+
# into it by the prepack script `scripts/prepack.ts`.
3+
4+
*
5+
6+
!/cjs/**/*
7+
!/esm/**/*
8+
!/types/**/*
9+
10+
# These paths are necessary for Node AWS Lambda layer creation
11+
!/build/cjs/**/*
12+
!/build/esm/**/*
13+
!/build/types/**/*

packages/serverless/scripts/build-awslambda-layer.js

+69-33
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const findUp = require('find-up');
88
const packList = require('npm-packlist');
99
const readPkg = require('read-pkg');
1010

11-
const serverlessPackage = require('../package.json');
11+
const serverlessPackageJson = require('../package.json');
1212

1313
if (!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+
*/
4047
async function collectPackages(cwd, packages = {}) {
4148
const packageJson = await readPkg({ cwd });
4249

@@ -68,67 +75,89 @@ async function collectPackages(cwd, packages = {}) {
6875
}
6976

7077
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.`);
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`.

packages/tracing/.npmignore

+7
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@
66
!/cjs/**/*
77
!/esm/**/*
88
!/types/**/*
9+
10+
# These paths are necessary for Node AWS Lambda layer creation
11+
# This package is a (transitive) dependency of @sentry/serverless and thus it is pulled into
12+
# the lambda layer zip file.
13+
!/build/npm/cjs/**/*
14+
!/build/npm/esm/**/*
15+
!/build/npm/types/**/*

packages/types/.npmignore

+5
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@
66
!/cjs/**/*
77
!/esm/**/*
88
!/types/**/*
9+
10+
# These paths are necessary for @sentry/serverless AWS Lambda Layer creation
11+
!/build/cjs/**/*
12+
!/build/esm/**/*
13+
!/build/types/**/*

packages/utils/.npmignore

+7
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@
66
!/cjs/**/*
77
!/esm/**/*
88
!/types/**/*
9+
10+
# These paths are necessary for Node AWS Lambda layer creation
11+
# This package is a (transitive) dependency of @sentry/serverless and thus it is pulled into
12+
# the lambda layer zip file.
13+
!/build/cjs/**/*
14+
!/build/esm/**/*
15+
!/build/types/**/*

0 commit comments

Comments
 (0)