@cyclonedx/cdxgen 10.0.2 → 10.0.4
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 +3 -2
- package/analyzer.js +62 -24
- package/bin/cdxgen.js +1 -1
- package/index.js +24 -5
- package/package.json +3 -3
- package/postgen.js +23 -2
- package/postgen.test.js +3 -3
- package/utils.js +108 -6
- package/utils.test.js +18 -0
package/README.md
CHANGED
|
@@ -175,8 +175,9 @@ Options:
|
|
|
175
175
|
es. [boolean] [default: false]
|
|
176
176
|
--spec-version CycloneDX Specification version to use. Defaults
|
|
177
177
|
to 1.5 [default: 1.5]
|
|
178
|
-
--filter Filter components containing this word in purl
|
|
179
|
-
Multiple values
|
|
178
|
+
--filter Filter components containing this word in purl or
|
|
179
|
+
component.properties.value. Multiple values allo
|
|
180
|
+
wed. [array]
|
|
180
181
|
--only Include components only containing this word in
|
|
181
182
|
purl. Useful to generate BOM with first party co
|
|
182
183
|
mponents alone. Multiple values allowed. [array]
|
package/analyzer.js
CHANGED
|
@@ -8,7 +8,6 @@ import { basename, resolve, isAbsolute, relative } from "node:path";
|
|
|
8
8
|
const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
|
|
9
9
|
? process.env.ASTGEN_IGNORE_DIRS.split(",")
|
|
10
10
|
: [
|
|
11
|
-
"node_modules",
|
|
12
11
|
"venv",
|
|
13
12
|
"docs",
|
|
14
13
|
"test",
|
|
@@ -37,7 +36,7 @@ const IGNORE_FILE_PATTERN = new RegExp(
|
|
|
37
36
|
"i"
|
|
38
37
|
);
|
|
39
38
|
|
|
40
|
-
const getAllFiles = (dir, extn, files, result, regex) => {
|
|
39
|
+
const getAllFiles = (deep, dir, extn, files, result, regex) => {
|
|
41
40
|
files = files || readdirSync(dir);
|
|
42
41
|
result = result || [];
|
|
43
42
|
regex = regex || new RegExp(`\\${extn}$`);
|
|
@@ -60,8 +59,20 @@ const getAllFiles = (dir, extn, files, result, regex) => {
|
|
|
60
59
|
) {
|
|
61
60
|
continue;
|
|
62
61
|
}
|
|
62
|
+
// We need to include node_modules in deep mode to track exports
|
|
63
|
+
// Ignore only for non-deep analysis
|
|
64
|
+
if (!deep && dirName === "node_modules") {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
63
67
|
try {
|
|
64
|
-
result = getAllFiles(
|
|
68
|
+
result = getAllFiles(
|
|
69
|
+
deep,
|
|
70
|
+
file,
|
|
71
|
+
extn,
|
|
72
|
+
readdirSync(file),
|
|
73
|
+
result,
|
|
74
|
+
regex
|
|
75
|
+
);
|
|
65
76
|
} catch (error) {
|
|
66
77
|
continue;
|
|
67
78
|
}
|
|
@@ -101,7 +112,14 @@ const babelParserOptions = {
|
|
|
101
112
|
* Filter only references to (t|jsx?) or (less|scss) files for now.
|
|
102
113
|
* Opt to use our relative paths.
|
|
103
114
|
*/
|
|
104
|
-
const setFileRef = (
|
|
115
|
+
const setFileRef = (
|
|
116
|
+
allImports,
|
|
117
|
+
allExports,
|
|
118
|
+
src,
|
|
119
|
+
file,
|
|
120
|
+
pathnode,
|
|
121
|
+
specifiers = []
|
|
122
|
+
) => {
|
|
105
123
|
const pathway = pathnode.value || pathnode.name;
|
|
106
124
|
const sourceLoc = pathnode.loc?.start;
|
|
107
125
|
if (!pathway) {
|
|
@@ -115,9 +133,13 @@ const setFileRef = (allImports, src, file, pathnode, specifiers = []) => {
|
|
|
115
133
|
const importedModules = specifiers
|
|
116
134
|
.map((s) => s.imported?.name)
|
|
117
135
|
.filter((v) => v !== undefined);
|
|
136
|
+
const exportedModules = specifiers
|
|
137
|
+
.map((s) => s.exported?.name)
|
|
138
|
+
.filter((v) => v !== undefined);
|
|
118
139
|
const occurrence = {
|
|
119
140
|
importedAs: pathway,
|
|
120
141
|
importedModules,
|
|
142
|
+
exportedModules,
|
|
121
143
|
isExternal: true,
|
|
122
144
|
fileName: fileRelativeLoc,
|
|
123
145
|
lineNumber: sourceLoc && sourceLoc.line ? sourceLoc.line : undefined,
|
|
@@ -132,10 +154,13 @@ const setFileRef = (allImports, src, file, pathnode, specifiers = []) => {
|
|
|
132
154
|
moduleFullPath = relative(src, moduleFullPath);
|
|
133
155
|
wasAbsolute = true;
|
|
134
156
|
}
|
|
135
|
-
|
|
157
|
+
if (!moduleFullPath.startsWith("node_modules/")) {
|
|
158
|
+
occurrence.isExternal = false;
|
|
159
|
+
}
|
|
136
160
|
}
|
|
137
161
|
allImports[moduleFullPath] = allImports[moduleFullPath] || new Set();
|
|
138
162
|
allImports[moduleFullPath].add(occurrence);
|
|
163
|
+
|
|
139
164
|
// Handle module package name
|
|
140
165
|
// Eg: zone.js/dist/zone will be referred to as zone.js in package.json
|
|
141
166
|
if (!wasAbsolute && moduleFullPath.includes("/")) {
|
|
@@ -143,6 +168,16 @@ const setFileRef = (allImports, src, file, pathnode, specifiers = []) => {
|
|
|
143
168
|
allImports[modPkg] = allImports[modPkg] || new Set();
|
|
144
169
|
allImports[modPkg].add(occurrence);
|
|
145
170
|
}
|
|
171
|
+
if (exportedModules && exportedModules.length) {
|
|
172
|
+
moduleFullPath = moduleFullPath
|
|
173
|
+
.replace("node_modules/", "")
|
|
174
|
+
.replace("dist/", "")
|
|
175
|
+
.replace(/\.(js|ts|cjs|mjs)$/g, "")
|
|
176
|
+
.replace("src/", "");
|
|
177
|
+
allExports[moduleFullPath] = allExports[moduleFullPath] || new Set();
|
|
178
|
+
occurrence.exportedModules = exportedModules;
|
|
179
|
+
allExports[moduleFullPath].add(occurrence);
|
|
180
|
+
}
|
|
146
181
|
};
|
|
147
182
|
|
|
148
183
|
const vueCleaningRegex = /<\/*script.*>|<style[\s\S]*style>|<\/*br>/gi;
|
|
@@ -178,13 +213,14 @@ const fileToParseableCode = (file) => {
|
|
|
178
213
|
* Check AST tree for any (j|tsx?) files and set a file
|
|
179
214
|
* references for any import, require or dynamic import files.
|
|
180
215
|
*/
|
|
181
|
-
const parseFileASTTree = (src, file, allImports) => {
|
|
216
|
+
const parseFileASTTree = (src, file, allImports, allExports) => {
|
|
182
217
|
const ast = parse(fileToParseableCode(file), babelParserOptions);
|
|
183
218
|
traverse.default(ast, {
|
|
184
219
|
ImportDeclaration: (path) => {
|
|
185
220
|
if (path && path.node) {
|
|
186
221
|
setFileRef(
|
|
187
222
|
allImports,
|
|
223
|
+
allExports,
|
|
188
224
|
src,
|
|
189
225
|
file,
|
|
190
226
|
path.node.source,
|
|
@@ -200,24 +236,25 @@ const parseFileASTTree = (src, file, allImports) => {
|
|
|
200
236
|
path.node.name === "require" &&
|
|
201
237
|
path.parent.type === "CallExpression"
|
|
202
238
|
) {
|
|
203
|
-
setFileRef(allImports, src, file, path.parent.arguments[0]);
|
|
239
|
+
setFileRef(allImports, allExports, src, file, path.parent.arguments[0]);
|
|
204
240
|
}
|
|
205
241
|
},
|
|
206
242
|
// Use for dynamic imports like routes.jsx
|
|
207
243
|
CallExpression: (path) => {
|
|
208
244
|
if (path && path.node && path.node.callee.type === "Import") {
|
|
209
|
-
setFileRef(allImports, src, file, path.node.arguments[0]);
|
|
245
|
+
setFileRef(allImports, allExports, src, file, path.node.arguments[0]);
|
|
210
246
|
}
|
|
211
247
|
},
|
|
212
248
|
// Use for export barrells
|
|
213
249
|
ExportAllDeclaration: (path) => {
|
|
214
|
-
setFileRef(allImports, src, file, path.node.source);
|
|
250
|
+
setFileRef(allImports, allExports, src, file, path.node.source);
|
|
215
251
|
},
|
|
216
252
|
ExportNamedDeclaration: (path) => {
|
|
217
253
|
// ensure there is a path export
|
|
218
254
|
if (path && path.node && path.node.source) {
|
|
219
255
|
setFileRef(
|
|
220
256
|
allImports,
|
|
257
|
+
allExports,
|
|
221
258
|
src,
|
|
222
259
|
file,
|
|
223
260
|
path.node.source,
|
|
@@ -231,36 +268,37 @@ const parseFileASTTree = (src, file, allImports) => {
|
|
|
231
268
|
/**
|
|
232
269
|
* Return paths to all (j|tsx?) files.
|
|
233
270
|
*/
|
|
234
|
-
const getAllSrcJSAndTSFiles = (src) =>
|
|
271
|
+
const getAllSrcJSAndTSFiles = (src, deep) =>
|
|
235
272
|
Promise.all([
|
|
236
|
-
getAllFiles(src, ".js"),
|
|
237
|
-
getAllFiles(src, ".jsx"),
|
|
238
|
-
getAllFiles(src, ".cjs"),
|
|
239
|
-
getAllFiles(src, ".mjs"),
|
|
240
|
-
getAllFiles(src, ".ts"),
|
|
241
|
-
getAllFiles(src, ".tsx"),
|
|
242
|
-
getAllFiles(src, ".vue"),
|
|
243
|
-
getAllFiles(src, ".svelte")
|
|
273
|
+
getAllFiles(deep, src, ".js"),
|
|
274
|
+
getAllFiles(deep, src, ".jsx"),
|
|
275
|
+
getAllFiles(deep, src, ".cjs"),
|
|
276
|
+
getAllFiles(deep, src, ".mjs"),
|
|
277
|
+
getAllFiles(deep, src, ".ts"),
|
|
278
|
+
getAllFiles(deep, src, ".tsx"),
|
|
279
|
+
getAllFiles(deep, src, ".vue"),
|
|
280
|
+
getAllFiles(deep, src, ".svelte")
|
|
244
281
|
]);
|
|
245
282
|
|
|
246
283
|
/**
|
|
247
|
-
*
|
|
284
|
+
* Find all imports and exports
|
|
248
285
|
*/
|
|
249
|
-
export const
|
|
286
|
+
export const findJSImportsExports = async (src, deep) => {
|
|
250
287
|
const allImports = {};
|
|
288
|
+
const allExports = {};
|
|
251
289
|
const errFiles = [];
|
|
252
290
|
try {
|
|
253
|
-
const promiseMap = await getAllSrcJSAndTSFiles(src);
|
|
291
|
+
const promiseMap = await getAllSrcJSAndTSFiles(src, deep);
|
|
254
292
|
const srcFiles = promiseMap.flatMap((d) => d);
|
|
255
293
|
for (const file of srcFiles) {
|
|
256
294
|
try {
|
|
257
|
-
parseFileASTTree(src, file, allImports);
|
|
295
|
+
parseFileASTTree(src, file, allImports, allExports);
|
|
258
296
|
} catch (err) {
|
|
259
297
|
errFiles.push(file);
|
|
260
298
|
}
|
|
261
299
|
}
|
|
262
|
-
return allImports;
|
|
300
|
+
return { allImports, allExports };
|
|
263
301
|
} catch (err) {
|
|
264
|
-
return allImports;
|
|
302
|
+
return { allImports, allExports };
|
|
265
303
|
}
|
|
266
304
|
};
|
package/bin/cdxgen.js
CHANGED
|
@@ -193,7 +193,7 @@ const args = yargs(hideBin(process.argv))
|
|
|
193
193
|
})
|
|
194
194
|
.option("filter", {
|
|
195
195
|
description:
|
|
196
|
-
"Filter components containing this word in purl. Multiple values allowed."
|
|
196
|
+
"Filter components containing this word in purl or component.properties.value. Multiple values allowed."
|
|
197
197
|
})
|
|
198
198
|
.option("only", {
|
|
199
199
|
description:
|
package/index.js
CHANGED
|
@@ -133,7 +133,7 @@ const selfPJson = JSON.parse(
|
|
|
133
133
|
readFileSync(join(dirName, "package.json"), "utf-8")
|
|
134
134
|
);
|
|
135
135
|
const _version = selfPJson.version;
|
|
136
|
-
import {
|
|
136
|
+
import { findJSImportsExports } from "./analyzer.js";
|
|
137
137
|
import { gte, lte } from "semver";
|
|
138
138
|
import {
|
|
139
139
|
getPkgPathList,
|
|
@@ -1795,6 +1795,7 @@ export const createNodejsBom = async (path, options) => {
|
|
|
1795
1795
|
}
|
|
1796
1796
|
}
|
|
1797
1797
|
let allImports = {};
|
|
1798
|
+
let allExports = {};
|
|
1798
1799
|
if (
|
|
1799
1800
|
!["docker", "oci", "container", "os"].includes(options.projectType) &&
|
|
1800
1801
|
!options.noBabel
|
|
@@ -1804,7 +1805,9 @@ export const createNodejsBom = async (path, options) => {
|
|
|
1804
1805
|
`Performing babel-based package usage analysis with source code at ${path}`
|
|
1805
1806
|
);
|
|
1806
1807
|
}
|
|
1807
|
-
|
|
1808
|
+
const retData = await findJSImportsExports(path, options.deep);
|
|
1809
|
+
allImports = retData.allImports;
|
|
1810
|
+
allExports = retData.allExports;
|
|
1808
1811
|
}
|
|
1809
1812
|
const yarnLockFiles = getAllFiles(
|
|
1810
1813
|
path,
|
|
@@ -1976,7 +1979,12 @@ export const createNodejsBom = async (path, options) => {
|
|
|
1976
1979
|
if (existsSync(swFile)) {
|
|
1977
1980
|
let pkgList = await parseNodeShrinkwrap(swFile);
|
|
1978
1981
|
if (allImports && Object.keys(allImports).length) {
|
|
1979
|
-
pkgList = addEvidenceForImports(
|
|
1982
|
+
pkgList = await addEvidenceForImports(
|
|
1983
|
+
pkgList,
|
|
1984
|
+
allImports,
|
|
1985
|
+
allExports,
|
|
1986
|
+
options.deep
|
|
1987
|
+
);
|
|
1980
1988
|
}
|
|
1981
1989
|
return buildBomNSData(options, pkgList, "npm", {
|
|
1982
1990
|
allImports,
|
|
@@ -1986,10 +1994,16 @@ export const createNodejsBom = async (path, options) => {
|
|
|
1986
1994
|
} else if (existsSync(pnpmLock)) {
|
|
1987
1995
|
let pkgList = await parsePnpmLock(pnpmLock);
|
|
1988
1996
|
if (allImports && Object.keys(allImports).length) {
|
|
1989
|
-
pkgList = addEvidenceForImports(
|
|
1997
|
+
pkgList = await addEvidenceForImports(
|
|
1998
|
+
pkgList,
|
|
1999
|
+
allImports,
|
|
2000
|
+
allExports,
|
|
2001
|
+
options.deep
|
|
2002
|
+
);
|
|
1990
2003
|
}
|
|
1991
2004
|
return buildBomNSData(options, pkgList, "npm", {
|
|
1992
2005
|
allImports,
|
|
2006
|
+
allExports,
|
|
1993
2007
|
src: path,
|
|
1994
2008
|
filename: "pnpm-lock.yaml"
|
|
1995
2009
|
});
|
|
@@ -2140,7 +2154,12 @@ export const createNodejsBom = async (path, options) => {
|
|
|
2140
2154
|
// We need to set this to force our version to be used rather than the directory name based one.
|
|
2141
2155
|
options.parentComponent = parentComponent;
|
|
2142
2156
|
if (allImports && Object.keys(allImports).length) {
|
|
2143
|
-
pkgList = addEvidenceForImports(
|
|
2157
|
+
pkgList = await addEvidenceForImports(
|
|
2158
|
+
pkgList,
|
|
2159
|
+
allImports,
|
|
2160
|
+
allExports,
|
|
2161
|
+
options.deep
|
|
2162
|
+
);
|
|
2144
2163
|
}
|
|
2145
2164
|
return buildBomNSData(options, pkgList, "npm", {
|
|
2146
2165
|
src: path,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyclonedx/cdxgen",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.4",
|
|
4
4
|
"description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
|
|
5
5
|
"homepage": "http://github.com/cyclonedx/cdxgen",
|
|
6
6
|
"author": "Prabhu Subramanian <prabhu@appthreat.com>",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"find-up": "7.0.0",
|
|
66
66
|
"glob": "^10.3.10",
|
|
67
67
|
"global-agent": "^3.0.0",
|
|
68
|
-
"got": "14.
|
|
68
|
+
"got": "14.1.0",
|
|
69
69
|
"iconv-lite": "^0.6.3",
|
|
70
70
|
"js-yaml": "^4.1.0",
|
|
71
71
|
"jws": "^4.0.0",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"yargs": "^17.7.2"
|
|
83
83
|
},
|
|
84
84
|
"optionalDependencies": {
|
|
85
|
-
"@appthreat/atom": "2.0.
|
|
85
|
+
"@appthreat/atom": "2.0.7",
|
|
86
86
|
"@appthreat/cdx-proto": "^0.0.4",
|
|
87
87
|
"@cyclonedx/cdxgen-plugins-bin": "^1.5.8",
|
|
88
88
|
"@cyclonedx/cdxgen-plugins-bin-windows-amd64": "^1.5.8",
|
package/postgen.js
CHANGED
|
@@ -27,7 +27,10 @@ export const filterBom = (bomJson, options) => {
|
|
|
27
27
|
}
|
|
28
28
|
let purlfiltered = false;
|
|
29
29
|
for (const filterstr of options.only) {
|
|
30
|
-
if (
|
|
30
|
+
if (
|
|
31
|
+
filterstr.length &&
|
|
32
|
+
!comp.purl.toLowerCase().includes(filterstr.toLowerCase())
|
|
33
|
+
) {
|
|
31
34
|
filtered = true;
|
|
32
35
|
purlfiltered = true;
|
|
33
36
|
continue;
|
|
@@ -42,11 +45,29 @@ export const filterBom = (bomJson, options) => {
|
|
|
42
45
|
}
|
|
43
46
|
let purlfiltered = false;
|
|
44
47
|
for (const filterstr of options.filter) {
|
|
45
|
-
|
|
48
|
+
// Check the purl
|
|
49
|
+
if (
|
|
50
|
+
filterstr.length &&
|
|
51
|
+
comp.purl.toLowerCase().includes(filterstr.toLowerCase())
|
|
52
|
+
) {
|
|
46
53
|
filtered = true;
|
|
47
54
|
purlfiltered = true;
|
|
48
55
|
continue;
|
|
49
56
|
}
|
|
57
|
+
// Look for any properties value matching the string
|
|
58
|
+
const properties = comp.properties || [];
|
|
59
|
+
for (const aprop of properties) {
|
|
60
|
+
if (
|
|
61
|
+
filterstr.length &&
|
|
62
|
+
aprop &&
|
|
63
|
+
aprop.value &&
|
|
64
|
+
aprop.value.toLowerCase().includes(filterstr.toLowerCase())
|
|
65
|
+
) {
|
|
66
|
+
filtered = true;
|
|
67
|
+
purlfiltered = true;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
50
71
|
}
|
|
51
72
|
if (!purlfiltered) {
|
|
52
73
|
newPkgMap[comp["bom-ref"]] = comp;
|
package/postgen.test.js
CHANGED
|
@@ -41,14 +41,14 @@ test("filter bom tests2", () => {
|
|
|
41
41
|
throw new Error(`${comp.purl} is unexpected`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
expect(newBom.components.length).toEqual(
|
|
44
|
+
expect(newBom.components.length).toEqual(158);
|
|
45
45
|
newBom = filterBom(bomJson, { filter: ["apache", "json"] });
|
|
46
46
|
for (const comp of newBom.components) {
|
|
47
47
|
if (comp.purl.includes("apache") || comp.purl.includes("json")) {
|
|
48
48
|
throw new Error(`${comp.purl} is unexpected`);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
expect(newBom.components.length).toEqual(
|
|
51
|
+
expect(newBom.components.length).toEqual(135);
|
|
52
52
|
expect(newBom.compositions).toBeUndefined();
|
|
53
53
|
newBom = filterBom(bomJson, {
|
|
54
54
|
only: ["org.springframework"],
|
|
@@ -60,7 +60,7 @@ test("filter bom tests2", () => {
|
|
|
60
60
|
throw new Error(`${comp.purl} is unexpected`);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
expect(newBom.components.length).toEqual(
|
|
63
|
+
expect(newBom.components.length).toEqual(29);
|
|
64
64
|
expect(newBom.compositions).toEqual([
|
|
65
65
|
{
|
|
66
66
|
aggregate: "incomplete_first_party_only",
|
package/utils.js
CHANGED
|
@@ -559,13 +559,29 @@ export const parsePkgJson = async (pkgJsonFile, simple = false) => {
|
|
|
559
559
|
null,
|
|
560
560
|
null
|
|
561
561
|
).toString();
|
|
562
|
+
const author = pkgData.author;
|
|
563
|
+
const authorString =
|
|
564
|
+
author instanceof Object
|
|
565
|
+
? `${author.name}${author.email ? ` <${author.email}>` : ""}${
|
|
566
|
+
author.url ? ` (${author.url})` : ""
|
|
567
|
+
}`
|
|
568
|
+
: author;
|
|
562
569
|
const apkg = {
|
|
563
570
|
name,
|
|
564
571
|
group,
|
|
565
572
|
version: pkgData.version,
|
|
573
|
+
description: pkgData.description,
|
|
566
574
|
purl: purl,
|
|
567
|
-
"bom-ref": decodeURIComponent(purl)
|
|
575
|
+
"bom-ref": decodeURIComponent(purl),
|
|
576
|
+
author: authorString,
|
|
577
|
+
license: pkgData.license
|
|
568
578
|
};
|
|
579
|
+
if (pkgData.homepage) {
|
|
580
|
+
apkg.homepage = { url: pkgData.homepage };
|
|
581
|
+
}
|
|
582
|
+
if (pkgData.repository && pkgData.repository.url) {
|
|
583
|
+
apkg.repository = { url: pkgData.repository.url };
|
|
584
|
+
}
|
|
569
585
|
if (!simple) {
|
|
570
586
|
apkg.properties = [
|
|
571
587
|
{
|
|
@@ -720,6 +736,12 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
|
|
|
720
736
|
value: node.resolved
|
|
721
737
|
});
|
|
722
738
|
}
|
|
739
|
+
if (node.location) {
|
|
740
|
+
pkg.properties.push({
|
|
741
|
+
name: "LocalNodeModulesPath",
|
|
742
|
+
value: node.location
|
|
743
|
+
});
|
|
744
|
+
}
|
|
723
745
|
}
|
|
724
746
|
const packageLicense = node.package.license;
|
|
725
747
|
if (packageLicense) {
|
|
@@ -1185,8 +1207,11 @@ export const parseYarnLock = async function (yarnLockFile) {
|
|
|
1185
1207
|
}
|
|
1186
1208
|
// checksum used by yarn 2/3 is hex encoded
|
|
1187
1209
|
if (l.startsWith("checksum")) {
|
|
1210
|
+
// in some cases yarn 4 will add a prefix to the checksum, containing the cachekey and compression level
|
|
1211
|
+
// example: 10c0/53c2b231a61a46792b39a0d43bc4f4f77...
|
|
1212
|
+
const checksum = parts[1].split("/").pop();
|
|
1188
1213
|
integrity =
|
|
1189
|
-
"sha512-" + Buffer.from(
|
|
1214
|
+
"sha512-" + Buffer.from(checksum, "hex").toString("base64");
|
|
1190
1215
|
}
|
|
1191
1216
|
if (l.startsWith("resolved")) {
|
|
1192
1217
|
const tmpB = parts[1].split("#");
|
|
@@ -8132,9 +8157,16 @@ export const parsePackageJsonName = (name) => {
|
|
|
8132
8157
|
*
|
|
8133
8158
|
* @param {array} pkgList List of package
|
|
8134
8159
|
* @param {object} allImports Import statements object with package name as key and an object with file and location details
|
|
8160
|
+
* @param {object} allExports Exported modules if available from node_modules
|
|
8135
8161
|
*/
|
|
8136
|
-
export const addEvidenceForImports = (
|
|
8162
|
+
export const addEvidenceForImports = async (
|
|
8163
|
+
pkgList,
|
|
8164
|
+
allImports,
|
|
8165
|
+
allExports,
|
|
8166
|
+
deep
|
|
8167
|
+
) => {
|
|
8137
8168
|
const impPkgs = Object.keys(allImports);
|
|
8169
|
+
const exportedPkgs = Object.keys(allExports);
|
|
8138
8170
|
for (const pkg of pkgList) {
|
|
8139
8171
|
if (impPkgs && impPkgs.length) {
|
|
8140
8172
|
// Assume that all packages are optional until we see an evidence
|
|
@@ -8155,6 +8187,47 @@ export const addEvidenceForImports = (pkgList, allImports) => {
|
|
|
8155
8187
|
find_pkg.startsWith(alias) &&
|
|
8156
8188
|
(find_pkg.length === alias.length || find_pkg[alias.length] === "/")
|
|
8157
8189
|
);
|
|
8190
|
+
const all_exports = exportedPkgs.filter((find_pkg) =>
|
|
8191
|
+
find_pkg.startsWith(alias)
|
|
8192
|
+
);
|
|
8193
|
+
if (all_exports && all_exports.length) {
|
|
8194
|
+
let exportedModules = new Set(all_exports);
|
|
8195
|
+
pkg.properties = pkg.properties || [];
|
|
8196
|
+
for (const subevidence of all_exports) {
|
|
8197
|
+
const evidences = allExports[subevidence];
|
|
8198
|
+
for (const evidence of evidences) {
|
|
8199
|
+
if (evidence && Object.keys(evidence).length) {
|
|
8200
|
+
if (evidence.exportedModules.length > 1) {
|
|
8201
|
+
for (const aexpsubm of evidence.exportedModules) {
|
|
8202
|
+
// Be selective on the submodule names
|
|
8203
|
+
if (
|
|
8204
|
+
!evidence.importedAs
|
|
8205
|
+
.toLowerCase()
|
|
8206
|
+
.includes(aexpsubm.toLowerCase()) &&
|
|
8207
|
+
!alias.endsWith(aexpsubm)
|
|
8208
|
+
) {
|
|
8209
|
+
// Store both the short and long form of the exported sub modules
|
|
8210
|
+
if (aexpsubm.length > 3) {
|
|
8211
|
+
exportedModules.add(aexpsubm);
|
|
8212
|
+
}
|
|
8213
|
+
exportedModules.add(
|
|
8214
|
+
`${evidence.importedAs.replace("./", "")}/${aexpsubm}`
|
|
8215
|
+
);
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
exportedModules = Array.from(exportedModules);
|
|
8223
|
+
if (exportedModules.length) {
|
|
8224
|
+
pkg.properties.push({
|
|
8225
|
+
name: "ExportedModules",
|
|
8226
|
+
value: exportedModules.join(",")
|
|
8227
|
+
});
|
|
8228
|
+
}
|
|
8229
|
+
}
|
|
8230
|
+
// Identify all the imported modules of a component
|
|
8158
8231
|
if (impPkgs.includes(alias) || all_includes.length) {
|
|
8159
8232
|
let importedModules = new Set();
|
|
8160
8233
|
pkg.scope = "required";
|
|
@@ -8175,7 +8248,9 @@ export const addEvidenceForImports = (pkgList, allImports) => {
|
|
|
8175
8248
|
continue;
|
|
8176
8249
|
}
|
|
8177
8250
|
// Store both the short and long form of the imported sub modules
|
|
8178
|
-
|
|
8251
|
+
if (importedSm.length > 3) {
|
|
8252
|
+
importedModules.add(importedSm);
|
|
8253
|
+
}
|
|
8179
8254
|
importedModules.add(`${evidence.importedAs}/${importedSm}`);
|
|
8180
8255
|
}
|
|
8181
8256
|
}
|
|
@@ -8191,8 +8266,35 @@ export const addEvidenceForImports = (pkgList, allImports) => {
|
|
|
8191
8266
|
}
|
|
8192
8267
|
break;
|
|
8193
8268
|
}
|
|
8194
|
-
|
|
8195
|
-
|
|
8269
|
+
// Capture metadata such as description from local node_modules in deep mode
|
|
8270
|
+
if (deep && !pkg.description && pkg.properties) {
|
|
8271
|
+
let localNodeModulesPath = undefined;
|
|
8272
|
+
for (const aprop of pkg.properties) {
|
|
8273
|
+
if (aprop.name === "LocalNodeModulesPath") {
|
|
8274
|
+
localNodeModulesPath = resolve(join(aprop.value, "package.json"));
|
|
8275
|
+
break;
|
|
8276
|
+
}
|
|
8277
|
+
}
|
|
8278
|
+
if (localNodeModulesPath && existsSync(localNodeModulesPath)) {
|
|
8279
|
+
const lnmPkgList = await parsePkgJson(localNodeModulesPath, true);
|
|
8280
|
+
if (lnmPkgList && lnmPkgList.length === 1) {
|
|
8281
|
+
const lnmMetadata = lnmPkgList[0];
|
|
8282
|
+
if (lnmMetadata && Object.keys(lnmMetadata).length) {
|
|
8283
|
+
pkg.description = lnmMetadata.description;
|
|
8284
|
+
pkg.author = lnmMetadata.author;
|
|
8285
|
+
pkg.license = lnmMetadata.license;
|
|
8286
|
+
pkg.homepage = lnmMetadata.homepage;
|
|
8287
|
+
pkg.repository = lnmMetadata.repository;
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8290
|
+
}
|
|
8291
|
+
}
|
|
8292
|
+
} // for alias
|
|
8293
|
+
// Trim the properties
|
|
8294
|
+
pkg.properties = pkg.properties.filter(
|
|
8295
|
+
(p) => p.name !== "LocalNodeModulesPath"
|
|
8296
|
+
);
|
|
8297
|
+
} // for pkg
|
|
8196
8298
|
return pkgList;
|
|
8197
8299
|
};
|
|
8198
8300
|
|
package/utils.test.js
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
parsePom,
|
|
39
39
|
getMvnMetadata,
|
|
40
40
|
getLicenses,
|
|
41
|
+
parsePkgJson,
|
|
41
42
|
parsePkgLock,
|
|
42
43
|
parseBowerJson,
|
|
43
44
|
parseNodeShrinkwrap,
|
|
@@ -1719,6 +1720,11 @@ test("get licenses", () => {
|
|
|
1719
1720
|
]);
|
|
1720
1721
|
});
|
|
1721
1722
|
|
|
1723
|
+
test("parsePkgJson", async () => {
|
|
1724
|
+
const pkgList = await parsePkgJson("./package.json", true);
|
|
1725
|
+
expect(pkgList.length).toEqual(1);
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1722
1728
|
test("parsePkgLock v1", async () => {
|
|
1723
1729
|
const parsedList = await parsePkgLock(
|
|
1724
1730
|
"./test/data/package-json/v1/package-lock.json"
|
|
@@ -2399,6 +2405,18 @@ test("parseYarnLock", async () => {
|
|
|
2399
2405
|
expect(parsedList.pkgList[0]["bom-ref"]).toEqual(
|
|
2400
2406
|
"pkg:npm/@aashutoshrathi/word-wrap@1.2.6"
|
|
2401
2407
|
);
|
|
2408
|
+
parsedList = await parseYarnLock("./test/data/yarn_locks/yarnv4.1.lock");
|
|
2409
|
+
expect(parsedList.pkgList.length).toEqual(861);
|
|
2410
|
+
expect(parsedList.dependenciesList.length).toEqual(858);
|
|
2411
|
+
expect(parsedList.pkgList[0].purl).toEqual(
|
|
2412
|
+
"pkg:npm/%40aashutoshrathi/word-wrap@1.2.6"
|
|
2413
|
+
);
|
|
2414
|
+
expect(parsedList.pkgList[0]["bom-ref"]).toEqual(
|
|
2415
|
+
"pkg:npm/@aashutoshrathi/word-wrap@1.2.6"
|
|
2416
|
+
);
|
|
2417
|
+
expect(parsedList.pkgList[0]._integrity).toEqual(
|
|
2418
|
+
"sha512-U8KyMaYaRnkrOaDUO8T093a7RUKqV+4EkwZ2gC5VASgsL8iqwU5M0fESD/i1Jha2/1q1Oa0wqiJ31yZES3Fhnw=="
|
|
2419
|
+
);
|
|
2402
2420
|
});
|
|
2403
2421
|
|
|
2404
2422
|
test("parseComposerLock", () => {
|