@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.
- package/README.md +15 -8
- package/bin/audit.js +7 -4
- package/bin/cdxgen.js +256 -37
- package/bin/convert.js +146 -22
- package/bin/evinse.js +25 -3
- package/bin/repl.js +47 -3
- package/bin/sign.js +211 -34
- package/bin/tracebom.js +18 -6
- package/bin/validate.js +18 -3
- package/bin/verify.js +113 -5
- package/data/behavior-taxonomy.schema.json +1491 -0
- package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
- package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
- package/data/pqc-migration.json +239 -0
- package/data/remediations.json +52 -0
- package/data/rules/cbom-security.yaml +4 -3
- package/data/rules/package-integrity.yaml +28 -0
- package/lib/audit/index.js +25 -3
- package/lib/cli/bomAssembly.js +80 -1
- package/lib/cli/cliOptions.js +121 -17
- package/lib/cli/index.js +609 -898
- package/lib/cli/jsBom.js +3 -0
- package/lib/cli/jvmBom.js +365 -40
- package/lib/cli/managedBom.js +19 -3
- package/lib/cli/nativeBom.js +10 -0
- package/lib/core/env.js +26 -0
- package/lib/core/fs.js +126 -3
- package/lib/core/globPatterns.js +218 -0
- package/lib/core/processTree.js +99 -12
- package/lib/core/projectConfig.js +332 -25
- package/lib/core/toolRegistry.js +15 -1
- package/lib/ecosystems/denoutils.js +2 -2
- package/lib/ecosystems/golem.js +3 -8
- package/lib/ecosystems/gradleutils.js +1082 -6
- package/lib/ecosystems/jsEvidence.js +90 -38
- package/lib/ecosystems/kosi.js +3 -5
- package/lib/ecosystems/npmVersionResolution.js +376 -0
- package/lib/ecosystems/parsers-python.js +89 -28
- package/lib/ecosystems/parsers-rust.js +117 -15
- package/lib/ecosystems/remote/huggingface.js +22 -0
- package/lib/ecosystems/rusi.js +2 -4
- package/lib/ecosystems/scalasem.js +2 -1
- package/lib/evinser/evinser.js +196 -34
- package/lib/evinser/scalasem.js +5 -1
- package/lib/helpers/agenticToolutils.js +112 -1
- package/lib/helpers/bomSigner.js +43 -9
- package/lib/helpers/jssSigner.js +1276 -0
- package/lib/inventory/agentBlueprint.js +193 -0
- package/lib/inventory/agentToolNames.js +145 -0
- package/lib/inventory/aiCollector.js +153 -7
- package/lib/inventory/aiInventory.js +44 -8
- package/lib/inventory/aiProviders.js +324 -0
- package/lib/inventory/bomUtils.js +8 -1
- package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
- package/lib/inventory/citations.js +31 -2
- package/lib/inventory/communityAiConfigParser.js +978 -80
- package/lib/inventory/cyclonedx20.js +152 -0
- package/lib/inventory/cyclonedx20Common.js +286 -0
- package/lib/inventory/cyclonedx20Derived.js +159 -0
- package/lib/inventory/cyclonedx20Parties.js +552 -0
- package/lib/inventory/cyclonedx20Read.js +768 -0
- package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
- package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
- package/lib/inventory/cyclonedx20ReadContext.js +380 -0
- package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
- package/lib/inventory/cyclonedx20ReadSections.js +599 -0
- package/lib/inventory/cyclonedx20Render.js +766 -0
- package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
- package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
- package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
- package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
- package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
- package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
- package/lib/inventory/cyclonedx20RenderSections.js +499 -0
- package/lib/inventory/cyclonedx20Tables.js +594 -0
- package/lib/inventory/deps.js +106 -13
- package/lib/inventory/dosai.js +236 -1
- package/lib/inventory/dosaiParsers.js +3 -6
- package/lib/inventory/dotnetRuntime.js +164 -14
- package/lib/inventory/ecosystemFormulationParsers.js +979 -0
- package/lib/inventory/envcontext.js +132 -38
- package/lib/inventory/evidenceUtils.js +2 -1
- package/lib/inventory/formulationParsers.js +95 -13
- package/lib/inventory/hbom.js +45 -6
- package/lib/inventory/hbomLoader.js +166 -0
- package/lib/inventory/mcpConfigParser.js +5 -0
- package/lib/inventory/occurrenceKey.js +90 -0
- package/lib/inventory/pqcMigration.js +426 -0
- package/lib/inventory/rustFormulationParser.js +21 -10
- package/lib/inventory/source.js +65 -6
- package/lib/parsers/huggingfaceManifest.js +9 -1
- package/lib/parsers/huggingfaceUtils.js +18 -0
- package/lib/parsers/kiroPermissions.js +166 -0
- package/lib/server/server.js +36 -34
- package/lib/stages/postgen/postgen.js +41 -1
- package/lib/stages/postgen/specVersionCompat.js +76 -0
- package/lib/validator/bomValidator.js +8 -1
- package/lib/validator/complianceEngine.js +30 -3
- package/lib/validator/complianceRules.js +1416 -4
- package/lib/validator/generated/validate-2.0.mjs +57554 -13851
- package/lib/validator/index.js +48 -7
- package/lib/validator/reporters/json.js +6 -0
- package/package.json +9 -9
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +6 -2
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +16 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +6 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +2 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +15 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +30 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +10 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +4 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/globPatterns.d.ts +70 -0
- package/types/lib/core/globPatterns.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +9 -0
- package/types/lib/core/paths.d.ts.map +1 -1
- package/types/lib/core/processTree.d.ts +16 -5
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts +47 -8
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +248 -8
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
- package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts +58 -16
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +61 -9
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/scalasem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +53 -19
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +20 -3
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/helpers/jssSigner.d.ts +288 -0
- package/types/lib/helpers/jssSigner.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +303 -19
- package/types/lib/helpers/vsixutils.d.ts.map +1 -1
- package/types/lib/inventory/agentBlueprint.d.ts +70 -0
- package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
- package/types/lib/inventory/agentToolNames.d.ts +51 -0
- package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/aiProviders.d.ts +85 -0
- package/types/lib/inventory/aiProviders.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -1
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +5 -0
- package/types/lib/inventory/citations.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/cyclonedx20.d.ts +99 -0
- package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +35 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +19 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +22 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts +11 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomLoader.d.ts +54 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/occurrenceKey.d.ts +52 -0
- package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
- package/types/lib/inventory/pqcMigration.d.ts +84 -0
- package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
- package/types/lib/inventory/scalaDedupe.d.ts +43 -0
- package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +15 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/kiroPermissions.d.ts +62 -0
- package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +7 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts +5 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceEngine.d.ts +4 -4
- package/types/lib/validator/complianceEngine.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts +14 -2
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reader converters for CycloneDX 2.0 components
|
|
3
|
+
* (#/$defs/cyclonedx-component-2.0/$defs/component) back to 1.x components
|
|
4
|
+
* and, for `type: service`, 1.x services. Each frozen spec is the allowlist
|
|
5
|
+
* of the members 1.x carries; everything else is reported as a loss.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { isDeepStrictEqual } from "node:util";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createRenderStats,
|
|
12
|
+
isNonEmptyString,
|
|
13
|
+
isObjectRecord,
|
|
14
|
+
shadowKey,
|
|
15
|
+
} from "./cyclonedx20Common.js";
|
|
16
|
+
import { readParty, reverseParties } from "./cyclonedx20Parties.js";
|
|
17
|
+
import {
|
|
18
|
+
ATTACHMENT_RULE,
|
|
19
|
+
EXTERNAL_REFERENCES_RULE,
|
|
20
|
+
HASHES_RULE,
|
|
21
|
+
LICENSES_RULE,
|
|
22
|
+
PATENT_ASSERTIONS_RULE,
|
|
23
|
+
PROPERTIES_RULE,
|
|
24
|
+
RESOLVES_RULE,
|
|
25
|
+
reverseAttachment,
|
|
26
|
+
reverseProperty,
|
|
27
|
+
reverseReleaseNotes,
|
|
28
|
+
} from "./cyclonedx20ReadCommon.js";
|
|
29
|
+
import {
|
|
30
|
+
addLoss,
|
|
31
|
+
jsonPointer,
|
|
32
|
+
listOf,
|
|
33
|
+
objectOf,
|
|
34
|
+
registerShadowKey,
|
|
35
|
+
reportUnmapped,
|
|
36
|
+
reverseFields,
|
|
37
|
+
reverseList,
|
|
38
|
+
reverseObject,
|
|
39
|
+
} from "./cyclonedx20ReadContext.js";
|
|
40
|
+
import {
|
|
41
|
+
reverseCryptoProperties,
|
|
42
|
+
reverseEvidence,
|
|
43
|
+
} from "./cyclonedx20ReadEvidence.js";
|
|
44
|
+
import {
|
|
45
|
+
convertComponent,
|
|
46
|
+
convertService,
|
|
47
|
+
} from "./cyclonedx20RenderComponents.js";
|
|
48
|
+
import { identifierPartyRefId } from "./cyclonedx20RenderEvidence.js";
|
|
49
|
+
import {
|
|
50
|
+
CARRIED_SERVICE_PROPERTIES,
|
|
51
|
+
COMPONENT_TYPES_1X,
|
|
52
|
+
IDENTITY_SCHEME_FIELDS_1X,
|
|
53
|
+
} from "./cyclonedx20Tables.js";
|
|
54
|
+
|
|
55
|
+
const COMPONENT_ROLES = Object.freeze({
|
|
56
|
+
supplier: "supplier",
|
|
57
|
+
manufacturer: "manufacturer",
|
|
58
|
+
author: "authors",
|
|
59
|
+
publisher: "publisher",
|
|
60
|
+
});
|
|
61
|
+
const SERVICE_ROLES = Object.freeze({ provider: "provider" });
|
|
62
|
+
|
|
63
|
+
// The 1.x mime-type pattern (data/bom-1.7.schema.json component.mime-type).
|
|
64
|
+
const isMimeType1x = (value) => {
|
|
65
|
+
if (typeof value !== "string") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const slash = value.indexOf("/");
|
|
69
|
+
const token = (part) =>
|
|
70
|
+
part.length > 0 &&
|
|
71
|
+
[...part].every(
|
|
72
|
+
(char) =>
|
|
73
|
+
(char >= "a" && char <= "z") ||
|
|
74
|
+
(char >= "0" && char <= "9") ||
|
|
75
|
+
char === "-" ||
|
|
76
|
+
char === "+" ||
|
|
77
|
+
char === ".",
|
|
78
|
+
);
|
|
79
|
+
return (
|
|
80
|
+
slash > 0 &&
|
|
81
|
+
value.indexOf("/", slash + 1) === -1 &&
|
|
82
|
+
token(value.slice(0, slash)) &&
|
|
83
|
+
token(value.slice(slash + 1))
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** One identity of an identifier group onto the 1.x fields. */
|
|
88
|
+
function reverseIdentity(ctx, fields, identity, pointer, componentName) {
|
|
89
|
+
if (!isObjectRecord(identity) || typeof identity.value !== "string") {
|
|
90
|
+
addLoss(ctx, pointer, "identity has no value");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
reportUnmapped(ctx, "identity", identity, pointer, IDENTITY_KEYS);
|
|
94
|
+
const scheme = identity.scheme;
|
|
95
|
+
const field =
|
|
96
|
+
typeof scheme === "string" &&
|
|
97
|
+
Object.hasOwn(IDENTITY_SCHEME_FIELDS_1X, scheme)
|
|
98
|
+
? IDENTITY_SCHEME_FIELDS_1X[scheme]
|
|
99
|
+
: undefined;
|
|
100
|
+
if (!field) {
|
|
101
|
+
addLoss(
|
|
102
|
+
ctx,
|
|
103
|
+
jsonPointer(pointer, "scheme"),
|
|
104
|
+
"identity scheme has no 1.x field",
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (field === "omniborId" || field === "swhid") {
|
|
109
|
+
(fields[field] ||= []).push(identity.value);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (fields[field] !== undefined) {
|
|
113
|
+
addLoss(ctx, pointer, `1.x holds one ${field}; only the first is kept`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (field !== "swid") {
|
|
117
|
+
fields[field] = identity.value;
|
|
118
|
+
} else if (isNonEmptyString(componentName)) {
|
|
119
|
+
// 1.x swid requires tagId and name; the swid name is the software name.
|
|
120
|
+
fields.swid = { tagId: identity.value, name: componentName };
|
|
121
|
+
} else {
|
|
122
|
+
addLoss(ctx, pointer, "a 1.x swid needs the component name");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const IDENTITY_KEYS = Object.freeze(new Set(["scheme", "value"]));
|
|
126
|
+
const IDENTIFIER_KEYS = Object.freeze(new Set(["party", "identities"]));
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* component.identifiers → purl, cpe, swid, omniborId and swhid. 1.x has no
|
|
130
|
+
* attribution, so an identifier asserted by a party other than the one the
|
|
131
|
+
* renderer derives from the document's tools (identifierPartyRefId) is a
|
|
132
|
+
* loss, as is any scheme without a 1.x field.
|
|
133
|
+
*/
|
|
134
|
+
function reverseIdentifiers(ctx, value, pointer, component) {
|
|
135
|
+
const fields = {};
|
|
136
|
+
reverseList(ctx, value, pointer, (_ctx, group, groupPointer) => {
|
|
137
|
+
if (!isObjectRecord(group)) {
|
|
138
|
+
addLoss(ctx, groupPointer, "identifier is not an object");
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
reportUnmapped(ctx, "identifier", group, groupPointer, IDENTIFIER_KEYS);
|
|
142
|
+
if (group.party !== identifierPartyRefId(ctx.claims)) {
|
|
143
|
+
addLoss(
|
|
144
|
+
ctx,
|
|
145
|
+
jsonPointer(groupPointer, "party"),
|
|
146
|
+
"1.x identifiers carry no asserting party",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
(Array.isArray(group.identities) ? group.identities : []).forEach(
|
|
150
|
+
(identity, index) => {
|
|
151
|
+
reverseIdentity(
|
|
152
|
+
ctx,
|
|
153
|
+
fields,
|
|
154
|
+
identity,
|
|
155
|
+
jsonPointer(groupPointer, "identities", index),
|
|
156
|
+
component.name,
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
return undefined;
|
|
161
|
+
});
|
|
162
|
+
return fields;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** data classification: a 2.0 enum value or custom `{name}` → 1.x string. */
|
|
166
|
+
function reverseClassification(ctx, value, pointer) {
|
|
167
|
+
if (typeof value === "string") {
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
if (isObjectRecord(value) && isNonEmptyString(value.name)) {
|
|
171
|
+
reportUnmapped(ctx, "classification", value, pointer, NAME_KEYS);
|
|
172
|
+
return value.name;
|
|
173
|
+
}
|
|
174
|
+
addLoss(ctx, pointer, "classification has no 1.x form");
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const NAME_KEYS = Object.freeze(new Set(["name"]));
|
|
178
|
+
const CLASSIFICATION_RULE = Object.freeze({ convert: reverseClassification });
|
|
179
|
+
|
|
180
|
+
const GOVERNANCE_ROLE_FIELDS = Object.freeze({
|
|
181
|
+
custodian: "custodians",
|
|
182
|
+
steward: "stewards",
|
|
183
|
+
owner: "owners",
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* dataGovernance parties → the 1.x {custodians, stewards, owners} lists of
|
|
188
|
+
* `{organization}` or `{contact}` entries, the inverse of convertGovernance.
|
|
189
|
+
*/
|
|
190
|
+
function reverseGovernance(ctx, value, pointer) {
|
|
191
|
+
const governance = {};
|
|
192
|
+
reverseList(ctx, value, pointer, (_ctx, entry, entryPointer) => {
|
|
193
|
+
const read = readParty(ctx, entry, entryPointer, GOVERNANCE_ROLE_FIELDS);
|
|
194
|
+
for (const role of read?.roles || []) {
|
|
195
|
+
const responsible =
|
|
196
|
+
read.identity.kind === "organization"
|
|
197
|
+
? { organization: structuredClone(read.identity.identity) }
|
|
198
|
+
: { contact: structuredClone(read.identity.identity) };
|
|
199
|
+
(governance[GOVERNANCE_ROLE_FIELDS[role]] ||= []).push(responsible);
|
|
200
|
+
}
|
|
201
|
+
return undefined;
|
|
202
|
+
});
|
|
203
|
+
return Object.keys(governance).length ? governance : undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const DATA_CONTENTS_SPEC = Object.freeze({
|
|
207
|
+
attachment: ATTACHMENT_RULE,
|
|
208
|
+
url: true,
|
|
209
|
+
properties: PROPERTIES_RULE,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
/** graphics collection: graphic references have no 1.x form. */
|
|
213
|
+
function reverseGraphic(ctx, value, pointer) {
|
|
214
|
+
if (isObjectRecord(value) && typeof value.ref === "string") {
|
|
215
|
+
addLoss(ctx, pointer, "graphic reference has no 1.x form");
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
return reverseObject(ctx, "graphic", value, pointer, {
|
|
219
|
+
name: true,
|
|
220
|
+
image: Object.freeze({ convert: reverseAttachment }),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const GRAPHICS_SPEC = Object.freeze({
|
|
225
|
+
description: true,
|
|
226
|
+
collection: listOf(reverseGraphic),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const COMPONENT_DATA_SPEC = Object.freeze({
|
|
230
|
+
refId: "bom-ref",
|
|
231
|
+
type: true,
|
|
232
|
+
name: true,
|
|
233
|
+
contents: objectOf("data.contents", DATA_CONTENTS_SPEC),
|
|
234
|
+
classification: CLASSIFICATION_RULE,
|
|
235
|
+
sensitiveData: true,
|
|
236
|
+
graphics: objectOf("data.graphics", GRAPHICS_SPEC),
|
|
237
|
+
description: true,
|
|
238
|
+
governance: Object.freeze({ convert: reverseGovernance }),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/** componentData → 1.x componentData. */
|
|
242
|
+
function reverseComponentData(ctx, value, pointer) {
|
|
243
|
+
return reverseObject(
|
|
244
|
+
ctx,
|
|
245
|
+
"componentData",
|
|
246
|
+
value,
|
|
247
|
+
pointer,
|
|
248
|
+
COMPONENT_DATA_SPEC,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const SERVICE_DATA_SPEC = Object.freeze({
|
|
253
|
+
type: true,
|
|
254
|
+
name: true,
|
|
255
|
+
description: true,
|
|
256
|
+
classification: CLASSIFICATION_RULE,
|
|
257
|
+
governance: Object.freeze({ convert: reverseGovernance }),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Service componentData → 1.x serviceData, which requires `flow` and
|
|
262
|
+
* `classification`. 2.0 has no flow, so it reads as `unknown`; an entry with
|
|
263
|
+
* no classification cannot be 1.x service data.
|
|
264
|
+
*/
|
|
265
|
+
function reverseServiceData(ctx, value, pointer) {
|
|
266
|
+
if (!isObjectRecord(value) || value.classification === undefined) {
|
|
267
|
+
addLoss(
|
|
268
|
+
ctx,
|
|
269
|
+
pointer,
|
|
270
|
+
"service data without a classification has no 1.x form",
|
|
271
|
+
);
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
const entry = reverseFields(
|
|
275
|
+
ctx,
|
|
276
|
+
"serviceData",
|
|
277
|
+
value,
|
|
278
|
+
pointer,
|
|
279
|
+
SERVICE_DATA_SPEC,
|
|
280
|
+
);
|
|
281
|
+
if (entry.type !== "other") {
|
|
282
|
+
addLoss(
|
|
283
|
+
ctx,
|
|
284
|
+
jsonPointer(pointer, "type"),
|
|
285
|
+
"service data type has no 1.x field",
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
delete entry.type;
|
|
289
|
+
if (entry.classification === undefined) {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
return { flow: "unknown", ...entry };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** A pedigree ancestor, descendant or variant entry → a 1.x component. */
|
|
296
|
+
function reversePedigreeEntry(ctx, value, pointer) {
|
|
297
|
+
const resolved = resolveComponentEntry(ctx, value, pointer);
|
|
298
|
+
if (!resolved) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
if (resolved.component.type === "service") {
|
|
302
|
+
addLoss(ctx, pointer, "a service has no 1.x pedigree form");
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
return reverseComponent(ctx, resolved.component, resolved.pointer);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const COMMIT_SPEC = Object.freeze({
|
|
309
|
+
uid: true,
|
|
310
|
+
url: true,
|
|
311
|
+
author: true,
|
|
312
|
+
committer: true,
|
|
313
|
+
message: true,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
/** patch: a patch reference has no 1.x form, nor does the patch refId. */
|
|
317
|
+
function reversePatch(ctx, value, pointer) {
|
|
318
|
+
if (isObjectRecord(value) && typeof value.ref === "string") {
|
|
319
|
+
addLoss(ctx, pointer, "patch reference has no 1.x form");
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
return reverseObject(ctx, "patch", value, pointer, {
|
|
323
|
+
type: true,
|
|
324
|
+
diff: objectOf("diff", { text: ATTACHMENT_RULE, url: true }),
|
|
325
|
+
resolves: RESOLVES_RULE,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const PEDIGREE_SPEC = Object.freeze({
|
|
330
|
+
ancestors: listOf(reversePedigreeEntry),
|
|
331
|
+
descendants: listOf(reversePedigreeEntry),
|
|
332
|
+
variants: listOf(reversePedigreeEntry),
|
|
333
|
+
commits: listOf((ctx, value, pointer) =>
|
|
334
|
+
reverseObject(ctx, "commit", value, pointer, COMMIT_SPEC),
|
|
335
|
+
),
|
|
336
|
+
patches: listOf(reversePatch),
|
|
337
|
+
notes: true,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
/** Nested componentEntries → the 1.x `components` and `services`. */
|
|
341
|
+
function reverseNestedEntries(ctx, value, pointer) {
|
|
342
|
+
const components = [];
|
|
343
|
+
const services = [];
|
|
344
|
+
reverseList(ctx, value, pointer, (_ctx, entry, entryPointer) => {
|
|
345
|
+
const resolved = resolveComponentEntry(ctx, entry, entryPointer);
|
|
346
|
+
if (!resolved) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
if (resolved.component.type === "service") {
|
|
350
|
+
const service = reverseService(ctx, resolved.component, resolved.pointer);
|
|
351
|
+
if (service) {
|
|
352
|
+
services.push(service);
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
const component = reverseComponent(
|
|
356
|
+
ctx,
|
|
357
|
+
resolved.component,
|
|
358
|
+
resolved.pointer,
|
|
359
|
+
);
|
|
360
|
+
if (component) {
|
|
361
|
+
components.push(component);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return undefined;
|
|
365
|
+
});
|
|
366
|
+
const fields = {};
|
|
367
|
+
if (components.length) {
|
|
368
|
+
fields.components = components;
|
|
369
|
+
}
|
|
370
|
+
if (services.length) {
|
|
371
|
+
fields.services = services;
|
|
372
|
+
}
|
|
373
|
+
return fields;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** modelProperties: only `properties` matches the 1.x modelCard. */
|
|
377
|
+
function reverseModelProperties(ctx, value, pointer) {
|
|
378
|
+
const modelCard = reverseObject(ctx, "modelProperties", value, pointer, {
|
|
379
|
+
properties: PROPERTIES_RULE,
|
|
380
|
+
});
|
|
381
|
+
return modelCard;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const COMPONENT_SPEC = Object.freeze({
|
|
385
|
+
type: true,
|
|
386
|
+
mediaType: Object.freeze({
|
|
387
|
+
to: "mime-type",
|
|
388
|
+
convert: (ctx, value, pointer) => {
|
|
389
|
+
if (isMimeType1x(value)) {
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
392
|
+
addLoss(
|
|
393
|
+
ctx,
|
|
394
|
+
pointer,
|
|
395
|
+
"mediaType does not match the 1.x mime-type pattern",
|
|
396
|
+
);
|
|
397
|
+
return undefined;
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
400
|
+
refId: "bom-ref",
|
|
401
|
+
parties: Object.freeze({
|
|
402
|
+
spread: true,
|
|
403
|
+
convert: (ctx, value, pointer) =>
|
|
404
|
+
reverseParties(ctx, value, pointer, COMPONENT_ROLES),
|
|
405
|
+
}),
|
|
406
|
+
group: true,
|
|
407
|
+
name: true,
|
|
408
|
+
version: true,
|
|
409
|
+
versionRange: true,
|
|
410
|
+
isExternal: true,
|
|
411
|
+
description: true,
|
|
412
|
+
scope: true,
|
|
413
|
+
hashes: HASHES_RULE,
|
|
414
|
+
licenses: LICENSES_RULE,
|
|
415
|
+
copyright: true,
|
|
416
|
+
patentAssertions: PATENT_ASSERTIONS_RULE,
|
|
417
|
+
identifiers: Object.freeze({ spread: true, convert: reverseIdentifiers }),
|
|
418
|
+
pedigree: objectOf("pedigree", PEDIGREE_SPEC),
|
|
419
|
+
components: Object.freeze({ spread: true, convert: reverseNestedEntries }),
|
|
420
|
+
evidence: Object.freeze({ convert: reverseEvidence }),
|
|
421
|
+
releaseNotes: Object.freeze({ convert: reverseReleaseNotes }),
|
|
422
|
+
modelProperties: Object.freeze({
|
|
423
|
+
to: "modelCard",
|
|
424
|
+
convert: reverseModelProperties,
|
|
425
|
+
}),
|
|
426
|
+
data: listOf(reverseComponentData),
|
|
427
|
+
cryptoProperties: Object.freeze({ convert: reverseCryptoProperties }),
|
|
428
|
+
tags: true,
|
|
429
|
+
properties: PROPERTIES_RULE,
|
|
430
|
+
externalReferences: EXTERNAL_REFERENCES_RULE,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* A 2.0 component (not a service) → a 1.x component, or undefined when its
|
|
435
|
+
* type has no 1.x equivalent (a loss).
|
|
436
|
+
*
|
|
437
|
+
* @param {object} ctx Reader context
|
|
438
|
+
* @param {object} component 2.0 component
|
|
439
|
+
* @param {string} pointer JSON pointer of `component`
|
|
440
|
+
* @returns {object|undefined} 1.x component
|
|
441
|
+
*/
|
|
442
|
+
export function reverseComponent(ctx, component, pointer) {
|
|
443
|
+
if (!COMPONENT_TYPES_1X.has(component.type)) {
|
|
444
|
+
addLoss(
|
|
445
|
+
ctx,
|
|
446
|
+
jsonPointer(pointer, "type"),
|
|
447
|
+
`component type '${component.type}' has no 1.x equivalent; component dropped`,
|
|
448
|
+
);
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
return reverseFields(ctx, "component", component, pointer, COMPONENT_SPEC);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const SERVICE_SPEC = Object.freeze({
|
|
455
|
+
type: Object.freeze({ convert: () => undefined }),
|
|
456
|
+
refId: "bom-ref",
|
|
457
|
+
parties: Object.freeze({
|
|
458
|
+
spread: true,
|
|
459
|
+
convert: (ctx, value, pointer) =>
|
|
460
|
+
reverseParties(ctx, value, pointer, SERVICE_ROLES),
|
|
461
|
+
}),
|
|
462
|
+
group: true,
|
|
463
|
+
name: true,
|
|
464
|
+
version: true,
|
|
465
|
+
description: true,
|
|
466
|
+
endpoints: true,
|
|
467
|
+
licenses: LICENSES_RULE,
|
|
468
|
+
patentAssertions: PATENT_ASSERTIONS_RULE,
|
|
469
|
+
externalReferences: EXTERNAL_REFERENCES_RULE,
|
|
470
|
+
data: listOf(reverseServiceData),
|
|
471
|
+
components: Object.freeze({
|
|
472
|
+
to: "services",
|
|
473
|
+
convert: (ctx, value, pointer) => {
|
|
474
|
+
const nested = reverseNestedEntries(ctx, value, pointer);
|
|
475
|
+
if (nested.components) {
|
|
476
|
+
addLoss(ctx, pointer, "1.x services cannot nest components");
|
|
477
|
+
}
|
|
478
|
+
return nested.services;
|
|
479
|
+
},
|
|
480
|
+
}),
|
|
481
|
+
releaseNotes: Object.freeze({ convert: reverseReleaseNotes }),
|
|
482
|
+
tags: true,
|
|
483
|
+
properties: Object.freeze({
|
|
484
|
+
spread: true,
|
|
485
|
+
convert: reverseServiceProperties,
|
|
486
|
+
}),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
/** CARRIED_SERVICE_PROPERTIES keyed by property name. */
|
|
490
|
+
const CARRIED_SERVICE_PROPERTY_BY_NAME = Object.freeze(
|
|
491
|
+
Object.fromEntries(
|
|
492
|
+
CARRIED_SERVICE_PROPERTIES.map(([field, name, kind]) => [
|
|
493
|
+
name,
|
|
494
|
+
Object.freeze({ field, kind }),
|
|
495
|
+
]),
|
|
496
|
+
),
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
/** A carried property value as its 1.x field value, or undefined. */
|
|
500
|
+
function parseCarriedServiceValue(kind, value) {
|
|
501
|
+
if (typeof value !== "string") {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
if (kind === "boolean") {
|
|
505
|
+
return value === "true" ? true : value === "false" ? false : undefined;
|
|
506
|
+
}
|
|
507
|
+
return value;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Service properties: the cdx:service:* properties the renderer adds for the
|
|
512
|
+
* 1.x `authenticated`, `x-trust-boundary` and `trustZone` become those fields
|
|
513
|
+
* again (booleans for the first two) and leave the property list, so a
|
|
514
|
+
* render of the read model does not add them twice. A value that does not
|
|
515
|
+
* parse (or a repeat of a name already restored) stays a property.
|
|
516
|
+
*/
|
|
517
|
+
function reverseServiceProperties(ctx, value, pointer) {
|
|
518
|
+
const properties = reverseList(ctx, value, pointer, reverseProperty);
|
|
519
|
+
if (!properties) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
const out = {};
|
|
523
|
+
const kept = [];
|
|
524
|
+
for (const property of properties) {
|
|
525
|
+
const carried = Object.hasOwn(
|
|
526
|
+
CARRIED_SERVICE_PROPERTY_BY_NAME,
|
|
527
|
+
property?.name ?? "",
|
|
528
|
+
)
|
|
529
|
+
? CARRIED_SERVICE_PROPERTY_BY_NAME[property.name]
|
|
530
|
+
: undefined;
|
|
531
|
+
const parsed = carried
|
|
532
|
+
? parseCarriedServiceValue(carried.kind, property.value)
|
|
533
|
+
: undefined;
|
|
534
|
+
if (parsed !== undefined && !Object.hasOwn(out, carried.field)) {
|
|
535
|
+
out[carried.field] = parsed;
|
|
536
|
+
} else {
|
|
537
|
+
kept.push(property);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (kept.length) {
|
|
541
|
+
out.properties = kept;
|
|
542
|
+
}
|
|
543
|
+
return out;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* A 2.0 component of type `service` → a 1.x service.
|
|
548
|
+
*
|
|
549
|
+
* @param {object} ctx Reader context
|
|
550
|
+
* @param {object} component 2.0 service component
|
|
551
|
+
* @param {string} pointer JSON pointer of `component`
|
|
552
|
+
* @returns {object} 1.x service
|
|
553
|
+
*/
|
|
554
|
+
export function reverseService(ctx, component, pointer) {
|
|
555
|
+
return reverseFields(ctx, "service", component, pointer, SERVICE_SPEC);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Resolve a componentEntry (component, componentReference or
|
|
560
|
+
* component-choice) to a concrete component. A reference resolves against
|
|
561
|
+
* every declared component; a choice keeps its first resolvable member.
|
|
562
|
+
*
|
|
563
|
+
* @param {object} ctx Reader context (with `componentIndex`)
|
|
564
|
+
* @param {object} entry 2.0 componentEntry
|
|
565
|
+
* @param {string} pointer JSON pointer of `entry`
|
|
566
|
+
* @returns {{component: object, pointer: string}|undefined}
|
|
567
|
+
*/
|
|
568
|
+
export function resolveComponentEntry(ctx, entry, pointer, depth = 0) {
|
|
569
|
+
if (!isObjectRecord(entry) || depth > 32) {
|
|
570
|
+
addLoss(ctx, pointer, "component entry is not an object");
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
if (typeof entry.ref === "string" && entry.name === undefined) {
|
|
574
|
+
const declared = ctx.componentIndex.get(entry.ref);
|
|
575
|
+
if (!declared) {
|
|
576
|
+
addLoss(
|
|
577
|
+
ctx,
|
|
578
|
+
jsonPointer(pointer, "ref"),
|
|
579
|
+
`component reference '${entry.ref}' could not be resolved`,
|
|
580
|
+
);
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
return resolveComponentEntry(
|
|
584
|
+
ctx,
|
|
585
|
+
declared.entry,
|
|
586
|
+
declared.pointer,
|
|
587
|
+
depth + 1,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
if (entry.type === "component-choice") {
|
|
591
|
+
const members = Array.isArray(entry.components) ? entry.components : [];
|
|
592
|
+
for (let index = 0; index < members.length; index++) {
|
|
593
|
+
const memberPointer = jsonPointer(pointer, "components", index);
|
|
594
|
+
const resolved = resolveComponentEntry(
|
|
595
|
+
ctx,
|
|
596
|
+
members[index],
|
|
597
|
+
memberPointer,
|
|
598
|
+
depth + 1,
|
|
599
|
+
);
|
|
600
|
+
if (resolved) {
|
|
601
|
+
addLoss(
|
|
602
|
+
ctx,
|
|
603
|
+
pointer,
|
|
604
|
+
"component-choice has no 1.x form; its first member is kept",
|
|
605
|
+
);
|
|
606
|
+
return resolved;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
addLoss(ctx, pointer, "component-choice has no resolvable member");
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
return { component: entry, pointer };
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Read one top-level component entry (an inventory entry, subject, tool,
|
|
617
|
+
* definitions component, formula component or annotator) into 1.x, as a
|
|
618
|
+
* component or a service. `needsShadow` is set when the 1.x value does not
|
|
619
|
+
* render back to the same 2.0 entry; a caller that keeps the entry passes the
|
|
620
|
+
* result to keepComponentShadow.
|
|
621
|
+
*
|
|
622
|
+
* @param {object} ctx Reader context
|
|
623
|
+
* @param {object} entry 2.0 componentEntry
|
|
624
|
+
* @param {string} pointer JSON pointer of `entry`
|
|
625
|
+
* @returns {{internal: object, isService: boolean, refId?: string, entry: object, needsShadow: boolean}|undefined}
|
|
626
|
+
*/
|
|
627
|
+
export function readComponentEntry(ctx, entry, pointer) {
|
|
628
|
+
const before = ctx.losses.length;
|
|
629
|
+
const resolved = resolveComponentEntry(ctx, entry, pointer);
|
|
630
|
+
if (!resolved) {
|
|
631
|
+
return undefined;
|
|
632
|
+
}
|
|
633
|
+
const isService = resolved.component.type === "service";
|
|
634
|
+
const internal = isService
|
|
635
|
+
? reverseService(ctx, resolved.component, resolved.pointer)
|
|
636
|
+
: reverseComponent(ctx, resolved.component, resolved.pointer);
|
|
637
|
+
if (!internal) {
|
|
638
|
+
return undefined;
|
|
639
|
+
}
|
|
640
|
+
const stats = createRenderStats(undefined, ctx.claims);
|
|
641
|
+
const rendered = isService
|
|
642
|
+
? convertService(internal, stats)
|
|
643
|
+
: convertComponent(internal, stats);
|
|
644
|
+
return {
|
|
645
|
+
internal,
|
|
646
|
+
isService,
|
|
647
|
+
refId: resolved.component.refId,
|
|
648
|
+
entry,
|
|
649
|
+
needsShadow:
|
|
650
|
+
ctx.losses.length > before || !isDeepStrictEqual(rendered, entry),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Note a kept component entry for the shadows. Entries read inside a section
|
|
656
|
+
* entry that is itself restored whole (annotations, vulnerabilities) are not
|
|
657
|
+
* noted, since the renderer never converts them.
|
|
658
|
+
*
|
|
659
|
+
* @param {object} ctx Reader context
|
|
660
|
+
* @param {object} read Result of readComponentEntry
|
|
661
|
+
*/
|
|
662
|
+
export function keepComponentShadow(ctx, read) {
|
|
663
|
+
if (!read || ctx.shadowSuspended) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
ctx.keptComponents.push({
|
|
667
|
+
key: shadowKey(read.internal),
|
|
668
|
+
original: read.entry,
|
|
669
|
+
needsShadow: read.needsShadow,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Register the component shadows. Kept entries whose 1.x values are
|
|
675
|
+
* identical share a key; when any of them needs a shadow, all of them are
|
|
676
|
+
* registered in document order, which is the order the renderer converts
|
|
677
|
+
* them in, so each gets its own original back.
|
|
678
|
+
*
|
|
679
|
+
* @param {object} ctx Reader context
|
|
680
|
+
*/
|
|
681
|
+
export function flushComponentShadows(ctx) {
|
|
682
|
+
const needed = new Set(
|
|
683
|
+
ctx.keptComponents
|
|
684
|
+
.filter((kept) => kept.needsShadow)
|
|
685
|
+
.map((kept) => kept.key),
|
|
686
|
+
);
|
|
687
|
+
for (const kept of ctx.keptComponents) {
|
|
688
|
+
if (needed.has(kept.key)) {
|
|
689
|
+
registerShadowKey(ctx, "component", kept.key, kept.original);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|