@aws-sdk/find-v2 0.6.0 → 0.7.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.
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,3 @@
1
+ export const PACKAGE_JSON = "package.json";
2
+ export const NODE_MODULES = "node_modules";
3
+ export const AWS_SDK = "aws-sdk";
@@ -1,5 +1,6 @@
1
1
  import StreamZip from "node-stream-zip";
2
- const PACKAGE_JSON_FILENAME = "package.json";
2
+ import { AWS_SDK, NODE_MODULES, PACKAGE_JSON } from "./constants.js";
3
+ import { join } from "node:path";
3
4
  /**
4
5
  * Extracts the contents of a Lambda Function zip file.
5
6
  * Returns string contents of package.json files, if available.
@@ -10,7 +11,9 @@ const PACKAGE_JSON_FILENAME = "package.json";
10
11
  */
11
12
  export const getLambdaFunctionContents = async (zipPath) => {
12
13
  const zip = new StreamZip.async({ file: zipPath });
13
- const packageJsonFiles = [];
14
+ const codeMap = {};
15
+ const packageJsonMap = {};
16
+ const awsSdkPackageJsonMap = {};
14
17
  let zipEntries = {};
15
18
  try {
16
19
  zipEntries = await zip.entries();
@@ -20,51 +23,46 @@ export const getLambdaFunctionContents = async (zipPath) => {
20
23
  // ToDo: add warning when logging is supported in future.
21
24
  }
22
25
  for (const zipEntry of Object.values(zipEntries)) {
23
- // Skip 'node_modules' directory, as it's not the customer source code.
24
- if (zipEntry.name.includes("node_modules/"))
26
+ // Skip 'node_modules' directory, except for aws-sdk package.json file.
27
+ if (zipEntry.name.includes(`${NODE_MODULES}/`)) {
28
+ if (zipEntry.name.endsWith(join(NODE_MODULES, AWS_SDK, PACKAGE_JSON)) && zipEntry.isFile) {
29
+ const packageJsonContent = await zip.entryData(zipEntry.name);
30
+ awsSdkPackageJsonMap[zipEntry.name] = packageJsonContent.toString();
31
+ }
25
32
  continue;
26
- // Skip anything which is not 'package.json'
27
- if (!zipEntry.name.endsWith(PACKAGE_JSON_FILENAME))
28
- continue;
29
- // Skip if 'package.json' is not a file
30
- if (!zipEntry.isFile)
31
- continue;
32
- try {
33
- const packageJsonContent = await zip.entryData(zipEntry.name);
34
- packageJsonFiles.push({
35
- path: zipEntry.name,
36
- content: packageJsonContent.toString(),
37
- });
38
- }
39
- catch {
40
- // Continue without adding package.json file, if entry data can't be read.
41
- // ToDo: add warning when logging is supported in future.
42
33
  }
43
- }
44
- if (packageJsonFiles.length !== 0) {
45
- await zip.close();
46
- return { packageJsonFiles };
47
- }
48
- for (const path of ["index.js", "index.mjs", "index.cjs"]) {
49
- if (!zipEntries[path])
34
+ // Skip if it is not a file
35
+ if (!zipEntry.isFile)
50
36
  continue;
51
- if (!zipEntries[path].isFile)
37
+ // Populate 'package.json' files.
38
+ if (zipEntry.name.endsWith(PACKAGE_JSON)) {
39
+ try {
40
+ const packageJsonContent = await zip.entryData(zipEntry.name);
41
+ packageJsonMap[zipEntry.name] = packageJsonContent.toString();
42
+ }
43
+ catch {
44
+ // Continue without adding package.json file, if entry data can't be read.
45
+ // ToDo: add warning when logging is supported in future.
46
+ }
52
47
  continue;
53
- try {
54
- const bundleContent = await zip.entryData(path);
55
- await zip.close();
56
- return {
57
- bundleFile: {
58
- path,
59
- content: bundleContent.toString(),
60
- },
61
- };
62
48
  }
63
- catch {
64
- // Continue processing next index file, if entry data can't be read.
65
- // ToDo: add warning when logging is supported in future.
49
+ // Populate JavaScript/TypeScript files.
50
+ if (zipEntry.name.match(/\.(js|ts|mjs|cjs)$/)) {
51
+ try {
52
+ const codeContent = await zip.entryData(zipEntry.name);
53
+ codeMap[zipEntry.name] = codeContent.toString();
54
+ }
55
+ catch {
56
+ // Continue without adding code, if entry data can't be read.
57
+ // ToDo: add warning when logging is supported in future.
58
+ }
59
+ continue;
66
60
  }
67
61
  }
68
62
  await zip.close();
69
- return {};
63
+ return {
64
+ codeMap,
65
+ ...(Object.keys(packageJsonMap).length > 0 && { packageJsonMap }),
66
+ ...(Object.keys(awsSdkPackageJsonMap).length > 0 && { awsSdkPackageJsonMap }),
67
+ };
70
68
  };
@@ -1,10 +1,13 @@
1
- import { satisfies } from "compare-versions";
1
+ import { satisfies, validate } from "compare-versions";
2
+ import { AWS_SDK, NODE_MODULES, PACKAGE_JSON } from "./constants.js";
2
3
  import { downloadFile } from "./downloadFile.js";
3
4
  import { getLambdaFunctionContents, } from "./getLambdaFunctionContents.js";
5
+ import { getPossibleHandlerFiles } from "./getPossibleHandlerFiles.js";
4
6
  import { hasSdkV2InBundle } from "./hasSdkV2InBundle.js";
7
+ import { hasSdkV2InFile } from "./hasSdkV2InFile.js";
5
8
  import { rm } from "node:fs/promises";
6
9
  import { tmpdir } from "node:os";
7
- import { join } from "node:path";
10
+ import { dirname, join } from "node:path";
8
11
  /**
9
12
  * Scans a Lambda function to detect AWS SDK for JavaScript v2 usage.
10
13
  *
@@ -44,16 +47,65 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
44
47
  finally {
45
48
  await rm(zipPath, { force: true });
46
49
  }
47
- const { packageJsonFiles, bundleFile } = lambdaFunctionContents;
48
- // Search for "aws-sdk" in package.json dependencies if present.
49
- if (packageJsonFiles && packageJsonFiles.length > 0) {
50
- for (const { path: packageJsonPath, content: packageJsonContent } of packageJsonFiles) {
50
+ const { packageJsonMap, awsSdkPackageJsonMap, codeMap } = lambdaFunctionContents;
51
+ // Process handler as bundle file first.
52
+ const possibleHandlerFiles = getPossibleHandlerFiles(response.Configuration?.Handler ?? "index.handler");
53
+ for (const handlerFile of possibleHandlerFiles) {
54
+ if (handlerFile in codeMap) {
55
+ if (hasSdkV2InBundle(codeMap[handlerFile], sdkVersionRange)) {
56
+ output.ContainsAwsSdkJsV2 = true;
57
+ output.AwsSdkJsV2Locations = [handlerFile];
58
+ return output;
59
+ }
60
+ }
61
+ }
62
+ const filesWithJsSdkV2 = [];
63
+ // Search for JS SDK v2 occurrence in source code
64
+ for (const [filePath, fileContent] of Object.entries(codeMap)) {
65
+ try {
66
+ if (hasSdkV2InFile(filePath, fileContent)) {
67
+ filesWithJsSdkV2.push(filePath);
68
+ }
69
+ }
70
+ catch {
71
+ // Skip files that fail to parse
72
+ }
73
+ }
74
+ // JS SDK v2 not found in source code.
75
+ if (filesWithJsSdkV2.length === 0) {
76
+ output.ContainsAwsSdkJsV2 = false;
77
+ return output;
78
+ }
79
+ // Search for JS SDK v2 version from package.json
80
+ if (packageJsonMap && Object.keys(packageJsonMap).length > 0) {
81
+ for (const [packageJsonPath, packageJsonContent] of Object.entries(packageJsonMap)) {
51
82
  try {
52
83
  const packageJson = JSON.parse(packageJsonContent);
53
84
  const dependencies = packageJson.dependencies || {};
54
- if ("aws-sdk" in dependencies) {
85
+ if (AWS_SDK in dependencies) {
86
+ const awsSdkVersionInPackageJson = dependencies[AWS_SDK];
87
+ const awsSdkPackageJsonPathInNodeModules = join(NODE_MODULES, AWS_SDK, PACKAGE_JSON);
88
+ // Get aws-sdk package.json from nested node_modules or root node_modules.
89
+ const awsSdkPackageJson = awsSdkPackageJsonMap
90
+ ? (awsSdkPackageJsonMap[join(dirname(packageJsonPath), awsSdkPackageJsonPathInNodeModules)] ?? awsSdkPackageJsonMap[awsSdkPackageJsonPathInNodeModules])
91
+ : undefined;
92
+ let awsSdkVersionInNodeModules;
55
93
  try {
56
- if (!satisfies(dependencies["aws-sdk"], sdkVersionRange)) {
94
+ if (awsSdkPackageJson) {
95
+ awsSdkVersionInNodeModules = JSON.parse(awsSdkPackageJson).version;
96
+ }
97
+ }
98
+ catch {
99
+ // Skip if JSON can't be parsed.
100
+ // ToDo: add warning when logging is supported in future.
101
+ }
102
+ const sdkVersionToCheck = validate(awsSdkVersionInPackageJson) || awsSdkPackageJson === undefined
103
+ ? // Use version in package.json dependencies, if fixed version is defined or aws-sdk package.json is not available.
104
+ awsSdkVersionInPackageJson
105
+ : // Use version from aws-sdk package.json, if defined
106
+ (awsSdkVersionInNodeModules ?? awsSdkVersionInPackageJson);
107
+ try {
108
+ if (!satisfies(sdkVersionToCheck, sdkVersionRange)) {
57
109
  continue;
58
110
  }
59
111
  }
@@ -64,7 +116,7 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
64
116
  return output;
65
117
  }
66
118
  output.ContainsAwsSdkJsV2 = true;
67
- output.AwsSdkJsV2Location = `Defined in dependencies of '${packageJsonPath}'`;
119
+ output.AwsSdkJsV2Locations = filesWithJsSdkV2;
68
120
  return output;
69
121
  }
70
122
  }
@@ -76,23 +128,7 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
76
128
  }
77
129
  }
78
130
  }
79
- // Check for code of "aws-sdk" in bundle, if not found in package.json dependencies.
80
- if (bundleFile) {
81
- try {
82
- if (hasSdkV2InBundle(bundleFile.content, sdkVersionRange)) {
83
- output.ContainsAwsSdkJsV2 = true;
84
- output.AwsSdkJsV2Location = `Bundled in '${bundleFile.path}'`;
85
- return output;
86
- }
87
- }
88
- catch (error) {
89
- const errorPrefix = `Error reading bundle '${bundleFile.path}' for aws-sdk@${sdkVersionRange}`;
90
- output.AwsSdkJsV2Error =
91
- error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
92
- return output;
93
- }
94
- }
95
- // "aws-sdk" dependency/code not found.
131
+ // JS SDK v2 dependency/code not found.
96
132
  output.ContainsAwsSdkJsV2 = false;
97
133
  return output;
98
134
  };
@@ -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.0",
3
+ "version": "0.7.0",
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": {