sdkvm 1.0.3 → 1.0.4

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.
Files changed (4) hide show
  1. package/README.en.md +123 -99
  2. package/README.md +124 -100
  3. package/dist/index.js +370 -89
  4. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -3521,7 +3521,7 @@ var log = {
3521
3521
  };
3522
3522
 
3523
3523
  // src/cli/install.ts
3524
- import fs10 from "fs";
3524
+ import fs11 from "fs";
3525
3525
  import path9 from "path";
3526
3526
 
3527
3527
  // src/core/platform.ts
@@ -3809,6 +3809,63 @@ function parseNodeUserSpec(input) {
3809
3809
  hint: "Expected: 22, 22.20.0, lts, latest, or with vendor prefix like nodejs-22.20.0"
3810
3810
  });
3811
3811
  }
3812
+ var MAVEN_VENDOR_IDS = ["maven"];
3813
+ function parseMavenVersion(vendor, input) {
3814
+ const raw = input.trim();
3815
+ const s = raw.replace(/^maven-/i, "").replace(/^v/i, "");
3816
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(s);
3817
+ if (!m || !m[1] || !m[2] || !m[3]) {
3818
+ throw new SdkvmError(`Invalid Maven version: "${input}"`, {
3819
+ hint: "Expected forms: 3, 3.9, 3.9.9, 4.0.0-rc-4, latest"
3820
+ });
3821
+ }
3822
+ return {
3823
+ vendor,
3824
+ major: Number(m[1]),
3825
+ minor: Number(m[2]),
3826
+ patch: Number(m[3]),
3827
+ extra: m[4] ?? null,
3828
+ build: null,
3829
+ raw
3830
+ };
3831
+ }
3832
+ function formatMavenVersion(v) {
3833
+ const base = `${v.major}.${v.minor}.${v.patch ?? 0}`;
3834
+ return v.extra ? `${base}-${v.extra}` : base;
3835
+ }
3836
+ function parseMavenDirName(dir) {
3837
+ const m = new RegExp(`^(${MAVEN_VENDOR_IDS.join("|")})-(.+)$`).exec(dir);
3838
+ if (!m || !m[1] || !m[2]) return null;
3839
+ try {
3840
+ return parseMavenVersion(m[1], m[2]);
3841
+ } catch {
3842
+ return null;
3843
+ }
3844
+ }
3845
+ function parseMavenUserSpec(input) {
3846
+ let s = input.trim().toLowerCase();
3847
+ let vendor;
3848
+ const m = /^(maven)-(.+)$/.exec(s);
3849
+ if (m && m[2]) {
3850
+ vendor = "maven";
3851
+ s = m[2];
3852
+ }
3853
+ if (s === "latest") return { vendor, spec: { kind: "latest" } };
3854
+ if (s === "lts" || s === "--lts") {
3855
+ throw new SdkvmError(`Invalid Maven version: "${input}"`, {
3856
+ hint: 'Maven has no lts alias \u2014 use "3", "3.9", "3.9.9", or "latest"'
3857
+ });
3858
+ }
3859
+ if (/^\d+\.\d+\.\d+(-[0-9a-z.\-]+)?$/.test(s)) return { vendor, spec: { kind: "full", version: s } };
3860
+ const line = /^(\d+)\.(\d+)$/.exec(s);
3861
+ if (line && line[1] && line[2]) {
3862
+ return { vendor, spec: { kind: "line", major: Number(line[1]), minor: Number(line[2]) } };
3863
+ }
3864
+ if (/^\d+$/.test(s)) return { vendor, spec: { kind: "major", major: Number(s) } };
3865
+ throw new SdkvmError(`Invalid Maven version: "${input}"`, {
3866
+ hint: "Expected: 3, 3.9, 3.9.9, 4.0.0-rc-4, latest, or with vendor prefix like maven-3.9.9"
3867
+ });
3868
+ }
3812
3869
 
3813
3870
  // src/core/lock.ts
3814
3871
  import fs5 from "fs";
@@ -4399,16 +4456,109 @@ var nodejsVendor = {
4399
4456
  }
4400
4457
  };
4401
4458
 
4459
+ // src/vendor/maven.ts
4460
+ var META_URL = "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/maven-metadata.xml";
4461
+ var REPO = "https://repo.maven.apache.org/maven2";
4462
+ var MIN_MAJOR = 3;
4463
+ function tryParse(raw) {
4464
+ try {
4465
+ const v = parseMavenVersion("maven", raw);
4466
+ if (v.major < MIN_MAJOR) return null;
4467
+ return { raw, v, stable: v.extra === null };
4468
+ } catch {
4469
+ return null;
4470
+ }
4471
+ }
4472
+ async function fetchListed() {
4473
+ const xml = await httpText(META_URL);
4474
+ const out = [];
4475
+ for (const m of xml.matchAll(/<version>([^<]+)<\/version>/g)) {
4476
+ const raw = m[1];
4477
+ if (!raw) continue;
4478
+ const parsed = tryParse(raw);
4479
+ if (parsed) out.push(parsed);
4480
+ }
4481
+ return out;
4482
+ }
4483
+ function stablesDesc(listed) {
4484
+ return listed.filter((x) => x.stable).sort((a, b) => compareVersions(b.v, a.v));
4485
+ }
4486
+ function specLabel3(spec) {
4487
+ if (spec.kind === "major") return String(spec.major);
4488
+ if (spec.kind === "line") return `${spec.major}.${spec.minor}`;
4489
+ if (spec.kind === "full") return spec.version;
4490
+ return spec.kind;
4491
+ }
4492
+ function buildArtifact4(v, platform) {
4493
+ const display = formatMavenVersion(v);
4494
+ const ext = platform.os === "windows" ? "zip" : "tar.gz";
4495
+ const filename = `apache-maven-${display}-bin.${ext}`;
4496
+ const downloadUrl = `${REPO}/org/apache/maven/apache-maven/${display}/${filename}`;
4497
+ return {
4498
+ vendorId: "maven",
4499
+ version: v,
4500
+ dirName: `maven-${display}`,
4501
+ displayName: `Apache Maven ${display}`,
4502
+ downloadUrl,
4503
+ checksum: { kind: "sha512", url: `${downloadUrl}.sha512` },
4504
+ archive: ext === "zip" ? "zip" : "tar.gz"
4505
+ };
4506
+ }
4507
+ var mavenVendor = {
4508
+ id: "maven",
4509
+ label: "Apache Maven",
4510
+ sdk: "maven",
4511
+ supportsFullVersionList: true,
4512
+ /** 一条 minor 线(3.9)只含稳定版;预发布不单独成线 */
4513
+ async listMajors() {
4514
+ const versions = stablesDesc(await fetchListed());
4515
+ const lines = /* @__PURE__ */ new Map();
4516
+ for (const { v } of versions) {
4517
+ const key = `${v.major}.${v.minor}`;
4518
+ const cur = lines.get(key);
4519
+ if (!cur || compareVersions(v, cur) > 0) lines.set(key, v);
4520
+ }
4521
+ return [...lines.entries()].sort((a, b) => compareVersions(b[1], a[1])).map(([key, v]) => ({ key, lts: false, latestFullVersion: formatMavenVersion(v) }));
4522
+ },
4523
+ async resolve(spec, platform) {
4524
+ if (spec.kind === "lts") {
4525
+ throw new SdkvmError("Unsupported version spec for Maven: lts", {
4526
+ hint: 'Maven has no lts alias \u2014 use "3", "3.9", "3.9.9", or "latest"'
4527
+ });
4528
+ }
4529
+ const listed = await fetchListed();
4530
+ const stables = stablesDesc(listed);
4531
+ let target;
4532
+ if (spec.kind === "latest") {
4533
+ target = stables[0];
4534
+ } else if (spec.kind === "major") {
4535
+ target = stables.find((x) => x.v.major === spec.major);
4536
+ } else if (spec.kind === "line") {
4537
+ target = stables.find((x) => x.v.major === spec.major && x.v.minor === spec.minor);
4538
+ } else {
4539
+ target = listed.find((x) => formatMavenVersion(x.v) === spec.version);
4540
+ }
4541
+ if (!target) {
4542
+ throw new SdkvmError(`No Maven release matches "${specLabel3(spec)}"`, {
4543
+ hint: `Run \`${cmdPath("maven")} ls -r\` to see available versions.`
4544
+ });
4545
+ }
4546
+ return buildArtifact4(target.v, platform);
4547
+ }
4548
+ };
4549
+
4402
4550
  // src/vendor/index.ts
4403
4551
  var JAVA_VENDORS = [temurinVendor, zuluVendor, correttoVendor];
4404
4552
  var GO_VENDORS = [golangVendor];
4405
4553
  var FLUTTER_VENDORS = [flutterVendor];
4406
4554
  var NODE_VENDORS = [nodejsVendor];
4555
+ var MAVEN_VENDORS = [mavenVendor];
4407
4556
  function vendorsFor(type) {
4408
4557
  if (type === "java") return JAVA_VENDORS;
4409
4558
  if (type === "go") return GO_VENDORS;
4410
4559
  if (type === "flutter") return FLUTTER_VENDORS;
4411
4560
  if (type === "node") return NODE_VENDORS;
4561
+ if (type === "maven") return MAVEN_VENDORS;
4412
4562
  throw new SdkvmError(`Unknown SDK type: ${type}`);
4413
4563
  }
4414
4564
  function allVendorIds(type) {
@@ -4536,14 +4686,40 @@ var nodeSdk = {
4536
4686
  versionCheck: { args: ["--version"], stream: "stdout" }
4537
4687
  };
4538
4688
 
4689
+ // src/sdk/maven.ts
4690
+ var mavenSdk = {
4691
+ id: "maven",
4692
+ label: "Maven",
4693
+ installDirName: "mavens",
4694
+ currentLinkName: "current-maven",
4695
+ envVar: "MAVEN_HOME",
4696
+ supportsLts: false,
4697
+ requiresJdk: true,
4698
+ vendors: MAVEN_VENDORS,
4699
+ parseUserSpec: parseMavenUserSpec,
4700
+ parseDirName: parseMavenDirName,
4701
+ formatVersion: formatMavenVersion,
4702
+ compareVersions,
4703
+ binRelPath(platform) {
4704
+ return `bin/${platform.os === "windows" ? "mvn.cmd" : "mvn"}`;
4705
+ },
4706
+ envBinSuffix(platform) {
4707
+ return platform.os === "windows" ? "\\bin" : "/bin";
4708
+ },
4709
+ // 归档是单根 apache-maven-<version>/ 目录
4710
+ locateHome: (root) => root,
4711
+ versionCheck: { args: ["-version"], stream: "stdout" }
4712
+ };
4713
+
4539
4714
  // src/sdk/index.ts
4540
4715
  var SPECS = {
4541
4716
  java: javaSdk,
4542
4717
  go: goSdk,
4543
4718
  flutter: flutterSdk,
4544
- node: nodeSdk
4719
+ node: nodeSdk,
4720
+ maven: mavenSdk
4545
4721
  };
4546
- var SDK_TYPES = ["java", "go", "flutter", "node"];
4722
+ var SDK_TYPES = ["java", "go", "flutter", "node", "maven"];
4547
4723
  function getSdkType(id) {
4548
4724
  const spec = SPECS[id];
4549
4725
  if (!spec) throw new SdkvmError(`Unknown SDK type: ${id}`);
@@ -4724,7 +4900,7 @@ function updateConfig(mutator) {
4724
4900
  }
4725
4901
 
4726
4902
  // src/vendor/mirror.ts
4727
- var MIRROR_REWRITE_VENDORS = /* @__PURE__ */ new Set(["temurin", "golang", "flutter", "nodejs"]);
4903
+ var MIRROR_REWRITE_VENDORS = /* @__PURE__ */ new Set(["temurin", "golang", "flutter", "nodejs", "maven"]);
4728
4904
  function applyMirrorDetail(artifact, platform, mirrorRoot) {
4729
4905
  if (!mirrorRoot?.trim()) return { artifact, applied: false };
4730
4906
  const root = mirrorRoot.trim().replace(/\/+$/, "");
@@ -4746,6 +4922,11 @@ function applyMirrorDetail(artifact, platform, mirrorRoot) {
4746
4922
  if (url2 === artifact.downloadUrl) return { artifact, applied: false };
4747
4923
  return { artifact: { ...artifact, downloadUrl: url2 }, applied: true };
4748
4924
  }
4925
+ if (artifact.vendorId === "maven") {
4926
+ const url2 = artifact.downloadUrl.replace(/^https:\/\/repo\.maven\.apache\.org\/maven2/, root);
4927
+ if (url2 === artifact.downloadUrl) return { artifact, applied: false };
4928
+ return { artifact: { ...artifact, downloadUrl: url2 }, applied: true };
4929
+ }
4749
4930
  if (artifact.vendorId !== "temurin") return { artifact, applied: false };
4750
4931
  const m = /^https:\/\/github\.com\/adoptium\/temurin(\d+)-binaries\/releases\/download\/[^/]+\/(.+)$/.exec(
4751
4932
  artifact.downloadUrl
@@ -4754,6 +4935,13 @@ function applyMirrorDetail(artifact, platform, mirrorRoot) {
4754
4935
  const url = `${root}/${m[1]}/jdk/${platform.arch}/${platform.os}/${m[2]}`;
4755
4936
  return { artifact: { ...artifact, downloadUrl: url }, applied: true };
4756
4937
  }
4938
+ function checksumSidecarFallback(officialDownload, officialChecksum, mirroredDownload) {
4939
+ if (!officialChecksum || mirroredDownload === officialDownload) return void 0;
4940
+ if (!officialChecksum.startsWith(officialDownload)) return void 0;
4941
+ const suffix = officialChecksum.slice(officialDownload.length);
4942
+ if (!/^\.[A-Za-z0-9.]+$/.test(suffix)) return void 0;
4943
+ return mirroredDownload + suffix;
4944
+ }
4757
4945
 
4758
4946
  // src/net/download.ts
4759
4947
  import crypto from "crypto";
@@ -4761,10 +4949,25 @@ import { once } from "events";
4761
4949
  import fs7 from "fs";
4762
4950
  import path6 from "path";
4763
4951
  var IDLE_TIMEOUT_MS = 6e4;
4952
+ function watchWriteStream(out) {
4953
+ let streamError = null;
4954
+ out.on("error", (err) => {
4955
+ streamError ??= err;
4956
+ });
4957
+ return { error: () => streamError };
4958
+ }
4959
+ async function closeWriteStream(out) {
4960
+ if (out.closed) return;
4961
+ await new Promise((resolve) => {
4962
+ out.once("close", () => resolve());
4963
+ if (!out.destroyed) out.destroy();
4964
+ });
4965
+ }
4764
4966
  async function downloadFile(url, destFile, onProgress) {
4765
4967
  const partFile = `${destFile}.part`;
4766
4968
  const hash = crypto.createHash("sha256");
4767
4969
  const out = fs7.createWriteStream(partFile);
4970
+ const stream = watchWriteStream(out);
4768
4971
  let bytes = 0;
4769
4972
  let total = null;
4770
4973
  let encoded2 = false;
@@ -4774,6 +4977,10 @@ async function downloadFile(url, destFile, onProgress) {
4774
4977
  stalled = true;
4775
4978
  ac.abort();
4776
4979
  }, IDLE_TIMEOUT_MS);
4980
+ const throwIfStreamError = () => {
4981
+ const err = stream.error();
4982
+ if (err) throw err;
4983
+ };
4777
4984
  try {
4778
4985
  const res = await httpFetch(url, {
4779
4986
  signal: ac.signal,
@@ -4785,26 +4992,28 @@ async function downloadFile(url, destFile, onProgress) {
4785
4992
  if (!res.body) throw new SdkvmError(`Empty response body: ${url}`);
4786
4993
  for await (const chunk of res.body) {
4787
4994
  timer.refresh();
4995
+ throwIfStreamError();
4788
4996
  if (!out.write(chunk)) await once(out, "drain");
4997
+ throwIfStreamError();
4789
4998
  hash.update(chunk);
4790
4999
  bytes += chunk.byteLength;
4791
5000
  onProgress?.(bytes, total);
4792
5001
  }
5002
+ throwIfStreamError();
4793
5003
  await new Promise((resolve, reject) => {
4794
5004
  out.end((err) => err ? reject(err) : resolve());
4795
5005
  });
4796
5006
  } catch (err) {
4797
- await new Promise((resolve) => {
4798
- out.once("close", resolve);
4799
- out.destroy();
4800
- });
5007
+ await closeWriteStream(out);
4801
5008
  fs7.rmSync(partFile, { force: true });
4802
5009
  if (stalled) {
4803
5010
  throw new SdkvmError(`Download stalled: no data for ${IDLE_TIMEOUT_MS / 1e3}s (${bytes} bytes so far)`, {
4804
5011
  hint: url
4805
5012
  });
4806
5013
  }
4807
- throw err;
5014
+ if (err instanceof SdkvmError) throw err;
5015
+ const detail = err instanceof Error ? err.message : String(err);
5016
+ throw new SdkvmError(`Download failed after ${bytes} bytes: ${detail}`, { hint: url });
4808
5017
  } finally {
4809
5018
  clearTimeout(timer);
4810
5019
  }
@@ -4825,13 +5034,20 @@ function cacheFileName(url) {
4825
5034
  }
4826
5035
 
4827
5036
  // src/net/checksum.ts
5037
+ import crypto2 from "crypto";
5038
+ import fs8 from "fs";
4828
5039
  var HEX64 = /^[0-9a-f]{64}$/i;
5040
+ var HEX128 = /^[0-9a-f]{128}$/i;
5041
+ function hexOf(value, kind) {
5042
+ const re = kind === "sha512" ? HEX128 : HEX64;
5043
+ return typeof value === "string" && re.test(value) ? value.toLowerCase() : null;
5044
+ }
4829
5045
  function hex64(value) {
4830
- return typeof value === "string" && HEX64.test(value) ? value.toLowerCase() : null;
5046
+ return hexOf(value, "sha256");
4831
5047
  }
4832
- function extractExpectedChecksum(text) {
5048
+ function extractExpectedChecksum(text, kind = "sha256") {
4833
5049
  const trimmed = text.trim();
4834
- if (trimmed.startsWith("{")) {
5050
+ if (kind === "sha256" && trimmed.startsWith("{")) {
4835
5051
  try {
4836
5052
  const obj = JSON.parse(trimmed);
4837
5053
  const direct = hex64(obj.checksum) ?? hex64(obj.sha256);
@@ -4852,7 +5068,38 @@ function extractExpectedChecksum(text) {
4852
5068
  return null;
4853
5069
  }
4854
5070
  const token = trimmed.split(/\s+/)[0] ?? "";
4855
- return HEX64.test(token) ? token.toLowerCase() : null;
5071
+ return hexOf(token, kind);
5072
+ }
5073
+ function hashFile(file, algorithm) {
5074
+ return new Promise((resolve, reject) => {
5075
+ const hash = crypto2.createHash(algorithm);
5076
+ fs8.createReadStream(file).on("error", reject).on("data", (chunk) => {
5077
+ hash.update(chunk);
5078
+ }).on("end", () => resolve(hash.digest("hex")));
5079
+ });
5080
+ }
5081
+ async function readFallbackChecksum(artifact, url, kind, strict) {
5082
+ let expected;
5083
+ try {
5084
+ expected = extractExpectedChecksum(await httpText(url), kind);
5085
+ } catch (err) {
5086
+ if (strict) {
5087
+ const detail = err instanceof Error ? err.message.split("\n")[0] : String(err);
5088
+ throw new SdkvmError(`Cannot fetch checksum for ${artifact.displayName}`, {
5089
+ hint: `${detail}. Official and mirror checksum URLs were both unreachable.`
5090
+ });
5091
+ }
5092
+ log.warn(`cannot fetch mirror checksum for ${artifact.displayName}, skipping verification`);
5093
+ return null;
5094
+ }
5095
+ if (expected) return expected;
5096
+ if (strict) {
5097
+ throw new SdkvmError(`Checksum source has no valid hash for ${artifact.displayName}`, {
5098
+ hint: `Mirror sidecar ${url} did not contain a ${kind} hash.`
5099
+ });
5100
+ }
5101
+ log.warn(`mirror sidecar has no valid hash for ${artifact.displayName}, skipping verification`);
5102
+ return null;
4856
5103
  }
4857
5104
  async function verifyChecksum(artifact, actual, opts = {}) {
4858
5105
  const strict = opts.strict === true;
@@ -4868,17 +5115,28 @@ async function verifyChecksum(artifact, actual, opts = {}) {
4868
5115
  }
4869
5116
  let expected = info.expected?.toLowerCase() ?? null;
4870
5117
  if (!expected && info.url) {
5118
+ const fallback = opts.fallbackUrl && opts.fallbackUrl !== info.url ? opts.fallbackUrl : void 0;
4871
5119
  try {
4872
- expected = extractExpectedChecksum(await httpText(info.url));
5120
+ expected = extractExpectedChecksum(await httpText(info.url), info.kind);
4873
5121
  } catch (err) {
4874
- if (strict) {
4875
- const detail = err instanceof Error ? err.message.split("\n")[0] : String(err);
4876
- throw new SdkvmError(`Cannot fetch checksum for ${artifact.displayName}`, {
4877
- hint: `${detail}. Mirrored installs need the official checksum URL to be reachable.`
4878
- });
5122
+ if (!fallback) {
5123
+ if (strict) {
5124
+ const detail = err instanceof Error ? err.message.split("\n")[0] : String(err);
5125
+ throw new SdkvmError(`Cannot fetch checksum for ${artifact.displayName}`, {
5126
+ hint: `${detail}. Mirrored installs need a reachable checksum URL.`
5127
+ });
5128
+ }
5129
+ log.warn(`cannot fetch checksum for ${artifact.displayName}, skipping verification`);
5130
+ return;
4879
5131
  }
4880
- log.warn(`cannot fetch checksum for ${artifact.displayName}, skipping verification`);
4881
- return;
5132
+ log.warn(`cannot fetch official checksum for ${artifact.displayName}, trying the mirror sidecar`);
5133
+ expected = await readFallbackChecksum(artifact, fallback, info.kind, strict);
5134
+ if (!expected) return;
5135
+ }
5136
+ if (!expected && fallback) {
5137
+ log.warn(`official checksum has no valid hash for ${artifact.displayName}, trying the mirror sidecar`);
5138
+ expected = await readFallbackChecksum(artifact, fallback, info.kind, strict);
5139
+ if (!expected) return;
4882
5140
  }
4883
5141
  }
4884
5142
  if (!expected) {
@@ -4898,7 +5156,7 @@ async function verifyChecksum(artifact, actual, opts = {}) {
4898
5156
  }
4899
5157
 
4900
5158
  // src/fs/extract.ts
4901
- import fs8 from "fs";
5159
+ import fs9 from "fs";
4902
5160
  import path7 from "path";
4903
5161
 
4904
5162
  // src/util/spawn.ts
@@ -4924,9 +5182,9 @@ async function run(cmd, args, opts = {}) {
4924
5182
  // src/fs/extract.ts
4925
5183
  var WIN_TAR = "C:\\Windows\\System32\\tar.exe";
4926
5184
  async function extractArchive(archiveFile, archiveType, destDir, platform) {
4927
- fs8.mkdirSync(destDir, { recursive: true });
5185
+ fs9.mkdirSync(destDir, { recursive: true });
4928
5186
  if (platform.os === "windows") {
4929
- if (fs8.existsSync(WIN_TAR)) {
5187
+ if (fs9.existsSync(WIN_TAR)) {
4930
5188
  await run(WIN_TAR, ["-xf", archiveFile, "-C", destDir]);
4931
5189
  } else {
4932
5190
  await run("powershell.exe", [
@@ -4949,21 +5207,21 @@ function tmpExtractDir(base) {
4949
5207
  }
4950
5208
 
4951
5209
  // src/fs/layout.ts
4952
- import fs9 from "fs";
5210
+ import fs10 from "fs";
4953
5211
  import path8 from "path";
4954
5212
  function normalizeExtracted(tmpDir, platform, type) {
4955
5213
  const spec = getSdkType(type);
4956
- const entries = fs9.readdirSync(tmpDir).filter((e) => e !== "._" && !e.startsWith("._"));
5214
+ const entries = fs10.readdirSync(tmpDir).filter((e) => e !== "._" && !e.startsWith("._"));
4957
5215
  const real = entries.filter((e) => e !== ".DS_Store");
4958
5216
  let root;
4959
- if (real.length === 1 && fs9.statSync(path8.join(tmpDir, real[0])).isDirectory()) {
5217
+ if (real.length === 1 && fs10.statSync(path8.join(tmpDir, real[0])).isDirectory()) {
4960
5218
  root = path8.join(tmpDir, real[0]);
4961
5219
  } else {
4962
5220
  root = tmpDir;
4963
5221
  }
4964
5222
  const home = spec.locateHome(root);
4965
5223
  const bin = path8.join(home, spec.binRelPath(platform));
4966
- if (!fs9.existsSync(bin)) {
5224
+ if (!fs10.existsSync(bin)) {
4967
5225
  throw new SdkvmError(`Archive does not look like a valid ${spec.label} (bin not found)`, {
4968
5226
  hint: `expected ${bin}`
4969
5227
  });
@@ -5015,7 +5273,7 @@ function createProgress(label2) {
5015
5273
  async function renameWithRetry(from, to, attempts = 3) {
5016
5274
  for (let i = 1; ; i++) {
5017
5275
  try {
5018
- fs10.renameSync(from, to);
5276
+ fs11.renameSync(from, to);
5019
5277
  return;
5020
5278
  } catch (err) {
5021
5279
  if (i >= attempts) throw err;
@@ -5049,7 +5307,7 @@ async function installCommand(type, specInput, opts) {
5049
5307
  const hintVersion = type === "java" || type === "node" ? String(artifact.version.major) : `${artifact.version.major}.${artifact.version.minor}`;
5050
5308
  await withLock(async () => {
5051
5309
  ensureLayout();
5052
- if (fs10.existsSync(finalDir)) {
5310
+ if (fs11.existsSync(finalDir)) {
5053
5311
  if (!opts.force) {
5054
5312
  log.warn(`${artifact.displayName} is already installed`);
5055
5313
  log.info(`run: ${cmdPath(type)} use ${hintVersion}`);
@@ -5057,15 +5315,17 @@ async function installCommand(type, specInput, opts) {
5057
5315
  }
5058
5316
  log.warn(`--force: removing existing ${artifact.dirName}`);
5059
5317
  }
5060
- for (const f of fs10.readdirSync(paths.cache())) {
5061
- if (f.endsWith(".part")) fs10.rmSync(path9.join(paths.cache(), f), { force: true });
5318
+ for (const f of fs11.readdirSync(paths.cache())) {
5319
+ if (f.endsWith(".part")) fs11.rmSync(path9.join(paths.cache(), f), { force: true });
5062
5320
  }
5063
5321
  const dest = path9.join(paths.cache(), cacheFileName(artifact.downloadUrl));
5064
5322
  const progress = createProgress(`\u2193 ${artifact.displayName}`);
5065
5323
  log.info(`downloading ${artifact.downloadUrl}`);
5066
5324
  const dl = await downloadFile(artifact.downloadUrl, dest, (b, t) => progress.update(b, t));
5067
5325
  progress.done(dl.bytes, null);
5068
- await verifyChecksum(artifact, dl.sha256, { strict: applied });
5326
+ const actual = artifact.checksum?.kind === "sha512" ? await hashFile(dest, "sha512") : dl.sha256;
5327
+ const fallbackUrl = applied ? checksumSidecarFallback(resolved.downloadUrl, resolved.checksum?.url, artifact.downloadUrl) : void 0;
5328
+ await verifyChecksum(artifact, actual, { strict: applied, fallbackUrl });
5069
5329
  const tmp = tmpExtractDir(paths.tmp());
5070
5330
  const bak = `${finalDir}.bak`;
5071
5331
  let finalTmp = tmp;
@@ -5074,28 +5334,28 @@ async function installCommand(type, specInput, opts) {
5074
5334
  await extractArchive(dest, artifact.archive, tmp, platform);
5075
5335
  const normalized = normalizeExtracted(tmp, platform, type);
5076
5336
  finalTmp = normalized.root;
5077
- fs10.rmSync(bak, { recursive: true, force: true });
5078
- if (fs10.existsSync(finalDir)) fs10.renameSync(finalDir, bak);
5337
+ fs11.rmSync(bak, { recursive: true, force: true });
5338
+ if (fs11.existsSync(finalDir)) fs11.renameSync(finalDir, bak);
5079
5339
  try {
5080
5340
  await renameWithRetry(normalized.root, finalDir);
5081
5341
  } catch (err) {
5082
- if (fs10.existsSync(bak) && !fs10.existsSync(finalDir)) {
5342
+ if (fs11.existsSync(bak) && !fs11.existsSync(finalDir)) {
5083
5343
  try {
5084
- fs10.renameSync(bak, finalDir);
5344
+ fs11.renameSync(bak, finalDir);
5085
5345
  } catch {
5086
5346
  }
5087
5347
  }
5088
5348
  throw err;
5089
5349
  }
5090
- fs10.rmSync(bak, { recursive: true, force: true });
5350
+ fs11.rmSync(bak, { recursive: true, force: true });
5091
5351
  } catch (err) {
5092
- fs10.rmSync(finalTmp, { recursive: true, force: true });
5093
- fs10.rmSync(tmp, { recursive: true, force: true });
5352
+ fs11.rmSync(finalTmp, { recursive: true, force: true });
5353
+ fs11.rmSync(tmp, { recursive: true, force: true });
5094
5354
  throw err;
5095
5355
  } finally {
5096
- fs10.rmSync(dest, { force: true });
5097
- fs10.rmSync(paths.tmp(), { recursive: true, force: true });
5098
- fs10.mkdirSync(paths.tmp(), { recursive: true });
5356
+ fs11.rmSync(dest, { force: true });
5357
+ fs11.rmSync(paths.tmp(), { recursive: true, force: true });
5358
+ fs11.mkdirSync(paths.tmp(), { recursive: true });
5099
5359
  }
5100
5360
  log.ok(`installed ${artifact.displayName} \u2192 ${finalDir}`);
5101
5361
  log.info(`switch to it: ${cmdPath(type)} use ${hintVersion}`);
@@ -5108,34 +5368,34 @@ import { promisify as promisify2 } from "util";
5108
5368
  import path15 from "path";
5109
5369
 
5110
5370
  // src/core/registry.ts
5111
- import fs12 from "fs";
5371
+ import fs13 from "fs";
5112
5372
  import path11 from "path";
5113
5373
 
5114
5374
  // src/fs/link.ts
5115
- import fs11 from "fs";
5375
+ import fs12 from "fs";
5116
5376
  import path10 from "path";
5117
5377
  function setCurrent(type, target, platform) {
5118
5378
  const link = paths.current(type);
5119
5379
  if (platform.os === "windows") {
5120
5380
  try {
5121
- fs11.rmSync(link, { force: true });
5381
+ fs12.rmSync(link, { force: true });
5122
5382
  } catch {
5123
- fs11.rmSync(link, { recursive: true, force: true });
5383
+ fs12.rmSync(link, { recursive: true, force: true });
5124
5384
  }
5125
- fs11.symlinkSync(target, link, "junction");
5385
+ fs12.symlinkSync(target, link, "junction");
5126
5386
  return;
5127
5387
  }
5128
5388
  const tmp = `${link}.tmp-${process.pid}`;
5129
- fs11.rmSync(tmp, { force: true });
5130
- fs11.symlinkSync(target, tmp);
5131
- fs11.renameSync(tmp, link);
5389
+ fs12.rmSync(tmp, { force: true });
5390
+ fs12.symlinkSync(target, tmp);
5391
+ fs12.renameSync(tmp, link);
5132
5392
  }
5133
5393
  function readCurrent(type) {
5134
5394
  const link = paths.current(type);
5135
5395
  try {
5136
- const st = fs11.lstatSync(link);
5396
+ const st = fs12.lstatSync(link);
5137
5397
  if (!st.isSymbolicLink()) return null;
5138
- const raw = fs11.readlinkSync(link);
5398
+ const raw = fs12.readlinkSync(link);
5139
5399
  return path10.resolve(path10.dirname(link), raw);
5140
5400
  } catch {
5141
5401
  return null;
@@ -5144,9 +5404,9 @@ function readCurrent(type) {
5144
5404
  function clearCurrent(type) {
5145
5405
  const link = paths.current(type);
5146
5406
  try {
5147
- fs11.rmSync(link, { force: true });
5407
+ fs12.rmSync(link, { force: true });
5148
5408
  } catch {
5149
- fs11.rmSync(link, { recursive: true, force: true });
5409
+ fs12.rmSync(link, { recursive: true, force: true });
5150
5410
  }
5151
5411
  }
5152
5412
 
@@ -5154,13 +5414,13 @@ function clearCurrent(type) {
5154
5414
  function listInstalled(type) {
5155
5415
  const spec = getSdkType(type);
5156
5416
  const root = paths.sdks(type);
5157
- if (!fs12.existsSync(root)) return [];
5417
+ if (!fs13.existsSync(root)) return [];
5158
5418
  const result = [];
5159
- for (const name of fs12.readdirSync(root)) {
5419
+ for (const name of fs13.readdirSync(root)) {
5160
5420
  const version = spec.parseDirName(name);
5161
5421
  if (!version) continue;
5162
5422
  const dirPath = path11.join(root, name);
5163
- if (!fs12.statSync(dirPath).isDirectory()) continue;
5423
+ if (!fs13.statSync(dirPath).isDirectory()) continue;
5164
5424
  result.push({ type, version, dirPath, home: spec.locateHome(dirPath) });
5165
5425
  }
5166
5426
  result.sort((a, b) => spec.compareVersions(a.version, b.version));
@@ -5232,7 +5492,7 @@ function detectRcFile(platform) {
5232
5492
  }
5233
5493
 
5234
5494
  // src/shell/rc.ts
5235
- import fs13 from "fs";
5495
+ import fs14 from "fs";
5236
5496
  import os3 from "os";
5237
5497
  import path13 from "path";
5238
5498
  function rcBegin(type) {
@@ -5270,8 +5530,8 @@ ${rcBlock(type)}
5270
5530
  `;
5271
5531
  }
5272
5532
  function upsertRcFile(file, type) {
5273
- const content = fs13.existsSync(file) ? fs13.readFileSync(file, "utf8") : "";
5274
- fs13.writeFileSync(file, upsertRcContent(content, type));
5533
+ const content = fs14.existsSync(file) ? fs14.readFileSync(file, "utf8") : "";
5534
+ fs14.writeFileSync(file, upsertRcContent(content, type));
5275
5535
  }
5276
5536
  function escapeRegex(s) {
5277
5537
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -5374,6 +5634,9 @@ async function useCommand(type, specInput, opts) {
5374
5634
  console.log(rcBlock(type));
5375
5635
  }
5376
5636
  }
5637
+ if (spec.requiresJdk && !envGet("JAVA_HOME")) {
5638
+ log.warn(`${spec.label} needs a JDK. Run: ${CLI_BIN} java use <version>`);
5639
+ }
5377
5640
  await showSdkVersion(path15.join(installed.home, spec.binRelPath(platform)), type);
5378
5641
  }
5379
5642
 
@@ -5454,7 +5717,7 @@ function currentCommand(types = SDK_TYPES) {
5454
5717
  }
5455
5718
 
5456
5719
  // src/cli/uninstall.ts
5457
- import fs14 from "fs";
5720
+ import fs15 from "fs";
5458
5721
  async function uninstallCommand(type, specInput, opts) {
5459
5722
  const spec = getSdkType(type);
5460
5723
  const installed = findInstalled(type, specInput, opts.vendor);
@@ -5464,7 +5727,7 @@ async function uninstallCommand(type, specInput, opts) {
5464
5727
  log.warn(`uninstalled the current ${spec.label}; ${spec.envVar} is now dangling`);
5465
5728
  log.info(`select another: ${cmdPath(type)} use <version>`);
5466
5729
  }
5467
- fs14.rmSync(installed.dirPath, { recursive: true, force: true });
5730
+ fs15.rmSync(installed.dirPath, { recursive: true, force: true });
5468
5731
  });
5469
5732
  log.ok(`removed ${installed.version.vendor}-${spec.formatVersion(installed.version)}`);
5470
5733
  }
@@ -5474,7 +5737,8 @@ var MIRRORABLE_BY_TYPE = {
5474
5737
  java: ["temurin"],
5475
5738
  go: ["golang"],
5476
5739
  flutter: ["flutter"],
5477
- node: ["nodejs"]
5740
+ node: ["nodejs"],
5741
+ maven: ["maven"]
5478
5742
  };
5479
5743
  var MIRROR_SITE_PRESETS = [
5480
5744
  {
@@ -5502,14 +5766,16 @@ var MIRROR_SITE_PRESETS = [
5502
5766
  list: true,
5503
5767
  vendors: {
5504
5768
  golang: "https://mirrors.aliyun.com/golang",
5505
- nodejs: "https://mirrors.aliyun.com/nodejs-release"
5769
+ nodejs: "https://mirrors.aliyun.com/nodejs-release",
5770
+ maven: "https://maven.aliyun.com/repository/central"
5506
5771
  }
5507
5772
  },
5508
5773
  {
5509
5774
  name: "huawei",
5510
5775
  list: true,
5511
5776
  vendors: {
5512
- nodejs: "https://repo.huaweicloud.com/nodejs"
5777
+ nodejs: "https://repo.huaweicloud.com/nodejs",
5778
+ maven: "https://repo.huaweicloud.com/repository/maven"
5513
5779
  }
5514
5780
  },
5515
5781
  {
@@ -5758,7 +6024,7 @@ function mirrorCommand(type, action, arg1, arg2) {
5758
6024
 
5759
6025
  // src/cli/upgrade.ts
5760
6026
  import { spawn } from "child_process";
5761
- import fs15 from "fs";
6027
+ import fs16 from "fs";
5762
6028
  import path16 from "path";
5763
6029
  import { fileURLToPath as fileURLToPath2 } from "url";
5764
6030
  var RELEASE_REPO = "QInJ1995/sdkvm";
@@ -5794,12 +6060,12 @@ function checksumFor(sumsText, fileName) {
5794
6060
  }
5795
6061
  async function prepareCliPackage(archiveFile, home = sdkvmHome()) {
5796
6062
  const staging = path16.join(home, "cli.next");
5797
- fs15.rmSync(staging, { recursive: true, force: true });
5798
- fs15.mkdirSync(home, { recursive: true });
6063
+ fs16.rmSync(staging, { recursive: true, force: true });
6064
+ fs16.mkdirSync(home, { recursive: true });
5799
6065
  await extractArchive(archiveFile, "tar.gz", staging, detectPlatform());
5800
6066
  const unpacked = path16.join(staging, "package");
5801
- if (!fs15.existsSync(path16.join(unpacked, "package.json"))) {
5802
- fs15.rmSync(staging, { recursive: true, force: true });
6067
+ if (!fs16.existsSync(path16.join(unpacked, "package.json"))) {
6068
+ fs16.rmSync(staging, { recursive: true, force: true });
5803
6069
  throw new SdkvmError("Release archive is missing package/package.json", { hint: archiveFile });
5804
6070
  }
5805
6071
  return unpacked;
@@ -5809,22 +6075,22 @@ async function replaceCliPackage(archiveFile, home = sdkvmHome()) {
5809
6075
  const bak = path16.join(home, "cli.bak");
5810
6076
  const cli = path16.join(home, "cli");
5811
6077
  const unpacked = await prepareCliPackage(archiveFile, home);
5812
- fs15.rmSync(bak, { recursive: true, force: true });
5813
- if (fs15.existsSync(cli)) fs15.renameSync(cli, bak);
6078
+ fs16.rmSync(bak, { recursive: true, force: true });
6079
+ if (fs16.existsSync(cli)) fs16.renameSync(cli, bak);
5814
6080
  try {
5815
- fs15.renameSync(unpacked, cli);
6081
+ fs16.renameSync(unpacked, cli);
5816
6082
  } catch (err) {
5817
- if (fs15.existsSync(bak) && !fs15.existsSync(cli)) {
6083
+ if (fs16.existsSync(bak) && !fs16.existsSync(cli)) {
5818
6084
  try {
5819
- fs15.renameSync(bak, cli);
6085
+ fs16.renameSync(bak, cli);
5820
6086
  } catch {
5821
6087
  }
5822
6088
  }
5823
- fs15.rmSync(staging, { recursive: true, force: true });
6089
+ fs16.rmSync(staging, { recursive: true, force: true });
5824
6090
  throw err;
5825
6091
  }
5826
- fs15.rmSync(bak, { recursive: true, force: true });
5827
- fs15.rmSync(staging, { recursive: true, force: true });
6092
+ fs16.rmSync(bak, { recursive: true, force: true });
6093
+ fs16.rmSync(staging, { recursive: true, force: true });
5828
6094
  }
5829
6095
  function windowsUpgradeScript(home) {
5830
6096
  return [
@@ -5847,7 +6113,7 @@ function windowsUpgradeScript(home) {
5847
6113
  }
5848
6114
  function scheduleWindowsCliReplace(home) {
5849
6115
  const script = path16.join(home, "upgrade-apply.cmd");
5850
- fs15.writeFileSync(script, windowsUpgradeScript(home), "utf8");
6116
+ fs16.writeFileSync(script, windowsUpgradeScript(home), "utf8");
5851
6117
  const child = spawn("cmd.exe", ["/c", script], {
5852
6118
  detached: true,
5853
6119
  stdio: "ignore",
@@ -5869,7 +6135,7 @@ async function upgradeCommand() {
5869
6135
  if (!expected) {
5870
6136
  throw new SdkvmError(`No checksum for ${RELEASE_ASSET}`, { hint: sumsUrl });
5871
6137
  }
5872
- fs15.mkdirSync(paths.cache(), { recursive: true });
6138
+ fs16.mkdirSync(paths.cache(), { recursive: true });
5873
6139
  const dest = path16.join(paths.cache(), RELEASE_ASSET);
5874
6140
  try {
5875
6141
  const downloaded = await downloadFile(assetUrl, dest);
@@ -5894,8 +6160,8 @@ async function upgradeCommand() {
5894
6160
  `upgraded CLI ${before} \u2192 ${after} in ${path16.join(home, "cli")}; runtime and installed SDKs were left in place`
5895
6161
  );
5896
6162
  } finally {
5897
- fs15.rmSync(dest, { force: true });
5898
- fs15.rmSync(`${dest}.part`, { force: true });
6163
+ fs16.rmSync(dest, { force: true });
6164
+ fs16.rmSync(`${dest}.part`, { force: true });
5899
6165
  }
5900
6166
  });
5901
6167
  }
@@ -6095,17 +6361,29 @@ async function nrmTest(name, opts = {}) {
6095
6361
 
6096
6362
  // src/index.ts
6097
6363
  var program2 = new Command();
6098
- program2.name("sdkvm").description("SDK version manager \u2014 install & switch JDKs (Temurin / Zulu / Corretto), Go toolchains, Flutter SDKs, and Node.js runtimes").version(getVersion());
6364
+ program2.name("sdkvm").description("SDK version manager \u2014 install & switch JDKs (Temurin / Zulu / Corretto), Go toolchains, Flutter SDKs, Node.js runtimes, and Apache Maven").version(getVersion());
6365
+ var INSTALL_HELP = {
6366
+ java: "21 | lts | 21.0.5 | 21.0.5+11 | temurin-21",
6367
+ go: "1.24 | 1.24.5 | latest | golang-1.24",
6368
+ flutter: "3.47 | 3.47.5 | 3.49.0-0.1.pre | latest | flutter-3.47",
6369
+ node: "22 | 22.20.0 | lts | latest | nodejs-22.20.0",
6370
+ maven: "3 | 3.9 | 3.9.9 | 4.0.0-rc-4 | latest | maven-3.9"
6371
+ };
6372
+ var VERSION_EXAMPLE = {
6373
+ java: "21 / temurin-21.0.5+11",
6374
+ go: "1.24 / golang-1.24.5",
6375
+ flutter: "3.47 / flutter-3.47.5",
6376
+ node: "22 / nodejs-22.20.0",
6377
+ maven: "3.9 / maven-3.9.9"
6378
+ };
6099
6379
  function registerSdkCommands(cmd, type) {
6100
6380
  const s = getSdkType(type);
6101
- const isJava = type === "java";
6102
- const installHelp = isJava ? "21 | lts | 21.0.5 | 21.0.5+11 | temurin-21" : type === "go" ? "1.24 | 1.24.5 | latest | golang-1.24" : type === "flutter" ? "3.47 | 3.47.5 | 3.49.0-0.1.pre | latest | flutter-3.47" : "22 | 22.20.0 | lts | latest | nodejs-22.20.0";
6103
6381
  const vendorIds = s.vendors.map((v) => v.id).join(" | ");
6104
- cmd.command("install").description(`install a ${s.label}: ${installHelp}`).argument("<version>", "version spec (optional vendor- prefix)").option("--vendor <id>", `vendor: ${vendorIds}`).option("--force", "reinstall even if already installed").action((v, o) => installCommand(type, v, o));
6105
- cmd.command("use").description(`switch the current ${s.label} (updates ${s.envVar} / PATH)`).argument("<version>", `installed version, e.g. ${isJava ? "21 / temurin-21.0.5+11" : type === "go" ? "1.24 / golang-1.24.5" : type === "flutter" ? "3.47 / flutter-3.47.5" : "22 / nodejs-22.20.0"}`).option("--vendor <id>", "restrict matching to a vendor").action((v, o) => useCommand(type, v, o));
6382
+ cmd.command("install").description(`install a ${s.label}: ${INSTALL_HELP[type]}`).argument("<version>", "version spec (optional vendor- prefix)").option("--vendor <id>", `vendor: ${vendorIds}`).option("--force", "reinstall even if already installed").action((v, o) => installCommand(type, v, o));
6383
+ cmd.command("use").description(`switch the current ${s.label} (updates ${s.envVar} / PATH)`).argument("<version>", `installed version, e.g. ${VERSION_EXAMPLE[type]}`).option("--vendor <id>", "restrict matching to a vendor").action((v, o) => useCommand(type, v, o));
6106
6384
  cmd.command("ls").alias("list").description(`list installed ${s.label}s (\u2192 marks current); -r lists installable versions`).option("-r, --remote", "list installable versions from all vendors").option("--vendor <id>", "filter --remote output to one vendor").action((o) => lsCommand(type, o));
6107
6385
  cmd.command("current").description(`show the current ${s.label}`).action(() => currentCommand([type]));
6108
- cmd.command("uninstall").description(`remove an installed ${s.label}`).argument("<version>", `installed version, e.g. ${isJava ? "21 / temurin-21.0.5+11" : type === "go" ? "1.24 / golang-1.24.5" : type === "flutter" ? "3.47 / flutter-3.47.5" : "22 / nodejs-22.20.0"}`).option("--vendor <id>", "restrict matching to a vendor").action((v, o) => uninstallCommand(type, v, o));
6386
+ cmd.command("uninstall").description(`remove an installed ${s.label}`).argument("<version>", `installed version, e.g. ${VERSION_EXAMPLE[type]}`).option("--vendor <id>", "restrict matching to a vendor").action((v, o) => uninstallCommand(type, v, o));
6109
6387
  cmd.command("mirror").description("show, set, or switch download mirror sites for this SDK").argument("[action]", "ls | use | current | show | set | unset").argument("[nameOrVendor]", "site name, vendor id, or URL").argument("[url]", "mirror root URL (for set)").action(
6110
6388
  (a, v, u) => mirrorCommand(type, a, v, u)
6111
6389
  );
@@ -6123,6 +6401,9 @@ flutterCmd.action(() => flutterCmd.help());
6123
6401
  var nodeCmd = program2.command("node").description("Node.js subcommands");
6124
6402
  registerSdkCommands(nodeCmd, "node");
6125
6403
  nodeCmd.action(() => nodeCmd.help());
6404
+ var mavenCmd = program2.command("maven").description("Apache Maven subcommands");
6405
+ registerSdkCommands(mavenCmd, "maven");
6406
+ mavenCmd.action(() => mavenCmd.help());
6126
6407
  program2.commands.find((c) => c.name() === "current")?.action(() => currentCommand());
6127
6408
  program2.command("version").description("print sdkvm CLI version").action(versionCommand);
6128
6409
  program2.command("upgrade").description("upgrade the sdkvm CLI (script install replaces ~/.sdkvm/cli; npm install prints npm update -g)").action(upgradeCommand);