@cdxgen/cdxgen 13.0.1 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -16
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +141 -7
- package/bin/evinse.js +3 -1
- package/data/component-tags.json +8 -1
- package/data/crypto-oid.json +88 -12
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1167 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +32 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +268 -49
- package/lib/cli/jsBom.js +91 -5
- package/lib/cli/jvmBom.js +1515 -1314
- package/lib/cli/managedBom.js +249 -28
- package/lib/cli/nativeBom.js +986 -10
- package/lib/core/activity.js +120 -2
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +108 -2
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +250 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +498 -17
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +188 -2
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +172 -36
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +944 -19
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +141 -13
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/auditCategories.js +17 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +508 -22
- package/lib/inventory/envcontext.js +215 -24
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +131 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +594 -0
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/osinfo.js +1 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +250 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +284 -3
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +26 -26
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +10 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +43 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +53 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +21 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +34 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +12 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +43 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +43 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +35 -2
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { readEnvironmentVariable } from "../core/activity.js";
|
|
5
|
+
import { JVM_BUILD_TOOL_TYPE_PREFIXES } from "../core/env.js";
|
|
6
|
+
import { safeExistsSync } from "../core/fs.js";
|
|
7
|
+
import {
|
|
8
|
+
compareVersions,
|
|
9
|
+
extractJavaMajor,
|
|
10
|
+
isPrereleaseVersion,
|
|
11
|
+
} from "./toolRequirements.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The general-purpose version comparator, prerelease classifier, and JDK
|
|
15
|
+
* major extractor live in `toolRequirements.js` so every ecosystem shares one
|
|
16
|
+
* comparator; they are re-exported here under the names this module's
|
|
17
|
+
* consumers already use.
|
|
18
|
+
*/
|
|
19
|
+
export {
|
|
20
|
+
compareVersions as compareSdkmanVersions,
|
|
21
|
+
extractJavaMajor,
|
|
22
|
+
isPrereleaseVersion,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Upper bound on the `.sdkmanrc` content that is scanned, so that a corrupt or
|
|
27
|
+
* hostile file cannot force cdxgen to walk unbounded content.
|
|
28
|
+
*
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
const MAX_SDKMANRC_BYTES = 64 * 1024;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Maximum accepted length for a single sdkman version identifier.
|
|
35
|
+
*
|
|
36
|
+
* @type {number}
|
|
37
|
+
*/
|
|
38
|
+
const MAX_VERSION_TOKEN_LENGTH = 64;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* sdkman candidate names that cdxgen can provision for JVM builds. The keys
|
|
42
|
+
* are the canonical candidate identifiers used with `sdk install <candidate>`.
|
|
43
|
+
*/
|
|
44
|
+
const JVM_BUILD_TOOL_CANDIDATES = ["maven", "gradle", "sbt", "scala"];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Anchored validator for sdkman version identifiers such as `3.9.9`,
|
|
48
|
+
* `4.0.0-rc-5`, `8.14`, `21.0.7-tem`, or `26.0.2+1.1-tem`. Versions are
|
|
49
|
+
* interpolated into a shell command by the sdkman installer, so every
|
|
50
|
+
* identifier must be a single safe token: it must start with a digit and may
|
|
51
|
+
* only contain digits, ascii letters, dots, hyphens, and plus signs (`+` has
|
|
52
|
+
* no shell meaning and sdkman uses it in Temurin respin identifiers).
|
|
53
|
+
*
|
|
54
|
+
* Every separator must be followed by at least one alphanumeric character, and
|
|
55
|
+
* a single repeated group covers both the dotted numeric part and the vendor
|
|
56
|
+
* or prerelease suffix. That leaves each character exactly one role, so the
|
|
57
|
+
* pattern matches in linear time on untrusted input.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} version Candidate version identifier
|
|
60
|
+
* @returns {boolean} True when the identifier is safe to pass to sdkman
|
|
61
|
+
*/
|
|
62
|
+
export function isValidSdkmanVersion(version) {
|
|
63
|
+
return (
|
|
64
|
+
typeof version === "string" &&
|
|
65
|
+
version.length > 0 &&
|
|
66
|
+
version.length <= MAX_VERSION_TOKEN_LENGTH &&
|
|
67
|
+
/^\d+(?:[-.+][0-9A-Za-z]+)*$/.test(version)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse a versioned JVM build tool project type such as `maven3.9.9`,
|
|
73
|
+
* `mvn3.9.9`, `gradle8.14`, `sbt1.10`, or `scala3.6.4` into its sdkman
|
|
74
|
+
* candidate name and version token.
|
|
75
|
+
*
|
|
76
|
+
* The returned object distinguishes between a syntactically valid pin and a
|
|
77
|
+
* rejected one so callers can print a precise CLI error instead of silently
|
|
78
|
+
* ignoring the type.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} projectType Project type argument from the CLI
|
|
81
|
+
* @returns {Object|undefined} `{ tool, version, valid }` when the type looks
|
|
82
|
+
* like a versioned JVM build tool, `undefined` otherwise. `valid` is false
|
|
83
|
+
* when the version token fails validation.
|
|
84
|
+
*/
|
|
85
|
+
export function parseJvmToolProjectType(projectType) {
|
|
86
|
+
const pt = `${projectType || ""}`.toLowerCase();
|
|
87
|
+
for (const prefix of JVM_BUILD_TOOL_TYPE_PREFIXES) {
|
|
88
|
+
if (!pt.startsWith(prefix)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const version = pt.slice(prefix.length);
|
|
92
|
+
if (!/^\d/.test(version)) {
|
|
93
|
+
// Bare aliases such as "maven" or unrelated types are not versioned pins.
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const tool = prefix === "mvn" ? "maven" : prefix;
|
|
97
|
+
return { tool, version, valid: isValidSdkmanVersion(version) };
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse a `.sdkmanrc` environment file into the sdkman candidates it pins.
|
|
104
|
+
* Only the JVM-related candidates cdxgen knows how to provision are kept;
|
|
105
|
+
* unknown keys are ignored so newer sdkman files remain readable. Malformed
|
|
106
|
+
* lines are skipped instead of aborting the whole file.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} filePath Project directory that may contain a `.sdkmanrc`
|
|
109
|
+
* @returns {Object|undefined} Map of candidate name to exact version
|
|
110
|
+
* identifier, or `undefined` when the file is missing or unreadable.
|
|
111
|
+
*/
|
|
112
|
+
export function parseSdkmanrc(filePath) {
|
|
113
|
+
const sdkmanrcFile = join(filePath, ".sdkmanrc");
|
|
114
|
+
if (!safeExistsSync(sdkmanrcFile)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
let content;
|
|
118
|
+
try {
|
|
119
|
+
content = readFileSync(sdkmanrcFile, {
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
flag: "r",
|
|
122
|
+
}).slice(0, MAX_SDKMANRC_BYTES);
|
|
123
|
+
} catch (_e) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const pins = {};
|
|
127
|
+
const acceptedCandidates = new Set(["java", ...JVM_BUILD_TOOL_CANDIDATES]);
|
|
128
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
129
|
+
const line = rawLine.trim();
|
|
130
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const separatorIndex = line.indexOf("=");
|
|
134
|
+
if (separatorIndex <= 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const candidate = line.slice(0, separatorIndex).trim().toLowerCase();
|
|
138
|
+
const version = line.slice(separatorIndex + 1).trim();
|
|
139
|
+
if (!acceptedCandidates.has(candidate)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!isValidSdkmanVersion(version)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (candidate === "java" && !version.includes("-")) {
|
|
146
|
+
// sdkman java identifiers carry a vendor suffix, e.g. 21.0.7-tem. A
|
|
147
|
+
// bare number such as "21" is not installable, so skip it.
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
pins[candidate] = version;
|
|
151
|
+
}
|
|
152
|
+
return Object.keys(pins).length ? pins : undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Read the version pinned by a Gradle wrapper's `gradle-wrapper.properties`.
|
|
157
|
+
* The `distributionUrl` value may escape colons as `\:` per the Java
|
|
158
|
+
* properties format; the escapes are removed before parsing.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} rootPath Directory holding the `gradle/wrapper` folder
|
|
161
|
+
* @returns {Object|undefined} `{ version, distributionUrl, distributionSha256Sum? }`
|
|
162
|
+
* or `undefined` when the wrapper is missing or unparsable.
|
|
163
|
+
*/
|
|
164
|
+
export function readGradleWrapperVersion(rootPath) {
|
|
165
|
+
const wrapperPropsFile = join(
|
|
166
|
+
rootPath,
|
|
167
|
+
"gradle",
|
|
168
|
+
"wrapper",
|
|
169
|
+
"gradle-wrapper.properties",
|
|
170
|
+
);
|
|
171
|
+
if (!safeExistsSync(wrapperPropsFile)) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
let propContent;
|
|
175
|
+
try {
|
|
176
|
+
propContent = readFileSync(wrapperPropsFile, {
|
|
177
|
+
encoding: "utf-8",
|
|
178
|
+
flag: "r",
|
|
179
|
+
});
|
|
180
|
+
} catch (_e) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const properties = parsePropertiesContent(propContent);
|
|
184
|
+
const distributionUrl = properties.distributionUrl?.replaceAll("\\:", ":");
|
|
185
|
+
if (!distributionUrl) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
const version = extractVersionFromZipBasename(distributionUrl);
|
|
189
|
+
if (!version) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
const wrapperInfo = { version, distributionUrl };
|
|
193
|
+
const distributionSha256Sum = properties.distributionSha256Sum;
|
|
194
|
+
// The checksum is copied into the BOM as a CycloneDX hash, whose schema
|
|
195
|
+
// accepts hex digests only. Anything else in the properties file is dropped
|
|
196
|
+
// rather than emitted as a hash that would fail validation downstream.
|
|
197
|
+
if (/^[a-fA-F0-9]{64}$/.test(`${distributionSha256Sum}`)) {
|
|
198
|
+
wrapperInfo.distributionSha256Sum = distributionSha256Sum;
|
|
199
|
+
}
|
|
200
|
+
return wrapperInfo;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Read the version pinned by a Maven wrapper's `maven-wrapper.properties`.
|
|
205
|
+
* Legacy wrappers (pre wrapper-distribution downloads) lack a
|
|
206
|
+
* `distributionUrl` and are treated as unpinned.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} rootPath Directory holding the `.mvn/wrapper` folder
|
|
209
|
+
* @returns {Object|undefined} `{ version, distributionUrl }` or `undefined`
|
|
210
|
+
* when the wrapper is missing or unpinned.
|
|
211
|
+
*/
|
|
212
|
+
export function readMavenWrapperVersion(rootPath) {
|
|
213
|
+
const wrapperPropsFile = join(
|
|
214
|
+
rootPath,
|
|
215
|
+
".mvn",
|
|
216
|
+
"wrapper",
|
|
217
|
+
"maven-wrapper.properties",
|
|
218
|
+
);
|
|
219
|
+
if (!safeExistsSync(wrapperPropsFile)) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
let propContent;
|
|
223
|
+
try {
|
|
224
|
+
propContent = readFileSync(wrapperPropsFile, {
|
|
225
|
+
encoding: "utf-8",
|
|
226
|
+
flag: "r",
|
|
227
|
+
});
|
|
228
|
+
} catch (_e) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
const properties = parsePropertiesContent(propContent);
|
|
232
|
+
const distributionUrl = properties.distributionUrl?.replaceAll("\\:", ":");
|
|
233
|
+
if (!distributionUrl) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const version = extractVersionFromZipBasename(distributionUrl);
|
|
237
|
+
if (!version) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
return { version, distributionUrl };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Read the sbt version declared in `project/build.properties`. Kept here as a
|
|
245
|
+
* small properties-file read so environment preparation does not depend on
|
|
246
|
+
* higher layers.
|
|
247
|
+
*
|
|
248
|
+
* @param {string} projectPath SBT project directory
|
|
249
|
+
* @returns {string|undefined} Declared sbt version, when present and valid.
|
|
250
|
+
*/
|
|
251
|
+
export function readSbtBuildPropertiesVersion(projectPath) {
|
|
252
|
+
const buildPropsFile = join(projectPath, "project", "build.properties");
|
|
253
|
+
if (!safeExistsSync(buildPropsFile)) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
let propContent;
|
|
257
|
+
try {
|
|
258
|
+
propContent = readFileSync(buildPropsFile, {
|
|
259
|
+
encoding: "utf-8",
|
|
260
|
+
flag: "r",
|
|
261
|
+
});
|
|
262
|
+
} catch (_e) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
const sbtVersion = parsePropertiesContent(propContent)["sbt.version"];
|
|
266
|
+
return isValidSdkmanVersion(sbtVersion) ? sbtVersion : undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Parse the text content of a Java `.properties` file into a flat map. Only
|
|
271
|
+
* the keys cdxgen consumes are of interest, so a line-based parse with
|
|
272
|
+
* `key=value` splitting is sufficient and avoids regex-heavy parsing of
|
|
273
|
+
* untrusted files.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} content Properties file content
|
|
276
|
+
* @returns {Object} Map of property name to raw value
|
|
277
|
+
*/
|
|
278
|
+
function parsePropertiesContent(content) {
|
|
279
|
+
const properties = {};
|
|
280
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
281
|
+
const line = rawLine.trim();
|
|
282
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const separatorIndex = line.indexOf("=");
|
|
286
|
+
if (separatorIndex <= 0) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
290
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
291
|
+
if (key && !(key in properties)) {
|
|
292
|
+
properties[key] = value;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return properties;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Extract a version identifier from the basename of a distribution zip such
|
|
300
|
+
* as `gradle-8.14.3-bin.zip` or `apache-maven-3.9.9-bin.zip` using token
|
|
301
|
+
* splits instead of a capture-everything regex.
|
|
302
|
+
*
|
|
303
|
+
* @param {string} url Distribution URL
|
|
304
|
+
* @returns {string|undefined} Validated version identifier.
|
|
305
|
+
*/
|
|
306
|
+
function extractVersionFromZipBasename(url) {
|
|
307
|
+
const pathSeparatorIndex = Math.max(
|
|
308
|
+
url.lastIndexOf("/"),
|
|
309
|
+
url.lastIndexOf("\\"),
|
|
310
|
+
);
|
|
311
|
+
const basename =
|
|
312
|
+
pathSeparatorIndex >= 0 ? url.slice(pathSeparatorIndex + 1) : url;
|
|
313
|
+
for (const token of basename.split("-")) {
|
|
314
|
+
if (/^\d/.test(token) && isValidSdkmanVersion(token)) {
|
|
315
|
+
return token;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Minimum JDK major version required to run each build tool, keyed by the
|
|
323
|
+
* tool's major version line. Versions absent from the table fall back to the
|
|
324
|
+
* tool's `default` entry.
|
|
325
|
+
*
|
|
326
|
+
* Sources: Maven release history (maven.apache.org/docs/history.html), the
|
|
327
|
+
* Gradle compatibility table (docs.gradle.org), and the sbt setup notes.
|
|
328
|
+
*
|
|
329
|
+
* @type {Object<string, Object<number, number>|number>}
|
|
330
|
+
*/
|
|
331
|
+
export const TOOL_JDK_REQUIREMENTS = {
|
|
332
|
+
maven: { default: 8, 3: 8, 4: 17 },
|
|
333
|
+
gradle: { default: 8, 8: 8, 9: 17 },
|
|
334
|
+
sbt: { default: 8, 1: 8, 2: 17 },
|
|
335
|
+
scala: { default: 8, 2: 8, 3: 8 },
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Minimum Gradle version that supports a given JDK major, for the
|
|
340
|
+
* combinations where running an older Gradle on a newer JDK is known to fail.
|
|
341
|
+
* Used to warn instead of guess-installing when the environment contradicts
|
|
342
|
+
* the project's Gradle pin.
|
|
343
|
+
*
|
|
344
|
+
* @type {Object<number, string>}
|
|
345
|
+
*/
|
|
346
|
+
export const GRADLE_JAVA_CAPS = {
|
|
347
|
+
17: "7.3",
|
|
348
|
+
21: "8.5",
|
|
349
|
+
23: "8.10",
|
|
350
|
+
25: "9.1.0",
|
|
351
|
+
26: "9.4.0",
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/** JDK major that satisfies every current build tool minimum. */
|
|
355
|
+
export const DEFAULT_JAVA_MAJOR = 21;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Look up the minimum JDK major required to run a tool version.
|
|
359
|
+
*
|
|
360
|
+
* @param {string} tool Tool name (maven, gradle, sbt, scala)
|
|
361
|
+
* @param {string} version Tool version identifier
|
|
362
|
+
* @returns {number|undefined} Minimum JDK major, when known.
|
|
363
|
+
*/
|
|
364
|
+
export function minimumJdkForToolVersion(tool, version) {
|
|
365
|
+
const toolRequirements = TOOL_JDK_REQUIREMENTS[`${tool}`.toLowerCase()];
|
|
366
|
+
if (!toolRequirements || !isValidSdkmanVersion(`${version}`)) {
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
const major = Number.parseInt(`${version}`, 10);
|
|
370
|
+
if (Number.isNaN(major)) {
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
return toolRequirements[major] ?? toolRequirements.default;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Compute the highest JDK major required across a set of tool pins.
|
|
378
|
+
*
|
|
379
|
+
* @param {Array<{tool: string, version: string}>} toolPins Pinned tools
|
|
380
|
+
* @returns {number|undefined} Highest required JDK major, when any pin maps.
|
|
381
|
+
*/
|
|
382
|
+
export function determineRequiredJavaVersion(toolPins) {
|
|
383
|
+
let required;
|
|
384
|
+
for (const pin of toolPins || []) {
|
|
385
|
+
const minimum = minimumJdkForToolVersion(pin?.tool, pin?.version);
|
|
386
|
+
if (
|
|
387
|
+
minimum !== undefined &&
|
|
388
|
+
(required === undefined || minimum > required)
|
|
389
|
+
) {
|
|
390
|
+
required = minimum;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return required;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Minimum Gradle version that can run on the given JDK major, when such a cap
|
|
398
|
+
* is known.
|
|
399
|
+
*
|
|
400
|
+
* @param {number} javaMajor JDK major version
|
|
401
|
+
* @returns {string|undefined} Minimum Gradle version, when capped.
|
|
402
|
+
*/
|
|
403
|
+
export function minimumGradleVersionForJava(javaMajor) {
|
|
404
|
+
return GRADLE_JAVA_CAPS[javaMajor];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Collect the version tokens printed by `sdk list <candidate>`. The output is
|
|
409
|
+
* a fixed-width table whose cells may carry `+`, `*`, or `>` status markers;
|
|
410
|
+
* every token that passes the sdkman version validator is kept, which
|
|
411
|
+
* naturally drops banners, legends, and shell noise.
|
|
412
|
+
*
|
|
413
|
+
* @param {string} stdout Raw `sdk list` output
|
|
414
|
+
* @returns {string[]} Version identifiers found in the output.
|
|
415
|
+
*/
|
|
416
|
+
export function extractSdkListVersions(stdout) {
|
|
417
|
+
if (typeof stdout !== "string") {
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
const versions = [];
|
|
421
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
422
|
+
for (const token of rawLine.trim().split(/\s+/)) {
|
|
423
|
+
const cleaned = token.replace(/^[+*>]+/, "");
|
|
424
|
+
if (cleaned && isValidSdkmanVersion(cleaned)) {
|
|
425
|
+
versions.push(cleaned);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return versions;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Resolve a partial version prefix such as `3.9` or `8.14` to the newest
|
|
434
|
+
* matching version in `sdk list` output. Stable versions are preferred unless
|
|
435
|
+
* the requested prefix itself is a prerelease.
|
|
436
|
+
*
|
|
437
|
+
* @param {string} stdout Raw `sdk list <candidate>` output
|
|
438
|
+
* @param {string} prefix Partial or exact version prefix; empty matches all
|
|
439
|
+
* @returns {string|undefined} Best matching version identifier.
|
|
440
|
+
*/
|
|
441
|
+
export function resolvePartialVersion(stdout, prefix) {
|
|
442
|
+
const versions = extractSdkListVersions(stdout);
|
|
443
|
+
const wantedPrefix = `${prefix || ""}`;
|
|
444
|
+
const allowPrerelease = isPrereleaseVersion(wantedPrefix);
|
|
445
|
+
let best;
|
|
446
|
+
for (const version of versions) {
|
|
447
|
+
const matches =
|
|
448
|
+
!wantedPrefix ||
|
|
449
|
+
version === wantedPrefix ||
|
|
450
|
+
version.startsWith(`${wantedPrefix}.`) ||
|
|
451
|
+
version.startsWith(`${wantedPrefix}-`);
|
|
452
|
+
if (!matches) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (!allowPrerelease && isPrereleaseVersion(version)) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (best === undefined || compareVersions(version, best) > 0) {
|
|
459
|
+
best = version;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return best;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Environment variable hints that carry a tool version, used by container
|
|
467
|
+
* images and CI setups to describe the preinstalled JVM toolchain.
|
|
468
|
+
*
|
|
469
|
+
* @type {Object<string, string>}
|
|
470
|
+
*/
|
|
471
|
+
export const JVM_TOOL_VERSION_ENV_HINTS = {
|
|
472
|
+
maven: "MAVEN_VERSION",
|
|
473
|
+
gradle: "GRADLE_VERSION",
|
|
474
|
+
sbt: "SBT_VERSION",
|
|
475
|
+
scala: "SCALA_VERSION",
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Environment variable overrides that declare the sdkman version to use for
|
|
480
|
+
* a tool, mirroring the existing `JAVA<NN>_TOOL` pattern.
|
|
481
|
+
*
|
|
482
|
+
* @type {Object<string, string>}
|
|
483
|
+
*/
|
|
484
|
+
export const JVM_TOOL_VERSION_OVERRIDES = {
|
|
485
|
+
maven: "MAVEN_TOOL",
|
|
486
|
+
gradle: "GRADLE_TOOL",
|
|
487
|
+
sbt: "SBT_TOOL",
|
|
488
|
+
scala: "SCALA_TOOL",
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Read the `MAVEN_TOOL`/`GRADLE_TOOL`/`SBT_TOOL`/`SCALA_TOOL` override for a
|
|
493
|
+
* tool, when set to a valid sdkman version identifier.
|
|
494
|
+
*
|
|
495
|
+
* @param {string} tool Tool name
|
|
496
|
+
* @returns {string|undefined} Overridden version identifier.
|
|
497
|
+
*/
|
|
498
|
+
export function readToolVersionOverride(tool) {
|
|
499
|
+
const envName = JVM_TOOL_VERSION_OVERRIDES[tool];
|
|
500
|
+
if (!envName) {
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
const value = readEnvironmentVariable(envName);
|
|
504
|
+
return isValidSdkmanVersion(value) ? value : undefined;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Read the container/CI version hint (`MAVEN_VERSION` etc.) for a tool, when
|
|
509
|
+
* set to a valid sdkman version identifier.
|
|
510
|
+
*
|
|
511
|
+
* @param {string} tool Tool name
|
|
512
|
+
* @returns {string|undefined} Hinted version identifier.
|
|
513
|
+
*/
|
|
514
|
+
export function readToolVersionEnvHint(tool) {
|
|
515
|
+
const envName = JVM_TOOL_VERSION_ENV_HINTS[tool];
|
|
516
|
+
if (!envName) {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
const value = readEnvironmentVariable(envName);
|
|
520
|
+
return isValidSdkmanVersion(value) ? value : undefined;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Well-known build files scanned for the JDK major a project targets.
|
|
525
|
+
*
|
|
526
|
+
* @type {string[]}
|
|
527
|
+
*/
|
|
528
|
+
const JAVA_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Detect the JDK major a project asks for from its build files: Gradle
|
|
532
|
+
* toolchain declarations (`JavaLanguageVersion.of(N)`) and Maven compiler or
|
|
533
|
+
* enforcer properties (`maven.compiler.release`, `maven.compiler.source`,
|
|
534
|
+
* `requireJavaVersion`). The scan is intentionally line-based and
|
|
535
|
+
* conservative: it only recognises whole-number values.
|
|
536
|
+
*
|
|
537
|
+
* @param {string} projectPath Project directory
|
|
538
|
+
* @returns {number|undefined} JDK major requested by the project.
|
|
539
|
+
*/
|
|
540
|
+
export function detectProjectJavaMajor(projectPath) {
|
|
541
|
+
if (!projectPath) {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
for (const buildFileName of JAVA_BUILD_FILES) {
|
|
545
|
+
const buildFile = join(projectPath, buildFileName);
|
|
546
|
+
if (!safeExistsSync(buildFile)) {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
let content;
|
|
550
|
+
try {
|
|
551
|
+
content = readFileSync(buildFile, { encoding: "utf-8", flag: "r" });
|
|
552
|
+
} catch (_e) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const detected = scanBuildFileForJavaMajor(content);
|
|
556
|
+
if (detected !== undefined) {
|
|
557
|
+
return detected;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Scan Gradle or Maven build file content for the JDK major it targets.
|
|
565
|
+
*
|
|
566
|
+
* @param {string} content Build file content
|
|
567
|
+
* @returns {number|undefined} JDK major, when recognisable.
|
|
568
|
+
*/
|
|
569
|
+
function scanBuildFileForJavaMajor(content) {
|
|
570
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
571
|
+
const line = rawLine.trim();
|
|
572
|
+
let match = line.match(/JavaLanguageVersion\.of\((\d{1,2})\)/);
|
|
573
|
+
if (match) {
|
|
574
|
+
return Number.parseInt(match[1], 10);
|
|
575
|
+
}
|
|
576
|
+
match = line.match(
|
|
577
|
+
/<(?:maven\.compiler\.release|maven\.compiler\.source|release|source)>(?:1\.)?(\d{1,2})</,
|
|
578
|
+
);
|
|
579
|
+
if (match) {
|
|
580
|
+
return Number.parseInt(match[1], 10);
|
|
581
|
+
}
|
|
582
|
+
// Enforcer requireJavaVersion ranges such as <version>[17,)</version>.
|
|
583
|
+
// Dependency and plugin version ranges share this element name, so only
|
|
584
|
+
// values that are plausible JDK majors are accepted.
|
|
585
|
+
match = line.match(/<version>\[(?:1\.)?(\d{1,2})[,)\]]/);
|
|
586
|
+
if (match) {
|
|
587
|
+
const major = Number.parseInt(match[1], 10);
|
|
588
|
+
if (major >= 8) {
|
|
589
|
+
return major;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|