@cdxgen/cdxgen 13.6.0 → 13.7.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 (264) hide show
  1. package/README.md +15 -8
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +256 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +25 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +18 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/pqc-migration.json +239 -0
  15. package/data/remediations.json +52 -0
  16. package/data/rules/cbom-security.yaml +4 -3
  17. package/data/rules/package-integrity.yaml +28 -0
  18. package/lib/audit/index.js +25 -3
  19. package/lib/cli/bomAssembly.js +80 -1
  20. package/lib/cli/cliOptions.js +121 -17
  21. package/lib/cli/index.js +609 -898
  22. package/lib/cli/jsBom.js +3 -0
  23. package/lib/cli/jvmBom.js +365 -40
  24. package/lib/cli/managedBom.js +19 -3
  25. package/lib/cli/nativeBom.js +10 -0
  26. package/lib/core/env.js +26 -0
  27. package/lib/core/fs.js +126 -3
  28. package/lib/core/globPatterns.js +218 -0
  29. package/lib/core/processTree.js +99 -12
  30. package/lib/core/projectConfig.js +332 -25
  31. package/lib/core/toolRegistry.js +15 -1
  32. package/lib/ecosystems/denoutils.js +2 -2
  33. package/lib/ecosystems/golem.js +3 -8
  34. package/lib/ecosystems/gradleutils.js +1082 -6
  35. package/lib/ecosystems/jsEvidence.js +90 -38
  36. package/lib/ecosystems/kosi.js +3 -5
  37. package/lib/ecosystems/npmVersionResolution.js +376 -0
  38. package/lib/ecosystems/parsers-python.js +89 -28
  39. package/lib/ecosystems/parsers-rust.js +117 -15
  40. package/lib/ecosystems/remote/huggingface.js +22 -0
  41. package/lib/ecosystems/rusi.js +2 -4
  42. package/lib/ecosystems/scalasem.js +2 -1
  43. package/lib/evinser/evinser.js +196 -34
  44. package/lib/evinser/scalasem.js +5 -1
  45. package/lib/helpers/agenticToolutils.js +112 -1
  46. package/lib/helpers/bomSigner.js +43 -9
  47. package/lib/helpers/jssSigner.js +1276 -0
  48. package/lib/inventory/agentBlueprint.js +193 -0
  49. package/lib/inventory/agentToolNames.js +145 -0
  50. package/lib/inventory/aiCollector.js +153 -7
  51. package/lib/inventory/aiInventory.js +44 -8
  52. package/lib/inventory/aiProviders.js +324 -0
  53. package/lib/inventory/bomUtils.js +8 -1
  54. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  55. package/lib/inventory/citations.js +31 -2
  56. package/lib/inventory/communityAiConfigParser.js +978 -80
  57. package/lib/inventory/cyclonedx20.js +152 -0
  58. package/lib/inventory/cyclonedx20Common.js +286 -0
  59. package/lib/inventory/cyclonedx20Derived.js +159 -0
  60. package/lib/inventory/cyclonedx20Parties.js +552 -0
  61. package/lib/inventory/cyclonedx20Read.js +768 -0
  62. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  63. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  64. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  65. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  66. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  67. package/lib/inventory/cyclonedx20Render.js +766 -0
  68. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  69. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  70. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  71. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  72. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  73. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  74. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  75. package/lib/inventory/cyclonedx20Tables.js +594 -0
  76. package/lib/inventory/deps.js +106 -13
  77. package/lib/inventory/dosai.js +236 -1
  78. package/lib/inventory/dosaiParsers.js +3 -6
  79. package/lib/inventory/dotnetRuntime.js +164 -14
  80. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  81. package/lib/inventory/envcontext.js +132 -38
  82. package/lib/inventory/evidenceUtils.js +2 -1
  83. package/lib/inventory/formulationParsers.js +95 -13
  84. package/lib/inventory/hbom.js +45 -6
  85. package/lib/inventory/hbomLoader.js +166 -0
  86. package/lib/inventory/mcpConfigParser.js +5 -0
  87. package/lib/inventory/occurrenceKey.js +90 -0
  88. package/lib/inventory/pqcMigration.js +426 -0
  89. package/lib/inventory/rustFormulationParser.js +21 -10
  90. package/lib/inventory/source.js +65 -6
  91. package/lib/parsers/huggingfaceManifest.js +9 -1
  92. package/lib/parsers/huggingfaceUtils.js +18 -0
  93. package/lib/parsers/kiroPermissions.js +166 -0
  94. package/lib/server/server.js +36 -34
  95. package/lib/stages/postgen/postgen.js +41 -1
  96. package/lib/stages/postgen/specVersionCompat.js +76 -0
  97. package/lib/validator/bomValidator.js +8 -1
  98. package/lib/validator/complianceEngine.js +30 -3
  99. package/lib/validator/complianceRules.js +1416 -4
  100. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  101. package/lib/validator/index.js +48 -7
  102. package/lib/validator/reporters/json.js +6 -0
  103. package/package.json +9 -9
  104. package/types/bin/repl.d.ts.map +1 -1
  105. package/types/lib/audit/index.d.ts +6 -2
  106. package/types/lib/audit/index.d.ts.map +1 -1
  107. package/types/lib/cli/bomAssembly.d.ts +16 -0
  108. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  109. package/types/lib/cli/cliOptions.d.ts +6 -0
  110. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  111. package/types/lib/cli/index.d.ts.map +1 -1
  112. package/types/lib/cli/jsBom.d.ts +2 -1
  113. package/types/lib/cli/jsBom.d.ts.map +1 -1
  114. package/types/lib/cli/jvmBom.d.ts +15 -0
  115. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  116. package/types/lib/cli/managedBom.d.ts.map +1 -1
  117. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  118. package/types/lib/core/boundedFile.d.ts +30 -0
  119. package/types/lib/core/boundedFile.d.ts.map +1 -1
  120. package/types/lib/core/env.d.ts +10 -0
  121. package/types/lib/core/env.d.ts.map +1 -1
  122. package/types/lib/core/fs.d.ts +4 -0
  123. package/types/lib/core/fs.d.ts.map +1 -1
  124. package/types/lib/core/globPatterns.d.ts +70 -0
  125. package/types/lib/core/globPatterns.d.ts.map +1 -0
  126. package/types/lib/core/paths.d.ts +9 -0
  127. package/types/lib/core/paths.d.ts.map +1 -1
  128. package/types/lib/core/processTree.d.ts +16 -5
  129. package/types/lib/core/processTree.d.ts.map +1 -1
  130. package/types/lib/core/projectConfig.d.ts +47 -8
  131. package/types/lib/core/projectConfig.d.ts.map +1 -1
  132. package/types/lib/core/toolRegistry.d.ts.map +1 -1
  133. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  134. package/types/lib/ecosystems/gradleutils.d.ts +248 -8
  135. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  136. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  137. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  139. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  140. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  141. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  142. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  143. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  144. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  145. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  146. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  147. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  148. package/types/lib/evinser/evinser.d.ts +61 -9
  149. package/types/lib/evinser/evinser.d.ts.map +1 -1
  150. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  151. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  152. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  153. package/types/lib/helpers/bomSigner.d.ts +20 -3
  154. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  155. package/types/lib/helpers/jssSigner.d.ts +288 -0
  156. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  157. package/types/lib/helpers/vsixutils.d.ts +303 -19
  158. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  159. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  160. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  161. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  162. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  163. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  164. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  165. package/types/lib/inventory/aiProviders.d.ts +85 -0
  166. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  167. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  168. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  169. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  170. package/types/lib/inventory/citations.d.ts +5 -0
  171. package/types/lib/inventory/citations.d.ts.map +1 -1
  172. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  173. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  174. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  175. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  176. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  177. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  178. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  179. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  180. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  181. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  182. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  183. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  184. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  185. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  186. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  187. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  188. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  189. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  190. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  191. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  192. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  193. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  194. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  195. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  196. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  197. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  198. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  199. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  200. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  201. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  202. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  203. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  204. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  205. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  206. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  207. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  208. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  209. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  210. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  211. package/types/lib/inventory/deps.d.ts +35 -2
  212. package/types/lib/inventory/deps.d.ts.map +1 -1
  213. package/types/lib/inventory/dosai.d.ts +19 -0
  214. package/types/lib/inventory/dosai.d.ts.map +1 -1
  215. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  216. package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
  217. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
  218. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  219. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  220. package/types/lib/inventory/envcontext.d.ts +22 -6
  221. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  222. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  224. package/types/lib/inventory/hbom.d.ts +11 -0
  225. package/types/lib/inventory/hbom.d.ts.map +1 -1
  226. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  227. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  228. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  229. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  230. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  231. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  232. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  233. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  234. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  235. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  236. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  237. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  238. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  239. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  240. package/types/lib/inventory/source.d.ts +15 -0
  241. package/types/lib/inventory/source.d.ts.map +1 -1
  242. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  243. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  244. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  245. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  246. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  247. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  248. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  249. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  250. package/types/lib/server/server.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  252. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  253. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  254. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  255. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  256. package/types/lib/validator/bomValidator.d.ts +5 -1
  257. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  258. package/types/lib/validator/complianceEngine.d.ts +4 -4
  259. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  260. package/types/lib/validator/complianceRules.d.ts +14 -2
  261. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  262. package/types/lib/validator/index.d.ts +10 -0
  263. package/types/lib/validator/index.d.ts.map +1 -1
  264. package/types/lib/validator/reporters/json.d.ts.map +1 -1
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Shared state and the field-mapping engine of the CycloneDX 2.0 reader.
3
+ *
4
+ * Every 2.0 object kind the reader converts has a frozen field spec: the
5
+ * allowlist of the members the 1.x model carries, with the 1.x name or the
6
+ * converter for each. `reverseFields` walks the object's own members in
7
+ * document order and reports every member the spec does not list as a loss,
8
+ * so a field the reader has no mapping for can never vanish silently.
9
+ *
10
+ * Loss pointers are RFC 6901 JSON pointers into the 2.0 input, with array
11
+ * indexes and `~`/`/` escaped.
12
+ */
13
+
14
+ import { clone, isObjectRecord, shadowKey } from "./cyclonedx20Common.js";
15
+
16
+ // Marks a 1.x-shaped model that already went through the 2.0 reader or the
17
+ // 2.0 compatibility pass, so loadBomForAnalysis never reads it again as a 2.0
18
+ // document. Symbol.for keeps the mark recognisable across module instances.
19
+ const INTERNAL_MODEL_MARK = Symbol.for("cdxgen.cyclonedx20.internalModel");
20
+
21
+ /**
22
+ * Mark `bom` as cdxgen's 1.x-shaped internal model. The mark is a
23
+ * non-enumerable symbol property, so it never reaches JSON output, and a
24
+ * copy made with JSON or structuredClone does not carry it.
25
+ *
26
+ * @param {object} bom The internal model
27
+ * @returns {object} The same object
28
+ */
29
+ export function markInternalModel(bom) {
30
+ if (isObjectRecord(bom) && !Object.hasOwn(bom, INTERNAL_MODEL_MARK)) {
31
+ Object.defineProperty(bom, INTERNAL_MODEL_MARK, {
32
+ value: true,
33
+ enumerable: false,
34
+ });
35
+ }
36
+ return bom;
37
+ }
38
+
39
+ /**
40
+ * Whether `bom` carries the internal model mark of markInternalModel.
41
+ *
42
+ * @param {object} bom Any value
43
+ * @returns {boolean} True for a marked internal model
44
+ */
45
+ export function isMarkedInternalModel(bom) {
46
+ return isObjectRecord(bom) && bom[INTERNAL_MODEL_MARK] === true;
47
+ }
48
+
49
+ // Nested objects the reader follows. A document nested deeper (components
50
+ // inside components, for example) is cut at this depth, with a loss, so the
51
+ // recursive converters stay well inside the call stack.
52
+ export const MAX_READ_DEPTH = 256;
53
+
54
+ /**
55
+ * Copy a parsed JSON value, leaving out every object nested deeper than
56
+ * `maxDepth` objects and recording its JSON pointer. Arrays do not count
57
+ * towards the depth, so every object entry of one array sits at the same
58
+ * depth and is cut with the rest; an array left empty by the cut is left out
59
+ * of its parent object too. Iterative, so no input depth can exhaust the
60
+ * call stack.
61
+ *
62
+ * @param {*} value Parsed JSON value
63
+ * @param {number} [maxDepth] Deepest object level kept
64
+ * @returns {{value: *, truncated: string[]}} The copy and the pointers of the
65
+ * objects that were cut
66
+ */
67
+ export function cloneWithDepthLimit(value, maxDepth = MAX_READ_DEPTH) {
68
+ const truncated = [];
69
+ // A parsed "__proto__" member is an own property; plain assignment would
70
+ // set the prototype instead.
71
+ const assign = (target, key, child) => {
72
+ if (Array.isArray(target)) {
73
+ target.push(child);
74
+ return;
75
+ }
76
+ Object.defineProperty(target, key, {
77
+ value: child,
78
+ enumerable: true,
79
+ writable: true,
80
+ configurable: true,
81
+ });
82
+ };
83
+ const emptyCopy = (node) => (Array.isArray(node) ? [] : {});
84
+ const isContainer = (node) => node !== null && typeof node === "object";
85
+ const root = isContainer(value) ? emptyCopy(value) : value;
86
+ // Arrays that lost entries, with the object member holding them.
87
+ const cutArrays = [];
88
+ const stack = isContainer(value)
89
+ ? [{ source: value, target: root, depth: 1, pointer: "" }]
90
+ : [];
91
+ while (stack.length) {
92
+ const { source, target, depth, pointer, parent, parentKey } = stack.pop();
93
+ const keys = Array.isArray(source) ? source.keys() : Object.keys(source);
94
+ let cut = false;
95
+ for (const key of keys) {
96
+ const child = source[key];
97
+ if (!isContainer(child)) {
98
+ assign(target, key, child);
99
+ continue;
100
+ }
101
+ const childPointer = jsonPointer(pointer, key);
102
+ const childDepth = Array.isArray(child) ? depth : depth + 1;
103
+ if (childDepth > maxDepth) {
104
+ truncated.push(childPointer);
105
+ cut = true;
106
+ continue;
107
+ }
108
+ const copy = emptyCopy(child);
109
+ assign(target, key, copy);
110
+ stack.push({
111
+ source: child,
112
+ target: copy,
113
+ depth: childDepth,
114
+ pointer: childPointer,
115
+ parent: target,
116
+ parentKey: key,
117
+ });
118
+ }
119
+ if (cut && Array.isArray(target)) {
120
+ cutArrays.push({ target, parent, parentKey });
121
+ }
122
+ }
123
+ for (const { target, parent, parentKey } of cutArrays) {
124
+ if (!target.length && parent && !Array.isArray(parent)) {
125
+ delete parent[parentKey];
126
+ }
127
+ }
128
+ return { value: root, truncated };
129
+ }
130
+
131
+ /**
132
+ * A fresh reader context for one 2.0 document.
133
+ *
134
+ * @param {object} doc The (private, cloned) 2.0 document
135
+ * @returns {object} Reader context
136
+ */
137
+ export function createReadContext(doc) {
138
+ return {
139
+ doc,
140
+ losses: [],
141
+ shadows: {},
142
+ keptComponents: [],
143
+ keptIdentities: [],
144
+ // What renderCycloneDx20 attributes cdxgen's identity claims with; set
145
+ // from the read metadata (identityClaimContext).
146
+ claims: {},
147
+ shadowSuspended: false,
148
+ partyIndex: indexParties(doc),
149
+ licenseIndex: indexLicenses(doc),
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Map refId → {entry, pointer} for every party declared anywhere in the
155
+ * document (definitions.parties first, then inline `parties` lists), so a
156
+ * partyReference resolves wherever its party is declared.
157
+ */
158
+ function indexParties(doc) {
159
+ const index = new Map();
160
+ const add = (entry, pointer) => {
161
+ if (
162
+ isObjectRecord(entry) &&
163
+ typeof entry.refId === "string" &&
164
+ entry.ref === undefined &&
165
+ !index.has(entry.refId)
166
+ ) {
167
+ index.set(entry.refId, { entry, pointer });
168
+ }
169
+ };
170
+ (Array.isArray(doc.definitions?.parties)
171
+ ? doc.definitions.parties
172
+ : []
173
+ ).forEach((entry, i) => {
174
+ add(entry, jsonPointer("/definitions/parties", i));
175
+ });
176
+ const visit = (node, pointer) => {
177
+ if (Array.isArray(node)) {
178
+ node.forEach((child, i) => {
179
+ visit(child, jsonPointer(pointer, i));
180
+ });
181
+ return;
182
+ }
183
+ if (!isObjectRecord(node)) {
184
+ return;
185
+ }
186
+ for (const [key, value] of Object.entries(node)) {
187
+ const childPointer = jsonPointer(pointer, key);
188
+ if (key === "parties" && Array.isArray(value)) {
189
+ value.forEach((entry, i) => {
190
+ add(entry, jsonPointer(childPointer, i));
191
+ });
192
+ }
193
+ visit(value, childPointer);
194
+ }
195
+ };
196
+ visit(doc, "");
197
+ return index;
198
+ }
199
+
200
+ /**
201
+ * Map refId → {entry, pointer} for definitions.licenses; a license
202
+ * declaration's refId sits on the expression or on the license object.
203
+ */
204
+ function indexLicenses(doc) {
205
+ const index = new Map();
206
+ const licenses = Array.isArray(doc.definitions?.licenses)
207
+ ? doc.definitions.licenses
208
+ : [];
209
+ licenses.forEach((entry, i) => {
210
+ const refId = entry?.refId ?? entry?.license?.refId;
211
+ if (typeof refId === "string" && !index.has(refId)) {
212
+ index.set(refId, {
213
+ entry,
214
+ pointer: jsonPointer("/definitions/licenses", i),
215
+ });
216
+ }
217
+ });
218
+ return index;
219
+ }
220
+
221
+ /**
222
+ * Append reference tokens to a JSON pointer (RFC 6901): `~` becomes `~0` and
223
+ * `/` becomes `~1`; array indexes are plain numbers.
224
+ *
225
+ * @param {string} base Pointer so far ("" is the whole document)
226
+ * @param {...(string|number)} tokens Member names or array indexes
227
+ * @returns {string} The extended pointer
228
+ */
229
+ export function jsonPointer(base, ...tokens) {
230
+ let pointer = base;
231
+ for (const token of tokens) {
232
+ pointer += `/${String(token).replaceAll("~", "~0").replaceAll("/", "~1")}`;
233
+ }
234
+ return pointer;
235
+ }
236
+
237
+ /** Record one loss. */
238
+ export function addLoss(ctx, pointer, reason) {
239
+ ctx.losses.push({ pointer, reason });
240
+ }
241
+
242
+ /**
243
+ * Report every defined member of `source` that `carried` does not list.
244
+ *
245
+ * @param {object} ctx Reader context
246
+ * @param {string} kind 2.0 object kind, used in the reason
247
+ * @param {object} source 2.0 object
248
+ * @param {string} pointer JSON pointer of `source`
249
+ * @param {Set<string>|object} carried Member names (a Set or spec keys)
250
+ */
251
+ export function reportUnmapped(ctx, kind, source, pointer, carried) {
252
+ const has = (key) =>
253
+ carried instanceof Set ? carried.has(key) : Object.hasOwn(carried, key);
254
+ for (const [key, value] of Object.entries(source)) {
255
+ if (value !== undefined && !has(key)) {
256
+ addLoss(
257
+ ctx,
258
+ jsonPointer(pointer, key),
259
+ `${kind}.${key} has no 1.x field`,
260
+ );
261
+ }
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Convert one 2.0 object with a field spec. A spec maps each carried 2.0
267
+ * member to `true` (copied under the same name), a string (copied under that
268
+ * 1.x name) or `{to?, spread?, convert}` where
269
+ * `convert(ctx, value, pointer, source)` returns the 1.x value or undefined
270
+ * (and reports its own losses); a `spread` rule returns an object of 1.x
271
+ * members to merge (one 2.0 member feeding several 1.x fields). Members
272
+ * the spec does not list are reported through reportUnmapped.
273
+ *
274
+ * @param {object} ctx Reader context
275
+ * @param {string} kind 2.0 object kind
276
+ * @param {object} source 2.0 object
277
+ * @param {string} pointer JSON pointer of `source`
278
+ * @param {object} spec Frozen field spec
279
+ * @returns {object} The 1.x object
280
+ */
281
+ export function reverseFields(ctx, kind, source, pointer, spec) {
282
+ const out = {};
283
+ for (const [key, value] of Object.entries(source)) {
284
+ if (value === undefined || !Object.hasOwn(spec, key)) {
285
+ continue;
286
+ }
287
+ const rule = spec[key];
288
+ if (rule === true) {
289
+ out[key] = clone(value);
290
+ } else if (typeof rule === "string") {
291
+ out[rule] = clone(value);
292
+ } else {
293
+ const converted = rule.convert(
294
+ ctx,
295
+ value,
296
+ jsonPointer(pointer, key),
297
+ source,
298
+ );
299
+ if (converted !== undefined && rule.spread) {
300
+ Object.assign(out, converted);
301
+ } else if (converted !== undefined) {
302
+ out[rule.to ?? key] = converted;
303
+ }
304
+ }
305
+ }
306
+ reportUnmapped(ctx, kind, source, pointer, spec);
307
+ return out;
308
+ }
309
+
310
+ /**
311
+ * Convert each entry of a 2.0 array with `convertEntry(ctx, entry, pointer)`,
312
+ * dropping entries that convert to undefined. A non-array is a loss.
313
+ */
314
+ export function reverseList(ctx, value, pointer, convertEntry) {
315
+ if (!Array.isArray(value)) {
316
+ addLoss(ctx, pointer, "expected an array");
317
+ return undefined;
318
+ }
319
+ const out = [];
320
+ value.forEach((entry, index) => {
321
+ const converted = convertEntry(ctx, entry, jsonPointer(pointer, index));
322
+ if (converted !== undefined) {
323
+ out.push(converted);
324
+ }
325
+ });
326
+ return out.length ? out : undefined;
327
+ }
328
+
329
+ /** A spec rule converting an array entry by entry. */
330
+ export function listOf(convertEntry, to) {
331
+ return Object.freeze({
332
+ to,
333
+ convert: (ctx, value, pointer) =>
334
+ reverseList(ctx, value, pointer, convertEntry),
335
+ });
336
+ }
337
+
338
+ /** A spec rule converting a nested object with its own spec. */
339
+ export function objectOf(kind, spec, to) {
340
+ return Object.freeze({
341
+ to,
342
+ convert: (ctx, value, pointer) =>
343
+ reverseObject(ctx, kind, value, pointer, spec),
344
+ });
345
+ }
346
+
347
+ /** reverseFields for a value that must be an object; a loss otherwise. */
348
+ export function reverseObject(ctx, kind, value, pointer, spec) {
349
+ if (!isObjectRecord(value)) {
350
+ addLoss(ctx, pointer, `${kind} is not an object`);
351
+ return undefined;
352
+ }
353
+ const out = reverseFields(ctx, kind, value, pointer, spec);
354
+ return Object.keys(out).length ? out : undefined;
355
+ }
356
+
357
+ /** Keep `original` for the 1.x value `internal` in namespace `kind`. */
358
+ export function registerShadow(ctx, kind, internal, original) {
359
+ registerShadowKey(ctx, kind, shadowKey(internal), original);
360
+ }
361
+
362
+ /** Keep `original` under a precomputed shadow key. */
363
+ export function registerShadowKey(ctx, kind, key, original) {
364
+ const byKind = (ctx.shadows[kind] ||= {});
365
+ (byKind[key] ||= []).push(original);
366
+ }
367
+
368
+ /**
369
+ * Run `read()` with component shadows suspended: for section entries that
370
+ * are restored whole, whose nested components the renderer never converts.
371
+ */
372
+ export function withoutComponentShadows(ctx, read) {
373
+ const previous = ctx.shadowSuspended;
374
+ ctx.shadowSuspended = true;
375
+ try {
376
+ return read();
377
+ } finally {
378
+ ctx.shadowSuspended = previous;
379
+ }
380
+ }