sdkvm 1.0.2 → 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 +153 -122
  2. package/README.md +154 -123
  3. package/dist/index.js +385 -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,19 +5034,72 @@ 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;
4829
- function extractExpectedChecksum(text) {
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
+ }
5045
+ function hex64(value) {
5046
+ return hexOf(value, "sha256");
5047
+ }
5048
+ function extractExpectedChecksum(text, kind = "sha256") {
4830
5049
  const trimmed = text.trim();
4831
- if (trimmed.startsWith("{")) {
5050
+ if (kind === "sha256" && trimmed.startsWith("{")) {
4832
5051
  try {
4833
5052
  const obj = JSON.parse(trimmed);
4834
- if (obj.checksum && HEX64.test(obj.checksum)) return obj.checksum.toLowerCase();
5053
+ const direct = hex64(obj.checksum) ?? hex64(obj.sha256);
5054
+ if (direct) return direct;
5055
+ if (Array.isArray(obj.hashes)) {
5056
+ for (const item of obj.hashes) {
5057
+ if (!item || typeof item !== "object") continue;
5058
+ const hash = item;
5059
+ const alg = typeof hash.alg === "string" ? hash.alg.toLowerCase().replace(/-/g, "") : "";
5060
+ if (alg === "sha256") {
5061
+ const content = hex64(hash.content);
5062
+ if (content) return content;
5063
+ }
5064
+ }
5065
+ }
4835
5066
  } catch {
4836
5067
  }
4837
5068
  return null;
4838
5069
  }
4839
5070
  const token = trimmed.split(/\s+/)[0] ?? "";
4840
- 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;
4841
5103
  }
4842
5104
  async function verifyChecksum(artifact, actual, opts = {}) {
4843
5105
  const strict = opts.strict === true;
@@ -4853,17 +5115,28 @@ async function verifyChecksum(artifact, actual, opts = {}) {
4853
5115
  }
4854
5116
  let expected = info.expected?.toLowerCase() ?? null;
4855
5117
  if (!expected && info.url) {
5118
+ const fallback = opts.fallbackUrl && opts.fallbackUrl !== info.url ? opts.fallbackUrl : void 0;
4856
5119
  try {
4857
- expected = extractExpectedChecksum(await httpText(info.url));
5120
+ expected = extractExpectedChecksum(await httpText(info.url), info.kind);
4858
5121
  } catch (err) {
4859
- if (strict) {
4860
- const detail = err instanceof Error ? err.message.split("\n")[0] : String(err);
4861
- throw new SdkvmError(`Cannot fetch checksum for ${artifact.displayName}`, {
4862
- hint: `${detail}. Mirrored installs need the official checksum URL to be reachable.`
4863
- });
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;
4864
5131
  }
4865
- log.warn(`cannot fetch checksum for ${artifact.displayName}, skipping verification`);
4866
- 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;
4867
5140
  }
4868
5141
  }
4869
5142
  if (!expected) {
@@ -4883,7 +5156,7 @@ async function verifyChecksum(artifact, actual, opts = {}) {
4883
5156
  }
4884
5157
 
4885
5158
  // src/fs/extract.ts
4886
- import fs8 from "fs";
5159
+ import fs9 from "fs";
4887
5160
  import path7 from "path";
4888
5161
 
4889
5162
  // src/util/spawn.ts
@@ -4909,9 +5182,9 @@ async function run(cmd, args, opts = {}) {
4909
5182
  // src/fs/extract.ts
4910
5183
  var WIN_TAR = "C:\\Windows\\System32\\tar.exe";
4911
5184
  async function extractArchive(archiveFile, archiveType, destDir, platform) {
4912
- fs8.mkdirSync(destDir, { recursive: true });
5185
+ fs9.mkdirSync(destDir, { recursive: true });
4913
5186
  if (platform.os === "windows") {
4914
- if (fs8.existsSync(WIN_TAR)) {
5187
+ if (fs9.existsSync(WIN_TAR)) {
4915
5188
  await run(WIN_TAR, ["-xf", archiveFile, "-C", destDir]);
4916
5189
  } else {
4917
5190
  await run("powershell.exe", [
@@ -4934,21 +5207,21 @@ function tmpExtractDir(base) {
4934
5207
  }
4935
5208
 
4936
5209
  // src/fs/layout.ts
4937
- import fs9 from "fs";
5210
+ import fs10 from "fs";
4938
5211
  import path8 from "path";
4939
5212
  function normalizeExtracted(tmpDir, platform, type) {
4940
5213
  const spec = getSdkType(type);
4941
- const entries = fs9.readdirSync(tmpDir).filter((e) => e !== "._" && !e.startsWith("._"));
5214
+ const entries = fs10.readdirSync(tmpDir).filter((e) => e !== "._" && !e.startsWith("._"));
4942
5215
  const real = entries.filter((e) => e !== ".DS_Store");
4943
5216
  let root;
4944
- 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()) {
4945
5218
  root = path8.join(tmpDir, real[0]);
4946
5219
  } else {
4947
5220
  root = tmpDir;
4948
5221
  }
4949
5222
  const home = spec.locateHome(root);
4950
5223
  const bin = path8.join(home, spec.binRelPath(platform));
4951
- if (!fs9.existsSync(bin)) {
5224
+ if (!fs10.existsSync(bin)) {
4952
5225
  throw new SdkvmError(`Archive does not look like a valid ${spec.label} (bin not found)`, {
4953
5226
  hint: `expected ${bin}`
4954
5227
  });
@@ -5000,7 +5273,7 @@ function createProgress(label2) {
5000
5273
  async function renameWithRetry(from, to, attempts = 3) {
5001
5274
  for (let i = 1; ; i++) {
5002
5275
  try {
5003
- fs10.renameSync(from, to);
5276
+ fs11.renameSync(from, to);
5004
5277
  return;
5005
5278
  } catch (err) {
5006
5279
  if (i >= attempts) throw err;
@@ -5034,7 +5307,7 @@ async function installCommand(type, specInput, opts) {
5034
5307
  const hintVersion = type === "java" || type === "node" ? String(artifact.version.major) : `${artifact.version.major}.${artifact.version.minor}`;
5035
5308
  await withLock(async () => {
5036
5309
  ensureLayout();
5037
- if (fs10.existsSync(finalDir)) {
5310
+ if (fs11.existsSync(finalDir)) {
5038
5311
  if (!opts.force) {
5039
5312
  log.warn(`${artifact.displayName} is already installed`);
5040
5313
  log.info(`run: ${cmdPath(type)} use ${hintVersion}`);
@@ -5042,15 +5315,17 @@ async function installCommand(type, specInput, opts) {
5042
5315
  }
5043
5316
  log.warn(`--force: removing existing ${artifact.dirName}`);
5044
5317
  }
5045
- for (const f of fs10.readdirSync(paths.cache())) {
5046
- 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 });
5047
5320
  }
5048
5321
  const dest = path9.join(paths.cache(), cacheFileName(artifact.downloadUrl));
5049
5322
  const progress = createProgress(`\u2193 ${artifact.displayName}`);
5050
5323
  log.info(`downloading ${artifact.downloadUrl}`);
5051
5324
  const dl = await downloadFile(artifact.downloadUrl, dest, (b, t) => progress.update(b, t));
5052
5325
  progress.done(dl.bytes, null);
5053
- 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 });
5054
5329
  const tmp = tmpExtractDir(paths.tmp());
5055
5330
  const bak = `${finalDir}.bak`;
5056
5331
  let finalTmp = tmp;
@@ -5059,28 +5334,28 @@ async function installCommand(type, specInput, opts) {
5059
5334
  await extractArchive(dest, artifact.archive, tmp, platform);
5060
5335
  const normalized = normalizeExtracted(tmp, platform, type);
5061
5336
  finalTmp = normalized.root;
5062
- fs10.rmSync(bak, { recursive: true, force: true });
5063
- 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);
5064
5339
  try {
5065
5340
  await renameWithRetry(normalized.root, finalDir);
5066
5341
  } catch (err) {
5067
- if (fs10.existsSync(bak) && !fs10.existsSync(finalDir)) {
5342
+ if (fs11.existsSync(bak) && !fs11.existsSync(finalDir)) {
5068
5343
  try {
5069
- fs10.renameSync(bak, finalDir);
5344
+ fs11.renameSync(bak, finalDir);
5070
5345
  } catch {
5071
5346
  }
5072
5347
  }
5073
5348
  throw err;
5074
5349
  }
5075
- fs10.rmSync(bak, { recursive: true, force: true });
5350
+ fs11.rmSync(bak, { recursive: true, force: true });
5076
5351
  } catch (err) {
5077
- fs10.rmSync(finalTmp, { recursive: true, force: true });
5078
- fs10.rmSync(tmp, { recursive: true, force: true });
5352
+ fs11.rmSync(finalTmp, { recursive: true, force: true });
5353
+ fs11.rmSync(tmp, { recursive: true, force: true });
5079
5354
  throw err;
5080
5355
  } finally {
5081
- fs10.rmSync(dest, { force: true });
5082
- fs10.rmSync(paths.tmp(), { recursive: true, force: true });
5083
- 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 });
5084
5359
  }
5085
5360
  log.ok(`installed ${artifact.displayName} \u2192 ${finalDir}`);
5086
5361
  log.info(`switch to it: ${cmdPath(type)} use ${hintVersion}`);
@@ -5093,34 +5368,34 @@ import { promisify as promisify2 } from "util";
5093
5368
  import path15 from "path";
5094
5369
 
5095
5370
  // src/core/registry.ts
5096
- import fs12 from "fs";
5371
+ import fs13 from "fs";
5097
5372
  import path11 from "path";
5098
5373
 
5099
5374
  // src/fs/link.ts
5100
- import fs11 from "fs";
5375
+ import fs12 from "fs";
5101
5376
  import path10 from "path";
5102
5377
  function setCurrent(type, target, platform) {
5103
5378
  const link = paths.current(type);
5104
5379
  if (platform.os === "windows") {
5105
5380
  try {
5106
- fs11.rmSync(link, { force: true });
5381
+ fs12.rmSync(link, { force: true });
5107
5382
  } catch {
5108
- fs11.rmSync(link, { recursive: true, force: true });
5383
+ fs12.rmSync(link, { recursive: true, force: true });
5109
5384
  }
5110
- fs11.symlinkSync(target, link, "junction");
5385
+ fs12.symlinkSync(target, link, "junction");
5111
5386
  return;
5112
5387
  }
5113
5388
  const tmp = `${link}.tmp-${process.pid}`;
5114
- fs11.rmSync(tmp, { force: true });
5115
- fs11.symlinkSync(target, tmp);
5116
- fs11.renameSync(tmp, link);
5389
+ fs12.rmSync(tmp, { force: true });
5390
+ fs12.symlinkSync(target, tmp);
5391
+ fs12.renameSync(tmp, link);
5117
5392
  }
5118
5393
  function readCurrent(type) {
5119
5394
  const link = paths.current(type);
5120
5395
  try {
5121
- const st = fs11.lstatSync(link);
5396
+ const st = fs12.lstatSync(link);
5122
5397
  if (!st.isSymbolicLink()) return null;
5123
- const raw = fs11.readlinkSync(link);
5398
+ const raw = fs12.readlinkSync(link);
5124
5399
  return path10.resolve(path10.dirname(link), raw);
5125
5400
  } catch {
5126
5401
  return null;
@@ -5129,9 +5404,9 @@ function readCurrent(type) {
5129
5404
  function clearCurrent(type) {
5130
5405
  const link = paths.current(type);
5131
5406
  try {
5132
- fs11.rmSync(link, { force: true });
5407
+ fs12.rmSync(link, { force: true });
5133
5408
  } catch {
5134
- fs11.rmSync(link, { recursive: true, force: true });
5409
+ fs12.rmSync(link, { recursive: true, force: true });
5135
5410
  }
5136
5411
  }
5137
5412
 
@@ -5139,13 +5414,13 @@ function clearCurrent(type) {
5139
5414
  function listInstalled(type) {
5140
5415
  const spec = getSdkType(type);
5141
5416
  const root = paths.sdks(type);
5142
- if (!fs12.existsSync(root)) return [];
5417
+ if (!fs13.existsSync(root)) return [];
5143
5418
  const result = [];
5144
- for (const name of fs12.readdirSync(root)) {
5419
+ for (const name of fs13.readdirSync(root)) {
5145
5420
  const version = spec.parseDirName(name);
5146
5421
  if (!version) continue;
5147
5422
  const dirPath = path11.join(root, name);
5148
- if (!fs12.statSync(dirPath).isDirectory()) continue;
5423
+ if (!fs13.statSync(dirPath).isDirectory()) continue;
5149
5424
  result.push({ type, version, dirPath, home: spec.locateHome(dirPath) });
5150
5425
  }
5151
5426
  result.sort((a, b) => spec.compareVersions(a.version, b.version));
@@ -5217,7 +5492,7 @@ function detectRcFile(platform) {
5217
5492
  }
5218
5493
 
5219
5494
  // src/shell/rc.ts
5220
- import fs13 from "fs";
5495
+ import fs14 from "fs";
5221
5496
  import os3 from "os";
5222
5497
  import path13 from "path";
5223
5498
  function rcBegin(type) {
@@ -5255,8 +5530,8 @@ ${rcBlock(type)}
5255
5530
  `;
5256
5531
  }
5257
5532
  function upsertRcFile(file, type) {
5258
- const content = fs13.existsSync(file) ? fs13.readFileSync(file, "utf8") : "";
5259
- fs13.writeFileSync(file, upsertRcContent(content, type));
5533
+ const content = fs14.existsSync(file) ? fs14.readFileSync(file, "utf8") : "";
5534
+ fs14.writeFileSync(file, upsertRcContent(content, type));
5260
5535
  }
5261
5536
  function escapeRegex(s) {
5262
5537
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -5359,6 +5634,9 @@ async function useCommand(type, specInput, opts) {
5359
5634
  console.log(rcBlock(type));
5360
5635
  }
5361
5636
  }
5637
+ if (spec.requiresJdk && !envGet("JAVA_HOME")) {
5638
+ log.warn(`${spec.label} needs a JDK. Run: ${CLI_BIN} java use <version>`);
5639
+ }
5362
5640
  await showSdkVersion(path15.join(installed.home, spec.binRelPath(platform)), type);
5363
5641
  }
5364
5642
 
@@ -5439,7 +5717,7 @@ function currentCommand(types = SDK_TYPES) {
5439
5717
  }
5440
5718
 
5441
5719
  // src/cli/uninstall.ts
5442
- import fs14 from "fs";
5720
+ import fs15 from "fs";
5443
5721
  async function uninstallCommand(type, specInput, opts) {
5444
5722
  const spec = getSdkType(type);
5445
5723
  const installed = findInstalled(type, specInput, opts.vendor);
@@ -5449,7 +5727,7 @@ async function uninstallCommand(type, specInput, opts) {
5449
5727
  log.warn(`uninstalled the current ${spec.label}; ${spec.envVar} is now dangling`);
5450
5728
  log.info(`select another: ${cmdPath(type)} use <version>`);
5451
5729
  }
5452
- fs14.rmSync(installed.dirPath, { recursive: true, force: true });
5730
+ fs15.rmSync(installed.dirPath, { recursive: true, force: true });
5453
5731
  });
5454
5732
  log.ok(`removed ${installed.version.vendor}-${spec.formatVersion(installed.version)}`);
5455
5733
  }
@@ -5459,7 +5737,8 @@ var MIRRORABLE_BY_TYPE = {
5459
5737
  java: ["temurin"],
5460
5738
  go: ["golang"],
5461
5739
  flutter: ["flutter"],
5462
- node: ["nodejs"]
5740
+ node: ["nodejs"],
5741
+ maven: ["maven"]
5463
5742
  };
5464
5743
  var MIRROR_SITE_PRESETS = [
5465
5744
  {
@@ -5487,14 +5766,16 @@ var MIRROR_SITE_PRESETS = [
5487
5766
  list: true,
5488
5767
  vendors: {
5489
5768
  golang: "https://mirrors.aliyun.com/golang",
5490
- nodejs: "https://mirrors.aliyun.com/nodejs-release"
5769
+ nodejs: "https://mirrors.aliyun.com/nodejs-release",
5770
+ maven: "https://maven.aliyun.com/repository/central"
5491
5771
  }
5492
5772
  },
5493
5773
  {
5494
5774
  name: "huawei",
5495
5775
  list: true,
5496
5776
  vendors: {
5497
- nodejs: "https://repo.huaweicloud.com/nodejs"
5777
+ nodejs: "https://repo.huaweicloud.com/nodejs",
5778
+ maven: "https://repo.huaweicloud.com/repository/maven"
5498
5779
  }
5499
5780
  },
5500
5781
  {
@@ -5743,7 +6024,7 @@ function mirrorCommand(type, action, arg1, arg2) {
5743
6024
 
5744
6025
  // src/cli/upgrade.ts
5745
6026
  import { spawn } from "child_process";
5746
- import fs15 from "fs";
6027
+ import fs16 from "fs";
5747
6028
  import path16 from "path";
5748
6029
  import { fileURLToPath as fileURLToPath2 } from "url";
5749
6030
  var RELEASE_REPO = "QInJ1995/sdkvm";
@@ -5779,12 +6060,12 @@ function checksumFor(sumsText, fileName) {
5779
6060
  }
5780
6061
  async function prepareCliPackage(archiveFile, home = sdkvmHome()) {
5781
6062
  const staging = path16.join(home, "cli.next");
5782
- fs15.rmSync(staging, { recursive: true, force: true });
5783
- fs15.mkdirSync(home, { recursive: true });
6063
+ fs16.rmSync(staging, { recursive: true, force: true });
6064
+ fs16.mkdirSync(home, { recursive: true });
5784
6065
  await extractArchive(archiveFile, "tar.gz", staging, detectPlatform());
5785
6066
  const unpacked = path16.join(staging, "package");
5786
- if (!fs15.existsSync(path16.join(unpacked, "package.json"))) {
5787
- fs15.rmSync(staging, { recursive: true, force: true });
6067
+ if (!fs16.existsSync(path16.join(unpacked, "package.json"))) {
6068
+ fs16.rmSync(staging, { recursive: true, force: true });
5788
6069
  throw new SdkvmError("Release archive is missing package/package.json", { hint: archiveFile });
5789
6070
  }
5790
6071
  return unpacked;
@@ -5794,22 +6075,22 @@ async function replaceCliPackage(archiveFile, home = sdkvmHome()) {
5794
6075
  const bak = path16.join(home, "cli.bak");
5795
6076
  const cli = path16.join(home, "cli");
5796
6077
  const unpacked = await prepareCliPackage(archiveFile, home);
5797
- fs15.rmSync(bak, { recursive: true, force: true });
5798
- 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);
5799
6080
  try {
5800
- fs15.renameSync(unpacked, cli);
6081
+ fs16.renameSync(unpacked, cli);
5801
6082
  } catch (err) {
5802
- if (fs15.existsSync(bak) && !fs15.existsSync(cli)) {
6083
+ if (fs16.existsSync(bak) && !fs16.existsSync(cli)) {
5803
6084
  try {
5804
- fs15.renameSync(bak, cli);
6085
+ fs16.renameSync(bak, cli);
5805
6086
  } catch {
5806
6087
  }
5807
6088
  }
5808
- fs15.rmSync(staging, { recursive: true, force: true });
6089
+ fs16.rmSync(staging, { recursive: true, force: true });
5809
6090
  throw err;
5810
6091
  }
5811
- fs15.rmSync(bak, { recursive: true, force: true });
5812
- fs15.rmSync(staging, { recursive: true, force: true });
6092
+ fs16.rmSync(bak, { recursive: true, force: true });
6093
+ fs16.rmSync(staging, { recursive: true, force: true });
5813
6094
  }
5814
6095
  function windowsUpgradeScript(home) {
5815
6096
  return [
@@ -5832,7 +6113,7 @@ function windowsUpgradeScript(home) {
5832
6113
  }
5833
6114
  function scheduleWindowsCliReplace(home) {
5834
6115
  const script = path16.join(home, "upgrade-apply.cmd");
5835
- fs15.writeFileSync(script, windowsUpgradeScript(home), "utf8");
6116
+ fs16.writeFileSync(script, windowsUpgradeScript(home), "utf8");
5836
6117
  const child = spawn("cmd.exe", ["/c", script], {
5837
6118
  detached: true,
5838
6119
  stdio: "ignore",
@@ -5854,7 +6135,7 @@ async function upgradeCommand() {
5854
6135
  if (!expected) {
5855
6136
  throw new SdkvmError(`No checksum for ${RELEASE_ASSET}`, { hint: sumsUrl });
5856
6137
  }
5857
- fs15.mkdirSync(paths.cache(), { recursive: true });
6138
+ fs16.mkdirSync(paths.cache(), { recursive: true });
5858
6139
  const dest = path16.join(paths.cache(), RELEASE_ASSET);
5859
6140
  try {
5860
6141
  const downloaded = await downloadFile(assetUrl, dest);
@@ -5879,8 +6160,8 @@ async function upgradeCommand() {
5879
6160
  `upgraded CLI ${before} \u2192 ${after} in ${path16.join(home, "cli")}; runtime and installed SDKs were left in place`
5880
6161
  );
5881
6162
  } finally {
5882
- fs15.rmSync(dest, { force: true });
5883
- fs15.rmSync(`${dest}.part`, { force: true });
6163
+ fs16.rmSync(dest, { force: true });
6164
+ fs16.rmSync(`${dest}.part`, { force: true });
5884
6165
  }
5885
6166
  });
5886
6167
  }
@@ -6080,17 +6361,29 @@ async function nrmTest(name, opts = {}) {
6080
6361
 
6081
6362
  // src/index.ts
6082
6363
  var program2 = new Command();
6083
- 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
+ };
6084
6379
  function registerSdkCommands(cmd, type) {
6085
6380
  const s = getSdkType(type);
6086
- const isJava = type === "java";
6087
- 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";
6088
6381
  const vendorIds = s.vendors.map((v) => v.id).join(" | ");
6089
- 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));
6090
- 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));
6091
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));
6092
6385
  cmd.command("current").description(`show the current ${s.label}`).action(() => currentCommand([type]));
6093
- 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));
6094
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(
6095
6388
  (a, v, u) => mirrorCommand(type, a, v, u)
6096
6389
  );
@@ -6108,6 +6401,9 @@ flutterCmd.action(() => flutterCmd.help());
6108
6401
  var nodeCmd = program2.command("node").description("Node.js subcommands");
6109
6402
  registerSdkCommands(nodeCmd, "node");
6110
6403
  nodeCmd.action(() => nodeCmd.help());
6404
+ var mavenCmd = program2.command("maven").description("Apache Maven subcommands");
6405
+ registerSdkCommands(mavenCmd, "maven");
6406
+ mavenCmd.action(() => mavenCmd.help());
6111
6407
  program2.commands.find((c) => c.name() === "current")?.action(() => currentCommand());
6112
6408
  program2.command("version").description("print sdkvm CLI version").action(versionCommand);
6113
6409
  program2.command("upgrade").description("upgrade the sdkvm CLI (script install replaces ~/.sdkvm/cli; npm install prints npm update -g)").action(upgradeCommand);