@aws-sdk/find-v2 0.6.1 → 0.7.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.
package/README.md CHANGED
@@ -33,17 +33,19 @@ Run `lambda` command to scan Lambda Node.js Functions for JavaScript SDK v2.
33
33
 
34
34
  ```console
35
35
  $ npx @aws-sdk/find-v2 lambda --yes --output table
36
- ┌─────────────────────────────────────────┬───────────┬────────────┬────────────┬────────────────────────────────────────────────┐
37
- │ FunctionName │ Region │ Runtime │ SdkVersion │ ContainsAwsSdkJsV2
38
- ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────────────────────────────────┤
39
- │ fn-without-aws-sdk-in-bundle │ us-east-2 │ nodejs24.x │ >=2.0.0 │ No.
40
- ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────────────────────────────────┤
41
- │ fn-with-aws-sdk-in-bundle │ us-east-2 │ nodejs24.x │ >=2.0.0 │ Yes. Bundled in 'index.js'
42
- ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────────────────────────────────┤
43
- │ fn-with-aws-sdk-in-package-json-deps │ us-east-2 │ nodejs24.x │ >=2.0.0 │ Yes. Defined in dependencies of 'package.json' │
44
- ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────────────────────────────────┤
45
- fn-without-aws-sdk-in-package-json-deps us-east-2 nodejs24.x >=2.0.0 No.
46
- └─────────────────────────────────────────┴───────────┴────────────┴────────────┴────────────────────────────────────────────────┘
36
+ ┌─────────────────────────────────────────┬───────────┬────────────┬────────────┬────────────────────┐
37
+ │ FunctionName │ Region │ Runtime │ SdkVersion │ ContainsAwsSdkJsV2
38
+ ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────┤
39
+ │ fn-without-aws-sdk-in-bundle │ us-east-2 │ nodejs24.x │ >=2.0.0 │ No.
40
+ ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────┤
41
+ │ fn-with-aws-sdk-in-bundle │ us-east-2 │ nodejs24.x │ >=2.0.0 │ Yes. Found in:
42
+ │ │ │ │ │ - index.js │
43
+ ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────┤
44
+ │ fn-with-aws-sdk-in-package-json-deps │ us-east-2 │ nodejs24.x │ >=2.0.0 │ Yes. Found in: │
45
+ - index.mjs
46
+ ├─────────────────────────────────────────┼───────────┼────────────┼────────────┼────────────────────┤
47
+ │ fn-without-aws-sdk-in-package-json-deps │ us-east-2 │ nodejs24.x │ >=2.0.0 │ No. │
48
+ └─────────────────────────────────────────┴───────────┴────────────┴────────────┴────────────────────┘
47
49
  ```
48
50
 
49
51
  This script requires AWS Managed Policy [AWSLambda_ReadOnlyAccess][].
@@ -0,0 +1,44 @@
1
+ import { dirname, join } from "node:path";
2
+ import { AWS_SDK, NODE_MODULES, PACKAGE_JSON } from "./constants.js";
3
+ const AWS_SDK_PACKAGE_JSON = join(NODE_MODULES, AWS_SDK, PACKAGE_JSON);
4
+ const safeParse = (json) => {
5
+ try {
6
+ return JSON.parse(json);
7
+ }
8
+ catch {
9
+ // ToDo: add warning when logging is supported in future.
10
+ return {};
11
+ }
12
+ };
13
+ /**
14
+ * Maps code file paths to their JS SDK v2 versions.
15
+ *
16
+ * Searches up the directory tree from each code path to find package.json,
17
+ * then resolves SDK version from node_modules/aws-sdk or dependencies.
18
+ *
19
+ * @param codePaths - Code file paths to map.
20
+ * @param packageJsonMap - Map of package.json paths to contents.
21
+ * @param awsSdkPackageJsonMap - Map of aws-sdk package.json paths to contents.
22
+ * @returns Map of code paths to SDK versions (undefined if not found).
23
+ */
24
+ export const getCodePathToSdkVersionMap = (codePaths, packageJsonMap = new Map(), awsSdkPackageJsonMap = new Map()) => {
25
+ const dirToSdkVersionMap = new Map();
26
+ const getSdkVersion = (dir) => {
27
+ if (dirToSdkVersionMap.has(dir))
28
+ return dirToSdkVersionMap.get(dir);
29
+ let version;
30
+ // Assign version from node_modules aws-sdk package.json, if available.
31
+ const awsSdkPackageJson = awsSdkPackageJsonMap.get(join(dir, AWS_SDK_PACKAGE_JSON)) ??
32
+ awsSdkPackageJsonMap.get(AWS_SDK_PACKAGE_JSON);
33
+ version ??= awsSdkPackageJson && safeParse(awsSdkPackageJson).version;
34
+ // Assign version from package.json dependencies, if not populated yet.
35
+ const pkgJson = packageJsonMap.get(join(dir, PACKAGE_JSON));
36
+ version ??= pkgJson && safeParse(pkgJson).dependencies?.[AWS_SDK];
37
+ // Assign undefined if it's rootDir, else call getSdkVersion on parent dir, if not populated yet.
38
+ const parentDir = dirname(dir);
39
+ version ??= parentDir !== dir ? getSdkVersion(parentDir) : undefined;
40
+ dirToSdkVersionMap.set(dir, version);
41
+ return version;
42
+ };
43
+ return new Map(codePaths.map((codePath) => [codePath, getSdkVersion(dirname(codePath))]));
44
+ };
@@ -3,16 +3,20 @@ import { AWS_SDK, NODE_MODULES, PACKAGE_JSON } from "./constants.js";
3
3
  import { join } from "node:path";
4
4
  /**
5
5
  * Extracts the contents of a Lambda Function zip file.
6
- * Returns string contents of package.json files, if available.
7
- * Otherwise, returns the contents of bundle file.
6
+ *
7
+ * Parses the zip and returns:
8
+ * - JS/TS source files (excluding node_modules)
9
+ * - package.json files (excluding node_modules)
10
+ * - aws-sdk package.json from node_modules (for version detection)
8
11
  *
9
12
  * @param zipPath - The path to the zip file of Lambda Function.
10
- * @returns Promise<LambdaFunctionContents> - Resolves to an object containing the extracted contents.
13
+ * @returns Extracted contents categorized by file type.
11
14
  */
12
15
  export const getLambdaFunctionContents = async (zipPath) => {
13
16
  const zip = new StreamZip.async({ file: zipPath });
14
- const packageJsonFiles = [];
15
- const awsSdkPackageJsonMap = {};
17
+ const codeMap = new Map();
18
+ const packageJsonMap = new Map();
19
+ const awsSdkPackageJsonMap = new Map();
16
20
  let zipEntries = {};
17
21
  try {
18
22
  zipEntries = await zip.entries();
@@ -26,55 +30,42 @@ export const getLambdaFunctionContents = async (zipPath) => {
26
30
  if (zipEntry.name.includes(`${NODE_MODULES}/`)) {
27
31
  if (zipEntry.name.endsWith(join(NODE_MODULES, AWS_SDK, PACKAGE_JSON)) && zipEntry.isFile) {
28
32
  const packageJsonContent = await zip.entryData(zipEntry.name);
29
- awsSdkPackageJsonMap[zipEntry.name] = packageJsonContent.toString();
33
+ awsSdkPackageJsonMap.set(zipEntry.name, packageJsonContent.toString());
30
34
  }
31
35
  continue;
32
36
  }
33
- // Skip anything which is not 'package.json'
34
- if (!zipEntry.name.endsWith(PACKAGE_JSON))
35
- continue;
36
- // Skip if 'package.json' is not a file
37
+ // Skip if it is not a file
37
38
  if (!zipEntry.isFile)
38
39
  continue;
39
- try {
40
- const packageJsonContent = await zip.entryData(zipEntry.name);
41
- packageJsonFiles.push({
42
- path: zipEntry.name,
43
- content: packageJsonContent.toString(),
44
- });
45
- }
46
- catch {
47
- // Continue without adding package.json file, if entry data can't be read.
48
- // ToDo: add warning when logging is supported in future.
49
- }
50
- }
51
- if (packageJsonFiles.length !== 0) {
52
- await zip.close();
53
- return {
54
- packageJsonFiles,
55
- ...(Object.keys(awsSdkPackageJsonMap).length > 0 && { awsSdkPackageJsonMap }),
56
- };
57
- }
58
- for (const path of ["index.js", "index.mjs", "index.cjs"]) {
59
- if (!zipEntries[path])
60
- continue;
61
- if (!zipEntries[path].isFile)
40
+ // Populate 'package.json' files.
41
+ if (zipEntry.name.endsWith(PACKAGE_JSON)) {
42
+ try {
43
+ const packageJsonContent = await zip.entryData(zipEntry.name);
44
+ packageJsonMap.set(zipEntry.name, packageJsonContent.toString());
45
+ }
46
+ catch {
47
+ // Continue without adding package.json file, if entry data can't be read.
48
+ // ToDo: add warning when logging is supported in future.
49
+ }
62
50
  continue;
63
- try {
64
- const bundleContent = await zip.entryData(path);
65
- await zip.close();
66
- return {
67
- bundleFile: {
68
- path,
69
- content: bundleContent.toString(),
70
- },
71
- };
72
51
  }
73
- catch {
74
- // Continue processing next index file, if entry data can't be read.
75
- // ToDo: add warning when logging is supported in future.
52
+ // Populate JavaScript/TypeScript files.
53
+ if (zipEntry.name.match(/\.(js|ts|mjs|cjs)$/)) {
54
+ try {
55
+ const codeContent = await zip.entryData(zipEntry.name);
56
+ codeMap.set(zipEntry.name, codeContent.toString());
57
+ }
58
+ catch {
59
+ // Continue without adding code, if entry data can't be read.
60
+ // ToDo: add warning when logging is supported in future.
61
+ }
62
+ continue;
76
63
  }
77
64
  }
78
65
  await zip.close();
79
- return {};
66
+ return {
67
+ codeMap,
68
+ ...(packageJsonMap.size > 0 && { packageJsonMap }),
69
+ ...(awsSdkPackageJsonMap.size > 0 && { awsSdkPackageJsonMap }),
70
+ };
80
71
  };
@@ -1,21 +1,27 @@
1
1
  import { satisfies, validate } from "compare-versions";
2
2
  import { downloadFile } from "./downloadFile.js";
3
3
  import { getLambdaFunctionContents, } from "./getLambdaFunctionContents.js";
4
+ import { getPossibleHandlerFiles } from "./getPossibleHandlerFiles.js";
4
5
  import { hasSdkV2InBundle } from "./hasSdkV2InBundle.js";
6
+ import { hasSdkV2InFile } from "./hasSdkV2InFile.js";
5
7
  import { rm } from "node:fs/promises";
6
8
  import { tmpdir } from "node:os";
7
- import { dirname, join } from "node:path";
8
- import { AWS_SDK, NODE_MODULES, PACKAGE_JSON } from "./constants.js";
9
+ import { join } from "node:path";
10
+ import { getCodePathToSdkVersionMap } from "./getCodePathToSdkVersionMap.js";
9
11
  /**
10
12
  * Scans a Lambda function to detect AWS SDK for JavaScript v2 usage.
11
13
  *
12
- * Downloads the function code, extracts it, and checks for v2 SDK in:
13
- * 1. package.json dependencies
14
- * 2. Bundled index file
14
+ * Downloads the function code, extracts it, and checks for JS SDK v2 signature in handled file if it's a bundle.
15
+ * If not found, it checks for source code files has require/imports JS SDK v2. It also checks dependencies in
16
+ * package.json for version validation.
15
17
  *
16
18
  * @param client - AWS Lambda client instance
17
19
  * @param options - Scan configuration options
18
- * @returns Scan results including SDK v2 detection status and location
20
+ * @param options.functionName - The name of the Lambda function
21
+ * @param options.region - AWS region the Lambda function is deployed to
22
+ * @param options.runtime - Lambda Function's Node.js runtime
23
+ * @param options.sdkVersionRange - Semver range string to check for AWS SDK for JavaScript v2
24
+ * @returns Scan results including SDK v2 detection status and locations
19
25
  */
20
26
  export const getLambdaFunctionScanOutput = async (client, { functionName, region, runtime, sdkVersionRange }) => {
21
27
  const output = {
@@ -45,76 +51,57 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
45
51
  finally {
46
52
  await rm(zipPath, { force: true });
47
53
  }
48
- const { packageJsonFiles, awsSdkPackageJsonMap, bundleFile } = lambdaFunctionContents;
49
- // Search for JS SDK v2 in package.json dependencies if present.
50
- if (packageJsonFiles && packageJsonFiles.length > 0) {
51
- for (const { path: packageJsonPath, content: packageJsonContent } of packageJsonFiles) {
52
- try {
53
- const packageJson = JSON.parse(packageJsonContent);
54
- const dependencies = packageJson.dependencies || {};
55
- if (AWS_SDK in dependencies) {
56
- const awsSdkVersionInPackageJson = dependencies[AWS_SDK];
57
- const awsSdkPackageJsonPathInNodeModules = join(NODE_MODULES, AWS_SDK, PACKAGE_JSON);
58
- // Get aws-sdk package.json from nested node_modules or root node_modules.
59
- const awsSdkPackageJson = awsSdkPackageJsonMap
60
- ? (awsSdkPackageJsonMap[join(dirname(packageJsonPath), awsSdkPackageJsonPathInNodeModules)] ?? awsSdkPackageJsonMap[awsSdkPackageJsonPathInNodeModules])
61
- : undefined;
62
- let awsSdkVersionInNodeModules;
63
- try {
64
- if (awsSdkPackageJson) {
65
- awsSdkVersionInNodeModules = JSON.parse(awsSdkPackageJson).version;
66
- }
67
- }
68
- catch {
69
- // Skip if JSON can't be parsed.
70
- // ToDo: add warning when logging is supported in future.
71
- }
72
- const sdkVersionToCheck = validate(awsSdkVersionInPackageJson) || awsSdkPackageJson === undefined
73
- ? // Use version in package.json dependencies, if fixed version is defined or aws-sdk package.json is not available.
74
- awsSdkVersionInPackageJson
75
- : // Use version from aws-sdk package.json, if defined
76
- (awsSdkVersionInNodeModules ?? awsSdkVersionInPackageJson);
77
- try {
78
- if (!satisfies(sdkVersionToCheck, sdkVersionRange)) {
79
- continue;
80
- }
81
- }
82
- catch (error) {
83
- const errorPrefix = `Error checking version range '${sdkVersionRange}' for aws-sdk@${dependencies["aws-sdk"]} in '${packageJsonPath}'`;
84
- output.AwsSdkJsV2Error =
85
- error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
86
- return output;
87
- }
88
- output.ContainsAwsSdkJsV2 = true;
89
- output.AwsSdkJsV2Location = `Defined in dependencies of '${packageJsonPath}'`;
90
- return output;
91
- }
92
- }
93
- catch (error) {
94
- const errorPrefix = `Error parsing '${packageJsonPath}'`;
95
- output.AwsSdkJsV2Error =
96
- error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
54
+ const { packageJsonMap, awsSdkPackageJsonMap, codeMap } = lambdaFunctionContents;
55
+ // Process handler as bundle file first.
56
+ const possibleHandlerFiles = getPossibleHandlerFiles(response.Configuration?.Handler ?? "index.handler");
57
+ for (const handlerFile of possibleHandlerFiles) {
58
+ const handlerContent = codeMap.get(handlerFile);
59
+ if (handlerContent !== undefined) {
60
+ if (hasSdkV2InBundle(handlerContent, sdkVersionRange)) {
61
+ output.ContainsAwsSdkJsV2 = true;
62
+ output.AwsSdkJsV2Locations = [handlerFile];
97
63
  return output;
98
64
  }
99
65
  }
100
66
  }
101
- // Check for signature of JS SDK v2 in bundle, if not found in package.json dependencies.
102
- if (bundleFile) {
67
+ const filesWithJsSdkV2 = [];
68
+ // Search for JS SDK v2 occurrence in source code
69
+ for (const [filePath, fileContent] of codeMap) {
103
70
  try {
104
- if (hasSdkV2InBundle(bundleFile.content, sdkVersionRange)) {
105
- output.ContainsAwsSdkJsV2 = true;
106
- output.AwsSdkJsV2Location = `Bundled in '${bundleFile.path}'`;
107
- return output;
71
+ if (hasSdkV2InFile(filePath, fileContent)) {
72
+ filesWithJsSdkV2.push(filePath);
108
73
  }
109
74
  }
110
- catch (error) {
111
- const errorPrefix = `Error reading bundle '${bundleFile.path}' for aws-sdk@${sdkVersionRange}`;
112
- output.AwsSdkJsV2Error =
113
- error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
114
- return output;
75
+ catch {
76
+ // Skip files that fail to parse
77
+ // ToDo: add warning when logging is supported in future.
78
+ }
79
+ }
80
+ // JS SDK v2 not found in source code.
81
+ if (filesWithJsSdkV2.length === 0) {
82
+ output.ContainsAwsSdkJsV2 = false;
83
+ return output;
84
+ }
85
+ const codePathToSdkVersionMap = getCodePathToSdkVersionMap(filesWithJsSdkV2, packageJsonMap, awsSdkPackageJsonMap);
86
+ const jsSdkV2FilesInSdkVersionRange = [];
87
+ for (const [codePath, version] of codePathToSdkVersionMap) {
88
+ if (version && validate(version)) {
89
+ try {
90
+ if (satisfies(version, sdkVersionRange)) {
91
+ jsSdkV2FilesInSdkVersionRange.push(codePath);
92
+ }
93
+ }
94
+ catch {
95
+ // Ignore if satisfies throws error
96
+ // ToDo: add warning when logging is supported in future.
97
+ }
115
98
  }
116
99
  }
117
- // JS SDK v2 dependency/code not found.
100
+ if (jsSdkV2FilesInSdkVersionRange.length > 0) {
101
+ output.ContainsAwsSdkJsV2 = true;
102
+ output.AwsSdkJsV2Locations = jsSdkV2FilesInSdkVersionRange;
103
+ return output;
104
+ }
118
105
  output.ContainsAwsSdkJsV2 = false;
119
106
  return output;
120
107
  };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Returns possible file paths for a Lambda handler.
3
+ * @param handlerPath - Lambda handler path (e.g., "index.handler").
4
+ * @returns Array of possible file paths with js, mjs, cjs, ts extensions.
5
+ */
6
+ export const getPossibleHandlerFiles = (handlerPath) => {
7
+ const [handlerFile] = handlerPath.split(".");
8
+ return [`${handlerFile}.js`, `${handlerFile}.mjs`, `${handlerFile}.cjs`, `${handlerFile}.ts`];
9
+ };
@@ -0,0 +1,45 @@
1
+ import { parseSync } from "oxc-parser";
2
+ const isAwsSdkV2 = (path) => path === "aws-sdk" || path.startsWith("aws-sdk/");
3
+ /**
4
+ * Recursively searches AST for JS SDK v2 require/import patterns.
5
+ * @param node - AST node to search.
6
+ * @returns true if JS SDK v2 require/import pattern is found.
7
+ */
8
+ const hasAwsSdkV2InAst = (node) => {
9
+ if (!node || typeof node !== "object")
10
+ return false;
11
+ const n = node;
12
+ // Search for aws-sdk in require
13
+ if (n.type === "CallExpression" &&
14
+ n.callee?.name === "require" &&
15
+ isAwsSdkV2(n.arguments?.[0]?.value)) {
16
+ return true;
17
+ }
18
+ // Search for aws-sdk in import equals
19
+ if (n.type === "TSImportEqualsDeclaration" &&
20
+ n.moduleReference?.type === "TSExternalModuleReference" &&
21
+ isAwsSdkV2(n.moduleReference?.expression?.value)) {
22
+ return true;
23
+ }
24
+ // Search for aws-sdk in dynamic import
25
+ if (n.type === "ImportExpression" && isAwsSdkV2(n.source?.value ?? "")) {
26
+ return true;
27
+ }
28
+ return Object.values(n).some((child) => Array.isArray(child) ? child.some(hasAwsSdkV2InAst) : hasAwsSdkV2InAst(child));
29
+ };
30
+ /**
31
+ * Checks if a file contains AWS SDK for JavaScript v2 imports or requires.
32
+ * @param filePath - Path to the file (used for parser configuration).
33
+ * @param fileContent - Content of the file to analyze.
34
+ * @returns true if the file contains AWS SDK v2 usage.
35
+ */
36
+ export const hasSdkV2InFile = (filePath, fileContent) => {
37
+ const { module, program } = parseSync(filePath, fileContent);
38
+ for (const { moduleRequest } of module.staticImports) {
39
+ if (isAwsSdkV2(moduleRequest.value))
40
+ return true;
41
+ }
42
+ if (hasAwsSdkV2InAst(program))
43
+ return true;
44
+ return false;
45
+ };
@@ -25,8 +25,8 @@ export const printLambdaCommandOutput = (output, outputType) => {
25
25
  if (scanOutput.AwsSdkJsV2Error !== undefined) {
26
26
  notes += ` ${scanOutput.AwsSdkJsV2Error}`;
27
27
  }
28
- if (scanOutput.AwsSdkJsV2Location !== undefined) {
29
- notes += ` ${scanOutput.AwsSdkJsV2Location}`;
28
+ if (scanOutput.AwsSdkJsV2Locations !== undefined) {
29
+ notes += ` Found in:\n${scanOutput.AwsSdkJsV2Locations.map((location) => `- ${location}`).join("\n")}`;
30
30
  }
31
31
  table.push([
32
32
  scanOutput.FunctionName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-sdk/find-v2",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "CLI to find resources which call AWS using JavaScript SDK v2",
5
5
  "main": "dist/cli.js",
6
6
  "types": "dist/cli.d.ts",
@@ -11,6 +11,7 @@
11
11
  "commander": "^14.0.2",
12
12
  "compare-versions": "^6.1.1",
13
13
  "node-stream-zip": "^1.15.0",
14
+ "oxc-parser": "^0.107.0",
14
15
  "p-limit": "^7.2.0"
15
16
  },
16
17
  "devDependencies": {