@cdxgen/cdxgen 13.0.1 → 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.
- package/README.md +30 -16
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +141 -7
- package/bin/evinse.js +3 -1
- package/data/component-tags.json +8 -1
- package/data/crypto-oid.json +88 -12
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1167 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +32 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +268 -49
- package/lib/cli/jsBom.js +91 -5
- package/lib/cli/jvmBom.js +1515 -1314
- package/lib/cli/managedBom.js +249 -28
- package/lib/cli/nativeBom.js +986 -10
- package/lib/core/activity.js +120 -2
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +108 -2
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +250 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +498 -17
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +188 -2
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +172 -36
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +944 -19
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +141 -13
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/auditCategories.js +17 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +508 -22
- package/lib/inventory/envcontext.js +215 -24
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +131 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +594 -0
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/osinfo.js +1 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +250 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +284 -3
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +26 -26
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +10 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +43 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +53 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +21 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +34 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +12 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +43 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +43 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +35 -2
- 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/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/lib/cli/nativeBom.js
CHANGED
|
@@ -8,13 +8,17 @@ import { build } from "@cdxgen/cdx-purl";
|
|
|
8
8
|
import { parse as loadYaml } from "yaml";
|
|
9
9
|
|
|
10
10
|
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
11
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
12
|
+
import { deferFailOnError } from "../core/deferredExit.js";
|
|
11
13
|
import {
|
|
12
14
|
CARGO_CMD,
|
|
15
|
+
CARGO_TARGET,
|
|
13
16
|
CLJ_CMD,
|
|
14
17
|
hasAnyProjectType,
|
|
15
18
|
LEIN_CMD,
|
|
16
19
|
SWIFT_CMD,
|
|
17
20
|
shouldFetchLicense,
|
|
21
|
+
shouldFetchPackageMetadata,
|
|
18
22
|
} from "../core/env.js";
|
|
19
23
|
import {
|
|
20
24
|
checksumFile,
|
|
@@ -34,6 +38,14 @@ import {
|
|
|
34
38
|
getCratesMetadata,
|
|
35
39
|
getSwiftPackageMetadata,
|
|
36
40
|
} from "../ecosystems/ecosystems.js";
|
|
41
|
+
import { parseShardsProject } from "../ecosystems/parsers-crystal.js";
|
|
42
|
+
import {
|
|
43
|
+
createElmCacheReader,
|
|
44
|
+
getElmMetadata,
|
|
45
|
+
isElmProjectFile,
|
|
46
|
+
parseElmProject,
|
|
47
|
+
parseLegacyElmProject,
|
|
48
|
+
} from "../ecosystems/parsers-elm.js";
|
|
37
49
|
import { parseGleamProject } from "../ecosystems/parsers-gleam.js";
|
|
38
50
|
import {
|
|
39
51
|
parseGoListDep,
|
|
@@ -44,12 +56,17 @@ import {
|
|
|
44
56
|
parseGopkgData,
|
|
45
57
|
parseGosumData,
|
|
46
58
|
} from "../ecosystems/parsers-go.js";
|
|
59
|
+
import { parseJuliaProject } from "../ecosystems/parsers-julia.js";
|
|
47
60
|
import {
|
|
48
61
|
parseCljDep,
|
|
49
62
|
parseEdnData,
|
|
50
63
|
parseLeinDep,
|
|
51
64
|
parseLeiningenData,
|
|
52
65
|
} from "../ecosystems/parsers-jvm.js";
|
|
66
|
+
import {
|
|
67
|
+
parseLuaRocksLock,
|
|
68
|
+
parseRockspecFile,
|
|
69
|
+
} from "../ecosystems/parsers-lua.js";
|
|
53
70
|
import {
|
|
54
71
|
buildObjectForCocoaPod,
|
|
55
72
|
executeAlpmList,
|
|
@@ -76,16 +93,32 @@ import {
|
|
|
76
93
|
parseSwiftResolved,
|
|
77
94
|
prefetchCocoaPodspecs,
|
|
78
95
|
} from "../ecosystems/parsers-misc.js";
|
|
96
|
+
import { parseNimProject } from "../ecosystems/parsers-nim.js";
|
|
97
|
+
import { parseOpamLockedFile } from "../ecosystems/parsers-opam.js";
|
|
98
|
+
import { parseRebarLock } from "../ecosystems/parsers-rebar.js";
|
|
79
99
|
import {
|
|
100
|
+
applyCargoDependencyKindMetadata,
|
|
101
|
+
buildCargoLockDependencyGraph,
|
|
102
|
+
buildCargoNativeLibraryComponents,
|
|
103
|
+
cargoComponentKey,
|
|
80
104
|
parseCargoData,
|
|
81
105
|
parseCargoDependencyData,
|
|
82
106
|
parseCargoManifestDependencyData,
|
|
107
|
+
parseCargoManifestDependencyKinds,
|
|
108
|
+
parseCargoMetadataResolve,
|
|
83
109
|
parseCargoTomlData,
|
|
110
|
+
rollupCargoDependencyKinds,
|
|
84
111
|
} from "../ecosystems/parsers-rust.js";
|
|
112
|
+
import { parseSpackLock } from "../ecosystems/parsers-spack.js";
|
|
113
|
+
import { parseStackProject } from "../ecosystems/parsers-stack.js";
|
|
85
114
|
import { resolveZigGraph } from "../ecosystems/zigResolver.js";
|
|
86
115
|
import { mergeDependencies, trimComponents } from "../inventory/depsUtils.js";
|
|
87
116
|
import { convertOSQueryResults } from "../inventory/evidenceUtils.js";
|
|
88
117
|
import { applyPurl, nixBomRef, tryBuildPurl } from "../inventory/purl.js";
|
|
118
|
+
import {
|
|
119
|
+
classifyProbeResult,
|
|
120
|
+
classifySpawnRestriction,
|
|
121
|
+
} from "../inventory/toolRequirements.js";
|
|
89
122
|
import { executeOsQuery } from "../managers/binary.js";
|
|
90
123
|
import {
|
|
91
124
|
buildBomNSData,
|
|
@@ -357,13 +390,34 @@ export async function createGoBom(path, options) {
|
|
|
357
390
|
// So we support a fallback to only operate with go mod graph command output in such instances
|
|
358
391
|
console.log("go list -deps command has failed for", basePath);
|
|
359
392
|
shouldManuallyParse = true;
|
|
393
|
+
// A spawn the runtime or a policy denied is neither a missing
|
|
394
|
+
// toolchain nor an engaged fallback; the version probes report
|
|
395
|
+
// that as degraded evidence and the policy entries name the fix.
|
|
396
|
+
if (
|
|
397
|
+
!classifySpawnRestriction("go") &&
|
|
398
|
+
result.error &&
|
|
399
|
+
classifyProbeResult(result) === "missing"
|
|
400
|
+
) {
|
|
401
|
+
recordDegradation("go.toolchain.missing", {
|
|
402
|
+
ecosystem: "go",
|
|
403
|
+
tool: "go",
|
|
404
|
+
impact: "transitive-deps",
|
|
405
|
+
command: "go list -deps ./...",
|
|
406
|
+
detail:
|
|
407
|
+
"The go executable could not be run, so the module graph was not resolved.",
|
|
408
|
+
});
|
|
409
|
+
}
|
|
360
410
|
if (DEBUG_MODE && result.stdout) {
|
|
361
411
|
console.log(result.stdout);
|
|
362
412
|
}
|
|
363
413
|
if (DEBUG_MODE && result.stderr) {
|
|
364
414
|
console.log(result.stderr);
|
|
365
415
|
}
|
|
366
|
-
options
|
|
416
|
+
deferFailOnError(options, {
|
|
417
|
+
ecosystem: "go",
|
|
418
|
+
tool: "go",
|
|
419
|
+
detail: "the go module graph command failed",
|
|
420
|
+
});
|
|
367
421
|
}
|
|
368
422
|
const stdout = result.stdout;
|
|
369
423
|
if (stdout) {
|
|
@@ -411,7 +465,11 @@ export async function createGoBom(path, options) {
|
|
|
411
465
|
if (DEBUG_MODE && result.stderr) {
|
|
412
466
|
console.log(result.stderr);
|
|
413
467
|
}
|
|
414
|
-
options
|
|
468
|
+
deferFailOnError(options, {
|
|
469
|
+
ecosystem: "go",
|
|
470
|
+
tool: "go",
|
|
471
|
+
detail: "the go list command failed",
|
|
472
|
+
});
|
|
415
473
|
}
|
|
416
474
|
if (result.stdout) {
|
|
417
475
|
cmdOutput = Buffer.from(result.stdout).toString();
|
|
@@ -492,7 +550,11 @@ export async function createGoBom(path, options) {
|
|
|
492
550
|
console.log(
|
|
493
551
|
"3. Alternatively, consider generating a post-build SBOM from the built binary using blint. Use the official container image and invoke cdxgen with the arguments `-t binary --lifecycle post-build`.",
|
|
494
552
|
);
|
|
495
|
-
options
|
|
553
|
+
deferFailOnError(options, {
|
|
554
|
+
ecosystem: "go",
|
|
555
|
+
tool: "go",
|
|
556
|
+
detail: "no go dependency evidence could be collected",
|
|
557
|
+
});
|
|
496
558
|
}
|
|
497
559
|
}
|
|
498
560
|
}
|
|
@@ -510,6 +572,19 @@ export async function createGoBom(path, options) {
|
|
|
510
572
|
if (
|
|
511
573
|
!hasAnyProjectType(["docker", "oci", "container", "os"], options, false)
|
|
512
574
|
) {
|
|
575
|
+
// When the environment itself denies the go command, the version
|
|
576
|
+
// probes record the restriction as degraded evidence and the policy
|
|
577
|
+
// entries name the fix; the fallback is the finding only when the
|
|
578
|
+
// toolchain was free to run and could not resolve the graph.
|
|
579
|
+
if (!classifySpawnRestriction("go")) {
|
|
580
|
+
recordDegradation("go.mod-only-fallback", {
|
|
581
|
+
ecosystem: "go",
|
|
582
|
+
tool: "go",
|
|
583
|
+
impact: "transitive-deps",
|
|
584
|
+
detail:
|
|
585
|
+
"The go toolchain could not resolve the module graph, so go.mod was parsed manually and components carry only the declared requires.",
|
|
586
|
+
});
|
|
587
|
+
}
|
|
513
588
|
console.log(
|
|
514
589
|
"Manually parsing go.mod files. The resultant BOM would be incomplete.",
|
|
515
590
|
);
|
|
@@ -584,6 +659,31 @@ export async function createGoBom(path, options) {
|
|
|
584
659
|
return {};
|
|
585
660
|
}
|
|
586
661
|
|
|
662
|
+
/**
|
|
663
|
+
* Whether the cargo executable answers a version probe.
|
|
664
|
+
*
|
|
665
|
+
* A cargo command run through a shell — the Windows path — reports an absent
|
|
666
|
+
* executable as a non-zero exit with the shell's own complaint on stderr, not
|
|
667
|
+
* as a spawn error, so a failed build alone cannot tell a missing toolchain
|
|
668
|
+
* from a broken project. The probe answers that question directly.
|
|
669
|
+
*
|
|
670
|
+
* @param {string} cwd Directory to probe from.
|
|
671
|
+
* @returns {boolean} True when cargo answered.
|
|
672
|
+
*/
|
|
673
|
+
function cargoAnswersVersion(cwd) {
|
|
674
|
+
const probe = safeSpawnSync(CARGO_CMD, ["--version"], {
|
|
675
|
+
cdxgenActivity: {
|
|
676
|
+
kind: "probe",
|
|
677
|
+
metadata: { tool: "cargo" },
|
|
678
|
+
probeType: "build-tool-availability",
|
|
679
|
+
},
|
|
680
|
+
cwd,
|
|
681
|
+
encoding: "utf-8",
|
|
682
|
+
shell: isWin,
|
|
683
|
+
});
|
|
684
|
+
return classifyProbeResult(probe) === "found";
|
|
685
|
+
}
|
|
686
|
+
|
|
587
687
|
/**
|
|
588
688
|
* Function to create bom string for Rust projects
|
|
589
689
|
*
|
|
@@ -591,6 +691,223 @@ export async function createGoBom(path, options) {
|
|
|
591
691
|
* @param {Object} options Parse options from the cli
|
|
592
692
|
* @returns {Promise<Object|undefined>} Promise resolving to a BOM object or undefined
|
|
593
693
|
*/
|
|
694
|
+
/**
|
|
695
|
+
* Run `cargo metadata` for a manifest and return its parsed resolve graph.
|
|
696
|
+
*
|
|
697
|
+
* The lock file is the union of every dependency kind and every target triple,
|
|
698
|
+
* so it cannot answer which crates are dev-only or platform-gated. Only
|
|
699
|
+
* `cargo metadata` carries that, per dependency edge, in `resolve.nodes[].deps[].dep_kinds`.
|
|
700
|
+
*
|
|
701
|
+
* @param {string} cargoTomlFile Manifest path
|
|
702
|
+
* @param {string} [filterPlatform] Target triple to resolve the graph for
|
|
703
|
+
* @returns {object|undefined} Parsed `cargo metadata` output
|
|
704
|
+
*/
|
|
705
|
+
function runCargoMetadata(cargoTomlFile, filterPlatform) {
|
|
706
|
+
const basePath = dirname(cargoTomlFile);
|
|
707
|
+
const platformArgs = filterPlatform
|
|
708
|
+
? ["--filter-platform", filterPlatform]
|
|
709
|
+
: [];
|
|
710
|
+
// `--locked` keeps the user's lock file untouched; a workspace that is out of
|
|
711
|
+
// date with its lock file needs the retry without it.
|
|
712
|
+
for (const lockArgs of [["--locked"], []]) {
|
|
713
|
+
const cargoArgs = [
|
|
714
|
+
"metadata",
|
|
715
|
+
"--format-version",
|
|
716
|
+
"1",
|
|
717
|
+
"--manifest-path",
|
|
718
|
+
cargoTomlFile,
|
|
719
|
+
...platformArgs,
|
|
720
|
+
...lockArgs,
|
|
721
|
+
];
|
|
722
|
+
if (DEBUG_MODE) {
|
|
723
|
+
console.log("Executing", CARGO_CMD, cargoArgs.join(" "), "in", basePath);
|
|
724
|
+
}
|
|
725
|
+
const result = safeSpawnSync(CARGO_CMD, cargoArgs, {
|
|
726
|
+
cwd: basePath,
|
|
727
|
+
shell: isWin,
|
|
728
|
+
});
|
|
729
|
+
if (result.status === 0 && !result.error && result.stdout) {
|
|
730
|
+
try {
|
|
731
|
+
return JSON.parse(result.stdout);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
if (DEBUG_MODE) {
|
|
734
|
+
console.log(`Unable to parse the cargo metadata output: ${err}`);
|
|
735
|
+
}
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (DEBUG_MODE) {
|
|
740
|
+
console.log(result.error, result.stderr);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Annotate cargo components with their dependency kind, platform gate and
|
|
748
|
+
* scope.
|
|
749
|
+
*
|
|
750
|
+
* With `--deep`, the edge kinds come from `cargo metadata` and are exact at
|
|
751
|
+
* every depth. Otherwise they are recovered from the manifests, which is exact
|
|
752
|
+
* for the dependencies a workspace member declares and inherited below them.
|
|
753
|
+
*
|
|
754
|
+
* @param {object[]} pkgList Components to annotate
|
|
755
|
+
* @param {object[]} dependencyTree Lockfile dependency relationships
|
|
756
|
+
* @param {string[]} cargoFiles Manifest paths
|
|
757
|
+
* @param {Map<string, string>} manifestRootKeys Manifest path to root component key
|
|
758
|
+
* @param {object} options CLI options
|
|
759
|
+
*/
|
|
760
|
+
function annotateCargoDependencyKinds(
|
|
761
|
+
pkgList,
|
|
762
|
+
dependencyTree,
|
|
763
|
+
cargoFiles,
|
|
764
|
+
manifestRootKeys,
|
|
765
|
+
options,
|
|
766
|
+
) {
|
|
767
|
+
if (!pkgList?.length) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const allEdges = new Map();
|
|
771
|
+
if (options?.deep) {
|
|
772
|
+
const rollups = new Map();
|
|
773
|
+
const packageInfo = new Map();
|
|
774
|
+
const visitedWorkspaceRoots = new Set();
|
|
775
|
+
// The keys that survive resolution for the requested target triple. The
|
|
776
|
+
// unfiltered graph still supplies the kinds, because a crate's kind does
|
|
777
|
+
// not change with the triple.
|
|
778
|
+
const targetIncludedKeys = CARGO_TARGET ? new Set() : undefined;
|
|
779
|
+
let targetFilterApplied = !CARGO_TARGET;
|
|
780
|
+
let anyMetadata = false;
|
|
781
|
+
let everyMetadata = true;
|
|
782
|
+
for (const cargoTomlFile of cargoFiles) {
|
|
783
|
+
const metadata = runCargoMetadata(cargoTomlFile);
|
|
784
|
+
if (!metadata) {
|
|
785
|
+
everyMetadata = false;
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
// Every member of a workspace answers with the same resolve graph.
|
|
789
|
+
const workspaceRoot = metadata.workspace_root || cargoTomlFile;
|
|
790
|
+
if (visitedWorkspaceRoots.has(workspaceRoot)) {
|
|
791
|
+
anyMetadata = true;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
visitedWorkspaceRoots.add(workspaceRoot);
|
|
795
|
+
anyMetadata = true;
|
|
796
|
+
if (CARGO_TARGET) {
|
|
797
|
+
const targetMetadata = runCargoMetadata(cargoTomlFile, CARGO_TARGET);
|
|
798
|
+
if (targetMetadata) {
|
|
799
|
+
for (const key of parseCargoMetadataResolve(targetMetadata)
|
|
800
|
+
.resolvedKeys) {
|
|
801
|
+
targetIncludedKeys.add(key);
|
|
802
|
+
}
|
|
803
|
+
targetFilterApplied = true;
|
|
804
|
+
} else {
|
|
805
|
+
console.warn(
|
|
806
|
+
`cargo metadata could not resolve the dependency graph for the target ${CARGO_TARGET}. The components are not filtered by target.`,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
const graph = parseCargoMetadataResolve(metadata);
|
|
811
|
+
for (const [key, value] of graph.packageInfo) {
|
|
812
|
+
packageInfo.set(key, value);
|
|
813
|
+
}
|
|
814
|
+
for (const [key, value] of graph.edges) {
|
|
815
|
+
allEdges.set(key, [...(allEdges.get(key) || []), ...value]);
|
|
816
|
+
}
|
|
817
|
+
for (const [key, value] of rollupCargoDependencyKinds(graph)) {
|
|
818
|
+
const existing = rollups.get(key);
|
|
819
|
+
if (!existing) {
|
|
820
|
+
rollups.set(key, value);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
// A crate that is a runtime dependency of any workspace in the scan is
|
|
824
|
+
// a runtime dependency of the BOM.
|
|
825
|
+
for (const kind of value.kinds) {
|
|
826
|
+
existing.kinds.add(kind);
|
|
827
|
+
}
|
|
828
|
+
for (const target of value.targets) {
|
|
829
|
+
existing.targets.add(target);
|
|
830
|
+
}
|
|
831
|
+
const precedence = ["root", "runtime", "build", "dev"];
|
|
832
|
+
if (
|
|
833
|
+
precedence.indexOf(value.kind) < precedence.indexOf(existing.kind)
|
|
834
|
+
) {
|
|
835
|
+
existing.kind = value.kind;
|
|
836
|
+
existing.targetGated = value.targetGated;
|
|
837
|
+
} else if (value.kind === existing.kind && !value.targetGated) {
|
|
838
|
+
existing.targetGated = false;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (anyMetadata && rollups.size) {
|
|
843
|
+
applyCargoDependencyKindMetadata(pkgList, rollups, packageInfo, {
|
|
844
|
+
resolveIsComplete: everyMetadata,
|
|
845
|
+
filterPlatform: targetFilterApplied ? CARGO_TARGET : undefined,
|
|
846
|
+
targetIncludedKeys: targetFilterApplied
|
|
847
|
+
? targetIncludedKeys
|
|
848
|
+
: undefined,
|
|
849
|
+
});
|
|
850
|
+
// A `-sys` crate puts a C library into the artifact that the cargo
|
|
851
|
+
// dependency graph itself cannot describe.
|
|
852
|
+
const nativeLibraries = buildCargoNativeLibraryComponents(
|
|
853
|
+
packageInfo,
|
|
854
|
+
allEdges,
|
|
855
|
+
rollups,
|
|
856
|
+
);
|
|
857
|
+
if (nativeLibraries.components.length) {
|
|
858
|
+
pkgList.push(...nativeLibraries.components);
|
|
859
|
+
if (dependencyTree) {
|
|
860
|
+
dependencyTree.push(...nativeLibraries.dependencies);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
recordDegradation("rust.cargo-metadata-unavailable", {
|
|
866
|
+
ecosystem: "rust",
|
|
867
|
+
tool: "cargo",
|
|
868
|
+
impact: "components",
|
|
869
|
+
command: `${basename(CARGO_CMD)} metadata`,
|
|
870
|
+
detail:
|
|
871
|
+
"cargo metadata could not be executed, so dependency kinds and platform gates were recovered from the manifests alone.",
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
if (!dependencyTree?.length || !manifestRootKeys.size) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const declaredEdgesByRoot = new Map();
|
|
878
|
+
for (const [cargoTomlFile, rootKey] of manifestRootKeys) {
|
|
879
|
+
if (!rootKey) {
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
const declaredEdges = parseCargoManifestDependencyKinds(cargoTomlFile, {
|
|
883
|
+
includeWorkspaceMembers: !options.multiProject,
|
|
884
|
+
});
|
|
885
|
+
const existing = declaredEdgesByRoot.get(rootKey);
|
|
886
|
+
if (!existing) {
|
|
887
|
+
declaredEdgesByRoot.set(rootKey, declaredEdges);
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
for (const [name, value] of declaredEdges) {
|
|
891
|
+
const knownEdge = existing.get(name);
|
|
892
|
+
if (!knownEdge) {
|
|
893
|
+
existing.set(name, value);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
for (const kind of value.kinds) {
|
|
897
|
+
knownEdge.kinds.add(kind);
|
|
898
|
+
}
|
|
899
|
+
for (const target of value.targets) {
|
|
900
|
+
knownEdge.targets.add(target);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const graph = buildCargoLockDependencyGraph(
|
|
905
|
+
dependencyTree,
|
|
906
|
+
declaredEdgesByRoot,
|
|
907
|
+
);
|
|
908
|
+
applyCargoDependencyKindMetadata(pkgList, rollupCargoDependencyKinds(graph));
|
|
909
|
+
}
|
|
910
|
+
|
|
594
911
|
export async function createRustBom(path, options) {
|
|
595
912
|
let pkgList = [];
|
|
596
913
|
let parentComponent = {};
|
|
@@ -649,7 +966,27 @@ export async function createRustBom(path, options) {
|
|
|
649
966
|
if (cargoInstallResult.status !== 0 || cargoInstallResult.error) {
|
|
650
967
|
console.error("Error running the cargo command");
|
|
651
968
|
console.log(cargoInstallResult.error, cargoInstallResult.stderr);
|
|
652
|
-
|
|
969
|
+
// A spawn the runtime or a policy denied is neither a missing
|
|
970
|
+
// toolchain nor an engaged fallback; the version probes record
|
|
971
|
+
// that as degraded evidence.
|
|
972
|
+
const toolchainMissing = cargoInstallResult.error
|
|
973
|
+
? classifyProbeResult(cargoInstallResult) === "missing"
|
|
974
|
+
: !cargoAnswersVersion(basePath);
|
|
975
|
+
if (!classifySpawnRestriction(CARGO_CMD) && toolchainMissing) {
|
|
976
|
+
recordDegradation("rust.toolchain.missing", {
|
|
977
|
+
ecosystem: "rust",
|
|
978
|
+
tool: "cargo",
|
|
979
|
+
impact: "transitive-deps",
|
|
980
|
+
command: `${basename(CARGO_CMD)} ${cargoArgs.join(" ")}`,
|
|
981
|
+
detail:
|
|
982
|
+
"The cargo executable could not be run, so the lock file could not be generated.",
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
deferFailOnError(options, {
|
|
986
|
+
ecosystem: "rust",
|
|
987
|
+
tool: "cargo",
|
|
988
|
+
detail: "the cargo command failed",
|
|
989
|
+
});
|
|
653
990
|
}
|
|
654
991
|
}
|
|
655
992
|
}
|
|
@@ -667,6 +1004,16 @@ export async function createRustBom(path, options) {
|
|
|
667
1004
|
options,
|
|
668
1005
|
);
|
|
669
1006
|
const cargoLockMode = cargoLockFiles.length;
|
|
1007
|
+
if (cargoFiles.length && !cargoLockMode) {
|
|
1008
|
+
recordDegradation("rust.cargo-lock-missing", {
|
|
1009
|
+
ecosystem: "rust",
|
|
1010
|
+
tool: "cargo",
|
|
1011
|
+
impact: "transitive-deps",
|
|
1012
|
+
detail:
|
|
1013
|
+
"No Cargo.lock was found beside the manifest, so components carry the declared dependencies without locked versions or the resolved crate graph.",
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
const manifestRootKeys = new Map();
|
|
670
1017
|
for (const f of cargoFiles) {
|
|
671
1018
|
if (DEBUG_MODE) {
|
|
672
1019
|
console.log(`Parsing ${f}`);
|
|
@@ -675,6 +1022,7 @@ export async function createRustBom(path, options) {
|
|
|
675
1022
|
includeWorkspaceMembers: !options.multiProject,
|
|
676
1023
|
});
|
|
677
1024
|
if (dlist?.length) {
|
|
1025
|
+
manifestRootKeys.set(f, cargoComponentKey(dlist[0]));
|
|
678
1026
|
if (!cargoLockMode) {
|
|
679
1027
|
pkgList = pkgList.concat(dlist);
|
|
680
1028
|
} else {
|
|
@@ -730,6 +1078,15 @@ export async function createRustBom(path, options) {
|
|
|
730
1078
|
);
|
|
731
1079
|
}
|
|
732
1080
|
}
|
|
1081
|
+
if (cargoLockMode) {
|
|
1082
|
+
annotateCargoDependencyKinds(
|
|
1083
|
+
pkgList,
|
|
1084
|
+
dependencyTree,
|
|
1085
|
+
cargoFiles,
|
|
1086
|
+
manifestRootKeys,
|
|
1087
|
+
options,
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
733
1090
|
return buildBomNSData(options, pkgList, "cargo", {
|
|
734
1091
|
src: path,
|
|
735
1092
|
filename: cargoLockFiles.join(", "),
|
|
@@ -868,6 +1225,15 @@ export async function createDartBom(path, options) {
|
|
|
868
1225
|
`${options.multiProject ? "**/" : ""}pubspec.yaml`,
|
|
869
1226
|
options,
|
|
870
1227
|
);
|
|
1228
|
+
if (pubSpecYamlFiles.length && !pubFiles.length) {
|
|
1229
|
+
recordDegradation("dart.pub-get-needed", {
|
|
1230
|
+
ecosystem: "dart",
|
|
1231
|
+
tool: "dart",
|
|
1232
|
+
impact: "versions",
|
|
1233
|
+
detail:
|
|
1234
|
+
"pubspec.yaml was parsed without pubspec.lock, so components carry version constraints instead of the locked versions.",
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
871
1237
|
let dependencies = [];
|
|
872
1238
|
let pkgList = [];
|
|
873
1239
|
let parentComponent;
|
|
@@ -1367,8 +1733,22 @@ export function createClojureBom(path, options) {
|
|
|
1367
1733
|
if (result.status !== 0 || result.error) {
|
|
1368
1734
|
if (result.stderr) {
|
|
1369
1735
|
console.error(result.stdout, result.stderr);
|
|
1370
|
-
options
|
|
1736
|
+
deferFailOnError(options, {
|
|
1737
|
+
ecosystem: "clojure",
|
|
1738
|
+
tool: "lein",
|
|
1739
|
+
detail: "the leiningen command failed",
|
|
1740
|
+
});
|
|
1371
1741
|
}
|
|
1742
|
+
recordDegradation("clojure.lein.missing", {
|
|
1743
|
+
ecosystem: "clojure",
|
|
1744
|
+
tool: "lein",
|
|
1745
|
+
impact: "transitive-deps",
|
|
1746
|
+
command: `${LEIN_CMD} ${LEIN_ARGS.join(" ")}`,
|
|
1747
|
+
exitCode:
|
|
1748
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1749
|
+
detail:
|
|
1750
|
+
"lein did not produce a dependency list, so the project.clj manifest is parsed manually.",
|
|
1751
|
+
});
|
|
1372
1752
|
console.log(
|
|
1373
1753
|
"Check if the correct version of lein is installed and available in PATH. Falling back to manual parsing.",
|
|
1374
1754
|
);
|
|
@@ -1390,7 +1770,11 @@ export function createClojureBom(path, options) {
|
|
|
1390
1770
|
}
|
|
1391
1771
|
} else {
|
|
1392
1772
|
console.error("lein unexpectedly didn't return any output");
|
|
1393
|
-
options
|
|
1773
|
+
deferFailOnError(options, {
|
|
1774
|
+
ecosystem: "clojure",
|
|
1775
|
+
tool: "lein",
|
|
1776
|
+
detail: "the leiningen command produced no output",
|
|
1777
|
+
});
|
|
1394
1778
|
}
|
|
1395
1779
|
}
|
|
1396
1780
|
}
|
|
@@ -1413,8 +1797,22 @@ export function createClojureBom(path, options) {
|
|
|
1413
1797
|
if (result.status !== 0 || result.error) {
|
|
1414
1798
|
if (result.stderr) {
|
|
1415
1799
|
console.error(result.stdout, result.stderr);
|
|
1416
|
-
options
|
|
1800
|
+
deferFailOnError(options, {
|
|
1801
|
+
ecosystem: "clojure",
|
|
1802
|
+
tool: "clj",
|
|
1803
|
+
detail: "the clojure cli command failed",
|
|
1804
|
+
});
|
|
1417
1805
|
}
|
|
1806
|
+
recordDegradation("clojure.cli.missing", {
|
|
1807
|
+
ecosystem: "clojure",
|
|
1808
|
+
tool: "clojure",
|
|
1809
|
+
impact: "transitive-deps",
|
|
1810
|
+
command: `${CLJ_CMD} ${CLJ_ARGS.join(" ")}`,
|
|
1811
|
+
exitCode:
|
|
1812
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1813
|
+
detail:
|
|
1814
|
+
"The clojure CLI did not produce a dependency tree, so the deps.edn manifest is parsed manually.",
|
|
1815
|
+
});
|
|
1418
1816
|
console.log(
|
|
1419
1817
|
"Check if the correct version of clojure cli is installed and available in PATH. Falling back to manual parsing.",
|
|
1420
1818
|
);
|
|
@@ -1436,7 +1834,11 @@ export function createClojureBom(path, options) {
|
|
|
1436
1834
|
}
|
|
1437
1835
|
} else {
|
|
1438
1836
|
console.error("clj unexpectedly didn't return any output");
|
|
1439
|
-
options
|
|
1837
|
+
deferFailOnError(options, {
|
|
1838
|
+
ecosystem: "clojure",
|
|
1839
|
+
tool: "clj",
|
|
1840
|
+
detail: "the clojure cli command produced no output",
|
|
1841
|
+
});
|
|
1440
1842
|
}
|
|
1441
1843
|
}
|
|
1442
1844
|
}
|
|
@@ -1449,6 +1851,44 @@ export function createClojureBom(path, options) {
|
|
|
1449
1851
|
return {};
|
|
1450
1852
|
}
|
|
1451
1853
|
|
|
1854
|
+
/**
|
|
1855
|
+
* Locate the Stack lock files under a path.
|
|
1856
|
+
*
|
|
1857
|
+
* Stack names the lock after the configuration file it completes, so the
|
|
1858
|
+
* default `stack.yaml` produces `stack.yaml.lock` and a project with several
|
|
1859
|
+
* configurations produces one lock per configuration. `stack.lock` is also
|
|
1860
|
+
* accepted because tooling and documentation examples have used that name.
|
|
1861
|
+
*
|
|
1862
|
+
* @param {string} path Project path
|
|
1863
|
+
* @param {Object} options Parse options from the cli
|
|
1864
|
+
* @returns {string[]} Lock file paths, deduplicated
|
|
1865
|
+
*/
|
|
1866
|
+
export function findStackLockFiles(path, options) {
|
|
1867
|
+
const prefix = options.multiProject ? "**/" : "";
|
|
1868
|
+
const candidates = new Set([
|
|
1869
|
+
...getAllFiles(path, `${prefix}*.yaml.lock`, options),
|
|
1870
|
+
...getAllFiles(path, `${prefix}stack.lock`, options),
|
|
1871
|
+
]);
|
|
1872
|
+
return [...candidates].filter((lockFile) => {
|
|
1873
|
+
if (/(^|[\\/])stack\.(yaml\.)?lock$/u.test(lockFile)) {
|
|
1874
|
+
return true;
|
|
1875
|
+
}
|
|
1876
|
+
// `*.yaml.lock` is not a name Stack owns, so a custom configuration is
|
|
1877
|
+
// accepted only when the file it completes names a snapshot.
|
|
1878
|
+
const configFile = lockFile.replace(/\.lock$/u, "");
|
|
1879
|
+
if (!safeExistsSync(configFile)) {
|
|
1880
|
+
return false;
|
|
1881
|
+
}
|
|
1882
|
+
try {
|
|
1883
|
+
return /^\s*(snapshot|resolver)\s*:/mu.test(
|
|
1884
|
+
readFileSync(configFile, "utf-8"),
|
|
1885
|
+
);
|
|
1886
|
+
} catch {
|
|
1887
|
+
return false;
|
|
1888
|
+
}
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1452
1892
|
/**
|
|
1453
1893
|
* Function to create bom string for Haskell projects
|
|
1454
1894
|
*
|
|
@@ -1479,6 +1919,45 @@ export function createHaskellBom(path, options) {
|
|
|
1479
1919
|
filename: cabalFiles.join(", "),
|
|
1480
1920
|
});
|
|
1481
1921
|
}
|
|
1922
|
+
// Stack projects keep the resolved extra-deps in a lock file named after
|
|
1923
|
+
// the configuration file, so `stack.yaml` locks to `stack.yaml.lock`.
|
|
1924
|
+
const stackLockFiles = findStackLockFiles(path, options);
|
|
1925
|
+
if (stackLockFiles.length) {
|
|
1926
|
+
const dependencies = [];
|
|
1927
|
+
let parentComponent = {};
|
|
1928
|
+
for (const stackLockFile of stackLockFiles) {
|
|
1929
|
+
if (DEBUG_MODE) {
|
|
1930
|
+
console.log(`Parsing ${stackLockFile}`);
|
|
1931
|
+
}
|
|
1932
|
+
const stackYamlFile = stackLockFile.replace(/\.lock$/u, "");
|
|
1933
|
+
const result = parseStackProject(
|
|
1934
|
+
stackLockFile,
|
|
1935
|
+
safeExistsSync(stackYamlFile) ? stackYamlFile : undefined,
|
|
1936
|
+
);
|
|
1937
|
+
if (result.pkgList?.length) {
|
|
1938
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
1939
|
+
}
|
|
1940
|
+
if (!Object.keys(parentComponent).length) {
|
|
1941
|
+
parentComponent = result.parentComponent;
|
|
1942
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
if (pkgList.length) {
|
|
1946
|
+
return buildBomNSData(options, pkgList, "hackage", {
|
|
1947
|
+
src: path,
|
|
1948
|
+
filename: stackLockFiles.join(", "),
|
|
1949
|
+
dependencies,
|
|
1950
|
+
parentComponent,
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
recordDegradation("haskell.freeze-missing", {
|
|
1955
|
+
ecosystem: "haskell",
|
|
1956
|
+
tool: "cabal",
|
|
1957
|
+
impact: "components",
|
|
1958
|
+
detail:
|
|
1959
|
+
"No cabal.project.freeze or stack.yaml.lock was found; those lock files are the sources the parsers read for the package set.",
|
|
1960
|
+
});
|
|
1482
1961
|
return {};
|
|
1483
1962
|
}
|
|
1484
1963
|
|
|
@@ -1512,6 +1991,13 @@ export function createElixirBom(path, options) {
|
|
|
1512
1991
|
filename: mixFiles.join(", "),
|
|
1513
1992
|
});
|
|
1514
1993
|
}
|
|
1994
|
+
recordDegradation("elixir.deps-not-fetched", {
|
|
1995
|
+
ecosystem: "elixir",
|
|
1996
|
+
tool: "mix",
|
|
1997
|
+
impact: "components",
|
|
1998
|
+
detail:
|
|
1999
|
+
"No mix.lock was found, and the lock file written by fetching the dependencies is the source the parser reads.",
|
|
2000
|
+
});
|
|
1515
2001
|
return {};
|
|
1516
2002
|
}
|
|
1517
2003
|
|
|
@@ -1634,6 +2120,16 @@ export async function createSwiftBom(path, options) {
|
|
|
1634
2120
|
`It looks like the 'Sources' directory is missing, so we are missing the components and dependencies for '${basename(basePath)}'.`,
|
|
1635
2121
|
);
|
|
1636
2122
|
} else if (readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true") {
|
|
2123
|
+
recordDegradation("swift.toolchain.missing", {
|
|
2124
|
+
ecosystem: "swift",
|
|
2125
|
+
tool: "swift",
|
|
2126
|
+
impact: "transitive-deps",
|
|
2127
|
+
command: `${swiftCommand} ${packageArgs.join(" ")}`,
|
|
2128
|
+
exitCode:
|
|
2129
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
2130
|
+
detail:
|
|
2131
|
+
"The swift package command did not succeed, so the swift package graph was not resolved.",
|
|
2132
|
+
});
|
|
1637
2133
|
console.log(
|
|
1638
2134
|
"Consider using the cdxgen container image (`ghcr.io/cdxgen/cdxgen`), which includes Swift and additional build tools.",
|
|
1639
2135
|
);
|
|
@@ -1645,7 +2141,11 @@ export async function createSwiftBom(path, options) {
|
|
|
1645
2141
|
}
|
|
1646
2142
|
}
|
|
1647
2143
|
console.error(result.stderr);
|
|
1648
|
-
options
|
|
2144
|
+
deferFailOnError(options, {
|
|
2145
|
+
ecosystem: "swift",
|
|
2146
|
+
tool: "swift",
|
|
2147
|
+
detail: "the swift package command failed",
|
|
2148
|
+
});
|
|
1649
2149
|
}
|
|
1650
2150
|
}
|
|
1651
2151
|
}
|
|
@@ -1691,7 +2191,11 @@ export async function createCocoaBom(path, options) {
|
|
|
1691
2191
|
"No 'Podfile.lock' found and '--no-install-deps' is set -- A Podfile.lock is needed to parse dependencies!",
|
|
1692
2192
|
);
|
|
1693
2193
|
missingLockWarningShown = true;
|
|
1694
|
-
options
|
|
2194
|
+
deferFailOnError(options, {
|
|
2195
|
+
ecosystem: "cocoa",
|
|
2196
|
+
tool: "pod",
|
|
2197
|
+
detail: "no Podfile.lock was found",
|
|
2198
|
+
});
|
|
1695
2199
|
}
|
|
1696
2200
|
} else if (options.deep && options.installDeps) {
|
|
1697
2201
|
executePodCommand(["install"], projectPath, options);
|
|
@@ -2123,3 +2627,475 @@ export async function createGleamBom(path, options) {
|
|
|
2123
2627
|
|
|
2124
2628
|
return {};
|
|
2125
2629
|
}
|
|
2630
|
+
|
|
2631
|
+
/**
|
|
2632
|
+
* Function to create bom string for Julia projects.
|
|
2633
|
+
*
|
|
2634
|
+
* The resolved `Manifest.toml` is preferred over the declared `Project.toml`
|
|
2635
|
+
* so the BOM carries pinned versions and the package graph.
|
|
2636
|
+
*
|
|
2637
|
+
* @param {string} path to the project
|
|
2638
|
+
* @param {Object} options Parse options from the cli
|
|
2639
|
+
* @returns {Promise<Object>} BOM object
|
|
2640
|
+
*/
|
|
2641
|
+
export async function createJuliaBom(path, options) {
|
|
2642
|
+
let pkgList = [];
|
|
2643
|
+
let dependencies = [];
|
|
2644
|
+
let parentComponent = {};
|
|
2645
|
+
|
|
2646
|
+
const projectTomlFiles = getAllFiles(
|
|
2647
|
+
path,
|
|
2648
|
+
`${options.multiProject ? "**/" : ""}Project.toml`,
|
|
2649
|
+
options,
|
|
2650
|
+
);
|
|
2651
|
+
const manifestOnly = !projectTomlFiles.length;
|
|
2652
|
+
|
|
2653
|
+
for (const projectTomlFile of projectTomlFiles) {
|
|
2654
|
+
if (DEBUG_MODE) {
|
|
2655
|
+
console.log(`Parsing ${projectTomlFile}`);
|
|
2656
|
+
}
|
|
2657
|
+
const baseDir = dirname(projectTomlFile);
|
|
2658
|
+
const manifestTomlFile = join(baseDir, "Manifest.toml");
|
|
2659
|
+
const result = parseJuliaProject(
|
|
2660
|
+
projectTomlFile,
|
|
2661
|
+
safeExistsSync(manifestTomlFile) ? manifestTomlFile : undefined,
|
|
2662
|
+
);
|
|
2663
|
+
collectProjectResult(result);
|
|
2664
|
+
}
|
|
2665
|
+
if (manifestOnly) {
|
|
2666
|
+
for (const manifestTomlFile of getAllFiles(
|
|
2667
|
+
path,
|
|
2668
|
+
`${options.multiProject ? "**/" : ""}Manifest.toml`,
|
|
2669
|
+
options,
|
|
2670
|
+
)) {
|
|
2671
|
+
if (DEBUG_MODE) {
|
|
2672
|
+
console.log(`Parsing ${manifestTomlFile}`);
|
|
2673
|
+
}
|
|
2674
|
+
// Gleam also writes a `manifest.toml`; on case-insensitive filesystems
|
|
2675
|
+
// the lookup above can surface it, so only accept files that parse as
|
|
2676
|
+
// a Julia manifest.
|
|
2677
|
+
const parsed = parseJuliaProject(undefined, manifestTomlFile);
|
|
2678
|
+
if (parsed.pkgList.length) {
|
|
2679
|
+
collectProjectResult(parsed);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
function collectProjectResult(result) {
|
|
2685
|
+
if (result.pkgList?.length) {
|
|
2686
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2687
|
+
}
|
|
2688
|
+
if (result.dependencies?.length) {
|
|
2689
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
2690
|
+
}
|
|
2691
|
+
if (
|
|
2692
|
+
!Object.keys(parentComponent).length &&
|
|
2693
|
+
Object.keys(result.parentComponent).length
|
|
2694
|
+
) {
|
|
2695
|
+
parentComponent = result.parentComponent;
|
|
2696
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
2701
|
+
return buildBomNSData(options, pkgList, "julia", {
|
|
2702
|
+
src: path,
|
|
2703
|
+
filename: projectTomlFiles.join(", "),
|
|
2704
|
+
dependencies,
|
|
2705
|
+
parentComponent,
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
return {};
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
/**
|
|
2712
|
+
* Attach a root dependency edge from the parent component to its direct
|
|
2713
|
+
* dependencies, following the shape used by the Gleam and Nix flows.
|
|
2714
|
+
*
|
|
2715
|
+
* @param {{rootInputs?: string[]}} result Parser result with root inputs
|
|
2716
|
+
* @param {Object} parentComponent Parent component to give a bom-ref
|
|
2717
|
+
* @param {Object[]} dependencies Dependency edge list to append to
|
|
2718
|
+
*/
|
|
2719
|
+
function attachRootEdge(result, parentComponent, dependencies) {
|
|
2720
|
+
if (result.rootInputs?.length && parentComponent.name) {
|
|
2721
|
+
const rootRef = `application:${parentComponent.name}:${parentComponent.version || "latest"}`;
|
|
2722
|
+
parentComponent["bom-ref"] = rootRef;
|
|
2723
|
+
dependencies.push({
|
|
2724
|
+
ref: rootRef,
|
|
2725
|
+
dependsOn: [...new Set(result.rootInputs)],
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
/**
|
|
2731
|
+
* Function to create bom string for Erlang projects using rebar3.
|
|
2732
|
+
*
|
|
2733
|
+
* @param {string} path to the project
|
|
2734
|
+
* @param {Object} options Parse options from the cli
|
|
2735
|
+
* @returns {Promise<Object>} BOM object
|
|
2736
|
+
*/
|
|
2737
|
+
export async function createErlangBom(path, options) {
|
|
2738
|
+
let pkgList = [];
|
|
2739
|
+
const rebarLockFiles = getAllFiles(
|
|
2740
|
+
path,
|
|
2741
|
+
`${options.multiProject ? "**/" : ""}rebar.lock`,
|
|
2742
|
+
options,
|
|
2743
|
+
);
|
|
2744
|
+
for (const rebarLockFile of rebarLockFiles) {
|
|
2745
|
+
if (DEBUG_MODE) {
|
|
2746
|
+
console.log(`Parsing ${rebarLockFile}`);
|
|
2747
|
+
}
|
|
2748
|
+
const result = parseRebarLock(rebarLockFile);
|
|
2749
|
+
if (result.pkgList?.length) {
|
|
2750
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
if (pkgList.length) {
|
|
2754
|
+
return buildBomNSData(options, pkgList, "erlang", {
|
|
2755
|
+
src: path,
|
|
2756
|
+
filename: rebarLockFiles.join(", "),
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
return {};
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
/**
|
|
2763
|
+
* Function to create bom string for OCaml projects using opam.
|
|
2764
|
+
*
|
|
2765
|
+
* Resolved `.opam.locked` files are preferred; a plain `.opam` file is used
|
|
2766
|
+
* as a declared fallback with unpinned versions.
|
|
2767
|
+
*
|
|
2768
|
+
* @param {string} path to the project
|
|
2769
|
+
* @param {Object} options Parse options from the cli
|
|
2770
|
+
* @returns {Promise<Object>} BOM object
|
|
2771
|
+
*/
|
|
2772
|
+
export async function createOCamlBom(path, options) {
|
|
2773
|
+
let pkgList = [];
|
|
2774
|
+
const opamLockedFiles = getAllFiles(
|
|
2775
|
+
path,
|
|
2776
|
+
`${options.multiProject ? "**/" : ""}*.opam.locked`,
|
|
2777
|
+
options,
|
|
2778
|
+
);
|
|
2779
|
+
const opamFiles = opamLockedFiles.length
|
|
2780
|
+
? []
|
|
2781
|
+
: getAllFiles(path, `${options.multiProject ? "**/" : ""}*.opam`, options);
|
|
2782
|
+
for (const opamFile of [...opamLockedFiles, ...opamFiles]) {
|
|
2783
|
+
if (DEBUG_MODE) {
|
|
2784
|
+
console.log(`Parsing ${opamFile}`);
|
|
2785
|
+
}
|
|
2786
|
+
const result = parseOpamLockedFile(opamFile);
|
|
2787
|
+
if (result.pkgList?.length) {
|
|
2788
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
if (pkgList.length) {
|
|
2792
|
+
return buildBomNSData(options, pkgList, "ocaml", {
|
|
2793
|
+
src: path,
|
|
2794
|
+
filename: [...opamLockedFiles, ...opamFiles].join(", "),
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
return {};
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
/**
|
|
2801
|
+
* Function to create bom string for Elm projects.
|
|
2802
|
+
*
|
|
2803
|
+
* @param {string} path to the project
|
|
2804
|
+
* @param {Object} options Parse options from the cli
|
|
2805
|
+
* @returns {Promise<Object>} BOM object
|
|
2806
|
+
*/
|
|
2807
|
+
export async function createElmBom(path, options) {
|
|
2808
|
+
let pkgList = [];
|
|
2809
|
+
let dependencies = [];
|
|
2810
|
+
let parentComponent = {};
|
|
2811
|
+
|
|
2812
|
+
const elmJsonFiles = getAllFiles(
|
|
2813
|
+
path,
|
|
2814
|
+
`${options.multiProject ? "**/" : ""}elm.json`,
|
|
2815
|
+
options,
|
|
2816
|
+
).filter(isElmProjectFile);
|
|
2817
|
+
// A single reader memoises the cache lookups shared by every manifest.
|
|
2818
|
+
const cacheReader = createElmCacheReader();
|
|
2819
|
+
for (const elmJsonFile of elmJsonFiles) {
|
|
2820
|
+
if (DEBUG_MODE) {
|
|
2821
|
+
console.log(`Parsing ${elmJsonFile}`);
|
|
2822
|
+
}
|
|
2823
|
+
const result = parseElmProject(elmJsonFile, cacheReader);
|
|
2824
|
+
if (result.pkgList?.length) {
|
|
2825
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2826
|
+
}
|
|
2827
|
+
if (result.dependencies?.length) {
|
|
2828
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
2829
|
+
}
|
|
2830
|
+
if (!Object.keys(parentComponent).length) {
|
|
2831
|
+
parentComponent = result.parentComponent;
|
|
2832
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
const legacyFiles = getAllFiles(
|
|
2836
|
+
path,
|
|
2837
|
+
`${options.multiProject ? "**/" : ""}elm-package.json`,
|
|
2838
|
+
options,
|
|
2839
|
+
);
|
|
2840
|
+
for (const legacyFile of legacyFiles) {
|
|
2841
|
+
if (DEBUG_MODE) {
|
|
2842
|
+
console.log(`Parsing ${legacyFile}`);
|
|
2843
|
+
}
|
|
2844
|
+
const baseDir = dirname(legacyFile);
|
|
2845
|
+
const exactDepsFile = join(baseDir, "elm-stuff", "exact-dependencies.json");
|
|
2846
|
+
const result = parseLegacyElmProject(
|
|
2847
|
+
legacyFile,
|
|
2848
|
+
safeExistsSync(exactDepsFile) ? exactDepsFile : undefined,
|
|
2849
|
+
);
|
|
2850
|
+
if (result.pkgList?.length) {
|
|
2851
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2852
|
+
}
|
|
2853
|
+
if (result.dependencies?.length) {
|
|
2854
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
2855
|
+
}
|
|
2856
|
+
if (!Object.keys(parentComponent).length) {
|
|
2857
|
+
parentComponent = result.parentComponent;
|
|
2858
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
if (pkgList.length && shouldFetchPackageMetadata()) {
|
|
2862
|
+
pkgList = await getElmMetadata(pkgList, dependencies);
|
|
2863
|
+
}
|
|
2864
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
2865
|
+
return buildBomNSData(options, pkgList, "elm", {
|
|
2866
|
+
src: path,
|
|
2867
|
+
filename: [...elmJsonFiles, ...legacyFiles].join(", "),
|
|
2868
|
+
dependencies,
|
|
2869
|
+
parentComponent,
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
return {};
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
/**
|
|
2876
|
+
* Function to create bom string for Crystal projects using shards.
|
|
2877
|
+
*
|
|
2878
|
+
* @param {string} path to the project
|
|
2879
|
+
* @param {Object} options Parse options from the cli
|
|
2880
|
+
* @returns {Promise<Object>} BOM object
|
|
2881
|
+
*/
|
|
2882
|
+
export async function createCrystalBom(path, options) {
|
|
2883
|
+
let pkgList = [];
|
|
2884
|
+
const dependencies = [];
|
|
2885
|
+
let parentComponent = {};
|
|
2886
|
+
|
|
2887
|
+
const shardYmlFiles = getAllFiles(
|
|
2888
|
+
path,
|
|
2889
|
+
`${options.multiProject ? "**/" : ""}shard.yml`,
|
|
2890
|
+
options,
|
|
2891
|
+
);
|
|
2892
|
+
for (const shardYmlFile of shardYmlFiles) {
|
|
2893
|
+
if (DEBUG_MODE) {
|
|
2894
|
+
console.log(`Parsing ${shardYmlFile}`);
|
|
2895
|
+
}
|
|
2896
|
+
const baseDir = dirname(shardYmlFile);
|
|
2897
|
+
const shardLockFile = join(baseDir, "shard.lock");
|
|
2898
|
+
const result = parseShardsProject(
|
|
2899
|
+
shardYmlFile,
|
|
2900
|
+
safeExistsSync(shardLockFile) ? shardLockFile : undefined,
|
|
2901
|
+
);
|
|
2902
|
+
if (result.pkgList?.length) {
|
|
2903
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2904
|
+
}
|
|
2905
|
+
if (!Object.keys(parentComponent).length) {
|
|
2906
|
+
parentComponent = result.parentComponent;
|
|
2907
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
2911
|
+
return buildBomNSData(options, pkgList, "crystal", {
|
|
2912
|
+
src: path,
|
|
2913
|
+
filename: shardYmlFiles.join(", "),
|
|
2914
|
+
dependencies,
|
|
2915
|
+
parentComponent,
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
return {};
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
/**
|
|
2922
|
+
* Function to create bom string for Nim projects using nimble.
|
|
2923
|
+
*
|
|
2924
|
+
* @param {string} path to the project
|
|
2925
|
+
* @param {Object} options Parse options from the cli
|
|
2926
|
+
* @returns {Promise<Object>} BOM object
|
|
2927
|
+
*/
|
|
2928
|
+
export async function createNimBom(path, options) {
|
|
2929
|
+
let pkgList = [];
|
|
2930
|
+
let dependencies = [];
|
|
2931
|
+
let parentComponent = {};
|
|
2932
|
+
|
|
2933
|
+
const nimbleFiles = getAllFiles(
|
|
2934
|
+
path,
|
|
2935
|
+
`${options.multiProject ? "**/" : ""}*.nimble`,
|
|
2936
|
+
options,
|
|
2937
|
+
);
|
|
2938
|
+
for (const nimbleFile of nimbleFiles) {
|
|
2939
|
+
if (DEBUG_MODE) {
|
|
2940
|
+
console.log(`Parsing ${nimbleFile}`);
|
|
2941
|
+
}
|
|
2942
|
+
const baseDir = dirname(nimbleFile);
|
|
2943
|
+
const nimbleLockFile = join(baseDir, "nimble.lock");
|
|
2944
|
+
const result = parseNimProject(
|
|
2945
|
+
nimbleFile,
|
|
2946
|
+
safeExistsSync(nimbleLockFile) ? nimbleLockFile : undefined,
|
|
2947
|
+
);
|
|
2948
|
+
if (result.pkgList?.length) {
|
|
2949
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2950
|
+
}
|
|
2951
|
+
if (result.dependencies?.length) {
|
|
2952
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
2953
|
+
}
|
|
2954
|
+
if (!Object.keys(parentComponent).length) {
|
|
2955
|
+
parentComponent = result.parentComponent;
|
|
2956
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
2960
|
+
return buildBomNSData(options, pkgList, "nim", {
|
|
2961
|
+
src: path,
|
|
2962
|
+
filename: nimbleFiles.join(", "),
|
|
2963
|
+
dependencies,
|
|
2964
|
+
parentComponent,
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
return {};
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
/**
|
|
2971
|
+
* Function to create bom string for Lua projects using LuaRocks.
|
|
2972
|
+
*
|
|
2973
|
+
* When a `luarocks.lock` is present next to a rockspec it supplies the
|
|
2974
|
+
* resolved versions; otherwise the rockspec's declared dependencies are
|
|
2975
|
+
* inventoried without versions.
|
|
2976
|
+
*
|
|
2977
|
+
* @param {string} path to the project
|
|
2978
|
+
* @param {Object} options Parse options from the cli
|
|
2979
|
+
* @returns {Promise<Object>} BOM object
|
|
2980
|
+
*/
|
|
2981
|
+
export async function createLuaBom(path, options) {
|
|
2982
|
+
let pkgList = [];
|
|
2983
|
+
let parentComponent = {};
|
|
2984
|
+
const seenLockFiles = new Set();
|
|
2985
|
+
const rockspecFiles = getAllFiles(
|
|
2986
|
+
path,
|
|
2987
|
+
`${options.multiProject ? "**/" : ""}*.rockspec`,
|
|
2988
|
+
options,
|
|
2989
|
+
);
|
|
2990
|
+
for (const rockspecFile of rockspecFiles) {
|
|
2991
|
+
if (DEBUG_MODE) {
|
|
2992
|
+
console.log(`Parsing ${rockspecFile}`);
|
|
2993
|
+
}
|
|
2994
|
+
const baseDir = dirname(rockspecFile);
|
|
2995
|
+
const lockFile = join(baseDir, "luarocks.lock");
|
|
2996
|
+
// The rockspec names the project and its declared set either way; the
|
|
2997
|
+
// lock, when present, supplies the resolved versions.
|
|
2998
|
+
const declared = parseRockspecFile(rockspecFile);
|
|
2999
|
+
if (!Object.keys(parentComponent).length) {
|
|
3000
|
+
parentComponent = declared.parentComponent;
|
|
3001
|
+
}
|
|
3002
|
+
let resolved;
|
|
3003
|
+
if (safeExistsSync(lockFile)) {
|
|
3004
|
+
seenLockFiles.add(lockFile);
|
|
3005
|
+
resolved = parseLuaRocksLock(lockFile, rockspecFile);
|
|
3006
|
+
}
|
|
3007
|
+
const chosen = resolved?.pkgList?.length ? resolved : declared;
|
|
3008
|
+
if (chosen.pkgList?.length) {
|
|
3009
|
+
pkgList = pkgList.concat(chosen.pkgList);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
// A lock can also stand alone, in a project whose rockspec is generated at
|
|
3013
|
+
// release time rather than committed.
|
|
3014
|
+
for (const lockFile of getAllFiles(
|
|
3015
|
+
path,
|
|
3016
|
+
`${options.multiProject ? "**/" : ""}luarocks.lock`,
|
|
3017
|
+
options,
|
|
3018
|
+
)) {
|
|
3019
|
+
if (seenLockFiles.has(lockFile)) {
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3022
|
+
if (DEBUG_MODE) {
|
|
3023
|
+
console.log(`Parsing ${lockFile}`);
|
|
3024
|
+
}
|
|
3025
|
+
const result = parseLuaRocksLock(lockFile);
|
|
3026
|
+
if (result.pkgList?.length) {
|
|
3027
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
if (pkgList.length) {
|
|
3031
|
+
return buildBomNSData(options, pkgList, "lua", {
|
|
3032
|
+
src: path,
|
|
3033
|
+
filename: rockspecFiles.join(", "),
|
|
3034
|
+
parentComponent,
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
return {};
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
/**
|
|
3041
|
+
* Function to create bom string for Spack environments.
|
|
3042
|
+
*
|
|
3043
|
+
* @param {string} path to the project
|
|
3044
|
+
* @param {Object} options Parse options from the cli
|
|
3045
|
+
* @returns {Promise<Object>} BOM object
|
|
3046
|
+
*/
|
|
3047
|
+
export async function createSpackBom(path, options) {
|
|
3048
|
+
let pkgList = [];
|
|
3049
|
+
let dependencies = [];
|
|
3050
|
+
let rootInputs = [];
|
|
3051
|
+
const spackLockFiles = getAllFiles(
|
|
3052
|
+
path,
|
|
3053
|
+
`${options.multiProject ? "**/" : ""}spack.lock`,
|
|
3054
|
+
options,
|
|
3055
|
+
);
|
|
3056
|
+
for (const spackLockFile of spackLockFiles) {
|
|
3057
|
+
if (DEBUG_MODE) {
|
|
3058
|
+
console.log(`Parsing ${spackLockFile}`);
|
|
3059
|
+
}
|
|
3060
|
+
const result = parseSpackLock(spackLockFile);
|
|
3061
|
+
if (result.pkgList?.length) {
|
|
3062
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
3063
|
+
}
|
|
3064
|
+
if (result.dependencies?.length) {
|
|
3065
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
3066
|
+
}
|
|
3067
|
+
if (result.rootInputs?.length) {
|
|
3068
|
+
rootInputs = rootInputs.concat(result.rootInputs);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
if (!pkgList.length) {
|
|
3072
|
+
return {};
|
|
3073
|
+
}
|
|
3074
|
+
// The lock names no project component, so the environment directory is the
|
|
3075
|
+
// name the environment is already known by. Its roots are the specs the
|
|
3076
|
+
// lock records as requested, which is why they are linked explicitly rather
|
|
3077
|
+
// than inferred from which nodes have no incoming edge: a build-only tool
|
|
3078
|
+
// such as gmake also has none, and it is not a root.
|
|
3079
|
+
const envName = basename(resolve(path));
|
|
3080
|
+
const parentComponent = {
|
|
3081
|
+
type: "application",
|
|
3082
|
+
name: envName,
|
|
3083
|
+
"bom-ref": `application:${envName}:latest`,
|
|
3084
|
+
description: `Spack environment: ${envName}`,
|
|
3085
|
+
properties: [
|
|
3086
|
+
{ name: "internal:SrcFile", value: spackLockFiles.join(", ") },
|
|
3087
|
+
],
|
|
3088
|
+
};
|
|
3089
|
+
if (rootInputs.length) {
|
|
3090
|
+
dependencies.push({
|
|
3091
|
+
ref: parentComponent["bom-ref"],
|
|
3092
|
+
dependsOn: [...new Set(rootInputs)].sort(),
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
return buildBomNSData(options, pkgList, "spack", {
|
|
3096
|
+
src: path,
|
|
3097
|
+
filename: spackLockFiles.join(", "),
|
|
3098
|
+
dependencies,
|
|
3099
|
+
parentComponent,
|
|
3100
|
+
});
|
|
3101
|
+
}
|