@cyclonedx/cdxgen 11.2.2 → 11.2.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.
@@ -53,13 +53,13 @@ import { xml2js } from "xml-js";
53
53
  import { getTreeWithPlugin } from "../managers/piptree.js";
54
54
  import { thoughtLog } from "./logger.js";
55
55
 
56
- let url = import.meta.url;
57
- if (!url.startsWith("file://")) {
56
+ let url = import.meta?.url;
57
+ if (url && !url.startsWith("file://")) {
58
58
  url = new URL(`file://${import.meta.url}`).toString();
59
59
  }
60
60
  // TODO: verify if this is a good method (Prabhu)
61
61
  // this is due to dirNameStr being "cdxgen/lib/helpers" which causes errors
62
- export const dirNameStr = import.meta
62
+ export const dirNameStr = url
63
63
  ? dirname(dirname(dirname(fileURLToPath(url))))
64
64
  : __dirname;
65
65
 
@@ -159,8 +159,7 @@ const RUBY_KNOWN_MODULES = JSON.parse(
159
159
  // Debug mode flag
160
160
  export const DEBUG_MODE =
161
161
  ["debug", "verbose"].includes(process.env.CDXGEN_DEBUG_MODE) ||
162
- process.env.SCAN_DEBUG_MODE === "debug" ||
163
- process.env.NODE_ENV === "development";
162
+ process.env.SCAN_DEBUG_MODE === "debug";
164
163
 
165
164
  // Timeout milliseconds. Default 20 mins
166
165
  export const TIMEOUT_MS =
@@ -334,6 +333,7 @@ export const PROJECT_TYPE_ALIASES = {
334
333
  "java21",
335
334
  "java22",
336
335
  "java23",
336
+ "java24",
337
337
  "groovy",
338
338
  "kotlin",
339
339
  "kt",
@@ -345,6 +345,7 @@ export const PROJECT_TYPE_ALIASES = {
345
345
  "sbt",
346
346
  "bazel",
347
347
  "quarkus",
348
+ "mill",
348
349
  ],
349
350
  android: ["android", "apk", "aab"],
350
351
  jar: ["jar", "war", "ear"],
@@ -467,6 +468,7 @@ export const PROJECT_TYPE_ALIASES = {
467
468
  binary: ["binary", "blint"],
468
469
  oci: ["docker", "oci", "container", "podman"],
469
470
  cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
471
+ scala: ["scala", "scala3", "sbt", "mill"],
470
472
  };
471
473
 
472
474
  // Package manager aliases
@@ -3412,6 +3414,140 @@ export function parseMavenTree(rawOutput, pomFile) {
3412
3414
  };
3413
3415
  }
3414
3416
 
3417
+ /**
3418
+ * Parse mill dependencies from file
3419
+ *
3420
+ * @param {string} module name of the module
3421
+ * @param {map} dependencies the parsed dependencies
3422
+ * @param {map} relations a map containing all relations
3423
+ * @param {string} millRootPath root of the project
3424
+ *
3425
+ * @returns the bom-ref of the module
3426
+ */
3427
+ export function parseMillDependency(
3428
+ module,
3429
+ dependencies,
3430
+ relations,
3431
+ millRootPath,
3432
+ ) {
3433
+ const treeRegex = /^(?<treeIndentation>(?:[?├│└─ ]{3})+)(?<dependency>.*)/m;
3434
+ const ESC = "\\x1B";
3435
+ const versionRegex = new RegExp(
3436
+ `^(?:${ESC}\\[\\d+m.+ -> )?(?<version>[^\\ ${ESC}]+)(?:.*${ESC}\\[0m)?`,
3437
+ "m",
3438
+ );
3439
+ const levelCache = new Map();
3440
+ const moduleComponent = completeComponent({
3441
+ name: module,
3442
+ version: "latest",
3443
+ });
3444
+ dependencies.set(moduleComponent["bom-ref"], moduleComponent);
3445
+ relations.set(moduleComponent["bom-ref"], []);
3446
+ levelCache.set(0, moduleComponent["bom-ref"]);
3447
+ let moduleFilePath = module;
3448
+ const versionNumbers = [];
3449
+ let indexOfBracket = moduleFilePath.indexOf("[");
3450
+ for (let versionIndex = 0; indexOfBracket !== -1; versionIndex++) {
3451
+ // Special handling for modules called something like 'main.init.sbt.models[2.12.20]'
3452
+ // This needs to be turned into a path like 'main/init/sbt/models/2.12.20'
3453
+ // However, since all other periods need to be changed to slashes, this needs something more...
3454
+ versionNumbers.push(
3455
+ moduleFilePath.substring(
3456
+ indexOfBracket + 1,
3457
+ moduleFilePath.indexOf("]", indexOfBracket + 1),
3458
+ ),
3459
+ );
3460
+ moduleFilePath = moduleFilePath.replace(
3461
+ `[${versionNumbers[versionIndex]}]`,
3462
+ "[]",
3463
+ );
3464
+ indexOfBracket = moduleFilePath.indexOf("[", indexOfBracket + 1);
3465
+ }
3466
+ moduleFilePath = moduleFilePath.replaceAll(".", "/");
3467
+ for (const versionNumber of versionNumbers) {
3468
+ // Now put the versions we removed above back into the path and replace the brackets at the same time
3469
+ moduleFilePath = moduleFilePath.replace("[]", `/${versionNumber}/`);
3470
+ }
3471
+ moduleFilePath = resolve(
3472
+ millRootPath,
3473
+ "out",
3474
+ moduleFilePath,
3475
+ "ivyDepsTree.log",
3476
+ );
3477
+ moduleComponent.properties = [
3478
+ {
3479
+ name: "SrcFile",
3480
+ value: moduleFilePath,
3481
+ },
3482
+ ];
3483
+ moduleComponent.evidence = {
3484
+ identity: {
3485
+ field: "purl",
3486
+ confidence: 0.6,
3487
+ methods: [
3488
+ {
3489
+ technique: "manifest-analysis",
3490
+ confidence: 0.6,
3491
+ value: moduleFilePath,
3492
+ },
3493
+ ],
3494
+ },
3495
+ };
3496
+ const dependencyTreeLog = readFileSync(moduleFilePath, "utf-8");
3497
+ const dependencyTreeLines = dependencyTreeLog
3498
+ .trim()
3499
+ .split("\n")
3500
+ .map((dependency) => dependency.replaceAll("\r", ""));
3501
+ for (const line of dependencyTreeLines) {
3502
+ const match = treeRegex.exec(line);
3503
+ if (match === null) {
3504
+ continue;
3505
+ }
3506
+ const level = match.groups.treeIndentation.length / 3;
3507
+ let group;
3508
+ let name;
3509
+ let version;
3510
+ if (match.groups.dependency.indexOf(":") === -1) {
3511
+ name = match.groups.dependency;
3512
+ version = "latest";
3513
+ } else {
3514
+ [group, name, version] = match.groups.dependency.split(":");
3515
+ version = versionRegex.exec(version).groups.version;
3516
+ }
3517
+ const component = completeComponent({
3518
+ group,
3519
+ name,
3520
+ version,
3521
+ });
3522
+ if (!dependencies.has(component["bom-ref"])) {
3523
+ dependencies.set(component["bom-ref"], component);
3524
+ relations.set(component["bom-ref"], []);
3525
+ }
3526
+ if (
3527
+ !relations.get(levelCache.get(level - 1)).includes(component["bom-ref"])
3528
+ ) {
3529
+ relations.get(levelCache.get(level - 1)).push(component["bom-ref"]);
3530
+ }
3531
+ levelCache.set(level, component["bom-ref"]);
3532
+ }
3533
+ return moduleComponent["bom-ref"];
3534
+ }
3535
+
3536
+ function completeComponent(component) {
3537
+ component["type"] = component.group ? "library" : "application";
3538
+ const purl = new PackageURL(
3539
+ "maven",
3540
+ component.group,
3541
+ component.name,
3542
+ component.version,
3543
+ { type: "jar" },
3544
+ null,
3545
+ ).toString();
3546
+ component["purl"] = purl;
3547
+ component["bom-ref"] = decodeURIComponent(purl);
3548
+ return component;
3549
+ }
3550
+
3415
3551
  /**
3416
3552
  * Parse gradle dependencies output
3417
3553
  * @param {string} rawOutput Raw string output
@@ -4847,7 +4983,7 @@ export function parsePyProjectTomlFile(tomlFile) {
4847
4983
  });
4848
4984
  }
4849
4985
  }
4850
- if (tomlData?.tool?.poetry) {
4986
+ if (tomlData?.tool?.poetry?.dependencies) {
4851
4987
  for (const adep of Object.keys(tomlData?.tool?.poetry?.dependencies)) {
4852
4988
  if (
4853
4989
  ![
@@ -5249,7 +5385,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
5249
5385
  }
5250
5386
  }
5251
5387
  }
5252
- } else if (pkg.dependencies && Object.keys(apkg.dependencies).length) {
5388
+ } else if (apkg.dependencies && Object.keys(apkg.dependencies).length) {
5253
5389
  for (const apkgDep of Object.keys(apkg.dependencies)) {
5254
5390
  depsMap[pkg["bom-ref"]].add(existingPkgMap[apkgDep] || apkgDep);
5255
5391
  }
@@ -5517,7 +5653,7 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
5517
5653
  export async function getPyModules(src, epkgList, options) {
5518
5654
  const allImports = {};
5519
5655
  const dependenciesList = [];
5520
- let modList;
5656
+ let modList = [];
5521
5657
  const slicesFile = resolve(
5522
5658
  options.depsSlicesFile || options.usagesSlicesFile,
5523
5659
  );
@@ -9595,9 +9731,8 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
9595
9731
  if (!inputStr) {
9596
9732
  return null;
9597
9733
  }
9598
- const extractNameOperatorVersion = /([\w.-]+)\s*([><=!]+)\s*([\d.]+)/;
9599
- const match = inputStr.match(extractNameOperatorVersion);
9600
-
9734
+ const extractNameOperatorVersion = /([\w.-]+)\s*([><=!]+)\s*(.*)/;
9735
+ let match = inputStr.match(extractNameOperatorVersion);
9601
9736
  if (match) {
9602
9737
  return {
9603
9738
  name: match[1],
@@ -9605,6 +9740,14 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
9605
9740
  version: match[3],
9606
9741
  };
9607
9742
  }
9743
+ match = inputStr.split(" ");
9744
+ if (match && match.length === 3) {
9745
+ return {
9746
+ name: match[1],
9747
+ operator: match[2],
9748
+ version: match[3],
9749
+ };
9750
+ }
9608
9751
  return null;
9609
9752
  }
9610
9753
 
@@ -9662,7 +9805,7 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
9662
9805
  const tversion = tmpParts[1];
9663
9806
  if (
9664
9807
  tname.toLowerCase() === nameOperatorVersion.name.toLowerCase() &&
9665
- tversion === nameOperatorVersion.version
9808
+ tversion === nameOperatorVersion.version.replace("-*", "")
9666
9809
  ) {
9667
9810
  nameToUse = tname;
9668
9811
  matchFound = true;
@@ -11016,9 +11159,6 @@ export async function collectGradleDependencies(
11016
11159
  */
11017
11160
  export async function collectJarNS(jarPath, pomPathMap = {}) {
11018
11161
  const jarNSMapping = {};
11019
- console.log(
11020
- `About to identify class names for all jars in the path ${jarPath}`,
11021
- );
11022
11162
  const env = {
11023
11163
  ...process.env,
11024
11164
  };
@@ -11030,7 +11170,9 @@ export async function collectJarNS(jarPath, pomPathMap = {}) {
11030
11170
  )}`;
11031
11171
  }
11032
11172
  // Parse jar files to get class names
11033
- const jarFiles = getAllFiles(jarPath, "**/*.jar");
11173
+ const jarFiles = jarPath.endsWith(".jar")
11174
+ ? [jarPath]
11175
+ : getAllFiles(jarPath, "**/*.jar");
11034
11176
  if (jarFiles?.length) {
11035
11177
  for (const jf of jarFiles) {
11036
11178
  let pomname =
@@ -11164,7 +11306,9 @@ export async function collectJarNS(jarPath, pomPathMap = {}) {
11164
11306
  console.log(`Unable to determine class names for the jars in ${jarPath}`);
11165
11307
  }
11166
11308
  } else {
11167
- console.log(`${jarPath} did not contain any jars.`);
11309
+ console.log(
11310
+ `${jarPath} did not contain any jars. Try building the project to improve the BOM precision.`,
11311
+ );
11168
11312
  }
11169
11313
  return jarNSMapping;
11170
11314
  }
@@ -11349,6 +11493,31 @@ export function checksumFile(hashName, path) {
11349
11493
  });
11350
11494
  }
11351
11495
 
11496
+ /**
11497
+ * Computes multiple checksum for a file path using the given hash algorithms
11498
+ *
11499
+ * @param {Array[String]} algorithms Array of algorithms
11500
+ * @param {string} path path to file
11501
+ * @returns {Promise<Object>} hashes object
11502
+ */
11503
+ export function multiChecksumFile(algorithms, path) {
11504
+ return new Promise((resolve, reject) => {
11505
+ const hashes = {};
11506
+ for (const alg of algorithms) {
11507
+ hashes[alg] = createHash(alg);
11508
+ }
11509
+ const stream = createReadStream(path);
11510
+ stream.on("error", (err) => reject(err));
11511
+ stream.on("data", (chunk) =>
11512
+ algorithms.forEach((alg) => hashes[alg].update(chunk)),
11513
+ );
11514
+ stream.on("end", () => {
11515
+ algorithms.forEach((alg) => (hashes[alg] = hashes[alg].digest("hex")));
11516
+ resolve(hashes);
11517
+ });
11518
+ });
11519
+ }
11520
+
11352
11521
  /**
11353
11522
  * Method to extract a war or ear file
11354
11523
  *
@@ -11637,6 +11806,14 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
11637
11806
  name: "Namespaces",
11638
11807
  value: jarNSMapping[apkg.purl].namespaces.join("\n"),
11639
11808
  });
11809
+ } else {
11810
+ const tmpJarNSMapping = await collectJarNS(jf);
11811
+ if (tmpJarNSMapping?.[jf]?.namespaces?.length) {
11812
+ apkg.properties.push({
11813
+ name: "Namespaces",
11814
+ value: tmpJarNSMapping[jf].namespaces.join("\n"),
11815
+ });
11816
+ }
11640
11817
  }
11641
11818
  pkgList.push(apkg);
11642
11819
  } else {
@@ -11662,7 +11839,7 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
11662
11839
  if (jarFiles.length !== pkgList.length) {
11663
11840
  if (pkgList.length) {
11664
11841
  console.log(
11665
- `Obtained only ${pkgList.length} components from ${jarFiles.length} jars.`,
11842
+ `Obtained only ${pkgList.length} components from ${jarFiles.length} jars at ${tempDir}.`,
11666
11843
  );
11667
11844
  } else {
11668
11845
  console.log(
@@ -11916,6 +12093,26 @@ export function getGradleCommand(srcPath, rootPath) {
11916
12093
  return gradleCmd;
11917
12094
  }
11918
12095
 
12096
+ /**
12097
+ * Method to return the mill command to use.
12098
+ *
12099
+ * @param {string} srcPath Path to look for mill wrapper
12100
+ */
12101
+ export function getMillCommand(srcPath) {
12102
+ let millCmd = `mill${platform() === "win32" ? ".bat" : ""}`;
12103
+ if (safeExistsSync(join(srcPath, millCmd))) {
12104
+ // Use local mill wrapper if available
12105
+ // Enable execute permission
12106
+ try {
12107
+ chmodSync(join(srcPath, millCmd), 0o775);
12108
+ } catch (e) {
12109
+ // continue regardless of error
12110
+ }
12111
+ millCmd = resolve(join(srcPath, millCmd));
12112
+ }
12113
+ return millCmd;
12114
+ }
12115
+
11919
12116
  /**
11920
12117
  * Method to combine the general gradle arguments, the sub-commands and the sub-commands' arguments in the correct way
11921
12118
  *
@@ -12741,7 +12938,7 @@ export function getAtomCommand() {
12741
12938
  return "atom";
12742
12939
  }
12743
12940
 
12744
- export function executeAtom(src, args) {
12941
+ export function executeAtom(src, args, extra_env = {}) {
12745
12942
  const cwd =
12746
12943
  safeExistsSync(src) && lstatSync(src).isDirectory() ? src : dirname(src);
12747
12944
  let ATOM_BIN = getAtomCommand();
@@ -12758,6 +12955,7 @@ export function executeAtom(src, args) {
12758
12955
  }
12759
12956
  const env = {
12760
12957
  ...process.env,
12958
+ ...extra_env,
12761
12959
  };
12762
12960
  // Atom requires Java >= 21
12763
12961
  if (process.env?.ATOM_JAVA_HOME) {
@@ -68,6 +68,7 @@ import {
68
68
  parseLeiningenData,
69
69
  parseMakeDFile,
70
70
  parseMavenTree,
71
+ parseMillDependency,
71
72
  parseMixLockData,
72
73
  parseNodeShrinkwrap,
73
74
  parseNupkg,
@@ -2455,7 +2456,7 @@ test("parse github actions workflow data", () => {
2455
2456
  dep_list = parseGitHubWorkflowData(
2456
2457
  readFileSync("./.github/workflows/repotests.yml", { encoding: "utf-8" }),
2457
2458
  );
2458
- expect(dep_list.length).toEqual(13);
2459
+ expect(dep_list.length).toEqual(14);
2459
2460
  expect(dep_list[0]).toEqual({
2460
2461
  group: "actions",
2461
2462
  name: "checkout",
@@ -3784,8 +3785,8 @@ test("parsePnpmLock", async () => {
3784
3785
  expect(parsedList.dependenciesList).toHaveLength(462);
3785
3786
  expect(parsedList.pkgList.filter((pkg) => !pkg.scope)).toHaveLength(3);
3786
3787
  parsedList = await parsePnpmLock("./pnpm-lock.yaml");
3787
- expect(parsedList.pkgList.length).toEqual(623);
3788
- expect(parsedList.dependenciesList.length).toEqual(623);
3788
+ expect(parsedList.pkgList.length).toEqual(622);
3789
+ expect(parsedList.dependenciesList.length).toEqual(622);
3789
3790
  expect(parsedList.pkgList[0]).toEqual({
3790
3791
  group: "@ampproject",
3791
3792
  name: "remapping",
@@ -6169,3 +6170,48 @@ test("buildObjectForCocoaPod tests", async () => {
6169
6170
  "bom-ref": "pkg:cocoapods/boost@= 1.59.0#graph-includes",
6170
6171
  });
6171
6172
  });
6173
+
6174
+ test("parseMillDependency test", () => {
6175
+ const millTestDataRoot = "./test/data/mill/";
6176
+ const dependencies = new Map();
6177
+ const relations = new Map();
6178
+
6179
+ expect(dependencies.has("pkg:maven/bar@latest?type=jar")).toBeftoBeFalsy;
6180
+ expect(dependencies.has("pkg:maven/bar.test@latest?type=jar")).toBeftoBeFalsy;
6181
+ expect(dependencies.has("pkg:maven/foo@latest?type=jar")).toBeftoBeFalsy;
6182
+ expect(dependencies.has("pkg:maven/foo.test@latest?type=jar")).toBeftoBeFalsy;
6183
+ expect(dependencies.size).toEqual(0);
6184
+ expect(relations.size).toEqual(0);
6185
+
6186
+ parseMillDependency("bar", dependencies, relations, millTestDataRoot);
6187
+ expect(dependencies.has("pkg:maven/bar@latest?type=jar")).toBeTruthy;
6188
+ expect(dependencies.has("pkg:maven/bar.test@latest?type=jar")).toBeftoBeFalsy;
6189
+ expect(dependencies.has("pkg:maven/foo@latest?type=jar")).toBeftoBeFalsy;
6190
+ expect(dependencies.has("pkg:maven/foo.test@latest?type=jar")).toBeftoBeFalsy;
6191
+ expect(dependencies.size).toEqual(8);
6192
+ expect(relations.size).toEqual(8);
6193
+
6194
+ parseMillDependency("bar.test", dependencies, relations, millTestDataRoot);
6195
+ expect(dependencies.has("pkg:maven/bar@latest?type=jar")).toBeTruthy;
6196
+ expect(dependencies.has("pkg:maven/bar.test@latest?type=jar")).toBeTruthy;
6197
+ expect(dependencies.has("pkg:maven/foo@latest?type=jar")).toBeftoBeFalsy;
6198
+ expect(dependencies.has("pkg:maven/foo.test@latest?type=jar")).toBeftoBeFalsy;
6199
+ expect(dependencies.size).toEqual(13);
6200
+ expect(relations.size).toEqual(13);
6201
+
6202
+ parseMillDependency("foo", dependencies, relations, millTestDataRoot);
6203
+ expect(dependencies.has("pkg:maven/bar@latest?type=jar")).toBeTruthy;
6204
+ expect(dependencies.has("pkg:maven/bar.test@latest?type=jar")).toBeTruthy;
6205
+ expect(dependencies.has("pkg:maven/foo@latest?type=jar")).toBeTruthy;
6206
+ expect(dependencies.has("pkg:maven/foo.test@latest?type=jar")).toBeftoBeFalsy;
6207
+ expect(dependencies.size).toEqual(14);
6208
+ expect(relations.size).toEqual(14);
6209
+
6210
+ parseMillDependency("foo.test", dependencies, relations, millTestDataRoot);
6211
+ expect(dependencies.has("pkg:maven/bar@latest?type=jar")).toBeTruthy;
6212
+ expect(dependencies.has("pkg:maven/bar.test@latest?type=jar")).toBeTruthy;
6213
+ expect(dependencies.has("pkg:maven/foo@latest?type=jar")).toBeTruthy;
6214
+ expect(dependencies.has("pkg:maven/foo.test@latest?type=jar")).toBeTruthy;
6215
+ expect(dependencies.size).toEqual(15);
6216
+ expect(relations.size).toEqual(15);
6217
+ });
@@ -7,10 +7,7 @@ import { DEBUG_MODE, dirNameStr, isPartialTree } from "./utils.js";
7
7
 
8
8
  import { URL } from "node:url";
9
9
  import { thoughtLog } from "./logger.js";
10
- let url = import.meta.url;
11
- if (!url.startsWith("file://")) {
12
- url = new URL(`file://${import.meta.url}`).toString();
13
- }
10
+
14
11
  const dirName = dirNameStr;
15
12
 
16
13
  /**