@cyclonedx/cdxgen 12.1.4 → 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 (184) hide show
  1. package/README.md +47 -39
  2. package/bin/cdxgen.js +181 -90
  3. package/bin/evinse.js +4 -4
  4. package/bin/repl.js +3 -3
  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 +484 -440
  14. package/lib/evinser/db.js +137 -0
  15. package/lib/{helpers → evinser}/db.poku.js +2 -6
  16. package/lib/evinser/evinser.js +5 -18
  17. package/lib/evinser/swiftsem.js +1 -1
  18. package/lib/helpers/bomSigner.js +312 -0
  19. package/lib/helpers/bomSigner.poku.js +156 -0
  20. package/lib/helpers/caxa.js +1 -1
  21. package/lib/helpers/ciParsers/azurePipelines.js +295 -0
  22. package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
  23. package/lib/helpers/ciParsers/circleCi.js +286 -0
  24. package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
  25. package/lib/helpers/ciParsers/common.js +24 -0
  26. package/lib/helpers/ciParsers/githubActions.js +636 -0
  27. package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
  28. package/lib/helpers/ciParsers/gitlabCi.js +213 -0
  29. package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
  30. package/lib/helpers/ciParsers/jenkins.js +181 -0
  31. package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
  32. package/lib/helpers/depsUtils.js +203 -0
  33. package/lib/helpers/depsUtils.poku.js +150 -0
  34. package/lib/helpers/display.js +429 -14
  35. package/lib/helpers/envcontext.js +23 -8
  36. package/lib/helpers/formulationParsers.js +351 -0
  37. package/lib/helpers/logger.js +14 -0
  38. package/lib/helpers/protobom.js +9 -9
  39. package/lib/helpers/pythonutils.js +305 -0
  40. package/lib/helpers/pythonutils.poku.js +469 -0
  41. package/lib/helpers/utils.js +970 -528
  42. package/lib/helpers/utils.poku.js +139 -256
  43. package/lib/helpers/versutils.js +202 -0
  44. package/lib/helpers/versutils.poku.js +315 -0
  45. package/lib/helpers/vsixutils.js +1061 -0
  46. package/lib/helpers/vsixutils.poku.js +2247 -0
  47. package/lib/managers/binary.js +19 -19
  48. package/lib/managers/docker.js +108 -1
  49. package/lib/managers/oci.js +10 -0
  50. package/lib/managers/piptree.js +4 -10
  51. package/lib/parsers/npmrc.js +92 -0
  52. package/lib/parsers/npmrc.poku.js +528 -0
  53. package/lib/server/openapi.yaml +1 -10
  54. package/lib/server/server.js +58 -16
  55. package/lib/server/server.poku.js +123 -144
  56. package/lib/stages/postgen/annotator.js +1 -1
  57. package/lib/stages/postgen/auditBom.js +197 -0
  58. package/lib/stages/postgen/auditBom.poku.js +378 -0
  59. package/lib/stages/postgen/postgen.js +54 -1
  60. package/lib/stages/postgen/postgen.poku.js +90 -1
  61. package/lib/stages/postgen/ruleEngine.js +369 -0
  62. package/lib/stages/pregen/envAudit.js +299 -0
  63. package/lib/stages/pregen/envAudit.poku.js +572 -0
  64. package/lib/stages/pregen/pregen.js +12 -8
  65. package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
  66. package/lib/third-party/arborist/lib/node.js +3 -3
  67. package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
  68. package/lib/third-party/arborist/lib/tree-check.js +1 -1
  69. package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
  70. package/lib/validator/complianceEngine.js +241 -0
  71. package/lib/validator/complianceEngine.poku.js +168 -0
  72. package/lib/validator/complianceRules.js +1610 -0
  73. package/lib/validator/complianceRules.poku.js +328 -0
  74. package/lib/validator/index.js +222 -0
  75. package/lib/validator/index.poku.js +144 -0
  76. package/lib/validator/reporters/annotations.js +121 -0
  77. package/lib/validator/reporters/console.js +149 -0
  78. package/lib/validator/reporters/index.js +41 -0
  79. package/lib/validator/reporters/json.js +37 -0
  80. package/lib/validator/reporters/sarif.js +184 -0
  81. package/lib/validator/reporters.poku.js +150 -0
  82. package/package.json +8 -8
  83. package/types/bin/sign.d.ts +3 -0
  84. package/types/bin/sign.d.ts.map +1 -0
  85. package/types/bin/validate.d.ts +3 -0
  86. package/types/bin/validate.d.ts.map +1 -0
  87. package/types/helpers/utils.d.ts +0 -1
  88. package/types/lib/cli/index.d.ts +49 -52
  89. package/types/lib/cli/index.d.ts.map +1 -1
  90. package/types/lib/evinser/db.d.ts +34 -0
  91. package/types/lib/evinser/db.d.ts.map +1 -0
  92. package/types/lib/evinser/evinser.d.ts +63 -16
  93. package/types/lib/evinser/evinser.d.ts.map +1 -1
  94. package/types/lib/helpers/bomSigner.d.ts +27 -0
  95. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  96. package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
  97. package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
  98. package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
  99. package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
  100. package/types/lib/helpers/ciParsers/common.d.ts +11 -0
  101. package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
  102. package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
  103. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
  104. package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
  105. package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
  106. package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
  107. package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
  108. package/types/lib/helpers/depsUtils.d.ts +21 -0
  109. package/types/lib/helpers/depsUtils.d.ts.map +1 -0
  110. package/types/lib/helpers/display.d.ts +111 -11
  111. package/types/lib/helpers/display.d.ts.map +1 -1
  112. package/types/lib/helpers/envcontext.d.ts +19 -7
  113. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  114. package/types/lib/helpers/formulationParsers.d.ts +50 -0
  115. package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
  116. package/types/lib/helpers/logger.d.ts +15 -1
  117. package/types/lib/helpers/logger.d.ts.map +1 -1
  118. package/types/lib/helpers/protobom.d.ts +2 -2
  119. package/types/lib/helpers/pythonutils.d.ts +18 -0
  120. package/types/lib/helpers/pythonutils.d.ts.map +1 -0
  121. package/types/lib/helpers/utils.d.ts +532 -128
  122. package/types/lib/helpers/utils.d.ts.map +1 -1
  123. package/types/lib/helpers/versutils.d.ts +8 -0
  124. package/types/lib/helpers/versutils.d.ts.map +1 -0
  125. package/types/lib/helpers/vsixutils.d.ts +130 -0
  126. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  127. package/types/lib/managers/docker.d.ts +12 -31
  128. package/types/lib/managers/docker.d.ts.map +1 -1
  129. package/types/lib/managers/oci.d.ts +11 -1
  130. package/types/lib/managers/oci.d.ts.map +1 -1
  131. package/types/lib/managers/piptree.d.ts.map +1 -1
  132. package/types/lib/parsers/npmrc.d.ts +26 -0
  133. package/types/lib/parsers/npmrc.d.ts.map +1 -0
  134. package/types/lib/server/server.d.ts +21 -2
  135. package/types/lib/server/server.d.ts.map +1 -1
  136. package/types/lib/stages/postgen/auditBom.d.ts +20 -0
  137. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  138. package/types/lib/stages/postgen/postgen.d.ts +8 -1
  139. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  140. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  141. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  142. package/types/lib/stages/pregen/envAudit.d.ts +8 -0
  143. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  144. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  145. package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
  146. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  147. package/types/lib/validator/complianceEngine.d.ts +66 -0
  148. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  149. package/types/lib/validator/complianceRules.d.ts +70 -0
  150. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  151. package/types/lib/validator/index.d.ts +70 -0
  152. package/types/lib/validator/index.d.ts.map +1 -0
  153. package/types/lib/validator/reporters/annotations.d.ts +31 -0
  154. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  155. package/types/lib/validator/reporters/console.d.ts +30 -0
  156. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  157. package/types/lib/validator/reporters/index.d.ts +21 -0
  158. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  159. package/types/lib/validator/reporters/json.d.ts +11 -0
  160. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  161. package/types/lib/validator/reporters/sarif.d.ts +16 -0
  162. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  163. package/lib/helpers/db.js +0 -162
  164. package/types/helpers/db.d.ts +0 -35
  165. package/types/helpers/db.d.ts.map +0 -1
  166. package/types/lib/helpers/db.d.ts +0 -35
  167. package/types/lib/helpers/db.d.ts.map +0 -1
  168. package/types/lib/helpers/validator.d.ts.map +0 -1
  169. package/types/managers/binary.d.ts +0 -37
  170. package/types/managers/binary.d.ts.map +0 -1
  171. package/types/managers/docker.d.ts +0 -56
  172. package/types/managers/docker.d.ts.map +0 -1
  173. package/types/managers/oci.d.ts +0 -2
  174. package/types/managers/oci.d.ts.map +0 -1
  175. package/types/managers/piptree.d.ts +0 -2
  176. package/types/managers/piptree.d.ts.map +0 -1
  177. package/types/server/server.d.ts +0 -34
  178. package/types/server/server.d.ts.map +0 -1
  179. package/types/stages/postgen/annotator.d.ts +0 -27
  180. package/types/stages/postgen/annotator.d.ts.map +0 -1
  181. package/types/stages/postgen/postgen.d.ts +0 -51
  182. package/types/stages/postgen/postgen.d.ts.map +0 -1
  183. package/types/stages/pregen/pregen.d.ts +0 -59
  184. 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,14 +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
- getBranch,
32
- getOriginUrl,
33
- gitTreeHashes,
34
- listFiles,
35
- } from "../helpers/envcontext.js";
28
+ import { mergeDependencies, trimComponents } from "../helpers/depsUtils.js";
29
+ import { GIT_COMMAND } from "../helpers/envcontext.js";
36
30
  import { thoughtLog } from "../helpers/logger.js";
37
31
  import {
38
32
  addEvidenceForDotnet,
@@ -175,6 +169,14 @@ import {
175
169
  shouldFetchLicense,
176
170
  splitOutputByGradleProjects,
177
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";
178
180
  import {
179
181
  executeOsQuery,
180
182
  getBinaryBom,
@@ -188,6 +190,7 @@ import {
188
190
  getPkgPathList,
189
191
  parseImageName,
190
192
  } from "../managers/docker.js";
193
+ import { DEFAULT_NPMRC_BLOCKLIST, parseNpmrc } from "../parsers/npmrc.js";
191
194
 
192
195
  const dirName = dirNameStr;
193
196
 
@@ -436,160 +439,6 @@ const addLifecyclesSection = (options) => {
436
439
  return lifecycles;
437
440
  };
438
441
 
439
- /**
440
- * Method to generate the formulation section based on git metadata
441
- *
442
- * @param {Object} options
443
- * @param {Object} context Context
444
- * @returns {Array} formulation array
445
- */
446
- const addFormulationSection = (options, context) => {
447
- const formulation = [];
448
- const provides = [];
449
- const gitBranch = getBranch();
450
- const originUrl = getOriginUrl();
451
- const gitFiles = listFiles();
452
- const treeHashes = gitTreeHashes();
453
- let parentOmniborId;
454
- let treeOmniborId;
455
- let components = [];
456
- const aformulation = {};
457
- // Reuse any existing formulation components
458
- // See: PR #1172
459
- if (context?.formulationList?.length) {
460
- components = components.concat(trimComponents(context.formulationList));
461
- }
462
- if (options.specVersion >= 1.6 && Object.keys(treeHashes).length === 2) {
463
- parentOmniborId = `gitoid:blob:sha1:${treeHashes.parent}`;
464
- treeOmniborId = `gitoid:blob:sha1:${treeHashes.tree}`;
465
- components.push({
466
- type: "file",
467
- name: "git-parent",
468
- description: "Git Parent Node.",
469
- "bom-ref": parentOmniborId,
470
- omniborId: [parentOmniborId],
471
- swhid: [`swh:1:rev:${treeHashes.parent}`],
472
- });
473
- components.push({
474
- type: "file",
475
- name: "git-tree",
476
- description: "Git Tree Node.",
477
- "bom-ref": treeOmniborId,
478
- omniborId: [treeOmniborId],
479
- swhid: [`swh:1:rev:${treeHashes.tree}`],
480
- });
481
- provides.push({
482
- ref: parentOmniborId,
483
- provides: [treeOmniborId],
484
- });
485
- }
486
- // Collect git related components
487
- if (gitBranch && gitFiles) {
488
- const gitFileComponents = gitFiles.map((f) =>
489
- options.specVersion >= 1.6
490
- ? {
491
- type: "file",
492
- name: f.name,
493
- version: f.hash,
494
- omniborId: [f.omniborId],
495
- swhid: [f.swhid],
496
- }
497
- : {
498
- type: "file",
499
- name: f.name,
500
- version: f.hash,
501
- },
502
- );
503
- components = components.concat(gitFileComponents);
504
- // Complete the Artifact Dependency Graph
505
- if (options.specVersion >= 1.6 && treeOmniborId) {
506
- provides.push({
507
- ref: treeOmniborId,
508
- provides: gitFiles.map((f) => f.ref),
509
- });
510
- }
511
- }
512
- // Collect build environment details
513
- const infoComponents = collectEnvInfo(options.path);
514
- if (infoComponents?.length) {
515
- components = components.concat(infoComponents);
516
- }
517
- // Should we include the OS crypto libraries
518
- if (options.includeCrypto) {
519
- const cryptoLibs = collectOSCryptoLibs(options);
520
- if (cryptoLibs?.length) {
521
- components = components.concat(cryptoLibs);
522
- }
523
- }
524
- aformulation["bom-ref"] = uuidv4();
525
- aformulation.components = trimComponents(components);
526
- let environmentVars = gitBranch?.length
527
- ? [{ name: "GIT_BRANCH", value: gitBranch }]
528
- : [];
529
- const envPrefixes = [
530
- "GIT",
531
- "ANDROID",
532
- "DENO",
533
- "DOTNET",
534
- "JAVA_",
535
- "SDKMAN",
536
- "CARGO",
537
- "CONDA",
538
- "RUST",
539
- "GEM_",
540
- "SCALA_",
541
- "MAVEN_",
542
- "GRADLE_",
543
- "NODE_",
544
- ];
545
- const envBlocklist = [
546
- "key",
547
- "token",
548
- "pass",
549
- "secret",
550
- "user",
551
- "email",
552
- "auth",
553
- "session",
554
- "proxy",
555
- "cred",
556
- ];
557
-
558
- for (const aevar of Object.keys(process.env)) {
559
- const lower = aevar.toLowerCase();
560
- const value = process.env[aevar] ?? "";
561
- if (
562
- envPrefixes.some((p) => aevar.startsWith(p)) &&
563
- !envBlocklist.some((b) => lower.includes(b)) &&
564
- !envBlocklist.some((b) => value.includes(b)) &&
565
- value.length
566
- ) {
567
- environmentVars.push({
568
- name: aevar,
569
- value,
570
- });
571
- }
572
- }
573
- if (!environmentVars.length) {
574
- environmentVars = undefined;
575
- }
576
- let sourceInput;
577
- if (environmentVars) {
578
- sourceInput = { environmentVars };
579
- }
580
- const sourceWorkflow = {
581
- "bom-ref": uuidv4(),
582
- uid: uuidv4(),
583
- taskTypes: originUrl ? ["build", "clone"] : ["build"],
584
- };
585
- if (sourceInput) {
586
- sourceWorkflow.inputs = [sourceInput];
587
- }
588
- aformulation.workflows = [sourceWorkflow];
589
- formulation.push(aformulation);
590
- return { formulation, provides };
591
- };
592
-
593
442
  /**
594
443
  * Function to create metadata block
595
444
  *
@@ -998,6 +847,7 @@ function addExternalReferences(opkg) {
998
847
  * @param {Object} allImports All imports
999
848
  * @param {Object} pkg Package object
1000
849
  * @param {string} ptype Package type
850
+ * @returns {Object[]} Array of component objects
1001
851
  */
1002
852
  export function listComponents(options, allImports, pkg, ptype = "npm") {
1003
853
  const compMap = {};
@@ -1065,6 +915,15 @@ function addComponent(
1065
915
  purl = undefined;
1066
916
  purlString = undefined;
1067
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
+ }
1068
927
  const description = pkg.description || undefined;
1069
928
  let compScope = pkg.scope;
1070
929
  if (allImports) {
@@ -1104,7 +963,12 @@ function addComponent(
1104
963
  component.data = pkg.data || undefined;
1105
964
  }
1106
965
  component["type"] = determinePackageType(pkg);
1107
- 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
+
1108
972
  if (
1109
973
  component.externalReferences === undefined ||
1110
974
  component.externalReferences.length === 0
@@ -1134,6 +998,18 @@ function addComponent(
1134
998
  }
1135
999
  delete component.authors;
1136
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
+ }
1137
1013
  // Retain any tags
1138
1014
  if (
1139
1015
  options.specVersion >= 1.6 &&
@@ -1153,12 +1029,10 @@ function addComponent(
1153
1029
  if (pkg.components) {
1154
1030
  component.components = pkg.components;
1155
1031
  }
1032
+ const compMapKey = component.purl || component["bom-ref"];
1156
1033
  // Issue: 1353. We need to keep merging the properties
1157
- if (compMap[component.purl]) {
1158
- const mergedComponents = trimComponents([
1159
- compMap[component.purl],
1160
- component,
1161
- ]);
1034
+ if (compMap[compMapKey]) {
1035
+ const mergedComponents = trimComponents([compMap[compMapKey], component]);
1162
1036
  if (mergedComponents?.length === 1) {
1163
1037
  component = mergedComponents[0];
1164
1038
  }
@@ -1194,7 +1068,7 @@ function addComponent(
1194
1068
  component.evidence.identity = pkg.evidence.identity[0];
1195
1069
  }
1196
1070
  }
1197
- compMap[component.purl] = component;
1071
+ compMap[compMapKey] = component;
1198
1072
  }
1199
1073
  if (pkg.dependencies) {
1200
1074
  Object.keys(pkg.dependencies)
@@ -1385,17 +1259,15 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1385
1259
  components,
1386
1260
  dependencies,
1387
1261
  };
1388
- const formulationData =
1389
- options.includeFormulation && options.specVersion >= 1.5
1390
- ? addFormulationSection(options, context)
1391
- : undefined;
1392
- if (formulationData) {
1393
- jsonTpl.formulation = formulationData.formulation;
1394
- }
1395
1262
  bomNSData.bomJson = jsonTpl;
1396
1263
  bomNSData.nsMapping = nsMapping;
1397
1264
  bomNSData.dependencies = dependencies;
1398
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
+ }
1399
1271
  }
1400
1272
  return bomNSData;
1401
1273
  };
@@ -1487,6 +1359,7 @@ export async function createJarBom(path, options) {
1487
1359
  *
1488
1360
  * @param {string} path to the project
1489
1361
  * @param {Object} options Parse options from the cli
1362
+ * @returns {Object|undefined} BOM object
1490
1363
  */
1491
1364
  export function createAndroidBom(path, options) {
1492
1365
  return createBinaryBom(path, options);
@@ -1497,6 +1370,7 @@ export function createAndroidBom(path, options) {
1497
1370
  *
1498
1371
  * @param {string} path to the project
1499
1372
  * @param {Object} options Parse options from the cli
1373
+ * @returns {Object|undefined} BOM object
1500
1374
  */
1501
1375
  export function createBinaryBom(path, options) {
1502
1376
  const tempDir = mkdtempSync(join(getTmpDir(), "blint-tmp-"));
@@ -1520,6 +1394,7 @@ export function createBinaryBom(path, options) {
1520
1394
  *
1521
1395
  * @param {string} path to the project
1522
1396
  * @param {Object} options Parse options from the cli
1397
+ * @returns {Promise<Object>} Promise resolving to BOM object
1523
1398
  */
1524
1399
  export async function createJavaBom(path, options) {
1525
1400
  let jarNSMapping = {};
@@ -2767,6 +2642,7 @@ export async function createJavaBom(path, options) {
2767
2642
  *
2768
2643
  * @param {string} path to the project
2769
2644
  * @param {Object} options Parse options from the cli
2645
+ * @returns {Promise<Object>} Promise resolving to BOM object
2770
2646
  */
2771
2647
  export async function createNodejsBom(path, options) {
2772
2648
  let pkgList = [];
@@ -2879,6 +2755,7 @@ export async function createNodejsBom(path, options) {
2879
2755
  );
2880
2756
  const npmInstallCount =
2881
2757
  Number.parseInt(process.env.NPM_INSTALL_COUNT, 10) || 2;
2758
+ let anyInstallSuccess = false;
2882
2759
  // Automatic npm install logic.
2883
2760
  // Only perform npm install for smaller projects (< 2 package.json) without the correct number of lock files
2884
2761
  if (
@@ -2889,7 +2766,6 @@ export async function createNodejsBom(path, options) {
2889
2766
  pkgJsonFiles?.length <= npmInstallCount &&
2890
2767
  options.installDeps
2891
2768
  ) {
2892
- let anyInstallSuccess = false;
2893
2769
  for (const apkgJson of pkgJsonFiles) {
2894
2770
  let pkgMgr = "npm";
2895
2771
  const supPkgMgrs = ["npm", "yarn", "yarnpkg", "pnpm", "pnpx"];
@@ -2938,17 +2814,50 @@ export async function createNodejsBom(path, options) {
2938
2814
  if (pkgMgr === "pnpm") {
2939
2815
  installArgs.push("--ignore-pnpmfile");
2940
2816
  }
2941
- if (pkgMgr === "npm" && !installArgs.includes("--no-audit")) {
2942
- installArgs.push("--no-audit");
2817
+ if (pkgMgr === "npm") {
2818
+ for (const c of ["--no-audit", "--no-bin-links"]) {
2819
+ if (!installArgs.includes(c)) {
2820
+ installArgs.push(c);
2821
+ }
2822
+ }
2823
+ installArgs.push(`--git=${GIT_COMMAND}`);
2943
2824
  }
2944
2825
  }
2826
+ if (
2827
+ pkgMgr === "npm" &&
2828
+ isSecureMode &&
2829
+ !installArgs.join(" ").includes("--allow-git")
2830
+ ) {
2831
+ console.log(
2832
+ "Consider passing '--allow-git=none' via the environment variable NPM_INSTALL_ARGS to prevent any git dependencies from being fetched and installed via npm.",
2833
+ );
2834
+ }
2945
2835
  const basePath = dirname(apkgJson);
2836
+ let npmrcData;
2837
+ if (safeExistsSync(join(basePath, ".npmrc"))) {
2838
+ thoughtLog(
2839
+ "Wait, there is a .npmrc file here! I'm going to check if it has anything malicious.",
2840
+ );
2841
+ npmrcData = readFileSync(join(basePath, ".npmrc"), "utf-8");
2842
+ const npmrcObj = parseNpmrc(npmrcData);
2843
+ for (const [key, value] of Object.entries(npmrcObj)) {
2844
+ const baseKey = key.replace(/^(?:\/\/[^/]+\/|@[^:]+:)/, "");
2845
+ if (
2846
+ DEFAULT_NPMRC_BLOCKLIST.has(baseKey) ||
2847
+ DEFAULT_NPMRC_BLOCKLIST.has(key)
2848
+ ) {
2849
+ console.warn(
2850
+ `\x1b[1;35mSECURE MODE: Dangerous configuration ${key}=${value} detected in .npmrc! Verify if this is a trusted project. Remove this setting or any other problematic configurations to proceed.\x1b[0m`,
2851
+ );
2852
+ process.exit(1);
2853
+ }
2854
+ }
2855
+ }
2946
2856
  // juice-shop mode
2947
2857
  // Projects such as juice-shop prevent lockfile creations using .npmrc files
2948
2858
  // Plus, they might require specific npm install args such as --legacy-peer-deps that could lead to strange node_modules structure
2949
2859
  // To keep life simple, let's look for any .npmrc file that has package-lock=false to toggle before npm install
2950
- if (pkgMgr === "npm" && safeExistsSync(join(basePath, ".npmrc"))) {
2951
- const npmrcData = readFileSync(join(basePath, ".npmrc"));
2860
+ if (pkgMgr === "npm") {
2952
2861
  if (
2953
2862
  npmrcData?.includes("package-lock=false") &&
2954
2863
  !installArgs.includes("--package-lock")
@@ -2966,6 +2875,9 @@ export async function createNodejsBom(path, options) {
2966
2875
  `**PACKAGE MANAGER**: Let's run the '${pkgMgr}' command with the arguments '${installArgs.join(" ")}' to generate the needed lock files.`,
2967
2876
  );
2968
2877
  }
2878
+ console.warn(
2879
+ "\x1b[1;35mNotice: Generating an SBOM without a lockfile is risky and non-deterministic. Consider generating and committing the lockfile to your repository to ensure reproducible builds and SBOMs.\x1b[0m",
2880
+ );
2969
2881
  console.log(
2970
2882
  `Executing '${pkgMgr} ${installArgs.join(" ")}' in`,
2971
2883
  basePath,
@@ -3165,8 +3077,9 @@ export async function createNodejsBom(path, options) {
3165
3077
  const basePath = dirname(f);
3166
3078
  // Determine the parent component
3167
3079
  const packageJsonF = join(basePath, "package.json");
3168
- const pnpmHooks = join(basePath, ".pnpmfile.cjs");
3169
- if (safeExistsSync(pnpmHooks)) {
3080
+ const pnpmCjsHooks = join(basePath, ".pnpmfile.cjs");
3081
+ const pnpmMjsHooks = join(basePath, ".pnpmfile.mjs");
3082
+ if (safeExistsSync(pnpmMjsHooks) || safeExistsSync(pnpmCjsHooks)) {
3170
3083
  thoughtLog("Wait, this pnpm project uses install hooks.");
3171
3084
  }
3172
3085
  if (!Object.keys(parentComponent).length) {
@@ -3239,6 +3152,11 @@ export async function createNodejsBom(path, options) {
3239
3152
  pkgLockFiles?.length &&
3240
3153
  isPackageManagerAllowed("npm", ["pnpm", "yarn"], options)
3241
3154
  ) {
3155
+ if (anyInstallSuccess) {
3156
+ thoughtLog(
3157
+ `I have ${pkgLockFiles.length} package-lock.json file(s) now after a successful npm install.`,
3158
+ );
3159
+ }
3242
3160
  manifestFiles = manifestFiles.concat(pkgLockFiles);
3243
3161
  for (const f of pkgLockFiles) {
3244
3162
  if (DEBUG_MODE) {
@@ -3644,6 +3562,7 @@ export async function createNodejsBom(path, options) {
3644
3562
  *
3645
3563
  * @param {String} path
3646
3564
  * @param {Object} options
3565
+ * @returns {Object | null} BOM object, or `null` when `pixi.lock` is absent and `options.installDeps` is false
3647
3566
  */
3648
3567
  export function createPixiBom(path, options) {
3649
3568
  const allImports = {};
@@ -3722,6 +3641,7 @@ export function createPixiBom(path, options) {
3722
3641
  *
3723
3642
  * @param {string} path to the project
3724
3643
  * @param {Object} options Parse options from the cli
3644
+ * @returns {Promise<Object>} Promise resolving to BOM object
3725
3645
  */
3726
3646
  export async function createPythonBom(path, options) {
3727
3647
  let allImports = {};
@@ -3848,7 +3768,6 @@ export async function createPythonBom(path, options) {
3848
3768
  console.log(`Parsing ${f}`);
3849
3769
  }
3850
3770
  let retMap = await parsePyLockData(lockData, f);
3851
- // Should we exit for workspace errors
3852
3771
  if (retMap?.workspaceWarningShown) {
3853
3772
  options.failOnError && process.exit(1);
3854
3773
  }
@@ -3856,7 +3775,6 @@ export async function createPythonBom(path, options) {
3856
3775
  pkgList = pkgList.concat(retMap.pkgList);
3857
3776
  pkgList = trimComponents(pkgList);
3858
3777
  }
3859
- // Retain the parent hierarchy
3860
3778
  if (retMap?.parentComponent?.components?.length) {
3861
3779
  if (!parentComponent.components) {
3862
3780
  parentComponent.components = [];
@@ -3872,36 +3790,81 @@ export async function createPythonBom(path, options) {
3872
3790
  parentComponent,
3873
3791
  );
3874
3792
  }
3875
- // Retrieve the tree using virtualenv in deep mode and as a fallback
3876
- // This is a slow operation
3877
3793
  if ((options.deep || !dependencies.length) && !f.endsWith("uv.lock")) {
3878
- retMap = await getPipFrozenTree(basePath, f, tempDir, parentComponent);
3879
- if (retMap.pkgList?.length) {
3880
- pkgList = pkgList.concat(retMap.pkgList);
3881
- }
3882
- if (retMap.formulationList?.length) {
3883
- formulationList = formulationList.concat(retMap.formulationList);
3884
- }
3885
- if (retMap.dependenciesList) {
3886
- dependencies = mergeDependencies(
3887
- dependencies,
3888
- retMap.dependenciesList,
3794
+ if (options.installDeps) {
3795
+ retMap = await getPipFrozenTree(
3796
+ basePath,
3797
+ f,
3798
+ tempDir,
3889
3799
  parentComponent,
3890
3800
  );
3801
+ if (retMap.pkgList?.length) pkgList = pkgList.concat(retMap.pkgList);
3802
+ if (retMap.formulationList?.length)
3803
+ formulationList = formulationList.concat(retMap.formulationList);
3804
+ if (retMap.dependenciesList)
3805
+ dependencies = mergeDependencies(
3806
+ dependencies,
3807
+ retMap.dependenciesList,
3808
+ parentComponent,
3809
+ );
3810
+ if (retMap.rootList) {
3811
+ const parentDependsOn = new Set();
3812
+ for (const p of retMap.rootList)
3813
+ parentDependsOn.add(
3814
+ `pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
3815
+ );
3816
+ dependencies.splice(0, 0, {
3817
+ ref: parentComponent["bom-ref"],
3818
+ dependsOn: [...parentDependsOn].sort(),
3819
+ });
3820
+ }
3821
+ } else {
3822
+ let exportedReqs = "";
3823
+ if (f.endsWith("poetry.lock")) {
3824
+ thoughtLog(
3825
+ "Using poetry export as a safe, static alternative to pip install.",
3826
+ );
3827
+ const expCmd = safeSpawnSync(
3828
+ "poetry",
3829
+ ["export", "-f", "requirements.txt"],
3830
+ { cwd: basePath, shell: false },
3831
+ );
3832
+ if (expCmd.status === 0 && expCmd.stdout)
3833
+ exportedReqs = expCmd.stdout.toString();
3834
+ } else if (f.endsWith("pdm.lock")) {
3835
+ thoughtLog(
3836
+ "Using pdm export as a safe, static alternative to pip install.",
3837
+ );
3838
+ const expCmd = safeSpawnSync(
3839
+ "pdm",
3840
+ ["export", "-f", "requirements"],
3841
+ { cwd: basePath, shell: false },
3842
+ );
3843
+ if (expCmd.status === 0 && expCmd.stdout)
3844
+ exportedReqs = expCmd.stdout.toString();
3845
+ }
3846
+ if (exportedReqs) {
3847
+ const tmpReqFile = join(
3848
+ tempDir,
3849
+ `exported-${basename(basePath)}-reqs.txt`,
3850
+ );
3851
+ writeFileSync(tmpReqFile, exportedReqs);
3852
+ const dlist = await parseReqFile(tmpReqFile, false);
3853
+ if (dlist?.length) {
3854
+ pkgList = pkgList.concat(dlist);
3855
+ const parentDependsOn = new Set();
3856
+ for (const p of dlist)
3857
+ parentDependsOn.add(
3858
+ `pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
3859
+ );
3860
+ dependencies.splice(0, 0, {
3861
+ ref: parentComponent["bom-ref"],
3862
+ dependsOn: [...parentDependsOn].sort(),
3863
+ });
3864
+ }
3865
+ }
3891
3866
  }
3892
3867
  }
3893
- if (retMap.rootList) {
3894
- const parentDependsOn = new Set();
3895
- // Complete the dependency tree by making parent component depend on the first level
3896
- for (const p of retMap.rootList) {
3897
- parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
3898
- }
3899
- const pdependencies = {
3900
- ref: parentComponent["bom-ref"],
3901
- dependsOn: [...parentDependsOn].sort(),
3902
- };
3903
- dependencies.splice(0, 0, pdependencies);
3904
- }
3905
3868
  }
3906
3869
  options.parentComponent = parentComponent;
3907
3870
  } // poetryMode
@@ -3919,7 +3882,7 @@ export async function createPythonBom(path, options) {
3919
3882
  for (const wf of whlFiles) {
3920
3883
  const mData = await readZipEntry(wf, "METADATA");
3921
3884
  if (mData) {
3922
- const dlist = parseBdistMetadata(undefined, mData);
3885
+ const dlist = parseBdistMetadata(join(wf, "METADATA"), mData);
3923
3886
  if (dlist?.length) {
3924
3887
  pkgList = pkgList.concat(dlist);
3925
3888
  }
@@ -4001,29 +3964,29 @@ export async function createPythonBom(path, options) {
4001
3964
  for (const f of reqFiles) {
4002
3965
  const basePath = dirname(f);
4003
3966
  if (options.installDeps) {
4004
- const pkgMap = await getPipFrozenTree(
3967
+ const rpkgMap = await getPipFrozenTree(
4005
3968
  basePath,
4006
3969
  f,
4007
3970
  tempDir,
4008
3971
  parentComponent,
4009
3972
  );
4010
- if (pkgMap.pkgList?.length) {
4011
- pkgList = pkgList.concat(pkgMap.pkgList);
3973
+ if (rpkgMap.pkgList?.length) {
3974
+ pkgList = pkgList.concat(rpkgMap.pkgList);
4012
3975
  pkgList = trimComponents(pkgList);
4013
3976
  }
4014
- if (pkgMap.formulationList?.length) {
4015
- formulationList = formulationList.concat(pkgMap.formulationList);
3977
+ if (rpkgMap.formulationList?.length) {
3978
+ formulationList = formulationList.concat(rpkgMap.formulationList);
4016
3979
  formulationList = trimComponents(formulationList);
4017
3980
  }
4018
- if (pkgMap.dependenciesList) {
3981
+ if (rpkgMap.dependenciesList) {
4019
3982
  dependencies = mergeDependencies(
4020
3983
  dependencies,
4021
- pkgMap.dependenciesList,
3984
+ rpkgMap.dependenciesList,
4022
3985
  parentComponent,
4023
3986
  );
4024
3987
  }
4025
3988
  // Add the root packages from this file to the parent's dependencies
4026
- for (const p of pkgMap.rootList) {
3989
+ for (const p of rpkgMap.rootList) {
4027
3990
  if (
4028
3991
  parentComponent &&
4029
3992
  p.name === parentComponent.name &&
@@ -4046,12 +4009,42 @@ export async function createPythonBom(path, options) {
4046
4009
  parentComponent,
4047
4010
  );
4048
4011
  }
4049
-
4012
+ if (pkgMap) {
4013
+ // Complete the dependency tree by making parent component depend on the first level
4014
+ for (const p of pkgMap.rootList) {
4015
+ if (
4016
+ parentComponent &&
4017
+ p.name === parentComponent.name &&
4018
+ (p.version === parentComponent.version || p.version === "latest")
4019
+ ) {
4020
+ continue;
4021
+ }
4022
+ parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
4023
+ }
4024
+ if (pkgMap?.pkgList?.length) {
4025
+ pkgList = pkgList.concat(pkgMap.pkgList);
4026
+ }
4027
+ if (pkgMap?.formulationList?.length) {
4028
+ formulationList = formulationList.concat(pkgMap.formulationList);
4029
+ }
4030
+ if (pkgMap?.dependenciesList) {
4031
+ dependencies = mergeDependencies(
4032
+ dependencies,
4033
+ pkgMap.dependenciesList,
4034
+ parentComponent,
4035
+ );
4036
+ }
4037
+ }
4050
4038
  // ATOM parsedeps block
4051
4039
  // Atom parsedeps slices can be used to identify packages that are not declared in manifests
4052
4040
  // Since it is a slow operation, we only use it as a fallback or in deep mode
4053
4041
  // This change was made in 10.9.2 release onwards
4054
4042
  if (options.deep || !pkgList.length) {
4043
+ if (!pkgList.length) {
4044
+ thoughtLog(
4045
+ "I couldn't find any components yet. Let's try static analysis with atom parsedeps command.",
4046
+ );
4047
+ }
4055
4048
  const retMap = await getPyModules(path, pkgList, options);
4056
4049
  // We need to patch the existing package list to add ImportedModules for evinse to work
4057
4050
  if (retMap.modList?.length) {
@@ -4100,32 +4093,6 @@ export async function createPythonBom(path, options) {
4100
4093
  }
4101
4094
  }
4102
4095
  // ATOM parsedeps block
4103
- if (pkgMap) {
4104
- // Complete the dependency tree by making parent component depend on the first level
4105
- for (const p of pkgMap.rootList) {
4106
- if (
4107
- parentComponent &&
4108
- p.name === parentComponent.name &&
4109
- (p.version === parentComponent.version || p.version === "latest")
4110
- ) {
4111
- continue;
4112
- }
4113
- parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
4114
- }
4115
- if (pkgMap?.pkgList?.length) {
4116
- pkgList = pkgList.concat(pkgMap.pkgList);
4117
- }
4118
- if (pkgMap?.formulationList?.length) {
4119
- formulationList = formulationList.concat(pkgMap.formulationList);
4120
- }
4121
- if (pkgMap?.dependenciesList) {
4122
- dependencies = mergeDependencies(
4123
- dependencies,
4124
- pkgMap.dependenciesList,
4125
- parentComponent,
4126
- );
4127
- }
4128
- }
4129
4096
  let parentPresent = false;
4130
4097
  for (const d of dependencies) {
4131
4098
  if (d.ref === parentComponent["bom-ref"]) {
@@ -4144,7 +4111,6 @@ export async function createPythonBom(path, options) {
4144
4111
  }
4145
4112
  }
4146
4113
  }
4147
-
4148
4114
  // Final fallback is to manually parse setup.py if we still
4149
4115
  // have an empty list
4150
4116
  if (!pkgList.length && setupPyMode) {
@@ -4217,6 +4183,7 @@ export async function createPythonBom(path, options) {
4217
4183
  *
4218
4184
  * @param {string} path to the project
4219
4185
  * @param {Object} options Parse options from the cli
4186
+ * @returns {Promise<Object | undefined>} Promise resolving to a BOM object or `undefined`
4220
4187
  */
4221
4188
  export async function createGoBom(path, options) {
4222
4189
  let pkgList = [];
@@ -4643,6 +4610,7 @@ export async function createGoBom(path, options) {
4643
4610
  *
4644
4611
  * @param {string} path to the project
4645
4612
  * @param {Object} options Parse options from the cli
4613
+ * @returns {Promise<Object|undefined>} Promise resolving to a BOM object or undefined
4646
4614
  */
4647
4615
  export async function createRustBom(path, options) {
4648
4616
  let pkgList = [];
@@ -4782,6 +4750,7 @@ export async function createRustBom(path, options) {
4782
4750
  *
4783
4751
  * @param {string} path to the project
4784
4752
  * @param {Object} options Parse options from the cli
4753
+ * @returns {Promise<Object>} Promise resolving to BOM object
4785
4754
  */
4786
4755
  export async function createDartBom(path, options) {
4787
4756
  const pubFiles = getAllFiles(
@@ -4853,6 +4822,7 @@ export async function createDartBom(path, options) {
4853
4822
  *
4854
4823
  * @param {string} path to the project
4855
4824
  * @param {Object} options Parse options from the cli
4825
+ * @returns {Object} BOM object
4856
4826
  */
4857
4827
  export function createCppBom(path, options) {
4858
4828
  let parentComponent;
@@ -5064,6 +5034,7 @@ export function createCppBom(path, options) {
5064
5034
  *
5065
5035
  * @param {string} path to the project
5066
5036
  * @param {Object} options Parse options from the cli
5037
+ * @returns {Object} BOM object
5067
5038
  */
5068
5039
  export function createClojureBom(path, options) {
5069
5040
  const ednFiles = getAllFiles(
@@ -5181,6 +5152,7 @@ export function createClojureBom(path, options) {
5181
5152
  *
5182
5153
  * @param {string} path to the project
5183
5154
  * @param {Object} options Parse options from the cli
5155
+ * @returns {Object} BOM object
5184
5156
  */
5185
5157
  export function createHaskellBom(path, options) {
5186
5158
  const cabalFiles = getAllFiles(
@@ -5213,6 +5185,7 @@ export function createHaskellBom(path, options) {
5213
5185
  *
5214
5186
  * @param {string} path to the project
5215
5187
  * @param {Object} options Parse options from the cli
5188
+ * @returns {Object} BOM object
5216
5189
  */
5217
5190
  export function createElixirBom(path, options) {
5218
5191
  const mixFiles = getAllFiles(
@@ -5245,6 +5218,7 @@ export function createElixirBom(path, options) {
5245
5218
  *
5246
5219
  * @param {string} path to the project
5247
5220
  * @param {Object} options Parse options from the cli
5221
+ * @returns {Object} BOM object
5248
5222
  */
5249
5223
  export function createGitHubBom(path, options) {
5250
5224
  const ghactionFiles = getAllFiles(
@@ -5276,6 +5250,7 @@ export function createGitHubBom(path, options) {
5276
5250
  *
5277
5251
  * @param {string} path to the project
5278
5252
  * @param {Object} options Parse options from the cli
5253
+ * @returns {Object} BOM object
5279
5254
  */
5280
5255
  export function createCloudBuildBom(path, options) {
5281
5256
  const cbFiles = getAllFiles(path, "cloudbuild.yml", options);
@@ -5304,6 +5279,7 @@ export function createCloudBuildBom(path, options) {
5304
5279
  *
5305
5280
  * @param {string} _path to the project
5306
5281
  * @param {Object} options Parse options from the cli
5282
+ * @returns {Promise<Object>} Promise resolving to BOM object
5307
5283
  */
5308
5284
  export function createOSBom(_path, options) {
5309
5285
  console.warn(
@@ -5362,6 +5338,7 @@ export function createOSBom(_path, options) {
5362
5338
  *
5363
5339
  * @param {string} path to the project
5364
5340
  * @param {Object} options Parse options from the cli
5341
+ * @returns {Promise<Object>} Promise resolving to BOM object
5365
5342
  */
5366
5343
  export async function createJenkinsBom(path, options) {
5367
5344
  let pkgList = [];
@@ -5411,6 +5388,7 @@ export async function createJenkinsBom(path, options) {
5411
5388
  *
5412
5389
  * @param {string} path to the project
5413
5390
  * @param {Object} options Parse options from the cli
5391
+ * @returns {Object} BOM object
5414
5392
  */
5415
5393
  export function createHelmBom(path, options) {
5416
5394
  let pkgList = [];
@@ -5443,6 +5421,7 @@ export function createHelmBom(path, options) {
5443
5421
  *
5444
5422
  * @param {string} path to the project
5445
5423
  * @param {Object} options Parse options from the cli
5424
+ * @returns {Promise<Object>} Promise resolving to BOM object
5446
5425
  */
5447
5426
  export async function createSwiftBom(path, options) {
5448
5427
  const swiftFiles = getAllFiles(
@@ -5587,6 +5566,7 @@ export async function createSwiftBom(path, options) {
5587
5566
  *
5588
5567
  * @param {string} path to the project
5589
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
5590
5570
  */
5591
5571
  export async function createCocoaBom(path, options) {
5592
5572
  const cocoaFiles = getAllFiles(
@@ -5603,7 +5583,7 @@ export async function createCocoaBom(path, options) {
5603
5583
  for (const podFile of cocoaFiles) {
5604
5584
  const projectPath = dirname(podFile);
5605
5585
  const lockFile = `${podFile}.lock`;
5606
- if (!existsSync(lockFile) || options.deep) {
5586
+ if (!safeExistsSync(lockFile) || options.deep) {
5607
5587
  if (options.installDeps) {
5608
5588
  executePodCommand(["install"], projectPath, options);
5609
5589
  } else {
@@ -5741,6 +5721,7 @@ export async function createCocoaBom(path, options) {
5741
5721
  *
5742
5722
  * @param {string} path to the project
5743
5723
  * @param {Object} options Parse options from the cli
5724
+ * @returns {Promise<Object>} Promise resolving to BOM object
5744
5725
  */
5745
5726
  export async function createNixBom(path, options) {
5746
5727
  let pkgList = [];
@@ -5863,6 +5844,7 @@ export async function createNixBom(path, options) {
5863
5844
  *
5864
5845
  * @param {string} path to the project
5865
5846
  * @param {Object} options Parse options from the cli
5847
+ * @returns {Promise<Object>} Promise resolving to BOM object
5866
5848
  */
5867
5849
  export async function createCaxaBom(path, options) {
5868
5850
  let pkgList = [];
@@ -5914,6 +5896,7 @@ export async function createCaxaBom(path, options) {
5914
5896
  *
5915
5897
  * @param {string} path to the project
5916
5898
  * @param {Object} options Parse options from the cli
5899
+ * @returns {Promise<Object>} Promise resolving to BOM object
5917
5900
  */
5918
5901
  export async function createContainerSpecLikeBom(path, options) {
5919
5902
  let services = [];
@@ -6242,6 +6225,7 @@ export async function createContainerSpecLikeBom(path, options) {
6242
6225
  *
6243
6226
  * @param {string} path to the project
6244
6227
  * @param {Object} options Parse options from the cli
6228
+ * @returns {Object} BOM object
6245
6229
  */
6246
6230
  export function createPHPBom(path, options) {
6247
6231
  let dependencies = [];
@@ -6349,7 +6333,7 @@ export function createPHPBom(path, options) {
6349
6333
  // Track all the modules in a mono-repo
6350
6334
  if (!Object.keys(parentComponent).length) {
6351
6335
  parentComponent = { ...moduleParent };
6352
- } else {
6336
+ } else if (moduleParent?.["bom-ref"]) {
6353
6337
  parentComponent.components = parentComponent.components || [];
6354
6338
  parentComponent.components.push(moduleParent);
6355
6339
  }
@@ -6365,7 +6349,9 @@ export function createPHPBom(path, options) {
6365
6349
  dependencies.splice(0, 0, {
6366
6350
  ref: moduleParent["bom-ref"],
6367
6351
  dependsOn: [
6368
- ...new Set(retMap.rootList.map((p) => p["bom-ref"])),
6352
+ ...new Set(
6353
+ retMap.rootList.map((p) => p["bom-ref"]).filter(Boolean),
6354
+ ),
6369
6355
  ].sort(),
6370
6356
  });
6371
6357
  }
@@ -6378,9 +6364,9 @@ export function createPHPBom(path, options) {
6378
6364
  }
6379
6365
  // Complete the root dependency tree
6380
6366
  if (parentComponent?.components?.length) {
6381
- const parentDependsOn = parentComponent.components.map(
6382
- (d) => d["bom-ref"],
6383
- );
6367
+ const parentDependsOn = parentComponent.components
6368
+ .map((d) => d["bom-ref"])
6369
+ .filter(Boolean);
6384
6370
  dependencies = mergeDependencies(
6385
6371
  [{ ref: parentComponent["bom-ref"], dependsOn: parentDependsOn }],
6386
6372
  dependencies,
@@ -6402,6 +6388,7 @@ export function createPHPBom(path, options) {
6402
6388
  *
6403
6389
  * @param {string} path to the project
6404
6390
  * @param {Object} options Parse options from the cli
6391
+ * @returns {Promise<Object>} Promise resolving to BOM object
6405
6392
  */
6406
6393
  export async function createRubyBom(path, options) {
6407
6394
  // We can look for gem files within node_modules directory
@@ -6655,6 +6642,7 @@ export async function createRubyBom(path, options) {
6655
6642
  *
6656
6643
  * @param {string} path to the project
6657
6644
  * @param {Object} options Parse options from the cli
6645
+ * @returns {Promise<Object|undefined>} Promise resolving to BOM object
6658
6646
  */
6659
6647
  export async function createCsharpBom(path, options) {
6660
6648
  let manifestFiles = [];
@@ -7090,11 +7078,202 @@ export async function createCsharpBom(path, options) {
7090
7078
  });
7091
7079
  }
7092
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
+
7093
7271
  /**
7094
7272
  * Function to create bom object for cryptographic certificate files
7095
7273
  *
7096
7274
  * @param {string} path to the project
7097
7275
  * @param {Object} options Parse options from the cli
7276
+ * @returns {Promise<Object>} Promise resolving to BOM object
7098
7277
  */
7099
7278
  export async function createCryptoCertsBom(path, options) {
7100
7279
  const pkgList = [];
@@ -7131,191 +7310,6 @@ export async function createCryptoCertsBom(path, options) {
7131
7310
  };
7132
7311
  }
7133
7312
 
7134
- export function mergeDependencies(
7135
- dependencies,
7136
- newDependencies,
7137
- parentComponent = {},
7138
- ) {
7139
- if (!parentComponent && DEBUG_MODE) {
7140
- console.log(
7141
- "Unable to determine parent component. Dependencies will be flattened.",
7142
- );
7143
- }
7144
- let providesFound = false;
7145
- const deps_map = {};
7146
- const provides_map = {};
7147
- const parentRef = parentComponent?.["bom-ref"]
7148
- ? parentComponent["bom-ref"]
7149
- : undefined;
7150
- const combinedDeps = dependencies.concat(newDependencies || []);
7151
- for (const adep of combinedDeps) {
7152
- if (!deps_map[adep.ref]) {
7153
- deps_map[adep.ref] = new Set();
7154
- }
7155
- if (!provides_map[adep.ref]) {
7156
- provides_map[adep.ref] = new Set();
7157
- }
7158
- if (adep["dependsOn"]) {
7159
- for (const eachDepends of adep["dependsOn"]) {
7160
- if (parentRef && eachDepends) {
7161
- if (eachDepends.toLowerCase() !== parentRef.toLowerCase()) {
7162
- deps_map[adep.ref].add(eachDepends);
7163
- }
7164
- } else {
7165
- deps_map[adep.ref].add(eachDepends);
7166
- }
7167
- }
7168
- }
7169
- if (adep["provides"]) {
7170
- providesFound = true;
7171
- for (const eachProvides of adep["provides"]) {
7172
- if (
7173
- parentRef &&
7174
- eachProvides.toLowerCase() !== parentRef.toLowerCase()
7175
- ) {
7176
- provides_map[adep.ref].add(eachProvides);
7177
- }
7178
- }
7179
- }
7180
- }
7181
- const retlist = [];
7182
- for (const akey of Object.keys(deps_map)) {
7183
- if (providesFound) {
7184
- retlist.push({
7185
- ref: akey,
7186
- dependsOn: Array.from(deps_map[akey]).sort(),
7187
- provides: Array.from(provides_map[akey]).sort(),
7188
- });
7189
- } else {
7190
- retlist.push({
7191
- ref: akey,
7192
- dependsOn: Array.from(deps_map[akey]).sort(),
7193
- });
7194
- }
7195
- }
7196
- return retlist;
7197
- }
7198
-
7199
- /**
7200
- * Trim duplicate components by retaining all the properties
7201
- *
7202
- * @param {Array} components Components
7203
- *
7204
- * @returns {Array} Filtered components
7205
- */
7206
- export function trimComponents(components) {
7207
- const keyCache = {};
7208
- const filteredComponents = [];
7209
- for (const comp of components) {
7210
- const key = (
7211
- comp.purl ||
7212
- comp["bom-ref"] ||
7213
- comp.name + comp.version
7214
- ).toLowerCase();
7215
- if (!keyCache[key]) {
7216
- keyCache[key] = comp;
7217
- } else {
7218
- const existingComponent = keyCache[key];
7219
- // We need to retain any properties that differ
7220
- if (comp.properties) {
7221
- if (existingComponent.properties) {
7222
- for (const newprop of comp.properties) {
7223
- if (
7224
- !existingComponent.properties.find(
7225
- (prop) =>
7226
- prop.name === newprop.name && prop.value === newprop.value,
7227
- )
7228
- ) {
7229
- existingComponent.properties.push(newprop);
7230
- }
7231
- }
7232
- } else {
7233
- existingComponent.properties = comp.properties;
7234
- }
7235
- }
7236
- // Retain all component.evidence.identity
7237
- if (comp?.evidence?.identity) {
7238
- if (!existingComponent.evidence) {
7239
- existingComponent.evidence = { identity: [] };
7240
- } else if (!existingComponent?.evidence?.identity) {
7241
- existingComponent.evidence.identity = [];
7242
- } else if (
7243
- existingComponent?.evidence?.identity &&
7244
- !Array.isArray(existingComponent.evidence.identity)
7245
- ) {
7246
- existingComponent.evidence.identity = [
7247
- existingComponent.evidence.identity,
7248
- ];
7249
- }
7250
- // comp.evidence.identity can be an array or object
7251
- // Merge the evidence.identity based on methods or objects
7252
- const isIdentityArray = Array.isArray(comp.evidence.identity);
7253
- const identities = isIdentityArray
7254
- ? comp.evidence.identity
7255
- : [comp.evidence.identity];
7256
- for (const aident of identities) {
7257
- let methodBasedMerge = false;
7258
- if (aident?.methods?.length) {
7259
- for (const amethod of aident.methods) {
7260
- for (const existIdent of existingComponent.evidence.identity) {
7261
- if (existIdent.field === aident.field) {
7262
- if (!existIdent.methods) {
7263
- existIdent.methods = [];
7264
- }
7265
- let isDup = false;
7266
- for (const emethod of existIdent.methods) {
7267
- if (emethod?.value === amethod?.value) {
7268
- isDup = true;
7269
- break;
7270
- }
7271
- }
7272
- if (!isDup) {
7273
- existIdent.methods.push(amethod);
7274
- }
7275
- methodBasedMerge = true;
7276
- }
7277
- }
7278
- }
7279
- }
7280
- if (!methodBasedMerge && aident.field && aident.confidence) {
7281
- existingComponent.evidence.identity.push(aident);
7282
- }
7283
- }
7284
- if (!isIdentityArray) {
7285
- const firstIdentity = existingComponent.evidence.identity[0];
7286
- let identConfidence = firstIdentity?.confidence;
7287
- // We need to set the confidence to the max of all confidences
7288
- if (firstIdentity?.methods?.length > 1) {
7289
- for (const aidentMethod of firstIdentity.methods) {
7290
- if (
7291
- aidentMethod?.confidence &&
7292
- aidentMethod.confidence > identConfidence
7293
- ) {
7294
- identConfidence = aidentMethod.confidence;
7295
- }
7296
- }
7297
- }
7298
- firstIdentity.confidence = identConfidence;
7299
- existingComponent.evidence = {
7300
- identity: firstIdentity,
7301
- };
7302
- }
7303
- }
7304
- // If the component is required in any of the child projects, then make it required
7305
- if (
7306
- existingComponent?.scope !== "required" &&
7307
- comp?.scope === "required"
7308
- ) {
7309
- existingComponent.scope = "required";
7310
- }
7311
- }
7312
- }
7313
- for (const akey of Object.keys(keyCache)) {
7314
- filteredComponents.push(keyCache[akey]);
7315
- }
7316
- return filteredComponents;
7317
- }
7318
-
7319
7313
  /**
7320
7314
  * Dedupe components
7321
7315
  *
@@ -7372,11 +7366,13 @@ export function dedupeBom(options, components, parentComponent, dependencies) {
7372
7366
  *
7373
7367
  * @param {string[]} pathList list of to the project
7374
7368
  * @param {Object} options Parse options from the cli
7369
+ * @returns {Promise<Object>} Promise resolving to BOM object
7375
7370
  */
7376
7371
  export async function createMultiXBom(pathList, options) {
7377
7372
  let components = [];
7378
7373
  let dependencies = [];
7379
7374
  let bomData;
7375
+ let formulationList = [];
7380
7376
  let parentComponent = determineParentComponent(options) || {};
7381
7377
  let parentSubComponents = [];
7382
7378
  options.createMultiXBom = true;
@@ -7587,6 +7583,9 @@ export async function createMultiXBom(pathList, options) {
7587
7583
  parentSubComponents.push(bomData.parentComponent);
7588
7584
  }
7589
7585
  }
7586
+ if (bomData?.formulationList?.length) {
7587
+ formulationList = formulationList.concat(bomData.formulationList);
7588
+ }
7590
7589
  }
7591
7590
  if (hasAnyProjectType(["oci", "go"], options)) {
7592
7591
  if (!hasAnyProjectType(["oci"], options, false)) {
@@ -8079,6 +8078,27 @@ export async function createMultiXBom(pathList, options) {
8079
8078
  }
8080
8079
  }
8081
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
+ }
8082
8102
  // Collect any crypto keys
8083
8103
  if (options.specVersion >= 1.6 && options.includeCrypto) {
8084
8104
  if (!hasAnyProjectType(["oci"], options, false)) {
@@ -8177,7 +8197,16 @@ export async function createMultiXBom(pathList, options) {
8177
8197
  }
8178
8198
  }
8179
8199
  }
8180
- 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;
8181
8210
  }
8182
8211
 
8183
8212
  /**
@@ -8185,6 +8214,7 @@ export async function createMultiXBom(pathList, options) {
8185
8214
  *
8186
8215
  * @param {string} path to the project
8187
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
8188
8218
  */
8189
8219
  export async function createXBom(path, options) {
8190
8220
  try {
@@ -8423,6 +8453,16 @@ export async function createXBom(path, options) {
8423
8453
  return await createJenkinsBom(path, options);
8424
8454
  }
8425
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
+
8426
8466
  // Helm charts
8427
8467
  const chartFiles = getAllFiles(
8428
8468
  path,
@@ -8530,6 +8570,7 @@ export async function createXBom(path, options) {
8530
8570
  *
8531
8571
  * @param {string} path to the project
8532
8572
  * @param {Object} options Parse options from the cli
8573
+ * @returns {Promise<Object>} Promise resolving to BOM object
8533
8574
  */
8534
8575
  export async function createBom(path, options) {
8535
8576
  let { projectType } = options;
@@ -8774,6 +8815,9 @@ export async function createBom(path, options) {
8774
8815
  if (PROJECT_TYPE_ALIASES["caxa"].includes(projectType[0])) {
8775
8816
  return await createCaxaBom(path, options);
8776
8817
  }
8818
+ if (PROJECT_TYPE_ALIASES["vscode-extension"].includes(projectType[0])) {
8819
+ return await createVscodeExtensionBom(path, options);
8820
+ }
8777
8821
  switch (projectType[0]) {
8778
8822
  case "jar":
8779
8823
  return createJarBom(path, options);