@aws-sdk/find-v2 0.7.0 → 0.7.2
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/dist/scanLambdaFunctions.js +4 -9
- package/dist/utils/constants.js +3 -1
- package/dist/utils/getCodePathToSdkVersionMap.js +43 -0
- package/dist/utils/getCodeSizeToDownload.js +3 -0
- package/dist/utils/getCodeSizeToSaveOnDisk.js +10 -0
- package/dist/utils/getLambdaFunctionContents.js +64 -55
- package/dist/utils/getLambdaFunctionScanOutput.js +37 -68
- package/dist/utils/getLambdaLayerContents.js +27 -0
- package/dist/utils/getLambdaLayerToCodeSizeMap.js +4 -0
- package/dist/utils/getSdkVersionFromLambdaLayerContents.js +24 -0
- package/dist/utils/processRemoteZip.js +27 -0
- package/dist/utils/processZipEntries.js +17 -0
- package/package.json +2 -2
- package/dist/utils/downloadFile.js +0 -19
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Lambda } from "@aws-sdk/client-lambda";
|
|
2
2
|
import pLimit from "p-limit";
|
|
3
|
+
import { getCodeSizeToDownload } from "./utils/getCodeSizeToDownload.js";
|
|
4
|
+
import { getCodeSizeToSaveOnDisk } from "./utils/getCodeSizeToSaveOnDisk.js";
|
|
3
5
|
import { getDownloadConfirmation } from "./utils/getDownloadConfirmation.js";
|
|
4
6
|
import { getLambdaFunctions } from "./utils/getLambdaFunctions.js";
|
|
5
7
|
import { getLambdaFunctionScanOutput } from "./utils/getLambdaFunctionScanOutput.js";
|
|
@@ -18,19 +20,13 @@ export const scanLambdaFunctions = async (options) => {
|
|
|
18
20
|
}
|
|
19
21
|
const functions = await getLambdaFunctions(client, lambdaNodeJsMajorVersions);
|
|
20
22
|
const functionCount = functions.length;
|
|
21
|
-
const concurrency = Math.min(functionCount, jobs || 1);
|
|
22
|
-
const codeSizeToDownload = functions.reduce((acc, fn) => acc + (fn.CodeSize || 0), 0);
|
|
23
|
-
const codeSizeToSaveOnDisk = functions
|
|
24
|
-
.map((fn) => fn.CodeSize || 0)
|
|
25
|
-
.sort((a, b) => b - a)
|
|
26
|
-
.slice(0, concurrency)
|
|
27
|
-
.reduce((acc, size) => acc + size, 0);
|
|
28
23
|
if (functionCount === 0) {
|
|
29
24
|
printLambdaCommandOutput([], output);
|
|
30
25
|
return;
|
|
31
26
|
}
|
|
27
|
+
const concurrency = Math.min(functionCount, jobs || 1);
|
|
32
28
|
if (!yes) {
|
|
33
|
-
const confirmation = await getDownloadConfirmation(functionCount,
|
|
29
|
+
const confirmation = await getDownloadConfirmation(functionCount, getCodeSizeToDownload(functions), getCodeSizeToSaveOnDisk(functions, concurrency));
|
|
34
30
|
console.log();
|
|
35
31
|
if (!confirmation) {
|
|
36
32
|
console.log("Exiting.");
|
|
@@ -42,7 +38,6 @@ export const scanLambdaFunctions = async (options) => {
|
|
|
42
38
|
const scanOutput = await Promise.all(functions.map((fn) => limit(() => getLambdaFunctionScanOutput(client, {
|
|
43
39
|
functionName: fn.FunctionName,
|
|
44
40
|
region: clientRegion,
|
|
45
|
-
runtime: fn.Runtime,
|
|
46
41
|
sdkVersionRange: sdk,
|
|
47
42
|
}))));
|
|
48
43
|
printLambdaCommandOutput(scanOutput, output);
|
package/dist/utils/constants.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { join } from "node:path";
|
|
2
2
|
export const NODE_MODULES = "node_modules";
|
|
3
3
|
export const AWS_SDK = "aws-sdk";
|
|
4
|
+
export const PACKAGE_JSON = "package.json";
|
|
5
|
+
export const AWS_SDK_PACKAGE_JSON = join(NODE_MODULES, AWS_SDK, PACKAGE_JSON);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { AWS_SDK, AWS_SDK_PACKAGE_JSON, PACKAGE_JSON } from "./constants.js";
|
|
3
|
+
const safeParse = (json) => {
|
|
4
|
+
try {
|
|
5
|
+
return JSON.parse(json);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
// ToDo: add warning when logging is supported in future.
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Maps code file paths to their JS SDK v2 versions.
|
|
14
|
+
*
|
|
15
|
+
* Searches up the directory tree from each code path to find package.json,
|
|
16
|
+
* then resolves SDK version from node_modules/aws-sdk or dependencies.
|
|
17
|
+
*
|
|
18
|
+
* @param codePaths - Code file paths to map.
|
|
19
|
+
* @param packageJsonMap - Map of package.json paths to contents.
|
|
20
|
+
* @param awsSdkPackageJsonMap - Map of aws-sdk package.json paths to contents.
|
|
21
|
+
* @returns Map of code paths to SDK versions (undefined if not found).
|
|
22
|
+
*/
|
|
23
|
+
export const getCodePathToSdkVersionMap = (codePaths, packageJsonMap = new Map(), awsSdkPackageJsonMap = new Map()) => {
|
|
24
|
+
const dirToSdkVersionMap = new Map();
|
|
25
|
+
const getSdkVersion = (dir) => {
|
|
26
|
+
if (dirToSdkVersionMap.has(dir))
|
|
27
|
+
return dirToSdkVersionMap.get(dir);
|
|
28
|
+
let version;
|
|
29
|
+
// Assign version from node_modules aws-sdk package.json, if available.
|
|
30
|
+
const awsSdkPackageJson = awsSdkPackageJsonMap.get(join(dir, AWS_SDK_PACKAGE_JSON)) ??
|
|
31
|
+
awsSdkPackageJsonMap.get(AWS_SDK_PACKAGE_JSON);
|
|
32
|
+
version ??= awsSdkPackageJson && safeParse(awsSdkPackageJson).version;
|
|
33
|
+
// Assign version from package.json dependencies, if not populated yet.
|
|
34
|
+
const pkgJson = packageJsonMap.get(join(dir, PACKAGE_JSON));
|
|
35
|
+
version ??= pkgJson && safeParse(pkgJson).dependencies?.[AWS_SDK];
|
|
36
|
+
// Assign undefined if it's rootDir, else call getSdkVersion on parent dir, if not populated yet.
|
|
37
|
+
const parentDir = dirname(dir);
|
|
38
|
+
version ??= parentDir !== dir ? getSdkVersion(parentDir) : undefined;
|
|
39
|
+
dirToSdkVersionMap.set(dir, version);
|
|
40
|
+
return version;
|
|
41
|
+
};
|
|
42
|
+
return new Map(codePaths.map((codePath) => [codePath, getSdkVersion(dirname(codePath))]));
|
|
43
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { getLambdaLayerToCodeSizeMap } from "./getLambdaLayerToCodeSizeMap.js";
|
|
2
|
+
export const getCodeSizeToDownload = (functions) => functions.reduce((acc, fn) => acc + (fn.CodeSize || 0), 0) +
|
|
3
|
+
[...getLambdaLayerToCodeSizeMap(functions).values()].reduce((acc, size) => acc + size, 0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getLambdaLayerToCodeSizeMap } from "./getLambdaLayerToCodeSizeMap.js";
|
|
2
|
+
const getFunctionTotalSize = (fn) => (fn.CodeSize || 0) + (fn.Layers?.reduce((acc, l) => acc + (l.CodeSize || 0), 0) || 0);
|
|
3
|
+
export const getCodeSizeToSaveOnDisk = (functions, num) => {
|
|
4
|
+
const largestFunctions = [...functions]
|
|
5
|
+
.sort((a, b) => getFunctionTotalSize(b) - getFunctionTotalSize(a))
|
|
6
|
+
.slice(0, num);
|
|
7
|
+
const functionSize = largestFunctions.reduce((acc, fn) => acc + (fn.CodeSize || 0), 0);
|
|
8
|
+
const layerSize = [...getLambdaLayerToCodeSizeMap(largestFunctions).values()].reduce((acc, size) => acc + size, 0);
|
|
9
|
+
return functionSize + layerSize;
|
|
10
|
+
};
|
|
@@ -1,68 +1,77 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { AWS_SDK_PACKAGE_JSON, NODE_MODULES, PACKAGE_JSON } from "./constants.js";
|
|
2
|
+
import { getLambdaLayerContents } from "./getLambdaLayerContents.js";
|
|
3
|
+
import { getSdkVersionFromLambdaLayerContents } from "./getSdkVersionFromLambdaLayerContents.js";
|
|
4
|
+
import { processRemoteZip } from "./processRemoteZip.js";
|
|
5
|
+
import { processZipEntries } from "./processZipEntries.js";
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
7
|
+
* Cache for Lambda layer contents to avoid redundant downloads.
|
|
8
|
+
* Maps layer ARN to extracted layer contents.
|
|
9
|
+
*/
|
|
10
|
+
const lambdaLayerCache = new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Extracts and categorizes contents from a Lambda Function deployment package.
|
|
13
|
+
*
|
|
14
|
+
* Downloads and processes the Lambda function's zip file to extract:
|
|
15
|
+
* - JavaScript/TypeScript source files (excluding node_modules)
|
|
16
|
+
* - package.json files (excluding node_modules)
|
|
17
|
+
* - AWS SDK package.json files from node_modules and layers (for version detection)
|
|
8
18
|
*
|
|
9
|
-
* @param
|
|
10
|
-
* @
|
|
19
|
+
* @param client - The Lambda client instance for API calls
|
|
20
|
+
* @param options - Configuration options for content extraction
|
|
21
|
+
* @param options.codeLocation - Presigned URL to download the Lambda function code
|
|
22
|
+
* @param options.runtime - Lambda runtime identifier (e.g., 'nodejs20.x')
|
|
23
|
+
* @param options.layers - Array of Lambda layers attached to the function
|
|
24
|
+
* @returns Promise resolving to categorized file contents with optional maps for package.json and AWS SDK files
|
|
11
25
|
*/
|
|
12
|
-
export const getLambdaFunctionContents = async (
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
zipEntries = await zip.entries();
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
// Continue with empty object, if zip entries can't be read.
|
|
23
|
-
// ToDo: add warning when logging is supported in future.
|
|
24
|
-
}
|
|
25
|
-
for (const zipEntry of Object.values(zipEntries)) {
|
|
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
|
-
}
|
|
26
|
+
export const getLambdaFunctionContents = async (client, { codeLocation, runtime, layers = [] }) => {
|
|
27
|
+
const codeMap = new Map();
|
|
28
|
+
const packageJsonMap = new Map();
|
|
29
|
+
const awsSdkPackageJsonMap = new Map();
|
|
30
|
+
// Populate awsSdkPackageJsonMap with layers first.
|
|
31
|
+
for (const layer of layers) {
|
|
32
|
+
if (!layer.Arn)
|
|
32
33
|
continue;
|
|
34
|
+
if (!lambdaLayerCache.has(layer.Arn)) {
|
|
35
|
+
const response = await client.getLayerVersionByArn({ Arn: layer.Arn });
|
|
36
|
+
const layerContents = response.Content?.Location
|
|
37
|
+
? await getLambdaLayerContents(response.Content.Location)
|
|
38
|
+
: new Map();
|
|
39
|
+
lambdaLayerCache.set(layer.Arn, layerContents);
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
}
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
// Populate JavaScript/TypeScript files.
|
|
50
|
-
if (zipEntry.name.match(/\.(js|ts|mjs|cjs)$/)) {
|
|
41
|
+
const layerContents = lambdaLayerCache.get(layer.Arn) || new Map();
|
|
42
|
+
const version = getSdkVersionFromLambdaLayerContents(layerContents, runtime);
|
|
43
|
+
if (version)
|
|
44
|
+
awsSdkPackageJsonMap.set(AWS_SDK_PACKAGE_JSON, JSON.stringify({ version }));
|
|
45
|
+
}
|
|
46
|
+
await processRemoteZip(codeLocation, async (zipPath) => {
|
|
47
|
+
await processZipEntries(zipPath, async (entry, getData) => {
|
|
48
|
+
if (!entry.isFile)
|
|
49
|
+
return;
|
|
51
50
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
// Handle aws-sdk package.json in node_modules
|
|
52
|
+
if (entry.name.endsWith(AWS_SDK_PACKAGE_JSON)) {
|
|
53
|
+
awsSdkPackageJsonMap.set(entry.name, (await getData()).toString());
|
|
54
|
+
}
|
|
55
|
+
// Handle files outside of node_modules
|
|
56
|
+
else if (!entry.name.includes(`${NODE_MODULES}/`)) {
|
|
57
|
+
// Handle package.json
|
|
58
|
+
if (entry.name.endsWith(PACKAGE_JSON)) {
|
|
59
|
+
packageJsonMap.set(entry.name, (await getData()).toString());
|
|
60
|
+
}
|
|
61
|
+
// Handle JS/TS files
|
|
62
|
+
else if (entry.name.match(/\.(js|ts|mjs|cjs)$/)) {
|
|
63
|
+
codeMap.set(entry.name, (await getData()).toString());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
54
66
|
}
|
|
55
67
|
catch {
|
|
56
|
-
// Continue
|
|
57
|
-
// ToDo: add warning when logging is supported in future.
|
|
68
|
+
// Continue if entry data can't be read.
|
|
58
69
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
await zip.close();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
63
72
|
return {
|
|
64
73
|
codeMap,
|
|
65
|
-
...(
|
|
66
|
-
...(
|
|
74
|
+
...(packageJsonMap.size > 0 && { packageJsonMap }),
|
|
75
|
+
...(awsSdkPackageJsonMap.size > 0 && { awsSdkPackageJsonMap }),
|
|
67
76
|
};
|
|
68
77
|
};
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { satisfies, validate } from "compare-versions";
|
|
2
|
-
import {
|
|
3
|
-
import { downloadFile } from "./downloadFile.js";
|
|
2
|
+
import { getCodePathToSdkVersionMap } from "./getCodePathToSdkVersionMap.js";
|
|
4
3
|
import { getLambdaFunctionContents, } from "./getLambdaFunctionContents.js";
|
|
5
4
|
import { getPossibleHandlerFiles } from "./getPossibleHandlerFiles.js";
|
|
6
5
|
import { hasSdkV2InBundle } from "./hasSdkV2InBundle.js";
|
|
7
6
|
import { hasSdkV2InFile } from "./hasSdkV2InFile.js";
|
|
8
|
-
import { rm } from "node:fs/promises";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { dirname, join } from "node:path";
|
|
11
7
|
/**
|
|
12
8
|
* Scans a Lambda function to detect AWS SDK for JavaScript v2 usage.
|
|
13
9
|
*
|
|
14
|
-
* Downloads the function code, extracts it, and checks for
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* Downloads the function code, extracts it, and checks for JS SDK v2 signature in handled file if it's a bundle.
|
|
11
|
+
* If not found, it checks for source code files has require/imports JS SDK v2. It also checks dependencies in
|
|
12
|
+
* package.json for version validation.
|
|
17
13
|
*
|
|
18
14
|
* @param client - AWS Lambda client instance
|
|
19
15
|
* @param options - Scan configuration options
|
|
20
|
-
* @
|
|
16
|
+
* @param options.functionName - The name of the Lambda function
|
|
17
|
+
* @param options.region - AWS region the Lambda function is deployed to
|
|
18
|
+
* @param options.sdkVersionRange - Semver range string to check for AWS SDK for JavaScript v2
|
|
19
|
+
* @returns Scan results including SDK v2 detection status and locations
|
|
21
20
|
*/
|
|
22
|
-
export const getLambdaFunctionScanOutput = async (client, { functionName, region,
|
|
21
|
+
export const getLambdaFunctionScanOutput = async (client, { functionName, region, sdkVersionRange }) => {
|
|
22
|
+
const response = await client.getFunction({ FunctionName: functionName });
|
|
23
|
+
const runtime = response.Configuration?.Runtime;
|
|
23
24
|
const output = {
|
|
24
25
|
FunctionName: functionName,
|
|
25
26
|
Region: region,
|
|
@@ -27,16 +28,18 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
|
|
|
27
28
|
SdkVersion: sdkVersionRange,
|
|
28
29
|
ContainsAwsSdkJsV2: null,
|
|
29
30
|
};
|
|
30
|
-
const
|
|
31
|
-
if (!
|
|
31
|
+
const codeLocation = response.Code?.Location;
|
|
32
|
+
if (!codeLocation) {
|
|
32
33
|
output.AwsSdkJsV2Error = "Function Code location not found.";
|
|
33
34
|
return output;
|
|
34
35
|
}
|
|
35
|
-
const zipPath = join(tmpdir(), `${functionName}.zip`);
|
|
36
36
|
let lambdaFunctionContents;
|
|
37
37
|
try {
|
|
38
|
-
await
|
|
39
|
-
|
|
38
|
+
lambdaFunctionContents = await getLambdaFunctionContents(client, {
|
|
39
|
+
codeLocation,
|
|
40
|
+
runtime,
|
|
41
|
+
layers: response.Configuration?.Layers,
|
|
42
|
+
});
|
|
40
43
|
}
|
|
41
44
|
catch (error) {
|
|
42
45
|
const errorPrefix = "Error downloading or reading Lambda function code";
|
|
@@ -44,15 +47,13 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
|
|
|
44
47
|
error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
|
|
45
48
|
return output;
|
|
46
49
|
}
|
|
47
|
-
finally {
|
|
48
|
-
await rm(zipPath, { force: true });
|
|
49
|
-
}
|
|
50
50
|
const { packageJsonMap, awsSdkPackageJsonMap, codeMap } = lambdaFunctionContents;
|
|
51
51
|
// Process handler as bundle file first.
|
|
52
52
|
const possibleHandlerFiles = getPossibleHandlerFiles(response.Configuration?.Handler ?? "index.handler");
|
|
53
53
|
for (const handlerFile of possibleHandlerFiles) {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const handlerContent = codeMap.get(handlerFile);
|
|
55
|
+
if (handlerContent !== undefined) {
|
|
56
|
+
if (hasSdkV2InBundle(handlerContent, sdkVersionRange)) {
|
|
56
57
|
output.ContainsAwsSdkJsV2 = true;
|
|
57
58
|
output.AwsSdkJsV2Locations = [handlerFile];
|
|
58
59
|
return output;
|
|
@@ -61,7 +62,7 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
|
|
|
61
62
|
}
|
|
62
63
|
const filesWithJsSdkV2 = [];
|
|
63
64
|
// Search for JS SDK v2 occurrence in source code
|
|
64
|
-
for (const [filePath, fileContent] of
|
|
65
|
+
for (const [filePath, fileContent] of codeMap) {
|
|
65
66
|
try {
|
|
66
67
|
if (hasSdkV2InFile(filePath, fileContent)) {
|
|
67
68
|
filesWithJsSdkV2.push(filePath);
|
|
@@ -69,6 +70,7 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
|
|
|
69
70
|
}
|
|
70
71
|
catch {
|
|
71
72
|
// Skip files that fail to parse
|
|
73
|
+
// ToDo: add warning when logging is supported in future.
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
// JS SDK v2 not found in source code.
|
|
@@ -76,59 +78,26 @@ export const getLambdaFunctionScanOutput = async (client, { functionName, region
|
|
|
76
78
|
output.ContainsAwsSdkJsV2 = false;
|
|
77
79
|
return output;
|
|
78
80
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
const codePathToSdkVersionMap = getCodePathToSdkVersionMap(filesWithJsSdkV2, packageJsonMap, awsSdkPackageJsonMap);
|
|
82
|
+
const jsSdkV2FilesInSdkVersionRange = [];
|
|
83
|
+
for (const [codePath, version] of codePathToSdkVersionMap) {
|
|
84
|
+
if (version && validate(version)) {
|
|
82
85
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
93
|
-
try {
|
|
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)) {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
const errorPrefix = `Error checking version range '${sdkVersionRange}' for aws-sdk@${dependencies["aws-sdk"]} in '${packageJsonPath}'`;
|
|
114
|
-
output.AwsSdkJsV2Error =
|
|
115
|
-
error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
|
|
116
|
-
return output;
|
|
117
|
-
}
|
|
118
|
-
output.ContainsAwsSdkJsV2 = true;
|
|
119
|
-
output.AwsSdkJsV2Locations = filesWithJsSdkV2;
|
|
120
|
-
return output;
|
|
86
|
+
if (satisfies(version, sdkVersionRange)) {
|
|
87
|
+
jsSdkV2FilesInSdkVersionRange.push(codePath);
|
|
121
88
|
}
|
|
122
89
|
}
|
|
123
|
-
catch
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
error instanceof Error ? `${errorPrefix}: ${error.message}` : errorPrefix;
|
|
127
|
-
return output;
|
|
90
|
+
catch {
|
|
91
|
+
// Ignore if satisfies throws error
|
|
92
|
+
// ToDo: add warning when logging is supported in future.
|
|
128
93
|
}
|
|
129
94
|
}
|
|
130
95
|
}
|
|
131
|
-
|
|
96
|
+
if (jsSdkV2FilesInSdkVersionRange.length > 0) {
|
|
97
|
+
output.ContainsAwsSdkJsV2 = true;
|
|
98
|
+
output.AwsSdkJsV2Locations = jsSdkV2FilesInSdkVersionRange;
|
|
99
|
+
return output;
|
|
100
|
+
}
|
|
132
101
|
output.ContainsAwsSdkJsV2 = false;
|
|
133
102
|
return output;
|
|
134
103
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AWS_SDK_PACKAGE_JSON } from "./constants.js";
|
|
2
|
+
import { processZipEntries } from "./processZipEntries.js";
|
|
3
|
+
import { processRemoteZip } from "./processRemoteZip.js";
|
|
4
|
+
/**
|
|
5
|
+
* Downloads and extracts the contents of a Lambda layer from its presigned URL.
|
|
6
|
+
* Parses the zip and returns aws-sdk package.json from node_modules
|
|
7
|
+
*
|
|
8
|
+
* @param codeLocation - The presigned URL to download the Lambda layer.
|
|
9
|
+
* @returns Map of aws-sdk package.json files with their versions found in the layer.
|
|
10
|
+
*/
|
|
11
|
+
export const getLambdaLayerContents = async (codeLocation) => {
|
|
12
|
+
const lambdaLayerContents = new Map();
|
|
13
|
+
await processRemoteZip(codeLocation, async (zipPath) => {
|
|
14
|
+
await processZipEntries(zipPath, async (entry, getData) => {
|
|
15
|
+
if (!entry.isFile || !entry.name.endsWith(AWS_SDK_PACKAGE_JSON))
|
|
16
|
+
return;
|
|
17
|
+
try {
|
|
18
|
+
const { version } = JSON.parse((await getData()).toString());
|
|
19
|
+
lambdaLayerContents.set(entry.name, { version });
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Continue without adding package.json file, if entry data can't be read or there's parse error.
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
return lambdaLayerContents;
|
|
27
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AWS_SDK_PACKAGE_JSON } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns version from aws-sdk package.json value based on specificity
|
|
4
|
+
* - nodejs/node{major-version}/node_modules/aws-sdk/package.json
|
|
5
|
+
* - nodejs/node_modules/aws-sdk/package.json
|
|
6
|
+
* - node_modules/aws-sdk/package.json
|
|
7
|
+
*
|
|
8
|
+
* @param lambdaLayerContents - Map with aws-sdk package.json filepath as key and contents as value.
|
|
9
|
+
* @param runtime - Lambda runtime (e.g., nodejs20.x)
|
|
10
|
+
* @returns The sdk version string, or undefined if not found.
|
|
11
|
+
*/
|
|
12
|
+
export const getSdkVersionFromLambdaLayerContents = (lambdaLayerContents, runtime) => {
|
|
13
|
+
const majorVersion = runtime.match(/nodejs(\d+)/)?.[1];
|
|
14
|
+
const paths = [
|
|
15
|
+
`nodejs/node${majorVersion}/${AWS_SDK_PACKAGE_JSON}`,
|
|
16
|
+
`nodejs/${AWS_SDK_PACKAGE_JSON}`,
|
|
17
|
+
AWS_SDK_PACKAGE_JSON,
|
|
18
|
+
];
|
|
19
|
+
for (const path of paths) {
|
|
20
|
+
const content = lambdaLayerContents.get(path);
|
|
21
|
+
if (content)
|
|
22
|
+
return content.version;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Downloads a zip file, runs a processor, then cleans up.
|
|
7
|
+
*
|
|
8
|
+
* @param url - The URL to download the zip from.
|
|
9
|
+
* @param processor - Function that processes the zip file at the given path.
|
|
10
|
+
*/
|
|
11
|
+
export const processRemoteZip = async (url, processor) => {
|
|
12
|
+
const zipPath = join(tmpdir(), `${randomUUID()}.zip`);
|
|
13
|
+
const response = await fetch(url);
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(`Failed to download '${url}'. Received ${response.status} with '${response.statusText}'.`);
|
|
16
|
+
}
|
|
17
|
+
if (!response.body) {
|
|
18
|
+
throw new Error(`Response body is null for '${url}'`);
|
|
19
|
+
}
|
|
20
|
+
await writeFile(zipPath, response.body);
|
|
21
|
+
try {
|
|
22
|
+
await processor(zipPath);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await rm(zipPath, { force: true });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import StreamZip from "node-stream-zip";
|
|
2
|
+
/**
|
|
3
|
+
* Processes entries in a zip file using a callback function.
|
|
4
|
+
*
|
|
5
|
+
* @param zipPath - Path to the zip file.
|
|
6
|
+
* @param processor - Callback to process each entry.
|
|
7
|
+
*/
|
|
8
|
+
export const processZipEntries = async (zipPath, processor) => {
|
|
9
|
+
const zip = new StreamZip.async({ file: zipPath });
|
|
10
|
+
// Continue with empty object, if zip entries can't be read.
|
|
11
|
+
const zipEntries = await zip.entries().catch(() => ({}));
|
|
12
|
+
for (const entry of Object.values(zipEntries)) {
|
|
13
|
+
// Processor callback is provided by callee, and it should handle errors.
|
|
14
|
+
await processor(entry, () => zip.entryData(entry.name)).catch(() => { });
|
|
15
|
+
}
|
|
16
|
+
await zip.close();
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-sdk/find-v2",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"oxfmt": "^0.21.0",
|
|
32
32
|
"oxlint": "^1.33.0",
|
|
33
33
|
"parcel": "^2.16.3",
|
|
34
|
-
"rolldown": "1.0.0-beta.
|
|
34
|
+
"rolldown": "1.0.0-beta.59",
|
|
35
35
|
"rollup": "^4.53.3",
|
|
36
36
|
"typescript": "^5.9.3",
|
|
37
37
|
"vitest": "^4.0.15",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { writeFile } from "node:fs/promises";
|
|
2
|
-
/**
|
|
3
|
-
* Downloads a file from a URL and saves it to the specified path.
|
|
4
|
-
*
|
|
5
|
-
* @param url - The URL of the file to download.
|
|
6
|
-
* @param outputPath - The local file path where the downloaded file should be saved.
|
|
7
|
-
* @throws {Error} If the download fails or response body is null.
|
|
8
|
-
* @returns Promise that resolves when file is downloaded and saved.
|
|
9
|
-
*/
|
|
10
|
-
export const downloadFile = async (url, outputPath) => {
|
|
11
|
-
const response = await fetch(url);
|
|
12
|
-
if (!response.ok) {
|
|
13
|
-
throw new Error(`Failed to download '${url}'. Received ${response.status} with '${response.statusText}'.`);
|
|
14
|
-
}
|
|
15
|
-
if (!response.body) {
|
|
16
|
-
throw new Error(`Response body is null for '${url}'`);
|
|
17
|
-
}
|
|
18
|
-
await writeFile(outputPath, response.body);
|
|
19
|
-
};
|