sdkvm 1.0.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +444 -0
  3. package/dist/index.js +1828 -0
  4. package/package.json +54 -0
package/dist/index.js ADDED
@@ -0,0 +1,1828 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/util/errors.ts
7
+ var SdkvmError = class extends Error {
8
+ exitCode;
9
+ hint;
10
+ constructor(message, opts = {}) {
11
+ super(message);
12
+ this.name = "SdkvmError";
13
+ this.exitCode = opts.exitCode ?? 1;
14
+ this.hint = opts.hint;
15
+ }
16
+ };
17
+ function toSdkvmError(err) {
18
+ if (err instanceof SdkvmError) return err;
19
+ if (err instanceof Error) return new SdkvmError(err.message, { hint: err.stack });
20
+ return new SdkvmError(String(err));
21
+ }
22
+
23
+ // src/ui/log.ts
24
+ import pc from "picocolors";
25
+
26
+ // src/core/env.ts
27
+ function envOverride(newName, legacyName) {
28
+ const fresh = process.env[newName];
29
+ if (fresh !== void 0 && fresh !== "") return fresh;
30
+ const legacy = process.env[legacyName];
31
+ if (legacy !== void 0 && legacy !== "") return legacy;
32
+ return void 0;
33
+ }
34
+
35
+ // src/ui/log.ts
36
+ var isQuiet = () => Boolean(envOverride("SDKVM_QUIET", "JVM_QUIET"));
37
+ var label = {
38
+ info: pc.cyan("sdkvm"),
39
+ ok: pc.green("sdkvm"),
40
+ warn: pc.yellow("sdkvm"),
41
+ error: pc.red("sdkvm")
42
+ };
43
+ var log = {
44
+ info(msg) {
45
+ if (!isQuiet()) console.log(`${label.info} ${msg}`);
46
+ },
47
+ ok(msg) {
48
+ if (!isQuiet()) console.log(`${label.ok} ${msg}`);
49
+ },
50
+ warn(msg) {
51
+ if (!isQuiet()) console.error(`${label.warn} ${pc.yellow(msg)}`);
52
+ },
53
+ error(msg) {
54
+ console.error(`${label.error} ${pc.red(msg)}`);
55
+ },
56
+ /** 无前缀输出(列表、表格数据) */
57
+ raw(msg) {
58
+ console.log(msg);
59
+ }
60
+ };
61
+
62
+ // src/cli/install.ts
63
+ import fs9 from "fs";
64
+ import path8 from "path";
65
+
66
+ // src/core/platform.ts
67
+ function detectPlatform(override) {
68
+ const rawPlatform = override?.platform ?? process.platform;
69
+ const rawArch = override?.arch ?? process.arch;
70
+ const osMap = {
71
+ darwin: "mac",
72
+ linux: "linux",
73
+ win32: "windows"
74
+ };
75
+ const archMap = {
76
+ arm64: "aarch64",
77
+ aarch64: "aarch64",
78
+ x64: "x64"
79
+ };
80
+ const os6 = osMap[rawPlatform];
81
+ const arch = archMap[rawArch];
82
+ if (!os6) {
83
+ throw new SdkvmError(`Unsupported operating system: ${rawPlatform}`, {
84
+ hint: "sdkvm currently supports macOS, Linux and Windows."
85
+ });
86
+ }
87
+ if (!arch) {
88
+ throw new SdkvmError(`Unsupported CPU architecture: ${rawArch}`, {
89
+ hint: "sdkvm currently supports aarch64 (Apple Silicon / ARM) and x64."
90
+ });
91
+ }
92
+ return { os: os6, arch, rawPlatform, rawArch };
93
+ }
94
+
95
+ // src/core/config.ts
96
+ import fs4 from "fs";
97
+ import path4 from "path";
98
+
99
+ // src/core/version.ts
100
+ var LTS_MAJORS = /* @__PURE__ */ new Set([8, 11, 17, 21, 25]);
101
+ var JAVA_VENDOR_IDS = ["temurin", "zulu", "corretto"];
102
+ function parseVersion(vendor, input) {
103
+ const raw = input.trim();
104
+ const vendorPrefix = new RegExp(`^(${JAVA_VENDOR_IDS.join("|")})-`, "i");
105
+ let s = raw.replace(/^jdk-/i, "").replace(vendorPrefix, "").replace(/^v/i, "");
106
+ s = s.replace(/^zulu\d[\d.]*-ca-jdk[\d.]*-?/i, "");
107
+ const plusIdx = s.indexOf("+");
108
+ const basePart = plusIdx >= 0 ? s.slice(0, plusIdx) : s;
109
+ const build = plusIdx >= 0 ? s.slice(plusIdx + 1) : null;
110
+ const segs = basePart.split(".").filter((x) => x.length > 0);
111
+ if (segs.length === 0) {
112
+ throw new SdkvmError(`Invalid JDK version: "${input}"`);
113
+ }
114
+ const nums = segs.map((x) => Number(x));
115
+ if (nums.some((n) => !Number.isInteger(n) || n < 0)) {
116
+ throw new SdkvmError(`Invalid JDK version: "${input}"`, {
117
+ hint: "Expected forms: 21, 21.0.5, 21.0.5+11"
118
+ });
119
+ }
120
+ return {
121
+ vendor,
122
+ major: nums[0],
123
+ minor: nums[1] ?? 0,
124
+ patch: nums[2] ?? 0,
125
+ extra: segs.length > 3 ? segs.slice(3).join(".") : null,
126
+ build,
127
+ raw
128
+ };
129
+ }
130
+ function formatVersion(v) {
131
+ if (v.patch !== null && v.minor === 0 && v.patch === 0 && !v.extra && !v.build) return String(v.major);
132
+ let s = `${v.major}.${v.minor}.${v.patch ?? 0}`;
133
+ if (v.extra) s += `.${v.extra}`;
134
+ if (v.build) s += `+${v.build}`;
135
+ return s;
136
+ }
137
+ function parseDirName(dir) {
138
+ const m = new RegExp(`^(${JAVA_VENDOR_IDS.join("|")})-(.+)$`).exec(dir);
139
+ if (!m || !m[1] || !m[2]) return null;
140
+ try {
141
+ return parseVersion(m[1], m[2]);
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+ function numericPairwise(a, b) {
147
+ const as = a ? a.split(".") : [];
148
+ const bs = b ? b.split(".") : [];
149
+ const len = Math.max(as.length, bs.length);
150
+ for (let i = 0; i < len; i++) {
151
+ const x = as[i];
152
+ const y = bs[i];
153
+ if (x === void 0 && y !== void 0) return -1;
154
+ if (x !== void 0 && y === void 0) return 1;
155
+ const xn = Number(x);
156
+ const yn = Number(y);
157
+ if (Number.isInteger(xn) && Number.isInteger(yn)) {
158
+ if (xn !== yn) return xn - yn > 0 ? 1 : -1;
159
+ } else {
160
+ const c = String(x).localeCompare(String(y));
161
+ if (c !== 0) return c > 0 ? 1 : -1;
162
+ }
163
+ }
164
+ return 0;
165
+ }
166
+ function compareVersions(a, b) {
167
+ for (const key of ["major", "minor"]) {
168
+ if (a[key] !== b[key]) return a[key] - b[key] > 0 ? 1 : -1;
169
+ }
170
+ const pa = a.patch ?? 0;
171
+ const pb = b.patch ?? 0;
172
+ if (pa !== pb) return pa - pb > 0 ? 1 : -1;
173
+ const extra = numericPairwise(a.extra, b.extra);
174
+ if (extra !== 0) return extra;
175
+ return numericPairwise(a.build, b.build);
176
+ }
177
+ function parseUserSpec(input) {
178
+ let s = input.trim().toLowerCase();
179
+ let vendor;
180
+ const m = new RegExp(`^(${JAVA_VENDOR_IDS.join("|")})-(.+)$`).exec(s);
181
+ if (m && m[1] && m[2]) {
182
+ vendor = m[1];
183
+ s = m[2];
184
+ }
185
+ if (s === "lts" || s === "--lts") return { vendor, spec: { kind: "lts" } };
186
+ if (/^\d+$/.test(s)) return { vendor, spec: { kind: "major", major: Number(s) } };
187
+ if (/^\d+(\.\d+)*(\+[0-9.]+)?$/.test(s)) return { vendor, spec: { kind: "full", version: s } };
188
+ throw new SdkvmError(`Invalid version: "${input}"`, {
189
+ hint: "Expected: 21, lts, 21.0.5, 21.0.5+11, or with vendor prefix like temurin-21"
190
+ });
191
+ }
192
+ var GO_VENDOR_IDS = ["golang"];
193
+ function parseGoVersion(vendor, input) {
194
+ const raw = input.trim();
195
+ const s = raw.replace(/^golang-/i, "").replace(/^go/i, "").replace(/^v/i, "");
196
+ const m = /^(\d+)\.(\d+)(?:\.(\d+))?$/.exec(s);
197
+ if (!m || !m[1] || !m[2]) {
198
+ throw new SdkvmError(`Invalid Go version: "${input}"`, {
199
+ hint: "Expected forms: 1.24, 1.24.5, go1.24.5"
200
+ });
201
+ }
202
+ return {
203
+ vendor,
204
+ major: Number(m[1]),
205
+ minor: Number(m[2]),
206
+ patch: m[3] !== void 0 ? Number(m[3]) : null,
207
+ extra: null,
208
+ build: null,
209
+ raw
210
+ };
211
+ }
212
+ function formatGoVersion(v) {
213
+ return v.patch === null ? `${v.major}.${v.minor}` : `${v.major}.${v.minor}.${v.patch}`;
214
+ }
215
+ function parseGoDirName(dir) {
216
+ const m = new RegExp(`^(${GO_VENDOR_IDS.join("|")})-(.+)$`).exec(dir);
217
+ if (!m || !m[1] || !m[2]) return null;
218
+ try {
219
+ return parseGoVersion(m[1], m[2]);
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
224
+ function parseGoUserSpec(input) {
225
+ let s = input.trim().toLowerCase();
226
+ let vendor;
227
+ const m = new RegExp(`^(${GO_VENDOR_IDS.join("|")})-(.+)$`).exec(s);
228
+ if (m && m[1] && m[2]) {
229
+ vendor = m[1];
230
+ s = m[2];
231
+ }
232
+ if (s === "latest") return { vendor, spec: { kind: "latest" } };
233
+ if (/^\d+\.\d+\.\d+$/.test(s)) return { vendor, spec: { kind: "full", version: s } };
234
+ const line = /^(\d+)\.(\d+)$/.exec(s);
235
+ if (line && line[1] && line[2]) {
236
+ return { vendor, spec: { kind: "line", major: Number(line[1]), minor: Number(line[2]) } };
237
+ }
238
+ throw new SdkvmError(`Invalid version: "${input}"`, {
239
+ hint: "Expected: 1.24, 1.24.5, latest, or with vendor prefix like golang-1.24"
240
+ });
241
+ }
242
+ var FLUTTER_VENDOR_IDS = ["flutter"];
243
+ function parseFlutterVersion(vendor, input) {
244
+ const raw = input.trim();
245
+ const s = raw.replace(/^flutter-/i, "").replace(/^v/i, "");
246
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(s);
247
+ if (!m || !m[1] || !m[2] || !m[3]) {
248
+ throw new SdkvmError(`Invalid Flutter version: "${input}"`, {
249
+ hint: "Expected forms: 3.47, 3.47.5, 3.49.0-0.1.pre, latest"
250
+ });
251
+ }
252
+ return {
253
+ vendor,
254
+ major: Number(m[1]),
255
+ minor: Number(m[2]),
256
+ patch: Number(m[3]),
257
+ extra: m[4] ?? null,
258
+ build: null,
259
+ raw
260
+ };
261
+ }
262
+ function formatFlutterVersion(v) {
263
+ const base = `${v.major}.${v.minor}.${v.patch ?? 0}`;
264
+ return v.extra ? `${base}-${v.extra}` : base;
265
+ }
266
+ function parseFlutterDirName(dir) {
267
+ const m = new RegExp(`^(${FLUTTER_VENDOR_IDS.join("|")})-(.+)$`).exec(dir);
268
+ if (!m || !m[1] || !m[2]) return null;
269
+ try {
270
+ return parseFlutterVersion(m[1], m[2]);
271
+ } catch {
272
+ return null;
273
+ }
274
+ }
275
+ function parseFlutterUserSpec(input) {
276
+ let s = input.trim().toLowerCase();
277
+ let vendor;
278
+ const m = new RegExp(`^(${FLUTTER_VENDOR_IDS.join("|")})-(.+)$`).exec(s);
279
+ if (m && m[1] && m[2]) {
280
+ vendor = m[1];
281
+ s = m[2];
282
+ }
283
+ if (s === "latest") return { vendor, spec: { kind: "latest" } };
284
+ if (/^\d+\.\d+\.\d+(-[0-9a-z.\-]+)?$/.test(s)) return { vendor, spec: { kind: "full", version: s } };
285
+ const line = /^(\d+)\.(\d+)$/.exec(s);
286
+ if (line && line[1] && line[2]) {
287
+ return { vendor, spec: { kind: "line", major: Number(line[1]), minor: Number(line[2]) } };
288
+ }
289
+ if (/^\d+$/.test(s)) {
290
+ throw new SdkvmError(`Invalid Flutter version: "${input}"`, {
291
+ hint: 'Bare major is ambiguous \u2014 use "latest" or a minor line like "3.47"'
292
+ });
293
+ }
294
+ throw new SdkvmError(`Invalid Flutter version: "${input}"`, {
295
+ hint: "Expected: 3.47, 3.47.5, 3.49.0-0.1.pre, latest, or with vendor prefix like flutter-3.47"
296
+ });
297
+ }
298
+
299
+ // src/core/paths.ts
300
+ import fs3 from "fs";
301
+ import os from "os";
302
+ import path3 from "path";
303
+
304
+ // src/sdk/java.ts
305
+ import fs2 from "fs";
306
+ import path2 from "path";
307
+
308
+ // src/cli/misc.ts
309
+ import fs from "fs";
310
+ import { fileURLToPath } from "url";
311
+ import path from "path";
312
+ function getVersion() {
313
+ const fallback = "0.1.0";
314
+ try {
315
+ const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
316
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
317
+ return pkg.version ?? fallback;
318
+ } catch {
319
+ return fallback;
320
+ }
321
+ }
322
+ function versionCommand() {
323
+ log.raw(getVersion());
324
+ }
325
+
326
+ // src/net/http.ts
327
+ var UA = `sdkvm/${getVersion()} (npm sdkvm)`;
328
+ var CONNECT_TIMEOUT_MS = 3e4;
329
+ var RETRIES = 3;
330
+ var HttpError = class extends SdkvmError {
331
+ constructor(message, status, url) {
332
+ super(message, { hint: `URL: ${url}` });
333
+ this.status = status;
334
+ this.url = url;
335
+ this.name = "HttpError";
336
+ }
337
+ status;
338
+ url;
339
+ };
340
+ function sleep(ms) {
341
+ return new Promise((r) => setTimeout(r, ms));
342
+ }
343
+ async function fetchOnce(url, init) {
344
+ const signal = init.signal ?? AbortSignal.timeout(CONNECT_TIMEOUT_MS);
345
+ const res = await fetch(url, { ...init, signal, headers: { "user-agent": UA, ...init.headers } });
346
+ return res;
347
+ }
348
+ async function httpFetch(url, init = {}) {
349
+ let lastErr;
350
+ for (let attempt = 1; attempt <= RETRIES; attempt++) {
351
+ try {
352
+ const res = await fetchOnce(url, init);
353
+ if (init.redirect === "manual" && res.status >= 300 && res.status < 400) {
354
+ return res;
355
+ }
356
+ if (res.status >= 500 && attempt < RETRIES) {
357
+ lastErr = new HttpError(`Server error ${res.status}`, res.status, url);
358
+ await sleep(500 * 2 ** (attempt - 1));
359
+ continue;
360
+ }
361
+ if (!res.ok) {
362
+ throw new HttpError(`HTTP ${res.status} ${res.statusText || ""}`.trim(), res.status, url);
363
+ }
364
+ return res;
365
+ } catch (err) {
366
+ if (err instanceof HttpError && err.status < 500) throw err;
367
+ lastErr = err;
368
+ if (attempt < RETRIES) await sleep(500 * 2 ** (attempt - 1));
369
+ }
370
+ }
371
+ if (lastErr instanceof SdkvmError) throw lastErr;
372
+ throw new SdkvmError(`Network error after ${RETRIES} attempts: ${url}`, {
373
+ hint: lastErr instanceof Error ? lastErr.message : String(lastErr)
374
+ });
375
+ }
376
+ async function httpJson(url) {
377
+ const res = await httpFetch(url, { headers: { accept: "application/json" } });
378
+ return await res.json();
379
+ }
380
+ async function httpText(url) {
381
+ const res = await httpFetch(url);
382
+ return await res.text();
383
+ }
384
+
385
+ // src/vendor/temurin.ts
386
+ var API = "https://api.adoptium.net";
387
+ async function resolveLatestRedirect(major, os6, arch) {
388
+ const url = `${API}/v3/binary/latest/${major}/ga/${os6}/${arch}/jdk/hotspot/normal/eclipse`;
389
+ const res = await httpFetch(url, { redirect: "manual" });
390
+ if (res.status !== 302 && res.status !== 307 && res.status !== 308) {
391
+ throw new SdkvmError(`Adoptium API returned ${res.status} for JDK ${major}`);
392
+ }
393
+ const location = res.headers.get("location");
394
+ if (!location) throw new SdkvmError(`Adoptium API returned no redirect for JDK ${major}`);
395
+ return location;
396
+ }
397
+ function versionFromGithubUrl(url) {
398
+ const m = /\/download\/jdk-([^/%]+(?:%2B[^/%]+)?)\//i.exec(url);
399
+ if (!m || !m[1]) throw new SdkvmError(`Cannot parse version from Adoptium URL: ${url}`);
400
+ return decodeURIComponent(m[1]);
401
+ }
402
+ function githubAssetUrl(version, os6, arch) {
403
+ const v = parseVersion("temurin", version);
404
+ const major = v.major;
405
+ const ext = os6 === "windows" ? "zip" : "tar.gz";
406
+ const underscored = formatVersion(v).replace("+", "_");
407
+ const file = `OpenJDK${major}U-jdk_${arch}_${os6}_hotspot_${underscored}.${ext}`;
408
+ return `https://github.com/adoptium/temurin${major}-binaries/releases/download/jdk-${encodeURIComponent(version)}/${file}`;
409
+ }
410
+ async function fetchAvailable() {
411
+ return httpJson(`${API}/v3/info/available_releases`);
412
+ }
413
+ async function resolveMajor(major, platform) {
414
+ const location = await resolveLatestRedirect(major, platform.os, platform.arch);
415
+ const version = versionFromGithubUrl(location);
416
+ return buildArtifact(version, platform, location);
417
+ }
418
+ function buildArtifact(version, platform, downloadUrl) {
419
+ const v = parseVersion("temurin", version);
420
+ const archive = platform.os === "windows" ? "zip" : "tar.gz";
421
+ return {
422
+ vendorId: "temurin",
423
+ version: v,
424
+ dirName: `temurin-${formatVersion(v)}`,
425
+ displayName: `Temurin ${formatVersion(v)}`,
426
+ downloadUrl,
427
+ checksum: { kind: "sha256", url: `${downloadUrl}.json` },
428
+ archive
429
+ };
430
+ }
431
+ var temurinVendor = {
432
+ id: "temurin",
433
+ label: "Adoptium Temurin",
434
+ sdk: "java",
435
+ supportsFullVersionList: true,
436
+ async listMajors() {
437
+ const data = await fetchAvailable();
438
+ return data.available_releases.slice().sort((a, b) => a - b).map((major) => ({ key: String(major), lts: data.available_lts_releases.includes(major) }));
439
+ },
440
+ async resolve(spec, platform) {
441
+ if (spec.kind === "major") {
442
+ return resolveMajor(spec.major, platform);
443
+ }
444
+ if (spec.kind === "lts") {
445
+ const data = await fetchAvailable();
446
+ const ltsMajors = data.available_releases.filter(
447
+ (m) => data.available_lts_releases.includes(m)
448
+ );
449
+ const latest = Math.max(...ltsMajors);
450
+ return resolveMajor(latest, platform);
451
+ }
452
+ if (spec.kind !== "full") {
453
+ throw new SdkvmError(`Unsupported version spec for Temurin: ${spec.kind}`);
454
+ }
455
+ const url = githubAssetUrl(spec.version, platform.os, platform.arch);
456
+ return buildArtifact(spec.version, platform, url);
457
+ }
458
+ };
459
+
460
+ // src/vendor/zulu.ts
461
+ var API2 = "https://api.azul.com/metadata/v1";
462
+ function azulParams(platform) {
463
+ return {
464
+ os: platform.os === "mac" ? "macos" : platform.os,
465
+ arch: platform.arch === "aarch64" ? "arm" : "x64"
466
+ };
467
+ }
468
+ function pickPlainJdk(packages, platform, versionPrefix) {
469
+ const ext = platform.os === "windows" ? ".zip" : ".tar.gz";
470
+ const wanted = versionPrefix ?? "";
471
+ const candidates = packages.filter((p) => {
472
+ if (!/^zulu[\d.]+-ca-jdk[\d.]*-/i.test(p.name)) return false;
473
+ if (!p.name.endsWith(ext)) return false;
474
+ if (wanted && !p.java_version.join(".").startsWith(wanted)) return false;
475
+ return true;
476
+ });
477
+ candidates.sort((a, b) => {
478
+ const ja = a.java_version.join(".").localeCompare(b.java_version.join("."), void 0, {
479
+ numeric: true
480
+ });
481
+ return ja;
482
+ });
483
+ return candidates[candidates.length - 1] ?? null;
484
+ }
485
+ async function queryPackages(javaVersion, platform) {
486
+ const { os: os6, arch } = azulParams(platform);
487
+ const url = `${API2}/zulu/packages/?java_version=${javaVersion}&os=${os6}&arch=${arch}&hw_bitness=64&release_status=ga&page_size=50`;
488
+ const data = await httpJson(url);
489
+ return Array.isArray(data) ? data : [];
490
+ }
491
+ var zuluVendor = {
492
+ id: "zulu",
493
+ label: "Azul Zulu",
494
+ sdk: "java",
495
+ supportsFullVersionList: true,
496
+ async listMajors() {
497
+ const universe = await temurinVendor.listMajors();
498
+ const results = await Promise.all(
499
+ universe.map(async ({ key }) => {
500
+ try {
501
+ const packages = await queryPackages(key, {
502
+ os: "mac",
503
+ arch: "aarch64"
504
+ });
505
+ const pick = pickPlainJdk(packages, { os: "mac", arch: "aarch64" });
506
+ if (!pick) return null;
507
+ return { key, lts: LTS_MAJORS.has(Number(key)), latestFullVersion: pick.java_version.join(".") };
508
+ } catch {
509
+ return null;
510
+ }
511
+ })
512
+ );
513
+ return results.filter((r) => r !== null);
514
+ },
515
+ async resolve(spec, platform) {
516
+ if (spec.kind === "lts") {
517
+ const majors = await this.listMajors();
518
+ const lts = majors.filter((m) => m.lts);
519
+ if (lts.length === 0) throw new SdkvmError("No Zulu LTS release found");
520
+ const latest = lts[lts.length - 1];
521
+ if (!latest) throw new SdkvmError("No Zulu LTS release found");
522
+ return this.resolve({ kind: "major", major: Number(latest.key) }, platform);
523
+ }
524
+ if (spec.kind !== "major" && spec.kind !== "full") {
525
+ throw new SdkvmError(`Unsupported version spec for Zulu: ${spec.kind}`);
526
+ }
527
+ const versionPrefix = spec.kind === "major" ? String(spec.major) : spec.version;
528
+ const major = spec.kind === "major" ? spec.major : parseVersion("zulu", spec.version).major;
529
+ const packages = await queryPackages(String(major), platform);
530
+ const pick = pickPlainJdk(packages, platform, versionPrefix);
531
+ if (!pick) {
532
+ throw new SdkvmError(`No Zulu JDK build matches "${versionPrefix}"`, {
533
+ hint: "Run `jvm ls -r` to see available versions."
534
+ });
535
+ }
536
+ const versionStr = pick.java_version.join(".");
537
+ const v = parseVersion("zulu", versionStr);
538
+ return {
539
+ vendorId: "zulu",
540
+ version: v,
541
+ dirName: `zulu-${formatVersion(v)}`,
542
+ displayName: `Zulu ${formatVersion(v)}`,
543
+ downloadUrl: pick.download_url,
544
+ checksum: pick.sha256_hash ? { kind: "sha256", expected: pick.sha256_hash } : null,
545
+ archive: platform.os === "windows" ? "zip" : "tar.gz"
546
+ };
547
+ }
548
+ };
549
+
550
+ // src/vendor/corretto.ts
551
+ var BASE = "https://corretto.aws/downloads";
552
+ var MAJORS = [8, 11, 17, 21, 25];
553
+ function latestFileName(major, platform) {
554
+ const osName = platform.os === "mac" ? "macos" : platform.os;
555
+ const ext = platform.os === "windows" ? "zip" : "tar.gz";
556
+ return `amazon-corretto-${major}-${platform.arch}-${osName}-jdk.${ext}`;
557
+ }
558
+ function resourceFileName(version, platform) {
559
+ if (platform.os === "mac") return `amazon-corretto-${version}-macosx-${platform.arch}.tar.gz`;
560
+ if (platform.os === "linux") return `amazon-corretto-${version}-linux-${platform.arch}.tar.gz`;
561
+ return `amazon-corretto-${version}-windows-${platform.arch}-jdk.zip`;
562
+ }
563
+ function resourceUrl(version, platform) {
564
+ return `${BASE}/resources/${version}/${resourceFileName(version, platform)}`;
565
+ }
566
+ async function resolveLatestVersion(major, platform) {
567
+ const url = `${BASE}/latest/${latestFileName(major, platform)}`;
568
+ const res = await httpFetch(url, { redirect: "manual" });
569
+ if (res.status !== 301 && res.status !== 302 && res.status !== 307 && res.status !== 308) {
570
+ throw new SdkvmError(`Corretto returned ${res.status} for JDK ${major}`);
571
+ }
572
+ const location = res.headers.get("location") ?? "";
573
+ const m = /\/resources\/([^/]+)\//.exec(location);
574
+ if (!m || !m[1]) {
575
+ throw new SdkvmError(`Cannot parse Corretto version from redirect: ${location}`);
576
+ }
577
+ return m[1];
578
+ }
579
+ var correttoVendor = {
580
+ id: "corretto",
581
+ label: "Amazon Corretto",
582
+ sdk: "java",
583
+ supportsFullVersionList: false,
584
+ async listMajors() {
585
+ return MAJORS.map((major) => ({ key: String(major), lts: LTS_MAJORS.has(major) }));
586
+ },
587
+ async resolve(spec, platform) {
588
+ let version;
589
+ if (spec.kind === "lts") {
590
+ const latestLts = Math.max(...MAJORS);
591
+ version = await resolveLatestVersion(latestLts, platform);
592
+ } else if (spec.kind === "major") {
593
+ if (!MAJORS.includes(spec.major)) {
594
+ throw new SdkvmError(`Corretto does not publish JDK ${spec.major}`, {
595
+ hint: `Available majors: ${MAJORS.join(", ")}`
596
+ });
597
+ }
598
+ version = await resolveLatestVersion(spec.major, platform);
599
+ } else if (spec.kind === "full") {
600
+ const v2 = parseVersion("corretto", spec.version);
601
+ if (!MAJORS.includes(v2.major)) {
602
+ throw new SdkvmError(`Corretto does not publish JDK ${v2.major}`);
603
+ }
604
+ version = spec.version;
605
+ } else {
606
+ throw new SdkvmError(`Unsupported version spec for Corretto: ${spec.kind}`);
607
+ }
608
+ const v = parseVersion("corretto", version);
609
+ const url = resourceUrl(version, platform);
610
+ return {
611
+ vendorId: "corretto",
612
+ version: v,
613
+ dirName: `corretto-${formatVersion(v)}`,
614
+ displayName: `Corretto ${formatVersion(v)}`,
615
+ downloadUrl: url,
616
+ checksum: { kind: "sha256", url: `${url}.sha256` },
617
+ archive: platform.os === "windows" ? "zip" : "tar.gz"
618
+ };
619
+ }
620
+ };
621
+
622
+ // src/cli/cmdname.ts
623
+ var CLI_BIN = "sdkvm";
624
+ function cmdPath(type) {
625
+ return type === "java" ? CLI_BIN : `${CLI_BIN} ${type}`;
626
+ }
627
+
628
+ // src/vendor/golang.ts
629
+ var LIST_URL = "https://go.dev/dl/?mode=json&include=all";
630
+ var GO_OS = { mac: "darwin", linux: "linux", windows: "windows" };
631
+ var GO_ARCH = { aarch64: "arm64", x64: "amd64" };
632
+ async function fetchReleases() {
633
+ return httpJson(LIST_URL);
634
+ }
635
+ async function stableVersions() {
636
+ const releases = await fetchReleases();
637
+ const parsed = [];
638
+ for (const r of releases) {
639
+ if (!r.stable) continue;
640
+ try {
641
+ parsed.push({ raw: r, v: parseGoVersion("golang", r.version) });
642
+ } catch {
643
+ }
644
+ }
645
+ parsed.sort((a, b) => compareVersions(b.v, a.v));
646
+ return parsed;
647
+ }
648
+ function pickFile(r, platform) {
649
+ return r.files.find(
650
+ (f) => f.kind === "archive" && f.os === GO_OS[platform.os] && f.arch === GO_ARCH[platform.arch]
651
+ );
652
+ }
653
+ function buildArtifact2(r, f, v) {
654
+ return {
655
+ vendorId: "golang",
656
+ version: v,
657
+ dirName: `golang-${formatGoVersion(v)}`,
658
+ displayName: `Go ${formatGoVersion(v)}`,
659
+ downloadUrl: `https://go.dev/dl/${f.filename}`,
660
+ checksum: { kind: "sha256", expected: f.sha256 },
661
+ archive: f.filename.endsWith(".zip") ? "zip" : "tar.gz"
662
+ };
663
+ }
664
+ function specLabel(spec) {
665
+ if (spec.kind === "line") return `${spec.major}.${spec.minor}`;
666
+ if (spec.kind === "full") return spec.version;
667
+ return "latest";
668
+ }
669
+ var golangVendor = {
670
+ id: "golang",
671
+ label: "Go (official)",
672
+ sdk: "go",
673
+ supportsFullVersionList: true,
674
+ /** go 无 major 概念:一条 minor 线(1.24)等价 java 的一个 major */
675
+ async listMajors() {
676
+ const versions = await stableVersions();
677
+ const lines = /* @__PURE__ */ new Map();
678
+ for (const { v } of versions) {
679
+ const key = `${v.major}.${v.minor}`;
680
+ const cur = lines.get(key);
681
+ if (!cur || compareVersions(v, cur) > 0) lines.set(key, v);
682
+ }
683
+ return [...lines.entries()].map(([key, v]) => ({ key, lts: false, latestFullVersion: formatGoVersion(v) })).sort((a, b) => compareVersions(parseGoVersion("golang", b.key), parseGoVersion("golang", a.key)));
684
+ },
685
+ async resolve(spec, platform) {
686
+ if (spec.kind === "lts" || spec.kind === "major") {
687
+ throw new SdkvmError(`Unsupported version spec for Go: ${spec.kind}`);
688
+ }
689
+ const versions = await stableVersions();
690
+ let target;
691
+ if (spec.kind === "latest") {
692
+ target = versions[0];
693
+ } else if (spec.kind === "line") {
694
+ target = versions.find((x) => x.v.major === spec.major && x.v.minor === spec.minor);
695
+ } else {
696
+ target = versions.find((x) => formatGoVersion(x.v) === spec.version);
697
+ }
698
+ if (!target) {
699
+ throw new SdkvmError(`No Go release matches "${specLabel(spec)}"`, {
700
+ hint: `Run \`${cmdPath("go")} ls -r\` to see available versions.`
701
+ });
702
+ }
703
+ const file = pickFile(target.raw, platform);
704
+ if (!file) {
705
+ throw new SdkvmError(
706
+ `Go ${formatGoVersion(target.v)} has no archive for ${platform.os}/${platform.arch}`
707
+ );
708
+ }
709
+ return buildArtifact2(target.raw, file, target.v);
710
+ }
711
+ };
712
+
713
+ // src/vendor/flutter.ts
714
+ var MANIFEST_BASE = "https://storage.googleapis.com/flutter_infra_release/releases";
715
+ var FLUTTER_OS = { mac: "macos", linux: "linux", windows: "windows" };
716
+ var FLUTTER_ARCH = { aarch64: "arm64", x64: "x64" };
717
+ async function fetchEntries(platform) {
718
+ const manifest = await httpJson(`${MANIFEST_BASE}/releases_${FLUTTER_OS[platform.os]}.json`);
719
+ const arch = FLUTTER_ARCH[platform.arch];
720
+ const parsed = [];
721
+ for (const r of manifest.releases) {
722
+ if (r.dart_sdk_arch !== arch) continue;
723
+ try {
724
+ parsed.push({ raw: r, v: parseFlutterVersion("flutter", r.version) });
725
+ } catch {
726
+ }
727
+ }
728
+ parsed.sort((a, b) => compareVersions(b.v, a.v));
729
+ return {
730
+ baseUrl: manifest.base_url,
731
+ stable: parsed.filter((x) => x.raw.channel === "stable"),
732
+ all: parsed
733
+ };
734
+ }
735
+ function buildArtifact3(baseUrl, r, v, platform) {
736
+ return {
737
+ vendorId: "flutter",
738
+ version: v,
739
+ dirName: `flutter-${formatFlutterVersion(v)}`,
740
+ displayName: `Flutter ${formatFlutterVersion(v)}`,
741
+ downloadUrl: `${baseUrl}/${r.archive}`,
742
+ checksum: { kind: "sha256", expected: r.sha256 },
743
+ archive: platform.os === "linux" ? "tar.xz" : "zip"
744
+ };
745
+ }
746
+ function specLabel2(spec) {
747
+ if (spec.kind === "line") return `${spec.major}.${spec.minor}`;
748
+ if (spec.kind === "full") return spec.version;
749
+ return "latest";
750
+ }
751
+ var flutterVendor = {
752
+ id: "flutter",
753
+ label: "Flutter (official)",
754
+ sdk: "flutter",
755
+ supportsFullVersionList: true,
756
+ /** 一条 minor 线(3.47)等价 java 的一个 major;latest/line 只看 stable 通道 */
757
+ async listMajors() {
758
+ const { stable } = await fetchEntries(detectPlatform());
759
+ const lines = /* @__PURE__ */ new Map();
760
+ for (const { v } of stable) {
761
+ const key = `${v.major}.${v.minor}`;
762
+ const cur = lines.get(key);
763
+ if (!cur || compareVersions(v, cur) > 0) lines.set(key, v);
764
+ }
765
+ return [...lines.entries()].map(([key, v]) => ({ key, v })).sort((a, b) => compareVersions(b.v, a.v)).map(({ key, v }) => ({ key, lts: false, latestFullVersion: formatFlutterVersion(v) }));
766
+ },
767
+ async resolve(spec, platform) {
768
+ if (spec.kind === "lts" || spec.kind === "major") {
769
+ throw new SdkvmError(`Unsupported version spec for Flutter: ${spec.kind}`);
770
+ }
771
+ const { baseUrl, stable, all } = await fetchEntries(platform);
772
+ if (all.length === 0) {
773
+ throw new SdkvmError(
774
+ `No Flutter archive for ${platform.os}/${platform.arch} (official releases are x64-only on Linux/Windows)`
775
+ );
776
+ }
777
+ let target;
778
+ if (spec.kind === "latest") {
779
+ target = stable[0];
780
+ } else if (spec.kind === "line") {
781
+ target = stable.find((x) => x.v.major === spec.major && x.v.minor === spec.minor);
782
+ } else {
783
+ target = stable.find((x) => formatFlutterVersion(x.v) === spec.version) ?? all.find((x) => formatFlutterVersion(x.v) === spec.version);
784
+ }
785
+ if (!target) {
786
+ throw new SdkvmError(`No Flutter release matches "${specLabel2(spec)}"`, {
787
+ hint: `Run \`${cmdPath("flutter")} ls -r\` to see available versions.`
788
+ });
789
+ }
790
+ return buildArtifact3(baseUrl, target.raw, target.v, platform);
791
+ }
792
+ };
793
+
794
+ // src/vendor/index.ts
795
+ var JAVA_VENDORS = [temurinVendor, zuluVendor, correttoVendor];
796
+ var GO_VENDORS = [golangVendor];
797
+ var FLUTTER_VENDORS = [flutterVendor];
798
+ function vendorsFor(type) {
799
+ if (type === "java") return JAVA_VENDORS;
800
+ if (type === "go") return GO_VENDORS;
801
+ if (type === "flutter") return FLUTTER_VENDORS;
802
+ throw new SdkvmError(`Unknown SDK type: ${type}`);
803
+ }
804
+ function allVendorIds(type) {
805
+ return vendorsFor(type).map((v) => v.id);
806
+ }
807
+ function getVendor(type, id) {
808
+ const vendor = vendorsFor(type).find((v) => v.id === id);
809
+ if (!vendor) {
810
+ throw new SdkvmError(`Unknown vendor: ${id}`, {
811
+ hint: `Available vendors for ${type}: ${allVendorIds(type).join(", ")}`
812
+ });
813
+ }
814
+ return vendor;
815
+ }
816
+ function resolveVendorId(type, arg, config) {
817
+ const vendors = vendorsFor(type);
818
+ const id = arg ?? (type === "java" ? config.defaultVendor : vendors[0]?.id);
819
+ const found = vendors.find((v) => v.id === id);
820
+ if (!found) {
821
+ throw new SdkvmError(`Unknown vendor: ${arg}`, {
822
+ hint: `Available vendors: ${vendors.map((v) => v.id).join(", ")}`
823
+ });
824
+ }
825
+ return found.id;
826
+ }
827
+
828
+ // src/sdk/java.ts
829
+ var javaSdk = {
830
+ id: "java",
831
+ label: "Java (JDK)",
832
+ installDirName: "jdks",
833
+ currentLinkName: "current-java",
834
+ envVar: "JAVA_HOME",
835
+ supportsLts: true,
836
+ vendors: JAVA_VENDORS,
837
+ parseUserSpec,
838
+ parseDirName,
839
+ formatVersion,
840
+ compareVersions,
841
+ binRelPath(platform) {
842
+ return `bin/${platform.os === "windows" ? "java.exe" : "java"}`;
843
+ },
844
+ locateHome(root) {
845
+ const contentsHome = path2.join(root, "Contents", "Home");
846
+ return fs2.existsSync(path2.join(contentsHome, "bin")) ? contentsHome : root;
847
+ },
848
+ versionCheck: { args: ["-version"], stream: "stderr" }
849
+ };
850
+
851
+ // src/sdk/go.ts
852
+ var goSdk = {
853
+ id: "go",
854
+ label: "Go",
855
+ installDirName: "gos",
856
+ currentLinkName: "current-go",
857
+ envVar: "GOROOT",
858
+ supportsLts: false,
859
+ vendors: GO_VENDORS,
860
+ parseUserSpec: parseGoUserSpec,
861
+ parseDirName: parseGoDirName,
862
+ formatVersion: formatGoVersion,
863
+ compareVersions,
864
+ binRelPath(platform) {
865
+ return `bin/${platform.os === "windows" ? "go.exe" : "go"}`;
866
+ },
867
+ // go 归档是单根 go/ 目录,无 macOS bundle 概念
868
+ locateHome: (root) => root,
869
+ versionCheck: { args: ["version"], stream: "stdout" }
870
+ };
871
+
872
+ // src/sdk/flutter.ts
873
+ var flutterSdk = {
874
+ id: "flutter",
875
+ label: "Flutter",
876
+ installDirName: "flutters",
877
+ currentLinkName: "current-flutter",
878
+ envVar: "FLUTTER_ROOT",
879
+ supportsLts: false,
880
+ vendors: FLUTTER_VENDORS,
881
+ parseUserSpec: parseFlutterUserSpec,
882
+ parseDirName: parseFlutterDirName,
883
+ formatVersion: formatFlutterVersion,
884
+ compareVersions,
885
+ binRelPath(platform) {
886
+ return `bin/${platform.os === "windows" ? "flutter.bat" : "flutter"}`;
887
+ },
888
+ // flutter 归档是单根 flutter/ 目录,无 macOS bundle 概念
889
+ locateHome: (root) => root,
890
+ versionCheck: { args: ["--version"], stream: "stdout" }
891
+ };
892
+
893
+ // src/sdk/index.ts
894
+ var SPECS = {
895
+ java: javaSdk,
896
+ go: goSdk,
897
+ flutter: flutterSdk
898
+ };
899
+ var SDK_TYPES = ["java", "go", "flutter"];
900
+ function getSdkType(id) {
901
+ const spec = SPECS[id];
902
+ if (!spec) throw new SdkvmError(`Unknown SDK type: ${id}`);
903
+ return spec;
904
+ }
905
+
906
+ // src/core/paths.ts
907
+ function sdkvmHome() {
908
+ return envOverride("SDKVM_HOME", "JVM_HOME") ?? path3.join(os.homedir(), ".sdkvm");
909
+ }
910
+ var paths = {
911
+ root: sdkvmHome,
912
+ /** 各 SDK 类型的安装根(~/.sdkvm/jdks 等) */
913
+ sdks: (type) => path3.join(sdkvmHome(), getSdkType(type).installDirName),
914
+ /** 各 SDK 类型的 current 链接(~/.sdkvm/current-java 等) */
915
+ current: (type) => path3.join(sdkvmHome(), getSdkType(type).currentLinkName),
916
+ cache: () => path3.join(sdkvmHome(), "cache"),
917
+ tmp: () => path3.join(sdkvmHome(), "tmp"),
918
+ config: () => path3.join(sdkvmHome(), "config.json"),
919
+ lock: () => path3.join(sdkvmHome(), ".lock")
920
+ };
921
+ function ensureLayout() {
922
+ const dirs = [sdkvmHome(), ...SDK_TYPES.map((t) => paths.sdks(t)), paths.cache(), paths.tmp()];
923
+ for (const dir of dirs) {
924
+ fs3.mkdirSync(dir, { recursive: true });
925
+ }
926
+ }
927
+
928
+ // src/core/config.ts
929
+ var DEFAULT_CONFIG = {
930
+ version: 1,
931
+ defaultVendor: "temurin",
932
+ mirror: {}
933
+ };
934
+ function loadConfig() {
935
+ const file = paths.config();
936
+ if (!fs4.existsSync(file)) return { ...DEFAULT_CONFIG, mirror: {} };
937
+ try {
938
+ const parsed = JSON.parse(fs4.readFileSync(file, "utf8"));
939
+ const config = {
940
+ version: 1,
941
+ defaultVendor: parsed.defaultVendor && JAVA_VENDOR_IDS.includes(parsed.defaultVendor) ? parsed.defaultVendor : "temurin",
942
+ mirror: {}
943
+ };
944
+ if (parsed.mirror && typeof parsed.mirror === "object") {
945
+ for (const [id, v] of Object.entries(parsed.mirror)) {
946
+ if (typeof v === "string" && v.length > 0) config.mirror[id] = v;
947
+ }
948
+ }
949
+ return config;
950
+ } catch (err) {
951
+ const bak = `${file}.bak`;
952
+ try {
953
+ fs4.renameSync(file, bak);
954
+ log.warn(`config.json was corrupted; backed up to ${bak}, using defaults`);
955
+ } catch {
956
+ }
957
+ void err;
958
+ return { ...DEFAULT_CONFIG, mirror: {} };
959
+ }
960
+ }
961
+ function saveConfig(config) {
962
+ ensureLayout();
963
+ const file = paths.config();
964
+ const tmp = path4.join(path4.dirname(file), `.config.json.tmp-${process.pid}`);
965
+ fs4.writeFileSync(tmp, `${JSON.stringify(config, null, 2)}
966
+ `);
967
+ fs4.renameSync(tmp, file);
968
+ }
969
+
970
+ // src/core/lock.ts
971
+ import fs5 from "fs";
972
+ var STALE_MS = 5 * 60 * 1e3;
973
+ function acquireLock() {
974
+ const lockDir = paths.lock();
975
+ fs5.mkdirSync(paths.root(), { recursive: true });
976
+ try {
977
+ fs5.mkdirSync(lockDir);
978
+ fs5.writeFileSync(
979
+ `${lockDir}/info.json`,
980
+ JSON.stringify({ pid: process.pid, startedAt: Date.now() })
981
+ );
982
+ } catch (err) {
983
+ const e = err;
984
+ if (e.code === "EEXIST") {
985
+ const stat = fs5.statSync(lockDir);
986
+ if (Date.now() - stat.mtimeMs > STALE_MS) {
987
+ fs5.rmSync(lockDir, { recursive: true, force: true });
988
+ return acquireLock();
989
+ }
990
+ throw new SdkvmError("Another sdkvm operation is in progress", {
991
+ hint: "If this is wrong, remove ~/.sdkvm/.lock manually."
992
+ });
993
+ }
994
+ throw e;
995
+ }
996
+ }
997
+ function releaseLock() {
998
+ fs5.rmSync(paths.lock(), { recursive: true, force: true });
999
+ }
1000
+ async function withLock(fn) {
1001
+ acquireLock();
1002
+ try {
1003
+ return await fn();
1004
+ } finally {
1005
+ releaseLock();
1006
+ }
1007
+ }
1008
+
1009
+ // src/vendor/mirror.ts
1010
+ function applyMirror(artifact, platform, mirrorRoot) {
1011
+ if (!mirrorRoot) return artifact;
1012
+ if (artifact.vendorId === "golang") {
1013
+ const root2 = mirrorRoot.replace(/\/+$/, "");
1014
+ const file = artifact.downloadUrl.split("/").pop();
1015
+ if (!file) return artifact;
1016
+ return { ...artifact, downloadUrl: `${root2}/${file}` };
1017
+ }
1018
+ if (artifact.vendorId === "flutter") {
1019
+ const root2 = mirrorRoot.replace(/\/+$/, "");
1020
+ const url2 = artifact.downloadUrl.replace(
1021
+ /^https:\/\/storage\.googleapis\.com\/flutter_infra_release/,
1022
+ root2
1023
+ );
1024
+ return { ...artifact, downloadUrl: url2 };
1025
+ }
1026
+ if (artifact.vendorId !== "temurin") return artifact;
1027
+ const m = /^https:\/\/github\.com\/adoptium\/temurin(\d+)-binaries\/releases\/download\/[^/]+\/(.+)$/.exec(
1028
+ artifact.downloadUrl
1029
+ );
1030
+ if (!m || !m[1] || !m[2]) return artifact;
1031
+ const root = mirrorRoot.replace(/\/+$/, "");
1032
+ const url = `${root}/${m[1]}/jdk/${platform.arch}/${platform.os}/${m[2]}`;
1033
+ return { ...artifact, downloadUrl: url };
1034
+ }
1035
+
1036
+ // src/net/download.ts
1037
+ import crypto from "crypto";
1038
+ import fs6 from "fs";
1039
+ import path5 from "path";
1040
+ var IDLE_TIMEOUT_MS = 6e4;
1041
+ async function downloadFile(url, destFile, onProgress) {
1042
+ const partFile = `${destFile}.part`;
1043
+ const hash = crypto.createHash("sha256");
1044
+ const out = fs6.createWriteStream(partFile);
1045
+ let bytes = 0;
1046
+ let total = null;
1047
+ let stalled = false;
1048
+ const ac = new AbortController();
1049
+ const timer = setTimeout(() => {
1050
+ stalled = true;
1051
+ ac.abort();
1052
+ }, IDLE_TIMEOUT_MS);
1053
+ try {
1054
+ const res = await httpFetch(url, { signal: ac.signal });
1055
+ timer.refresh();
1056
+ total = Number(res.headers.get("content-length")) || null;
1057
+ if (!res.body) throw new SdkvmError(`Empty response body: ${url}`);
1058
+ for await (const chunk of res.body) {
1059
+ timer.refresh();
1060
+ out.write(chunk);
1061
+ hash.update(chunk);
1062
+ bytes += chunk.byteLength;
1063
+ onProgress?.(bytes, total);
1064
+ }
1065
+ await new Promise((resolve, reject) => {
1066
+ out.end((err) => err ? reject(err) : resolve());
1067
+ });
1068
+ } catch (err) {
1069
+ await new Promise((resolve) => {
1070
+ out.once("close", resolve);
1071
+ out.destroy();
1072
+ });
1073
+ fs6.rmSync(partFile, { force: true });
1074
+ if (stalled) {
1075
+ throw new SdkvmError(`Download stalled: no data for ${IDLE_TIMEOUT_MS / 1e3}s (${bytes} bytes so far)`, {
1076
+ hint: url
1077
+ });
1078
+ }
1079
+ throw err;
1080
+ } finally {
1081
+ clearTimeout(timer);
1082
+ }
1083
+ if (bytes === 0) {
1084
+ fs6.rmSync(partFile, { force: true });
1085
+ throw new SdkvmError(`Download incomplete: 0 bytes`, { hint: url });
1086
+ }
1087
+ if (total !== null && bytes !== total) {
1088
+ fs6.rmSync(partFile, { force: true });
1089
+ throw new SdkvmError(`Download incomplete: ${bytes}/${total} bytes`, { hint: url });
1090
+ }
1091
+ fs6.renameSync(partFile, destFile);
1092
+ return { file: destFile, sha256: hash.digest("hex"), bytes };
1093
+ }
1094
+ function cacheFileName(url) {
1095
+ const safe = path5.basename(new URL(url).pathname).replace(/[^\w.+-]/g, "_");
1096
+ return safe || `download-${Date.now()}`;
1097
+ }
1098
+
1099
+ // src/net/checksum.ts
1100
+ var HEX64 = /^[0-9a-f]{64}$/i;
1101
+ function extractExpectedChecksum(text) {
1102
+ const trimmed = text.trim();
1103
+ if (trimmed.startsWith("{")) {
1104
+ try {
1105
+ const obj = JSON.parse(trimmed);
1106
+ if (obj.checksum && HEX64.test(obj.checksum)) return obj.checksum.toLowerCase();
1107
+ } catch {
1108
+ }
1109
+ return null;
1110
+ }
1111
+ const token = trimmed.split(/\s+/)[0] ?? "";
1112
+ return HEX64.test(token) ? token.toLowerCase() : null;
1113
+ }
1114
+ async function verifyChecksum(artifact, actual) {
1115
+ const info = artifact.checksum;
1116
+ if (!info) {
1117
+ log.warn(`no checksum source for ${artifact.displayName}, skipping verification`);
1118
+ return;
1119
+ }
1120
+ let expected = info.expected?.toLowerCase() ?? null;
1121
+ if (!expected && info.url) {
1122
+ try {
1123
+ expected = extractExpectedChecksum(await httpText(info.url));
1124
+ } catch {
1125
+ log.warn(`cannot fetch checksum for ${artifact.displayName}, skipping verification`);
1126
+ return;
1127
+ }
1128
+ }
1129
+ if (!expected) {
1130
+ log.warn(`checksum source has no valid hash for ${artifact.displayName}, skipping`);
1131
+ return;
1132
+ }
1133
+ if (expected !== actual.toLowerCase()) {
1134
+ throw new SdkvmError(`Checksum mismatch for ${artifact.displayName}`, {
1135
+ hint: `expected ${expected}, got ${actual}`
1136
+ });
1137
+ }
1138
+ }
1139
+
1140
+ // src/fs/extract.ts
1141
+ import fs7 from "fs";
1142
+ import path6 from "path";
1143
+
1144
+ // src/util/spawn.ts
1145
+ import { execFile } from "child_process";
1146
+ import { promisify } from "util";
1147
+ var execFileAsync = promisify(execFile);
1148
+ async function run(cmd, args, opts = {}) {
1149
+ try {
1150
+ return await execFileAsync(cmd, args, {
1151
+ cwd: opts.cwd,
1152
+ windowsHide: true,
1153
+ maxBuffer: 32 * 1024 * 1024
1154
+ });
1155
+ } catch (err) {
1156
+ const e = err;
1157
+ const detail = (e.stderr || e.message || "").split("\n")[0];
1158
+ throw new SdkvmError(`Failed to run ${cmd}: ${detail}`, {
1159
+ hint: `args: ${args.join(" ")}`
1160
+ });
1161
+ }
1162
+ }
1163
+
1164
+ // src/fs/extract.ts
1165
+ var WIN_TAR = "C:\\Windows\\System32\\tar.exe";
1166
+ async function extractArchive(archiveFile, archiveType, destDir, platform) {
1167
+ fs7.mkdirSync(destDir, { recursive: true });
1168
+ if (platform.os === "windows") {
1169
+ if (fs7.existsSync(WIN_TAR)) {
1170
+ await run(WIN_TAR, ["-xf", archiveFile, "-C", destDir]);
1171
+ } else {
1172
+ await run("powershell.exe", [
1173
+ "-NoProfile",
1174
+ "-NonInteractive",
1175
+ "-Command",
1176
+ `Expand-Archive -LiteralPath '${archiveFile.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force`
1177
+ ]);
1178
+ }
1179
+ return;
1180
+ }
1181
+ if (archiveType === "zip" && platform.rawPlatform === "linux") {
1182
+ await run("unzip", ["-q", "-o", archiveFile, "-d", destDir]);
1183
+ return;
1184
+ }
1185
+ await run("tar", ["-xf", archiveFile, "-C", destDir]);
1186
+ }
1187
+ function tmpExtractDir(base) {
1188
+ return path6.join(base, `extract-${Date.now()}-${process.pid}`);
1189
+ }
1190
+
1191
+ // src/fs/layout.ts
1192
+ import fs8 from "fs";
1193
+ import path7 from "path";
1194
+ function normalizeExtracted(tmpDir, platform, type) {
1195
+ const spec = getSdkType(type);
1196
+ const entries = fs8.readdirSync(tmpDir).filter((e) => e !== "._" && !e.startsWith("._"));
1197
+ const real = entries.filter((e) => e !== ".DS_Store");
1198
+ let root;
1199
+ if (real.length === 1 && fs8.statSync(path7.join(tmpDir, real[0])).isDirectory()) {
1200
+ root = path7.join(tmpDir, real[0]);
1201
+ } else {
1202
+ root = tmpDir;
1203
+ }
1204
+ const home = spec.locateHome(root);
1205
+ const bin = path7.join(home, spec.binRelPath(platform));
1206
+ if (!fs8.existsSync(bin)) {
1207
+ throw new SdkvmError(`Archive does not look like a valid ${spec.label} (bin not found)`, {
1208
+ hint: `expected ${bin}`
1209
+ });
1210
+ }
1211
+ return { root, home };
1212
+ }
1213
+
1214
+ // src/ui/progress.ts
1215
+ var WIDTH = 26;
1216
+ function humanBytes(n) {
1217
+ if (n < 1024) return `${n}B`;
1218
+ const units = ["KB", "MB", "GB"];
1219
+ let v = n / 1024;
1220
+ let i = 0;
1221
+ while (v >= 1024 && i < units.length - 1) {
1222
+ v /= 1024;
1223
+ i++;
1224
+ }
1225
+ return `${v.toFixed(1)}${units[i]}`;
1226
+ }
1227
+ function createProgress(label2) {
1228
+ const isTty = process.stderr.isTTY === true;
1229
+ let lastLine = 0;
1230
+ return {
1231
+ update(bytes, total) {
1232
+ if (isTty) {
1233
+ const ratio = total ? Math.min(1, bytes / total) : null;
1234
+ const bar = ratio === null ? humanBytes(bytes) : `${"\u2588".repeat(Math.round(ratio * WIDTH))}${"\u2591".repeat(WIDTH - Math.round(ratio * WIDTH))} ${(ratio * 100).toFixed(1)}%`;
1235
+ process.stderr.write(`\r${label2} ${bar}`.padEnd(72));
1236
+ } else if (bytes - lastLine >= 16 * 1024 * 1024) {
1237
+ lastLine = bytes;
1238
+ process.stderr.write(`${label2} ${humanBytes(bytes)}${total ? ` / ${humanBytes(total)}` : ""}
1239
+ `);
1240
+ }
1241
+ },
1242
+ done(bytes, total) {
1243
+ if (isTty) {
1244
+ process.stderr.write(`\r${label2} ${humanBytes(bytes)}${total ? ` / ${humanBytes(total)}` : ""} \u2713
1245
+ `);
1246
+ } else if (bytes - lastLine >= 0) {
1247
+ process.stderr.write(`${label2} ${humanBytes(bytes)} complete
1248
+ `);
1249
+ }
1250
+ }
1251
+ };
1252
+ }
1253
+
1254
+ // src/cli/install.ts
1255
+ async function installCommand(type, specInput, opts) {
1256
+ const platform = detectPlatform();
1257
+ const config = loadConfig();
1258
+ const { vendor: specVendor, spec } = getSdkType(type).parseUserSpec(specInput);
1259
+ const vendorId = resolveVendorId(type, specVendor ?? opts.vendor, config);
1260
+ const vendor = getVendor(type, vendorId);
1261
+ log.info(`resolving ${vendor.label} ${specInput} for ${platform.os}/${platform.arch} ...`);
1262
+ const resolved = await vendor.resolve(spec, platform);
1263
+ const mirrorRoot = envOverride("SDKVM_MIRROR", "JVM_MIRROR") ?? config.mirror[vendorId] ?? null;
1264
+ const artifact = applyMirror(resolved, platform, mirrorRoot);
1265
+ const finalDir = path8.join(paths.sdks(type), artifact.dirName);
1266
+ const hintVersion = type === "java" ? String(artifact.version.major) : `${artifact.version.major}.${artifact.version.minor}`;
1267
+ if (fs9.existsSync(finalDir)) {
1268
+ if (!opts.force) {
1269
+ log.warn(`${artifact.displayName} is already installed`);
1270
+ log.info(`run: ${cmdPath(type)} use ${hintVersion}`);
1271
+ return;
1272
+ }
1273
+ log.warn(`--force: removing existing ${artifact.dirName}`);
1274
+ }
1275
+ await withLock(async () => {
1276
+ ensureLayout();
1277
+ for (const f of fs9.readdirSync(paths.cache())) {
1278
+ if (f.endsWith(".part")) fs9.rmSync(path8.join(paths.cache(), f), { force: true });
1279
+ }
1280
+ const dest = path8.join(paths.cache(), cacheFileName(artifact.downloadUrl));
1281
+ const progress = createProgress(`\u2193 ${artifact.displayName}`);
1282
+ log.info(`downloading ${artifact.downloadUrl}`);
1283
+ const dl = await downloadFile(artifact.downloadUrl, dest, (b, t) => progress.update(b, t));
1284
+ progress.done(dl.bytes, null);
1285
+ await verifyChecksum(artifact, dl.sha256);
1286
+ const tmp = tmpExtractDir(paths.tmp());
1287
+ let finalTmp = tmp;
1288
+ try {
1289
+ log.info("extracting ...");
1290
+ await extractArchive(dest, artifact.archive, tmp, platform);
1291
+ const normalized = normalizeExtracted(tmp, platform, type);
1292
+ finalTmp = normalized.root;
1293
+ if (fs9.existsSync(finalDir)) fs9.rmSync(finalDir, { recursive: true, force: true });
1294
+ fs9.renameSync(normalized.root, finalDir);
1295
+ } catch (err) {
1296
+ fs9.rmSync(finalTmp, { recursive: true, force: true });
1297
+ fs9.rmSync(tmp, { recursive: true, force: true });
1298
+ throw err instanceof SdkvmError ? err : err;
1299
+ } finally {
1300
+ fs9.rmSync(dest, { force: true });
1301
+ fs9.rmSync(paths.tmp(), { recursive: true, force: true });
1302
+ fs9.mkdirSync(paths.tmp(), { recursive: true });
1303
+ }
1304
+ });
1305
+ log.ok(`installed ${artifact.displayName} \u2192 ${finalDir}`);
1306
+ log.info(`switch to it: ${cmdPath(type)} use ${hintVersion}`);
1307
+ }
1308
+
1309
+ // src/cli/use.ts
1310
+ import { execFile as execFile2 } from "child_process";
1311
+ import { promisify as promisify2 } from "util";
1312
+ import path14 from "path";
1313
+
1314
+ // src/core/registry.ts
1315
+ import fs11 from "fs";
1316
+ import path10 from "path";
1317
+
1318
+ // src/fs/link.ts
1319
+ import fs10 from "fs";
1320
+ import path9 from "path";
1321
+ function setCurrent(type, target, platform) {
1322
+ const link = paths.current(type);
1323
+ if (platform.os === "windows") {
1324
+ try {
1325
+ fs10.rmSync(link, { force: true });
1326
+ } catch {
1327
+ fs10.rmSync(link, { recursive: true, force: true });
1328
+ }
1329
+ fs10.symlinkSync(target, link, "junction");
1330
+ return;
1331
+ }
1332
+ const tmp = `${link}.tmp-${process.pid}`;
1333
+ fs10.rmSync(tmp, { force: true });
1334
+ fs10.symlinkSync(target, tmp);
1335
+ fs10.renameSync(tmp, link);
1336
+ }
1337
+ function readCurrent(type) {
1338
+ const link = paths.current(type);
1339
+ try {
1340
+ const st = fs10.lstatSync(link);
1341
+ if (!st.isSymbolicLink()) return null;
1342
+ const raw = fs10.readlinkSync(link);
1343
+ return path9.resolve(path9.dirname(link), raw);
1344
+ } catch {
1345
+ return null;
1346
+ }
1347
+ }
1348
+ function clearCurrent(type) {
1349
+ const link = paths.current(type);
1350
+ try {
1351
+ fs10.rmSync(link, { force: true });
1352
+ } catch {
1353
+ fs10.rmSync(link, { recursive: true, force: true });
1354
+ }
1355
+ }
1356
+
1357
+ // src/core/registry.ts
1358
+ function listInstalled(type) {
1359
+ const spec = getSdkType(type);
1360
+ const root = paths.sdks(type);
1361
+ if (!fs11.existsSync(root)) return [];
1362
+ const result = [];
1363
+ for (const name of fs11.readdirSync(root)) {
1364
+ const version = spec.parseDirName(name);
1365
+ if (!version) continue;
1366
+ const dirPath = path10.join(root, name);
1367
+ if (!fs11.statSync(dirPath).isDirectory()) continue;
1368
+ result.push({ type, version, dirPath, home: spec.locateHome(dirPath) });
1369
+ }
1370
+ result.sort((a, b) => spec.compareVersions(a.version, b.version));
1371
+ return result;
1372
+ }
1373
+ function currentSdk(type) {
1374
+ const current = readCurrent(type);
1375
+ if (!current) return null;
1376
+ return listInstalled(type).find(
1377
+ (j) => j.home === current || j.dirPath === current || current.startsWith(j.dirPath + path10.sep)
1378
+ ) ?? null;
1379
+ }
1380
+ function findInstalled(type, specInput, vendorArg) {
1381
+ const spec = getSdkType(type);
1382
+ const { vendor: specVendor, spec: parsed } = spec.parseUserSpec(specInput);
1383
+ const vendor = vendorArg ?? specVendor;
1384
+ const all = listInstalled(type);
1385
+ const candidates = vendor ? all.filter((j) => j.version.vendor === vendor) : all;
1386
+ let matched;
1387
+ if (parsed.kind === "major") {
1388
+ matched = candidates.filter((j) => j.version.major === parsed.major);
1389
+ } else if (parsed.kind === "line") {
1390
+ matched = candidates.filter(
1391
+ (j) => j.version.major === parsed.major && j.version.minor === parsed.minor
1392
+ );
1393
+ } else if (parsed.kind === "lts") {
1394
+ if (!spec.supportsLts) {
1395
+ throw new SdkvmError("Go has no LTS releases", {
1396
+ hint: `Try: ${cmdPath(type)} install latest`
1397
+ });
1398
+ }
1399
+ matched = candidates.filter((j) => LTS_MAJORS.has(j.version.major));
1400
+ } else if (parsed.kind === "latest") {
1401
+ matched = candidates;
1402
+ } else {
1403
+ const v = parsed.version;
1404
+ matched = candidates.filter((j) => {
1405
+ const f = spec.formatVersion(j.version);
1406
+ return f === v || f.startsWith(`${v}+`) || f.startsWith(`${v}.`);
1407
+ });
1408
+ }
1409
+ if (matched.length === 0) {
1410
+ const installedList = all.map((j) => ` ${j.version.vendor}-${spec.formatVersion(j.version)}`).join("\n");
1411
+ const want = parsed.kind === "full" ? parsed.version : specInput;
1412
+ throw new SdkvmError(`No installed ${spec.label} matches "${specInput}"`, {
1413
+ hint: (all.length > 0 ? `Installed:
1414
+ ${installedList}
1415
+ ` : "") + `Install one first: ${cmdPath(type)} install ${want}`
1416
+ });
1417
+ }
1418
+ return matched[matched.length - 1];
1419
+ }
1420
+
1421
+ // src/shell/detect.ts
1422
+ import os2 from "os";
1423
+ import path11 from "path";
1424
+ function detectRcFile(platform) {
1425
+ if (platform === "windows") return null;
1426
+ const shell = process.env.SHELL ?? "";
1427
+ const base = path11.basename(shell);
1428
+ const home = os2.homedir();
1429
+ if (base === "zsh" || base === "-zsh") return path11.join(home, ".zshrc");
1430
+ if (base === "bash" || base === "-bash") {
1431
+ return platform === "mac" ? path11.join(home, ".bash_profile") : path11.join(home, ".bashrc");
1432
+ }
1433
+ if (base === "fish") return null;
1434
+ return null;
1435
+ }
1436
+
1437
+ // src/shell/rc.ts
1438
+ import fs12 from "fs";
1439
+ import os3 from "os";
1440
+ import path12 from "path";
1441
+ var LEGACY_RC_BEGIN = "# >>> jvm init >>>";
1442
+ var LEGACY_RC_END = "# <<< jvm init <<<";
1443
+ function rcBegin(type) {
1444
+ return `# >>> ${CLI_BIN} ${type} init >>>`;
1445
+ }
1446
+ function rcEnd(type) {
1447
+ return `# <<< ${CLI_BIN} ${type} init <<<`;
1448
+ }
1449
+ function rcBlock(type) {
1450
+ const spec = getSdkType(type);
1451
+ const abs = paths.current(type);
1452
+ const rel = path12.relative(os3.homedir(), abs);
1453
+ const link = rel.startsWith("..") ? abs : `$HOME/${rel}`;
1454
+ return [
1455
+ rcBegin(type),
1456
+ `export ${spec.envVar}="${link}"`,
1457
+ `case ":$PATH:" in *":$${spec.envVar}/bin:"*) ;; *) export PATH="$${spec.envVar}/bin:$PATH";; esac`,
1458
+ rcEnd(type)
1459
+ ].join("\n");
1460
+ }
1461
+ function stripRcBlock(content, type) {
1462
+ return stripBlockBetween(content, rcBegin(type), rcEnd(type));
1463
+ }
1464
+ function stripLegacyRcBlock(content) {
1465
+ return stripBlockBetween(content, LEGACY_RC_BEGIN, LEGACY_RC_END);
1466
+ }
1467
+ function stripBlockBetween(content, begin, end) {
1468
+ const re = new RegExp(`\\n*${escapeRegex(begin)}[\\s\\S]*?${escapeRegex(end)}\\n*`, "g");
1469
+ return content.replace(re, "\n");
1470
+ }
1471
+ function upsertRcContent(content, type) {
1472
+ const stripped = stripLegacyRcBlock(stripRcBlock(content, type)).replace(/\s+$/, "");
1473
+ return `${stripped}
1474
+
1475
+ ${rcBlock(type)}
1476
+ `;
1477
+ }
1478
+ function upsertRcFile(file, type) {
1479
+ const content = fs12.existsSync(file) ? fs12.readFileSync(file, "utf8") : "";
1480
+ fs12.writeFileSync(file, upsertRcContent(content, type));
1481
+ }
1482
+ function escapeRegex(s) {
1483
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1484
+ }
1485
+
1486
+ // src/shell/winenv.ts
1487
+ import os4 from "os";
1488
+ import path13 from "path";
1489
+ function encoded(ps) {
1490
+ return ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(ps, "utf16le").toString("base64")];
1491
+ }
1492
+ function currentLinkWin(type) {
1493
+ const rel = path13.relative(os4.homedir(), paths.current(type));
1494
+ return `%USERPROFILE%\\${rel.split(path13.sep).join("\\")}`;
1495
+ }
1496
+ async function setEnvWin(name, value) {
1497
+ await run("setx", [name, value]);
1498
+ }
1499
+ async function setSdkEnvWin(type) {
1500
+ const spec = getSdkType(type);
1501
+ await setEnvWin(spec.envVar, currentLinkWin(type));
1502
+ }
1503
+ async function ensureUserPathWin(entry) {
1504
+ const ps = [
1505
+ "$k=[Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment',$true)",
1506
+ "if(-not $k){ throw 'no Environment key' }",
1507
+ "$fmt=[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames",
1508
+ "$raw=[string]$k.GetValue('Path','',$fmt)",
1509
+ "$parts=@($raw -split ';' | Where-Object { $_ -ne '' })",
1510
+ `if($parts -notcontains '${entry.replace(/'/g, "''")}'){`,
1511
+ ` $parts += '${entry.replace(/'/g, "''")}'`,
1512
+ " $kind=[Microsoft.Win32.RegistryValueKind]::ExpandString",
1513
+ " if($k.GetValueKind('Path') -eq [Microsoft.Win32.RegistryValueKind]::String){ $kind=[Microsoft.Win32.RegistryValueKind]::String }",
1514
+ " $k.SetValue('Path', ($parts -join ';'), $kind)",
1515
+ "}",
1516
+ `$sig='[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);'`,
1517
+ "Add-Type -MemberDefinition $sig -Name NativeMethods -Namespace Win32",
1518
+ "$r=[UIntPtr]::Zero",
1519
+ "[Win32.NativeMethods]::SendMessageTimeout([IntPtr]0xffff, 0x1A, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$r) | Out-Null"
1520
+ ].join("\n");
1521
+ await run("powershell.exe", encoded(ps));
1522
+ }
1523
+ function sdkPathEntry(type) {
1524
+ return `%${getSdkType(type).envVar}%\\bin`;
1525
+ }
1526
+
1527
+ // src/cli/use.ts
1528
+ var execFileAsync2 = promisify2(execFile2);
1529
+ async function showSdkVersion(binPath, type) {
1530
+ const spec = getSdkType(type);
1531
+ try {
1532
+ const { stdout, stderr } = await execFileAsync2(binPath, spec.versionCheck.args, {
1533
+ timeout: 3e4
1534
+ });
1535
+ const text = spec.versionCheck.stream === "stdout" ? stdout : stderr;
1536
+ const line = (text || "").split("\n").find((l) => l.includes("version"));
1537
+ if (line) log.info(line.trim());
1538
+ } catch {
1539
+ }
1540
+ }
1541
+ async function useCommand(type, specInput, opts) {
1542
+ const platform = detectPlatform();
1543
+ const spec = getSdkType(type);
1544
+ const installed = findInstalled(type, specInput, opts.vendor);
1545
+ const label2 = `${installed.version.vendor}-${spec.formatVersion(installed.version)}`;
1546
+ setCurrent(type, installed.home, platform);
1547
+ log.ok(`current \u2192 ${label2}`);
1548
+ if (platform.os === "windows") {
1549
+ await setSdkEnvWin(type);
1550
+ await ensureUserPathWin(sdkPathEntry(type));
1551
+ log.info(`${spec.envVar} and PATH updated in user environment`);
1552
+ log.warn("reopen your terminal (or restart your IDE) for the change to take effect");
1553
+ } else {
1554
+ const rc = detectRcFile(platform.os);
1555
+ if (rc) {
1556
+ upsertRcFile(rc, type);
1557
+ log.info(`updated ${rc} \u2014 run: source ${rc} (or open a new terminal)`);
1558
+ } else {
1559
+ log.warn("could not detect your shell; add this to your rc file manually:");
1560
+ console.log(rcBlock(type));
1561
+ }
1562
+ }
1563
+ await showSdkVersion(path14.join(installed.home, spec.binRelPath(platform)), type);
1564
+ }
1565
+
1566
+ // src/cli/ls.ts
1567
+ async function lsCommand(type, opts) {
1568
+ if (opts.remote) return listRemote(type, opts);
1569
+ listLocal(type);
1570
+ }
1571
+ function listLocal(type) {
1572
+ const spec = getSdkType(type);
1573
+ const installed = listInstalled(type);
1574
+ if (installed.length === 0) {
1575
+ log.info(`no ${spec.label} installed`);
1576
+ log.info(`try: ${cmdPath(type)} install ${spec.supportsLts ? "lts" : "latest"}`);
1577
+ return;
1578
+ }
1579
+ const current = currentSdk(type);
1580
+ for (const j of installed) {
1581
+ const name = `${j.version.vendor}-${spec.formatVersion(j.version)}`;
1582
+ const marker = current?.dirPath === j.dirPath ? "\u2192" : " ";
1583
+ log.raw(`${marker} ${name}`);
1584
+ }
1585
+ }
1586
+ async function listRemote(type, opts) {
1587
+ const config = loadConfig();
1588
+ const ids = opts.vendor ? [resolveVendorId(type, opts.vendor, config)] : allVendorIds(type);
1589
+ const sections = await Promise.all(
1590
+ ids.map(async (id) => {
1591
+ const vendor = getVendor(type, id);
1592
+ try {
1593
+ const lines = (await vendor.listMajors()).slice().sort((a, b) => b.key.localeCompare(a.key, void 0, { numeric: true }));
1594
+ return { vendor, lines };
1595
+ } catch (err) {
1596
+ log.warn(`${vendor.label}: list unavailable (${toSdkvmError(err).message})`);
1597
+ return null;
1598
+ }
1599
+ })
1600
+ );
1601
+ for (const sec of sections) {
1602
+ if (!sec || sec.lines.length === 0) continue;
1603
+ const { vendor, lines } = sec;
1604
+ log.raw("");
1605
+ log.raw(`# ${vendor.label}`);
1606
+ const width = Math.max(...lines.map((m) => `${vendor.id}-${m.key}`.length)) + 2;
1607
+ const MAX_LINES = 12;
1608
+ const shown = lines.slice(0, MAX_LINES);
1609
+ for (const m of shown) {
1610
+ const name = `${vendor.id}-${m.key}`.padEnd(width);
1611
+ const lts = m.lts ? "(lts) " : "";
1612
+ const latest = m.latestFullVersion ? `latest: ${vendor.id}-${m.latestFullVersion}` : "";
1613
+ log.raw(` ${name}${lts}${latest}`.trimEnd());
1614
+ }
1615
+ if (lines.length > shown.length) {
1616
+ log.raw(` # note: ${lines.length - shown.length} older version lines hidden; install older ones by exact name, e.g. ${vendor.id}-${lines[lines.length - 1]?.key}`);
1617
+ }
1618
+ if (!vendor.supportsFullVersionList) {
1619
+ const first = lines[0]?.key ?? "";
1620
+ log.raw(` # note: no version-list API; install resolves to the latest patch, e.g. ${cmdPath(type)} install ${vendor.id}-${first}`);
1621
+ }
1622
+ }
1623
+ log.raw("");
1624
+ log.raw(`# install with: ${cmdPath(type)} install <name>`);
1625
+ }
1626
+ function currentCommand(types = SDK_TYPES) {
1627
+ let any = false;
1628
+ for (const type of types) {
1629
+ const spec = getSdkType(type);
1630
+ const current = currentSdk(type);
1631
+ if (!current) continue;
1632
+ any = true;
1633
+ log.raw(`${type}: ${current.version.vendor}-${spec.formatVersion(current.version)}`);
1634
+ log.raw(` ${spec.envVar} \u2192 ${current.home}`);
1635
+ }
1636
+ if (!any) {
1637
+ log.info("no current SDK");
1638
+ log.info(`run: ${cmdPath("java")} use <version> (see: ${cmdPath("java")} ls)`);
1639
+ }
1640
+ }
1641
+
1642
+ // src/cli/uninstall.ts
1643
+ import fs13 from "fs";
1644
+ async function uninstallCommand(type, specInput, opts) {
1645
+ const spec = getSdkType(type);
1646
+ const installed = findInstalled(type, specInput, opts.vendor);
1647
+ await withLock(async () => {
1648
+ if (currentSdk(type)?.dirPath === installed.dirPath) {
1649
+ clearCurrent(type);
1650
+ log.warn(`uninstalled the current ${spec.label}; ${spec.envVar} is now dangling`);
1651
+ log.info(`select another: ${cmdPath(type)} use <version>`);
1652
+ }
1653
+ fs13.rmSync(installed.dirPath, { recursive: true, force: true });
1654
+ });
1655
+ log.ok(`removed ${installed.version.vendor}-${spec.formatVersion(installed.version)}`);
1656
+ }
1657
+
1658
+ // src/cli/mirror.ts
1659
+ var RECOMMENDED = {
1660
+ temurin: "https://mirrors.nju.edu.cn/adoptium",
1661
+ golang: "https://golang.google.cn/dl",
1662
+ flutter: "https://mirror.nju.edu.cn/flutter/flutter_infra_release"
1663
+ };
1664
+ function mirrorCommand(type, action, urlOrVendor, maybeUrl) {
1665
+ const config = loadConfig();
1666
+ const mirrorable = allVendorIds(type).filter((id) => id in RECOMMENDED);
1667
+ const first = mirrorable[0] ?? allVendorIds(type)[0] ?? "";
1668
+ if (action === "set") {
1669
+ let vendor;
1670
+ let url;
1671
+ if (maybeUrl !== void 0) {
1672
+ vendor = urlOrVendor ?? "";
1673
+ url = maybeUrl;
1674
+ } else {
1675
+ vendor = first;
1676
+ url = urlOrVendor;
1677
+ }
1678
+ if (!url) throw new SdkvmError(`usage: ${cmdPath(type)} mirror set [vendor] <url>`);
1679
+ if (!mirrorable.includes(vendor)) {
1680
+ throw new SdkvmError(`mirroring is only supported for ${mirrorable.join(", ") || "none"} (got "${vendor}")`, {
1681
+ hint: `recommended: ${cmdPath(type)} mirror set ${first} ${RECOMMENDED[first] ?? ""}`.trim()
1682
+ });
1683
+ }
1684
+ try {
1685
+ new URL(url);
1686
+ } catch {
1687
+ throw new SdkvmError(`invalid URL: ${url}`);
1688
+ }
1689
+ config.mirror[vendor] = url.replace(/\/+$/, "");
1690
+ saveConfig(config);
1691
+ log.ok(`mirror for ${vendor} \u2192 ${config.mirror[vendor]}`);
1692
+ return;
1693
+ }
1694
+ if (action === "unset") {
1695
+ const vendor = urlOrVendor ?? first;
1696
+ if (!mirrorable.includes(vendor)) {
1697
+ throw new SdkvmError(`mirroring is only supported for ${mirrorable.join(", ") || "none"}`);
1698
+ }
1699
+ config.mirror[vendor] = null;
1700
+ saveConfig(config);
1701
+ log.ok(`mirror for ${vendor} cleared (official source)`);
1702
+ return;
1703
+ }
1704
+ log.raw("mirrors:");
1705
+ for (const id of allVendorIds(type)) {
1706
+ const url = config.mirror[id];
1707
+ log.raw(` ${id.padEnd(8)} ${url ?? "(official)"}`);
1708
+ }
1709
+ if (first in RECOMMENDED) {
1710
+ log.raw(`recommended for CN users: ${cmdPath(type)} mirror set ${first} ${RECOMMENDED[first]}`);
1711
+ }
1712
+ }
1713
+
1714
+ // src/core/migrate.ts
1715
+ import fs14 from "fs";
1716
+ import os5 from "os";
1717
+ import path15 from "path";
1718
+ async function migrateLegacyHomeIfNeeded(opts = {}) {
1719
+ const oldHome = opts.oldHome ?? path15.join(os5.homedir(), ".jvm");
1720
+ const newHome = opts.newHome ?? sdkvmHome();
1721
+ if (opts.newHome === void 0 && envOverride("SDKVM_HOME", "JVM_HOME")) return "not-needed";
1722
+ if (!fs14.existsSync(oldHome)) return "not-needed";
1723
+ if (fs14.existsSync(newHome)) {
1724
+ log.warn(`found both ${oldHome} and ${newHome}; keeping ${newHome}, not merging`);
1725
+ log.warn(`move SDKs into ${newHome} manually if needed, then remove ${oldHome}`);
1726
+ return "conflict";
1727
+ }
1728
+ const platform = opts.platform ?? detectPlatform();
1729
+ const rc = opts.rcFile !== void 0 ? opts.rcFile : detectRcFile(platform.os);
1730
+ const oldLock = path15.join(oldHome, ".lock");
1731
+ if (fs14.existsSync(oldLock)) {
1732
+ const stat = fs14.statSync(oldLock);
1733
+ if (Date.now() - stat.mtimeMs <= 5 * 60 * 1e3) {
1734
+ log.warn("another sdkvm operation seems in progress; skipping migration this run");
1735
+ return "not-needed";
1736
+ }
1737
+ }
1738
+ fs14.renameSync(oldHome, newHome);
1739
+ const prevHome = process.env.SDKVM_HOME;
1740
+ process.env.SDKVM_HOME = newHome;
1741
+ try {
1742
+ await fixupAfterRename(oldHome, newHome, platform, rc);
1743
+ } finally {
1744
+ if (prevHome === void 0) delete process.env.SDKVM_HOME;
1745
+ else process.env.SDKVM_HOME = prevHome;
1746
+ }
1747
+ log.ok(`migrated ${oldHome} \u2192 ${newHome} (your installed JDKs are untouched)`);
1748
+ return "migrated";
1749
+ }
1750
+ async function fixupAfterRename(oldHome, newHome, platform, rc) {
1751
+ try {
1752
+ const oldLink = path15.join(newHome, "current");
1753
+ let target = null;
1754
+ try {
1755
+ const st = fs14.lstatSync(oldLink);
1756
+ if (st.isSymbolicLink()) {
1757
+ target = path15.resolve(path15.dirname(oldLink), fs14.readlinkSync(oldLink));
1758
+ }
1759
+ } catch {
1760
+ }
1761
+ if (target) {
1762
+ const newTarget = target.startsWith(oldHome) ? newHome + target.slice(oldHome.length) : target;
1763
+ if (fs14.existsSync(newTarget)) setCurrent("java", newTarget, platform);
1764
+ }
1765
+ try {
1766
+ fs14.rmSync(oldLink, { force: true });
1767
+ } catch {
1768
+ fs14.rmSync(oldLink, { recursive: true, force: true });
1769
+ }
1770
+ } catch (err) {
1771
+ log.warn(
1772
+ `current link not migrated (${err instanceof Error ? err.message : String(err)}); run: sdkvm java use <version>`
1773
+ );
1774
+ }
1775
+ if (platform.os === "windows") {
1776
+ try {
1777
+ await setSdkEnvWin("java");
1778
+ } catch (err) {
1779
+ log.warn(`registry JAVA_HOME not updated: ${err instanceof Error ? err.message : String(err)}`);
1780
+ }
1781
+ }
1782
+ if (rc && fs14.existsSync(rc)) {
1783
+ try {
1784
+ if (fs14.readFileSync(rc, "utf8").includes(LEGACY_RC_BEGIN)) upsertRcFile(rc, "java");
1785
+ } catch {
1786
+ log.warn(`rc file not updated: ${rc}`);
1787
+ }
1788
+ }
1789
+ }
1790
+
1791
+ // src/index.ts
1792
+ var program = new Command();
1793
+ program.name("sdkvm").description("SDK version manager \u2014 install & switch JDKs (Temurin / Zulu / Corretto), Go toolchains, and Flutter SDKs").version(getVersion());
1794
+ function registerSdkCommands(cmd, type) {
1795
+ const s = getSdkType(type);
1796
+ const isJava = type === "java";
1797
+ 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" : "3.47 | 3.47.5 | 3.49.0-0.1.pre | latest | flutter-3.47";
1798
+ const vendorIds = s.vendors.map((v) => v.id).join(" | ");
1799
+ 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));
1800
+ 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" : "3.47 / flutter-3.47.5"}`).option("--vendor <id>", "restrict matching to a vendor").action((v, o) => useCommand(type, v, o));
1801
+ 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));
1802
+ cmd.command("current").description(`show the current ${s.label}`).action(() => currentCommand([type]));
1803
+ 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" : "3.47 / flutter-3.47.5"}`).option("--vendor <id>", "restrict matching to a vendor").action((v, o) => uninstallCommand(type, v, o));
1804
+ cmd.command("mirror").description("show or set the download mirror").argument("[action]", "show | set | unset").argument("[vendor]", "vendor id").argument("[url]", "mirror root URL").action(
1805
+ (a, v, u) => mirrorCommand(type, a, v, u)
1806
+ );
1807
+ }
1808
+ registerSdkCommands(program, "java");
1809
+ var javaCmd = program.command("java").description("Java (JDK) subcommands (same as the bare commands)");
1810
+ registerSdkCommands(javaCmd, "java");
1811
+ javaCmd.action(() => javaCmd.help());
1812
+ var goCmd = program.command("go").description("Go toolchain subcommands");
1813
+ registerSdkCommands(goCmd, "go");
1814
+ goCmd.action(() => goCmd.help());
1815
+ var flutterCmd = program.command("flutter").description("Flutter SDK subcommands");
1816
+ registerSdkCommands(flutterCmd, "flutter");
1817
+ flutterCmd.action(() => flutterCmd.help());
1818
+ program.commands.find((c) => c.name() === "current")?.action(() => currentCommand());
1819
+ program.command("version").description("print sdkvm CLI version").action(versionCommand);
1820
+ program.hook("preAction", async () => {
1821
+ await migrateLegacyHomeIfNeeded();
1822
+ });
1823
+ program.parseAsync(process.argv).catch((err) => {
1824
+ const e = toSdkvmError(err);
1825
+ log.error(e.message);
1826
+ if (e.hint) log.info(e.hint);
1827
+ process.exitCode = e.exitCode;
1828
+ });