@cyclonedx/cdxgen 11.1.9 → 11.2.0

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/bin/cdxgen.js CHANGED
@@ -579,13 +579,25 @@ const applyAdvancedOptions = (options) => {
579
579
  break;
580
580
  }
581
581
  // When the user specifies source-code-analysis as a technique, then enable deep and evidence mode.
582
- if (
583
- options?.technique &&
584
- Array.isArray(options.technique) &&
585
- options?.technique?.includes("source-code-analysis")
586
- ) {
587
- options.deep = true;
588
- options.evidence = true;
582
+ if (options?.technique && Array.isArray(options.technique)) {
583
+ if (options?.technique?.includes("source-code-analysis")) {
584
+ options.deep = true;
585
+ options.evidence = true;
586
+ }
587
+ if (options.technique.length === 1) {
588
+ thoughtLog(
589
+ `Wait, the user wants me to use only the following technique: '${options.technique.join(", ")}'.`,
590
+ );
591
+ } else {
592
+ thoughtLog(
593
+ `Alright, I will use only the following techniques: '${options.technique.join(", ")}' for the final BOM.`,
594
+ );
595
+ }
596
+ }
597
+ if (!options.installDeps) {
598
+ thoughtLog(
599
+ "I must avoid any package installations and focus solely on the available artefacts, such as lock files.",
600
+ );
589
601
  }
590
602
  return options;
591
603
  };
@@ -764,7 +776,11 @@ const checkPermissions = (filePath, options) => {
764
776
  prepareEnv(filePath, options);
765
777
  thoughtLog("Getting ready to generate the BOM ⚡️.");
766
778
  let bomNSData = (await createBom(filePath, options)) || {};
767
- thoughtLog("Tweaking the generated BOM data. Nearly there.");
779
+ if (bomNSData?.bomJson) {
780
+ thoughtLog(
781
+ "Tweaking the generated BOM data with useful annotations and properties.",
782
+ );
783
+ }
768
784
  // Add extra metadata and annotations with post processing
769
785
  bomNSData = postProcess(bomNSData, options);
770
786
  if (
@@ -972,7 +988,7 @@ const checkPermissions = (filePath, options) => {
972
988
  }
973
989
  }
974
990
  // Perform automatic validation
975
- if (options.validate) {
991
+ if (options.validate && bomNSData?.bomJson) {
976
992
  thoughtLog("Wait, let's check the generated BOM file for any issues.");
977
993
  if (!validateBom(bomNSData.bomJson)) {
978
994
  process.exit(1);
package/bin/repl.js CHANGED
@@ -137,6 +137,19 @@ cdxgenRepl.defineCommand("import", {
137
137
  this.displayPrompt();
138
138
  },
139
139
  });
140
+ cdxgenRepl.defineCommand("summary", {
141
+ help: "summarize an existing BOM",
142
+ action() {
143
+ if (sbom) {
144
+ printSummary(sbom);
145
+ } else {
146
+ console.log(
147
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM",
148
+ );
149
+ }
150
+ this.displayPrompt();
151
+ },
152
+ });
140
153
  cdxgenRepl.defineCommand("exit", {
141
154
  help: "exit",
142
155
  action() {
@@ -162,13 +175,13 @@ cdxgenRepl.defineCommand("search", {
162
175
  if (sbom) {
163
176
  if (searchStr) {
164
177
  try {
165
- const originalSearchString = searchStr;
166
- let dependenciesSearchStr = searchStr;
167
- if (!searchStr.includes("~>")) {
168
- dependenciesSearchStr = `dependencies[ref ~> /${searchStr}/i or dependsOn ~> /${searchStr}/i or provides ~> /${searchStr}/i]`;
169
- searchStr = `components[group ~> /${searchStr}/i or name ~> /${searchStr}/i or description ~> /${searchStr}/i or publisher ~> /${searchStr}/i or purl ~> /${searchStr}/i or tags ~> /${searchStr}/i]`;
178
+ let fixedSearchStr = searchStr.replaceAll("/", "\\/");
179
+ let dependenciesSearchStr = fixedSearchStr;
180
+ if (!fixedSearchStr.includes("~>")) {
181
+ dependenciesSearchStr = `dependencies[ref ~> /${fixedSearchStr}/i or dependsOn ~> /${fixedSearchStr}/i or provides ~> /${fixedSearchStr}/i]`;
182
+ fixedSearchStr = `components[group ~> /${fixedSearchStr}/i or name ~> /${fixedSearchStr}/i or description ~> /${fixedSearchStr}/i or publisher ~> /${fixedSearchStr}/i or purl ~> /${fixedSearchStr}/i or tags ~> /${fixedSearchStr}/i]`;
170
183
  }
171
- const expression = jsonata(searchStr);
184
+ const expression = jsonata(fixedSearchStr);
172
185
  let components = await expression.evaluate(sbom);
173
186
  const dexpression = jsonata(dependenciesSearchStr);
174
187
  let dependencies = await dexpression.evaluate(sbom);
@@ -181,16 +194,12 @@ cdxgenRepl.defineCommand("search", {
181
194
  if (!components) {
182
195
  console.log("No results found!");
183
196
  } else {
184
- printTable(
185
- { components, dependencies },
186
- undefined,
187
- originalSearchString,
188
- );
197
+ printTable({ components, dependencies }, undefined, searchStr);
189
198
  if (dependencies?.length) {
190
199
  printDependencyTree(
191
200
  { components, dependencies },
192
201
  "dependsOn",
193
- originalSearchString,
202
+ searchStr,
194
203
  );
195
204
  }
196
205
  }
package/lib/cli/index.js CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  } from "../helpers/envcontext.js";
33
33
  import { thoughtLog } from "../helpers/logger.js";
34
34
 
35
+ import { analyzeBuildSettings } from "../helpers/package_specific/gradleutils.js";
35
36
  import {
36
37
  CARGO_CMD,
37
38
  CLJ_CMD,
@@ -80,6 +81,7 @@ import {
80
81
  hasAnyProjectType,
81
82
  includeMavenTestScope,
82
83
  isFeatureEnabled,
84
+ isMac,
83
85
  isPackageManagerAllowed,
84
86
  isPartialTree,
85
87
  isSecureMode,
@@ -1627,6 +1629,13 @@ export async function createJavaBom(path, options) {
1627
1629
  tmpParentComponent.type = "application";
1628
1630
  if (dlist?.length) {
1629
1631
  pkgList = pkgList.concat(dlist);
1632
+ if (dlist.length > 1) {
1633
+ thoughtLog(`Obtained ${dlist.length} components from maven.`);
1634
+ } else {
1635
+ thoughtLog(
1636
+ `"Received very few components from the maven dependency tree command for ${basePath}."`,
1637
+ );
1638
+ }
1630
1639
  }
1631
1640
  // Retain the parent hierarchy
1632
1641
  if (!Object.keys(parentComponent).length) {
@@ -1635,12 +1644,18 @@ export async function createJavaBom(path, options) {
1635
1644
  } else {
1636
1645
  parentComponent.components.push(tmpParentComponent);
1637
1646
  }
1638
- if (parsedList.dependenciesList && parsedList.dependenciesList) {
1647
+ if (parsedList.dependenciesList) {
1639
1648
  dependencies = mergeDependencies(
1640
1649
  dependencies,
1641
1650
  parsedList.dependenciesList,
1642
1651
  tmpParentComponent,
1643
1652
  );
1653
+ } else {
1654
+ if (dlist?.length) {
1655
+ thoughtLog(
1656
+ `Hmm, I didn't find any dependencies after executing '${basename(mavenCmd)}'. However, I did get ${dlist.length} components, which is confusing.`,
1657
+ );
1658
+ }
1644
1659
  }
1645
1660
  unlinkSync(tempMvnTree);
1646
1661
  }
@@ -1755,6 +1770,11 @@ export async function createJavaBom(path, options) {
1755
1770
  ) {
1756
1771
  gradleRootPath = dirname(gradleFiles[0]);
1757
1772
  }
1773
+ if (safeExistsSync(join(gradleRootPath, "gradle.properties"))) {
1774
+ thoughtLog(
1775
+ "Hmm, there is a gradle.properties file. Do we need any private modules or custom JVM arguments for this project 🤔?",
1776
+ );
1777
+ }
1758
1778
  // Execute gradle properties
1759
1779
  if (
1760
1780
  gradleFiles?.length &&
@@ -1763,10 +1783,47 @@ export async function createJavaBom(path, options) {
1763
1783
  let rootProjects = [null];
1764
1784
  let allProjectsStr = [];
1765
1785
  let rootGradleModule = {};
1786
+ let includedProjectsFound = false;
1766
1787
  if (process.env.GRADLE_INCLUDED_BUILDS) {
1767
- rootProjects = rootProjects.concat(
1768
- process.env.GRADLE_INCLUDED_BUILDS.split(","),
1788
+ // Automatically add the colon prefix
1789
+ const includedBuilds = process.env.GRADLE_INCLUDED_BUILDS.split(",").map(
1790
+ (b) => (!b.startsWith(":") ? `:${b}` : b),
1791
+ );
1792
+ rootProjects = rootProjects.concat(includedBuilds);
1793
+ includedProjectsFound = true;
1794
+ } else {
1795
+ // Automatically detect included builds
1796
+ // Only from the root path for now
1797
+ for (const abuildFile of [
1798
+ join(gradleRootPath, "build.gradle"),
1799
+ join(gradleRootPath, "build.gradle.kts"),
1800
+ join(gradleRootPath, "settings.gradle"),
1801
+ join(gradleRootPath, "settings.gradle.kts"),
1802
+ ]) {
1803
+ if (!safeExistsSync(abuildFile)) {
1804
+ continue;
1805
+ }
1806
+ const buildSettings = analyzeBuildSettings(abuildFile);
1807
+ if (buildSettings?.includedBuilds?.length) {
1808
+ for (const aib of buildSettings.includedBuilds) {
1809
+ if (!rootProjects.includes(aib)) {
1810
+ rootProjects.push(aib);
1811
+ includedProjectsFound = true;
1812
+ }
1813
+ }
1814
+ break;
1815
+ }
1816
+ }
1817
+ }
1818
+ if (includedProjectsFound) {
1819
+ thoughtLog(
1820
+ `Wait, this gradle project uses composite builds. I must carefully process these ${rootProjects.length} projects including the root.`,
1769
1821
  );
1822
+ if (DEBUG_MODE) {
1823
+ console.log(
1824
+ `Additional root projects: ${rootProjects.join(" ").trim()}.`,
1825
+ );
1826
+ }
1770
1827
  }
1771
1828
  const parallelPropTaskOut = executeParallelGradleProperties(
1772
1829
  gradleRootPath,
@@ -1787,14 +1844,14 @@ export async function createJavaBom(path, options) {
1787
1844
  gradleModules.set(key, rootComponent);
1788
1845
  if (!rootProjects.includes(key)) {
1789
1846
  if (rootGradleModule.name) {
1790
- console.error(
1791
- "Found more than 1 root-components! Maybe you made a mistake in the name of an included-build module?",
1792
- );
1793
- throw new Error(
1794
- "Found more than 1 root-components! Maybe you made a mistake in the name of an included-build module?",
1795
- );
1847
+ if (DEBUG_MODE) {
1848
+ console.log(
1849
+ `Received new root component: ${rootComponent.name} with key ${key}. Please verify the value used for included builds. Using the name ${rootGradleModule.name}.`,
1850
+ );
1851
+ }
1852
+ } else {
1853
+ rootGradleModule = rootComponent;
1796
1854
  }
1797
- rootGradleModule = rootComponent;
1798
1855
  } else if (!allProjectsAddedPurls.includes(rootComponent["purl"])) {
1799
1856
  allProjects.push(rootComponent);
1800
1857
  rootDependsOn.add(rootComponent["bom-ref"]);
@@ -1809,7 +1866,11 @@ export async function createJavaBom(path, options) {
1809
1866
  const modulesToSkip = process.env.GRADLE_SKIP_MODULES
1810
1867
  ? process.env.GRADLE_SKIP_MODULES.split(",")
1811
1868
  : [];
1812
-
1869
+ if (modulesToSkip.length) {
1870
+ thoughtLog(
1871
+ `Good news. I know there are ${allProjectsStr.length} gradle modules at ${gradleRootPath}. I must skip ${modulesToSkip.length} out of these.`,
1872
+ );
1873
+ }
1813
1874
  const parallelPropTaskOut = executeParallelGradleProperties(
1814
1875
  gradleRootPath,
1815
1876
  allProjectsStr.filter((module) => !modulesToSkip.includes(module)),
@@ -1886,12 +1947,17 @@ export async function createJavaBom(path, options) {
1886
1947
  ? process.env.GRADLE_ARGS_DEPENDENCIES.split(" ")
1887
1948
  : [],
1888
1949
  );
1889
- console.log(
1890
- "Executing",
1891
- gradleCmd,
1892
- gradleArguments.join(" "),
1893
- "in",
1894
- gradleRootPath,
1950
+ if (DEBUG_MODE) {
1951
+ console.log(
1952
+ "Executing",
1953
+ gradleCmd,
1954
+ `${gradleArguments.join(" ").substring(0, 150)} ...`,
1955
+ "in",
1956
+ gradleRootPath,
1957
+ );
1958
+ }
1959
+ thoughtLog(
1960
+ `Let's invoke '${basename(gradleCmd)}' with the arguments '${gradleArguments.join(" ").substring(0, 100)} ...'.`,
1895
1961
  );
1896
1962
  const sresult = spawnSync(gradleCmd, gradleArguments, {
1897
1963
  cwd: gradleRootPath,
@@ -1899,7 +1965,6 @@ export async function createJavaBom(path, options) {
1899
1965
  timeout: TIMEOUT_MS,
1900
1966
  maxBuffer: MAX_BUFFER,
1901
1967
  });
1902
-
1903
1968
  if (sresult.status !== 0 || sresult.error) {
1904
1969
  if (options.failOnError || DEBUG_MODE) {
1905
1970
  console.error(sresult.stdout, sresult.stderr);
@@ -1920,22 +1985,20 @@ export async function createJavaBom(path, options) {
1920
1985
  gradleRootPath,
1921
1986
  );
1922
1987
  const dlist = parsedList.pkgList;
1923
- if (parsedList.dependenciesList && parsedList.dependenciesList) {
1988
+ if (parsedList.dependenciesList) {
1924
1989
  dependencies = mergeDependencies(
1925
1990
  dependencies,
1926
1991
  parsedList.dependenciesList,
1927
1992
  parentComponent,
1928
1993
  );
1929
- }
1930
- if (dlist?.length) {
1931
- if (DEBUG_MODE) {
1932
- console.log(
1933
- "Found",
1934
- dlist.length,
1935
- "packages in gradle project",
1936
- key,
1994
+ } else {
1995
+ if (dlist?.length) {
1996
+ thoughtLog(
1997
+ `Hmm, I didn't find any dependencies after executing '${basename(gradleCmd)}' for the project ${key}. However, I did get ${dlist.length} components, which is confusing.`,
1937
1998
  );
1938
1999
  }
2000
+ }
2001
+ if (dlist?.length) {
1939
2002
  pkgList = pkgList.concat(dlist);
1940
2003
  }
1941
2004
  }
@@ -1950,11 +2013,12 @@ export async function createJavaBom(path, options) {
1950
2013
  );
1951
2014
  }
1952
2015
  }
1953
- console.log(
1954
- "Obtained",
1955
- pkgList.length,
1956
- "from this gradle project. De-duping this list ...",
2016
+ thoughtLog(
2017
+ `Obtained ${pkgList.length} components by executing the '${basename(gradleCmd)}' command.`,
1957
2018
  );
2019
+ if (DEBUG_MODE) {
2020
+ console.log("Obtained", pkgList.length, "from this gradle project.");
2021
+ }
1958
2022
  } else {
1959
2023
  thoughtLog(
1960
2024
  "**GRADLE:** SBOM is incomplete. I recommend troubleshooting the issue to improve the BOM precision.",
@@ -2405,19 +2469,18 @@ export async function createNodejsBom(path, options) {
2405
2469
  `${options.multiProject ? "**/" : ""}package.json`,
2406
2470
  options,
2407
2471
  );
2408
- const yarnLockFile = getAllFiles(path, "yarn.lock", options);
2409
- const pnpmLockFile = getAllFiles(path, "pnpm-lock.yaml", options);
2410
2472
  const npmInstallCount = Number.parseInt(process.env.NPM_INSTALL_COUNT) || 2;
2411
2473
  // Automatic npm install logic.
2412
2474
  // Only perform npm install for smaller projects (< 2 package.json) without the correct number of lock files
2413
2475
  if (
2414
2476
  (pkgJsonLockFiles?.length === 0 ||
2415
2477
  pkgJsonLockFiles?.length < pkgJsonFiles?.length) &&
2416
- yarnLockFile?.length === 0 &&
2417
- pnpmLockFile?.length === 0 &&
2478
+ yarnLockFiles?.length === 0 &&
2479
+ pnpmLockFiles?.length === 0 &&
2418
2480
  pkgJsonFiles?.length <= npmInstallCount &&
2419
2481
  options.installDeps
2420
2482
  ) {
2483
+ let anyInstallSuccess = false;
2421
2484
  for (const apkgJson of pkgJsonFiles) {
2422
2485
  let pkgMgr = "npm";
2423
2486
  const supPkgMgrs = ["npm", "yarn", "yarnpkg", "pnpm", "pnpx"];
@@ -2427,8 +2490,21 @@ export async function createNodejsBom(path, options) {
2427
2490
  if (mgrData) {
2428
2491
  mgr = mgrData.split("@")[0];
2429
2492
  }
2430
- if (supPkgMgrs.includes(mgr)) {
2493
+ // Try harder to identify the correct package manager
2494
+ if (options?.projectType?.includes("npm")) {
2495
+ pkgMgr = "npm";
2496
+ } else if (supPkgMgrs.includes(mgr)) {
2431
2497
  pkgMgr = mgr;
2498
+ } else if (pkgData?.engines?.yarn) {
2499
+ pkgMgr = "yarn";
2500
+ } else if (
2501
+ isPackageManagerAllowed("yarn", ["npm", "pnpm", "rush"], options)
2502
+ ) {
2503
+ pkgMgr = "yarn";
2504
+ } else if (
2505
+ isPackageManagerAllowed("pnpm", ["npm", "yarn", "rush"], options)
2506
+ ) {
2507
+ pkgMgr = "pnpm";
2432
2508
  }
2433
2509
  let installCommand = "install";
2434
2510
  if (pkgMgr === "npm" && isSecureMode && pkgJsonLockFiles?.length > 0) {
@@ -2463,6 +2539,16 @@ export async function createNodejsBom(path, options) {
2463
2539
  installArgs.push("--package-lock");
2464
2540
  }
2465
2541
  }
2542
+ if (pkgMgr === "npm" && yarnLockFiles.length) {
2543
+ thoughtLog(
2544
+ `Wait, there are ${yarnLockFiles.length} yarn.lock files in this project; however, I'm about to invoke the '${pkgMgr} ${installArgs[0]}' command.`,
2545
+ );
2546
+ }
2547
+ if (pkgMgr !== "npm") {
2548
+ thoughtLog(
2549
+ `**PACKAGE MANAGER**: Let's run the '${pkgMgr}' command with the arguments '${installArgs.join(" ")}' to generate the needed lock files.`,
2550
+ );
2551
+ }
2466
2552
  console.log(
2467
2553
  `Executing '${pkgMgr} ${installArgs.join(" ")}' in`,
2468
2554
  basePath,
@@ -2477,6 +2563,9 @@ export async function createNodejsBom(path, options) {
2477
2563
  console.error(
2478
2564
  `${pkgMgr} install has failed. Generated SBOM will be empty or with a lower precision.`,
2479
2565
  );
2566
+ thoughtLog(
2567
+ "It looks like the install command has failed. I'm considering some troubleshooting ideas.",
2568
+ );
2480
2569
  if (DEBUG_MODE && result.stdout) {
2481
2570
  if (result.stdout.includes("EBADENGINE Unsupported engine")) {
2482
2571
  console.log(
@@ -2523,6 +2612,8 @@ export async function createNodejsBom(path, options) {
2523
2612
  console.log(result.stderr);
2524
2613
  }
2525
2614
  options.failOnError && process.exit(1);
2615
+ } else {
2616
+ anyInstallSuccess = true;
2526
2617
  }
2527
2618
  }
2528
2619
  pkgLockFiles = getAllFiles(
@@ -2540,6 +2631,16 @@ export async function createNodejsBom(path, options) {
2540
2631
  `${options.multiProject ? "**/" : ""}yarn.lock`,
2541
2632
  options,
2542
2633
  );
2634
+ if (
2635
+ anyInstallSuccess &&
2636
+ !pkgLockFiles.length &&
2637
+ !pnpmLockFiles.length &&
2638
+ !yarnLockFiles.length
2639
+ ) {
2640
+ thoughtLog(
2641
+ `Despite a successful installation step, I didn't find any lock files. Perhaps they're being created elsewhere, such as in the root directory. I am currently checking the directory at ${path}.`,
2642
+ );
2643
+ }
2543
2644
  }
2544
2645
  if (
2545
2646
  pnpmLockFiles?.length &&
@@ -2977,6 +3078,17 @@ export async function createNodejsBom(path, options) {
2977
3078
  }
2978
3079
  }
2979
3080
  }
3081
+ if (!pkgList.length && (yarnLockFiles.length || pkgLockFiles.length)) {
3082
+ if (options.projectType.length) {
3083
+ thoughtLog(
3084
+ `Despite seeing some lock files, I didn't find any components in this Node.js project. Is there an issue with the project type '${options.projectType.join(", ")}' used 🤔? I recommend trying again with a different type.`,
3085
+ );
3086
+ } else {
3087
+ thoughtLog(
3088
+ "Despite seeing some lock files, I didn't find any components in this Node.js project. Feels like a bug.",
3089
+ );
3090
+ }
3091
+ }
2980
3092
  // Retain the components of parent component
2981
3093
  if (parentSubComponents.length) {
2982
3094
  parentComponent.components = parentSubComponents;
@@ -4774,6 +4886,7 @@ export async function createSwiftBom(path, options) {
4774
4886
  let dependencies = [];
4775
4887
  let parentComponent = {};
4776
4888
  const completedPath = [];
4889
+ let packageArgsMessageShown = false;
4777
4890
  if (pkgResolvedFiles.length) {
4778
4891
  for (const f of pkgResolvedFiles) {
4779
4892
  if (!parentComponent || !Object.keys(parentComponent).length) {
@@ -4787,6 +4900,9 @@ export async function createSwiftBom(path, options) {
4787
4900
  pkgList = pkgList.concat(dlist);
4788
4901
  }
4789
4902
  }
4903
+ thoughtLog(
4904
+ `It looks like we have ${pkgResolvedFiles.length} Package.resolved files, which is good. To compute the dependency tree, let's try using the swift package command 📦.`,
4905
+ );
4790
4906
  }
4791
4907
  if (swiftFiles.length) {
4792
4908
  for (const f of swiftFiles) {
@@ -4795,7 +4911,25 @@ export async function createSwiftBom(path, options) {
4795
4911
  continue;
4796
4912
  }
4797
4913
  let treeData = undefined;
4798
- let packageArgs = ["package", "show-dependencies", "--format", "json"];
4914
+ let packageArgs = ["package"];
4915
+ // Additional arguments to pass to the swift package command.
4916
+ // Example: --swift-sdks-path <swift-sdks-path> --jobs <jobs>
4917
+ if (process.env.SWIFT_PACKAGE_ARGS) {
4918
+ packageArgs = packageArgs.concat(
4919
+ process.env.SWIFT_PACKAGE_ARGS.split(" "),
4920
+ );
4921
+ if (!packageArgsMessageShown) {
4922
+ thoughtLog(
4923
+ `Wait, let's use the additional arguments '${process.env.SWIFT_PACKAGE_ARGS}' for the swift package command.`,
4924
+ );
4925
+ packageArgsMessageShown = true;
4926
+ }
4927
+ }
4928
+ packageArgs = packageArgs.concat([
4929
+ "show-dependencies",
4930
+ "--format",
4931
+ "json",
4932
+ ]);
4799
4933
  let swiftCommand = SWIFT_CMD;
4800
4934
  if (swiftCommand.startsWith("xcrun")) {
4801
4935
  swiftCommand = "xcrun";
@@ -4803,8 +4937,7 @@ export async function createSwiftBom(path, options) {
4803
4937
  }
4804
4938
  if (DEBUG_MODE) {
4805
4939
  console.log(
4806
- `Executing '${swiftCommand} ${packageArgs.join(" ")}' in`,
4807
- basePath,
4940
+ `Executing '${swiftCommand} ${packageArgs.join(" ")}' in ${basePath}. Please wait ...`,
4808
4941
  );
4809
4942
  }
4810
4943
  const result = spawnSync(swiftCommand, packageArgs, {
@@ -4813,15 +4946,29 @@ export async function createSwiftBom(path, options) {
4813
4946
  timeout: TIMEOUT_MS,
4814
4947
  maxBuffer: MAX_BUFFER,
4815
4948
  });
4816
- if (result.status === 0 && result.stdout) {
4949
+ if (result.stdout) {
4817
4950
  completedPath.push(basePath);
4818
4951
  treeData = Buffer.from(result.stdout).toString();
4819
4952
  const retData = parseSwiftJsonTree(treeData, f);
4820
- if (retData.pkgList?.length) {
4821
- parentComponent = retData.pkgList.splice(0, 1)[0];
4822
- parentComponent.type = "application";
4823
- pkgList = pkgList.concat(retData.pkgList);
4953
+ if (retData.rootList?.length) {
4954
+ if (!Object.keys(parentComponent).length) {
4955
+ parentComponent = retData.rootList[0];
4956
+ if (retData.rootList.length > 1) {
4957
+ if (!parentComponent.components) {
4958
+ parentComponent.components = [];
4959
+ }
4960
+ for (const p of retData.rootList.splice(0, 1)) {
4961
+ parentComponent.components.push(p);
4962
+ }
4963
+ }
4964
+ } else {
4965
+ if (!parentComponent.components) {
4966
+ parentComponent.components = [];
4967
+ }
4968
+ parentComponent.components.concat(retData.rootList);
4969
+ }
4824
4970
  }
4971
+ pkgList = pkgList.concat(retData.pkgList);
4825
4972
  if (retData.dependenciesList) {
4826
4973
  dependencies = mergeDependencies(
4827
4974
  dependencies,
@@ -4829,11 +4976,25 @@ export async function createSwiftBom(path, options) {
4829
4976
  parentComponent,
4830
4977
  );
4831
4978
  }
4832
- } else {
4833
- if (DEBUG_MODE) {
4979
+ }
4980
+ if (result.status !== 0 || result.error) {
4981
+ if (result?.stderr?.includes("Source files for target")) {
4982
+ console.log(
4983
+ "The Sources directory is missing. Please run cdxgen from the directory that contains the complete source code.",
4984
+ );
4985
+ thoughtLog(
4986
+ `It looks like the 'Sources' directory is missing, so we are missing the components and dependencies for '${basename(basePath)}'.`,
4987
+ );
4988
+ } else if (process.env.CDXGEN_IN_CONTAINER !== "true") {
4834
4989
  console.log(
4835
- "Please install swift from https://www.swift.org/download/ or use the cdxgen container image",
4990
+ "Consider using the cdxgen container image (`ghcr.io/cyclonedx/cdxgen`), which includes Swift and additional build tools.",
4836
4991
  );
4992
+ if (!isMac) {
4993
+ console.log("Alternatively, try building this project from a Mac.");
4994
+ thoughtLog(
4995
+ "I'm wondering if the results might be better on a Mac 🤔.",
4996
+ );
4997
+ }
4837
4998
  }
4838
4999
  console.error(result.stderr);
4839
5000
  options.failOnError && process.exit(1);
@@ -7259,6 +7420,7 @@ export async function createBom(path, options) {
7259
7420
  console.log(
7260
7421
  `OS BOM generation has failed due to problems with exporting the image ${path}`,
7261
7422
  );
7423
+ options.failOnError && process.exit(1);
7262
7424
  return {};
7263
7425
  }
7264
7426
  isContainerMode = true;
@@ -7277,6 +7439,14 @@ export async function createBom(path, options) {
7277
7439
  if (exportData) {
7278
7440
  isContainerMode = true;
7279
7441
  } else {
7442
+ // Fail early for oci types
7443
+ if (hasAnyProjectType(["oci"], options, false)) {
7444
+ console.log(
7445
+ `OCI BOM generation has failed due to problems with exporting the image ${path}.`,
7446
+ );
7447
+ options.failOnError && process.exit(1);
7448
+ return {};
7449
+ }
7280
7450
  if (DEBUG_MODE) {
7281
7451
  console.log(path, "doesn't appear to be a valid container image.");
7282
7452
  }
@@ -7368,15 +7538,21 @@ export async function createBom(path, options) {
7368
7538
  }
7369
7539
  if (projectType.length > 1) {
7370
7540
  thoughtLog(
7371
- `The user has specified multiple project types: projectType.join(", "). Let's focus on the types one at a time.`,
7541
+ `The user has specified multiple project types: ${projectType.join(", ")}. Let's focus on the types one at a time.`,
7372
7542
  );
7373
7543
  console.log("Generate BOM for project types:", projectType.join(", "));
7374
7544
  return await createMultiXBom(path, options);
7375
7545
  }
7376
7546
  if (projectType.length === 1) {
7377
- thoughtLog(
7378
- `The user wants me to focus on a single type, '${projectType}'. Could there be an issue with auto-detection, or might they use another tool like cyclonedx-cli to merge all the generated BOMs later?`,
7379
- );
7547
+ if (hasAnyProjectType(["oci"], options, false)) {
7548
+ thoughtLog(
7549
+ "Okay, we're generating an SBOM for the OCI type. We'll need a compatible tool like Docker, Podman, or Nerdctl, along with the binary plugins.",
7550
+ );
7551
+ } else {
7552
+ thoughtLog(
7553
+ `The user wants me to focus on a single type, '${projectType}'. Could there be an issue with auto-detection, or might they use another tool like cyclonedx-cli to merge all the generated BOMs later?`,
7554
+ );
7555
+ }
7380
7556
  }
7381
7557
  // Use the project type alias to return any singular BOM
7382
7558
  if (PROJECT_TYPE_ALIASES["java"].includes(projectType[0])) {
@@ -228,7 +228,7 @@ export async function createSlice(
228
228
  // Handle language with version types
229
229
  if (language.startsWith("ruby")) {
230
230
  language = "ruby";
231
- } else if (language.startsWith("java")) {
231
+ } else if (language.startsWith("java") && language !== "javascript") {
232
232
  language = "java";
233
233
  } else if (language.startsWith("node")) {
234
234
  language = "js";
@@ -465,8 +465,10 @@ export function printSummary(bomJson) {
465
465
  let bomPkgNamespaces = [];
466
466
  // Print any annotations found
467
467
  const annotations = bomJson?.annotations || [];
468
- for (const annot of annotations) {
469
- message = `${message}\n${annot.text}`;
468
+ if (annotations.length) {
469
+ for (const annot of annotations) {
470
+ message = `${message}\n${annot.text}`;
471
+ }
470
472
  }
471
473
  const tools = bomJson?.metadata?.tools?.components;
472
474
  if (tools) {
@@ -6,7 +6,8 @@ import colors from "yoctocolors";
6
6
  // Enable think mode
7
7
  export const THINK_MODE =
8
8
  process.env.CDXGEN_THOUGHT_LOG ||
9
- ["true", "1"].includes(process.env.CDXGEN_THINK_MODE);
9
+ ["true", "1"].includes(process.env.CDXGEN_THINK_MODE) ||
10
+ process.env.CDXGEN_DEBUG_MODE === "verbose";
10
11
 
11
12
  const output = process.env.CDXGEN_THOUGHT_LOG
12
13
  ? fs.createWriteStream(process.env.CDXGEN_THOUGHT_LOG)