@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
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
relative,
|
|
9
9
|
resolve,
|
|
10
10
|
} from "node:path";
|
|
11
|
-
import process from "node:process";
|
|
12
11
|
|
|
13
12
|
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
14
13
|
import { parse as _load } from "yaml";
|
|
@@ -18,6 +17,8 @@ import {
|
|
|
18
17
|
DEBUG_MODE,
|
|
19
18
|
readEnvironmentVariable,
|
|
20
19
|
} from "../core/activity.js";
|
|
20
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
21
|
+
import { deferFailOnError } from "../core/deferredExit.js";
|
|
21
22
|
import { shouldFetchLicense } from "../core/env.js";
|
|
22
23
|
import {
|
|
23
24
|
safeExistsSync,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
mapConanPkgRefToPurlStringAndNameAndVersion,
|
|
41
42
|
nixGenericPurl,
|
|
42
43
|
purlFromUrlString,
|
|
44
|
+
tryBuildPurl,
|
|
43
45
|
} from "../inventory/purl.js";
|
|
44
46
|
import { getLicenses, spdxLicenses } from "../inventory/spdx.js";
|
|
45
47
|
import { getDartMetadata } from "./ecosystems.js";
|
|
@@ -2652,6 +2654,15 @@ export function executePodCommand(parameters, path, options) {
|
|
|
2652
2654
|
);
|
|
2653
2655
|
}
|
|
2654
2656
|
if (readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true") {
|
|
2657
|
+
recordDegradation("cocoa.pods.missing", {
|
|
2658
|
+
ecosystem: "cocoa",
|
|
2659
|
+
tool: "pod",
|
|
2660
|
+
impact: "transitive-deps",
|
|
2661
|
+
command: `pod ${parameters.join(" ")}`,
|
|
2662
|
+
exitCode: typeof result.status === "number" ? result.status : undefined,
|
|
2663
|
+
detail:
|
|
2664
|
+
"The pod command did not succeed, so the cocoapods dependency graph was not resolved.",
|
|
2665
|
+
});
|
|
2655
2666
|
console.log(
|
|
2656
2667
|
"Consider using the cdxgen container image (`ghcr.io/cdxgen/cdxgen`), which includes cocoapods and additional build tools.",
|
|
2657
2668
|
);
|
|
@@ -2667,7 +2678,11 @@ export function executePodCommand(parameters, path, options) {
|
|
|
2667
2678
|
if (result.stderr) {
|
|
2668
2679
|
console.log(result.stderr);
|
|
2669
2680
|
}
|
|
2670
|
-
options
|
|
2681
|
+
deferFailOnError(options, {
|
|
2682
|
+
ecosystem: "cocoa",
|
|
2683
|
+
tool: "pod",
|
|
2684
|
+
detail: "the pod command failed",
|
|
2685
|
+
});
|
|
2671
2686
|
}
|
|
2672
2687
|
}
|
|
2673
2688
|
return result;
|
|
@@ -3189,3 +3204,174 @@ async function fullScanCocoaPod(dependency, component, options) {
|
|
|
3189
3204
|
component.externalReferences = externalRefs;
|
|
3190
3205
|
}
|
|
3191
3206
|
}
|
|
3207
|
+
|
|
3208
|
+
/**
|
|
3209
|
+
* Parse an xmake `xmake-requires.lock` file.
|
|
3210
|
+
*
|
|
3211
|
+
* The lock is a Lua table, written when the `package.requires_lock` policy is
|
|
3212
|
+
* enabled. Its top level holds a `__meta__` entry plus one section per build
|
|
3213
|
+
* configuration, keyed `platform|arch`; each section maps a requirement key
|
|
3214
|
+
* (`"zlib#31fecfc4"`, or `"sol2 v3.2.1#72267bd5"` when the requirement
|
|
3215
|
+
* carries a version) to a table with the resolved `version` and the
|
|
3216
|
+
* `repo` revision the package definition came from.
|
|
3217
|
+
*
|
|
3218
|
+
* The same package appears once per configuration, so entries are collapsed
|
|
3219
|
+
* by name and version, and the configurations a package was resolved for are
|
|
3220
|
+
* kept as a property. A requirement key without a `version` field names a
|
|
3221
|
+
* system package xmake did not resolve; it is inventoried without a version.
|
|
3222
|
+
*
|
|
3223
|
+
* No `xmake` purl type is registered, so packages are identified as generic
|
|
3224
|
+
* carrying a `cdx:purl:proposedType=xmake` property, following the
|
|
3225
|
+
* convention used for nix and zig.
|
|
3226
|
+
*
|
|
3227
|
+
* @param {string} lockFile Path to `xmake-requires.lock`
|
|
3228
|
+
* @returns {Object[]} Package records for the pinned requirements
|
|
3229
|
+
*/
|
|
3230
|
+
export function parseXmakeRequiresLock(lockFile) {
|
|
3231
|
+
let text;
|
|
3232
|
+
try {
|
|
3233
|
+
text = readFileSync(lockFile, "utf-8");
|
|
3234
|
+
} catch (error) {
|
|
3235
|
+
console.warn(`Failed to read ${lockFile}: ${error.message}`);
|
|
3236
|
+
return [];
|
|
3237
|
+
}
|
|
3238
|
+
const byKey = new Map();
|
|
3239
|
+
// The outermost `{` is the file's own table, so a configuration section
|
|
3240
|
+
// opens at depth 1 and a requirement inside it at depth 2. Each is dropped
|
|
3241
|
+
// again once the brace depth falls back to where it opened.
|
|
3242
|
+
const CONFIGURATION_DEPTH = 1;
|
|
3243
|
+
const REQUIREMENT_DEPTH = 2;
|
|
3244
|
+
let configuration;
|
|
3245
|
+
let requirement;
|
|
3246
|
+
let depth = 0;
|
|
3247
|
+
for (const rawLine of text.split("\n")) {
|
|
3248
|
+
const line = rawLine.trim();
|
|
3249
|
+
if (!line || line.startsWith("--")) {
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
// `["macosx|arm64"] = {` opens a configuration; `["zlib#hash"] = {` opens
|
|
3253
|
+
// a requirement inside one.
|
|
3254
|
+
const opener = line.match(/^\[\s*"([^"]+)"\s*\]\s*=\s*\{/u);
|
|
3255
|
+
if (opener && depth === CONFIGURATION_DEPTH) {
|
|
3256
|
+
// `__meta__` is normally written unquoted, but a lock that quotes it
|
|
3257
|
+
// must not become a configuration section either.
|
|
3258
|
+
configuration = opener[1] === "__meta__" ? undefined : opener[1];
|
|
3259
|
+
} else if (opener && depth === REQUIREMENT_DEPTH && configuration) {
|
|
3260
|
+
requirement = xmakeRequirementName(opener[1]);
|
|
3261
|
+
const key = `${configuration}|${requirement}`;
|
|
3262
|
+
if (requirement && !byKey.has(key)) {
|
|
3263
|
+
byKey.set(key, { name: requirement, configuration });
|
|
3264
|
+
}
|
|
3265
|
+
} else if (!opener && configuration && requirement) {
|
|
3266
|
+
const version = line.match(/^version\s*=\s*"([^"]*)"/u);
|
|
3267
|
+
if (version) {
|
|
3268
|
+
const record = byKey.get(`${configuration}|${requirement}`);
|
|
3269
|
+
if (record) {
|
|
3270
|
+
record.version = version[1];
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
depth = Math.max(0, depth + countXmakeBraces(line));
|
|
3275
|
+
if (depth <= REQUIREMENT_DEPTH) {
|
|
3276
|
+
requirement = undefined;
|
|
3277
|
+
}
|
|
3278
|
+
if (depth <= CONFIGURATION_DEPTH) {
|
|
3279
|
+
configuration = undefined;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
// Collapse the per-configuration entries into one component per resolved
|
|
3284
|
+
// coordinate, listing the configurations it was resolved for.
|
|
3285
|
+
const merged = new Map();
|
|
3286
|
+
for (const record of byKey.values()) {
|
|
3287
|
+
const key = `${record.name}@${record.version || ""}`;
|
|
3288
|
+
const existing = merged.get(key);
|
|
3289
|
+
if (existing) {
|
|
3290
|
+
existing.configurations.add(record.configuration);
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3293
|
+
merged.set(key, {
|
|
3294
|
+
name: record.name,
|
|
3295
|
+
version: record.version,
|
|
3296
|
+
configurations: new Set([record.configuration]),
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
const pkgList = [];
|
|
3301
|
+
for (const record of merged.values()) {
|
|
3302
|
+
// A malformed coordinate in an untrusted lock costs one entry, not the
|
|
3303
|
+
// whole scan.
|
|
3304
|
+
const purl = tryBuildPurl({
|
|
3305
|
+
type: "generic",
|
|
3306
|
+
namespace: "xmake",
|
|
3307
|
+
name: record.name,
|
|
3308
|
+
version: record.version || null,
|
|
3309
|
+
});
|
|
3310
|
+
if (!purl) {
|
|
3311
|
+
continue;
|
|
3312
|
+
}
|
|
3313
|
+
pkgList.push({
|
|
3314
|
+
name: record.name,
|
|
3315
|
+
...(record.version ? { version: record.version } : {}),
|
|
3316
|
+
type: "library",
|
|
3317
|
+
scope: "required",
|
|
3318
|
+
purl,
|
|
3319
|
+
"bom-ref": decodeURIComponent(purl),
|
|
3320
|
+
properties: [
|
|
3321
|
+
{ name: "internal:SrcFile", value: lockFile },
|
|
3322
|
+
{ name: "cdx:purl:proposedType", value: "xmake" },
|
|
3323
|
+
{
|
|
3324
|
+
name: "cdx:xmake:configurations",
|
|
3325
|
+
value: [...record.configurations].sort().join(", "),
|
|
3326
|
+
},
|
|
3327
|
+
],
|
|
3328
|
+
evidence: {
|
|
3329
|
+
identity: {
|
|
3330
|
+
field: "purl",
|
|
3331
|
+
confidence: 0.5,
|
|
3332
|
+
methods: [
|
|
3333
|
+
{
|
|
3334
|
+
technique: "source-code-analysis",
|
|
3335
|
+
confidence: 0.5,
|
|
3336
|
+
value: `Filename ${lockFile}`,
|
|
3337
|
+
},
|
|
3338
|
+
],
|
|
3339
|
+
},
|
|
3340
|
+
},
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
return pkgList;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
/**
|
|
3347
|
+
* Reduce an xmake requirement key to the package name.
|
|
3348
|
+
*
|
|
3349
|
+
* Keys look like `zlib#31fecfc4` or `sol2 v3.2.1#72267bd5`: the trailing
|
|
3350
|
+
* `#<hash>` identifies the resolved configuration and the optional second
|
|
3351
|
+
* token is the requested version range, neither of which is the name.
|
|
3352
|
+
*
|
|
3353
|
+
* @param {string} key Requirement key as written in the lock
|
|
3354
|
+
* @returns {string|undefined} Package name
|
|
3355
|
+
*/
|
|
3356
|
+
function xmakeRequirementName(key) {
|
|
3357
|
+
const name = key.split("#")[0].trim().split(/\s+/u)[0];
|
|
3358
|
+
return name || undefined;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
/**
|
|
3362
|
+
* Net brace change contributed by one line of a Lua table.
|
|
3363
|
+
*
|
|
3364
|
+
* @param {string} line Line to scan
|
|
3365
|
+
* @returns {number} Opens minus closes
|
|
3366
|
+
*/
|
|
3367
|
+
function countXmakeBraces(line) {
|
|
3368
|
+
let delta = 0;
|
|
3369
|
+
for (const char of line) {
|
|
3370
|
+
if (char === "{") {
|
|
3371
|
+
delta += 1;
|
|
3372
|
+
} else if (char === "}") {
|
|
3373
|
+
delta -= 1;
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
return delta;
|
|
3377
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { tryBuildPurl } from "../inventory/purl.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Nim parser for `.nimble` manifests and `nimble.lock`.
|
|
8
|
+
*
|
|
9
|
+
* A `.nimble` file is a Nim script whose practical surface is a set of
|
|
10
|
+
* `key = "value"` assignments plus `requires "dep >= 1.0"` lines; it is read
|
|
11
|
+
* with a line scanner over those shapes rather than a Nim interpreter.
|
|
12
|
+
* Nimble persists the resolved dependency set to `nimble.lock` (JSON), which
|
|
13
|
+
* is the source of truth for versions.
|
|
14
|
+
*
|
|
15
|
+
* No `nim` purl type is registered, so packages are identified as generic
|
|
16
|
+
* carrying a `cdx:purl:proposedType=nim` property, following the convention
|
|
17
|
+
* used for nix and zig.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse a Nim project from a `.nimble` manifest and optional `nimble.lock`.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} nimbleFile Path to the `.nimble` file
|
|
24
|
+
* @param {string} [nimbleLockFile] Path to `nimble.lock`, if present
|
|
25
|
+
* @returns {{ pkgList: object[], dependencies: object[], parentComponent: object, rootInputs?: string[] }}
|
|
26
|
+
*/
|
|
27
|
+
export function parseNimProject(nimbleFile, nimbleLockFile) {
|
|
28
|
+
const fields = readNimbleFile(nimbleFile);
|
|
29
|
+
const parentComponent = {};
|
|
30
|
+
// Nimble files frequently omit `name`; the manifest file name is the
|
|
31
|
+
// package name in that case, which is also how nimble itself refers to it.
|
|
32
|
+
const projectName =
|
|
33
|
+
fields.name || basename(nimbleFile).replace(/\.nimble$/iu, "");
|
|
34
|
+
if (projectName) {
|
|
35
|
+
parentComponent.type = "application";
|
|
36
|
+
parentComponent.name = projectName;
|
|
37
|
+
if (fields.version) {
|
|
38
|
+
parentComponent.version = fields.version;
|
|
39
|
+
}
|
|
40
|
+
parentComponent.description =
|
|
41
|
+
fields.description || `Nim package: ${fields.name}`;
|
|
42
|
+
parentComponent.properties = [
|
|
43
|
+
{ name: "internal:SrcFile", value: nimbleFile },
|
|
44
|
+
];
|
|
45
|
+
if (fields.license) {
|
|
46
|
+
parentComponent.license = fields.license;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let lock;
|
|
51
|
+
if (nimbleLockFile) {
|
|
52
|
+
try {
|
|
53
|
+
lock = JSON.parse(readFileSync(nimbleLockFile, "utf-8"));
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn(`Failed to parse ${nimbleLockFile}: ${error.message}`);
|
|
56
|
+
lock = undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// nimble.lock has carried `packages` both as an object keyed by name and as
|
|
61
|
+
// an array of records; both shapes are accepted so locks from either
|
|
62
|
+
// nimble generation parse.
|
|
63
|
+
const lockedEntries = [];
|
|
64
|
+
if (Array.isArray(lock?.packages)) {
|
|
65
|
+
lockedEntries.push(...lock.packages);
|
|
66
|
+
} else if (lock?.packages && typeof lock.packages === "object") {
|
|
67
|
+
for (const [name, entry] of Object.entries(lock.packages)) {
|
|
68
|
+
lockedEntries.push({ name, ...entry });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!lockedEntries.length) {
|
|
73
|
+
// Lockless fallback: emit declared requirements without resolved versions.
|
|
74
|
+
const pkgList = fields.requires.map((req) =>
|
|
75
|
+
nimPackage(req.name, undefined, { direct: true, srcFile: nimbleFile }),
|
|
76
|
+
);
|
|
77
|
+
return {
|
|
78
|
+
pkgList,
|
|
79
|
+
dependencies: [],
|
|
80
|
+
parentComponent,
|
|
81
|
+
rootInputs: pkgList.map((p) => p["bom-ref"]),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const directNames = new Set(fields.requires.map((req) => req.name));
|
|
86
|
+
const pkgList = [];
|
|
87
|
+
const refByName = new Map();
|
|
88
|
+
const edges = [];
|
|
89
|
+
for (const entry of lockedEntries) {
|
|
90
|
+
if (typeof entry?.name !== "string" || !entry.name) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const version =
|
|
94
|
+
typeof entry.version === "string" && entry.version
|
|
95
|
+
? entry.version
|
|
96
|
+
: undefined;
|
|
97
|
+
const pkg = nimPackage(entry.name, version, {
|
|
98
|
+
direct: directNames.has(entry.name),
|
|
99
|
+
vcsRevision:
|
|
100
|
+
typeof entry.vcsRevision === "string" && entry.vcsRevision
|
|
101
|
+
? entry.vcsRevision
|
|
102
|
+
: undefined,
|
|
103
|
+
srcFile: nimbleLockFile,
|
|
104
|
+
});
|
|
105
|
+
pkgList.push(pkg);
|
|
106
|
+
refByName.set(entry.name, pkg["bom-ref"]);
|
|
107
|
+
}
|
|
108
|
+
for (const entry of lockedEntries) {
|
|
109
|
+
const ref = refByName.get(entry.name);
|
|
110
|
+
if (!ref) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const dependsOn = (
|
|
114
|
+
Array.isArray(entry.dependencies) ? entry.dependencies : []
|
|
115
|
+
)
|
|
116
|
+
.map((depName) =>
|
|
117
|
+
typeof depName === "string" ? refByName.get(depName) : undefined,
|
|
118
|
+
)
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
edges.push({ ref, dependsOn: [...new Set(dependsOn)].sort() });
|
|
121
|
+
}
|
|
122
|
+
const rootInputs = [...directNames]
|
|
123
|
+
.map((name) => refByName.get(name))
|
|
124
|
+
.filter(Boolean);
|
|
125
|
+
return { pkgList, dependencies: edges, parentComponent, rootInputs };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Build a component-like package record for a Nim package.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} name Package name
|
|
132
|
+
* @param {string|undefined} version Resolved version, if known
|
|
133
|
+
* @param {object} opts Extra context (`direct`, `vcsRevision`, `srcFile`)
|
|
134
|
+
* @returns {object} Package record
|
|
135
|
+
*/
|
|
136
|
+
function nimPackage(name, version, opts) {
|
|
137
|
+
const purl = tryBuildPurl({
|
|
138
|
+
type: "generic",
|
|
139
|
+
name,
|
|
140
|
+
version: version || undefined,
|
|
141
|
+
});
|
|
142
|
+
const properties = [
|
|
143
|
+
{ name: "internal:SrcFile", value: opts.srcFile },
|
|
144
|
+
{ name: "cdx:purl:proposedType", value: "nim" },
|
|
145
|
+
{
|
|
146
|
+
name: "cdx:nim:dependency",
|
|
147
|
+
value: opts.direct ? "direct" : "transitive",
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
if (opts.vcsRevision) {
|
|
151
|
+
properties.push({ name: "cdx:nim:vcsRevision", value: opts.vcsRevision });
|
|
152
|
+
}
|
|
153
|
+
const pkg = {
|
|
154
|
+
name,
|
|
155
|
+
...(version ? { version } : {}),
|
|
156
|
+
type: "library",
|
|
157
|
+
scope: "required",
|
|
158
|
+
properties,
|
|
159
|
+
};
|
|
160
|
+
if (purl) {
|
|
161
|
+
pkg.purl = purl;
|
|
162
|
+
pkg["bom-ref"] = decodeURIComponent(purl);
|
|
163
|
+
} else {
|
|
164
|
+
pkg["bom-ref"] = `library:${name}:${version || ""}`;
|
|
165
|
+
}
|
|
166
|
+
return pkg;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Read the assignments and requirements of a `.nimble` file.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} filePath File to read
|
|
173
|
+
* @returns {{name?: string, version?: string, description?: string, license?: string, requires: Array<{name: string, constraint?: string}>}}
|
|
174
|
+
*/
|
|
175
|
+
export function readNimbleFile(filePath) {
|
|
176
|
+
const result = { requires: [] };
|
|
177
|
+
let text;
|
|
178
|
+
try {
|
|
179
|
+
text = readFileSync(filePath, "utf-8");
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn(`Failed to read ${filePath}: ${error.message}`);
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
for (const rawLine of text.split("\n")) {
|
|
185
|
+
const line = rawLine.trim();
|
|
186
|
+
if (!line || line.startsWith("#")) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const assignment = line.match(
|
|
190
|
+
/^(name|version|description|license)\s*=\s*"([^"]*)"/u,
|
|
191
|
+
);
|
|
192
|
+
if (assignment) {
|
|
193
|
+
const value = assignment[2];
|
|
194
|
+
if (value) {
|
|
195
|
+
result[assignment[1]] = value;
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const requires = line.match(/^requires\s+"([^"]+)"/u);
|
|
200
|
+
if (requires) {
|
|
201
|
+
const tokens = requires[1].split(/\s+/u);
|
|
202
|
+
const first = tokens[0];
|
|
203
|
+
const constraint = tokens.slice(1).join(" ");
|
|
204
|
+
if (first.startsWith("http") || first.includes("://")) {
|
|
205
|
+
// URL requirements are identified by the repository name, which is
|
|
206
|
+
// how nimble itself keys them in the lock file. A trailing .nim
|
|
207
|
+
// extension is dropped because the lock keys by the bare name.
|
|
208
|
+
const withoutExtension = first.split(/[?#]/u)[0].replace(/\.git$/u, "");
|
|
209
|
+
const segments = withoutExtension.split("/").filter(Boolean);
|
|
210
|
+
const repoName = segments[segments.length - 1] || first;
|
|
211
|
+
result.requires.push({
|
|
212
|
+
name: repoName.replace(/\.nim$/iu, ""),
|
|
213
|
+
constraint,
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
result.requires.push({ name: first, constraint });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { tryBuildPurl } from "../inventory/purl.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OCaml opam parser for `.opam.locked` files.
|
|
7
|
+
*
|
|
8
|
+
* `opam lock` writes a `<name>.opam.locked` file that pins the full resolved
|
|
9
|
+
* dependency set of a project: a `depends:` field listing every package with
|
|
10
|
+
* an equality constraint on its version, and a `pin-depends:` field for
|
|
11
|
+
* packages pinned to a URL. The file is opam's own syntax, a small
|
|
12
|
+
* line-oriented field language, which is read here with a line scanner rather
|
|
13
|
+
* than a monolithic expression.
|
|
14
|
+
*
|
|
15
|
+
* A locked file holds the closure only: it does not record which packages are
|
|
16
|
+
* direct dependencies of the project, so every entry is reported with the
|
|
17
|
+
* same standing and the project root links to all of them.
|
|
18
|
+
*
|
|
19
|
+
* Packages are identified with the registered `opam` purl type
|
|
20
|
+
* (`pkg:opam/<name>@<version>`).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parse an `.opam.locked` (or plain `.opam`) file.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} opamLockedFile Path to the opam file
|
|
27
|
+
* @returns {{ pkgList: object[], pins: Array<{name: string, url: string}> }}
|
|
28
|
+
*/
|
|
29
|
+
export function parseOpamLockedFile(opamLockedFile) {
|
|
30
|
+
let text;
|
|
31
|
+
try {
|
|
32
|
+
text = readFileSync(opamLockedFile, "utf-8");
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.warn(`Failed to read ${opamLockedFile}: ${error.message}`);
|
|
35
|
+
return { pkgList: [], pins: [] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const depends = [];
|
|
39
|
+
const pins = [];
|
|
40
|
+
const lines = text.split("\n");
|
|
41
|
+
let section;
|
|
42
|
+
for (const rawLine of lines) {
|
|
43
|
+
const line = rawLine.trim();
|
|
44
|
+
if (!line || line.startsWith("#")) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (line.startsWith("depends:")) {
|
|
48
|
+
section = "depends";
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (line.startsWith("pin-depends:")) {
|
|
52
|
+
section = "pin-depends";
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// A bare `field:` at the start of a line ends the previous list section.
|
|
56
|
+
if (/^[A-Za-z0-9_-]+:/u.test(line)) {
|
|
57
|
+
section = undefined;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (section === "depends") {
|
|
61
|
+
collectDependsEntry(line, depends, opamLockedFile);
|
|
62
|
+
} else if (section === "pin-depends") {
|
|
63
|
+
collectPinEntry(line, pins);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// A pin overrides where a package is fetched from; reflect that on the
|
|
67
|
+
// resolved component so consumers can see the divergence from the registry.
|
|
68
|
+
for (const pin of pins) {
|
|
69
|
+
const target = depends.find((dep) => dep.name === pin.name);
|
|
70
|
+
if (target) {
|
|
71
|
+
target.properties.push({ name: "cdx:opam:pinned", value: "true" });
|
|
72
|
+
target.properties.push({
|
|
73
|
+
name: "cdx:opam:pinSource",
|
|
74
|
+
value: pin.url,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { pkgList: depends, pins };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Turn one `depends` list line into a pinned package record.
|
|
83
|
+
*
|
|
84
|
+
* Entries look like `"dune" {= "3.16.0"}` or `"ocaml" {= "5.1.1" & build}`.
|
|
85
|
+
* Only equality pins carry a version; constraint-only entries are recorded
|
|
86
|
+
* without one so the package is still inventoried.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} line Trimmed list line
|
|
89
|
+
* @param {object[]} depends Accumulator for package records
|
|
90
|
+
* @param {string} srcFile Source path for provenance
|
|
91
|
+
*/
|
|
92
|
+
function collectDependsEntry(line, depends, srcFile) {
|
|
93
|
+
const nameMatch = line.match(/^"([^"]+)"/u);
|
|
94
|
+
if (!nameMatch) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const name = nameMatch[1];
|
|
98
|
+
let version;
|
|
99
|
+
const constraints = line.match(/\{[^}]*\}/gu) || [];
|
|
100
|
+
for (const constraint of constraints) {
|
|
101
|
+
// A pin is a standalone `=`, not the tail of `>=`, `<=`, or `!=`.
|
|
102
|
+
const pinned = constraint.match(/(?<![<>!=])=(?!=)\s*"([^"]+)"/u);
|
|
103
|
+
if (pinned) {
|
|
104
|
+
version = pinned[1];
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (depends.some((dep) => dep.name === name && dep.version === version)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const purl = tryBuildPurl({
|
|
112
|
+
type: "opam",
|
|
113
|
+
name,
|
|
114
|
+
version: version || undefined,
|
|
115
|
+
});
|
|
116
|
+
const properties = [{ name: "internal:SrcFile", value: srcFile }];
|
|
117
|
+
const pkg = {
|
|
118
|
+
name,
|
|
119
|
+
...(version ? { version } : {}),
|
|
120
|
+
type: "library",
|
|
121
|
+
scope: "required",
|
|
122
|
+
properties,
|
|
123
|
+
};
|
|
124
|
+
if (purl) {
|
|
125
|
+
pkg.purl = purl;
|
|
126
|
+
pkg["bom-ref"] = decodeURIComponent(purl);
|
|
127
|
+
} else {
|
|
128
|
+
pkg["bom-ref"] = `library:${name}:${version || ""}`;
|
|
129
|
+
}
|
|
130
|
+
depends.push(pkg);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Turn one `pin-depends` list line into a pin record.
|
|
135
|
+
*
|
|
136
|
+
* Entries look like `[ "mylib.dev" "git+https://host/path#rev" ]`. The first
|
|
137
|
+
* string is a package *version* identifier — `<name>.<version>` — whereas the
|
|
138
|
+
* matching `depends` entry carries the bare name, so the version suffix is
|
|
139
|
+
* split off before the two can be matched. Package names cannot contain a
|
|
140
|
+
* dot, which makes the first dot the separator.
|
|
141
|
+
*
|
|
142
|
+
* The URL is reduced to its origin and path so credentials embedded in a
|
|
143
|
+
* query string never reach the BOM.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} line Trimmed list line
|
|
146
|
+
* @param {Array<{name: string, url: string}>} pins Accumulator for pins
|
|
147
|
+
*/
|
|
148
|
+
function collectPinEntry(line, pins) {
|
|
149
|
+
const strings = line.match(/"[^"]*"/gu) || [];
|
|
150
|
+
if (strings.length < 2) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const identifier = strings[0].slice(1, -1);
|
|
154
|
+
const rawUrl = strings[1].slice(1, -1);
|
|
155
|
+
pins.push({
|
|
156
|
+
name: identifier.split(".")[0],
|
|
157
|
+
url: sanitizedPinUrl(rawUrl),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reduce a pin URL to a safe origin plus path.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} rawUrl URL as written in the lock file
|
|
165
|
+
* @returns {string} Sanitised URL, or the raw value when it is not a URL
|
|
166
|
+
*/
|
|
167
|
+
function sanitizedPinUrl(rawUrl) {
|
|
168
|
+
const schemeSplit = rawUrl.split("://");
|
|
169
|
+
if (schemeSplit.length < 2) {
|
|
170
|
+
return rawUrl;
|
|
171
|
+
}
|
|
172
|
+
const scheme = schemeSplit[0];
|
|
173
|
+
const rest = schemeSplit[1];
|
|
174
|
+
const pathStart = rest.indexOf("/");
|
|
175
|
+
const authority = pathStart === -1 ? rest : rest.slice(0, pathStart);
|
|
176
|
+
const path = pathStart === -1 ? "" : rest.slice(pathStart).split(/[?#]/u)[0];
|
|
177
|
+
// Userinfo is dropped by taking only the authority's host portion.
|
|
178
|
+
const host = authority.split("@").pop();
|
|
179
|
+
return `${scheme}://${host}${path}`;
|
|
180
|
+
}
|