@aws-sdk/find-v2 0.5.0 → 0.6.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 +11 -11
- package/dist/cli.js +9 -0
- package/dist/scanLambdaFunctions.js +2 -1
- package/dist/utils/constants.js +3 -0
- package/dist/utils/getLambdaFunctionContents.js +15 -5
- package/dist/utils/getLambdaFunctionScanOutput.js +69 -14
- package/dist/utils/hasSdkV2InBundle.js +13 -2
- package/dist/utils/printLambdaCommandOutput.js +8 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -33,17 +33,17 @@ 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 │ ContainsAwsSdkJsV2 │
|
|
38
|
-
|
|
39
|
-
│ fn-without-aws-sdk-in-bundle │ us-east-2 │ nodejs24.x │ No. │
|
|
40
|
-
|
|
41
|
-
│ fn-with-aws-sdk-in-bundle │ us-east-2 │ nodejs24.x │ Yes. Bundled in 'index.js' │
|
|
42
|
-
|
|
43
|
-
│ fn-with-aws-sdk-in-package-json-deps │ us-east-2 │ nodejs24.x │ Yes. Defined in dependencies of 'package.json' │
|
|
44
|
-
|
|
45
|
-
│ fn-without-aws-sdk-in-package-json-deps │ us-east-2 │ nodejs24.x │ 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. 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
|
+
└─────────────────────────────────────────┴───────────┴────────────┴────────────┴────────────────────────────────────────────────┘
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
This script requires AWS Managed Policy [AWSLambda_ReadOnlyAccess][].
|
package/dist/cli.js
CHANGED
|
@@ -29,6 +29,15 @@ export const createProgram = () => {
|
|
|
29
29
|
}
|
|
30
30
|
return value;
|
|
31
31
|
}, ">=20")
|
|
32
|
+
.option("--sdk <semver>", "Semver range string to check for AWS SDK for JavaScript v2", (value) => {
|
|
33
|
+
try {
|
|
34
|
+
satisfies("0", value);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new Error(`Invalid semver range: ${value}`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}, ">=2.0.0")
|
|
32
41
|
.option("--region <region>", "AWS region to scan")
|
|
33
42
|
.option("--profile <profile>", "AWS profile to use")
|
|
34
43
|
.addOption(new Option("-o, --output <output>", "Output format")
|
|
@@ -6,7 +6,7 @@ import { getLambdaFunctionScanOutput } from "./utils/getLambdaFunctionScanOutput
|
|
|
6
6
|
import { getLambdaNodeJsMatchingVersions } from "./utils/getLambdaNodeJsMatchingVersions.js";
|
|
7
7
|
import { LambdaCommandOutputType, printLambdaCommandOutput, } from "./utils/printLambdaCommandOutput.js";
|
|
8
8
|
export const scanLambdaFunctions = async (options) => {
|
|
9
|
-
const { yes, node, region, profile, output, jobs } = options;
|
|
9
|
+
const { yes, node, sdk, region, profile, output, jobs } = options;
|
|
10
10
|
const client = new Lambda({
|
|
11
11
|
...(region && { region }),
|
|
12
12
|
...(profile && { profile }),
|
|
@@ -43,6 +43,7 @@ export const scanLambdaFunctions = async (options) => {
|
|
|
43
43
|
functionName: fn.FunctionName,
|
|
44
44
|
region: clientRegion,
|
|
45
45
|
runtime: fn.Runtime,
|
|
46
|
+
sdkVersionRange: sdk,
|
|
46
47
|
}))));
|
|
47
48
|
printLambdaCommandOutput(scanOutput, output);
|
|
48
49
|
};
|
|
@@ -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.
|
|
@@ -11,6 +12,7 @@ const PACKAGE_JSON_FILENAME = "package.json";
|
|
|
11
12
|
export const getLambdaFunctionContents = async (zipPath) => {
|
|
12
13
|
const zip = new StreamZip.async({ file: zipPath });
|
|
13
14
|
const packageJsonFiles = [];
|
|
15
|
+
const awsSdkPackageJsonMap = {};
|
|
14
16
|
let zipEntries = {};
|
|
15
17
|
try {
|
|
16
18
|
zipEntries = await zip.entries();
|
|
@@ -20,11 +22,16 @@ export const getLambdaFunctionContents = async (zipPath) => {
|
|
|
20
22
|
// ToDo: add warning when logging is supported in future.
|
|
21
23
|
}
|
|
22
24
|
for (const zipEntry of Object.values(zipEntries)) {
|
|
23
|
-
// Skip 'node_modules' directory,
|
|
24
|
-
if (zipEntry.name.includes(
|
|
25
|
+
// Skip 'node_modules' directory, except for aws-sdk package.json file.
|
|
26
|
+
if (zipEntry.name.includes(`${NODE_MODULES}/`)) {
|
|
27
|
+
if (zipEntry.name.endsWith(join(NODE_MODULES, AWS_SDK, PACKAGE_JSON)) && zipEntry.isFile) {
|
|
28
|
+
const packageJsonContent = await zip.entryData(zipEntry.name);
|
|
29
|
+
awsSdkPackageJsonMap[zipEntry.name] = packageJsonContent.toString();
|
|
30
|
+
}
|
|
25
31
|
continue;
|
|
32
|
+
}
|
|
26
33
|
// Skip anything which is not 'package.json'
|
|
27
|
-
if (!zipEntry.name.endsWith(
|
|
34
|
+
if (!zipEntry.name.endsWith(PACKAGE_JSON))
|
|
28
35
|
continue;
|
|
29
36
|
// Skip if 'package.json' is not a file
|
|
30
37
|
if (!zipEntry.isFile)
|
|
@@ -43,7 +50,10 @@ export const getLambdaFunctionContents = async (zipPath) => {
|
|
|
43
50
|
}
|
|
44
51
|
if (packageJsonFiles.length !== 0) {
|
|
45
52
|
await zip.close();
|
|
46
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
packageJsonFiles,
|
|
55
|
+
...(Object.keys(awsSdkPackageJsonMap).length > 0 && { awsSdkPackageJsonMap }),
|
|
56
|
+
};
|
|
47
57
|
}
|
|
48
58
|
for (const path of ["index.js", "index.mjs", "index.cjs"]) {
|
|
49
59
|
if (!zipEntries[path])
|
|
@@ -1,14 +1,28 @@
|
|
|
1
|
+
import { satisfies, validate } from "compare-versions";
|
|
1
2
|
import { downloadFile } from "./downloadFile.js";
|
|
2
3
|
import { getLambdaFunctionContents, } from "./getLambdaFunctionContents.js";
|
|
3
4
|
import { hasSdkV2InBundle } from "./hasSdkV2InBundle.js";
|
|
4
5
|
import { rm } from "node:fs/promises";
|
|
5
6
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { AWS_SDK, NODE_MODULES, PACKAGE_JSON } from "./constants.js";
|
|
9
|
+
/**
|
|
10
|
+
* Scans a Lambda function to detect AWS SDK for JavaScript v2 usage.
|
|
11
|
+
*
|
|
12
|
+
* Downloads the function code, extracts it, and checks for v2 SDK in:
|
|
13
|
+
* 1. package.json dependencies
|
|
14
|
+
* 2. Bundled index file
|
|
15
|
+
*
|
|
16
|
+
* @param client - AWS Lambda client instance
|
|
17
|
+
* @param options - Scan configuration options
|
|
18
|
+
* @returns Scan results including SDK v2 detection status and location
|
|
19
|
+
*/
|
|
20
|
+
export const getLambdaFunctionScanOutput = async (client, { functionName, region, runtime, sdkVersionRange }) => {
|
|
8
21
|
const output = {
|
|
9
22
|
FunctionName: functionName,
|
|
10
23
|
Region: region,
|
|
11
24
|
Runtime: runtime,
|
|
25
|
+
SdkVersion: sdkVersionRange,
|
|
12
26
|
ContainsAwsSdkJsV2: null,
|
|
13
27
|
};
|
|
14
28
|
const response = await client.getFunction({ FunctionName: functionName });
|
|
@@ -23,23 +37,54 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
|
|
|
23
37
|
lambdaFunctionContents = await getLambdaFunctionContents(zipPath);
|
|
24
38
|
}
|
|
25
39
|
catch (error) {
|
|
40
|
+
const errorPrefix = "Error downloading or reading Lambda function code";
|
|
26
41
|
output.AwsSdkJsV2Error =
|
|
27
|
-
error instanceof Error
|
|
28
|
-
? `Error downloading or reading Lambda function code: ${error.message}`
|
|
29
|
-
: "Error downloading or reading Lambda function code.";
|
|
42
|
+
error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
|
|
30
43
|
return output;
|
|
31
44
|
}
|
|
32
45
|
finally {
|
|
33
46
|
await rm(zipPath, { force: true });
|
|
34
47
|
}
|
|
35
|
-
const { packageJsonFiles, bundleFile } = lambdaFunctionContents;
|
|
36
|
-
// Search for
|
|
48
|
+
const { packageJsonFiles, awsSdkPackageJsonMap, bundleFile } = lambdaFunctionContents;
|
|
49
|
+
// Search for JS SDK v2 in package.json dependencies if present.
|
|
37
50
|
if (packageJsonFiles && packageJsonFiles.length > 0) {
|
|
38
51
|
for (const { path: packageJsonPath, content: packageJsonContent } of packageJsonFiles) {
|
|
39
52
|
try {
|
|
40
53
|
const packageJson = JSON.parse(packageJsonContent);
|
|
41
54
|
const dependencies = packageJson.dependencies || {};
|
|
42
|
-
if (
|
|
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
|
+
}
|
|
43
88
|
output.ContainsAwsSdkJsV2 = true;
|
|
44
89
|
output.AwsSdkJsV2Location = `Defined in dependencies of '${packageJsonPath}'`;
|
|
45
90
|
return output;
|
|
@@ -53,13 +98,23 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
|
|
|
53
98
|
}
|
|
54
99
|
}
|
|
55
100
|
}
|
|
56
|
-
// Check for
|
|
57
|
-
if (bundleFile
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
101
|
+
// Check for signature of JS SDK v2 in bundle, if not found in package.json dependencies.
|
|
102
|
+
if (bundleFile) {
|
|
103
|
+
try {
|
|
104
|
+
if (hasSdkV2InBundle(bundleFile.content, sdkVersionRange)) {
|
|
105
|
+
output.ContainsAwsSdkJsV2 = true;
|
|
106
|
+
output.AwsSdkJsV2Location = `Bundled in '${bundleFile.path}'`;
|
|
107
|
+
return output;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
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;
|
|
115
|
+
}
|
|
61
116
|
}
|
|
62
|
-
//
|
|
117
|
+
// JS SDK v2 dependency/code not found.
|
|
63
118
|
output.ContainsAwsSdkJsV2 = false;
|
|
64
119
|
return output;
|
|
65
120
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { satisfies } from "compare-versions";
|
|
1
2
|
const AWS_SDK_ENV_VARS = [
|
|
2
3
|
"AWS_CONFIG_FILE",
|
|
3
4
|
// "AWS_CONTAINER_AUTHORIZATION_TOKEN", // Tree shaken by esbuild
|
|
@@ -32,13 +33,23 @@ const AWS_SDK_ENV_VARS = [
|
|
|
32
33
|
* Checks if AWS SDK v2 is present in the provided bundle content by looking for specific environment variables.
|
|
33
34
|
*
|
|
34
35
|
* @param bundleContent - The string content of the bundle to check.
|
|
36
|
+
* @param sdkVersionRange - Semver range string to check for AWS SDK for JavaScript v2
|
|
35
37
|
* @returns boolean - Returns true if all AWS SDK v2 environment variables are found in the bundle content, false otherwise.
|
|
36
38
|
*/
|
|
37
|
-
export const hasSdkV2InBundle = (bundleContent) => {
|
|
39
|
+
export const hasSdkV2InBundle = (bundleContent, sdkVersionRange) => {
|
|
38
40
|
for (const envVar of AWS_SDK_ENV_VARS) {
|
|
39
41
|
if (!bundleContent.includes(envVar)) {
|
|
40
42
|
return false;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
|
|
45
|
+
// Get version number from `VERSION:'2.X.Y'` or `VERSION: '2.X.Y'`, including double quotes and backticks.
|
|
46
|
+
const matches = bundleContent.match(/VERSION:\s*(['"`])(2\.\d+\.\d+)\1/);
|
|
47
|
+
if (matches && matches[2]) {
|
|
48
|
+
const version = matches[2];
|
|
49
|
+
// If version is in the specified range, return true
|
|
50
|
+
if (satisfies(version, sdkVersionRange)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
44
55
|
};
|
|
@@ -13,7 +13,7 @@ export const printLambdaCommandOutput = (output, outputType) => {
|
|
|
13
13
|
}
|
|
14
14
|
// Output as table
|
|
15
15
|
const table = new Table({
|
|
16
|
-
head: ["FunctionName", "Region", "Runtime", "ContainsAwsSdkJsV2"],
|
|
16
|
+
head: ["FunctionName", "Region", "Runtime", "SdkVersion", "ContainsAwsSdkJsV2"],
|
|
17
17
|
style: { head: ["bold"] },
|
|
18
18
|
});
|
|
19
19
|
for (const scanOutput of output) {
|
|
@@ -28,7 +28,13 @@ export const printLambdaCommandOutput = (output, outputType) => {
|
|
|
28
28
|
if (scanOutput.AwsSdkJsV2Location !== undefined) {
|
|
29
29
|
notes += ` ${scanOutput.AwsSdkJsV2Location}`;
|
|
30
30
|
}
|
|
31
|
-
table.push([
|
|
31
|
+
table.push([
|
|
32
|
+
scanOutput.FunctionName,
|
|
33
|
+
scanOutput.Region,
|
|
34
|
+
scanOutput.Runtime,
|
|
35
|
+
scanOutput.SdkVersion,
|
|
36
|
+
notes,
|
|
37
|
+
]);
|
|
32
38
|
}
|
|
33
39
|
console.log(table.toString());
|
|
34
40
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-sdk/find-v2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"@tsconfig/node-ts": "^23.6.2",
|
|
26
26
|
"@tsconfig/node20": "^20.1.8",
|
|
27
27
|
"@types/node": "^20.14.8",
|
|
28
|
-
"aws-sdk": "
|
|
28
|
+
"aws-sdk": "2.1693.0",
|
|
29
29
|
"esbuild": "~0.27.1",
|
|
30
|
-
"oxfmt": "^0.
|
|
30
|
+
"oxfmt": "^0.21.0",
|
|
31
31
|
"oxlint": "^1.33.0",
|
|
32
32
|
"parcel": "^2.16.3",
|
|
33
|
-
"rolldown": "1.0.0-beta.
|
|
33
|
+
"rolldown": "1.0.0-beta.58",
|
|
34
34
|
"rollup": "^4.53.3",
|
|
35
35
|
"typescript": "^5.9.3",
|
|
36
36
|
"vitest": "^4.0.15",
|