cicy-desktop 2.1.107 → 2.1.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.107",
3
+ "version": "2.1.108",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -504,15 +504,51 @@ function wslMissing() {
504
504
  }
505
505
  }
506
506
 
507
+ // Read-only, works without elevation. True iff a Windows optional feature
508
+ // reports State : Enabled.
509
+ function featureEnabled(feature) {
510
+ return new Promise((resolve) => {
511
+ execFile("dism", ["/english", "/online", "/get-featureinfo", `/featurename:${feature}`],
512
+ { timeout: 30000, windowsHide: true },
513
+ (_e, out) => resolve(/State\s*:\s*Enabled/i.test(String(out || ""))));
514
+ });
515
+ }
516
+
517
+ // Enable ONE Windows optional feature, elevated, and WAIT until it actually
518
+ // reports Enabled. The old `wsl --install` was fire-and-forget — it could
519
+ // silently no-op, leaving the card stuck on "reboot required" forever. Here we
520
+ // drive DISM and verify, streaming every step to the install drawer.
521
+ async function dismEnableFeature(feature, label, { emit } = {}) {
522
+ if (await featureEnabled(feature)) {
523
+ emit && emit({ phase: "install-docker", status: "running", message: `${label}:已启用,跳过` });
524
+ return true;
525
+ }
526
+ emit && emit({ phase: "install-docker", status: "running", message: `${label}:正在启用(约 1–2 分钟,请稍候)…` });
527
+ await launchElevated("dism", ["/online", "/enable-feature", `/featurename:${feature}`, "/all", "/norestart"], { emit }).catch(() => {});
528
+ // launchElevated fires the elevated task and returns immediately; poll the
529
+ // real feature state (DISM exits 3010 = success + reboot-required).
530
+ const ok = await waitUntil(() => featureEnabled(feature), { totalMs: 240000, everyMs: 5000 });
531
+ emit && emit({ phase: "install-docker", status: ok ? "running" : "error", message: ok ? `${label}:已启用 ✓` : `${label}:未能确认启用(点「重试」)` });
532
+ return ok;
533
+ }
534
+
507
535
  // Ensure the WSL2 backend exists; install it (elevated) if missing. Returns
508
- // { ok } when present, or { needsReboot } after kicking off `wsl --install`
509
- // (which requires admin + a Windows reboot before Docker can use it).
536
+ // { ok } when already present, { needsReboot } after the two required Windows
537
+ // features are verified-enabled (a Windows reboot is then needed before Docker
538
+ // can use WSL2), or { failed } if a feature couldn't be enabled.
510
539
  async function ensureWsl({ emit } = {}) {
511
540
  if (!wslMissing()) return { ok: true };
512
- emit && emit({ phase: "install-docker", status: "running", message: "Docker 需要 WSL2 后端,正在安装 WSL(请在管理员授权框点「是」,装完需重启 Windows)…" });
513
- // --no-distribution: just the WSL2 platform (Docker brings its own distro);
514
- // falls back to plain `wsl --install` on older builds that reject the flag.
515
- await launchElevated("wsl", ["--install", "--no-distribution"], { emit });
541
+ emit && emit({ phase: "install-docker", status: "running", message: "Docker 需要 WSL2 后端,开始启用所需的 Windows 功能…" });
542
+ const a = await dismEnableFeature("Microsoft-Windows-Subsystem-Linux", "启用 WSL 功能 1/2 · Linux 子系统", { emit });
543
+ const b = await dismEnableFeature("VirtualMachinePlatform", "启用 WSL 功能 2/2 · 虚拟机平台", { emit });
544
+ if (!a || !b) {
545
+ emit && emit({ phase: "done", status: "error", message: "WSL 功能未能全部启用——请点「重试」" });
546
+ return { ok: false, needsReboot: false, failed: true };
547
+ }
548
+ // Best-effort: also pull the WSL2 kernel/plumbing (no-op until reboot on some
549
+ // builds; harmless if it errors).
550
+ await launchElevated("wsl", ["--install", "--no-distribution"], { emit }).catch(() => {});
551
+ emit && emit({ phase: "install-docker", status: "running", message: "WSL2 功能已启用 ✓,需【重启 Windows】后回来点「重试」继续。" });
516
552
  return { ok: false, needsReboot: true };
517
553
  }
518
554
 
@@ -11,7 +11,7 @@
11
11
  // → health-probe :8009 from Windows. Every step checks-then-acts and is
12
12
  // idempotent, so 重试 resumes.
13
13
 
14
- const { execFile, execFileSync } = require("child_process");
14
+ const { execFile, execFileSync, spawn } = require("child_process");
15
15
  const path = require("path");
16
16
  const docker = require("./docker"); // shared: downloads, waitUntil, probeHealth, launchElevated, ensureWsl…
17
17
 
@@ -32,6 +32,34 @@ function wslRun(cmd, { timeout = 60000, distro = DISTRO } = {}) {
32
32
  });
33
33
  }
34
34
 
35
+ // Like wslRun, but STREAMS each output line to the install drawer so the user
36
+ // (and the customer) watch the real install proceed — apt fetching/unpacking,
37
+ // `docker load` layers — instead of staring at a frozen spinner. Throttled so
38
+ // rapid output doesn't flood the log; resolves { stdout } with the full tail.
39
+ function wslRunStream(cmd, { emit, phase = "install-docker", timeout = 900000, distro = DISTRO } = {}) {
40
+ return new Promise((resolve, reject) => {
41
+ const child = spawn("wsl", ["-d", distro, "-u", "root", "--", "bash", "-lc", cmd], { windowsHide: true });
42
+ let buf = "", tail = "", last = 0;
43
+ const pump = (chunk) => {
44
+ buf += chunk.toString("utf8");
45
+ let nl;
46
+ while ((nl = buf.indexOf("\n")) >= 0) {
47
+ const line = buf.slice(0, nl).replace(/\r$/, "").trim();
48
+ buf = buf.slice(nl + 1);
49
+ if (!line) continue;
50
+ tail += line + "\n";
51
+ const now = Date.now();
52
+ if (emit && now - last > 350) { last = now; emit({ phase, status: "running", message: line.slice(0, 200) }); }
53
+ }
54
+ };
55
+ child.stdout.on("data", pump);
56
+ child.stderr.on("data", pump);
57
+ const timer = setTimeout(() => { try { child.kill("SIGKILL"); } catch {} reject(Object.assign(new Error("timeout"), { stdout: tail })); }, timeout);
58
+ child.on("error", (e) => { clearTimeout(timer); reject(e); });
59
+ child.on("close", (code) => { clearTimeout(timer); code === 0 ? resolve({ stdout: tail }) : reject(Object.assign(new Error(`exit ${code}`), { stdout: tail })); });
60
+ });
61
+ }
62
+
35
63
  // Is `DISTRO` registered? `wsl -l -q` lists installed distros (UTF-16LE).
36
64
  function distroInstalled(distro = DISTRO) {
37
65
  if (process.platform !== "win32") return false;
@@ -66,8 +94,8 @@ async function dockerInstalled() {
66
94
 
67
95
  // Install Docker Engine (docker.io) inside the distro.
68
96
  async function installDockerEngine({ emit } = {}) {
69
- emit && emit({ phase: "install-docker", status: "running", message: "在 Ubuntu 里安装 Docker(apt,几分钟)…" });
70
- await wslRun("apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io", { timeout: 900000 });
97
+ emit && emit({ phase: "install-docker", status: "running", message: "在 Ubuntu 里安装 Docker(apt,几分钟,下面是实时进度)…" });
98
+ await wslRunStream("apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io 2>&1", { emit, phase: "install-docker", timeout: 900000 });
71
99
  }
72
100
 
73
101
  // dockerd reachable inside the distro?
@@ -96,9 +124,9 @@ function toWslPath(winPath) {
96
124
 
97
125
  // docker load the (Windows-side) tarball into the distro's Docker + retag.
98
126
  async function loadImage(winTarballPath, { emit } = {}) {
99
- emit && emit({ phase: "image", status: "loading", message: "正在导入镜像到 Docker(较大,约 1-3 分钟,请稍候)…" });
127
+ emit && emit({ phase: "image", status: "loading", message: "正在导入镜像到 Docker(较大,约 1-3 分钟,下面是实时进度)…" });
100
128
  const p = toWslPath(winTarballPath);
101
- const { stdout } = await wslRun(`docker load -i "${p}"`, { timeout: 600000 });
129
+ const { stdout } = await wslRunStream(`docker load -i "${p}"`, { emit, phase: "image", timeout: 600000 });
102
130
  const m = String(stdout).match(/Loaded image:\s*(\S+)/i);
103
131
  if (m && m[1] !== IMAGE) { try { await wslRun(`docker tag ${m[1]} ${IMAGE}`, { timeout: 15000 }); } catch {} }
104
132
  }
@@ -155,7 +183,8 @@ async function bootstrap({ onProgress, port = 8009, container = "cicy-code-docke
155
183
  // 2) Ubuntu distro
156
184
  if (!distroInstalled()) {
157
185
  try { await installDistro({ emit }); } catch (e) { emit({ phase: "done", status: "error", message: `Ubuntu 安装失败:${e.message}(点重试)` }); return { ok: false, reason: "distro_install_failed" }; }
158
- const ok = await docker.waitUntil(() => distroInstalled(), { totalMs: 600000, everyMs: 5000 });
186
+ const t0 = Date.now();
187
+ const ok = await docker.waitUntil(() => distroInstalled(), { totalMs: 600000, everyMs: 5000, onTick: () => emit({ phase: "install-docker", status: "running", message: `正在下载/注册 Ubuntu…(已 ${Math.round((Date.now() - t0) / 1000)}s,首次较慢请耐心)` }) });
159
188
  if (!ok) { emit({ phase: "done", status: "error", message: "Ubuntu 还没装好——稍等或点「重试」" }); return { ok: false, reason: "distro_not_ready" }; }
160
189
  }
161
190