lambda-live-debugger 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -324,6 +324,7 @@ If you have a new feature idea, please create and issue.
324
324
 
325
325
  (alphabetical)
326
326
 
327
+ - [Roger Chi](https://rogerchi.com/)
327
328
  - ⭐ Your name here for notable code or documentation contributions or sample projects submitted with a bug report that resulted in tool improvement.
328
329
 
329
330
  ## Disclaimer
Binary file
@@ -142,21 +142,64 @@ export class CdkFramework {
142
142
  */
143
143
  async getLambdasDataFromCdkByCompilingAndRunning(cdkConfigPath, config) {
144
144
  const entryFile = await this.getCdkEntryFile(cdkConfigPath);
145
- // Define a plugin to prepend custom code to .ts or .tsx files
145
+ let isESM = false;
146
+ const packageJsonPath = await findPackageJson(entryFile);
147
+ if (packageJsonPath) {
148
+ try {
149
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, { encoding: 'utf-8' }));
150
+ if (packageJson.type === 'module') {
151
+ isESM = true;
152
+ Logger.verbose(`[CDK] Using ESM format`);
153
+ }
154
+ }
155
+ catch (err) {
156
+ Logger.error(`Error reading CDK package.json (${packageJsonPath}): ${err.message}`, err);
157
+ }
158
+ }
159
+ const rootDir = process.cwd();
160
+ // Plugin that:
161
+ // - Fixes __dirname issues
162
+ // - Injects code to get the file path of the Lambda function and CDK hierarchy
146
163
  const injectCodePlugin = {
147
164
  name: 'injectCode',
148
165
  setup(build) {
149
166
  build.onLoad({ filter: /.*/ }, async (args) => {
150
- const absolutePath = path.resolve(args.path);
151
- let source = await fs.readFile(absolutePath, 'utf8');
167
+ // fix __dirname issues
168
+ const isWindows = /^win/.test(process.platform);
169
+ const esc = (p) => (isWindows ? p.replace(/\\/g, '/') : p);
170
+ const variables = `
171
+ const __fileloc = {
172
+ filename: "${esc(args.path)}",
173
+ dirname: "${esc(path.dirname(args.path))}",
174
+ relativefilename: "${esc(path.relative(rootDir, args.path))}",
175
+ relativedirname: "${esc(path.relative(rootDir, path.dirname(args.path)))}",
176
+ import: { meta: { url: "file://${esc(args.path)}" } }
177
+ };
178
+ `;
179
+ let fileContent = new TextDecoder().decode(await fs.readFile(args.path));
180
+ // remove shebang
181
+ if (fileContent.startsWith('#!')) {
182
+ const firstNewLine = fileContent.indexOf('\n');
183
+ fileContent = fileContent.slice(firstNewLine + 1);
184
+ }
185
+ let contents;
186
+ if (args.path.endsWith('.ts') || args.path.endsWith('.js')) {
187
+ // add the variables at the top of the file, that contains the file location
188
+ contents = `${variables}\n${fileContent}`;
189
+ }
190
+ else {
191
+ contents = fileContent;
192
+ }
193
+ const loader = args.path.split('.').pop();
194
+ // Inject code to get the file path of the Lambda function and CDK hierarchy
152
195
  if (args.path.includes('aws-cdk-lib/aws-lambda/lib/function.')) {
153
196
  const codeToFind = 'try{jsiiDeprecationWarnings().aws_cdk_lib_aws_lambda_FunctionProps(props)}';
154
- if (!source.includes(codeToFind)) {
197
+ if (!contents.includes(codeToFind)) {
155
198
  throw new Error(`Can not find code to inject in ${args.path}`);
156
199
  }
157
200
  // Inject code to get the file path of the Lambda function and CDK hierarchy
158
201
  // path to match it with the Lambda function. Store data in the global variable.
159
- source = source.replace(codeToFind, `;
202
+ contents = contents.replace(codeToFind, `;
160
203
  global.lambdas = global.lambdas ?? [];
161
204
 
162
205
  const lambdaInfo = {
@@ -180,36 +223,20 @@ export class CdkFramework {
180
223
  }
181
224
  if (args.path.includes('aws-cdk-lib/aws-s3-deployment/lib/bucket-deployment.')) {
182
225
  const codeToFind = 'super(scope,id),this.requestDestinationArn=!1;';
183
- if (!source.includes(codeToFind)) {
226
+ if (!contents.includes(codeToFind)) {
184
227
  throw new Error(`Can not find code to inject in ${args.path}`);
185
228
  }
186
229
  // Inject code to prevent deploying the assets
187
- source = source.replace(codeToFind, codeToFind + `return;`);
230
+ contents = contents.replace(codeToFind, codeToFind + `return;`);
188
231
  }
189
232
  return {
190
- contents: source,
191
- loader: 'default',
233
+ contents,
234
+ loader,
192
235
  };
193
236
  });
194
237
  },
195
238
  };
196
- let isESM = false;
197
- // get packgage.json
198
- const packageJsonPath = await findPackageJson(entryFile);
199
- if (packageJsonPath) {
200
- try {
201
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, { encoding: 'utf-8' }));
202
- if (packageJson.type === 'module') {
203
- isESM = true;
204
- Logger.verbose(`[CDK] Using ESM format`);
205
- }
206
- }
207
- catch (err) {
208
- Logger.error(`Error reading CDK package.json (${packageJsonPath}): ${err.message}`, err);
209
- }
210
- }
211
239
  const compileOutput = path.join(getProjectDirname(), outputFolder, `compiledCdk.${isESM ? 'mjs' : 'cjs'}`);
212
- const dirname = path.join(...[getProjectDirname(), config.subfolder, 'x'].filter((p) => p));
213
240
  try {
214
241
  // Build CDK code
215
242
  await esbuild.build({
@@ -228,7 +255,6 @@ export class CdkFramework {
228
255
  banner: {
229
256
  js: [
230
257
  `import { createRequire as topLevelCreateRequire } from 'module';`,
231
- `import.meta.url = 'file:///${dirname}/cdkFrameworkWorker.mjs';`,
232
258
  `global.require = global.require ?? topLevelCreateRequire(import.meta.url);`,
233
259
  `import { fileURLToPath as topLevelFileUrlToPath, URL as topLevelURL } from "url"`,
234
260
  `global.__dirname = global.__dirname ?? topLevelFileUrlToPath(new topLevelURL(".", import.meta.url))`,
@@ -238,10 +264,15 @@ export class CdkFramework {
238
264
  : {
239
265
  format: 'cjs',
240
266
  target: 'node18',
241
- banner: {
242
- js: [`__dirname = '${dirname}';`].join('\n'),
243
- },
244
267
  }),
268
+ define: {
269
+ // replace __dirname,... with the a variable that contains the file location
270
+ __filename: '__fileloc.filename',
271
+ __dirname: '__fileloc.dirname',
272
+ __relativefilename: '__fileloc.relativefilename',
273
+ __relativedirname: '__fileloc.relativedirname',
274
+ 'import.meta.url': '__fileloc.import.meta.url',
275
+ },
245
276
  });
246
277
  }
247
278
  catch (error) {
@@ -1,12 +1,5 @@
1
1
  // @ts-nocheck
2
- import { createRequire as topLevelCreateRequire } from 'module';
3
- const require = topLevelCreateRequire(import.meta.url);
4
- import path from 'path';
5
-
6
2
  import { workerData, parentPort } from 'node:worker_threads';
7
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
- import fs from 'fs/promises'; // do not delete this line
9
-
10
3
  import { Logger } from '../logger.mjs';
11
4
 
12
5
  Logger.setVerbose(workerData.verbose);
@@ -21,7 +14,6 @@ parentPort.on('message', async (data) => {
21
14
  Logger.verbose(`[Worker ${workerData.workerId}] Received message`, data);
22
15
 
23
16
  // execute code to get the data into global.lambdas
24
- await fixCdkPaths(workerData.awsCdkLibPath);
25
17
  await import(data.compileOutput);
26
18
 
27
19
  if (!global.lambdas || global.lambdas?.length === 0) {
@@ -43,7 +35,7 @@ parentPort.on('message', async (data) => {
43
35
  }));
44
36
 
45
37
  Logger.verbose(
46
- `[CDK] [Worker] Sending found lambdas`,
38
+ `[CDK] [Worker] Sending found Lambdas`,
47
39
  JSON.stringify(lambdas, null, 2),
48
40
  );
49
41
  parentPort.postMessage(lambdas);
@@ -53,91 +45,6 @@ parentPort.on('message', async (data) => {
53
45
  }
54
46
  });
55
47
 
56
- /**
57
- * Some paths are not resolved correctly in the CDK code, so we need to fix them
58
- */
59
- async function fixCdkPaths(awsCdkLibPath) {
60
- // leave this lines for manual debugging
61
- //const awsCdkLibPath = path.resolve("node_modules/aws-cdk-lib");
62
- //const path = require("path");
63
-
64
- Logger.verbose(`[CDK] [Worker] aws-cdk-lib PATH ${awsCdkLibPath}`);
65
-
66
- const pathsFix = {
67
- 'custom-resource-handlers/': `${awsCdkLibPath}/custom-resource-handlers/`,
68
- 'aws-custom-resource-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/custom-resources/aws-custom-resource-handler`,
69
- 'auto-delete-objects-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-s3/auto-delete-objects-handler`,
70
- 'notifications-resource-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-s3/notifications-resource-handler`,
71
- 'drop-spam-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-ses/drop-spam-handler`,
72
- 'aws-stepfunctions-tasks/role-policy-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-stepfunctions-tasks/role-policy-handler`,
73
- 'eval-nodejs-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-stepfunctions-tasks/eval-nodejs-handler`,
74
- 'cross-account-zone-delegation-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-route53/cross-account-zone-delegation-handler`,
75
- 'delete-existing-record-set-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-route53/delete-existing-record-set-handler`,
76
- 'aws-api-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-events-targets/aws-api-handler`,
77
- 'log-retention-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-logs/log-retention-handler`,
78
- 'cluster-resource-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-eks/cluster-resource-handler`,
79
- 'auto-delete-images-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-ecr/auto-delete-images-handler`,
80
- 'bucket-deployment-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-s3-deployment/bucket-deployment-handler`,
81
- 'nodejs-entrypoint-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/core/nodejs-entrypoint-handler`,
82
- 'restrict-default-security-group-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-ec2/restrict-default-security-group-handler`,
83
- 'dns-validated-certificate-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-certificatemanager/dns-validated-certificate-handler`,
84
- 'auto-delete-underlying-resources-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-synthetics/auto-delete-underlying-resources-handler`,
85
- 'replica-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-dynamodb/replica-handler`,
86
- 'oidc-handler': `${awsCdkLibPath}/custom-resource-handlers/dist/aws-iam/oidc-handler`,
87
- };
88
-
89
- // Create a proxy to intercept calls to the path module so we can fix paths
90
- const pathProxy = new Proxy(path, {
91
- get(target, prop) {
92
- if (typeof target[prop] === 'function') {
93
- return function (...args) {
94
- if (prop === 'resolve') {
95
- let resolvedPath = target[prop].apply(target, args);
96
-
97
- for (const [key, value] of Object.entries(pathsFix)) {
98
- if (resolvedPath.includes(key)) {
99
- // replace the beginning of the path with the value
100
- const i = resolvedPath.indexOf(key);
101
- const newResolvedPath = `${value}${resolvedPath.substring(i + key.length)}`;
102
- Logger.verbose(
103
- `[CDK] [Worker] Fixing path ${resolvedPath} -> ${newResolvedPath}`,
104
- );
105
- resolvedPath = newResolvedPath;
106
- }
107
- }
108
-
109
- return resolvedPath;
110
- }
111
- if (prop === 'join') {
112
- let resolvedPath = target[prop].apply(target, args);
113
-
114
- for (const [key, value] of Object.entries(pathsFix)) {
115
- if (resolvedPath.includes(key)) {
116
- // replace the beginning of the path with the value
117
- const i = resolvedPath.indexOf(key);
118
- const newResolvedPath = `${value}${resolvedPath.substring(i + key.length)}`;
119
- Logger.verbose(
120
- `[CDK] [Worker] Fixing path ${resolvedPath} -> ${newResolvedPath}`,
121
- );
122
- resolvedPath = newResolvedPath;
123
- }
124
- }
125
-
126
- return resolvedPath;
127
- }
128
- return target[prop].apply(target, args);
129
- };
130
- }
131
- return target[prop];
132
- },
133
- });
134
-
135
- // Override the path module in the require cache
136
- require.cache[require.resolve('path')] = {
137
- exports: pathProxy,
138
- };
139
- }
140
-
141
48
  process.on('unhandledRejection', (error) => {
142
49
  Logger.error(`[CDK] [Worker] Unhandled Rejection`, error);
143
50
  });
@@ -22,23 +22,31 @@ async function runInWorker(input) {
22
22
  environment: input.environment,
23
23
  verbose: Configuration.config.verbose,
24
24
  });
25
+ worker.used = false;
26
+ worker.toKill = false;
25
27
  }
26
28
  else {
27
29
  Logger.verbose(`[Function ${input.fuctionRequest.functionId}] [Worker ${input.fuctionRequest.workerId}] Reusing worker`);
28
30
  }
29
- worker.on('message', (msg) => {
31
+ worker.onMessage = (msg) => {
30
32
  Logger.verbose(`[Function ${input.fuctionRequest.functionId}] [Worker ${input.fuctionRequest.workerId}] Worker message`, JSON.stringify(msg));
33
+ worker.used = false;
31
34
  if (msg?.errorType) {
32
35
  reject(msg);
33
36
  }
34
37
  else {
35
38
  resolve(msg);
36
39
  }
37
- });
38
- worker.on('error', (err) => {
40
+ if (worker.toKill) {
41
+ worker.toKill = false;
42
+ void worker.terminate();
43
+ }
44
+ };
45
+ worker.onError = (err) => {
39
46
  Logger.error(`[Function ${input.fuctionRequest.functionId}] [Worker ${input.fuctionRequest.workerId}] Error`, err);
40
47
  reject(err);
41
- });
48
+ };
49
+ worker.used = true;
42
50
  worker.postMessage({
43
51
  env: input.fuctionRequest.env,
44
52
  event: input.fuctionRequest.event,
@@ -78,6 +86,12 @@ function startWorker(input) {
78
86
  workers.delete(input.workerId);
79
87
  });
80
88
  workers.set(input.workerId, worker);
89
+ worker.on('message', (msg) => {
90
+ worker?.onMessage?.(msg);
91
+ });
92
+ worker.on('error', (err) => {
93
+ worker?.onError?.(err);
94
+ });
81
95
  return worker;
82
96
  }
83
97
  /**
@@ -87,7 +101,19 @@ async function stopAllWorkers() {
87
101
  Logger.verbose('Stopping all workers');
88
102
  const promises = [];
89
103
  for (const worker of workers.values()) {
90
- promises.push(worker.terminate());
104
+ if (worker.used) {
105
+ worker.toKill = true;
106
+ // set timout for 5 minutes and kill the worker if it is still running
107
+ setTimeout(() => {
108
+ if (worker.toKill) {
109
+ worker.toKill = false;
110
+ void worker.terminate();
111
+ }
112
+ }, 300000);
113
+ }
114
+ else {
115
+ promises.push(worker.terminate());
116
+ }
91
117
  }
92
118
  workers.clear();
93
119
  await Promise.all(promises);
@@ -13,16 +13,16 @@ Logger.verbose(
13
13
 
14
14
  parentPort.on('message', async (data) => {
15
15
  Logger.verbose(`[Worker ${workerData.workerId}] Received message`, data);
16
- const mod = await import(workerData.artifactFile);
17
- const fn = mod[workerData.handler];
16
+ try {
17
+ const mod = await import(workerData.artifactFile);
18
+ const fn = mod[workerData.handler];
18
19
 
19
- if (!fn) {
20
- throw new Error(
21
- `Handler '${workerData.handler}' not found for function '${workerData.functionId}'`,
22
- );
23
- }
20
+ if (!fn) {
21
+ throw new Error(
22
+ `Handler '${workerData.handler}' not found for function '${workerData.functionId}'`,
23
+ );
24
+ }
24
25
 
25
- try {
26
26
  const context = {
27
27
  ...data.context,
28
28
  getRemainingTimeInMillis: () => 2147483647, // Max 32-bit signed integer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambda-live-debugger",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "Debug Lambda functions locally like it is running in the cloud",
6
6
  "repository": {