@cyclonedx/cdxgen 12.1.5 → 12.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.
Files changed (181) hide show
  1. package/README.md +47 -39
  2. package/bin/cdxgen.js +175 -96
  3. package/bin/evinse.js +4 -4
  4. package/bin/repl.js +1 -1
  5. package/bin/sign.js +102 -0
  6. package/bin/validate.js +233 -0
  7. package/bin/verify.js +69 -28
  8. package/data/queries.json +1 -1
  9. package/data/rules/ci-permissions.yaml +186 -0
  10. package/data/rules/dependency-sources.yaml +123 -0
  11. package/data/rules/package-integrity.yaml +135 -0
  12. package/data/rules/vscode-extensions.yaml +228 -0
  13. package/lib/cli/index.js +327 -372
  14. package/lib/evinser/db.js +137 -0
  15. package/lib/{helpers → evinser}/db.poku.js +2 -6
  16. package/lib/evinser/evinser.js +2 -14
  17. package/lib/helpers/bomSigner.js +312 -0
  18. package/lib/helpers/bomSigner.poku.js +156 -0
  19. package/lib/helpers/ciParsers/azurePipelines.js +295 -0
  20. package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
  21. package/lib/helpers/ciParsers/circleCi.js +286 -0
  22. package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
  23. package/lib/helpers/ciParsers/common.js +24 -0
  24. package/lib/helpers/ciParsers/githubActions.js +636 -0
  25. package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
  26. package/lib/helpers/ciParsers/gitlabCi.js +213 -0
  27. package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
  28. package/lib/helpers/ciParsers/jenkins.js +181 -0
  29. package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
  30. package/lib/helpers/depsUtils.js +203 -0
  31. package/lib/helpers/depsUtils.poku.js +150 -0
  32. package/lib/helpers/display.js +423 -4
  33. package/lib/helpers/envcontext.js +18 -3
  34. package/lib/helpers/formulationParsers.js +351 -0
  35. package/lib/helpers/logger.js +14 -0
  36. package/lib/helpers/protobom.js +9 -9
  37. package/lib/helpers/pythonutils.js +9 -0
  38. package/lib/helpers/utils.js +681 -406
  39. package/lib/helpers/utils.poku.js +55 -255
  40. package/lib/helpers/versutils.js +202 -0
  41. package/lib/helpers/versutils.poku.js +315 -0
  42. package/lib/helpers/vsixutils.js +1061 -0
  43. package/lib/helpers/vsixutils.poku.js +2247 -0
  44. package/lib/managers/binary.js +19 -19
  45. package/lib/managers/docker.js +108 -1
  46. package/lib/managers/oci.js +10 -0
  47. package/lib/managers/piptree.js +3 -9
  48. package/lib/parsers/npmrc.js +17 -13
  49. package/lib/parsers/npmrc.poku.js +41 -5
  50. package/lib/server/openapi.yaml +1 -1
  51. package/lib/server/server.js +40 -11
  52. package/lib/server/server.poku.js +123 -144
  53. package/lib/stages/postgen/annotator.js +1 -1
  54. package/lib/stages/postgen/auditBom.js +197 -0
  55. package/lib/stages/postgen/auditBom.poku.js +378 -0
  56. package/lib/stages/postgen/postgen.js +54 -1
  57. package/lib/stages/postgen/postgen.poku.js +90 -1
  58. package/lib/stages/postgen/ruleEngine.js +369 -0
  59. package/lib/stages/pregen/envAudit.js +299 -0
  60. package/lib/stages/pregen/envAudit.poku.js +572 -0
  61. package/lib/stages/pregen/pregen.js +12 -8
  62. package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
  63. package/lib/validator/complianceEngine.js +241 -0
  64. package/lib/validator/complianceEngine.poku.js +168 -0
  65. package/lib/validator/complianceRules.js +1610 -0
  66. package/lib/validator/complianceRules.poku.js +328 -0
  67. package/lib/validator/index.js +222 -0
  68. package/lib/validator/index.poku.js +144 -0
  69. package/lib/validator/reporters/annotations.js +121 -0
  70. package/lib/validator/reporters/console.js +149 -0
  71. package/lib/validator/reporters/index.js +41 -0
  72. package/lib/validator/reporters/json.js +37 -0
  73. package/lib/validator/reporters/sarif.js +184 -0
  74. package/lib/validator/reporters.poku.js +150 -0
  75. package/package.json +8 -8
  76. package/types/bin/sign.d.ts +3 -0
  77. package/types/bin/sign.d.ts.map +1 -0
  78. package/types/bin/validate.d.ts +3 -0
  79. package/types/bin/validate.d.ts.map +1 -0
  80. package/types/helpers/utils.d.ts +0 -1
  81. package/types/lib/cli/index.d.ts +49 -52
  82. package/types/lib/cli/index.d.ts.map +1 -1
  83. package/types/lib/evinser/db.d.ts +34 -0
  84. package/types/lib/evinser/db.d.ts.map +1 -0
  85. package/types/lib/evinser/evinser.d.ts +63 -16
  86. package/types/lib/evinser/evinser.d.ts.map +1 -1
  87. package/types/lib/helpers/bomSigner.d.ts +27 -0
  88. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  89. package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
  90. package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
  91. package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
  92. package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
  93. package/types/lib/helpers/ciParsers/common.d.ts +11 -0
  94. package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
  95. package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
  96. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
  97. package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
  98. package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
  99. package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
  100. package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
  101. package/types/lib/helpers/depsUtils.d.ts +21 -0
  102. package/types/lib/helpers/depsUtils.d.ts.map +1 -0
  103. package/types/lib/helpers/display.d.ts +111 -11
  104. package/types/lib/helpers/display.d.ts.map +1 -1
  105. package/types/lib/helpers/envcontext.d.ts +19 -7
  106. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  107. package/types/lib/helpers/formulationParsers.d.ts +50 -0
  108. package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
  109. package/types/lib/helpers/logger.d.ts +15 -1
  110. package/types/lib/helpers/logger.d.ts.map +1 -1
  111. package/types/lib/helpers/protobom.d.ts +2 -2
  112. package/types/lib/helpers/pythonutils.d.ts +10 -1
  113. package/types/lib/helpers/pythonutils.d.ts.map +1 -1
  114. package/types/lib/helpers/utils.d.ts +532 -128
  115. package/types/lib/helpers/utils.d.ts.map +1 -1
  116. package/types/lib/helpers/versutils.d.ts +8 -0
  117. package/types/lib/helpers/versutils.d.ts.map +1 -0
  118. package/types/lib/helpers/vsixutils.d.ts +130 -0
  119. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  120. package/types/lib/managers/docker.d.ts +12 -31
  121. package/types/lib/managers/docker.d.ts.map +1 -1
  122. package/types/lib/managers/oci.d.ts +11 -1
  123. package/types/lib/managers/oci.d.ts.map +1 -1
  124. package/types/lib/managers/piptree.d.ts.map +1 -1
  125. package/types/lib/parsers/npmrc.d.ts +4 -1
  126. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  127. package/types/lib/server/server.d.ts +21 -2
  128. package/types/lib/server/server.d.ts.map +1 -1
  129. package/types/lib/stages/postgen/auditBom.d.ts +20 -0
  130. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  131. package/types/lib/stages/postgen/postgen.d.ts +8 -1
  132. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  133. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  134. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  135. package/types/lib/stages/pregen/envAudit.d.ts +8 -0
  136. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  137. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  138. package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
  139. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  140. package/types/lib/validator/complianceEngine.d.ts +66 -0
  141. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  142. package/types/lib/validator/complianceRules.d.ts +70 -0
  143. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  144. package/types/lib/validator/index.d.ts +70 -0
  145. package/types/lib/validator/index.d.ts.map +1 -0
  146. package/types/lib/validator/reporters/annotations.d.ts +31 -0
  147. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  148. package/types/lib/validator/reporters/console.d.ts +30 -0
  149. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  150. package/types/lib/validator/reporters/index.d.ts +21 -0
  151. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  152. package/types/lib/validator/reporters/json.d.ts +11 -0
  153. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  154. package/types/lib/validator/reporters/sarif.d.ts +16 -0
  155. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  156. package/lib/helpers/db.js +0 -162
  157. package/lib/stages/pregen/env-audit.js +0 -34
  158. package/lib/stages/pregen/env-audit.poku.js +0 -290
  159. package/types/helpers/db.d.ts +0 -35
  160. package/types/helpers/db.d.ts.map +0 -1
  161. package/types/lib/helpers/db.d.ts +0 -35
  162. package/types/lib/helpers/db.d.ts.map +0 -1
  163. package/types/lib/helpers/validator.d.ts.map +0 -1
  164. package/types/lib/stages/pregen/env-audit.d.ts +0 -2
  165. package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
  166. package/types/managers/binary.d.ts +0 -37
  167. package/types/managers/binary.d.ts.map +0 -1
  168. package/types/managers/docker.d.ts +0 -56
  169. package/types/managers/docker.d.ts.map +0 -1
  170. package/types/managers/oci.d.ts +0 -2
  171. package/types/managers/oci.d.ts.map +0 -1
  172. package/types/managers/piptree.d.ts +0 -2
  173. package/types/managers/piptree.d.ts.map +0 -1
  174. package/types/server/server.d.ts +0 -34
  175. package/types/server/server.d.ts.map +0 -1
  176. package/types/stages/postgen/annotator.d.ts +0 -27
  177. package/types/stages/postgen/annotator.d.ts.map +0 -1
  178. package/types/stages/postgen/postgen.d.ts +0 -51
  179. package/types/stages/postgen/postgen.d.ts.map +0 -1
  180. package/types/stages/pregen/pregen.d.ts +0 -59
  181. package/types/stages/pregen/pregen.d.ts.map +0 -1
package/lib/cli/index.js CHANGED
@@ -2,9 +2,9 @@ import { Buffer } from "node:buffer";
2
2
  import {
3
3
  accessSync,
4
4
  constants,
5
- existsSync,
6
5
  lstatSync,
7
6
  mkdtempSync,
7
+ readdirSync,
8
8
  readFileSync,
9
9
  rmSync,
10
10
  statSync,
@@ -25,15 +25,8 @@ import { parse as loadYaml } from "yaml";
25
25
 
26
26
  import { findJSImportsExports } from "../helpers/analyzer.js";
27
27
  import { parseCaxaMetadata } from "../helpers/caxa.js";
28
- import { collectOSCryptoLibs } from "../helpers/cbomutils.js";
29
- import {
30
- collectEnvInfo,
31
- GIT_COMMAND,
32
- getBranch,
33
- getOriginUrl,
34
- gitTreeHashes,
35
- listFiles,
36
- } from "../helpers/envcontext.js";
28
+ import { mergeDependencies, trimComponents } from "../helpers/depsUtils.js";
29
+ import { GIT_COMMAND } from "../helpers/envcontext.js";
37
30
  import { thoughtLog } from "../helpers/logger.js";
38
31
  import {
39
32
  addEvidenceForDotnet,
@@ -176,6 +169,14 @@ import {
176
169
  shouldFetchLicense,
177
170
  splitOutputByGradleProjects,
178
171
  } from "../helpers/utils.js";
172
+ import {
173
+ cleanupTempDir,
174
+ collectInstalledExtensions,
175
+ discoverIdeExtensionDirs,
176
+ extractVsixToTempDir,
177
+ parseVsixFile,
178
+ VSCODE_EXTENSION_PURL_TYPE,
179
+ } from "../helpers/vsixutils.js";
179
180
  import {
180
181
  executeOsQuery,
181
182
  getBinaryBom,
@@ -438,160 +439,6 @@ const addLifecyclesSection = (options) => {
438
439
  return lifecycles;
439
440
  };
440
441
 
441
- /**
442
- * Method to generate the formulation section based on git metadata
443
- *
444
- * @param {Object} options
445
- * @param {Object} context Context
446
- * @returns {Array} formulation array
447
- */
448
- const addFormulationSection = (options, context) => {
449
- const formulation = [];
450
- const provides = [];
451
- const gitBranch = getBranch();
452
- const originUrl = getOriginUrl();
453
- const gitFiles = listFiles();
454
- const treeHashes = gitTreeHashes();
455
- let parentOmniborId;
456
- let treeOmniborId;
457
- let components = [];
458
- const aformulation = {};
459
- // Reuse any existing formulation components
460
- // See: PR #1172
461
- if (context?.formulationList?.length) {
462
- components = components.concat(trimComponents(context.formulationList));
463
- }
464
- if (options.specVersion >= 1.6 && Object.keys(treeHashes).length === 2) {
465
- parentOmniborId = `gitoid:blob:sha1:${treeHashes.parent}`;
466
- treeOmniborId = `gitoid:blob:sha1:${treeHashes.tree}`;
467
- components.push({
468
- type: "file",
469
- name: "git-parent",
470
- description: "Git Parent Node.",
471
- "bom-ref": parentOmniborId,
472
- omniborId: [parentOmniborId],
473
- swhid: [`swh:1:rev:${treeHashes.parent}`],
474
- });
475
- components.push({
476
- type: "file",
477
- name: "git-tree",
478
- description: "Git Tree Node.",
479
- "bom-ref": treeOmniborId,
480
- omniborId: [treeOmniborId],
481
- swhid: [`swh:1:rev:${treeHashes.tree}`],
482
- });
483
- provides.push({
484
- ref: parentOmniborId,
485
- provides: [treeOmniborId],
486
- });
487
- }
488
- // Collect git related components
489
- if (gitBranch && gitFiles) {
490
- const gitFileComponents = gitFiles.map((f) =>
491
- options.specVersion >= 1.6
492
- ? {
493
- type: "file",
494
- name: f.name,
495
- version: f.hash,
496
- omniborId: [f.omniborId],
497
- swhid: [f.swhid],
498
- }
499
- : {
500
- type: "file",
501
- name: f.name,
502
- version: f.hash,
503
- },
504
- );
505
- components = components.concat(gitFileComponents);
506
- // Complete the Artifact Dependency Graph
507
- if (options.specVersion >= 1.6 && treeOmniborId) {
508
- provides.push({
509
- ref: treeOmniborId,
510
- provides: gitFiles.map((f) => f.ref),
511
- });
512
- }
513
- }
514
- // Collect build environment details
515
- const infoComponents = collectEnvInfo(options.path);
516
- if (infoComponents?.length) {
517
- components = components.concat(infoComponents);
518
- }
519
- // Should we include the OS crypto libraries
520
- if (options.includeCrypto) {
521
- const cryptoLibs = collectOSCryptoLibs(options);
522
- if (cryptoLibs?.length) {
523
- components = components.concat(cryptoLibs);
524
- }
525
- }
526
- aformulation["bom-ref"] = uuidv4();
527
- aformulation.components = trimComponents(components);
528
- let environmentVars = gitBranch?.length
529
- ? [{ name: "GIT_BRANCH", value: gitBranch }]
530
- : [];
531
- const envPrefixes = [
532
- "GIT",
533
- "ANDROID",
534
- "DENO",
535
- "DOTNET",
536
- "JAVA_",
537
- "SDKMAN",
538
- "CARGO",
539
- "CONDA",
540
- "RUST",
541
- "GEM_",
542
- "SCALA_",
543
- "MAVEN_",
544
- "GRADLE_",
545
- "NODE_",
546
- ];
547
- const envBlocklist = [
548
- "key",
549
- "token",
550
- "pass",
551
- "secret",
552
- "user",
553
- "email",
554
- "auth",
555
- "session",
556
- "proxy",
557
- "cred",
558
- ];
559
-
560
- for (const aevar of Object.keys(process.env)) {
561
- const lower = aevar.toLowerCase();
562
- const value = process.env[aevar] ?? "";
563
- if (
564
- envPrefixes.some((p) => aevar.startsWith(p)) &&
565
- !envBlocklist.some((b) => lower.includes(b)) &&
566
- !envBlocklist.some((b) => value.includes(b)) &&
567
- value.length
568
- ) {
569
- environmentVars.push({
570
- name: aevar,
571
- value,
572
- });
573
- }
574
- }
575
- if (!environmentVars.length) {
576
- environmentVars = undefined;
577
- }
578
- let sourceInput;
579
- if (environmentVars) {
580
- sourceInput = { environmentVars };
581
- }
582
- const sourceWorkflow = {
583
- "bom-ref": uuidv4(),
584
- uid: uuidv4(),
585
- taskTypes: originUrl ? ["build", "clone"] : ["build"],
586
- };
587
- if (sourceInput) {
588
- sourceWorkflow.inputs = [sourceInput];
589
- }
590
- aformulation.workflows = [sourceWorkflow];
591
- formulation.push(aformulation);
592
- return { formulation, provides };
593
- };
594
-
595
442
  /**
596
443
  * Function to create metadata block
597
444
  *
@@ -1000,6 +847,7 @@ function addExternalReferences(opkg) {
1000
847
  * @param {Object} allImports All imports
1001
848
  * @param {Object} pkg Package object
1002
849
  * @param {string} ptype Package type
850
+ * @returns {Object[]} Array of component objects
1003
851
  */
1004
852
  export function listComponents(options, allImports, pkg, ptype = "npm") {
1005
853
  const compMap = {};
@@ -1067,6 +915,15 @@ function addComponent(
1067
915
  purl = undefined;
1068
916
  purlString = undefined;
1069
917
  }
918
+ // Some applications like github workflow steps and commands do not have purl
919
+ if (
920
+ pkg.purl === undefined &&
921
+ !pkg?.["bom-ref"]?.startsWith("pkg:") &&
922
+ pkg?.type === "application"
923
+ ) {
924
+ purl = undefined;
925
+ purlString = undefined;
926
+ }
1070
927
  const description = pkg.description || undefined;
1071
928
  let compScope = pkg.scope;
1072
929
  if (allImports) {
@@ -1106,7 +963,12 @@ function addComponent(
1106
963
  component.data = pkg.data || undefined;
1107
964
  }
1108
965
  component["type"] = determinePackageType(pkg);
1109
- component["bom-ref"] = decodeURIComponent(purlString);
966
+ if (purlString) {
967
+ component["bom-ref"] = decodeURIComponent(purlString);
968
+ } else if (pkg["bom-ref"]) {
969
+ component["bom-ref"] = pkg["bom-ref"];
970
+ }
971
+
1110
972
  if (
1111
973
  component.externalReferences === undefined ||
1112
974
  component.externalReferences.length === 0
@@ -1136,6 +998,18 @@ function addComponent(
1136
998
  }
1137
999
  delete component.authors;
1138
1000
  }
1001
+ // Downgrade from 1.7
1002
+ if (options.specVersion < 1.7) {
1003
+ if (component.isExternal) {
1004
+ delete component.isExternal;
1005
+ }
1006
+ if (component.versionRange) {
1007
+ console.warn(
1008
+ `Version Range is not supported in ${options.specVersion} specifications. Please run cdxgen with --spec-version 1.7`,
1009
+ );
1010
+ delete component.versionRange;
1011
+ }
1012
+ }
1139
1013
  // Retain any tags
1140
1014
  if (
1141
1015
  options.specVersion >= 1.6 &&
@@ -1155,12 +1029,10 @@ function addComponent(
1155
1029
  if (pkg.components) {
1156
1030
  component.components = pkg.components;
1157
1031
  }
1032
+ const compMapKey = component.purl || component["bom-ref"];
1158
1033
  // Issue: 1353. We need to keep merging the properties
1159
- if (compMap[component.purl]) {
1160
- const mergedComponents = trimComponents([
1161
- compMap[component.purl],
1162
- component,
1163
- ]);
1034
+ if (compMap[compMapKey]) {
1035
+ const mergedComponents = trimComponents([compMap[compMapKey], component]);
1164
1036
  if (mergedComponents?.length === 1) {
1165
1037
  component = mergedComponents[0];
1166
1038
  }
@@ -1196,7 +1068,7 @@ function addComponent(
1196
1068
  component.evidence.identity = pkg.evidence.identity[0];
1197
1069
  }
1198
1070
  }
1199
- compMap[component.purl] = component;
1071
+ compMap[compMapKey] = component;
1200
1072
  }
1201
1073
  if (pkg.dependencies) {
1202
1074
  Object.keys(pkg.dependencies)
@@ -1387,17 +1259,15 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1387
1259
  components,
1388
1260
  dependencies,
1389
1261
  };
1390
- const formulationData =
1391
- options.includeFormulation && options.specVersion >= 1.5
1392
- ? addFormulationSection(options, context)
1393
- : undefined;
1394
- if (formulationData) {
1395
- jsonTpl.formulation = formulationData.formulation;
1396
- }
1397
1262
  bomNSData.bomJson = jsonTpl;
1398
1263
  bomNSData.nsMapping = nsMapping;
1399
1264
  bomNSData.dependencies = dependencies;
1400
1265
  bomNSData.parentComponent = parentComponent;
1266
+ // Carry language-specific formulation data (e.g. Pixi) so that
1267
+ // postProcess can merge it when building the final formulation section.
1268
+ if (context?.formulationList?.length) {
1269
+ bomNSData.formulationList = context.formulationList;
1270
+ }
1401
1271
  }
1402
1272
  return bomNSData;
1403
1273
  };
@@ -1489,6 +1359,7 @@ export async function createJarBom(path, options) {
1489
1359
  *
1490
1360
  * @param {string} path to the project
1491
1361
  * @param {Object} options Parse options from the cli
1362
+ * @returns {Object|undefined} BOM object
1492
1363
  */
1493
1364
  export function createAndroidBom(path, options) {
1494
1365
  return createBinaryBom(path, options);
@@ -1499,6 +1370,7 @@ export function createAndroidBom(path, options) {
1499
1370
  *
1500
1371
  * @param {string} path to the project
1501
1372
  * @param {Object} options Parse options from the cli
1373
+ * @returns {Object|undefined} BOM object
1502
1374
  */
1503
1375
  export function createBinaryBom(path, options) {
1504
1376
  const tempDir = mkdtempSync(join(getTmpDir(), "blint-tmp-"));
@@ -1522,6 +1394,7 @@ export function createBinaryBom(path, options) {
1522
1394
  *
1523
1395
  * @param {string} path to the project
1524
1396
  * @param {Object} options Parse options from the cli
1397
+ * @returns {Promise<Object>} Promise resolving to BOM object
1525
1398
  */
1526
1399
  export async function createJavaBom(path, options) {
1527
1400
  let jarNSMapping = {};
@@ -2769,6 +2642,7 @@ export async function createJavaBom(path, options) {
2769
2642
  *
2770
2643
  * @param {string} path to the project
2771
2644
  * @param {Object} options Parse options from the cli
2645
+ * @returns {Promise<Object>} Promise resolving to BOM object
2772
2646
  */
2773
2647
  export async function createNodejsBom(path, options) {
2774
2648
  let pkgList = [];
@@ -3203,8 +3077,9 @@ export async function createNodejsBom(path, options) {
3203
3077
  const basePath = dirname(f);
3204
3078
  // Determine the parent component
3205
3079
  const packageJsonF = join(basePath, "package.json");
3206
- const pnpmHooks = join(basePath, ".pnpmfile.cjs");
3207
- if (safeExistsSync(pnpmHooks)) {
3080
+ const pnpmCjsHooks = join(basePath, ".pnpmfile.cjs");
3081
+ const pnpmMjsHooks = join(basePath, ".pnpmfile.mjs");
3082
+ if (safeExistsSync(pnpmMjsHooks) || safeExistsSync(pnpmCjsHooks)) {
3208
3083
  thoughtLog("Wait, this pnpm project uses install hooks.");
3209
3084
  }
3210
3085
  if (!Object.keys(parentComponent).length) {
@@ -3687,6 +3562,7 @@ export async function createNodejsBom(path, options) {
3687
3562
  *
3688
3563
  * @param {String} path
3689
3564
  * @param {Object} options
3565
+ * @returns {Object | null} BOM object, or `null` when `pixi.lock` is absent and `options.installDeps` is false
3690
3566
  */
3691
3567
  export function createPixiBom(path, options) {
3692
3568
  const allImports = {};
@@ -3765,6 +3641,7 @@ export function createPixiBom(path, options) {
3765
3641
  *
3766
3642
  * @param {string} path to the project
3767
3643
  * @param {Object} options Parse options from the cli
3644
+ * @returns {Promise<Object>} Promise resolving to BOM object
3768
3645
  */
3769
3646
  export async function createPythonBom(path, options) {
3770
3647
  let allImports = {};
@@ -4306,6 +4183,7 @@ export async function createPythonBom(path, options) {
4306
4183
  *
4307
4184
  * @param {string} path to the project
4308
4185
  * @param {Object} options Parse options from the cli
4186
+ * @returns {Promise<Object | undefined>} Promise resolving to a BOM object or `undefined`
4309
4187
  */
4310
4188
  export async function createGoBom(path, options) {
4311
4189
  let pkgList = [];
@@ -4732,6 +4610,7 @@ export async function createGoBom(path, options) {
4732
4610
  *
4733
4611
  * @param {string} path to the project
4734
4612
  * @param {Object} options Parse options from the cli
4613
+ * @returns {Promise<Object|undefined>} Promise resolving to a BOM object or undefined
4735
4614
  */
4736
4615
  export async function createRustBom(path, options) {
4737
4616
  let pkgList = [];
@@ -4871,6 +4750,7 @@ export async function createRustBom(path, options) {
4871
4750
  *
4872
4751
  * @param {string} path to the project
4873
4752
  * @param {Object} options Parse options from the cli
4753
+ * @returns {Promise<Object>} Promise resolving to BOM object
4874
4754
  */
4875
4755
  export async function createDartBom(path, options) {
4876
4756
  const pubFiles = getAllFiles(
@@ -4942,6 +4822,7 @@ export async function createDartBom(path, options) {
4942
4822
  *
4943
4823
  * @param {string} path to the project
4944
4824
  * @param {Object} options Parse options from the cli
4825
+ * @returns {Object} BOM object
4945
4826
  */
4946
4827
  export function createCppBom(path, options) {
4947
4828
  let parentComponent;
@@ -5153,6 +5034,7 @@ export function createCppBom(path, options) {
5153
5034
  *
5154
5035
  * @param {string} path to the project
5155
5036
  * @param {Object} options Parse options from the cli
5037
+ * @returns {Object} BOM object
5156
5038
  */
5157
5039
  export function createClojureBom(path, options) {
5158
5040
  const ednFiles = getAllFiles(
@@ -5270,6 +5152,7 @@ export function createClojureBom(path, options) {
5270
5152
  *
5271
5153
  * @param {string} path to the project
5272
5154
  * @param {Object} options Parse options from the cli
5155
+ * @returns {Object} BOM object
5273
5156
  */
5274
5157
  export function createHaskellBom(path, options) {
5275
5158
  const cabalFiles = getAllFiles(
@@ -5302,6 +5185,7 @@ export function createHaskellBom(path, options) {
5302
5185
  *
5303
5186
  * @param {string} path to the project
5304
5187
  * @param {Object} options Parse options from the cli
5188
+ * @returns {Object} BOM object
5305
5189
  */
5306
5190
  export function createElixirBom(path, options) {
5307
5191
  const mixFiles = getAllFiles(
@@ -5334,6 +5218,7 @@ export function createElixirBom(path, options) {
5334
5218
  *
5335
5219
  * @param {string} path to the project
5336
5220
  * @param {Object} options Parse options from the cli
5221
+ * @returns {Object} BOM object
5337
5222
  */
5338
5223
  export function createGitHubBom(path, options) {
5339
5224
  const ghactionFiles = getAllFiles(
@@ -5365,6 +5250,7 @@ export function createGitHubBom(path, options) {
5365
5250
  *
5366
5251
  * @param {string} path to the project
5367
5252
  * @param {Object} options Parse options from the cli
5253
+ * @returns {Object} BOM object
5368
5254
  */
5369
5255
  export function createCloudBuildBom(path, options) {
5370
5256
  const cbFiles = getAllFiles(path, "cloudbuild.yml", options);
@@ -5393,6 +5279,7 @@ export function createCloudBuildBom(path, options) {
5393
5279
  *
5394
5280
  * @param {string} _path to the project
5395
5281
  * @param {Object} options Parse options from the cli
5282
+ * @returns {Promise<Object>} Promise resolving to BOM object
5396
5283
  */
5397
5284
  export function createOSBom(_path, options) {
5398
5285
  console.warn(
@@ -5451,6 +5338,7 @@ export function createOSBom(_path, options) {
5451
5338
  *
5452
5339
  * @param {string} path to the project
5453
5340
  * @param {Object} options Parse options from the cli
5341
+ * @returns {Promise<Object>} Promise resolving to BOM object
5454
5342
  */
5455
5343
  export async function createJenkinsBom(path, options) {
5456
5344
  let pkgList = [];
@@ -5500,6 +5388,7 @@ export async function createJenkinsBom(path, options) {
5500
5388
  *
5501
5389
  * @param {string} path to the project
5502
5390
  * @param {Object} options Parse options from the cli
5391
+ * @returns {Object} BOM object
5503
5392
  */
5504
5393
  export function createHelmBom(path, options) {
5505
5394
  let pkgList = [];
@@ -5532,6 +5421,7 @@ export function createHelmBom(path, options) {
5532
5421
  *
5533
5422
  * @param {string} path to the project
5534
5423
  * @param {Object} options Parse options from the cli
5424
+ * @returns {Promise<Object>} Promise resolving to BOM object
5535
5425
  */
5536
5426
  export async function createSwiftBom(path, options) {
5537
5427
  const swiftFiles = getAllFiles(
@@ -5676,6 +5566,7 @@ export async function createSwiftBom(path, options) {
5676
5566
  *
5677
5567
  * @param {string} path to the project
5678
5568
  * @param {Object} options Parse options from the cli
5569
+ * @returns {Promise<Object | undefined>} Promise resolving to a BOM object, or `undefined` when no Podfiles are found
5679
5570
  */
5680
5571
  export async function createCocoaBom(path, options) {
5681
5572
  const cocoaFiles = getAllFiles(
@@ -5692,7 +5583,7 @@ export async function createCocoaBom(path, options) {
5692
5583
  for (const podFile of cocoaFiles) {
5693
5584
  const projectPath = dirname(podFile);
5694
5585
  const lockFile = `${podFile}.lock`;
5695
- if (!existsSync(lockFile) || options.deep) {
5586
+ if (!safeExistsSync(lockFile) || options.deep) {
5696
5587
  if (options.installDeps) {
5697
5588
  executePodCommand(["install"], projectPath, options);
5698
5589
  } else {
@@ -5830,6 +5721,7 @@ export async function createCocoaBom(path, options) {
5830
5721
  *
5831
5722
  * @param {string} path to the project
5832
5723
  * @param {Object} options Parse options from the cli
5724
+ * @returns {Promise<Object>} Promise resolving to BOM object
5833
5725
  */
5834
5726
  export async function createNixBom(path, options) {
5835
5727
  let pkgList = [];
@@ -5952,6 +5844,7 @@ export async function createNixBom(path, options) {
5952
5844
  *
5953
5845
  * @param {string} path to the project
5954
5846
  * @param {Object} options Parse options from the cli
5847
+ * @returns {Promise<Object>} Promise resolving to BOM object
5955
5848
  */
5956
5849
  export async function createCaxaBom(path, options) {
5957
5850
  let pkgList = [];
@@ -6003,6 +5896,7 @@ export async function createCaxaBom(path, options) {
6003
5896
  *
6004
5897
  * @param {string} path to the project
6005
5898
  * @param {Object} options Parse options from the cli
5899
+ * @returns {Promise<Object>} Promise resolving to BOM object
6006
5900
  */
6007
5901
  export async function createContainerSpecLikeBom(path, options) {
6008
5902
  let services = [];
@@ -6331,6 +6225,7 @@ export async function createContainerSpecLikeBom(path, options) {
6331
6225
  *
6332
6226
  * @param {string} path to the project
6333
6227
  * @param {Object} options Parse options from the cli
6228
+ * @returns {Object} BOM object
6334
6229
  */
6335
6230
  export function createPHPBom(path, options) {
6336
6231
  let dependencies = [];
@@ -6438,7 +6333,7 @@ export function createPHPBom(path, options) {
6438
6333
  // Track all the modules in a mono-repo
6439
6334
  if (!Object.keys(parentComponent).length) {
6440
6335
  parentComponent = { ...moduleParent };
6441
- } else {
6336
+ } else if (moduleParent?.["bom-ref"]) {
6442
6337
  parentComponent.components = parentComponent.components || [];
6443
6338
  parentComponent.components.push(moduleParent);
6444
6339
  }
@@ -6454,7 +6349,9 @@ export function createPHPBom(path, options) {
6454
6349
  dependencies.splice(0, 0, {
6455
6350
  ref: moduleParent["bom-ref"],
6456
6351
  dependsOn: [
6457
- ...new Set(retMap.rootList.map((p) => p["bom-ref"])),
6352
+ ...new Set(
6353
+ retMap.rootList.map((p) => p["bom-ref"]).filter(Boolean),
6354
+ ),
6458
6355
  ].sort(),
6459
6356
  });
6460
6357
  }
@@ -6467,9 +6364,9 @@ export function createPHPBom(path, options) {
6467
6364
  }
6468
6365
  // Complete the root dependency tree
6469
6366
  if (parentComponent?.components?.length) {
6470
- const parentDependsOn = parentComponent.components.map(
6471
- (d) => d["bom-ref"],
6472
- );
6367
+ const parentDependsOn = parentComponent.components
6368
+ .map((d) => d["bom-ref"])
6369
+ .filter(Boolean);
6473
6370
  dependencies = mergeDependencies(
6474
6371
  [{ ref: parentComponent["bom-ref"], dependsOn: parentDependsOn }],
6475
6372
  dependencies,
@@ -6491,6 +6388,7 @@ export function createPHPBom(path, options) {
6491
6388
  *
6492
6389
  * @param {string} path to the project
6493
6390
  * @param {Object} options Parse options from the cli
6391
+ * @returns {Promise<Object>} Promise resolving to BOM object
6494
6392
  */
6495
6393
  export async function createRubyBom(path, options) {
6496
6394
  // We can look for gem files within node_modules directory
@@ -6744,6 +6642,7 @@ export async function createRubyBom(path, options) {
6744
6642
  *
6745
6643
  * @param {string} path to the project
6746
6644
  * @param {Object} options Parse options from the cli
6645
+ * @returns {Promise<Object|undefined>} Promise resolving to BOM object
6747
6646
  */
6748
6647
  export async function createCsharpBom(path, options) {
6749
6648
  let manifestFiles = [];
@@ -7179,11 +7078,202 @@ export async function createCsharpBom(path, options) {
7179
7078
  });
7180
7079
  }
7181
7080
 
7081
+ /**
7082
+ * Function to create BOM for VS Code / IDE extensions.
7083
+ * Supports two modes:
7084
+ * 1. Directory scan: Discovers `.vsix` files and installed extension directories
7085
+ * 2. IDE discovery: Automatically finds extensions installed by known IDEs
7086
+ *
7087
+ * @param {string} path to the project or directory to scan
7088
+ * @param {Object} options Parse options from the cli
7089
+ * @returns {Promise<Object>} Promise resolving to BOM object
7090
+ */
7091
+ export async function createVscodeExtensionBom(path, options) {
7092
+ let pkgList = [];
7093
+ let dependencies = [];
7094
+ const tempDirs = [];
7095
+
7096
+ // Mode 1: Scan for .vsix files in the given directory, or treat the input
7097
+ // path as a single .vsix file.
7098
+ let vsixFiles = [];
7099
+ if (path.endsWith(".vsix")) {
7100
+ vsixFiles = [resolve(path)];
7101
+ } else {
7102
+ vsixFiles = getAllFiles(
7103
+ path,
7104
+ `${options.multiProject ? "**/" : ""}*.vsix`,
7105
+ options,
7106
+ );
7107
+ }
7108
+ if (vsixFiles.length) {
7109
+ if (DEBUG_MODE) {
7110
+ console.log(`Found ${vsixFiles.length} .vsix file(s) to parse`);
7111
+ }
7112
+ for (const f of vsixFiles) {
7113
+ if (DEBUG_MODE) {
7114
+ console.log(`Parsing ${f}`);
7115
+ }
7116
+ // Get the extension component metadata
7117
+ const component = await parseVsixFile(f);
7118
+ if (component) {
7119
+ pkgList.push(component);
7120
+ }
7121
+ // Extract the vsix to a temp dir and run deep analysis
7122
+ const extractedDir = await extractVsixToTempDir(f);
7123
+ if (extractedDir) {
7124
+ tempDirs.push(extractedDir);
7125
+ const deepResult = await analyzeExtensionDir(extractedDir, options);
7126
+ if (deepResult.pkgList.length) {
7127
+ pkgList = pkgList.concat(deepResult.pkgList);
7128
+ }
7129
+ if (deepResult.dependencies.length) {
7130
+ dependencies = mergeDependencies(
7131
+ dependencies,
7132
+ deepResult.dependencies,
7133
+ );
7134
+ }
7135
+ }
7136
+ }
7137
+ }
7138
+
7139
+ // Mode 2: Auto-discover extensions from known IDE locations
7140
+ if (options.deep || options.projectType?.includes("ide-extensions")) {
7141
+ const ideDirs = discoverIdeExtensionDirs();
7142
+ if (ideDirs.length) {
7143
+ if (DEBUG_MODE) {
7144
+ console.log(
7145
+ `Discovered IDE extension directories: ${ideDirs.map((d) => `${d.name}: ${d.dir}`).join(", ")}`,
7146
+ );
7147
+ }
7148
+ const ideExtensions = collectInstalledExtensions(ideDirs);
7149
+ if (ideExtensions.length) {
7150
+ if (DEBUG_MODE) {
7151
+ console.log(
7152
+ `Found ${ideExtensions.length} IDE extension(s) from ${ideDirs.length} IDE location(s)`,
7153
+ );
7154
+ }
7155
+ pkgList = pkgList.concat(ideExtensions);
7156
+ // Deep analysis for IDE extension directories
7157
+ for (const ideDir of ideDirs) {
7158
+ await analyzeInstalledExtensionDirs(
7159
+ ideDir.dir,
7160
+ options,
7161
+ pkgList,
7162
+ dependencies,
7163
+ );
7164
+ }
7165
+ }
7166
+ }
7167
+ }
7168
+
7169
+ // Clean up temp directories from vsix extraction
7170
+ for (const td of tempDirs) {
7171
+ cleanupTempDir(td);
7172
+ }
7173
+ pkgList = trimComponents(pkgList);
7174
+ return buildBomNSData(options, pkgList, VSCODE_EXTENSION_PURL_TYPE, {
7175
+ src: path,
7176
+ filename: vsixFiles.join(", "),
7177
+ nsMapping: {},
7178
+ dependencies,
7179
+ });
7180
+ }
7181
+
7182
+ /**
7183
+ * Analyze an extracted extension directory for bundled dependencies.
7184
+ * Looks for npm lock files, node_modules, package.json files, minified JS,
7185
+ * and runs the babel-based analyzer on the source.
7186
+ *
7187
+ * @param {string} extDir Path to the extracted extension directory
7188
+ * @param {Object} options CLI options
7189
+ * @returns {Promise<{pkgList: Object[], dependencies: Object[]}>}
7190
+ */
7191
+ async function analyzeExtensionDir(extDir, options) {
7192
+ const pkgList = [];
7193
+ let dependencies = [];
7194
+ // Check if the extension directory contains node.js project artifacts
7195
+ const hasPackageJson = safeExistsSync(join(extDir, "package.json"));
7196
+ const hasNodeModules = safeExistsSync(join(extDir, "node_modules"));
7197
+ const hasLockFile =
7198
+ safeExistsSync(join(extDir, "package-lock.json")) ||
7199
+ safeExistsSync(join(extDir, "yarn.lock")) ||
7200
+ safeExistsSync(join(extDir, "pnpm-lock.yaml"));
7201
+
7202
+ // If there are lock files or node_modules, run the full Node.js BOM generator
7203
+ if (hasPackageJson && (hasLockFile || hasNodeModules)) {
7204
+ if (DEBUG_MODE) {
7205
+ console.log(
7206
+ `Running Node.js BOM analysis on extension directory: ${extDir}`,
7207
+ );
7208
+ }
7209
+ const nodeBomOptions = {
7210
+ ...options,
7211
+ path: extDir,
7212
+ multiProject: true,
7213
+ installDeps: false,
7214
+ noBabel: false,
7215
+ projectType: ["js"],
7216
+ };
7217
+ const bomData = await createNodejsBom(extDir, nodeBomOptions);
7218
+ if (bomData?.bomJson?.components?.length) {
7219
+ for (const comp of bomData.bomJson.components) {
7220
+ pkgList.push(comp);
7221
+ }
7222
+ }
7223
+ if (bomData?.bomJson?.dependencies?.length) {
7224
+ dependencies = mergeDependencies(
7225
+ dependencies,
7226
+ bomData.bomJson.dependencies,
7227
+ );
7228
+ }
7229
+ return { pkgList, dependencies };
7230
+ }
7231
+ return { pkgList, dependencies };
7232
+ }
7233
+
7234
+ /**
7235
+ * Run deep analysis on installed extension subdirectories within a parent
7236
+ * extensions directory. Each subdirectory represents an installed extension.
7237
+ *
7238
+ * @param {string} extensionsDir Parent directory containing extension subdirs
7239
+ * @param {Object} options CLI options
7240
+ * @param {Object[]} pkgList Mutable array to push discovered components into
7241
+ * @param {Object[]} dependencies Mutable array to merge dependencies into
7242
+ */
7243
+ async function analyzeInstalledExtensionDirs(
7244
+ extensionsDir,
7245
+ options,
7246
+ pkgList,
7247
+ dependencies,
7248
+ ) {
7249
+ let entries;
7250
+ try {
7251
+ entries = readdirSync(extensionsDir, { withFileTypes: true });
7252
+ } catch (_e) {
7253
+ return;
7254
+ }
7255
+ for (const entry of entries) {
7256
+ if (!entry.isDirectory() || entry.name.startsWith(".")) {
7257
+ continue;
7258
+ }
7259
+ const extDir = join(extensionsDir, entry.name);
7260
+ const deepResult = await analyzeExtensionDir(extDir, options);
7261
+ if (deepResult.pkgList.length) {
7262
+ pkgList.push(...deepResult.pkgList);
7263
+ }
7264
+ if (deepResult.dependencies.length) {
7265
+ const merged = mergeDependencies(dependencies, deepResult.dependencies);
7266
+ dependencies.splice(0, dependencies.length, ...merged);
7267
+ }
7268
+ }
7269
+ }
7270
+
7182
7271
  /**
7183
7272
  * Function to create bom object for cryptographic certificate files
7184
7273
  *
7185
7274
  * @param {string} path to the project
7186
7275
  * @param {Object} options Parse options from the cli
7276
+ * @returns {Promise<Object>} Promise resolving to BOM object
7187
7277
  */
7188
7278
  export async function createCryptoCertsBom(path, options) {
7189
7279
  const pkgList = [];
@@ -7220,191 +7310,6 @@ export async function createCryptoCertsBom(path, options) {
7220
7310
  };
7221
7311
  }
7222
7312
 
7223
- export function mergeDependencies(
7224
- dependencies,
7225
- newDependencies,
7226
- parentComponent = {},
7227
- ) {
7228
- if (!parentComponent && DEBUG_MODE) {
7229
- console.log(
7230
- "Unable to determine parent component. Dependencies will be flattened.",
7231
- );
7232
- }
7233
- let providesFound = false;
7234
- const deps_map = {};
7235
- const provides_map = {};
7236
- const parentRef = parentComponent?.["bom-ref"]
7237
- ? parentComponent["bom-ref"]
7238
- : undefined;
7239
- const combinedDeps = dependencies.concat(newDependencies || []);
7240
- for (const adep of combinedDeps) {
7241
- if (!deps_map[adep.ref]) {
7242
- deps_map[adep.ref] = new Set();
7243
- }
7244
- if (!provides_map[adep.ref]) {
7245
- provides_map[adep.ref] = new Set();
7246
- }
7247
- if (adep["dependsOn"]) {
7248
- for (const eachDepends of adep["dependsOn"]) {
7249
- if (parentRef && eachDepends) {
7250
- if (eachDepends.toLowerCase() !== parentRef.toLowerCase()) {
7251
- deps_map[adep.ref].add(eachDepends);
7252
- }
7253
- } else {
7254
- deps_map[adep.ref].add(eachDepends);
7255
- }
7256
- }
7257
- }
7258
- if (adep["provides"]) {
7259
- providesFound = true;
7260
- for (const eachProvides of adep["provides"]) {
7261
- if (
7262
- parentRef &&
7263
- eachProvides.toLowerCase() !== parentRef.toLowerCase()
7264
- ) {
7265
- provides_map[adep.ref].add(eachProvides);
7266
- }
7267
- }
7268
- }
7269
- }
7270
- const retlist = [];
7271
- for (const akey of Object.keys(deps_map)) {
7272
- if (providesFound) {
7273
- retlist.push({
7274
- ref: akey,
7275
- dependsOn: Array.from(deps_map[akey]).sort(),
7276
- provides: Array.from(provides_map[akey]).sort(),
7277
- });
7278
- } else {
7279
- retlist.push({
7280
- ref: akey,
7281
- dependsOn: Array.from(deps_map[akey]).sort(),
7282
- });
7283
- }
7284
- }
7285
- return retlist;
7286
- }
7287
-
7288
- /**
7289
- * Trim duplicate components by retaining all the properties
7290
- *
7291
- * @param {Array} components Components
7292
- *
7293
- * @returns {Array} Filtered components
7294
- */
7295
- export function trimComponents(components) {
7296
- const keyCache = {};
7297
- const filteredComponents = [];
7298
- for (const comp of components) {
7299
- const key = (
7300
- comp.purl ||
7301
- comp["bom-ref"] ||
7302
- comp.name + comp.version
7303
- ).toLowerCase();
7304
- if (!keyCache[key]) {
7305
- keyCache[key] = comp;
7306
- } else {
7307
- const existingComponent = keyCache[key];
7308
- // We need to retain any properties that differ
7309
- if (comp.properties) {
7310
- if (existingComponent.properties) {
7311
- for (const newprop of comp.properties) {
7312
- if (
7313
- !existingComponent.properties.find(
7314
- (prop) =>
7315
- prop.name === newprop.name && prop.value === newprop.value,
7316
- )
7317
- ) {
7318
- existingComponent.properties.push(newprop);
7319
- }
7320
- }
7321
- } else {
7322
- existingComponent.properties = comp.properties;
7323
- }
7324
- }
7325
- // Retain all component.evidence.identity
7326
- if (comp?.evidence?.identity) {
7327
- if (!existingComponent.evidence) {
7328
- existingComponent.evidence = { identity: [] };
7329
- } else if (!existingComponent?.evidence?.identity) {
7330
- existingComponent.evidence.identity = [];
7331
- } else if (
7332
- existingComponent?.evidence?.identity &&
7333
- !Array.isArray(existingComponent.evidence.identity)
7334
- ) {
7335
- existingComponent.evidence.identity = [
7336
- existingComponent.evidence.identity,
7337
- ];
7338
- }
7339
- // comp.evidence.identity can be an array or object
7340
- // Merge the evidence.identity based on methods or objects
7341
- const isIdentityArray = Array.isArray(comp.evidence.identity);
7342
- const identities = isIdentityArray
7343
- ? comp.evidence.identity
7344
- : [comp.evidence.identity];
7345
- for (const aident of identities) {
7346
- let methodBasedMerge = false;
7347
- if (aident?.methods?.length) {
7348
- for (const amethod of aident.methods) {
7349
- for (const existIdent of existingComponent.evidence.identity) {
7350
- if (existIdent.field === aident.field) {
7351
- if (!existIdent.methods) {
7352
- existIdent.methods = [];
7353
- }
7354
- let isDup = false;
7355
- for (const emethod of existIdent.methods) {
7356
- if (emethod?.value === amethod?.value) {
7357
- isDup = true;
7358
- break;
7359
- }
7360
- }
7361
- if (!isDup) {
7362
- existIdent.methods.push(amethod);
7363
- }
7364
- methodBasedMerge = true;
7365
- }
7366
- }
7367
- }
7368
- }
7369
- if (!methodBasedMerge && aident.field && aident.confidence) {
7370
- existingComponent.evidence.identity.push(aident);
7371
- }
7372
- }
7373
- if (!isIdentityArray) {
7374
- const firstIdentity = existingComponent.evidence.identity[0];
7375
- let identConfidence = firstIdentity?.confidence;
7376
- // We need to set the confidence to the max of all confidences
7377
- if (firstIdentity?.methods?.length > 1) {
7378
- for (const aidentMethod of firstIdentity.methods) {
7379
- if (
7380
- aidentMethod?.confidence &&
7381
- aidentMethod.confidence > identConfidence
7382
- ) {
7383
- identConfidence = aidentMethod.confidence;
7384
- }
7385
- }
7386
- }
7387
- firstIdentity.confidence = identConfidence;
7388
- existingComponent.evidence = {
7389
- identity: firstIdentity,
7390
- };
7391
- }
7392
- }
7393
- // If the component is required in any of the child projects, then make it required
7394
- if (
7395
- existingComponent?.scope !== "required" &&
7396
- comp?.scope === "required"
7397
- ) {
7398
- existingComponent.scope = "required";
7399
- }
7400
- }
7401
- }
7402
- for (const akey of Object.keys(keyCache)) {
7403
- filteredComponents.push(keyCache[akey]);
7404
- }
7405
- return filteredComponents;
7406
- }
7407
-
7408
7313
  /**
7409
7314
  * Dedupe components
7410
7315
  *
@@ -7461,11 +7366,13 @@ export function dedupeBom(options, components, parentComponent, dependencies) {
7461
7366
  *
7462
7367
  * @param {string[]} pathList list of to the project
7463
7368
  * @param {Object} options Parse options from the cli
7369
+ * @returns {Promise<Object>} Promise resolving to BOM object
7464
7370
  */
7465
7371
  export async function createMultiXBom(pathList, options) {
7466
7372
  let components = [];
7467
7373
  let dependencies = [];
7468
7374
  let bomData;
7375
+ let formulationList = [];
7469
7376
  let parentComponent = determineParentComponent(options) || {};
7470
7377
  let parentSubComponents = [];
7471
7378
  options.createMultiXBom = true;
@@ -7676,6 +7583,9 @@ export async function createMultiXBom(pathList, options) {
7676
7583
  parentSubComponents.push(bomData.parentComponent);
7677
7584
  }
7678
7585
  }
7586
+ if (bomData?.formulationList?.length) {
7587
+ formulationList = formulationList.concat(bomData.formulationList);
7588
+ }
7679
7589
  }
7680
7590
  if (hasAnyProjectType(["oci", "go"], options)) {
7681
7591
  if (!hasAnyProjectType(["oci"], options, false)) {
@@ -8168,6 +8078,27 @@ export async function createMultiXBom(pathList, options) {
8168
8078
  }
8169
8079
  }
8170
8080
  }
8081
+ if (hasAnyProjectType(["vscode-extension"], options)) {
8082
+ bomData = await createVscodeExtensionBom(path, options);
8083
+ if (bomData?.bomJson?.components?.length) {
8084
+ if (DEBUG_MODE) {
8085
+ console.log(
8086
+ `Found ${bomData.bomJson.components.length} VS Code extension(s) at ${path}`,
8087
+ );
8088
+ }
8089
+ components = components.concat(bomData.bomJson.components);
8090
+ dependencies = mergeDependencies(
8091
+ dependencies,
8092
+ bomData.bomJson.dependencies,
8093
+ );
8094
+ if (
8095
+ bomData.parentComponent &&
8096
+ Object.keys(bomData.parentComponent).length
8097
+ ) {
8098
+ parentSubComponents.push(bomData.parentComponent);
8099
+ }
8100
+ }
8101
+ }
8171
8102
  // Collect any crypto keys
8172
8103
  if (options.specVersion >= 1.6 && options.includeCrypto) {
8173
8104
  if (!hasAnyProjectType(["oci"], options, false)) {
@@ -8266,7 +8197,16 @@ export async function createMultiXBom(pathList, options) {
8266
8197
  }
8267
8198
  }
8268
8199
  }
8269
- return dedupeBom(options, components, parentComponent, dependencies);
8200
+ const multiResult = dedupeBom(
8201
+ options,
8202
+ components,
8203
+ parentComponent,
8204
+ dependencies,
8205
+ );
8206
+ if (formulationList.length) {
8207
+ multiResult.formulationList = formulationList;
8208
+ }
8209
+ return multiResult;
8270
8210
  }
8271
8211
 
8272
8212
  /**
@@ -8274,6 +8214,7 @@ export async function createMultiXBom(pathList, options) {
8274
8214
  *
8275
8215
  * @param {string} path to the project
8276
8216
  * @param {Object} options Parse options from the cli
8217
+ * @returns {Promise<Object|undefined>} Promise resolving to BOM object, or undefined if path is not readable
8277
8218
  */
8278
8219
  export async function createXBom(path, options) {
8279
8220
  try {
@@ -8512,6 +8453,16 @@ export async function createXBom(path, options) {
8512
8453
  return await createJenkinsBom(path, options);
8513
8454
  }
8514
8455
 
8456
+ // VS Code extensions (.vsix files)
8457
+ const vsixFiles = getAllFiles(
8458
+ path,
8459
+ `${options.multiProject ? "**/" : ""}*.vsix`,
8460
+ options,
8461
+ );
8462
+ if (vsixFiles.length) {
8463
+ return await createVscodeExtensionBom(path, options);
8464
+ }
8465
+
8515
8466
  // Helm charts
8516
8467
  const chartFiles = getAllFiles(
8517
8468
  path,
@@ -8619,6 +8570,7 @@ export async function createXBom(path, options) {
8619
8570
  *
8620
8571
  * @param {string} path to the project
8621
8572
  * @param {Object} options Parse options from the cli
8573
+ * @returns {Promise<Object>} Promise resolving to BOM object
8622
8574
  */
8623
8575
  export async function createBom(path, options) {
8624
8576
  let { projectType } = options;
@@ -8863,6 +8815,9 @@ export async function createBom(path, options) {
8863
8815
  if (PROJECT_TYPE_ALIASES["caxa"].includes(projectType[0])) {
8864
8816
  return await createCaxaBom(path, options);
8865
8817
  }
8818
+ if (PROJECT_TYPE_ALIASES["vscode-extension"].includes(projectType[0])) {
8819
+ return await createVscodeExtensionBom(path, options);
8820
+ }
8866
8821
  switch (projectType[0]) {
8867
8822
  case "jar":
8868
8823
  return createJarBom(path, options);