@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
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import {
|
|
4
|
+
basename,
|
|
5
|
+
dirname,
|
|
6
|
+
isAbsolute,
|
|
7
|
+
join,
|
|
8
|
+
relative,
|
|
9
|
+
resolve,
|
|
10
|
+
} from "node:path";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
|
|
13
|
+
import { readEnvironmentVariable } from "../core/activity.js";
|
|
14
|
+
import { parseMavenArgs } from "../core/env.js";
|
|
15
|
+
import { safeExistsSync } from "../core/fs.js";
|
|
16
|
+
import { registerRunReset } from "../core/runState.js";
|
|
17
|
+
import { xml2js } from "../parsers/xml.js";
|
|
18
|
+
|
|
19
|
+
// Local JVM artifact caches: the Maven local repository, the Gradle module
|
|
20
|
+
// cache and the Coursier cache. Every Maven Central request cdxgen can avoid
|
|
21
|
+
// starts with knowing where these live and what they already hold, so the
|
|
22
|
+
// lookups here are shared by license enrichment, jar identification and the
|
|
23
|
+
// sbt parsers.
|
|
24
|
+
|
|
25
|
+
const SHA1_HEX = /^[a-f0-9]{40}$/;
|
|
26
|
+
|
|
27
|
+
// Index walks stop after this many files so that a pathological cache cannot
|
|
28
|
+
// stall a scan. Real caches hold tens of thousands of artifacts.
|
|
29
|
+
const MAX_SHA1_INDEX_ENTRIES = 500_000;
|
|
30
|
+
|
|
31
|
+
// A repository prefix in the Coursier layout always starts with
|
|
32
|
+
// <protocol>/<host> and in practice adds at most a couple of path segments
|
|
33
|
+
// ("maven2", "content/repositories/releases"). The prefix scan stops at this
|
|
34
|
+
// depth so it never descends into the group directories.
|
|
35
|
+
const MAX_COURSIER_PREFIX_DEPTH = 5;
|
|
36
|
+
|
|
37
|
+
const settingsCache = new Map();
|
|
38
|
+
const repositoryListCache = new Map();
|
|
39
|
+
const artifactCache = new Map();
|
|
40
|
+
const coursierPrefixCache = new Map();
|
|
41
|
+
const coursierLocationCache = new Map();
|
|
42
|
+
let sha1Index;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Forget every memoised lookup. A long-lived process (server mode) calls this
|
|
46
|
+
* per scan so that artifacts a build downloaded in the meantime are found, and
|
|
47
|
+
* tests call it after changing the cache environment variables.
|
|
48
|
+
*
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
export function resetJvmLocalRepoCaches() {
|
|
52
|
+
settingsCache.clear();
|
|
53
|
+
repositoryListCache.clear();
|
|
54
|
+
artifactCache.clear();
|
|
55
|
+
coursierPrefixCache.clear();
|
|
56
|
+
coursierLocationCache.clear();
|
|
57
|
+
sha1Index = undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
registerRunReset("jvm:local-repositories", resetJvmLocalRepoCaches);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The user's home directory, preferring HOME/USERPROFILE so that tests and
|
|
64
|
+
* sandboxed runs that redirect the home directory are honoured.
|
|
65
|
+
*
|
|
66
|
+
* @returns {string|undefined} Home directory.
|
|
67
|
+
*/
|
|
68
|
+
function homeDir() {
|
|
69
|
+
return (
|
|
70
|
+
readEnvironmentVariable("HOME") ||
|
|
71
|
+
readEnvironmentVariable("USERPROFILE") ||
|
|
72
|
+
homedir()
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Add a path to a list once, resolved to an absolute path.
|
|
78
|
+
*
|
|
79
|
+
* @param {string[]} list Accumulator.
|
|
80
|
+
* @param {string|undefined} candidate Path to add.
|
|
81
|
+
* @returns {void}
|
|
82
|
+
*/
|
|
83
|
+
function pushUnique(list, candidate) {
|
|
84
|
+
if (!candidate || typeof candidate !== "string") {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const absolute = resolve(candidate);
|
|
88
|
+
if (!list.includes(absolute)) {
|
|
89
|
+
list.push(absolute);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Expand the property references Maven allows in a settings.xml
|
|
95
|
+
* `localRepository` element: `${user.home}` and `${env.NAME}`. Anything else is
|
|
96
|
+
* left as is, which makes the path fail the existence check rather than point
|
|
97
|
+
* somewhere unintended.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} value Raw element text.
|
|
100
|
+
* @returns {string} Expanded path.
|
|
101
|
+
*/
|
|
102
|
+
function expandMavenProperties(value) {
|
|
103
|
+
return (
|
|
104
|
+
value
|
|
105
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Maven property syntax, not a template.
|
|
106
|
+
.replaceAll("${user.home}", homeDir())
|
|
107
|
+
.replace(/\$\{env\.([A-Za-z0-9_]+)\}/g, (_match, name) => {
|
|
108
|
+
return readEnvironmentVariable(name) || "";
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Read `<localRepository>` from a Maven settings file.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} settingsFile Path to settings.xml.
|
|
117
|
+
* @returns {string|undefined} Absolute local repository path.
|
|
118
|
+
*/
|
|
119
|
+
export function localRepositoryFromSettings(settingsFile) {
|
|
120
|
+
if (!settingsFile || !safeExistsSync(settingsFile)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
let mtimeMs;
|
|
124
|
+
try {
|
|
125
|
+
mtimeMs = statSync(settingsFile).mtimeMs;
|
|
126
|
+
} catch (_err) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const cacheKey = `${settingsFile}|${mtimeMs}`;
|
|
130
|
+
if (settingsCache.has(cacheKey)) {
|
|
131
|
+
return settingsCache.get(cacheKey);
|
|
132
|
+
}
|
|
133
|
+
let localRepository;
|
|
134
|
+
try {
|
|
135
|
+
const settings = xml2js(readFileSync(settingsFile, "utf-8"), {
|
|
136
|
+
compact: true,
|
|
137
|
+
textKey: "_",
|
|
138
|
+
}).settings;
|
|
139
|
+
const raw = settings?.localRepository?._?.trim();
|
|
140
|
+
if (raw) {
|
|
141
|
+
localRepository = resolve(
|
|
142
|
+
dirname(settingsFile),
|
|
143
|
+
expandMavenProperties(raw),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
} catch (_err) {
|
|
147
|
+
localRepository = undefined;
|
|
148
|
+
}
|
|
149
|
+
settingsCache.set(cacheKey, localRepository);
|
|
150
|
+
return localRepository;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Pull the values cdxgen needs out of a Maven argument list: the
|
|
155
|
+
* `maven.repo.local` system property and the user settings file.
|
|
156
|
+
*
|
|
157
|
+
* @param {string[]} args Parsed arguments.
|
|
158
|
+
* @param {string} [baseDir] Directory relative paths resolve against.
|
|
159
|
+
* @returns {{repoLocal?: string, settingsFile?: string}}
|
|
160
|
+
*/
|
|
161
|
+
export function mavenRepoArgs(args, baseDir = process.cwd()) {
|
|
162
|
+
const result = {};
|
|
163
|
+
for (let i = 0; i < args.length; i++) {
|
|
164
|
+
const arg = args[i];
|
|
165
|
+
let property;
|
|
166
|
+
if (arg === "-D" && i + 1 < args.length) {
|
|
167
|
+
property = args[++i];
|
|
168
|
+
} else if (arg.startsWith("-D")) {
|
|
169
|
+
property = arg.substring(2);
|
|
170
|
+
}
|
|
171
|
+
if (property?.startsWith("maven.repo.local=")) {
|
|
172
|
+
const value = property.substring("maven.repo.local=".length);
|
|
173
|
+
if (value) {
|
|
174
|
+
result.repoLocal = resolve(baseDir, value);
|
|
175
|
+
}
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
let settingsFile;
|
|
179
|
+
if ((arg === "-s" || arg === "--settings") && i + 1 < args.length) {
|
|
180
|
+
settingsFile = args[++i];
|
|
181
|
+
} else if (arg.startsWith("--settings=")) {
|
|
182
|
+
settingsFile = arg.substring("--settings=".length);
|
|
183
|
+
}
|
|
184
|
+
if (settingsFile) {
|
|
185
|
+
result.settingsFile = resolve(baseDir, settingsFile);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The arguments that hand a scanned project's own `settings.xml` to Maven.
|
|
193
|
+
*
|
|
194
|
+
* The file is passed as global settings (`-gs`), so the user's
|
|
195
|
+
* `~/.m2/settings.xml` still applies and wins on mirrors, servers and proxies.
|
|
196
|
+
* Nothing is added, and `skipped` says why, in secure mode, where a scanned
|
|
197
|
+
* repository must not choose Maven's repositories, proxies or credentials, or
|
|
198
|
+
* when MVN_ARGS or MAVEN_ARGS already names a settings file.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} projectDir Directory holding the project's pom.xml.
|
|
201
|
+
* @param {{secureMode?: boolean}} [opts]
|
|
202
|
+
* @returns {{settingsFile?: string, args: string[], skipped?: "secure-mode"|"user-settings"}}
|
|
203
|
+
*/
|
|
204
|
+
export function projectSettingsArgs(projectDir, { secureMode = false } = {}) {
|
|
205
|
+
const settingsFile = join(projectDir, "settings.xml");
|
|
206
|
+
if (!safeExistsSync(settingsFile)) {
|
|
207
|
+
return { args: [] };
|
|
208
|
+
}
|
|
209
|
+
if (secureMode) {
|
|
210
|
+
return { settingsFile, args: [], skipped: "secure-mode" };
|
|
211
|
+
}
|
|
212
|
+
const userArgs = ["MVN_ARGS", "MAVEN_ARGS"].flatMap((name) =>
|
|
213
|
+
parseMavenArgs(readEnvironmentVariable(name)),
|
|
214
|
+
);
|
|
215
|
+
const namesSettings = userArgs.some(
|
|
216
|
+
(arg) =>
|
|
217
|
+
["-s", "--settings", "-gs", "--global-settings"].includes(arg) ||
|
|
218
|
+
arg.startsWith("--settings=") ||
|
|
219
|
+
arg.startsWith("--global-settings="),
|
|
220
|
+
);
|
|
221
|
+
if (namesSettings) {
|
|
222
|
+
return { settingsFile, args: [], skipped: "user-settings" };
|
|
223
|
+
}
|
|
224
|
+
return { settingsFile, args: ["-gs", settingsFile] };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Every Maven local repository this run may have populated, most specific
|
|
229
|
+
* first: MAVEN_CACHE_DIR, then an explicit `-Dmaven.repo.local` (from
|
|
230
|
+
* MVN_ARGS, MAVEN_ARGS, MAVEN_OPTS or the project's `.mvn/maven.config`), then
|
|
231
|
+
* `<localRepository>` from the settings Maven would read, then
|
|
232
|
+
* `~/.m2/repository`.
|
|
233
|
+
*
|
|
234
|
+
* @param {Object} [opts]
|
|
235
|
+
* @param {string} [opts.projectDir] Project directory, for `.mvn/maven.config`.
|
|
236
|
+
* @returns {string[]} Absolute paths. Not filtered for existence.
|
|
237
|
+
*/
|
|
238
|
+
export function mavenLocalRepositories({ projectDir } = {}) {
|
|
239
|
+
const signature = [
|
|
240
|
+
"MVN_ARGS",
|
|
241
|
+
"MAVEN_ARGS",
|
|
242
|
+
"MAVEN_OPTS",
|
|
243
|
+
"MAVEN_CACHE_DIR",
|
|
244
|
+
"MAVEN_HOME",
|
|
245
|
+
"M2_HOME",
|
|
246
|
+
"HOME",
|
|
247
|
+
"USERPROFILE",
|
|
248
|
+
]
|
|
249
|
+
.map((name) => readEnvironmentVariable(name) || "")
|
|
250
|
+
.concat(projectDir || "")
|
|
251
|
+
.join("\u0000");
|
|
252
|
+
if (repositoryListCache.has(signature)) {
|
|
253
|
+
return repositoryListCache.get(signature);
|
|
254
|
+
}
|
|
255
|
+
const repos = [];
|
|
256
|
+
pushUnique(repos, readEnvironmentVariable("MAVEN_CACHE_DIR"));
|
|
257
|
+
const settingsFiles = [];
|
|
258
|
+
const argSources = ["MVN_ARGS", "MAVEN_ARGS", "MAVEN_OPTS"].map((name) =>
|
|
259
|
+
parseMavenArgs(readEnvironmentVariable(name)),
|
|
260
|
+
);
|
|
261
|
+
if (projectDir) {
|
|
262
|
+
const mavenConfig = join(projectDir, ".mvn", "maven.config");
|
|
263
|
+
if (safeExistsSync(mavenConfig)) {
|
|
264
|
+
try {
|
|
265
|
+
argSources.push(parseMavenArgs(readFileSync(mavenConfig, "utf-8")));
|
|
266
|
+
} catch (_err) {
|
|
267
|
+
// An unreadable maven.config contributes nothing.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
for (const args of argSources) {
|
|
272
|
+
const { repoLocal, settingsFile } = mavenRepoArgs(
|
|
273
|
+
args,
|
|
274
|
+
projectDir || process.cwd(),
|
|
275
|
+
);
|
|
276
|
+
pushUnique(repos, repoLocal);
|
|
277
|
+
pushUnique(settingsFiles, settingsFile);
|
|
278
|
+
}
|
|
279
|
+
pushUnique(settingsFiles, join(homeDir(), ".m2", "settings.xml"));
|
|
280
|
+
const mavenHome =
|
|
281
|
+
readEnvironmentVariable("MAVEN_HOME") || readEnvironmentVariable("M2_HOME");
|
|
282
|
+
if (mavenHome) {
|
|
283
|
+
pushUnique(settingsFiles, join(mavenHome, "conf", "settings.xml"));
|
|
284
|
+
}
|
|
285
|
+
for (const settingsFile of settingsFiles) {
|
|
286
|
+
pushUnique(repos, localRepositoryFromSettings(settingsFile));
|
|
287
|
+
}
|
|
288
|
+
pushUnique(repos, join(homeDir(), ".m2", "repository"));
|
|
289
|
+
repositoryListCache.set(signature, repos);
|
|
290
|
+
return repos;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Gradle module cache directories (`.../modules-2/files-2.1`): the one under
|
|
295
|
+
* GRADLE_USER_HOME, the cdxgen-specific GRADLE_CACHE_DIR, the default
|
|
296
|
+
* `~/.gradle` when neither is set, and the read-only shared cache named by
|
|
297
|
+
* GRADLE_RO_DEP_CACHE.
|
|
298
|
+
*
|
|
299
|
+
* @returns {string[]} Absolute paths. Not filtered for existence.
|
|
300
|
+
*/
|
|
301
|
+
export function gradleCacheRoots() {
|
|
302
|
+
const roots = [];
|
|
303
|
+
const gradleUserHome = readEnvironmentVariable("GRADLE_USER_HOME");
|
|
304
|
+
const gradleCacheDir = readEnvironmentVariable("GRADLE_CACHE_DIR");
|
|
305
|
+
if (gradleUserHome) {
|
|
306
|
+
pushUnique(roots, join(gradleUserHome, "caches", "modules-2", "files-2.1"));
|
|
307
|
+
}
|
|
308
|
+
pushUnique(roots, gradleCacheDir);
|
|
309
|
+
if (!gradleUserHome && !gradleCacheDir) {
|
|
310
|
+
pushUnique(
|
|
311
|
+
roots,
|
|
312
|
+
join(homeDir(), ".gradle", "caches", "modules-2", "files-2.1"),
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const readOnlyCache = readEnvironmentVariable("GRADLE_RO_DEP_CACHE");
|
|
316
|
+
if (readOnlyCache) {
|
|
317
|
+
pushUnique(roots, join(readOnlyCache, "modules-2", "files-2.1"));
|
|
318
|
+
}
|
|
319
|
+
return roots;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The Coursier cache directory used by sbt, Mill and scala-cli: COURSIER_CACHE
|
|
324
|
+
* when set, otherwise the platform default (XDG_CACHE_HOME is honoured on
|
|
325
|
+
* Linux).
|
|
326
|
+
*
|
|
327
|
+
* @returns {string|undefined} Absolute path. Not checked for existence.
|
|
328
|
+
*/
|
|
329
|
+
export function coursierCacheDir() {
|
|
330
|
+
const configured = readEnvironmentVariable("COURSIER_CACHE");
|
|
331
|
+
if (configured) {
|
|
332
|
+
return resolve(configured);
|
|
333
|
+
}
|
|
334
|
+
const home = homeDir();
|
|
335
|
+
if (!home) {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
if (process.platform === "darwin") {
|
|
339
|
+
return join(home, "Library", "Caches", "Coursier", "v1");
|
|
340
|
+
}
|
|
341
|
+
if (process.platform === "win32") {
|
|
342
|
+
const localAppData = readEnvironmentVariable("LOCALAPPDATA");
|
|
343
|
+
return localAppData
|
|
344
|
+
? join(localAppData, "Coursier", "Cache", "v1")
|
|
345
|
+
: join(home, "AppData", "Local", "Coursier", "Cache", "v1");
|
|
346
|
+
}
|
|
347
|
+
const xdgCache = readEnvironmentVariable("XDG_CACHE_HOME");
|
|
348
|
+
return join(xdgCache || join(home, ".cache"), "coursier", "v1");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Repository prefixes present in a Coursier cache, found with a shallow,
|
|
353
|
+
* depth-bounded scan and memoised per cache root. See cdxgen issue 4291 for
|
|
354
|
+
* why this replaced a recursive glob per coordinate.
|
|
355
|
+
*
|
|
356
|
+
* @param {string} cacheRoot Coursier cache root.
|
|
357
|
+
* @returns {string[][]} Prefixes as path segments relative to the root.
|
|
358
|
+
*/
|
|
359
|
+
function coursierRepoPrefixes(cacheRoot) {
|
|
360
|
+
if (coursierPrefixCache.has(cacheRoot)) {
|
|
361
|
+
return coursierPrefixCache.get(cacheRoot);
|
|
362
|
+
}
|
|
363
|
+
const prefixes = [];
|
|
364
|
+
// Breadth-first so that shallower prefixes, the common ones, come first.
|
|
365
|
+
let frontier = [[]];
|
|
366
|
+
for (let depth = 1; depth <= MAX_COURSIER_PREFIX_DEPTH; depth++) {
|
|
367
|
+
const next = [];
|
|
368
|
+
for (const parts of frontier) {
|
|
369
|
+
let entries;
|
|
370
|
+
try {
|
|
371
|
+
entries = readdirSync(join(cacheRoot, ...parts), {
|
|
372
|
+
withFileTypes: true,
|
|
373
|
+
});
|
|
374
|
+
} catch (_err) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
if (!entry.isDirectory()) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const childParts = [...parts, entry.name];
|
|
382
|
+
// <protocol>/<host> is the shortest thing that can be a repository.
|
|
383
|
+
if (depth >= 2) {
|
|
384
|
+
prefixes.push(childParts);
|
|
385
|
+
}
|
|
386
|
+
next.push(childParts);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
frontier = next;
|
|
390
|
+
if (!frontier.length) {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
coursierPrefixCache.set(cacheRoot, prefixes);
|
|
395
|
+
return prefixes;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Convert a Coursier repository prefix into the repository URL it caches.
|
|
400
|
+
*
|
|
401
|
+
* @param {string[]} parts Prefix segments relative to the cache root.
|
|
402
|
+
* @returns {string|null} Repository URL.
|
|
403
|
+
*/
|
|
404
|
+
export function coursierPrefixToUrl(parts) {
|
|
405
|
+
if (!parts || parts.length < 2) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const protocol = parts[0];
|
|
409
|
+
if (protocol === "file") {
|
|
410
|
+
let localPath = parts.slice(1).join("/");
|
|
411
|
+
if (!localPath.startsWith("/")) {
|
|
412
|
+
localPath = `/${localPath}`;
|
|
413
|
+
}
|
|
414
|
+
return `file://${localPath}`;
|
|
415
|
+
}
|
|
416
|
+
const host = parts[1];
|
|
417
|
+
const repoPath = parts.slice(2).join("/");
|
|
418
|
+
let repoUrl = `${protocol}://${host}`;
|
|
419
|
+
if (repoPath) {
|
|
420
|
+
repoUrl += `/${repoPath}`;
|
|
421
|
+
}
|
|
422
|
+
return repoUrl.endsWith("/") ? repoUrl.slice(0, -1) : repoUrl;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Locate a Maven coordinate's directory in the Coursier cache.
|
|
427
|
+
*
|
|
428
|
+
* @param {string} group Maven groupId.
|
|
429
|
+
* @param {string} name Maven artifactId, including any Scala suffix.
|
|
430
|
+
* @param {string} version Version.
|
|
431
|
+
* @returns {{repoUrl: string, dir: string}|null} Repository URL and directory.
|
|
432
|
+
*/
|
|
433
|
+
export function locateInCoursierCache(group, name, version) {
|
|
434
|
+
if (!group || !name || !version) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
const cacheRoot = coursierCacheDir();
|
|
438
|
+
if (!cacheRoot || !safeExistsSync(cacheRoot)) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
// The root is part of the key so a changed COURSIER_CACHE is never answered
|
|
442
|
+
// from a memo built against the previous one.
|
|
443
|
+
const cacheKey = `${cacheRoot}|${group}:${name}:${version}`;
|
|
444
|
+
if (coursierLocationCache.has(cacheKey)) {
|
|
445
|
+
return coursierLocationCache.get(cacheKey);
|
|
446
|
+
}
|
|
447
|
+
const groupParts = group.split(".");
|
|
448
|
+
const prefixes = coursierRepoPrefixes(cacheRoot);
|
|
449
|
+
let result = null;
|
|
450
|
+
for (let i = 0; i < prefixes.length; i++) {
|
|
451
|
+
const dir = join(cacheRoot, ...prefixes[i], ...groupParts, name, version);
|
|
452
|
+
if (safeExistsSync(dir)) {
|
|
453
|
+
const repoUrl = coursierPrefixToUrl(prefixes[i]);
|
|
454
|
+
if (repoUrl) {
|
|
455
|
+
result = { repoUrl, dir };
|
|
456
|
+
// Most coordinates in a build come from a handful of repositories, so
|
|
457
|
+
// the prefix that just matched moves to the front.
|
|
458
|
+
if (i > 0) {
|
|
459
|
+
prefixes.unshift(prefixes.splice(i, 1)[0]);
|
|
460
|
+
}
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
coursierLocationCache.set(cacheKey, result);
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* The file name a Maven artifact is stored under.
|
|
471
|
+
*
|
|
472
|
+
* @param {string} name artifactId.
|
|
473
|
+
* @param {string} version Version.
|
|
474
|
+
* @param {string} [classifier] Classifier.
|
|
475
|
+
* @param {string} [extension] Extension without the dot.
|
|
476
|
+
* @returns {string} File name.
|
|
477
|
+
*/
|
|
478
|
+
function artifactFileName(name, version, classifier, extension) {
|
|
479
|
+
return `${name}-${version}${classifier ? `-${classifier}` : ""}.${extension}`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Find a file with an exact name in any immediate subdirectory of `dir`. The
|
|
484
|
+
* Gradle module cache keeps every file under a directory named after its
|
|
485
|
+
* SHA-1, so the name is known but the directory is not.
|
|
486
|
+
*
|
|
487
|
+
* @param {string} dir Version directory in the Gradle cache.
|
|
488
|
+
* @param {string} fileName Exact file name.
|
|
489
|
+
* @returns {{path: string, sha1: string}|undefined}
|
|
490
|
+
*/
|
|
491
|
+
function findInHashedDirs(dir, fileName) {
|
|
492
|
+
let entries;
|
|
493
|
+
try {
|
|
494
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
495
|
+
} catch (_err) {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
for (const entry of entries) {
|
|
499
|
+
if (!entry.isDirectory()) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const candidate = join(dir, entry.name, fileName);
|
|
503
|
+
if (safeExistsSync(candidate)) {
|
|
504
|
+
return { path: candidate, sha1: entry.name };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Locate a Maven artifact and its POM in the local caches.
|
|
512
|
+
*
|
|
513
|
+
* Only the exact file names Maven, Gradle and Coursier use are accepted, so a
|
|
514
|
+
* classifier jar (`-sources`, `-javadoc`, `-linux-x86_64`) is never mistaken
|
|
515
|
+
* for the main artifact. Results, including misses, are memoised until
|
|
516
|
+
* {@link resetJvmLocalRepoCaches}.
|
|
517
|
+
*
|
|
518
|
+
* @param {string} group Maven groupId.
|
|
519
|
+
* @param {string} name Maven artifactId.
|
|
520
|
+
* @param {string} version Version.
|
|
521
|
+
* @param {Object} [opts]
|
|
522
|
+
* @param {string} [opts.classifier] Classifier of the wanted jar.
|
|
523
|
+
* @param {string} [opts.extension] Artifact extension. Defaults to jar.
|
|
524
|
+
* @param {string} [opts.projectDir] Project directory, for `.mvn/maven.config`.
|
|
525
|
+
* @returns {{jarPath?: string, pomPath?: string, sha1?: string, repoUrl?: string}|null}
|
|
526
|
+
*/
|
|
527
|
+
export function findLocalMavenArtifact(group, name, version, opts = {}) {
|
|
528
|
+
if (!group || !name || !version) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const classifier = opts.classifier || "";
|
|
532
|
+
const extension = opts.extension || "jar";
|
|
533
|
+
const mavenRepos = mavenLocalRepositories({ projectDir: opts.projectDir });
|
|
534
|
+
const gradleRoots = gradleCacheRoots();
|
|
535
|
+
const coursierRoot = coursierCacheDir();
|
|
536
|
+
const cacheKey = [
|
|
537
|
+
group,
|
|
538
|
+
name,
|
|
539
|
+
version,
|
|
540
|
+
classifier,
|
|
541
|
+
extension,
|
|
542
|
+
mavenRepos.join(","),
|
|
543
|
+
gradleRoots.join(","),
|
|
544
|
+
coursierRoot,
|
|
545
|
+
].join("|");
|
|
546
|
+
if (artifactCache.has(cacheKey)) {
|
|
547
|
+
return artifactCache.get(cacheKey);
|
|
548
|
+
}
|
|
549
|
+
const jarName = artifactFileName(name, version, classifier, extension);
|
|
550
|
+
const pomName = artifactFileName(name, version, "", "pom");
|
|
551
|
+
const result = {};
|
|
552
|
+
const groupPath = group.split(".");
|
|
553
|
+
for (const repo of mavenRepos) {
|
|
554
|
+
const dir = join(repo, ...groupPath, name, version);
|
|
555
|
+
if (!safeExistsSync(dir)) {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (!result.jarPath && safeExistsSync(join(dir, jarName))) {
|
|
559
|
+
result.jarPath = join(dir, jarName);
|
|
560
|
+
}
|
|
561
|
+
if (!result.pomPath && safeExistsSync(join(dir, pomName))) {
|
|
562
|
+
result.pomPath = join(dir, pomName);
|
|
563
|
+
}
|
|
564
|
+
if (result.jarPath && result.pomPath) {
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (!result.jarPath || !result.pomPath) {
|
|
569
|
+
for (const root of gradleRoots) {
|
|
570
|
+
const dir = join(root, group, name, version);
|
|
571
|
+
if (!safeExistsSync(dir)) {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (!result.jarPath) {
|
|
575
|
+
const jar = findInHashedDirs(dir, jarName);
|
|
576
|
+
if (jar) {
|
|
577
|
+
result.jarPath = jar.path;
|
|
578
|
+
if (SHA1_HEX.test(jar.sha1)) {
|
|
579
|
+
result.sha1 = jar.sha1;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (!result.pomPath) {
|
|
584
|
+
result.pomPath = findInHashedDirs(dir, pomName)?.path;
|
|
585
|
+
}
|
|
586
|
+
if (result.jarPath && result.pomPath) {
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (!result.jarPath || !result.pomPath) {
|
|
592
|
+
const location = locateInCoursierCache(group, name, version);
|
|
593
|
+
if (location) {
|
|
594
|
+
if (!result.jarPath && safeExistsSync(join(location.dir, jarName))) {
|
|
595
|
+
result.jarPath = join(location.dir, jarName);
|
|
596
|
+
}
|
|
597
|
+
if (!result.pomPath && safeExistsSync(join(location.dir, pomName))) {
|
|
598
|
+
result.pomPath = join(location.dir, pomName);
|
|
599
|
+
}
|
|
600
|
+
if (result.jarPath || result.pomPath) {
|
|
601
|
+
result.repoUrl = location.repoUrl;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const found = result.jarPath || result.pomPath ? result : null;
|
|
606
|
+
artifactCache.set(cacheKey, found);
|
|
607
|
+
return found;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Split an artifact file name into its classifier and extension, given the
|
|
612
|
+
* artifactId and version it belongs to.
|
|
613
|
+
*
|
|
614
|
+
* @param {string} fileName File name, for example `netty-4.1.1-linux.jar`.
|
|
615
|
+
* @param {string} name artifactId.
|
|
616
|
+
* @param {string} version Version.
|
|
617
|
+
* @returns {{classifier: string, extension: string}|undefined} Undefined when
|
|
618
|
+
* the name does not belong to the coordinate.
|
|
619
|
+
*/
|
|
620
|
+
export function splitArtifactFileName(fileName, name, version) {
|
|
621
|
+
const prefix = `${name}-${version}`;
|
|
622
|
+
if (!fileName.startsWith(prefix)) {
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
const rest = fileName.substring(prefix.length);
|
|
626
|
+
const dot = rest.lastIndexOf(".");
|
|
627
|
+
if (dot === -1) {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
const extension = rest.substring(dot + 1);
|
|
631
|
+
const classifierPart = rest.substring(0, dot);
|
|
632
|
+
if (!classifierPart) {
|
|
633
|
+
return { classifier: "", extension };
|
|
634
|
+
}
|
|
635
|
+
if (!classifierPart.startsWith("-") || classifierPart.length < 2) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
return { classifier: classifierPart.substring(1), extension };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Coordinates from a path in the Maven repository layout:
|
|
643
|
+
* `<root>/<group path>/<artifactId>/<version>/<artifactId>-<version>[-<classifier>].<ext>`.
|
|
644
|
+
*
|
|
645
|
+
* @param {string[]} parts Path segments below the repository root.
|
|
646
|
+
* @returns {Object|undefined}
|
|
647
|
+
*/
|
|
648
|
+
function coordinatesFromMavenLayout(parts) {
|
|
649
|
+
if (parts.length < 4) {
|
|
650
|
+
return undefined;
|
|
651
|
+
}
|
|
652
|
+
const fileName = parts[parts.length - 1];
|
|
653
|
+
const version = parts[parts.length - 2];
|
|
654
|
+
const name = parts[parts.length - 3];
|
|
655
|
+
const group = parts.slice(0, -3).join(".");
|
|
656
|
+
let split = splitArtifactFileName(fileName, name, version);
|
|
657
|
+
if (!split && version.endsWith("-SNAPSHOT")) {
|
|
658
|
+
// Timestamped snapshot files replace -SNAPSHOT with the build stamp.
|
|
659
|
+
const base = version.substring(0, version.length - "-SNAPSHOT".length);
|
|
660
|
+
const stamp = /^-\d{8}\.\d{6}-\d+/.exec(
|
|
661
|
+
fileName.substring(`${name}-${base}`.length),
|
|
662
|
+
);
|
|
663
|
+
if (fileName.startsWith(`${name}-${base}`) && stamp) {
|
|
664
|
+
split = splitArtifactFileName(fileName, name, `${base}${stamp[0]}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (!group || !split) {
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
return { group, name, version, ...split };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Coordinates from a path in the Gradle module cache layout:
|
|
675
|
+
* `<root>/<group>/<artifactId>/<version>/<sha1>/<file>`.
|
|
676
|
+
*
|
|
677
|
+
* @param {string[]} parts Path segments below the cache root.
|
|
678
|
+
* @returns {Object|undefined}
|
|
679
|
+
*/
|
|
680
|
+
function coordinatesFromGradleLayout(parts) {
|
|
681
|
+
if (parts.length !== 5) {
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
684
|
+
const [group, name, version, sha1, fileName] = parts;
|
|
685
|
+
const split = splitArtifactFileName(fileName, name, version);
|
|
686
|
+
if (!split) {
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
group,
|
|
691
|
+
name,
|
|
692
|
+
version,
|
|
693
|
+
...split,
|
|
694
|
+
...(SHA1_HEX.test(sha1) ? { sha1 } : {}),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Segments of `filePath` below `root`, or undefined when it is not inside.
|
|
700
|
+
*
|
|
701
|
+
* @param {string} root Root directory.
|
|
702
|
+
* @param {string} filePath File path.
|
|
703
|
+
* @returns {string[]|undefined}
|
|
704
|
+
*/
|
|
705
|
+
function segmentsBelow(root, filePath) {
|
|
706
|
+
const rel = relative(root, filePath);
|
|
707
|
+
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
return rel.split(/[\\/]/).filter(Boolean);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Infer Maven coordinates from where an artifact sits on disk.
|
|
715
|
+
*
|
|
716
|
+
* Paths inside a configured Maven repository or Gradle cache are read by
|
|
717
|
+
* their layout. Paths elsewhere are recognised by the conventional
|
|
718
|
+
* `.m2/repository` and `files-2.1` segments, so a copied cache still works. A
|
|
719
|
+
* Coursier path is resolved through its sibling POM, because the repository
|
|
720
|
+
* prefix and the group path cannot be told apart from the path alone.
|
|
721
|
+
*
|
|
722
|
+
* @param {string} filePath Absolute path of a jar or other artifact.
|
|
723
|
+
* @returns {{group: string, name: string, version: string, classifier: string, extension: string, sha1?: string}|undefined}
|
|
724
|
+
*/
|
|
725
|
+
export function inferMavenCoordinatesFromPath(filePath) {
|
|
726
|
+
if (!filePath) {
|
|
727
|
+
return undefined;
|
|
728
|
+
}
|
|
729
|
+
const absolute = resolve(filePath);
|
|
730
|
+
for (const repo of mavenLocalRepositories()) {
|
|
731
|
+
const parts = segmentsBelow(repo, absolute);
|
|
732
|
+
if (parts) {
|
|
733
|
+
return coordinatesFromMavenLayout(parts);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
for (const root of gradleCacheRoots()) {
|
|
737
|
+
const parts = segmentsBelow(root, absolute);
|
|
738
|
+
if (parts) {
|
|
739
|
+
return coordinatesFromGradleLayout(parts);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const segments = absolute.split(/[\\/]/);
|
|
743
|
+
for (let i = segments.length - 2; i > 0; i--) {
|
|
744
|
+
if (segments[i] === "repository" && segments[i - 1] === ".m2") {
|
|
745
|
+
return coordinatesFromMavenLayout(segments.slice(i + 1));
|
|
746
|
+
}
|
|
747
|
+
if (segments[i] === "files-2.1") {
|
|
748
|
+
return coordinatesFromGradleLayout(segments.slice(i + 1));
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const coursierRoot = coursierCacheDir();
|
|
752
|
+
if (coursierRoot && segmentsBelow(coursierRoot, absolute)) {
|
|
753
|
+
const fileName = basename(absolute);
|
|
754
|
+
const version = basename(dirname(absolute));
|
|
755
|
+
const name = basename(dirname(dirname(absolute)));
|
|
756
|
+
const pomPath = join(dirname(absolute), `${name}-${version}.pom`);
|
|
757
|
+
const split = splitArtifactFileName(fileName, name, version);
|
|
758
|
+
if (split && safeExistsSync(pomPath)) {
|
|
759
|
+
try {
|
|
760
|
+
const project = xml2js(readFileSync(pomPath, "utf-8"), {
|
|
761
|
+
compact: true,
|
|
762
|
+
textKey: "_",
|
|
763
|
+
}).project;
|
|
764
|
+
const group = project?.groupId?._ || project?.parent?.groupId?._;
|
|
765
|
+
if (group && (project?.artifactId?._ || name) === name) {
|
|
766
|
+
return { group, name, version, ...split };
|
|
767
|
+
}
|
|
768
|
+
} catch (_err) {
|
|
769
|
+
return undefined;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return undefined;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Read the SHA-1 recorded in a Maven `*.sha1` checksum file.
|
|
778
|
+
*
|
|
779
|
+
* @param {string} file Checksum file.
|
|
780
|
+
* @returns {string|undefined} Lowercase hex digest.
|
|
781
|
+
*/
|
|
782
|
+
function readSha1File(file) {
|
|
783
|
+
try {
|
|
784
|
+
const digest = readFileSync(file, "utf-8").trim().split(/\s+/)[0];
|
|
785
|
+
const lower = digest?.toLowerCase();
|
|
786
|
+
return SHA1_HEX.test(lower) ? lower : undefined;
|
|
787
|
+
} catch (_err) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Walk a Maven repository and record `<jar>.sha1` digests.
|
|
794
|
+
*
|
|
795
|
+
* @param {string} dir Directory to walk.
|
|
796
|
+
* @param {Map<string, Object>} index Index being built.
|
|
797
|
+
* @param {string} repoRoot Repository root.
|
|
798
|
+
* @returns {void}
|
|
799
|
+
*/
|
|
800
|
+
function indexMavenRepository(dir, index, repoRoot) {
|
|
801
|
+
const stack = [dir];
|
|
802
|
+
while (stack.length && index.size < MAX_SHA1_INDEX_ENTRIES) {
|
|
803
|
+
const current = stack.pop();
|
|
804
|
+
let entries;
|
|
805
|
+
try {
|
|
806
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
807
|
+
} catch (_err) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
for (const entry of entries) {
|
|
811
|
+
const entryPath = join(current, entry.name);
|
|
812
|
+
if (entry.isDirectory()) {
|
|
813
|
+
stack.push(entryPath);
|
|
814
|
+
} else if (entry.name.endsWith(".jar.sha1")) {
|
|
815
|
+
const digest = readSha1File(entryPath);
|
|
816
|
+
const jarPath = entryPath.substring(0, entryPath.length - 5);
|
|
817
|
+
const parts = segmentsBelow(repoRoot, jarPath);
|
|
818
|
+
const coordinates = parts && coordinatesFromMavenLayout(parts);
|
|
819
|
+
if (digest && coordinates && !index.has(digest)) {
|
|
820
|
+
index.set(digest, { ...coordinates, path: jarPath });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Walk a Gradle module cache. The SHA-1 is the directory name, so no file is
|
|
829
|
+
* read: `<root>/<group>/<artifactId>/<version>/<sha1>/<file>`.
|
|
830
|
+
*
|
|
831
|
+
* @param {string} root Cache root.
|
|
832
|
+
* @param {Map<string, Object>} index Index being built.
|
|
833
|
+
* @returns {void}
|
|
834
|
+
*/
|
|
835
|
+
function indexGradleCache(root, index) {
|
|
836
|
+
const list = (dir) => {
|
|
837
|
+
try {
|
|
838
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) =>
|
|
839
|
+
e.isDirectory(),
|
|
840
|
+
);
|
|
841
|
+
} catch (_err) {
|
|
842
|
+
return [];
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
for (const groupEntry of list(root)) {
|
|
846
|
+
const groupDir = join(root, groupEntry.name);
|
|
847
|
+
for (const nameEntry of list(groupDir)) {
|
|
848
|
+
const nameDir = join(groupDir, nameEntry.name);
|
|
849
|
+
for (const versionEntry of list(nameDir)) {
|
|
850
|
+
const versionDir = join(nameDir, versionEntry.name);
|
|
851
|
+
for (const hashEntry of list(versionDir)) {
|
|
852
|
+
if (index.size >= MAX_SHA1_INDEX_ENTRIES) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const digest = hashEntry.name.toLowerCase();
|
|
856
|
+
if (!SHA1_HEX.test(digest) || index.has(digest)) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
let files;
|
|
860
|
+
try {
|
|
861
|
+
files = readdirSync(join(versionDir, hashEntry.name));
|
|
862
|
+
} catch (_err) {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
for (const fileName of files) {
|
|
866
|
+
const coordinates = coordinatesFromGradleLayout([
|
|
867
|
+
groupEntry.name,
|
|
868
|
+
nameEntry.name,
|
|
869
|
+
versionEntry.name,
|
|
870
|
+
hashEntry.name,
|
|
871
|
+
fileName,
|
|
872
|
+
]);
|
|
873
|
+
if (coordinates?.extension === "jar") {
|
|
874
|
+
index.set(digest, {
|
|
875
|
+
...coordinates,
|
|
876
|
+
path: join(versionDir, hashEntry.name, fileName),
|
|
877
|
+
});
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Look up the Maven coordinates of a jar by its SHA-1 in the local caches.
|
|
889
|
+
*
|
|
890
|
+
* The index is built once, on first use, from the Gradle module cache (whose
|
|
891
|
+
* directory names are SHA-1s) and from the `*.jar.sha1` files Maven writes
|
|
892
|
+
* next to every downloaded jar. It answers the question cdxgen would otherwise
|
|
893
|
+
* send to the Maven Central search API.
|
|
894
|
+
*
|
|
895
|
+
* @param {string} sha1 Hex SHA-1 of the jar.
|
|
896
|
+
* @returns {{group: string, name: string, version: string, classifier: string, extension: string, path: string}|undefined}
|
|
897
|
+
*/
|
|
898
|
+
export function findMavenCoordinatesBySha1(sha1) {
|
|
899
|
+
const digest = sha1?.toLowerCase();
|
|
900
|
+
if (!digest || !SHA1_HEX.test(digest)) {
|
|
901
|
+
return undefined;
|
|
902
|
+
}
|
|
903
|
+
if (!sha1Index) {
|
|
904
|
+
sha1Index = new Map();
|
|
905
|
+
for (const root of gradleCacheRoots()) {
|
|
906
|
+
if (safeExistsSync(root)) {
|
|
907
|
+
indexGradleCache(root, sha1Index);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
for (const repo of mavenLocalRepositories()) {
|
|
911
|
+
if (safeExistsSync(repo)) {
|
|
912
|
+
indexMavenRepository(repo, sha1Index, repo);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return sha1Index.get(digest);
|
|
917
|
+
}
|