@bifravst/aws-cdk-lambda-helpers 1.10.59 → 2.0.1

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.
@@ -3,5 +3,5 @@ import { Construct } from 'constructs';
3
3
  import type { PackedLambda } from './packLambda.js';
4
4
  export declare class LambdaSource extends Construct {
5
5
  readonly code: Lambda.S3Code;
6
- constructor(parent: Construct, packedLambda: Pick<PackedLambda, 'zipFile' | 'id' | 'hash'>);
6
+ constructor(parent: Construct, packedLambda: Pick<PackedLambda, 'zipFilePath' | 'id' | 'hash'>);
7
7
  }
@@ -5,7 +5,7 @@ export class LambdaSource extends Construct {
5
5
  constructor(parent, packedLambda) {
6
6
  super(parent, `${packedLambda.id}Source`);
7
7
  const asset = new S3Assets.Asset(this, 'asset', {
8
- path: packedLambda.zipFile,
8
+ path: packedLambda.zipFilePath,
9
9
  assetHash: packedLambda.hash,
10
10
  assetHashType: AssetHashType.CUSTOM,
11
11
  });
@@ -14,12 +14,12 @@ export const checkSumOfStrings = (strings) => {
14
14
  return hash.digest('hex');
15
15
  };
16
16
  const hashCache = {};
17
- const hashFile = async (file) => {
18
- if (hashCache[file] === undefined) {
19
- hashCache[file] = await new Promise((resolve) => {
17
+ const hashFile = async (filePath) => {
18
+ if (hashCache[filePath] === undefined) {
19
+ hashCache[filePath] = await new Promise((resolve) => {
20
20
  const hash = crypto.createHash('sha1');
21
21
  hash.setEncoding('hex');
22
- const fileStream = fs.createReadStream(file);
22
+ const fileStream = fs.createReadStream(filePath);
23
23
  fileStream.pipe(hash, { end: false });
24
24
  fileStream.on('end', () => {
25
25
  hash.end();
@@ -28,7 +28,7 @@ const hashFile = async (file) => {
28
28
  });
29
29
  });
30
30
  }
31
- return hashCache[file];
31
+ return hashCache[filePath];
32
32
  };
33
33
  /**
34
34
  * Computes the checksum for the given files
@@ -1,4 +1,9 @@
1
1
  /**
2
2
  * Resolve project-level dependencies for the given file using TypeScript compiler API
3
3
  */
4
- export declare const findDependencies: (sourceFile: string, imports?: string[], visited?: string[]) => string[];
4
+ export declare const findDependencies: ({ sourceFilePath, tsConfigFilePath, imports: importsArg, visited: visitedArg, }: {
5
+ sourceFilePath: string;
6
+ tsConfigFilePath?: string;
7
+ imports?: string[];
8
+ visited?: string[];
9
+ }) => string[];
@@ -4,25 +4,26 @@ import ts, {} from 'typescript';
4
4
  /**
5
5
  * Resolve project-level dependencies for the given file using TypeScript compiler API
6
6
  */
7
- export const findDependencies = (sourceFile, imports = [], visited = []) => {
8
- if (visited.includes(sourceFile))
7
+ export const findDependencies = ({ sourceFilePath, tsConfigFilePath, imports: importsArg, visited: visitedArg, }) => {
8
+ const visited = visitedArg ?? [];
9
+ const imports = importsArg ?? [];
10
+ if (visited.includes(sourceFilePath))
9
11
  return imports;
10
- const fileNode = ts.createSourceFile(sourceFile, readFileSync(sourceFile, 'utf-8').toString(), ts.ScriptTarget.ES2022,
12
+ const tsConfig = tsConfigFilePath !== undefined
13
+ ? JSON.parse(readFileSync(tsConfigFilePath, 'utf-8').toString())
14
+ : undefined;
15
+ const fileNode = ts.createSourceFile(sourceFilePath, readFileSync(sourceFilePath, 'utf-8').toString(), ts.ScriptTarget.ES2022,
11
16
  /*setParentNodes */ true);
12
17
  const parseChild = (node) => {
13
18
  if (node.kind !== ts.SyntaxKind.ImportDeclaration)
14
19
  return;
15
20
  const moduleSpecifier = node.moduleSpecifier.text;
16
- const file = moduleSpecifier.startsWith('.')
17
- ? path
18
- .resolve(path.parse(sourceFile).dir, moduleSpecifier)
19
- // In ECMA Script modules, all imports from local files must have an extension.
20
- // See https://nodejs.org/api/esm.html#mandatory-file-extensions
21
- // So we need to replace the `.js` in the import specification to find the TypeScript source for the file.
22
- // Example: import { Network, notifyClients } from './notifyClients.js'
23
- // The source file for that is actually in './notifyClients.ts'
24
- .replace(/\.js$/, '.ts')
25
- : moduleSpecifier;
21
+ const file = resolve({
22
+ moduleSpecifier,
23
+ sourceFilePath,
24
+ tsConfigFilePath,
25
+ tsConfig,
26
+ });
26
27
  try {
27
28
  const s = statSync(file);
28
29
  if (!s.isDirectory())
@@ -34,9 +35,49 @@ export const findDependencies = (sourceFile, imports = [], visited = []) => {
34
35
  }
35
36
  };
36
37
  ts.forEachChild(fileNode, parseChild);
37
- visited.push(sourceFile);
38
+ visited.push(sourceFilePath);
38
39
  for (const file of imports) {
39
- findDependencies(file, imports, visited);
40
+ findDependencies({
41
+ sourceFilePath: file,
42
+ imports,
43
+ visited,
44
+ tsConfigFilePath,
45
+ });
40
46
  }
41
47
  return imports;
42
48
  };
49
+ const resolve = ({ moduleSpecifier, sourceFilePath, tsConfigFilePath, tsConfig, }) => {
50
+ if (moduleSpecifier.startsWith('.'))
51
+ return (path
52
+ .resolve(path.parse(sourceFilePath).dir, moduleSpecifier)
53
+ // In ECMA Script modules, all imports from local files must have an extension.
54
+ // See https://nodejs.org/api/esm.html#mandatory-file-extensions
55
+ // So we need to replace the `.js` in the import specification to find the TypeScript source for the file.
56
+ // Example: import { Network, notifyClients } from './notifyClients.js'
57
+ // The source file for that is actually in './notifyClients.ts'
58
+ .replace(/\.js$/, '.ts'));
59
+ if (tsConfigFilePath !== undefined &&
60
+ tsConfig?.compilerOptions?.paths !== undefined) {
61
+ for (const [key, value] of Object.entries(tsConfig.compilerOptions.paths)) {
62
+ const [resolvedPath] = value;
63
+ if (resolvedPath === undefined)
64
+ continue;
65
+ // Exact match
66
+ if (moduleSpecifier === key) {
67
+ return path.join(path.parse(tsConfigFilePath).dir, tsConfig.compilerOptions.baseUrl, resolvedPath);
68
+ }
69
+ // Wildcard match
70
+ if (!key.includes('*'))
71
+ continue;
72
+ const rx = new RegExp(`^${key.replace('*', '(?<wildcard>.*)')}`);
73
+ const maybeMatch = rx.exec(moduleSpecifier);
74
+ if (maybeMatch?.groups?.wildcard === undefined)
75
+ continue;
76
+ return (path
77
+ .resolve(path.parse(tsConfigFilePath).dir, tsConfig.compilerOptions.baseUrl, resolvedPath.replace('*', maybeMatch.groups.wildcard))
78
+ // Same as above, replace `.js` with `.ts`
79
+ .replace(/\.js$/, '.ts'));
80
+ }
81
+ }
82
+ return moduleSpecifier;
83
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import assert from 'node:assert/strict';
2
+ import path from 'node:path';
3
+ import { describe, it } from 'node:test';
4
+ import { URL } from 'node:url';
5
+ import { findDependencies } from './findDependencies.js';
6
+ const __dirname = new URL('.', import.meta.url).pathname;
7
+ void describe('findDependencies()', () => {
8
+ void it('should honor tsconfig.json paths', () => {
9
+ const dependencies = findDependencies({
10
+ sourceFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'lambda.ts'),
11
+ tsConfigFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'tsconfig.json'),
12
+ });
13
+ assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', 'index.ts')), true, 'Should include the index.ts file');
14
+ assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', '2.ts')), true, 'Should include the module file');
15
+ });
16
+ });
@@ -9,11 +9,11 @@ const tmpDir = os.tmpdir();
9
9
  void describe('packLambda()', () => {
10
10
  // See https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/issues/93#issuecomment-2042201321
11
11
  void it('should fail if it imports from a folder on the same level that has the same name as the handler module', async () => assert.rejects(async () => packLambda({
12
- sourceFile: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'module-folder-named-like-handler-bug', 'same-level', 'acme.ts'),
13
- zipFile: path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'acme.zip'),
12
+ sourceFilePath: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'module-folder-named-like-handler-bug', 'same-level', 'acme.ts'),
13
+ zipFilePath: path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'acme.zip'),
14
14
  }), ImportFromFolderNameError));
15
15
  void it('should not fail if it a folder with the same name is on a different level', async () => assert.doesNotReject(async () => packLambda({
16
- sourceFile: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'module-folder-named-like-handler-bug', 'different-level', 'lambda', 'acme.ts'),
17
- zipFile: path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'acme.zip'),
16
+ sourceFilePath: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'module-folder-named-like-handler-bug', 'different-level', 'lambda', 'acme.ts'),
17
+ zipFilePath: path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'acme.zip'),
18
18
  })));
19
19
  });
@@ -1,15 +1,19 @@
1
1
  export type PackedLambda = {
2
2
  id: string;
3
- zipFile: string;
3
+ zipFilePath: string;
4
4
  handler: string;
5
5
  hash: string;
6
6
  };
7
7
  /**
8
8
  * In the bundle we only include code that's not in the layer.
9
9
  */
10
- export declare const packLambda: ({ sourceFile, zipFile, debug, progress, }: {
11
- sourceFile: string;
12
- zipFile: string;
10
+ export declare const packLambda: ({ sourceFilePath, zipFilePath, tsConfigFilePath, debug, progress, }: {
11
+ sourceFilePath: string;
12
+ zipFilePath: string;
13
+ /**
14
+ * Pass the path to the tsconfig.json file if you want to use paths from the tsconfig.json file.
15
+ */
16
+ tsConfigFilePath?: string;
13
17
  debug?: (label: string, info: string) => void;
14
18
  progress?: (label: string, info: string) => void;
15
19
  }) => Promise<{
@@ -7,8 +7,8 @@ import yazl from 'yazl';
7
7
  import { checkSumOfFiles } from './checksumOfFiles.js';
8
8
  import { commonParent } from './commonParent.js';
9
9
  import { findDependencies } from './findDependencies.js';
10
- const removeCommonAncestor = (parentDir) => (file) => {
11
- const p = parse(file);
10
+ const removeCommonAncestor = (parentDir) => (filePath) => {
11
+ const p = parse(filePath);
12
12
  const jsFileName = [
13
13
  p.dir.replace(parentDir.slice(0, parentDir.length - 1), ''),
14
14
  `${p.name}.js`,
@@ -21,12 +21,15 @@ const removeCommonAncestor = (parentDir) => (file) => {
21
21
  /**
22
22
  * In the bundle we only include code that's not in the layer.
23
23
  */
24
- export const packLambda = async ({ sourceFile, zipFile, debug, progress, }) => {
25
- const deps = findDependencies(sourceFile);
26
- const lambdaFiles = [sourceFile, ...deps];
24
+ export const packLambda = async ({ sourceFilePath, zipFilePath, tsConfigFilePath, debug, progress, }) => {
25
+ const deps = findDependencies({
26
+ sourceFilePath,
27
+ tsConfigFilePath,
28
+ });
29
+ const lambdaFiles = [sourceFilePath, ...deps];
27
30
  const zipfile = new yazl.ZipFile();
28
31
  const stripCommon = removeCommonAncestor(commonParent(lambdaFiles));
29
- const handler = stripCommon(sourceFile);
32
+ const handler = stripCommon(sourceFilePath);
30
33
  // Make sure that the handler does not import from a folder with the same name in the folder
31
34
  const handlerInfo = path.parse(handler);
32
35
  const handlerName = handlerInfo.name;
@@ -61,13 +64,15 @@ export const packLambda = async ({ sourceFile, zipFile, debug, progress, }) => {
61
64
  }), 'utf-8'), 'package.json');
62
65
  progress?.(`added`, 'package.json');
63
66
  await new Promise((resolve) => {
64
- zipfile.outputStream.pipe(createWriteStream(zipFile)).on('close', () => {
67
+ zipfile.outputStream
68
+ .pipe(createWriteStream(zipFilePath))
69
+ .on('close', () => {
65
70
  resolve();
66
71
  });
67
72
  zipfile.end();
68
73
  });
69
- progress?.(`written`, zipFile);
70
- return { handler: stripCommon(sourceFile), hash };
74
+ progress?.(`written`, zipFilePath);
75
+ return { handler: stripCommon(sourceFilePath), hash };
71
76
  };
72
77
  /**
73
78
  * @see https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/issues/93#issuecomment-2042201321
@@ -1,2 +1,18 @@
1
1
  import { type PackedLambda } from './packLambda.js';
2
- export declare const packLambdaFromPath: (id: string, sourceFile: string, handlerFunction?: string, baseDir?: string, distDir?: string) => Promise<PackedLambda>;
2
+ export declare const packLambdaFromPath: ({ id, sourceFilePath, handlerFunction: handlerFunctionArg, baseDir: baseDirArg, distDir: distDirArg, tsConfigFilePath, }: {
3
+ id: string;
4
+ sourceFilePath: string;
5
+ handlerFunction?: string;
6
+ /**
7
+ * @default process.cwd()
8
+ */
9
+ baseDir?: string;
10
+ /**
11
+ * @default ${baseDir}/dist/lambdas
12
+ */
13
+ distDir?: string;
14
+ /**
15
+ * Pass the path to the tsconfig.json file if you want to use paths from the tsconfig.json file.
16
+ */
17
+ tsConfigFilePath?: string;
18
+ }) => Promise<PackedLambda>;
@@ -1,15 +1,10 @@
1
1
  import { mkdir } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { packLambda } from './packLambda.js';
4
- export const packLambdaFromPath = async (id, sourceFile, handlerFunction = 'handler',
5
- /**
6
- * @default process.cwd()
7
- */
8
- baseDir = process.cwd(),
9
- /**
10
- * @default ${baseDir}/dist/lambdas
11
- */
12
- distDir = path.join(process.cwd(), 'dist', 'lambdas')) => {
4
+ export const packLambdaFromPath = async ({ id, sourceFilePath, handlerFunction: handlerFunctionArg, baseDir: baseDirArg, distDir: distDirArg, tsConfigFilePath, }) => {
5
+ const distDir = distDirArg ?? path.join(process.cwd(), 'dist', 'lambdas');
6
+ const baseDir = baseDirArg ?? process.cwd();
7
+ const handlerFunction = handlerFunctionArg ?? 'handler';
13
8
  try {
14
9
  await mkdir(distDir, {
15
10
  recursive: true,
@@ -20,12 +15,13 @@ distDir = path.join(process.cwd(), 'dist', 'lambdas')) => {
20
15
  }
21
16
  const zipFile = path.join(distDir, `${id}.zip`);
22
17
  const { handler, hash } = await packLambda({
23
- sourceFile: path.join(baseDir, sourceFile),
24
- zipFile,
18
+ sourceFilePath: path.join(baseDir, sourceFilePath),
19
+ zipFilePath: zipFile,
20
+ tsConfigFilePath,
25
21
  });
26
22
  return {
27
23
  id,
28
- zipFile,
24
+ zipFilePath: zipFile,
29
25
  handler: handler.replace('.js', `.${handlerFunction}`),
30
26
  hash,
31
27
  };
@@ -1,5 +1,5 @@
1
1
  export type PackedLayer = {
2
- layerZipFile: string;
2
+ layerZipFilePath: string;
3
3
  hash: string;
4
4
  };
5
5
  export declare const packLayer: ({ id, dependencies, baseDir, distDir, installCommand, }: {
@@ -17,7 +17,7 @@ export declare const packLayer: ({ id, dependencies, baseDir, distDir, installCo
17
17
  * Returns the command to run, the first element is the command (e.g. `npm`) and the rest are its arguments.
18
18
  */
19
19
  installCommand?: (args: {
20
- packageFile: string;
21
- packageLockFile: string;
20
+ packageFilePath: string;
21
+ packageLockFilePath: string;
22
22
  }) => [string, ...Array<string>];
23
23
  }) => Promise<PackedLayer>;
@@ -53,8 +53,8 @@ export const packLayer = async ({ id, dependencies, baseDir, distDir, installCom
53
53
  }
54
54
  await new Promise((resolve, reject) => {
55
55
  const [cmd, ...args] = installCommand?.({
56
- packageFile: packageJSON,
57
- packageLockFile: packageLockJsonFile,
56
+ packageFilePath: packageJSON,
57
+ packageLockFilePath: packageLockJsonFile,
58
58
  }) ?? [
59
59
  'npm',
60
60
  hasLockFile ? 'ci' : 'i',
@@ -91,7 +91,7 @@ export const packLayer = async ({ id, dependencies, baseDir, distDir, installCom
91
91
  zipfile.end();
92
92
  });
93
93
  return {
94
- layerZipFile: zipFileName,
94
+ layerZipFilePath: zipFileName,
95
95
  hash: checkSumOfStrings([
96
96
  JSON.stringify(dependencies),
97
97
  await checkSumOfFiles(checkSumFiles),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bifravst/aws-cdk-lambda-helpers",
3
- "version": "1.10.59",
3
+ "version": "2.0.1",
4
4
  "description": "Helper functions which simplify working with TypeScript lambdas for AWS CDK.",
5
5
  "exports": {
6
6
  ".": {
@@ -110,7 +110,7 @@
110
110
  "@swc/core": "1.9.2",
111
111
  "glob": "11.0.0",
112
112
  "typescript": "5.6.3",
113
- "yazl": "2.5.1"
113
+ "yazl": "3.3.0"
114
114
  },
115
115
  "peerDependencies": {
116
116
  "@bifravst/aws-ssm-settings-helpers": "^1.2.61",
@@ -1 +0,0 @@
1
- export declare const hello: () => string;
@@ -1 +0,0 @@
1
- export const hello = () => 'Hello World!';
@@ -1 +0,0 @@
1
- export declare const handler: () => string;
@@ -1,2 +0,0 @@
1
- import { hello } from '../acme/lib.js';
2
- export const handler = () => hello();
@@ -1 +0,0 @@
1
- export declare const hello: () => string;
@@ -1 +0,0 @@
1
- export const hello = () => 'Hello World!';
@@ -1 +0,0 @@
1
- export declare const handler: () => string;
@@ -1,2 +0,0 @@
1
- import { hello } from './acme/lib.js';
2
- export const handler = () => hello();