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

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.
@@ -1,9 +1,22 @@
1
1
  /**
2
2
  * Resolve project-level dependencies for the given file using TypeScript compiler API
3
3
  */
4
- export declare const findDependencies: ({ sourceFilePath, tsConfigFilePath, imports: importsArg, visited: visitedArg, }: {
4
+ export declare const findDependencies: (args: {
5
5
  sourceFilePath: string;
6
- tsConfigFilePath?: string;
7
6
  imports?: string[];
8
7
  visited?: string[];
9
- }) => string[];
8
+ packages?: Set<string>;
9
+ tsConfigFilePath?: string;
10
+ importsSubpathPatterns?: Record<string, string>;
11
+ }) => {
12
+ dependencies: string[];
13
+ /**
14
+ * A map of import subpath patterns to their resolved paths
15
+ * @see https://nodejs.org/api/packages.html#subpath-patterns
16
+ */
17
+ importsSubpathPatterns: Record<string, string>;
18
+ /**
19
+ * The external packages that the source file depends on
20
+ */
21
+ packages: Set<string>;
22
+ };
@@ -4,58 +4,71 @@ 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 = ({ sourceFilePath, tsConfigFilePath, imports: importsArg, visited: visitedArg, }) => {
8
- const visited = visitedArg ?? [];
9
- const imports = importsArg ?? [];
7
+ export const findDependencies = (args) => {
8
+ const sourceFilePath = args.sourceFilePath;
9
+ const visited = args.visited ?? [];
10
+ const dependencies = args.imports ?? [];
11
+ const packages = args.packages ?? new Set();
12
+ let importsSubpathPatterns = args.importsSubpathPatterns ?? {};
10
13
  if (visited.includes(sourceFilePath))
11
- return imports;
14
+ return { dependencies, importsSubpathPatterns, packages };
15
+ const tsConfigFilePath = args.tsConfigFilePath;
12
16
  const tsConfig = tsConfigFilePath !== undefined
13
17
  ? JSON.parse(readFileSync(tsConfigFilePath, 'utf-8').toString())
14
18
  : undefined;
15
19
  const fileNode = ts.createSourceFile(sourceFilePath, readFileSync(sourceFilePath, 'utf-8').toString(), ts.ScriptTarget.ES2022,
16
20
  /*setParentNodes */ true);
17
21
  const parseChild = (node) => {
18
- if (node.kind !== ts.SyntaxKind.ImportDeclaration)
22
+ if (node.kind !== ts.SyntaxKind.ImportDeclaration &&
23
+ node.kind !== ts.SyntaxKind.ExportDeclaration)
19
24
  return;
20
25
  const moduleSpecifier = node.moduleSpecifier.text;
21
- const file = resolve({
26
+ const { resolvedPath: file, importsSubpathPatterns: updatedImportsSubpathPatterns, } = resolve({
22
27
  moduleSpecifier,
23
28
  sourceFilePath,
24
29
  tsConfigFilePath,
25
30
  tsConfig,
31
+ importsSubpathPatterns,
26
32
  });
33
+ importsSubpathPatterns = updatedImportsSubpathPatterns;
27
34
  try {
28
35
  const s = statSync(file);
29
36
  if (!s.isDirectory())
30
- imports.push(file);
37
+ dependencies.push(file);
31
38
  }
32
39
  catch {
33
40
  // Module or file not found
34
41
  visited.push(file);
42
+ packages.add(moduleSpecifier);
35
43
  }
36
44
  };
37
45
  ts.forEachChild(fileNode, parseChild);
38
46
  visited.push(sourceFilePath);
39
- for (const file of imports) {
47
+ for (const file of dependencies) {
40
48
  findDependencies({
41
49
  sourceFilePath: file,
42
- imports,
50
+ imports: dependencies,
43
51
  visited,
44
52
  tsConfigFilePath,
53
+ importsSubpathPatterns,
54
+ packages,
45
55
  });
46
56
  }
47
- return imports;
57
+ return { dependencies, importsSubpathPatterns, packages };
48
58
  };
49
- const resolve = ({ moduleSpecifier, sourceFilePath, tsConfigFilePath, tsConfig, }) => {
59
+ const resolve = ({ moduleSpecifier, sourceFilePath, tsConfigFilePath, tsConfig, importsSubpathPatterns, }) => {
50
60
  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'));
61
+ return {
62
+ resolvedPath: path
63
+ .resolve(path.parse(sourceFilePath).dir, moduleSpecifier)
64
+ // In ECMA Script modules, all imports from local files must have an extension.
65
+ // See https://nodejs.org/api/esm.html#mandatory-file-extensions
66
+ // So we need to replace the `.js` in the import specification to find the TypeScript source for the file.
67
+ // Example: import { Network, notifyClients } from './notifyClients.js'
68
+ // The source file for that is actually in './notifyClients.ts'
69
+ .replace(/\.js$/, '.ts'),
70
+ importsSubpathPatterns,
71
+ };
59
72
  if (tsConfigFilePath !== undefined &&
60
73
  tsConfig?.compilerOptions?.paths !== undefined) {
61
74
  for (const [key, value] of Object.entries(tsConfig.compilerOptions.paths)) {
@@ -64,7 +77,18 @@ const resolve = ({ moduleSpecifier, sourceFilePath, tsConfigFilePath, tsConfig,
64
77
  continue;
65
78
  // Exact match
66
79
  if (moduleSpecifier === key) {
67
- return path.join(path.parse(tsConfigFilePath).dir, tsConfig.compilerOptions.baseUrl, resolvedPath);
80
+ const fullResolvedPath = path.join(path.parse(tsConfigFilePath).dir, tsConfig.compilerOptions.baseUrl, resolvedPath);
81
+ return {
82
+ resolvedPath: fullResolvedPath,
83
+ importsSubpathPatterns: {
84
+ ...importsSubpathPatterns,
85
+ [key]: [
86
+ tsConfig.compilerOptions.baseUrl,
87
+ path.sep,
88
+ resolvedPath.replace(/\.ts$/, '.js'),
89
+ ].join(''),
90
+ },
91
+ };
68
92
  }
69
93
  // Wildcard match
70
94
  if (!key.includes('*'))
@@ -73,11 +97,24 @@ const resolve = ({ moduleSpecifier, sourceFilePath, tsConfigFilePath, tsConfig,
73
97
  const maybeMatch = rx.exec(moduleSpecifier);
74
98
  if (maybeMatch?.groups?.wildcard === undefined)
75
99
  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'));
100
+ return {
101
+ resolvedPath: path
102
+ .resolve(path.parse(tsConfigFilePath).dir, tsConfig.compilerOptions.baseUrl, resolvedPath.replace('*', maybeMatch.groups.wildcard))
103
+ // Same as above, replace `.js` with `.ts`
104
+ .replace(/\.js$/, '.ts'),
105
+ importsSubpathPatterns: {
106
+ ...importsSubpathPatterns,
107
+ [key]: [
108
+ tsConfig.compilerOptions.baseUrl,
109
+ path.sep,
110
+ resolvedPath.replace(/\.ts$/, '.js'),
111
+ ].join(''),
112
+ },
113
+ };
80
114
  }
81
115
  }
82
- return moduleSpecifier;
116
+ return {
117
+ resolvedPath: moduleSpecifier,
118
+ importsSubpathPatterns,
119
+ };
83
120
  };
@@ -5,12 +5,30 @@ import { URL } from 'node:url';
5
5
  import { findDependencies } from './findDependencies.js';
6
6
  const __dirname = new URL('.', import.meta.url).pathname;
7
7
  void describe('findDependencies()', () => {
8
+ void it('should return a list of external dependencies', () => {
9
+ const { packages } = findDependencies({
10
+ sourceFilePath: path.join(__dirname, '..', 'cdk', 'lambda.ts'),
11
+ });
12
+ assert.equal(packages.has('aws-lambda'), true, "Should include the 'aws-lambda' package");
13
+ assert.equal(packages.has('id128'), true, "Should include the 'id128' package");
14
+ });
8
15
  void it('should honor tsconfig.json paths', () => {
9
- const dependencies = findDependencies({
16
+ const { dependencies } = findDependencies({
10
17
  sourceFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'lambda.ts'),
11
18
  tsConfigFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'tsconfig.json'),
12
19
  });
13
20
  assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', 'index.ts')), true, 'Should include the index.ts file');
21
+ assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', '1.ts')), true, 'Should include the module referenced in the index.ts file');
14
22
  assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', '2.ts')), true, 'Should include the module file');
15
23
  });
24
+ void it('should return an import map', () => {
25
+ const { importsSubpathPatterns } = findDependencies({
26
+ sourceFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'lambda.ts'),
27
+ tsConfigFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'tsconfig.json'),
28
+ });
29
+ assert.deepEqual(importsSubpathPatterns, {
30
+ '#foo': './foo/index.js',
31
+ '#foo/*': './foo/*',
32
+ });
33
+ });
16
34
  });
@@ -22,10 +22,11 @@ const removeCommonAncestor = (parentDir) => (filePath) => {
22
22
  * In the bundle we only include code that's not in the layer.
23
23
  */
24
24
  export const packLambda = async ({ sourceFilePath, zipFilePath, tsConfigFilePath, debug, progress, }) => {
25
- const deps = findDependencies({
25
+ const { dependencies: deps, importsSubpathPatterns, packages, } = findDependencies({
26
26
  sourceFilePath,
27
27
  tsConfigFilePath,
28
28
  });
29
+ debug?.(`dependencies`, [...packages].join(', '));
29
30
  const lambdaFiles = [sourceFilePath, ...deps];
30
31
  const zipfile = new yazl.ZipFile();
31
32
  const stripCommon = removeCommonAncestor(commonParent(lambdaFiles));
@@ -61,6 +62,8 @@ export const packLambda = async ({ sourceFilePath, zipFilePath, tsConfigFilePath
61
62
  // Mark it as ES module
62
63
  zipfile.addBuffer(Buffer.from(JSON.stringify({
63
64
  type: 'module',
65
+ imports: importsSubpathPatterns,
66
+ dependencies: Object.fromEntries(packages.values().map((pkg) => [pkg, '*'])),
64
67
  }), 'utf-8'), 'package.json');
65
68
  progress?.(`added`, 'package.json');
66
69
  await new Promise((resolve) => {
@@ -0,0 +1 @@
1
+ export declare const foo: () => number;
@@ -0,0 +1 @@
1
+ export const foo = () => 42;
@@ -0,0 +1 @@
1
+ export declare const foo2: () => number;
@@ -0,0 +1 @@
1
+ export const foo2 = () => 17;
@@ -0,0 +1 @@
1
+ export { foo } from './1.js';
@@ -0,0 +1 @@
1
+ export { foo } from './1.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bifravst/aws-cdk-lambda-helpers",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "Helper functions which simplify working with TypeScript lambdas for AWS CDK.",
5
5
  "exports": {
6
6
  ".": {
@@ -51,7 +51,7 @@
51
51
  "author": "Nordic Semiconductor ASA | nordicsemi.no",
52
52
  "license": "BSD-3-Clause",
53
53
  "devDependencies": {
54
- "@aws-sdk/client-cloudformation": "3.691.0",
54
+ "@aws-sdk/client-cloudformation": "3.693.0",
55
55
  "@bifravst/cloudformation-helpers": "9.1.1",
56
56
  "@bifravst/eslint-config-typescript": "6.1.18",
57
57
  "@bifravst/from-env": "3.0.2",
@@ -113,7 +113,7 @@
113
113
  "yazl": "3.3.0"
114
114
  },
115
115
  "peerDependencies": {
116
- "@bifravst/aws-ssm-settings-helpers": "^1.2.61",
116
+ "@bifravst/aws-ssm-settings-helpers": "^1.2.63",
117
117
  "aws-cdk-lib": "^2.167.0",
118
118
  "constructs": "^10.4.2"
119
119
  }