@cyclonedx/cdxgen 12.3.0 → 12.3.2

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 (121) hide show
  1. package/README.md +15 -5
  2. package/bin/audit.js +7 -0
  3. package/bin/cdxgen.js +241 -81
  4. package/bin/repl.js +138 -0
  5. package/data/rules/ai-agent-governance.yaml +249 -0
  6. package/data/rules/dependency-sources.yaml +41 -0
  7. package/data/rules/mcp-servers.yaml +304 -0
  8. package/data/rules/package-integrity.yaml +123 -0
  9. package/lib/audit/index.js +353 -29
  10. package/lib/audit/index.poku.js +247 -7
  11. package/lib/audit/reporters.js +26 -0
  12. package/lib/audit/scoring.js +262 -13
  13. package/lib/audit/scoring.poku.js +179 -0
  14. package/lib/audit/targets.js +391 -2
  15. package/lib/audit/targets.poku.js +416 -3
  16. package/lib/cli/index.js +588 -45
  17. package/lib/cli/index.poku.js +735 -1
  18. package/lib/evinser/evinser.js +8 -5
  19. package/lib/helpers/agentFormulationParser.js +318 -0
  20. package/lib/helpers/aiInventory.js +262 -0
  21. package/lib/helpers/aiInventory.poku.js +111 -0
  22. package/lib/helpers/analyzer.js +1769 -0
  23. package/lib/helpers/analyzer.poku.js +284 -3
  24. package/lib/helpers/auditCategories.js +76 -0
  25. package/lib/helpers/ciParsers/githubActions.js +140 -16
  26. package/lib/helpers/ciParsers/githubActions.poku.js +110 -0
  27. package/lib/helpers/communityAiConfigParser.js +672 -0
  28. package/lib/helpers/communityAiConfigParser.poku.js +63 -0
  29. package/lib/helpers/depsUtils.js +108 -0
  30. package/lib/helpers/depsUtils.poku.js +72 -1
  31. package/lib/helpers/display.js +325 -3
  32. package/lib/helpers/display.poku.js +301 -0
  33. package/lib/helpers/formulationParsers.js +28 -0
  34. package/lib/helpers/formulationParsers.poku.js +504 -1
  35. package/lib/helpers/jsonLike.js +102 -0
  36. package/lib/helpers/jsonLike.poku.js +34 -0
  37. package/lib/helpers/mcp.js +248 -0
  38. package/lib/helpers/mcp.poku.js +101 -0
  39. package/lib/helpers/mcpConfigParser.js +656 -0
  40. package/lib/helpers/mcpConfigParser.poku.js +126 -0
  41. package/lib/helpers/mcpDiscovery.js +84 -0
  42. package/lib/helpers/mcpDiscovery.poku.js +21 -0
  43. package/lib/helpers/protobom.js +3 -3
  44. package/lib/helpers/provenanceUtils.js +29 -4
  45. package/lib/helpers/provenanceUtils.poku.js +29 -3
  46. package/lib/helpers/registryProvenance.js +210 -0
  47. package/lib/helpers/registryProvenance.poku.js +144 -0
  48. package/lib/helpers/rustFormulationParser.js +330 -0
  49. package/lib/helpers/source.js +21 -2
  50. package/lib/helpers/source.poku.js +38 -0
  51. package/lib/helpers/utils.js +1331 -83
  52. package/lib/helpers/utils.poku.js +599 -188
  53. package/lib/helpers/vsixutils.js +12 -4
  54. package/lib/helpers/vsixutils.poku.js +34 -0
  55. package/lib/managers/binary.js +36 -12
  56. package/lib/managers/binary.poku.js +68 -0
  57. package/lib/managers/docker.js +59 -9
  58. package/lib/managers/docker.poku.js +61 -0
  59. package/lib/managers/piptree.js +12 -7
  60. package/lib/managers/piptree.poku.js +44 -0
  61. package/lib/stages/postgen/annotator.js +2 -1
  62. package/lib/stages/postgen/annotator.poku.js +15 -0
  63. package/lib/stages/postgen/auditBom.js +20 -6
  64. package/lib/stages/postgen/auditBom.poku.js +694 -1
  65. package/lib/stages/postgen/postgen.js +262 -11
  66. package/lib/stages/postgen/postgen.poku.js +306 -2
  67. package/lib/stages/postgen/ruleEngine.js +49 -1
  68. package/lib/stages/postgen/spdxConverter.poku.js +70 -0
  69. package/lib/stages/pregen/pregen.js +6 -4
  70. package/package.json +1 -1
  71. package/types/bin/repl.d.ts.map +1 -1
  72. package/types/lib/audit/index.d.ts.map +1 -1
  73. package/types/lib/audit/reporters.d.ts.map +1 -1
  74. package/types/lib/audit/scoring.d.ts.map +1 -1
  75. package/types/lib/audit/targets.d.ts +12 -0
  76. package/types/lib/audit/targets.d.ts.map +1 -1
  77. package/types/lib/cli/index.d.ts +2 -8
  78. package/types/lib/cli/index.d.ts.map +1 -1
  79. package/types/lib/evinser/evinser.d.ts.map +1 -1
  80. package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
  81. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
  82. package/types/lib/helpers/aiInventory.d.ts +23 -0
  83. package/types/lib/helpers/aiInventory.d.ts.map +1 -0
  84. package/types/lib/helpers/analyzer.d.ts +10 -0
  85. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  86. package/types/lib/helpers/auditCategories.d.ts +12 -0
  87. package/types/lib/helpers/auditCategories.d.ts.map +1 -0
  88. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  89. package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
  90. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
  91. package/types/lib/helpers/depsUtils.d.ts +8 -0
  92. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  93. package/types/lib/helpers/display.d.ts +17 -1
  94. package/types/lib/helpers/display.d.ts.map +1 -1
  95. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  96. package/types/lib/helpers/jsonLike.d.ts +4 -0
  97. package/types/lib/helpers/jsonLike.d.ts.map +1 -0
  98. package/types/lib/helpers/mcp.d.ts +29 -0
  99. package/types/lib/helpers/mcp.d.ts.map +1 -0
  100. package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
  101. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
  102. package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
  103. package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
  104. package/types/lib/helpers/provenanceUtils.d.ts +5 -3
  105. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -1
  106. package/types/lib/helpers/registryProvenance.d.ts +9 -0
  107. package/types/lib/helpers/registryProvenance.d.ts.map +1 -1
  108. package/types/lib/helpers/rustFormulationParser.d.ts +17 -0
  109. package/types/lib/helpers/rustFormulationParser.d.ts.map +1 -0
  110. package/types/lib/helpers/source.d.ts.map +1 -1
  111. package/types/lib/helpers/utils.d.ts +31 -1
  112. package/types/lib/helpers/utils.d.ts.map +1 -1
  113. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  114. package/types/lib/managers/binary.d.ts.map +1 -1
  115. package/types/lib/managers/docker.d.ts.map +1 -1
  116. package/types/lib/managers/piptree.d.ts.map +1 -1
  117. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  118. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  119. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  120. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  121. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
@@ -1,8 +1,13 @@
1
1
  import { PackageURL } from "packageurl-js";
2
2
 
3
3
  import { hasTrustedPublishingProperties } from "../helpers/provenanceUtils.js";
4
+ import {
5
+ getCratesMetadata,
6
+ getNpmMetadata,
7
+ getPyMetadata,
8
+ } from "../helpers/utils.js";
4
9
 
5
- const SUPPORTED_PURL_TYPES = new Set(["npm", "pypi"]);
10
+ const SUPPORTED_PURL_TYPES = new Set(["cargo", "npm", "pypi"]);
6
11
  const NON_REQUIRED_SCOPES = new Set(["excluded", "optional"]);
7
12
 
8
13
  /**
@@ -19,12 +24,14 @@ function normalizeTargetSelectionOptions(options) {
19
24
  if (typeof options === "number") {
20
25
  return {
21
26
  maxTargets: options,
27
+ prioritizeDirectRuntime: true,
22
28
  scope: undefined,
23
29
  trusted: "exclude",
24
30
  };
25
31
  }
26
32
  return {
27
33
  maxTargets: options?.maxTargets,
34
+ prioritizeDirectRuntime: options?.prioritizeDirectRuntime ?? true,
28
35
  scope: options?.scope === "required" ? "required" : undefined,
29
36
  trusted:
30
37
  options?.trusted === "only"
@@ -57,6 +64,313 @@ function normalizeComponentScope(scope) {
57
64
  return scope.toLowerCase();
58
65
  }
59
66
 
67
+ function getComponentPropertyValue(component, propertyName) {
68
+ return component?.properties?.find(
69
+ (property) => property.name === propertyName,
70
+ )?.value;
71
+ }
72
+
73
+ function getComponentPropertyValues(component, propertyName) {
74
+ return (component?.properties || [])
75
+ .filter((property) => property?.name === propertyName)
76
+ .map((property) => property?.value)
77
+ .filter((propertyValue) => typeof propertyValue === "string")
78
+ .map((propertyValue) => propertyValue.trim())
79
+ .filter(Boolean);
80
+ }
81
+
82
+ function hasTruthyComponentProperty(component, propertyName) {
83
+ return getComponentPropertyValue(component, propertyName) === "true";
84
+ }
85
+
86
+ function hasNonEmptyComponentProperty(component, propertyName) {
87
+ const propertyValue = getComponentPropertyValue(component, propertyName);
88
+ return typeof propertyValue === "string" && propertyValue.trim().length > 0;
89
+ }
90
+
91
+ function extractRootDependencyRefs(bomJson) {
92
+ const directRefs = new Set();
93
+ const rootRef =
94
+ bomJson?.metadata?.component?.["bom-ref"] ||
95
+ bomJson?.metadata?.component?.bomRef ||
96
+ bomJson?.metadata?.component?.purl;
97
+ if (!rootRef || !Array.isArray(bomJson?.dependencies)) {
98
+ return directRefs;
99
+ }
100
+ const rootDependency = bomJson.dependencies.find(
101
+ (dependency) => dependency?.ref === rootRef,
102
+ );
103
+ if (!Array.isArray(rootDependency?.dependsOn)) {
104
+ return directRefs;
105
+ }
106
+ for (const dependencyRef of rootDependency.dependsOn) {
107
+ if (dependencyRef) {
108
+ directRefs.add(dependencyRef);
109
+ }
110
+ }
111
+ return directRefs;
112
+ }
113
+
114
+ function isPlatformSpecificComponent(component, type) {
115
+ if (type === "npm") {
116
+ return ["cdx:npm:cpu", "cdx:npm:libc", "cdx:npm:os"].some((propertyName) =>
117
+ hasNonEmptyComponentProperty(component, propertyName),
118
+ );
119
+ }
120
+ if (type === "pypi") {
121
+ return [
122
+ "cdx:pip:markers",
123
+ "cdx:pypi:requiresPython",
124
+ "cdx:python:requires_python",
125
+ ].some((propertyName) =>
126
+ hasNonEmptyComponentProperty(component, propertyName),
127
+ );
128
+ }
129
+ if (type === "cargo") {
130
+ return ["cdx:cargo:target"].some((propertyName) =>
131
+ hasNonEmptyComponentProperty(component, propertyName),
132
+ );
133
+ }
134
+ return false;
135
+ }
136
+
137
+ function isDevelopmentOnlyComponent(component, type) {
138
+ if (type === "npm") {
139
+ return hasTruthyComponentProperty(component, "cdx:npm:package:development");
140
+ }
141
+ if (type === "cargo") {
142
+ const dependencyKinds = getComponentPropertyValues(
143
+ component,
144
+ "cdx:cargo:dependencyKind",
145
+ ).map((value) => value.toLowerCase());
146
+ return dependencyKinds.length
147
+ ? dependencyKinds.every((value) => value === "dev")
148
+ : false;
149
+ }
150
+ return false;
151
+ }
152
+
153
+ function isBuildOnlyWorkspaceCargoComponent(component) {
154
+ const dependencyKinds = getComponentPropertyValues(
155
+ component,
156
+ "cdx:cargo:dependencyKind",
157
+ ).map((value) => value.toLowerCase());
158
+ return (
159
+ dependencyKinds.length > 0 &&
160
+ dependencyKinds.every((value) => value === "build") &&
161
+ hasTruthyComponentProperty(
162
+ component,
163
+ "cdx:cargo:workspaceDependencyResolved",
164
+ )
165
+ );
166
+ }
167
+
168
+ function isRuntimeFacingCargoComponent(component, directDependency) {
169
+ const dependencyKinds = getComponentPropertyValues(
170
+ component,
171
+ "cdx:cargo:dependencyKind",
172
+ ).map((value) => value.toLowerCase());
173
+ if (dependencyKinds.includes("runtime")) {
174
+ return true;
175
+ }
176
+ return Boolean(
177
+ directDependency &&
178
+ !isDevelopmentOnlyComponent(component, "cargo") &&
179
+ !isBuildOnlyWorkspaceCargoComponent(component),
180
+ );
181
+ }
182
+
183
+ function getComponentOccurrenceCount(component) {
184
+ return Array.isArray(component?.evidence?.occurrences)
185
+ ? component.evidence.occurrences.length
186
+ : 0;
187
+ }
188
+
189
+ function triagePriorityScore(target) {
190
+ // Triage should start with the most user-actionable packages:
191
+ // direct runtime dependencies first, then other required deps, then
192
+ // deprioritize dev-only and platform-constrained packages.
193
+ let priority = 0;
194
+ if (target?.directDependency) {
195
+ priority += 16;
196
+ }
197
+ if (target?.explicitRequiredScope) {
198
+ priority += 8;
199
+ }
200
+ if (target?.required) {
201
+ priority += 4;
202
+ }
203
+ if (target?.type === "cargo" && target?.runtimeFacingCargo) {
204
+ priority += 6;
205
+ }
206
+ priority += Math.min(target?.occurrenceCount || 0, 6);
207
+ if (!target?.developmentOnly) {
208
+ priority += 2;
209
+ }
210
+ if (!target?.platformSpecific) {
211
+ priority += 1;
212
+ }
213
+ if (target?.type === "cargo" && target?.buildOnlyWorkspace) {
214
+ priority -= 5;
215
+ }
216
+ return priority;
217
+ }
218
+
219
+ function registryMetadataPropertyName(propertyName) {
220
+ return (
221
+ propertyName?.startsWith("cdx:cargo:") ||
222
+ propertyName?.startsWith("cdx:npm:trustedPublishing") ||
223
+ propertyName?.startsWith("cdx:npm:provenance") ||
224
+ propertyName?.startsWith("cdx:pypi:trustedPublishing") ||
225
+ propertyName?.startsWith("cdx:pypi:provenance") ||
226
+ propertyName === "cdx:pypi:uploaderVerified"
227
+ );
228
+ }
229
+
230
+ function appendUniqueProperties(targetProperties, extraProperties) {
231
+ const existing = new Set(
232
+ (targetProperties || []).map((property) =>
233
+ [property.name, property.value].join("="),
234
+ ),
235
+ );
236
+ for (const property of extraProperties || []) {
237
+ if (!property?.name) {
238
+ continue;
239
+ }
240
+ const key = [property.name, property.value].join("=");
241
+ if (existing.has(key)) {
242
+ continue;
243
+ }
244
+ targetProperties.push(property);
245
+ existing.add(key);
246
+ }
247
+ }
248
+
249
+ function auditRegistryMetadataKey(type, namespace, name, version) {
250
+ return [type, namespace || "", name || "", version || ""].join("|");
251
+ }
252
+
253
+ function buildRegistryMetadataCandidate(componentPurl, component) {
254
+ let purlObj;
255
+ try {
256
+ purlObj = PackageURL.fromString(componentPurl);
257
+ } catch {
258
+ return undefined;
259
+ }
260
+ if (!SUPPORTED_PURL_TYPES.has(purlObj.type) || !purlObj.version) {
261
+ return undefined;
262
+ }
263
+ const auditMetadataKey = auditRegistryMetadataKey(
264
+ purlObj.type,
265
+ purlObj.namespace,
266
+ purlObj.name,
267
+ purlObj.version,
268
+ );
269
+ return {
270
+ _auditMetadataKey: auditMetadataKey,
271
+ group: purlObj.namespace,
272
+ name: purlObj.name,
273
+ properties: Array.isArray(component?.properties)
274
+ ? component.properties.map((property) => ({ ...property }))
275
+ : [],
276
+ type: purlObj.type,
277
+ version: purlObj.version,
278
+ };
279
+ }
280
+
281
+ function restoreFetchPackageMetadata(originalFetchPackageMetadata) {
282
+ if (originalFetchPackageMetadata === undefined) {
283
+ delete process.env.CDXGEN_FETCH_PKG_METADATA;
284
+ return;
285
+ }
286
+ process.env.CDXGEN_FETCH_PKG_METADATA = originalFetchPackageMetadata;
287
+ }
288
+
289
+ /**
290
+ * Enrich input BOM components with registry provenance/trusted-publishing
291
+ * metadata so audit target filtering can exclude trusted packages even when the
292
+ * input BOM was generated without --bom-audit.
293
+ *
294
+ * @param {{ source: string, bomJson: object }[]} inputBoms loaded input BOMs
295
+ * @returns {Promise<void>}
296
+ */
297
+ export async function enrichInputBomsWithRegistryMetadata(inputBoms) {
298
+ const cargoCandidates = [];
299
+ const npmCandidates = [];
300
+ const pypiCandidates = [];
301
+ const componentRefs = new Map();
302
+ for (const inputBom of inputBoms || []) {
303
+ const components = Array.isArray(inputBom?.bomJson?.components)
304
+ ? inputBom.bomJson.components
305
+ : [];
306
+ for (const component of components) {
307
+ const componentPurl = component?.purl;
308
+ if (
309
+ !componentPurl ||
310
+ hasTrustedPublishingProperties(component?.properties || [])
311
+ ) {
312
+ continue;
313
+ }
314
+ const candidate = buildRegistryMetadataCandidate(
315
+ componentPurl,
316
+ component,
317
+ );
318
+ if (!candidate) {
319
+ continue;
320
+ }
321
+ if (!componentRefs.has(candidate._auditMetadataKey)) {
322
+ componentRefs.set(candidate._auditMetadataKey, []);
323
+ if (candidate.type === "cargo") {
324
+ cargoCandidates.push(candidate);
325
+ } else if (candidate.type === "npm") {
326
+ npmCandidates.push(candidate);
327
+ } else if (candidate.type === "pypi") {
328
+ pypiCandidates.push(candidate);
329
+ }
330
+ }
331
+ componentRefs.get(candidate._auditMetadataKey).push(component);
332
+ }
333
+ }
334
+ if (
335
+ !cargoCandidates.length &&
336
+ !npmCandidates.length &&
337
+ !pypiCandidates.length
338
+ ) {
339
+ return;
340
+ }
341
+ const originalFetchPackageMetadata = process.env.CDXGEN_FETCH_PKG_METADATA;
342
+ process.env.CDXGEN_FETCH_PKG_METADATA = "true";
343
+ try {
344
+ const enrichedCandidates = [
345
+ ...(cargoCandidates.length
346
+ ? await getCratesMetadata(cargoCandidates)
347
+ : []),
348
+ ...(npmCandidates.length ? await getNpmMetadata(npmCandidates) : []),
349
+ ...(pypiCandidates.length
350
+ ? await getPyMetadata(pypiCandidates, false)
351
+ : []),
352
+ ];
353
+ for (const candidate of enrichedCandidates) {
354
+ const matchedComponents =
355
+ componentRefs.get(candidate._auditMetadataKey) || [];
356
+ const registryProperties = (candidate.properties || []).filter(
357
+ (property) => registryMetadataPropertyName(property?.name),
358
+ );
359
+ if (!registryProperties.length) {
360
+ continue;
361
+ }
362
+ for (const component of matchedComponents) {
363
+ component.properties = Array.isArray(component.properties)
364
+ ? component.properties
365
+ : [];
366
+ appendUniqueProperties(component.properties, registryProperties);
367
+ }
368
+ }
369
+ } finally {
370
+ restoreFetchPackageMetadata(originalFetchPackageMetadata);
371
+ }
372
+ }
373
+
60
374
  function mergeTargetScope(existingTarget, nextTarget) {
61
375
  const mergedRequired = Boolean(
62
376
  existingTarget.required || nextTarget.required,
@@ -101,6 +415,7 @@ export function extractPurlTargetsFromBom(bomJson, sourceName, options) {
101
415
  const components = Array.isArray(bomJson?.components)
102
416
  ? bomJson.components
103
417
  : [];
418
+ const rootDependencyRefs = extractRootDependencyRefs(bomJson);
104
419
  for (const component of components) {
105
420
  const componentScope = normalizeComponentScope(component?.scope);
106
421
  if (
@@ -139,6 +454,10 @@ export function extractPurlTargetsFromBom(bomJson, sourceName, options) {
139
454
  }
140
455
  targets.push({
141
456
  bomRef: component?.["bom-ref"],
457
+ buildOnlyWorkspace:
458
+ purlObj.type === "cargo"
459
+ ? isBuildOnlyWorkspaceCargoComponent(component)
460
+ : false,
142
461
  name: purlObj.name,
143
462
  namespace: purlObj.namespace,
144
463
  purl: componentPurl,
@@ -146,7 +465,22 @@ export function extractPurlTargetsFromBom(bomJson, sourceName, options) {
146
465
  ? component.properties.map((property) => ({ ...property }))
147
466
  : [],
148
467
  qualifiers: purlObj.qualifiers,
468
+ directDependency:
469
+ Boolean(component?.["bom-ref"]) &&
470
+ rootDependencyRefs.has(component["bom-ref"]),
471
+ explicitRequiredScope: componentScope === "required",
472
+ developmentOnly: isDevelopmentOnlyComponent(component, purlObj.type),
473
+ occurrenceCount: getComponentOccurrenceCount(component),
474
+ platformSpecific: isPlatformSpecificComponent(component, purlObj.type),
149
475
  required: isRequiredComponentScope(componentScope),
476
+ runtimeFacingCargo:
477
+ purlObj.type === "cargo"
478
+ ? isRuntimeFacingCargoComponent(
479
+ component,
480
+ Boolean(component?.["bom-ref"]) &&
481
+ rootDependencyRefs.has(component["bom-ref"]),
482
+ )
483
+ : false,
150
484
  scope: componentScope,
151
485
  source: sourceName,
152
486
  trustedPublishing: hasTrustedPublishingProperties(component?.properties),
@@ -189,7 +523,30 @@ export function collectAuditTargets(inputBoms, options) {
189
523
  for (const target of extracted.targets) {
190
524
  const existing = targetMap.get(target.purl);
191
525
  if (existing) {
526
+ existing.directDependency = Boolean(
527
+ existing.directDependency || target.directDependency,
528
+ );
529
+ existing.explicitRequiredScope = Boolean(
530
+ existing.explicitRequiredScope || target.explicitRequiredScope,
531
+ );
532
+ // A package stays dev-only or platform-specific only when every observed
533
+ // occurrence carries that constraint. Any runtime/general occurrence
534
+ // should lift the deprioritization signal for triage ordering.
535
+ existing.developmentOnly = Boolean(
536
+ existing.developmentOnly && target.developmentOnly,
537
+ );
538
+ existing.occurrenceCount =
539
+ (existing.occurrenceCount || 0) + (target.occurrenceCount || 0);
540
+ existing.platformSpecific = Boolean(
541
+ existing.platformSpecific && target.platformSpecific,
542
+ );
543
+ existing.buildOnlyWorkspace = Boolean(
544
+ existing.buildOnlyWorkspace && target.buildOnlyWorkspace,
545
+ );
192
546
  existing.required = Boolean(existing.required || target.required);
547
+ existing.runtimeFacingCargo = Boolean(
548
+ existing.runtimeFacingCargo || target.runtimeFacingCargo,
549
+ );
193
550
  existing.scope = mergeTargetScope(existing, target);
194
551
  existing.trustedPublishing = Boolean(
195
552
  existing.trustedPublishing || target.trustedPublishing,
@@ -223,7 +580,15 @@ export function collectAuditTargets(inputBoms, options) {
223
580
  normalizedName: normalizePackageName(target.name),
224
581
  sources: [...target.sources].sort(),
225
582
  }));
226
- targets.sort((left, right) => left.purl.localeCompare(right.purl));
583
+ targets.sort((left, right) => {
584
+ if (selectorOptions.prioritizeDirectRuntime) {
585
+ const scoreDelta = triagePriorityScore(right) - triagePriorityScore(left);
586
+ if (scoreDelta !== 0) {
587
+ return scoreDelta;
588
+ }
589
+ }
590
+ return left.purl.localeCompare(right.purl);
591
+ });
227
592
  const trustedTargets = targets.filter((target) => target.trustedPublishing);
228
593
  if (selectorOptions.trusted === "only") {
229
594
  targets = trustedTargets;
@@ -232,6 +597,25 @@ export function collectAuditTargets(inputBoms, options) {
232
597
  }
233
598
  const requiredTargets = targets.filter((target) => target.required);
234
599
  const nonRequiredTargets = targets.filter((target) => !target.required);
600
+ const directRuntimeTargets = targets.filter(
601
+ (target) =>
602
+ target.directDependency &&
603
+ target.required &&
604
+ !target.developmentOnly &&
605
+ !target.platformSpecific,
606
+ );
607
+ const developmentOnlyTargets = targets.filter(
608
+ (target) => target.developmentOnly,
609
+ );
610
+ const platformSpecificTargets = targets.filter(
611
+ (target) => target.platformSpecific,
612
+ );
613
+ const buildOnlyWorkspaceTargets = targets.filter(
614
+ (target) => target.type === "cargo" && target.buildOnlyWorkspace,
615
+ );
616
+ const cargoRuntimeFacingTargets = targets.filter(
617
+ (target) => target.type === "cargo" && target.runtimeFacingCargo,
618
+ );
235
619
  const availableTargets = targets.length;
236
620
  if (
237
621
  typeof selectorOptions.maxTargets === "number" &&
@@ -246,7 +630,12 @@ export function collectAuditTargets(inputBoms, options) {
246
630
  skipped,
247
631
  stats: {
248
632
  availableTargets,
633
+ directRuntimeTargets: directRuntimeTargets.length,
634
+ buildOnlyWorkspaceTargets: buildOnlyWorkspaceTargets.length,
635
+ cargoRuntimeFacingTargets: cargoRuntimeFacingTargets.length,
636
+ developmentOnlyTargets: developmentOnlyTargets.length,
249
637
  nonRequiredTargets: nonRequiredTargets.length,
638
+ platformSpecificTargets: platformSpecificTargets.length,
250
639
  requiredTargets: requiredTargets.length,
251
640
  trustedTargets: trustedTargets.length,
252
641
  trustedTargetsExcluded: