cicy-desktop 2.1.335 → 2.1.336
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/app-updater.js
CHANGED
|
@@ -340,11 +340,40 @@ async function downloadUpdate() {
|
|
|
340
340
|
|
|
341
341
|
// 用户点「安装」:拉起原生安装器(win NSIS / mac pkg)并退出 app;linux AppImage 在
|
|
342
342
|
// 文件管理器里定位(AppImage 非安装器,用户自行替换运行)。
|
|
343
|
+
// Windows install + relaunch, as ONE detached chain.
|
|
344
|
+
//
|
|
345
|
+
// shell.openPath() ran the NSIS installer INTERACTIVELY: on a machine with
|
|
346
|
+
// nobody at the keyboard that is a wizard waiting forever for a click, and even
|
|
347
|
+
// when it did install, nothing started the app again — NSIS runAfterFinish does
|
|
348
|
+
// not fire on a /S install. Both together are why an unattended node that took
|
|
349
|
+
// an update simply never came back; it had to be started by hand, and a headless
|
|
350
|
+
// box has no hand. Observed on most of the fleet.
|
|
351
|
+
//
|
|
352
|
+
// So: /S for the install, and we own the relaunch. The chain is detached and in
|
|
353
|
+
// its own process group, so it outlives the app the installer is about to kill;
|
|
354
|
+
// cmd waits for the installer (no `start` on that leg), then gives Windows a
|
|
355
|
+
// moment to release the files before launching the new exe hidden.
|
|
356
|
+
function installWindows(installer) {
|
|
357
|
+
const { spawn } = require("child_process");
|
|
358
|
+
const exe = process.execPath;
|
|
359
|
+
const chain = `"${installer}" /S & timeout /t 20 /nobreak >nul & start "" "${exe}" --hidden`;
|
|
360
|
+
const ch = spawn(process.env.COMSPEC || "cmd.exe", ["/c", chain], {
|
|
361
|
+
detached: true,
|
|
362
|
+
stdio: "ignore",
|
|
363
|
+
windowsHide: true,
|
|
364
|
+
});
|
|
365
|
+
ch.unref();
|
|
366
|
+
log.info(`[app-updater] silent install + relaunch chain started (pid ${ch.pid})`);
|
|
367
|
+
// Quit so the installer can replace the files it is about to overwrite.
|
|
368
|
+
setTimeout(() => { try { app.quit(); } catch {} }, 1500);
|
|
369
|
+
}
|
|
370
|
+
|
|
343
371
|
function installNow() {
|
|
344
372
|
const f = _state.filePath;
|
|
345
373
|
if (!f) return;
|
|
346
374
|
try {
|
|
347
375
|
if (process.platform === "linux") { shell.showItemInFolder(f); return; }
|
|
376
|
+
if (process.platform === "win32") { installWindows(f); return; }
|
|
348
377
|
shell.openPath(f).then((err) => {
|
|
349
378
|
if (err) log.warn("[app-updater] openPath failed:", err);
|
|
350
379
|
else setTimeout(() => { try { app.quit(); } catch {} }, 800);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Copyright 2026 CiCy AI
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// installNow() used shell.openPath() on the NSIS installer. On a machine with
|
|
5
|
+
// nobody at the keyboard that is an interactive wizard waiting forever for a
|
|
6
|
+
// click; and even when it did install, nothing started the app again, because
|
|
7
|
+
// NSIS runAfterFinish does not fire on a /S install. Both together are why an
|
|
8
|
+
// unattended node that took an update never came back — observed across most of
|
|
9
|
+
// the fleet, each box needing a manual start.
|
|
10
|
+
const test = require("node:test");
|
|
11
|
+
const assert = require("node:assert/strict");
|
|
12
|
+
const fs = require("node:fs");
|
|
13
|
+
const path = require("node:path");
|
|
14
|
+
|
|
15
|
+
const src = fs.readFileSync(path.join(__dirname, "..", "src", "app-updater.js"), "utf8");
|
|
16
|
+
const win = src.slice(src.indexOf("function installWindows"), src.indexOf("function installNow"));
|
|
17
|
+
|
|
18
|
+
test("windows installs silently instead of opening a wizard nobody can click", () => {
|
|
19
|
+
assert.match(win, /"\$\{installer\}" \/S/);
|
|
20
|
+
const now = src.slice(src.indexOf("function installNow"), src.indexOf("module.exports"));
|
|
21
|
+
assert.match(now, /if \(process\.platform === "win32"\) \{ installWindows\(f\); return; \}/);
|
|
22
|
+
// the interactive path must no longer be what Windows takes
|
|
23
|
+
assert.ok(
|
|
24
|
+
now.indexOf('process.platform === "win32"') < now.indexOf("shell.openPath"),
|
|
25
|
+
"win32 must be handled before the openPath fallback"
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("the relaunch is part of the same detached chain, so it outlives the app", () => {
|
|
30
|
+
// The installer kills this process; a child in this process group would die
|
|
31
|
+
// with it and the machine would stay down.
|
|
32
|
+
assert.match(win, /detached: true/);
|
|
33
|
+
assert.match(win, /ch\.unref\(\)/);
|
|
34
|
+
assert.match(win, /start "" "\$\{exe\}" --hidden/);
|
|
35
|
+
assert.match(win, /windowsHide: true/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("the chain waits for the installer before relaunching", () => {
|
|
39
|
+
// No `start` on the installer leg → cmd blocks on it; then a pause so Windows
|
|
40
|
+
// releases the replaced files before the new exe runs.
|
|
41
|
+
assert.doesNotMatch(win, /start "" "\$\{installer\}"/);
|
|
42
|
+
assert.match(win, /timeout \/t 20 \/nobreak/);
|
|
43
|
+
const order = [win.indexOf("/S"), win.indexOf("timeout /t"), win.indexOf('start "" "${exe}"')];
|
|
44
|
+
assert.ok(order[0] < order[1] && order[1] < order[2], "install → wait → relaunch, in that order");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("the app quits so the installer can replace its files", () => {
|
|
48
|
+
assert.match(win, /app\.quit\(\)/);
|
|
49
|
+
assert.match(win, /setTimeout\(\(\) => \{ try \{ app\.quit\(\); \} catch \{\} \}, 1500\)/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("mac and linux keep their existing behaviour", () => {
|
|
53
|
+
const now = src.slice(src.indexOf("function installNow"), src.indexOf("module.exports"));
|
|
54
|
+
assert.match(
|
|
55
|
+
now,
|
|
56
|
+
/if \(process\.platform === "linux"\) \{ shell\.showItemInFolder\(f\); return; \}/
|
|
57
|
+
);
|
|
58
|
+
assert.match(now, /shell\.openPath\(f\)/); // mac pkg still opens the GUI installer
|
|
59
|
+
});
|