cicy-desktop 2.1.290 → 2.1.292
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
package/src/sidecar/docker.js
CHANGED
|
@@ -629,20 +629,21 @@ async function wslMissing() {
|
|
|
629
629
|
});
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
// True iff a Windows optional feature
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
//
|
|
632
|
+
// True iff a Windows optional feature is enabled. Primary source: WMI
|
|
633
|
+
// Win32_OptionalFeature (InstallState 1=Enabled, 2=Disabled) — readable WITHOUT
|
|
634
|
+
// elevation, unlike `dism /get-featureinfo`, which prints nothing at medium
|
|
635
|
+
// integrity (the desktop normally runs unelevated even for admin accounts) and
|
|
636
|
+
// was being misread as "disabled". dism stays as the fallback when WMI is unavailable.
|
|
636
637
|
function featureEnabled(feature) {
|
|
637
638
|
return new Promise((resolve) => {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
639
|
+
const ps = `(Get-CimInstance Win32_OptionalFeature -Filter "Name='${feature}'").InstallState`;
|
|
640
|
+
execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", ps], { timeout: 30000, windowsHide: true }, (err, out) => {
|
|
641
|
+
const v = String(out || "").trim();
|
|
642
|
+
if (!err && /^[0-9]+$/.test(v)) return resolve(v === "1");
|
|
643
|
+
execFile("dism", ["/english", "/online", "/get-featureinfo", `/featurename:${feature}`],
|
|
644
|
+
{ timeout: 30000, windowsHide: true },
|
|
645
|
+
(_e, out2) => resolve(/State\s*:\s*Enabled/i.test(String(out2 || ""))));
|
|
646
|
+
});
|
|
646
647
|
});
|
|
647
648
|
}
|
|
648
649
|
|
|
@@ -240,8 +240,20 @@ function importTarball(dest, installDir) {
|
|
|
240
240
|
// `wsl` call then blocks and the app goes 未响应). Bound it short so we FAIL FAST
|
|
241
241
|
// and the caller can `wsl --shutdown` + retry instead of hanging 10 minutes.
|
|
242
242
|
execFile("wsl", ["--import", DISTRO, installDir, dest, "--version", "2"],
|
|
243
|
-
{ timeout: 240000, windowsHide: true },
|
|
244
|
-
(err, _so, se) => {
|
|
243
|
+
{ timeout: 240000, windowsHide: true, encoding: "buffer" },
|
|
244
|
+
(err, _so, se) => {
|
|
245
|
+
if (err) {
|
|
246
|
+
// wsl.exe 的报错是 UTF-16 输出(常见:「请启用'虚拟机平台'Windows 功能并确保在 BIOS 中
|
|
247
|
+
// 启用虚拟化」);解码后拼进 message,抽屉才能显示真正原因而不是「Command failed」。
|
|
248
|
+
let text = "";
|
|
249
|
+
try { text = Buffer.isBuffer(se) ? se.toString("utf16le") : String(se || ""); } catch {}
|
|
250
|
+
text = text.replace(/\u0000/g, "").replace(/\s+/g, " ").trim();
|
|
251
|
+
if (text) err.message = `${err.message.split("\n")[0]} — ${text.slice(0, 300)}`;
|
|
252
|
+
err.stderr = text;
|
|
253
|
+
return reject(err);
|
|
254
|
+
}
|
|
255
|
+
resolve();
|
|
256
|
+
});
|
|
245
257
|
});
|
|
246
258
|
}
|
|
247
259
|
|
|
@@ -1081,6 +1093,13 @@ async function status(port = 8008) {
|
|
|
1081
1093
|
// downloads and exit-100. A second caller just attaches to the in-flight run.
|
|
1082
1094
|
let _bootstrapInFlight = null;
|
|
1083
1095
|
let _bootstrapBeat = 0; // 上次进度事件时间 —— 心跳,用于判定 in-flight 是否卡死
|
|
1096
|
+
// 所有跟随同一次安装的进度订阅者 + 最近事件缓存:守护循环先起的安装没有 UI,用户随后点
|
|
1097
|
+
// 「安装」打开抽屉时,把已发生的步骤回放给它,之后的事件也同时推给它,而不是只有一句
|
|
1098
|
+
// 「正在跟随同一进度…」。
|
|
1099
|
+
const _bootstrapListeners = new Set();
|
|
1100
|
+
let _bootstrapRecent = [];
|
|
1101
|
+
let _bootstrapT0 = 0;
|
|
1102
|
+
const BOOTSTRAP_RECENT_MAX = 60;
|
|
1084
1103
|
// 没有任何进度事件超过这个时长 = wedged(`wsl --import` 静默最久 ~1-4 分钟,留足余量)。
|
|
1085
1104
|
const BOOTSTRAP_STALL_MS = 6 * 60 * 1000;
|
|
1086
1105
|
|
|
@@ -1092,7 +1111,11 @@ async function bootstrap(opts = {}) {
|
|
|
1092
1111
|
// 丢弃它、重起一次;否则正常并发跟随同一进度。
|
|
1093
1112
|
const stalled = Date.now() - _bootstrapBeat > BOOTSTRAP_STALL_MS;
|
|
1094
1113
|
if (!stalled) {
|
|
1095
|
-
|
|
1114
|
+
if (opts.onProgress) {
|
|
1115
|
+
try { opts.onProgress({ phase: "install-docker", status: "running", message: `安装已在进行中(${Math.round((Date.now() - _bootstrapT0) / 1000)}s 前开始),正在跟随同一进度…` }); } catch {}
|
|
1116
|
+
for (const ev of _bootstrapRecent) { try { opts.onProgress(ev); } catch {} }
|
|
1117
|
+
_bootstrapListeners.add(opts.onProgress);
|
|
1118
|
+
}
|
|
1096
1119
|
return _bootstrapInFlight;
|
|
1097
1120
|
}
|
|
1098
1121
|
log.warn(`[bootstrap] in-flight 卡死(${Math.round((Date.now() - _bootstrapBeat) / 1000)}s 无进度)→ 丢弃僵死的,重起一次`);
|
|
@@ -1101,9 +1124,16 @@ async function bootstrap(opts = {}) {
|
|
|
1101
1124
|
}
|
|
1102
1125
|
// 包一层 onProgress:每来一个事件就刷新心跳,卡死检测才准。
|
|
1103
1126
|
_bootstrapBeat = Date.now();
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1127
|
+
_bootstrapT0 = Date.now();
|
|
1128
|
+
_bootstrapListeners.clear();
|
|
1129
|
+
_bootstrapRecent = [];
|
|
1130
|
+
if (opts.onProgress) _bootstrapListeners.add(opts.onProgress);
|
|
1131
|
+
const wrapped = { ...opts, onProgress: (ev) => {
|
|
1132
|
+
_bootstrapBeat = Date.now();
|
|
1133
|
+
_bootstrapRecent.push(ev); if (_bootstrapRecent.length > BOOTSTRAP_RECENT_MAX) _bootstrapRecent.shift();
|
|
1134
|
+
for (const fn of _bootstrapListeners) { try { fn(ev); } catch {} }
|
|
1135
|
+
} };
|
|
1136
|
+
_bootstrapInFlight = _bootstrap(wrapped).finally(() => { _bootstrapInFlight = null; _bootstrapListeners.clear(); });
|
|
1107
1137
|
return _bootstrapInFlight;
|
|
1108
1138
|
}
|
|
1109
1139
|
|
|
@@ -19,10 +19,16 @@ test("bootstrap failures land in lastError and pause the auto-bootstrap loop unt
|
|
|
19
19
|
assert.match(src, /logFile: _logFile/);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
test("featureEnabled
|
|
22
|
+
test("featureEnabled reads Win32_OptionalFeature (works unelevated), dism only as fallback", () => {
|
|
23
23
|
const src = read("src/sidecar/docker.js");
|
|
24
|
-
assert.match(src, /
|
|
25
|
-
assert.match(src, /
|
|
24
|
+
assert.match(src, /Get-CimInstance Win32_OptionalFeature -Filter "Name='\$\{feature\}'"\)\.InstallState/);
|
|
25
|
+
assert.match(src, /resolve\(v === "1"\)/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("wsl --import failures carry wsl.exe's decoded message into the drawer", () => {
|
|
29
|
+
const src = read("src/sidecar/wsl-docker.js");
|
|
30
|
+
assert.match(src, /encoding: "buffer"/);
|
|
31
|
+
assert.match(src, /se\.toString\("utf16le"\)/);
|
|
26
32
|
});
|
|
27
33
|
|
|
28
34
|
test("keepalive logon task degrades to LeastPrivilege / HKCU Run when not elevated", () => {
|
|
@@ -30,3 +36,9 @@ test("keepalive logon task degrades to LeastPrivilege / HKCU Run when not elevat
|
|
|
30
36
|
assert.match(src, /writeKeepaliveFiles\(\{ runLevel: "LeastPrivilege" \}\)/);
|
|
31
37
|
assert.match(src, /CurrentVersion\\\\Run/);
|
|
32
38
|
});
|
|
39
|
+
|
|
40
|
+
test("a second bootstrap caller gets the in-flight run's progress (replay + live)", () => {
|
|
41
|
+
const src = read("src/sidecar/wsl-docker.js");
|
|
42
|
+
assert.match(src, /for \(const ev of _bootstrapRecent\) \{ try \{ opts\.onProgress\(ev\); \} catch \{\} \}\n\s+_bootstrapListeners\.add\(opts\.onProgress\);/);
|
|
43
|
+
assert.match(src, /for \(const fn of _bootstrapListeners\) \{ try \{ fn\(ev\); \} catch \{\} \}/);
|
|
44
|
+
});
|