@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 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 allowed. [array]
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(file, extn, readdirSync(file), result, regex);
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 = (allImports, src, file, pathnode, specifiers = []) => {
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
- occurrence.isExternal = false;
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
- * Where Node CLI runs from.
284
+ * Find all imports and exports
248
285
  */
249
- export const findJSImports = async (src) => {
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 { findJSImports } from "./analyzer.js";
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
- allImports = await findJSImports(path);
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(pkgList, allImports);
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(pkgList, allImports);
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(pkgList, allImports);
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.2",
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.0.0",
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.6",
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 (filterstr.length && !comp.purl.toLowerCase().includes(filterstr)) {
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
- if (filterstr.length && comp.purl.toLowerCase().includes(filterstr)) {
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(177);
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(172);
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(37);
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(parts[1], "hex").toString("base64");
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 = (pkgList, allImports) => {
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
- importedModules.add(importedSm);
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", () => {