@cyclonedx/cdxgen 12.2.0 → 12.2.1

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 (34) hide show
  1. package/README.md +5 -2
  2. package/bin/cdxgen.js +19 -1
  3. package/lib/cli/index.js +122 -57
  4. package/lib/cli/index.poku.js +117 -0
  5. package/lib/helpers/analyzer.js +606 -3
  6. package/lib/helpers/analyzer.poku.js +230 -0
  7. package/lib/helpers/depsUtils.js +16 -0
  8. package/lib/helpers/depsUtils.poku.js +58 -1
  9. package/lib/helpers/display.js +4 -2
  10. package/lib/helpers/remote/dependency-track.js +84 -0
  11. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  12. package/lib/helpers/table.js +384 -0
  13. package/lib/helpers/table.poku.js +186 -0
  14. package/lib/helpers/utils.js +184 -10
  15. package/lib/helpers/utils.poku.js +118 -11
  16. package/lib/server/openapi.yaml +33 -0
  17. package/lib/server/server.js +10 -2
  18. package/lib/server/server.poku.js +209 -0
  19. package/lib/stages/postgen/auditBom.js +1 -2
  20. package/lib/validator/reporters/console.js +2 -2
  21. package/package.json +1 -2
  22. package/types/lib/cli/index.d.ts.map +1 -1
  23. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  24. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  25. package/types/lib/helpers/display.d.ts.map +1 -1
  26. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  27. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  28. package/types/lib/helpers/table.d.ts +6 -0
  29. package/types/lib/helpers/table.d.ts.map +1 -0
  30. package/types/lib/helpers/utils.d.ts +1 -0
  31. package/types/lib/helpers/utils.d.ts.map +1 -1
  32. package/types/lib/server/server.d.ts +1 -0
  33. package/types/lib/server/server.d.ts.map +1 -1
  34. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
@@ -337,6 +337,13 @@ export const DEBUG_MODE =
337
337
  ["debug", "verbose"].includes(process.env.CDXGEN_DEBUG_MODE) ||
338
338
  process.env.SCAN_DEBUG_MODE === "debug";
339
339
 
340
+ // Table border style for console output.
341
+ export const TABLE_BORDER_STYLE = ["ascii", "unicode", "auto"].includes(
342
+ `${process.env.CDXGEN_TABLE_BORDER || ""}`.toLowerCase(),
343
+ )
344
+ ? `${process.env.CDXGEN_TABLE_BORDER}`.toLowerCase()
345
+ : "auto";
346
+
340
347
  // Timeout milliseconds. Default 20 mins
341
348
  export const TIMEOUT_MS =
342
349
  Number.parseInt(process.env.CDXGEN_TIMEOUT_MS, 10) || 20 * 60 * 1000;
@@ -1120,7 +1127,44 @@ export function getLicenses(pkg) {
1120
1127
  licenseContent.name = l;
1121
1128
  }
1122
1129
  } else if (Object.keys(l).length) {
1123
- licenseContent = l;
1130
+ licenseContent = { ...l };
1131
+ if (
1132
+ licenseContent.type &&
1133
+ !licenseContent.id &&
1134
+ !licenseContent.name &&
1135
+ !licenseContent.expression
1136
+ ) {
1137
+ if (spdxLicenses.includes(licenseContent.type)) {
1138
+ licenseContent.id = licenseContent.type;
1139
+ } else if (isSpdxLicenseExpression(licenseContent.type)) {
1140
+ licenseContent.expression = licenseContent.type;
1141
+ } else {
1142
+ licenseContent.name = licenseContent.type;
1143
+ }
1144
+ }
1145
+ if (
1146
+ !licenseContent.id &&
1147
+ !licenseContent.name &&
1148
+ !licenseContent.expression &&
1149
+ licenseContent.url?.startsWith("http")
1150
+ ) {
1151
+ const knownLicense = getKnownLicense(licenseContent.url, pkg);
1152
+ if (knownLicense) {
1153
+ if (knownLicense.id) {
1154
+ licenseContent.id = knownLicense.id;
1155
+ } else if (knownLicense.name) {
1156
+ licenseContent.name = knownLicense.name;
1157
+ }
1158
+ }
1159
+ }
1160
+ if (
1161
+ !licenseContent.id &&
1162
+ !licenseContent.name &&
1163
+ !licenseContent.expression
1164
+ ) {
1165
+ licenseContent.name = "CUSTOM";
1166
+ }
1167
+ delete licenseContent.type;
1124
1168
  } else {
1125
1169
  return undefined;
1126
1170
  }
@@ -1572,7 +1616,8 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1572
1616
  const srcFilePath = node.path.includes(`${_sep}node_modules`)
1573
1617
  ? node.path.split(`${_sep}node_modules`)[0]
1574
1618
  : node.path;
1575
- const scope = node.dev === true ? "optional" : undefined;
1619
+ const scope =
1620
+ node.dev === true || node.optional === true ? "optional" : undefined;
1576
1621
  const integrity = node.integrity ? node.integrity : undefined;
1577
1622
 
1578
1623
  let pkg;
@@ -1645,6 +1690,9 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1645
1690
  purl: purlString,
1646
1691
  "bom-ref": decodeURIComponent(purlString),
1647
1692
  };
1693
+ if (node.dev === true) {
1694
+ _setNpmDevelopmentProperty(pkg);
1695
+ }
1648
1696
  if (node.resolved) {
1649
1697
  if (node.resolved.startsWith("file:")) {
1650
1698
  pkg.properties.push({
@@ -2826,6 +2874,51 @@ function _markTreeOptional(
2826
2874
  }
2827
2875
  }
2828
2876
 
2877
+ function _markTreeDevelopment(
2878
+ dbomRef,
2879
+ dependenciesMap,
2880
+ possibleDevelopmentDeps,
2881
+ visited,
2882
+ ) {
2883
+ // Production-required packages set this map entry to false, and that wins
2884
+ // over any later attempt to propagate a development-only marking.
2885
+ if (possibleDevelopmentDeps[dbomRef] === undefined) {
2886
+ possibleDevelopmentDeps[dbomRef] = true;
2887
+ }
2888
+ if (dependenciesMap[dbomRef] && !visited[dbomRef]) {
2889
+ visited[dbomRef] = true;
2890
+ for (const eachDep of dependenciesMap[dbomRef]) {
2891
+ // Undefined means we have not classified this dependency yet, so we
2892
+ // continue propagating the dev-only marking unless it was already proven
2893
+ // to be non-development via a false entry.
2894
+ if (possibleDevelopmentDeps[eachDep] !== false) {
2895
+ _markTreeDevelopment(
2896
+ eachDep,
2897
+ dependenciesMap,
2898
+ possibleDevelopmentDeps,
2899
+ visited,
2900
+ );
2901
+ }
2902
+ }
2903
+ }
2904
+ }
2905
+
2906
+ function _setNpmDevelopmentProperty(pkg) {
2907
+ if (!pkg.properties) {
2908
+ pkg.properties = [];
2909
+ }
2910
+ if (
2911
+ !pkg.properties.some((property) => {
2912
+ return property.name === "cdx:npm:package:development";
2913
+ })
2914
+ ) {
2915
+ pkg.properties.push({
2916
+ name: "cdx:npm:package:development",
2917
+ value: "true",
2918
+ });
2919
+ }
2920
+ }
2921
+
2829
2922
  function _setTreeWorkspaceRef(
2830
2923
  dependenciesMap,
2831
2924
  depref,
@@ -3201,6 +3294,7 @@ export async function parsePnpmLock(
3201
3294
  // See: #1163
3202
3295
  // Moreover, we have changed >= 9 for >= 6
3203
3296
  // See: discussion #1359
3297
+ const possibleDevelopmentDeps = {};
3204
3298
  const possibleOptionalDeps = {};
3205
3299
  const dependenciesMap = {};
3206
3300
  let ppurl = "";
@@ -3293,7 +3387,7 @@ export async function parsePnpmLock(
3293
3387
  null,
3294
3388
  null,
3295
3389
  ).toString();
3296
- possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
3390
+ possibleDevelopmentDeps[decodeURIComponent(dpurl)] = true;
3297
3391
  }
3298
3392
  // Find the root optional and peer dependencies
3299
3393
  for (const rdk of Object.keys({ ...rootOptionalDeps, ...rootPeerDeps })) {
@@ -3317,6 +3411,7 @@ export async function parsePnpmLock(
3317
3411
  null,
3318
3412
  ).toString();
3319
3413
  possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
3414
+ possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
3320
3415
  }
3321
3416
  // Find the root direct dependencies
3322
3417
  for (const dk of Object.keys(rootDirectDeps)) {
@@ -3344,6 +3439,7 @@ export async function parsePnpmLock(
3344
3439
  // These are direct dependencies so cannot be optional
3345
3440
  possibleOptionalDeps[decodeURIComponent(dpurl)] = false;
3346
3441
  }
3442
+ possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
3347
3443
  }
3348
3444
  // pnpm-lock.yaml contains more than root dependencies in importers
3349
3445
  // we do what we did above but for all the other components
@@ -3469,6 +3565,7 @@ export async function parsePnpmLock(
3469
3565
  // This is a definite dependency of this component
3470
3566
  comDepList.add(depRef);
3471
3567
  possibleOptionalDeps[depRef] = false;
3568
+ possibleDevelopmentDeps[depRef] = false;
3472
3569
  // Track the package.json files
3473
3570
  if (pkgSrcFile) {
3474
3571
  if (!srcFilesMap[depRef]) {
@@ -3488,7 +3585,7 @@ export async function parsePnpmLock(
3488
3585
  null,
3489
3586
  ).toString();
3490
3587
  const devDpRef = decodeURIComponent(dpurl);
3491
- possibleOptionalDeps[devDpRef] = true;
3588
+ possibleDevelopmentDeps[devDpRef] = true;
3492
3589
  // This is also a dependency of this component
3493
3590
  comDepList.add(devDpRef);
3494
3591
  }
@@ -3506,6 +3603,7 @@ export async function parsePnpmLock(
3506
3603
  null,
3507
3604
  ).toString();
3508
3605
  possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
3606
+ possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
3509
3607
  }
3510
3608
  dependenciesList.push({
3511
3609
  ref: decodeURIComponent(compPurl),
@@ -3708,7 +3806,21 @@ export async function parsePnpmLock(
3708
3806
  null,
3709
3807
  ).toString();
3710
3808
  const bomRef = decodeURIComponent(purlString);
3809
+ if (
3810
+ packageNode.dev === true &&
3811
+ possibleDevelopmentDeps[bomRef] === undefined
3812
+ ) {
3813
+ possibleDevelopmentDeps[bomRef] = true;
3814
+ }
3711
3815
  const isBaseOptional = possibleOptionalDeps[bomRef];
3816
+ // optionalDependencies are tracked separately because they may still
3817
+ // be runtime-relevant and should keep the CycloneDX optional scope.
3818
+ // packageNode.dev captures explicit dev-only packages from the lock
3819
+ // entry, while possibleDevelopmentDeps lets that marking propagate to
3820
+ // transitive dependencies discovered through the dependency graph.
3821
+ const isBaseDevelopment =
3822
+ packageNode.dev === true ||
3823
+ possibleDevelopmentDeps[bomRef] === true;
3712
3824
  const deplist = [];
3713
3825
  for (let dpkgName of Object.keys(deps)) {
3714
3826
  let vers = deps[dpkgName];
@@ -3751,6 +3863,18 @@ export async function parsePnpmLock(
3751
3863
  {},
3752
3864
  );
3753
3865
  }
3866
+ if (
3867
+ isBaseDevelopment &&
3868
+ possibleDevelopmentDeps[dbomRef] === undefined
3869
+ ) {
3870
+ possibleDevelopmentDeps[dbomRef] = true;
3871
+ _markTreeDevelopment(
3872
+ dbomRef,
3873
+ dependenciesMap,
3874
+ possibleDevelopmentDeps,
3875
+ {},
3876
+ );
3877
+ }
3754
3878
  }
3755
3879
  if (!dependenciesMap[bomRef]) {
3756
3880
  dependenciesMap[bomRef] = [];
@@ -3910,6 +4034,19 @@ export async function parsePnpmLock(
3910
4034
  }
3911
4035
  }
3912
4036
  }
4037
+ // Repeat development dependency detection after the dependency graph is fully
4038
+ // built, since a single package iteration can encounter a dev-only component
4039
+ // before its own dependency list has been captured in dependenciesMap.
4040
+ for (const dependencyRef of Object.keys(possibleDevelopmentDeps)) {
4041
+ if (possibleDevelopmentDeps[dependencyRef] === true) {
4042
+ _markTreeDevelopment(
4043
+ dependencyRef,
4044
+ dependenciesMap,
4045
+ possibleDevelopmentDeps,
4046
+ {},
4047
+ );
4048
+ }
4049
+ }
3913
4050
 
3914
4051
  // Problem: We might have over aggressively marked a package as optional even it is both required and optional
3915
4052
  // The below loops ensure required packages continue to stay required
@@ -3940,6 +4077,15 @@ export async function parsePnpmLock(
3940
4077
  if (requiredDependencies[apkg["bom-ref"]]) {
3941
4078
  apkg.scope = undefined;
3942
4079
  }
4080
+ if (
4081
+ !requiredDependencies[apkg["bom-ref"]] &&
4082
+ possibleDevelopmentDeps[apkg["bom-ref"]]
4083
+ ) {
4084
+ if (!apkg.scope) {
4085
+ apkg.scope = "optional";
4086
+ }
4087
+ _setNpmDevelopmentProperty(apkg);
4088
+ }
3943
4089
  if (possibleAliasesRefs[apkg["bom-ref"]]) {
3944
4090
  apkg.properties.push({
3945
4091
  name: "cdx:pnpm:alias",
@@ -16447,6 +16593,7 @@ export async function addEvidenceForImports(
16447
16593
  : [name];
16448
16594
  let isImported = false;
16449
16595
  for (const alias of aliases) {
16596
+ const isWasmAlias = /\.wasm([?#].*)?$/i.test(alias);
16450
16597
  const all_includes = impPkgs.filter(
16451
16598
  (find_pkg) =>
16452
16599
  find_pkg.startsWith(alias) &&
@@ -16456,12 +16603,20 @@ export async function addEvidenceForImports(
16456
16603
  find_pkg.startsWith(alias),
16457
16604
  );
16458
16605
  if (all_exports?.length) {
16459
- let exportedModules = new Set(all_exports);
16606
+ let exportedModules = new Set(isWasmAlias ? [] : all_exports);
16460
16607
  pkg.properties = pkg.properties || [];
16461
16608
  for (const subevidence of all_exports) {
16462
16609
  const evidences = allExports[subevidence];
16463
16610
  for (const evidence of evidences) {
16464
16611
  if (evidence && Object.keys(evidence).length) {
16612
+ if (isWasmAlias) {
16613
+ for (const wasmImportedModule of evidence.importedModules ||
16614
+ []) {
16615
+ if (wasmImportedModule?.length) {
16616
+ exportedModules.add(wasmImportedModule);
16617
+ }
16618
+ }
16619
+ }
16465
16620
  if (evidence.exportedModules.length > 1) {
16466
16621
  for (const aexpsubm of evidence.exportedModules) {
16467
16622
  // Be selective on the submodule names
@@ -16496,6 +16651,8 @@ export async function addEvidenceForImports(
16496
16651
  if (impPkgs.includes(alias) || all_includes.length) {
16497
16652
  isImported = true;
16498
16653
  let importedModules = new Set();
16654
+ let wasmExportedModules = new Set();
16655
+ const seenOccurrenceLocations = new Set();
16499
16656
  pkg.scope = "required";
16500
16657
  for (const subevidence of all_includes) {
16501
16658
  const evidences = allImports[subevidence];
@@ -16503,16 +16660,23 @@ export async function addEvidenceForImports(
16503
16660
  if (evidence && Object.keys(evidence).length && evidence.fileName) {
16504
16661
  pkg.evidence = pkg.evidence || {};
16505
16662
  pkg.evidence.occurrences = pkg.evidence.occurrences || [];
16506
- pkg.evidence.occurrences.push({
16507
- location: `${evidence.fileName}${
16508
- evidence.lineNumber ? `#${evidence.lineNumber}` : ""
16509
- }`,
16510
- });
16663
+ const occurrenceLocation = `${evidence.fileName}${
16664
+ evidence.lineNumber ? `#${evidence.lineNumber}` : ""
16665
+ }`;
16666
+ if (!seenOccurrenceLocations.has(occurrenceLocation)) {
16667
+ pkg.evidence.occurrences.push({
16668
+ location: occurrenceLocation,
16669
+ });
16670
+ seenOccurrenceLocations.add(occurrenceLocation);
16671
+ }
16511
16672
  importedModules.add(evidence.importedAs);
16512
16673
  for (const importedSm of evidence.importedModules || []) {
16513
16674
  if (!importedSm) {
16514
16675
  continue;
16515
16676
  }
16677
+ if (isWasmAlias) {
16678
+ wasmExportedModules.add(importedSm);
16679
+ }
16516
16680
  // Store both the short and long form of the imported sub modules
16517
16681
  if (importedSm.length > 3) {
16518
16682
  importedModules.add(importedSm);
@@ -16523,6 +16687,7 @@ export async function addEvidenceForImports(
16523
16687
  }
16524
16688
  }
16525
16689
  importedModules = Array.from(importedModules);
16690
+ wasmExportedModules = Array.from(wasmExportedModules);
16526
16691
  if (importedModules.length) {
16527
16692
  pkg.properties = pkg.properties || [];
16528
16693
  pkg.properties.push({
@@ -16530,6 +16695,15 @@ export async function addEvidenceForImports(
16530
16695
  value: importedModules.join(","),
16531
16696
  });
16532
16697
  }
16698
+ if (isWasmAlias && wasmExportedModules.length) {
16699
+ pkg.properties = pkg.properties || [];
16700
+ if (!pkg.properties.some((p) => p.name === "ExportedModules")) {
16701
+ pkg.properties.push({
16702
+ name: "ExportedModules",
16703
+ value: wasmExportedModules.join(","),
16704
+ });
16705
+ }
16706
+ }
16533
16707
  break;
16534
16708
  }
16535
16709
  if (
@@ -2671,7 +2671,7 @@ it("parse github actions workflow data", () => {
2671
2671
  dep_list = parseGitHubWorkflowData("./test/data/github-actions-tj.yaml");
2672
2672
  assert.deepStrictEqual(dep_list.length, 4);
2673
2673
  dep_list = parseGitHubWorkflowData("./.github/workflows/repotests.yml");
2674
- assert.deepStrictEqual(dep_list.length, 90);
2674
+ assert.deepStrictEqual(dep_list.length, 91);
2675
2675
  });
2676
2676
  // biome-ignore-end lint/suspicious/noTemplateCurlyInString: fp
2677
2677
 
@@ -3575,6 +3575,23 @@ it("get licenses", () => {
3575
3575
  },
3576
3576
  ]);
3577
3577
 
3578
+ licenses = getLicenses({
3579
+ license: [
3580
+ {
3581
+ type: "MIT",
3582
+ url: "https://github.com/harvesthq/chosen/blob/master/LICENSE.md",
3583
+ },
3584
+ ],
3585
+ });
3586
+ assert.deepStrictEqual(licenses, [
3587
+ {
3588
+ license: {
3589
+ id: "MIT",
3590
+ url: "https://github.com/harvesthq/chosen/blob/master/LICENSE.md",
3591
+ },
3592
+ },
3593
+ ]);
3594
+
3578
3595
  licenses = getLicenses({
3579
3596
  license: "GPL-2.0+",
3580
3597
  });
@@ -3677,6 +3694,30 @@ it("parsePkgLock v2", async () => {
3677
3694
  ],
3678
3695
  },
3679
3696
  });
3697
+ const devOnlyPkg = deps.find(
3698
+ (pkg) => pkg["bom-ref"] === "pkg:npm/@types/shelljs@0.8.11",
3699
+ );
3700
+ assert.ok(devOnlyPkg);
3701
+ assert.deepStrictEqual(devOnlyPkg.scope, "optional");
3702
+ assert.ok(
3703
+ devOnlyPkg.properties.some(
3704
+ (property) =>
3705
+ property.name === "cdx:npm:package:development" &&
3706
+ property.value === "true",
3707
+ ),
3708
+ );
3709
+ const devOptionalPkg = deps.find(
3710
+ (pkg) => pkg["bom-ref"] === "pkg:npm/@esbuild/android-arm@0.15.12",
3711
+ );
3712
+ assert.ok(devOptionalPkg);
3713
+ assert.deepStrictEqual(devOptionalPkg.scope, "optional");
3714
+ assert.ok(
3715
+ devOptionalPkg.properties.some(
3716
+ (property) =>
3717
+ property.name === "cdx:npm:package:development" &&
3718
+ property.value === "true",
3719
+ ),
3720
+ );
3680
3721
  assert.deepStrictEqual(parsedList.dependenciesList.length, 134);
3681
3722
  });
3682
3723
 
@@ -3865,10 +3906,8 @@ it("parsePnpmLock", async () => {
3865
3906
  type: "library",
3866
3907
  version: "7.16.7",
3867
3908
  properties: [
3868
- {
3869
- name: "SrcFile",
3870
- value: "./test/data/pnpm-lock.yaml",
3871
- },
3909
+ { name: "SrcFile", value: "./test/data/pnpm-lock.yaml" },
3910
+ { name: "cdx:npm:package:development", value: "true" },
3872
3911
  ],
3873
3912
  evidence: {
3874
3913
  identity: {
@@ -3970,7 +4009,10 @@ it("parsePnpmLock", async () => {
3970
4009
  type: "library",
3971
4010
  _integrity:
3972
4011
  "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
3973
- properties: [{ name: "SrcFile", value: "./test/data/pnpm-lock6.yaml" }],
4012
+ properties: [
4013
+ { name: "SrcFile", value: "./test/data/pnpm-lock6.yaml" },
4014
+ { name: "cdx:npm:package:development", value: "true" },
4015
+ ],
3974
4016
  evidence: {
3975
4017
  identity: {
3976
4018
  field: "purl",
@@ -3995,7 +4037,10 @@ it("parsePnpmLock", async () => {
3995
4037
  type: "library",
3996
4038
  _integrity:
3997
4039
  "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
3998
- properties: [{ name: "SrcFile", value: "./test/data/pnpm-lock6.yaml" }],
4040
+ properties: [
4041
+ { name: "SrcFile", value: "./test/data/pnpm-lock6.yaml" },
4042
+ { name: "cdx:npm:package:development", value: "true" },
4043
+ ],
3999
4044
  evidence: {
4000
4045
  identity: {
4001
4046
  field: "purl",
@@ -4023,7 +4068,10 @@ it("parsePnpmLock", async () => {
4023
4068
  type: "library",
4024
4069
  _integrity:
4025
4070
  "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
4026
- properties: [{ name: "SrcFile", value: "./test/data/pnpm-lock6a.yaml" }],
4071
+ properties: [
4072
+ { name: "SrcFile", value: "./test/data/pnpm-lock6a.yaml" },
4073
+ { name: "cdx:npm:package:development", value: "true" },
4074
+ ],
4027
4075
  evidence: {
4028
4076
  identity: {
4029
4077
  field: "purl",
@@ -4038,6 +4086,18 @@ it("parsePnpmLock", async () => {
4038
4086
  },
4039
4087
  },
4040
4088
  });
4089
+ const pnpmDevOptionalPkg = parsedList.pkgList.find(
4090
+ (pkg) => pkg["bom-ref"] === "pkg:npm/@cyclonedx/cdxgen-plugins-bin@1.0.5",
4091
+ );
4092
+ assert.ok(pnpmDevOptionalPkg);
4093
+ assert.deepStrictEqual(pnpmDevOptionalPkg.scope, "optional");
4094
+ assert.ok(
4095
+ pnpmDevOptionalPkg.properties.some(
4096
+ (property) =>
4097
+ property.name === "cdx:npm:package:development" &&
4098
+ property.value === "true",
4099
+ ),
4100
+ );
4041
4101
  // Test case to see if parsePnpmLock is finding all root deps
4042
4102
  const dummpyParent = {
4043
4103
  name: "rush",
@@ -4079,7 +4139,15 @@ it("parsePnpmLock", async () => {
4079
4139
  assert.deepStrictEqual(parsedList.pkgList.length, 1007);
4080
4140
  assert.deepStrictEqual(parsedList.dependenciesList.length, 1006);
4081
4141
  assert.deepStrictEqual(
4082
- parsedList.pkgList.filter((pkg) => !pkg.scope).length,
4142
+ parsedList.pkgList.filter(
4143
+ (pkg) =>
4144
+ !pkg.scope &&
4145
+ !pkg.properties?.some(
4146
+ (property) =>
4147
+ property.name === "cdx:npm:package:development" &&
4148
+ property.value === "true",
4149
+ ),
4150
+ ).length,
4083
4151
  0,
4084
4152
  );
4085
4153
  parsedList = await parsePnpmLock("./test/data/pnpm-lock9b.yaml", {
@@ -4089,7 +4157,15 @@ it("parsePnpmLock", async () => {
4089
4157
  assert.deepStrictEqual(parsedList.pkgList.length, 1366);
4090
4158
  assert.deepStrictEqual(parsedList.dependenciesList.length, 1353);
4091
4159
  assert.deepStrictEqual(
4092
- parsedList.pkgList.filter((pkg) => !pkg.scope).length,
4160
+ parsedList.pkgList.filter(
4161
+ (pkg) =>
4162
+ !pkg.scope &&
4163
+ !pkg.properties?.some(
4164
+ (property) =>
4165
+ property.name === "cdx:npm:package:development" &&
4166
+ property.value === "true",
4167
+ ),
4168
+ ).length,
4093
4169
  12,
4094
4170
  );
4095
4171
  parsedList = await parsePnpmLock("./test/data/pnpm-lock9c.yaml", {
@@ -4099,9 +4175,40 @@ it("parsePnpmLock", async () => {
4099
4175
  assert.deepStrictEqual(parsedList.pkgList.length, 461);
4100
4176
  assert.deepStrictEqual(parsedList.dependenciesList.length, 462);
4101
4177
  assert.deepStrictEqual(
4102
- parsedList.pkgList.filter((pkg) => !pkg.scope).length,
4178
+ parsedList.pkgList.filter(
4179
+ (pkg) =>
4180
+ !pkg.scope &&
4181
+ !pkg.properties?.some(
4182
+ (property) =>
4183
+ property.name === "cdx:npm:package:development" &&
4184
+ property.value === "true",
4185
+ ),
4186
+ ).length,
4103
4187
  3,
4104
4188
  );
4189
+ parsedList = await parsePnpmLock(
4190
+ "./test/data/pnpm-lock-dev-propagation.yaml",
4191
+ );
4192
+ assert.deepStrictEqual(parsedList.pkgList.length, 4);
4193
+ assert.deepStrictEqual(parsedList.dependenciesList.length, 4);
4194
+ assert.deepStrictEqual(
4195
+ parsedList.pkgList.filter(
4196
+ (pkg) =>
4197
+ pkg.scope === "optional" &&
4198
+ pkg.properties?.some(
4199
+ (property) =>
4200
+ property.name === "cdx:npm:package:development" &&
4201
+ property.value === "true",
4202
+ ),
4203
+ ).length,
4204
+ 4,
4205
+ );
4206
+ assert.ok(
4207
+ parsedList.pkgList.find((pkg) => pkg["bom-ref"] === "pkg:npm/gamma@1.0.0"),
4208
+ );
4209
+ assert.ok(
4210
+ parsedList.pkgList.find((pkg) => pkg["bom-ref"] === "pkg:npm/delta@1.0.0"),
4211
+ );
4105
4212
  parsedList = await parsePnpmLock(
4106
4213
  "./test/data/pnpm_locks/bytemd-pnpm-lock.yaml",
4107
4214
  );
@@ -94,11 +94,31 @@ paths:
94
94
  required: false
95
95
  schema:
96
96
  $ref: '#/components/schemas/CDXGEN/properties/projectVersion'
97
+ - name: autoCreate
98
+ in: query
99
+ required: false
100
+ schema:
101
+ $ref: '#/components/schemas/CDXGEN/properties/autoCreate'
102
+ - name: isLatest
103
+ in: query
104
+ required: false
105
+ schema:
106
+ $ref: '#/components/schemas/CDXGEN/properties/isLatest'
97
107
  - name: parentUUID
98
108
  in: query
99
109
  required: false
100
110
  schema:
101
111
  $ref: '#/components/schemas/CDXGEN/properties/parentUUID'
112
+ - name: parentProjectName
113
+ in: query
114
+ required: false
115
+ schema:
116
+ $ref: '#/components/schemas/CDXGEN/properties/parentProjectName'
117
+ - name: parentProjectVersion
118
+ in: query
119
+ required: false
120
+ schema:
121
+ $ref: '#/components/schemas/CDXGEN/properties/parentProjectVersion'
102
122
  - name: serverUrl
103
123
  in: query
104
124
  required: false
@@ -252,9 +272,22 @@ components:
252
272
  type: string
253
273
  description: Dependency Track project version
254
274
  default: ""
275
+ autoCreate:
276
+ type: boolean
277
+ description: Dependency Track autoCreate value for BOM submissions
278
+ default: true
279
+ isLatest:
280
+ type: boolean
281
+ description: Dependency Track isLatest value for BOM submissions
255
282
  parentUUID:
256
283
  type: string
257
284
  description: UUID of the parent Dependency Track project
285
+ parentProjectName:
286
+ type: string
287
+ description: Name of the parent Dependency Track project
288
+ parentProjectVersion:
289
+ type: string
290
+ description: Version of the parent Dependency Track project
258
291
  serverUrl:
259
292
  type: string
260
293
  description: URL to the Dependency Track API server
@@ -36,7 +36,11 @@ const ALLOWED_PARAMS = [
36
36
  "projectGroup",
37
37
  "projectTag",
38
38
  "projectVersion",
39
+ "autoCreate",
40
+ "isLatest",
39
41
  "parentUUID",
42
+ "parentProjectName",
43
+ "parentProjectVersion",
40
44
  "serverUrl",
41
45
  "apiKey",
42
46
  "specVersion",
@@ -270,7 +274,10 @@ function gitClone(repoUrl, branch = null) {
270
274
  "core.fsmonitor=false",
271
275
  "-c",
272
276
  "safe.bareRepository=explicit",
277
+ "-c",
278
+ "core.hooksPath=/dev/null",
273
279
  "clone",
280
+ "--template=",
274
281
  repoUrl,
275
282
  "--depth",
276
283
  "1",
@@ -297,13 +304,14 @@ function gitClone(repoUrl, branch = null) {
297
304
  GIT_CONFIG_VALUE_0: "false",
298
305
  GIT_CONFIG_KEY_1: "safe.bareRepository",
299
306
  GIT_CONFIG_VALUE_1: "explicit",
307
+ GIT_TERMINAL_PROMPT: "0",
300
308
  };
301
309
  const env = isSecureMode
302
310
  ? {
303
311
  ...process.env,
304
312
  ...envConfigs,
305
313
  GIT_CONFIG_NOSYSTEM: "1",
306
- GIT_CONFIG_NOGLOBAL: "1",
314
+ GIT_CONFIG_GLOBAL: "/dev/null",
307
315
  GIT_ALLOW_PROTOCOL: gitAllowProtocol,
308
316
  }
309
317
  : {
@@ -676,4 +684,4 @@ const start = (options) => {
676
684
  });
677
685
  };
678
686
 
679
- export { configureServer, start };
687
+ export { configureServer, gitClone, start };