@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 +13 -11
- package/dist/utils/constants.js +3 -0
- package/dist/utils/getLambdaFunctionContents.js +39 -41
- package/dist/utils/getLambdaFunctionScanOutput.js +62 -26
- package/dist/utils/getPossibleHandlerFiles.js +9 -0
- package/dist/utils/hasSdkV2InFile.js +45 -0
- package/dist/utils/printLambdaCommandOutput.js +2 -2
- package/package.json +2 -1
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.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
│
|
|
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][].
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import StreamZip from "node-stream-zip";
|
|
2
|
-
|
|
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
|
|
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,
|
|
24
|
-
if (zipEntry.name.includes(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 {
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
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 (
|
|
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 (
|
|
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.
|
|
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
|
-
//
|
|
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.
|
|
29
|
-
notes += ` ${scanOutput.
|
|
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.
|
|
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": {
|