@cdxgen/cdxgen 13.2.0 → 13.4.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 +33 -14
- package/bin/cdxgen.js +310 -138
- package/bin/evinse.js +22 -0
- package/bin/repl.js +86 -0
- package/bin/sign.js +119 -15
- package/bin/tracebom.js +69 -5
- package/bin/validate.js +107 -19
- package/bin/verify.js +172 -63
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +85 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +156 -58
- package/lib/cli/jsBom.js +99 -53
- package/lib/cli/jvmBom.js +708 -147
- package/lib/cli/managedBom.js +415 -224
- package/lib/cli/nativeBom.js +264 -44
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +22 -15
- package/lib/core/fs.js +176 -7
- package/lib/core/httpClient.js +159 -24
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +274 -108
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +76 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +399 -51
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1983 -793
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +356 -37
- package/lib/ecosystems/jsEvidence.js +104 -44
- package/lib/ecosystems/kosi.js +31 -7
- package/lib/ecosystems/npmutils.js +174 -9
- package/lib/ecosystems/parsers-dotnet.js +616 -159
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +371 -70
- package/lib/ecosystems/parsers-js.js +263 -76
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +929 -216
- package/lib/ecosystems/parsers-python.js +167 -24
- package/lib/ecosystems/parsers-rust.js +28 -22
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +1492 -182
- package/lib/ecosystems/rusi.js +54 -10
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +1008 -191
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +1101 -222
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +703 -43
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +798 -57
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +90 -6
- package/lib/inventory/evidenceUtils.js +166 -113
- package/lib/inventory/fetchBatch.js +690 -55
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +45 -27
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +295 -25
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +467 -46
- package/lib/managers/oci.js +63 -3
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +466 -0
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +83 -16
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +64 -11
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +2 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +23 -23
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- 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 +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +70 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +57 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +54 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +16 -5
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +164 -3
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +132 -15
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +182 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +219 -5
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +121 -9
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +59 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +17 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +77 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +18 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
|
|
4
|
-
import * as toml from "smol-toml";
|
|
5
|
-
|
|
6
4
|
import {
|
|
7
5
|
sanitizeBomPropertyValue,
|
|
8
6
|
sanitizeBomUrl,
|
|
9
7
|
} from "../core/propertySanitizer.js";
|
|
8
|
+
import { parseToml } from "../core/toml.js";
|
|
10
9
|
import { parseJsonLike } from "./jsonLike.js";
|
|
11
10
|
import { classifyMcpReference } from "./mcp.js";
|
|
12
11
|
import {
|
|
@@ -813,7 +812,7 @@ export const mcpConfigParser = {
|
|
|
813
812
|
let configJson;
|
|
814
813
|
try {
|
|
815
814
|
configJson = filePath.toLowerCase().endsWith(".toml")
|
|
816
|
-
?
|
|
815
|
+
? parseToml(raw)
|
|
817
816
|
: parseJsonLike(raw);
|
|
818
817
|
} catch {
|
|
819
818
|
continue;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which detected project becomes the parent (`metadata.component`) when one
|
|
3
|
+
* scan covers several ecosystems and no parent was given.
|
|
4
|
+
*
|
|
5
|
+
* Each ecosystem detects its own project from its own manifest. The project
|
|
6
|
+
* whose manifest sits at the scan root describes the repository; one found
|
|
7
|
+
* deeper is a part of it (a web client in `tools/ui/`, a helper script's
|
|
8
|
+
* `pyproject.toml`). So candidates are ordered by how deep their ecosystem's
|
|
9
|
+
* manifests sit, the scan root first, and ties keep the ecosystem order the
|
|
10
|
+
* scan ran in.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readdirSync } from "node:fs";
|
|
14
|
+
import { dirname, relative, resolve, sep } from "node:path";
|
|
15
|
+
|
|
16
|
+
import { getAllFiles } from "../core/fs.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Manifest file names (or `*.ext` suffixes) per purl type: the files a
|
|
20
|
+
* project of that ecosystem is described by.
|
|
21
|
+
*/
|
|
22
|
+
const MANIFESTS_BY_PURL_TYPE = {
|
|
23
|
+
npm: ["package.json"],
|
|
24
|
+
pypi: [
|
|
25
|
+
"pyproject.toml",
|
|
26
|
+
"setup.py",
|
|
27
|
+
"setup.cfg",
|
|
28
|
+
"Pipfile",
|
|
29
|
+
"requirements.txt",
|
|
30
|
+
"uv.lock",
|
|
31
|
+
"poetry.lock",
|
|
32
|
+
"pixi.toml",
|
|
33
|
+
],
|
|
34
|
+
generic: [
|
|
35
|
+
"CMakeLists.txt",
|
|
36
|
+
"meson.build",
|
|
37
|
+
"conanfile.txt",
|
|
38
|
+
"conanfile.py",
|
|
39
|
+
"vcpkg.json",
|
|
40
|
+
"xmake.lua",
|
|
41
|
+
],
|
|
42
|
+
maven: ["pom.xml", "build.gradle", "build.gradle.kts", "build.sbt"],
|
|
43
|
+
golang: ["go.mod"],
|
|
44
|
+
cargo: ["Cargo.toml"],
|
|
45
|
+
composer: ["composer.json"],
|
|
46
|
+
gem: ["Gemfile", "*.gemspec"],
|
|
47
|
+
nuget: ["*.csproj", "*.fsproj", "*.vbproj", "*.sln", "packages.config"],
|
|
48
|
+
pub: ["pubspec.yaml"],
|
|
49
|
+
hex: ["mix.exs"],
|
|
50
|
+
hackage: ["*.cabal", "stack.yaml"],
|
|
51
|
+
swift: ["Package.swift"],
|
|
52
|
+
cocoapods: ["Podfile"],
|
|
53
|
+
clojars: ["deps.edn", "project.clj"],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** The purl type of a component, or `undefined`. */
|
|
57
|
+
function purlTypeOf(component) {
|
|
58
|
+
const purl = `${component?.purl || ""}`;
|
|
59
|
+
if (!purl.startsWith("pkg:")) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const rest = purl.slice(4);
|
|
63
|
+
const end = rest.search(/[/@?#]/);
|
|
64
|
+
return (end < 0 ? rest : rest.slice(0, end)).toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function matchesManifest(name, patterns) {
|
|
68
|
+
return patterns.some((p) =>
|
|
69
|
+
p.startsWith("*.") ? name.endsWith(p.slice(1)) : name === p,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* How deep the shallowest manifest of an ecosystem sits under a scan root:
|
|
75
|
+
* 0 for one at the root itself, `Infinity` when there is none (or the
|
|
76
|
+
* ecosystem is not one this module knows).
|
|
77
|
+
*
|
|
78
|
+
* @param {string} root Scan root
|
|
79
|
+
* @param {string} purlType Purl type of the ecosystem
|
|
80
|
+
* @param {Object} options CLI options (exclusions)
|
|
81
|
+
* @returns {number}
|
|
82
|
+
*/
|
|
83
|
+
export function manifestDepth(root, purlType, options = {}) {
|
|
84
|
+
const patterns = MANIFESTS_BY_PURL_TYPE[purlType];
|
|
85
|
+
if (!patterns || !root) {
|
|
86
|
+
return Number.POSITIVE_INFINITY;
|
|
87
|
+
}
|
|
88
|
+
const absRoot = resolve(root);
|
|
89
|
+
try {
|
|
90
|
+
if (readdirSync(absRoot).some((name) => matchesManifest(name, patterns))) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
} catch (_err) {
|
|
94
|
+
return Number.POSITIVE_INFINITY;
|
|
95
|
+
}
|
|
96
|
+
const glob =
|
|
97
|
+
patterns.length === 1 ? `**/${patterns[0]}` : `**/{${patterns.join(",")}}`;
|
|
98
|
+
const files = getAllFiles(absRoot, glob, options);
|
|
99
|
+
let depth = Number.POSITIVE_INFINITY;
|
|
100
|
+
for (const f of files) {
|
|
101
|
+
const rel = relative(absRoot, dirname(resolve(f)));
|
|
102
|
+
if (!rel || rel.startsWith("..")) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
depth = Math.min(depth, rel.split(sep).length);
|
|
106
|
+
}
|
|
107
|
+
return depth;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Order parent candidates by the depth of their ecosystem's manifests under
|
|
112
|
+
* the scan roots (the shallowest first). The sort is stable, so candidates of
|
|
113
|
+
* equal depth keep the order the ecosystems were scanned in.
|
|
114
|
+
*
|
|
115
|
+
* @param {Object[]} candidates Detected parent components, in scan order
|
|
116
|
+
* @param {string[]} roots Scan roots
|
|
117
|
+
* @param {Object} options CLI options
|
|
118
|
+
* @returns {Object[]} The candidates, reordered
|
|
119
|
+
*/
|
|
120
|
+
export function orderParentCandidates(candidates, roots, options = {}) {
|
|
121
|
+
const depthByType = new Map();
|
|
122
|
+
const depthOf = (component) => {
|
|
123
|
+
const type = purlTypeOf(component);
|
|
124
|
+
if (!type) {
|
|
125
|
+
return Number.POSITIVE_INFINITY;
|
|
126
|
+
}
|
|
127
|
+
if (!depthByType.has(type)) {
|
|
128
|
+
depthByType.set(
|
|
129
|
+
type,
|
|
130
|
+
Math.min(
|
|
131
|
+
Number.POSITIVE_INFINITY,
|
|
132
|
+
...(roots || []).map((r) => manifestDepth(r, type, options)),
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return depthByType.get(type);
|
|
137
|
+
};
|
|
138
|
+
return candidates
|
|
139
|
+
.map((component, index) => ({
|
|
140
|
+
component,
|
|
141
|
+
index,
|
|
142
|
+
depth: depthOf(component),
|
|
143
|
+
}))
|
|
144
|
+
.sort((a, b) => a.depth - b.depth || a.index - b.index)
|
|
145
|
+
.map((c) => c.component);
|
|
146
|
+
}
|
|
@@ -143,7 +143,14 @@ export const readBinary = (binFile, asJson, specVersion) => {
|
|
|
143
143
|
? decodeBomBinary(specVersion, binaryData, BINARY_READ_OPTIONS)
|
|
144
144
|
: parseBomBinary(binaryData, BINARY_READ_OPTIONS);
|
|
145
145
|
if (asJson) {
|
|
146
|
-
|
|
146
|
+
const bomJson = encodeBomJson(bomObject);
|
|
147
|
+
// Protobuf omits empty repeated fields, so a round-trip drops empty
|
|
148
|
+
// dependsOn lists. Restore them so JSON and protobuf inputs yield
|
|
149
|
+
// identical documents.
|
|
150
|
+
for (const adep of bomJson?.dependencies || []) {
|
|
151
|
+
adep.dependsOn ??= [];
|
|
152
|
+
}
|
|
153
|
+
return bomJson;
|
|
147
154
|
}
|
|
148
155
|
return bomObject;
|
|
149
156
|
};
|
package/lib/inventory/purl.js
CHANGED
|
@@ -53,15 +53,30 @@ export function purlTypeIsRegisteredString(purlString) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Encode a string for
|
|
57
|
-
*
|
|
56
|
+
* Encode a string for inclusion in a hand-written PackageURL string,
|
|
57
|
+
* percent-encoding every character the purl grammar does not allow literally,
|
|
58
|
+
* while preserving already-encoded `%40` sequences and keeping `:` and `/`
|
|
59
|
+
* unencoded.
|
|
60
|
+
*
|
|
61
|
+
* `encodeURIComponent` alone leaves `!`, `'`, `(`, `)` and `*` literal, and the
|
|
62
|
+
* purl grammar requires them escaped, so they are escaped here as well.
|
|
63
|
+
*
|
|
64
|
+
* Only for comparing against or assembling purl text. Never pass the result to
|
|
65
|
+
* `build()`, `new Purl()` or one of the helpers in this module: they escape
|
|
66
|
+
* their inputs themselves, and escaping twice turns `+` into `%252B`.
|
|
58
67
|
*
|
|
59
68
|
* @param {string} s String to encode
|
|
60
69
|
* @returns {string} Encoded string suitable for use in a PackageURL component
|
|
61
70
|
*/
|
|
62
71
|
export function encodeForPurl(s) {
|
|
63
72
|
return s && !s.includes("%40")
|
|
64
|
-
? encodeURIComponent(s)
|
|
73
|
+
? encodeURIComponent(s)
|
|
74
|
+
.replace(
|
|
75
|
+
/[!'()*]/g,
|
|
76
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
77
|
+
)
|
|
78
|
+
.replace(/%3A/g, ":")
|
|
79
|
+
.replace(/%2F/g, "/")
|
|
65
80
|
: s;
|
|
66
81
|
}
|
|
67
82
|
|
|
@@ -328,6 +343,140 @@ export function isValidPurl(candidate) {
|
|
|
328
343
|
}
|
|
329
344
|
}
|
|
330
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Split a purl into its components without requiring it to be well-formed.
|
|
348
|
+
*
|
|
349
|
+
* `Purl.parse` rejects a purl whose parts are not already escaped, which is the
|
|
350
|
+
* case this exists to handle: a `bom-ref` (cdxgen writes the decoded form of the
|
|
351
|
+
* purl there), a `go mod graph` token such as `github.com/Azure/go-autorest@v14.2.0+incompatible`,
|
|
352
|
+
* or a purl from a third-party document that left a `+` or a `://` unescaped.
|
|
353
|
+
* The separators (`:`, `/`, `@`, `?`, `#`, `&`, `=`) are structural and are read
|
|
354
|
+
* positionally; everything between them is decoded and handed back raw, ready
|
|
355
|
+
* for `build` to escape correctly.
|
|
356
|
+
*
|
|
357
|
+
* The version is taken after the last `@` of the path, so a scoped npm name
|
|
358
|
+
* (`@scope/name@1.0.0`) and a composer branch version (`dev-feature/x`) both
|
|
359
|
+
* split correctly.
|
|
360
|
+
*
|
|
361
|
+
* @param {string} purl Purl to split
|
|
362
|
+
* @returns {{type: string, namespace: (string|null), name: string, version: (string|null), qualifiers: (Object|null), subpath: (string|null)}|null} Parts, or null when the purl has no type and name
|
|
363
|
+
*/
|
|
364
|
+
export function splitPurlLoose(purl) {
|
|
365
|
+
if (typeof purl !== "string" || !purl.startsWith("pkg:")) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const decode = (value) => {
|
|
369
|
+
try {
|
|
370
|
+
return decodeURIComponent(value);
|
|
371
|
+
} catch {
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
let rest = purl.slice("pkg:".length).replace(/^\/+/, "");
|
|
376
|
+
let subpath = null;
|
|
377
|
+
const hash = rest.indexOf("#");
|
|
378
|
+
if (hash !== -1) {
|
|
379
|
+
subpath = decode(rest.slice(hash + 1)) || null;
|
|
380
|
+
rest = rest.slice(0, hash);
|
|
381
|
+
}
|
|
382
|
+
let qualifiers = null;
|
|
383
|
+
const query = rest.indexOf("?");
|
|
384
|
+
if (query !== -1) {
|
|
385
|
+
qualifiers = {};
|
|
386
|
+
for (const pair of rest.slice(query + 1).split("&")) {
|
|
387
|
+
const equals = pair.indexOf("=");
|
|
388
|
+
if (equals <= 0) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
qualifiers[pair.slice(0, equals)] = decode(pair.slice(equals + 1));
|
|
392
|
+
}
|
|
393
|
+
if (!Object.keys(qualifiers).length) {
|
|
394
|
+
qualifiers = null;
|
|
395
|
+
}
|
|
396
|
+
rest = rest.slice(0, query);
|
|
397
|
+
}
|
|
398
|
+
const slash = rest.indexOf("/");
|
|
399
|
+
if (slash <= 0) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
const type = rest.slice(0, slash);
|
|
403
|
+
let path = rest.slice(slash + 1);
|
|
404
|
+
let version = null;
|
|
405
|
+
const at = path.lastIndexOf("@");
|
|
406
|
+
if (at > 0 && path[at - 1] !== "/") {
|
|
407
|
+
version = decode(path.slice(at + 1)) || null;
|
|
408
|
+
path = path.slice(0, at);
|
|
409
|
+
}
|
|
410
|
+
const segments = path.split("/").filter(Boolean).map(decode);
|
|
411
|
+
const name = segments.pop();
|
|
412
|
+
if (!name) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
type,
|
|
417
|
+
namespace: segments.length ? segments.join("/") : null,
|
|
418
|
+
name,
|
|
419
|
+
version,
|
|
420
|
+
qualifiers,
|
|
421
|
+
subpath,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Parse a purl string that may carry unescaped reserved characters.
|
|
427
|
+
*
|
|
428
|
+
* A well-formed purl is parsed strictly, so its components come back exactly as
|
|
429
|
+
* cdx-purl reads them. Anything else is split structurally by
|
|
430
|
+
* {@link splitPurlLoose}. Use this where the input is known to be a decoded or
|
|
431
|
+
* hand-written purl (a `bom-ref`, a workspace reference, a package manager's
|
|
432
|
+
* `name@version` token), never to accept an invalid purl into a `purl` field:
|
|
433
|
+
* pass the parts to `build()` instead, which escapes them.
|
|
434
|
+
*
|
|
435
|
+
* @param {string} purlString Purl or decoded purl
|
|
436
|
+
* @returns {{type: string, namespace: (string|null), name: string, version: (string|null), qualifiers: (Object|null), subpath: (string|null)}|null} Raw components, or null when nothing purl-shaped remains
|
|
437
|
+
*/
|
|
438
|
+
export function parsePurlLoose(purlString) {
|
|
439
|
+
if (typeof purlString !== "string" || !purlString.startsWith("pkg:")) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
const parsed = Purl.parse(purlString);
|
|
444
|
+
return {
|
|
445
|
+
type: parsed.type,
|
|
446
|
+
namespace: parsed.namespace || null,
|
|
447
|
+
name: parsed.name,
|
|
448
|
+
version: parsed.version || null,
|
|
449
|
+
qualifiers: parsed.qualifiers || null,
|
|
450
|
+
subpath: parsed.subpath || null,
|
|
451
|
+
};
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (!err?.code?.startsWith("E_")) {
|
|
454
|
+
throw err;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return splitPurlLoose(purlString);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Turn a decoded or hand-written purl into a canonical, correctly escaped one.
|
|
462
|
+
*
|
|
463
|
+
* `bom-ref` values are the decoded form of a purl
|
|
464
|
+
* (`pkg:npm/foo@1.0.0+build`), which cdx-purl rightly rejects as a purl. This
|
|
465
|
+
* recovers the purl the ref was made from by splitting it structurally and
|
|
466
|
+
* rebuilding it with `build()`, so every reserved character is escaped exactly
|
|
467
|
+
* once.
|
|
468
|
+
*
|
|
469
|
+
* @param {string} purlString Purl or decoded purl
|
|
470
|
+
* @returns {string|null} Canonical purl string, or `null` when no valid purl can be built
|
|
471
|
+
*/
|
|
472
|
+
export function canonicalPurlFromLooseString(purlString) {
|
|
473
|
+
const parts = parsePurlLoose(purlString);
|
|
474
|
+
if (!parts) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
return tryBuildPurl(parts);
|
|
478
|
+
}
|
|
479
|
+
|
|
331
480
|
/**
|
|
332
481
|
* Parse a purl string, returning `null` instead of throwing when it is invalid.
|
|
333
482
|
*
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
|
|
4
|
-
import * as toml from "smol-toml";
|
|
5
|
-
|
|
6
4
|
import { safeExistsSync } from "../core/fs.js";
|
|
5
|
+
import { parseToml } from "../core/toml.js";
|
|
7
6
|
|
|
8
7
|
// Keep this list conservative and high-signal: it is meant to surface common
|
|
9
8
|
// Rust native build helpers for audit triage, not to exhaustively model every
|
|
@@ -67,7 +66,7 @@ function createFormulationComponent(name, filePath, toolName) {
|
|
|
67
66
|
function parseCargoManifest(filePath) {
|
|
68
67
|
let cargoData;
|
|
69
68
|
try {
|
|
70
|
-
cargoData =
|
|
69
|
+
cargoData = parseToml(readFileSync(filePath, { encoding: "utf-8" }));
|
|
71
70
|
} catch {
|
|
72
71
|
return undefined;
|
|
73
72
|
}
|
|
@@ -252,7 +251,7 @@ function parseCargoManifest(filePath) {
|
|
|
252
251
|
function parsePyProject(filePath) {
|
|
253
252
|
let pyprojectData;
|
|
254
253
|
try {
|
|
255
|
-
pyprojectData =
|
|
254
|
+
pyprojectData = parseToml(readFileSync(filePath, { encoding: "utf-8" }));
|
|
256
255
|
} catch {
|
|
257
256
|
return undefined;
|
|
258
257
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
15
15
|
|
|
16
16
|
import { isSpdxJsonLd } from "./bomUtils.js";
|
|
17
|
+
import { splitPurlLoose } from "./purl.js";
|
|
17
18
|
import { toCycloneDxLikeBom } from "./spdxUtils.js";
|
|
18
19
|
|
|
19
20
|
// A document larger than this is not a plausible SBOM for a single
|
|
@@ -150,75 +151,6 @@ function encodeVersionBuildMetadata(purl) {
|
|
|
150
151
|
return `${head.slice(0, at + 1)}${head.slice(at + 1).replaceAll("+", "%2B")}${tail}`;
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
/**
|
|
154
|
-
* Split a purl into its components without requiring it to be well-formed.
|
|
155
|
-
*
|
|
156
|
-
* `Purl.parse` rejects a purl whose parts are not already escaped, which is the
|
|
157
|
-
* case this exists to handle. The separators (`:`, `/`, `@`, `?`, `#`, `&`,
|
|
158
|
-
* `=`) are structural and are read positionally; everything between them is
|
|
159
|
-
* decoded and handed back raw, ready for `build` to escape correctly.
|
|
160
|
-
*
|
|
161
|
-
* @param {string} purl Purl to split
|
|
162
|
-
* @returns {{type: string, namespace: (string|null), name: string, version: (string|null), qualifiers: (Object|null), subpath: (string|null)}|null} Parts, or null when the purl has no type and name
|
|
163
|
-
*/
|
|
164
|
-
function splitPurl(purl) {
|
|
165
|
-
const decode = (value) => {
|
|
166
|
-
try {
|
|
167
|
-
return decodeURIComponent(value);
|
|
168
|
-
} catch {
|
|
169
|
-
return value;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
let rest = purl.slice("pkg:".length).replace(/^\/+/, "");
|
|
173
|
-
let subpath = null;
|
|
174
|
-
const hash = rest.indexOf("#");
|
|
175
|
-
if (hash !== -1) {
|
|
176
|
-
subpath = decode(rest.slice(hash + 1)) || null;
|
|
177
|
-
rest = rest.slice(0, hash);
|
|
178
|
-
}
|
|
179
|
-
let qualifiers = null;
|
|
180
|
-
const query = rest.indexOf("?");
|
|
181
|
-
if (query !== -1) {
|
|
182
|
-
qualifiers = {};
|
|
183
|
-
for (const pair of rest.slice(query + 1).split("&")) {
|
|
184
|
-
const equals = pair.indexOf("=");
|
|
185
|
-
if (equals <= 0) {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
qualifiers[pair.slice(0, equals)] = decode(pair.slice(equals + 1));
|
|
189
|
-
}
|
|
190
|
-
if (!Object.keys(qualifiers).length) {
|
|
191
|
-
qualifiers = null;
|
|
192
|
-
}
|
|
193
|
-
rest = rest.slice(0, query);
|
|
194
|
-
}
|
|
195
|
-
const slash = rest.indexOf("/");
|
|
196
|
-
if (slash <= 0) {
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
const type = rest.slice(0, slash);
|
|
200
|
-
let path = rest.slice(slash + 1);
|
|
201
|
-
let version = null;
|
|
202
|
-
const at = path.lastIndexOf("@");
|
|
203
|
-
if (at > 0) {
|
|
204
|
-
version = decode(path.slice(at + 1)) || null;
|
|
205
|
-
path = path.slice(0, at);
|
|
206
|
-
}
|
|
207
|
-
const segments = path.split("/").filter(Boolean).map(decode);
|
|
208
|
-
const name = segments.pop();
|
|
209
|
-
if (!name) {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
type,
|
|
214
|
-
namespace: segments.length ? segments.join("/") : null,
|
|
215
|
-
name,
|
|
216
|
-
version,
|
|
217
|
-
qualifiers,
|
|
218
|
-
subpath,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
154
|
/**
|
|
223
155
|
* Repair the purl of a component read from a third-party document.
|
|
224
156
|
*
|
|
@@ -247,7 +179,7 @@ export function repairPurl(component) {
|
|
|
247
179
|
} catch {
|
|
248
180
|
// Fall through to the structural rebuild below.
|
|
249
181
|
}
|
|
250
|
-
const parts =
|
|
182
|
+
const parts = splitPurlLoose(repaired);
|
|
251
183
|
if (!parts) {
|
|
252
184
|
return;
|
|
253
185
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Purl } from "@cdxgen/cdx-purl";
|
|
2
|
+
|
|
3
|
+
// Scala libraries publish their Maven artifacts with a trailing Scala binary
|
|
4
|
+
// version suffix (`jwt-core_3`, `coursier_2.13`), optionally preceded by a
|
|
5
|
+
// platform suffix for Scala.js and Scala Native builds (`upickle_sjs1_3`,
|
|
6
|
+
// `upickle_native0.5_3`). sbt components strip the binary suffix and keep the
|
|
7
|
+
// platform suffix (`jwt-core`, `upickle_sjs1`), so the same library is named
|
|
8
|
+
// differently depending on the build tool that reported it.
|
|
9
|
+
const SCALA_BINARY_SUFFIX_REGEX = /_(2\.\d+|3)$/;
|
|
10
|
+
const SCALA_PLATFORM_SUFFIX_REGEX = /_(sjs1|sjs0\.6|native0\.\d+)$/;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse a Scala-published Maven artifact name into its coordinate parts.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} name Maven artifactId, for example `upickle_sjs1_3`
|
|
16
|
+
* @returns {{ originalName: string, artifactBase: string, binaryVersion: string|null,
|
|
17
|
+
* platformSuffix: string, purlName: string }} The artifact name without any
|
|
18
|
+
* Scala suffix (`upickle`), the Scala binary version from the suffix (`3`),
|
|
19
|
+
* the platform suffix including the underscore (`_sjs1`) or an empty string,
|
|
20
|
+
* and the artifact name sbt-style purls emit (binary suffix dropped, platform
|
|
21
|
+
* suffix kept).
|
|
22
|
+
*/
|
|
23
|
+
export function parseScalaArtifact(name) {
|
|
24
|
+
const result = {
|
|
25
|
+
originalName: name,
|
|
26
|
+
artifactBase: name,
|
|
27
|
+
binaryVersion: null,
|
|
28
|
+
platformSuffix: "",
|
|
29
|
+
purlName: name,
|
|
30
|
+
};
|
|
31
|
+
if (!name) {
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
let rest = name;
|
|
35
|
+
const binaryMatch = rest.match(SCALA_BINARY_SUFFIX_REGEX);
|
|
36
|
+
if (binaryMatch) {
|
|
37
|
+
result.binaryVersion = binaryMatch[1];
|
|
38
|
+
rest = rest.slice(0, rest.length - binaryMatch[0].length);
|
|
39
|
+
}
|
|
40
|
+
const platformMatch = rest.match(SCALA_PLATFORM_SUFFIX_REGEX);
|
|
41
|
+
if (platformMatch) {
|
|
42
|
+
result.platformSuffix = platformMatch[0];
|
|
43
|
+
rest = rest.slice(0, rest.length - platformMatch[0].length);
|
|
44
|
+
}
|
|
45
|
+
result.artifactBase = rest;
|
|
46
|
+
result.purlName = binaryMatch ? `${rest}${result.platformSuffix}` : name;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a qualifier-free coordinate key for a Scala-published artifact.
|
|
52
|
+
*
|
|
53
|
+
* The key ignores purl qualifiers such as `repository_url` and `type`, so the
|
|
54
|
+
* POM-coordinate keys of the jar namespace mapping and the sbt-style purls of
|
|
55
|
+
* the components resolve to the same entry.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} parts Coordinate parts
|
|
58
|
+
* @param {string} [parts.type] Purl type, defaults to `maven`
|
|
59
|
+
* @param {string} [parts.group] Maven groupId
|
|
60
|
+
* @param {string} parts.artifactBase Artifact name without any Scala suffix
|
|
61
|
+
* @param {string} [parts.platformSuffix] `_sjs1`, `_native0.5` or empty
|
|
62
|
+
* @param {string} [parts.version] Package version
|
|
63
|
+
* @returns {string} The coordinate key
|
|
64
|
+
*/
|
|
65
|
+
export function scalaCoordinateKey({
|
|
66
|
+
type = "maven",
|
|
67
|
+
group = "",
|
|
68
|
+
artifactBase = "",
|
|
69
|
+
platformSuffix = "",
|
|
70
|
+
version = "",
|
|
71
|
+
} = {}) {
|
|
72
|
+
return [type, group, artifactBase, platformSuffix, version].join(":");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build the qualifier-free coordinate key of a purl string.
|
|
77
|
+
*
|
|
78
|
+
* The artifact name is normalized with {@link parseScalaArtifact}, so
|
|
79
|
+
* `pkg:maven/g/foo_3@1.0` and the sbt-style `pkg:maven/g/foo@1.0` share a key,
|
|
80
|
+
* while the Scala.js build `foo_sjs1_3` keeps its own key (`foo` plus the
|
|
81
|
+
* `_sjs1` platform suffix) and never joins the plain `foo` component.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} purlString Package URL to normalize
|
|
84
|
+
* @returns {string|undefined} The coordinate key, or undefined when the string
|
|
85
|
+
* is not a parseable purl
|
|
86
|
+
*/
|
|
87
|
+
export function coordinateKeyFromPurl(purlString) {
|
|
88
|
+
return scalaCoordinateOfPurl(purlString)?.key;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The coordinate key of a purl together with the Scala binary version its
|
|
93
|
+
* artifact name carries, if any.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} purlString Package URL to normalize
|
|
96
|
+
* @returns {{ key: string, binaryVersion: string|null }|undefined} The key
|
|
97
|
+
* and binary version, or undefined when the string is not a parseable purl
|
|
98
|
+
*/
|
|
99
|
+
export function scalaCoordinateOfPurl(purlString) {
|
|
100
|
+
if (!purlString?.startsWith("pkg:")) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
let purlObj;
|
|
104
|
+
try {
|
|
105
|
+
purlObj = Purl.parse(purlString);
|
|
106
|
+
} catch (_err) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (!purlObj?.name) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const scalaInfo = parseScalaArtifact(purlObj.name);
|
|
113
|
+
return {
|
|
114
|
+
key: scalaCoordinateKey({
|
|
115
|
+
type: purlObj.type,
|
|
116
|
+
group: purlObj.namespace || "",
|
|
117
|
+
artifactBase: scalaInfo.artifactBase,
|
|
118
|
+
platformSuffix: scalaInfo.platformSuffix,
|
|
119
|
+
version: purlObj.version || "",
|
|
120
|
+
}),
|
|
121
|
+
binaryVersion: scalaInfo.binaryVersion,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// An artifact name that already ends in a full Scala version, as scala-cli
|
|
126
|
+
// `:::` and sbt `CrossVersion.full` artifacts do (`_3.3.7`, `_2.13.16`). A
|
|
127
|
+
// binary suffix such as `_3` left on a name is part of the name: the
|
|
128
|
+
// component producers strip exactly one, into the compiler version.
|
|
129
|
+
const SCALA_FULL_VERSION_SUFFIX_REGEX = /_\d+\.\d+\.\d+(?:-[A-Za-z0-9.]+)?$/;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The artifactId the library is published under, for cache and repository
|
|
133
|
+
* lookups only. Components a Scala build tool reports carry the name with the
|
|
134
|
+
* Scala binary suffix stripped (the platform suffix kept) together with
|
|
135
|
+
* `cdx:scala:compilerVersion`; on disk and in the repository the artifact is
|
|
136
|
+
* `cats-core_2.13` or `upickle_sjs1_3`. Names that end in a full Scala version
|
|
137
|
+
* are used as they are.
|
|
138
|
+
*
|
|
139
|
+
* The component's own `name`, `purl` and `bom-ref` never change.
|
|
140
|
+
*
|
|
141
|
+
* @param {Object} pkg Package with `name` and `properties`.
|
|
142
|
+
* @returns {string} The artifactId to look up.
|
|
143
|
+
*/
|
|
144
|
+
export function publishedArtifactId(pkg) {
|
|
145
|
+
const name = pkg?.name;
|
|
146
|
+
if (!name || SCALA_FULL_VERSION_SUFFIX_REGEX.test(name)) {
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
const binaryVersion = (pkg.properties || []).find(
|
|
150
|
+
(property) => property.name === "cdx:scala:compilerVersion",
|
|
151
|
+
)?.value;
|
|
152
|
+
return binaryVersion ? `${name}_${binaryVersion}` : name;
|
|
153
|
+
}
|