@cdxgen/cdxgen 13.1.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/bin/cdxgen.js +16 -3
- package/bin/evinse.js +2 -0
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +23 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +12 -0
- package/lib/cli/index.js +192 -1
- package/lib/cli/jsBom.js +59 -2
- package/lib/cli/jvmBom.js +22 -0
- package/lib/cli/managedBom.js +152 -0
- package/lib/cli/nativeBom.js +798 -1
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +39 -1
- 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 +241 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +143 -2
- 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-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +172 -0
- 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 +1 -1
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +932 -17
- 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/rusi.js +126 -6
- package/lib/evinser/evinser.js +111 -1
- package/lib/helpers/agenticToolutils.js +925 -0
- 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 +8 -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/dosai.js +77 -0
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +74 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- 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/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/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +169 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/package.json +25 -25
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -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/env.d.ts +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- 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 +20 -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-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- 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-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/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +29 -0
- 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/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 +4 -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/dosai.d.ts +23 -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 +34 -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 +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- 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/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/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/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
package/lib/cli/nativeBom.js
CHANGED
|
@@ -12,11 +12,13 @@ import { recordDegradation } from "../core/buildLedger.js";
|
|
|
12
12
|
import { deferFailOnError } from "../core/deferredExit.js";
|
|
13
13
|
import {
|
|
14
14
|
CARGO_CMD,
|
|
15
|
+
CARGO_TARGET,
|
|
15
16
|
CLJ_CMD,
|
|
16
17
|
hasAnyProjectType,
|
|
17
18
|
LEIN_CMD,
|
|
18
19
|
SWIFT_CMD,
|
|
19
20
|
shouldFetchLicense,
|
|
21
|
+
shouldFetchPackageMetadata,
|
|
20
22
|
} from "../core/env.js";
|
|
21
23
|
import {
|
|
22
24
|
checksumFile,
|
|
@@ -36,6 +38,14 @@ import {
|
|
|
36
38
|
getCratesMetadata,
|
|
37
39
|
getSwiftPackageMetadata,
|
|
38
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";
|
|
39
49
|
import { parseGleamProject } from "../ecosystems/parsers-gleam.js";
|
|
40
50
|
import {
|
|
41
51
|
parseGoListDep,
|
|
@@ -46,12 +56,17 @@ import {
|
|
|
46
56
|
parseGopkgData,
|
|
47
57
|
parseGosumData,
|
|
48
58
|
} from "../ecosystems/parsers-go.js";
|
|
59
|
+
import { parseJuliaProject } from "../ecosystems/parsers-julia.js";
|
|
49
60
|
import {
|
|
50
61
|
parseCljDep,
|
|
51
62
|
parseEdnData,
|
|
52
63
|
parseLeinDep,
|
|
53
64
|
parseLeiningenData,
|
|
54
65
|
} from "../ecosystems/parsers-jvm.js";
|
|
66
|
+
import {
|
|
67
|
+
parseLuaRocksLock,
|
|
68
|
+
parseRockspecFile,
|
|
69
|
+
} from "../ecosystems/parsers-lua.js";
|
|
55
70
|
import {
|
|
56
71
|
buildObjectForCocoaPod,
|
|
57
72
|
executeAlpmList,
|
|
@@ -78,12 +93,24 @@ import {
|
|
|
78
93
|
parseSwiftResolved,
|
|
79
94
|
prefetchCocoaPodspecs,
|
|
80
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";
|
|
81
99
|
import {
|
|
100
|
+
applyCargoDependencyKindMetadata,
|
|
101
|
+
buildCargoLockDependencyGraph,
|
|
102
|
+
buildCargoNativeLibraryComponents,
|
|
103
|
+
cargoComponentKey,
|
|
82
104
|
parseCargoData,
|
|
83
105
|
parseCargoDependencyData,
|
|
84
106
|
parseCargoManifestDependencyData,
|
|
107
|
+
parseCargoManifestDependencyKinds,
|
|
108
|
+
parseCargoMetadataResolve,
|
|
85
109
|
parseCargoTomlData,
|
|
110
|
+
rollupCargoDependencyKinds,
|
|
86
111
|
} from "../ecosystems/parsers-rust.js";
|
|
112
|
+
import { parseSpackLock } from "../ecosystems/parsers-spack.js";
|
|
113
|
+
import { parseStackProject } from "../ecosystems/parsers-stack.js";
|
|
87
114
|
import { resolveZigGraph } from "../ecosystems/zigResolver.js";
|
|
88
115
|
import { mergeDependencies, trimComponents } from "../inventory/depsUtils.js";
|
|
89
116
|
import { convertOSQueryResults } from "../inventory/evidenceUtils.js";
|
|
@@ -664,6 +691,223 @@ function cargoAnswersVersion(cwd) {
|
|
|
664
691
|
* @param {Object} options Parse options from the cli
|
|
665
692
|
* @returns {Promise<Object|undefined>} Promise resolving to a BOM object or undefined
|
|
666
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
|
+
|
|
667
911
|
export async function createRustBom(path, options) {
|
|
668
912
|
let pkgList = [];
|
|
669
913
|
let parentComponent = {};
|
|
@@ -769,6 +1013,7 @@ export async function createRustBom(path, options) {
|
|
|
769
1013
|
"No Cargo.lock was found beside the manifest, so components carry the declared dependencies without locked versions or the resolved crate graph.",
|
|
770
1014
|
});
|
|
771
1015
|
}
|
|
1016
|
+
const manifestRootKeys = new Map();
|
|
772
1017
|
for (const f of cargoFiles) {
|
|
773
1018
|
if (DEBUG_MODE) {
|
|
774
1019
|
console.log(`Parsing ${f}`);
|
|
@@ -777,6 +1022,7 @@ export async function createRustBom(path, options) {
|
|
|
777
1022
|
includeWorkspaceMembers: !options.multiProject,
|
|
778
1023
|
});
|
|
779
1024
|
if (dlist?.length) {
|
|
1025
|
+
manifestRootKeys.set(f, cargoComponentKey(dlist[0]));
|
|
780
1026
|
if (!cargoLockMode) {
|
|
781
1027
|
pkgList = pkgList.concat(dlist);
|
|
782
1028
|
} else {
|
|
@@ -832,6 +1078,15 @@ export async function createRustBom(path, options) {
|
|
|
832
1078
|
);
|
|
833
1079
|
}
|
|
834
1080
|
}
|
|
1081
|
+
if (cargoLockMode) {
|
|
1082
|
+
annotateCargoDependencyKinds(
|
|
1083
|
+
pkgList,
|
|
1084
|
+
dependencyTree,
|
|
1085
|
+
cargoFiles,
|
|
1086
|
+
manifestRootKeys,
|
|
1087
|
+
options,
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
835
1090
|
return buildBomNSData(options, pkgList, "cargo", {
|
|
836
1091
|
src: path,
|
|
837
1092
|
filename: cargoLockFiles.join(", "),
|
|
@@ -1596,6 +1851,44 @@ export function createClojureBom(path, options) {
|
|
|
1596
1851
|
return {};
|
|
1597
1852
|
}
|
|
1598
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
|
+
|
|
1599
1892
|
/**
|
|
1600
1893
|
* Function to create bom string for Haskell projects
|
|
1601
1894
|
*
|
|
@@ -1626,12 +1919,44 @@ export function createHaskellBom(path, options) {
|
|
|
1626
1919
|
filename: cabalFiles.join(", "),
|
|
1627
1920
|
});
|
|
1628
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
|
+
}
|
|
1629
1954
|
recordDegradation("haskell.freeze-missing", {
|
|
1630
1955
|
ecosystem: "haskell",
|
|
1631
1956
|
tool: "cabal",
|
|
1632
1957
|
impact: "components",
|
|
1633
1958
|
detail:
|
|
1634
|
-
"No cabal.project.freeze was found
|
|
1959
|
+
"No cabal.project.freeze or stack.yaml.lock was found; those lock files are the sources the parsers read for the package set.",
|
|
1635
1960
|
});
|
|
1636
1961
|
return {};
|
|
1637
1962
|
}
|
|
@@ -2302,3 +2627,475 @@ export async function createGleamBom(path, options) {
|
|
|
2302
2627
|
|
|
2303
2628
|
return {};
|
|
2304
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
|
+
}
|