@cdxgen/cdxgen 13.1.0 → 13.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 (186) hide show
  1. package/README.md +8 -7
  2. package/bin/cdxgen.js +16 -3
  3. package/bin/evinse.js +2 -0
  4. package/data/crypto-oid.json +88 -12
  5. package/data/remediations.json +23 -0
  6. package/data/rules/ai-bom.yaml +78 -0
  7. package/data/rules/cargo-rust.yaml +132 -0
  8. package/data/rules/mcp-servers.yaml +122 -0
  9. package/lib/audit/targets.js +6 -1
  10. package/lib/cli/bomAssembly.js +12 -0
  11. package/lib/cli/index.js +192 -1
  12. package/lib/cli/jsBom.js +59 -2
  13. package/lib/cli/jvmBom.js +22 -0
  14. package/lib/cli/managedBom.js +152 -0
  15. package/lib/cli/nativeBom.js +798 -1
  16. package/lib/core/activity.js +120 -2
  17. package/lib/core/env.js +39 -1
  18. package/lib/core/userAgent.js +151 -0
  19. package/lib/ecosystems/cargoCache.js +364 -0
  20. package/lib/ecosystems/caxa.js +13 -5
  21. package/lib/ecosystems/cppEvidence.js +40 -2
  22. package/lib/ecosystems/ecosystems.js +241 -91
  23. package/lib/ecosystems/golem.js +11 -1
  24. package/lib/ecosystems/gradleutils.js +143 -2
  25. package/lib/ecosystems/kosi.js +1256 -0
  26. package/lib/ecosystems/parsers-crystal.js +179 -0
  27. package/lib/ecosystems/parsers-dotnet.js +69 -39
  28. package/lib/ecosystems/parsers-elm.js +816 -0
  29. package/lib/ecosystems/parsers-gleam.js +1 -1
  30. package/lib/ecosystems/parsers-julia.js +268 -0
  31. package/lib/ecosystems/parsers-lua.js +218 -0
  32. package/lib/ecosystems/parsers-misc.js +172 -0
  33. package/lib/ecosystems/parsers-nim.js +221 -0
  34. package/lib/ecosystems/parsers-opam.js +180 -0
  35. package/lib/ecosystems/parsers-perl.js +277 -0
  36. package/lib/ecosystems/parsers-python.js +1 -1
  37. package/lib/ecosystems/parsers-r.js +148 -0
  38. package/lib/ecosystems/parsers-rebar.js +411 -0
  39. package/lib/ecosystems/parsers-rust.js +932 -17
  40. package/lib/ecosystems/parsers-spack.js +204 -0
  41. package/lib/ecosystems/parsers-stack.js +264 -0
  42. package/lib/ecosystems/parsers-terraform.js +194 -0
  43. package/lib/ecosystems/rusi.js +126 -6
  44. package/lib/evinser/evinser.js +111 -1
  45. package/lib/helpers/agenticToolutils.js +925 -0
  46. package/lib/inventory/agentFormulationParser.js +57 -4
  47. package/lib/inventory/aiCollector.js +504 -10
  48. package/lib/inventory/aiInventory.js +12 -0
  49. package/lib/inventory/analyzer.js +488 -30
  50. package/lib/inventory/astWorker.js +6 -0
  51. package/lib/inventory/auditCategories.js +8 -0
  52. package/lib/inventory/cbomutils.js +30 -0
  53. package/lib/inventory/cdxrs.js +7 -2
  54. package/lib/inventory/communityAiConfigParser.js +215 -2
  55. package/lib/inventory/dosai.js +77 -0
  56. package/lib/inventory/envcontext.js +9 -6
  57. package/lib/inventory/evidenceUtils.js +13 -0
  58. package/lib/inventory/fetchBatch.js +74 -2
  59. package/lib/inventory/fetchRate.js +74 -11
  60. package/lib/inventory/forgeEnricher.js +3 -2
  61. package/lib/inventory/jvmToolEnv.js +6 -5
  62. package/lib/inventory/mcpConfigParser.js +166 -2
  63. package/lib/inventory/mcpDiscovery.js +11 -0
  64. package/lib/inventory/mcpServerManifest.js +389 -0
  65. package/lib/inventory/mcpbBundle.js +419 -0
  66. package/lib/inventory/plugins.js +3 -0
  67. package/lib/inventory/protobom.js +54 -88
  68. package/lib/inventory/protobomLoader.js +4 -0
  69. package/lib/inventory/rustFormulationParser.js +1 -1
  70. package/lib/inventory/source.js +10 -4
  71. package/lib/inventory/stylePackageRefs.js +41 -0
  72. package/lib/inventory/svelteUtils.js +524 -0
  73. package/lib/parsers/huggingfaceUtils.js +15 -7
  74. package/lib/parsers/onnx.js +294 -0
  75. package/lib/parsers/safetensors.js +114 -0
  76. package/lib/stages/postgen/introspection/markers.js +29 -6
  77. package/lib/stages/postgen/postgen.js +169 -1
  78. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  79. package/package.json +25 -25
  80. package/types/lib/audit/targets.d.ts.map +1 -1
  81. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  82. package/types/lib/cli/index.d.ts +2 -2
  83. package/types/lib/cli/index.d.ts.map +1 -1
  84. package/types/lib/cli/jsBom.d.ts +23 -0
  85. package/types/lib/cli/jsBom.d.ts.map +1 -1
  86. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  87. package/types/lib/cli/managedBom.d.ts +33 -0
  88. package/types/lib/cli/managedBom.d.ts.map +1 -1
  89. package/types/lib/cli/nativeBom.d.ts +88 -8
  90. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  91. package/types/lib/core/activity.d.ts.map +1 -1
  92. package/types/lib/core/env.d.ts +21 -0
  93. package/types/lib/core/env.d.ts.map +1 -1
  94. package/types/lib/core/userAgent.d.ts +56 -0
  95. package/types/lib/core/userAgent.d.ts.map +1 -0
  96. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  97. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  98. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  99. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  100. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  101. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  102. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  103. package/types/lib/ecosystems/gradleutils.d.ts +20 -0
  104. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  105. package/types/lib/ecosystems/kosi.d.ts +73 -0
  106. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  107. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  108. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  109. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  110. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  111. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  112. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  113. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  114. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  115. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  116. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  117. package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
  118. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  119. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  120. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  121. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  122. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  123. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  124. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  125. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  126. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  127. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  128. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  129. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  130. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  131. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  132. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  133. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  134. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  135. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  136. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  137. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  138. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  139. package/types/lib/evinser/evinser.d.ts +29 -0
  140. package/types/lib/evinser/evinser.d.ts.map +1 -1
  141. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  142. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  143. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  144. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  145. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  146. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  147. package/types/lib/inventory/auditCategories.d.ts +4 -0
  148. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  149. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  150. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  151. package/types/lib/inventory/dosai.d.ts +23 -0
  152. package/types/lib/inventory/dosai.d.ts.map +1 -1
  153. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  154. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  155. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  156. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  157. package/types/lib/inventory/fetchRate.d.ts +9 -6
  158. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  159. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  160. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  161. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  162. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  163. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  164. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  165. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  166. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  167. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  168. package/types/lib/inventory/plugins.d.ts.map +1 -1
  169. package/types/lib/inventory/protobom.d.ts +18 -13
  170. package/types/lib/inventory/protobom.d.ts.map +1 -1
  171. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  172. package/types/lib/inventory/source.d.ts.map +1 -1
  173. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  174. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  175. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  176. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  177. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  178. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  179. package/types/lib/parsers/onnx.d.ts +28 -0
  180. package/types/lib/parsers/onnx.d.ts.map +1 -0
  181. package/types/lib/parsers/safetensors.d.ts +22 -0
  182. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  183. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  184. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  185. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  186. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
@@ -0,0 +1,364 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+
5
+ import * as toml from "smol-toml";
6
+
7
+ import { readEnvironmentVariable } from "../core/activity.js";
8
+ import { safeExistsSync } from "../core/fs.js";
9
+
10
+ /**
11
+ * Crate metadata read from the local Cargo registry, without any network.
12
+ *
13
+ * Cargo keeps two things under `$CARGO_HOME/registry` that together answer
14
+ * most of what the crates.io API is asked for:
15
+ *
16
+ * - `src/<registry>/<name>-<version>/Cargo.toml` — the manifest Cargo
17
+ * normalised at publish time, carrying the crate's license, description,
18
+ * repository, homepage and minimum toolchain.
19
+ * - `index/<registry>/.cache/<a>/<b>/<name>` — the sparse index cache, one
20
+ * JSON line per published version with its checksum, feature table, yanked
21
+ * flag and publish time.
22
+ *
23
+ * Reading them first matters because crates.io's crawler policy allows one
24
+ * request per second: a 200-crate lockfile is several minutes of waiting that
25
+ * a populated `~/.cargo` makes unnecessary.
26
+ *
27
+ * What the local cache cannot supply is publisher identity. The index records
28
+ * when a version was published but not by whom, so the publisher-drift and
29
+ * ownership signals in `collectCargoRegistryProvenanceProperties` have no
30
+ * local equivalent. A component served from here is marked
31
+ * `cdx:cargo:metadataSource=local-registry` so a consumer can tell why those
32
+ * properties are absent, and `CARGO_METADATA_SOURCE=registry` forces the
33
+ * network path for runs that need them.
34
+ */
35
+
36
+ /**
37
+ * One crate version as the local registry describes it.
38
+ *
39
+ * @typedef {Object} CargoCacheMetadata
40
+ * @property {string} [description] Crate description.
41
+ * @property {string} [license] SPDX license expression.
42
+ * @property {string} [repository] Source repository URL.
43
+ * @property {string} [homepage] Homepage URL.
44
+ * @property {string} [rustVersion] Minimum supported Rust version.
45
+ * @property {string} [checksum] SHA-256 of the published `.crate` archive.
46
+ * @property {Object} [features] Feature table.
47
+ * @property {boolean} [yanked] Whether this version was yanked.
48
+ * @property {string} [publishTime] RFC 3339 publish timestamp.
49
+ * @property {string} [latestVersion] Newest non-yanked version in the index.
50
+ * @property {string[]} sources Which local files answered, for provenance.
51
+ */
52
+
53
+ /** Parsed index entries, keyed by crate name. A scan asks for the same crate
54
+ * repeatedly across workspace members, and these files hold every version ever
55
+ * published — serde's is over 300 lines. */
56
+ let _indexCache = new Map();
57
+
58
+ /** Registry directories discovered under the Cargo home, memoized per run. */
59
+ let _registryDirs;
60
+
61
+ /**
62
+ * Reset memoized state. Tests that point `CARGO_HOME` at a fixture need the
63
+ * next call to look again rather than reuse the previous run's answer.
64
+ *
65
+ * @returns {void}
66
+ */
67
+ export function resetCargoCacheState() {
68
+ _indexCache = new Map();
69
+ _registryDirs = undefined;
70
+ }
71
+
72
+ /**
73
+ * Whether the local registry should be consulted before crates.io.
74
+ *
75
+ * @returns {boolean} false when `CARGO_METADATA_SOURCE=registry` opts out.
76
+ */
77
+ export function localCargoMetadataEnabled() {
78
+ return (
79
+ readEnvironmentVariable("CARGO_METADATA_SOURCE")?.toLowerCase() !==
80
+ "registry"
81
+ );
82
+ }
83
+
84
+ /**
85
+ * Resolve the Cargo registry root.
86
+ *
87
+ * @returns {string} Absolute path to `$CARGO_HOME/registry`.
88
+ */
89
+ export function getCargoRegistryDir() {
90
+ return resolve(
91
+ readEnvironmentVariable("CARGO_HOME") || join(homedir(), ".cargo"),
92
+ "registry",
93
+ );
94
+ }
95
+
96
+ /**
97
+ * List the per-registry directories under `src` and `index`.
98
+ *
99
+ * Each configured registry gets its own directory named after a hash of its
100
+ * URL (`index.crates.io-1949cf8c6b5b557f`). A machine that also pulls from a
101
+ * private registry has more than one, and the crate being looked up may live
102
+ * in any of them, so all are searched.
103
+ *
104
+ * @returns {{src: string[], index: string[]}} Absolute directory paths.
105
+ */
106
+ function registryDirs() {
107
+ if (_registryDirs) {
108
+ return _registryDirs;
109
+ }
110
+ const root = getCargoRegistryDir();
111
+ _registryDirs = { src: [], index: [] };
112
+ for (const kind of ["src", "index"]) {
113
+ const base = join(root, kind);
114
+ try {
115
+ for (const entry of readdirSync(base, { withFileTypes: true })) {
116
+ if (entry.isDirectory()) {
117
+ _registryDirs[kind].push(join(base, entry.name));
118
+ }
119
+ }
120
+ } catch {
121
+ // No Cargo home, or no registry of this kind: the caller falls back to
122
+ // the network.
123
+ }
124
+ }
125
+ return _registryDirs;
126
+ }
127
+
128
+ /**
129
+ * Read everything the local registry knows about one crate version.
130
+ *
131
+ * @param {string} name Crate name.
132
+ * @param {string} version Exact version.
133
+ * @returns {CargoCacheMetadata|undefined} Metadata, or undefined when neither
134
+ * local source has this crate.
135
+ */
136
+ export function readCargoCacheMetadata(name, version) {
137
+ if (!name || !version) {
138
+ return undefined;
139
+ }
140
+ const sources = [];
141
+ const result = { sources };
142
+ const manifest = readExtractedManifest(name, version);
143
+ if (manifest) {
144
+ Object.assign(result, manifest.values);
145
+ sources.push(manifest.source);
146
+ }
147
+ const index = readIndexEntry(name, version);
148
+ if (index) {
149
+ // The manifest is authoritative for the fields both carry, since it is the
150
+ // file Cargo itself compiles against; the index adds what it alone knows.
151
+ for (const [key, value] of Object.entries(index.values)) {
152
+ if (result[key] === undefined) {
153
+ result[key] = value;
154
+ }
155
+ }
156
+ sources.push(index.source);
157
+ }
158
+ return sources.length ? result : undefined;
159
+ }
160
+
161
+ /**
162
+ * Read the normalised `Cargo.toml` of an extracted crate.
163
+ *
164
+ * @param {string} name Crate name.
165
+ * @param {string} version Exact version.
166
+ * @returns {{values: Object, source: string}|undefined}
167
+ */
168
+ function readExtractedManifest(name, version) {
169
+ for (const dir of registryDirs().src) {
170
+ const manifestFile = join(dir, `${name}-${version}`, "Cargo.toml");
171
+ if (!safeExistsSync(manifestFile)) {
172
+ continue;
173
+ }
174
+ let parsed;
175
+ try {
176
+ parsed = toml.parse(readFileSync(manifestFile, "utf-8"));
177
+ } catch {
178
+ // A manifest that does not parse is not worth a warning: the network
179
+ // path still has the answer.
180
+ continue;
181
+ }
182
+ const pkg = parsed?.package;
183
+ if (!pkg || typeof pkg !== "object") {
184
+ continue;
185
+ }
186
+ const values = {};
187
+ assignString(values, "description", pkg.description);
188
+ assignString(values, "repository", pkg.repository);
189
+ assignString(values, "homepage", pkg.homepage);
190
+ assignString(values, "rustVersion", pkg["rust-version"]);
191
+ // A crate may carry `license` or, for a non-SPDX license, `license-file`.
192
+ // Only the expression is usable as a component license.
193
+ assignString(values, "license", pkg.license);
194
+ if (parsed.features && typeof parsed.features === "object") {
195
+ values.features = parsed.features;
196
+ }
197
+ return { values, source: manifestFile };
198
+ }
199
+ return undefined;
200
+ }
201
+
202
+ /**
203
+ * Read one version's entry from the sparse index cache.
204
+ *
205
+ * The cache file is a small binary envelope: a version byte, the index format
206
+ * version, then NUL-separated fields holding the registry revision followed by
207
+ * alternating version strings and JSON blobs. Rather than decode the header,
208
+ * every NUL-separated chunk that parses as JSON carrying a `vers` field is
209
+ * taken, which keeps working if Cargo revises the envelope.
210
+ *
211
+ * @param {string} name Crate name.
212
+ * @param {string} version Exact version.
213
+ * @returns {{values: Object, source: string}|undefined}
214
+ */
215
+ function readIndexEntry(name, version) {
216
+ const entries = indexEntriesFor(name);
217
+ if (!entries) {
218
+ return undefined;
219
+ }
220
+ const match = entries.byVersion.get(version);
221
+ if (!match) {
222
+ return undefined;
223
+ }
224
+ const values = {};
225
+ assignString(values, "checksum", match.cksum);
226
+ if (match.features && typeof match.features === "object") {
227
+ values.features = match.features;
228
+ }
229
+ if (typeof match.yanked === "boolean") {
230
+ values.yanked = match.yanked;
231
+ }
232
+ assignString(values, "publishTime", match.pubtime);
233
+ assignString(values, "rustVersion", match.rust_version);
234
+ assignString(values, "latestVersion", entries.latestVersion);
235
+ return { values, source: entries.source };
236
+ }
237
+
238
+ /**
239
+ * Parse and memoize the index cache file for one crate.
240
+ *
241
+ * @param {string} name Crate name.
242
+ * @returns {{byVersion: Map<string, Object>, latestVersion: string|undefined, source: string}|undefined}
243
+ */
244
+ function indexEntriesFor(name) {
245
+ if (_indexCache.has(name)) {
246
+ return _indexCache.get(name);
247
+ }
248
+ const parsed = parseIndexCacheFile(name);
249
+ _indexCache.set(name, parsed);
250
+ return parsed;
251
+ }
252
+
253
+ /**
254
+ * Locate and parse a crate's index cache file.
255
+ *
256
+ * @param {string} name Crate name.
257
+ * @returns {{byVersion: Map<string, Object>, latestVersion: string|undefined, source: string}|undefined}
258
+ */
259
+ function parseIndexCacheFile(name) {
260
+ for (const dir of registryDirs().index) {
261
+ const path = indexCachePath(dir, name);
262
+ if (!path) {
263
+ continue;
264
+ }
265
+ let raw;
266
+ try {
267
+ if (!statSync(path).isFile()) {
268
+ continue;
269
+ }
270
+ raw = readFileSync(path);
271
+ } catch {
272
+ continue;
273
+ }
274
+ const byVersion = new Map();
275
+ for (const chunk of raw.toString("utf-8").split("\u0000")) {
276
+ if (!chunk.startsWith("{")) {
277
+ continue;
278
+ }
279
+ let entry;
280
+ try {
281
+ entry = JSON.parse(chunk);
282
+ } catch {
283
+ continue;
284
+ }
285
+ if (typeof entry?.vers === "string" && entry.vers) {
286
+ byVersion.set(entry.vers, entry);
287
+ }
288
+ }
289
+ if (!byVersion.size) {
290
+ continue;
291
+ }
292
+ return {
293
+ byVersion,
294
+ latestVersion: newestVersion(byVersion),
295
+ source: path,
296
+ };
297
+ }
298
+ return undefined;
299
+ }
300
+
301
+ /**
302
+ * Build the `.cache` path Cargo uses for a crate name.
303
+ *
304
+ * Names are bucketed by length: one character goes under `1/`, two under `2/`,
305
+ * three under `3/<first letter>/`, and four or more under the first two
306
+ * characters then the next two.
307
+ *
308
+ * @param {string} indexDir The registry's index directory.
309
+ * @param {string} name Crate name.
310
+ * @returns {string|undefined} Absolute path, or undefined for an unusable name.
311
+ */
312
+ function indexCachePath(indexDir, name) {
313
+ const lower = name.toLowerCase();
314
+ if (!lower.length) {
315
+ return undefined;
316
+ }
317
+ const cacheDir = join(indexDir, ".cache");
318
+ if (lower.length === 1) {
319
+ return join(cacheDir, "1", lower);
320
+ }
321
+ if (lower.length === 2) {
322
+ return join(cacheDir, "2", lower);
323
+ }
324
+ if (lower.length === 3) {
325
+ return join(cacheDir, "3", lower.slice(0, 1), lower);
326
+ }
327
+ return join(cacheDir, lower.slice(0, 2), lower.slice(2, 4), lower);
328
+ }
329
+
330
+ /**
331
+ * Pick the newest non-yanked version from an index file.
332
+ *
333
+ * The index is written in publish order, so the last entry that was not yanked
334
+ * is the version crates.io would call newest. No semver comparison is done:
335
+ * a patch release for an older line is published after the newer line and
336
+ * would win a naive ordering, but it is also what the registry reports as most
337
+ * recent, so publish order is the honest reading.
338
+ *
339
+ * @param {Map<string, Object>} byVersion Parsed entries in file order.
340
+ * @returns {string|undefined} Newest version.
341
+ */
342
+ function newestVersion(byVersion) {
343
+ let newest;
344
+ for (const [version, entry] of byVersion) {
345
+ if (entry?.yanked !== true) {
346
+ newest = version;
347
+ }
348
+ }
349
+ return newest;
350
+ }
351
+
352
+ /**
353
+ * Copy a value onto the target when it is a usable string.
354
+ *
355
+ * @param {Object} target Accumulator.
356
+ * @param {string} key Field name.
357
+ * @param {*} value Candidate value.
358
+ * @returns {void}
359
+ */
360
+ function assignString(target, key, value) {
361
+ if (typeof value === "string" && value.length) {
362
+ target[key] = value;
363
+ }
364
+ }
@@ -51,16 +51,24 @@ export async function parseCaxaMetadata(mfile) {
51
51
  value: "true",
52
52
  });
53
53
  }
54
+ // The binary-analysis method names the executable the component was found
55
+ // in, which only a parent component can supply. Any `*metadata.json` in
56
+ // the scanned tree reaches this parser, so a document with components and
57
+ // no parent is ordinary input rather than a broken caxa build.
58
+ const methods = [];
59
+ if (parentComponent?.name) {
60
+ methods.push({
61
+ technique: "binary-analysis",
62
+ confidence: 1,
63
+ value: parentComponent.name,
64
+ });
65
+ }
54
66
  comp.evidence = {
55
67
  identity: {
56
68
  field: "purl",
57
69
  confidence: 1,
58
70
  methods: [
59
- {
60
- technique: "binary-analysis",
61
- confidence: 1,
62
- value: parentComponent.name,
63
- },
71
+ ...methods,
64
72
  {
65
73
  technique: "manifest-analysis",
66
74
  confidence: 1,
@@ -24,7 +24,11 @@ import { CPP_STD_MODULES } from "../core/state.js";
24
24
  import { findAppModules } from "../inventory/atomUtils.js";
25
25
  import { getOSPackageForFile } from "../inventory/osPackageResolver.js";
26
26
  import { locateGenericPackage } from "../inventory/purl.js";
27
- import { parseCmakeLikeFile, parseCUsageSlice } from "./parsers-misc.js";
27
+ import {
28
+ parseCmakeLikeFile,
29
+ parseCUsageSlice,
30
+ parseXmakeRequiresLock,
31
+ } from "./parsers-misc.js";
28
32
 
29
33
  /**
30
34
  * Method to find c/c++ modules by collecting usages with atom
@@ -72,11 +76,22 @@ export function getCppModules(src, options, osPkgsList, epkgList) {
72
76
  if (vcPkgData.homepage) {
73
77
  parentComponent.homepage = { url: vcPkgData.homepage };
74
78
  }
79
+ // The builtin baseline is the vcpkg commit that pins every port version,
80
+ // so it identifies the version set the manifest resolves against.
81
+ if (typeof vcPkgData["builtin-baseline"] === "string") {
82
+ parentComponent.properties = [
83
+ {
84
+ name: "cdx:vcpkg:baseline",
85
+ value: vcPkgData["builtin-baseline"],
86
+ },
87
+ ];
88
+ }
75
89
  // Are there any dependencies declared in vcpkg.json
76
90
  if (vcPkgData.dependencies && Array.isArray(vcPkgData.dependencies)) {
77
91
  for (const avcdep of vcPkgData.dependencies) {
78
92
  let avcpkgName;
79
93
  let scope;
94
+ let vcpkgFeatures;
80
95
  if (typeof avcdep === "string" || avcdep instanceof String) {
81
96
  avcpkgName = avcdep;
82
97
  } else if (Object.keys(avcdep).length && avcdep.name) {
@@ -84,6 +99,9 @@ export function getCppModules(src, options, osPkgsList, epkgList) {
84
99
  if (avcdep.host) {
85
100
  scope = "optional";
86
101
  }
102
+ vcpkgFeatures = Array.isArray(avcdep.features)
103
+ ? avcdep.features.join(",")
104
+ : undefined;
87
105
  }
88
106
  // Is this a dependency we haven't seen before including the all lower and upper case version?
89
107
  if (
@@ -106,6 +124,12 @@ export function getCppModules(src, options, osPkgsList, epkgList) {
106
124
  purl: pkgPurl,
107
125
  scope,
108
126
  "bom-ref": decodeURIComponent(pkgPurl),
127
+ properties: [
128
+ { name: "cdx:vcpkg:declared", value: "true" },
129
+ ...(vcpkgFeatures
130
+ ? [{ name: "cdx:vcpkg:features", value: vcpkgFeatures }]
131
+ : []),
132
+ ],
109
133
  evidence: {
110
134
  identity: {
111
135
  field: "purl",
@@ -129,7 +153,21 @@ export function getCppModules(src, options, osPkgsList, epkgList) {
129
153
  }
130
154
  }
131
155
  } // if
132
- } else if (safeExistsSync(join(src, "CMakeLists.txt"))) {
156
+ }
157
+ // xmake locks its resolved requirements in `xmake-requires.lock`; the
158
+ // entries are additions to whatever other evidence collectors found.
159
+ const xmakeLockFile = join(src, "xmake-requires.lock");
160
+ if (safeExistsSync(xmakeLockFile)) {
161
+ const xmakePkgs = parseXmakeRequiresLock(xmakeLockFile);
162
+ for (const apkg of xmakePkgs) {
163
+ if (!pkgAddedMap[apkg.name]) {
164
+ pkgList.push(apkg);
165
+ dependsOn.add(apkg["bom-ref"]);
166
+ pkgAddedMap[apkg.name] = true;
167
+ }
168
+ }
169
+ }
170
+ if (!parentComponent && safeExistsSync(join(src, "CMakeLists.txt"))) {
133
171
  const retMap = parseCmakeLikeFile(join(src, "CMakeLists.txt"), pkgType);
134
172
  if (retMap.parentComponent && Object.keys(retMap.parentComponent).length) {
135
173
  parentComponent = retMap.parentComponent;