cicy-desktop 2.1.91 → 2.1.93

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/bin/cicy-desktop CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const { spawn, execSync } = require("child_process");
2
+ const { spawn, execSync, execFileSync } = require("child_process");
3
3
  const path = require("path");
4
4
  const fs = require("fs");
5
5
  const os = require("os");
@@ -13,10 +13,13 @@ const GLOBAL_CONFIG_FILE = path.join(HOME, "global.json");
13
13
  const STATE_FILE = path.join(HOME, ".cicy-cluster.json");
14
14
  const LOGS_DIR = path.join(HOME, "logs");
15
15
  const PACKAGE_ROOT = path.join(__dirname, "..");
16
- // CN defaults: a fresh machine has no cached electron binary, so npx's electron
17
- // postinstall would hit GitHub releases and fail (ECOMPROMISED). Mirror it +
18
- // the npm registry to npmmirror (overridable via the same-named env vars).
19
- const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://npmmirror.com/mirrors/electron/";
16
+ // CN/global defaults: a fresh machine has no cached electron binary, so the
17
+ // electron postinstall (and our own self-provisioning below) would otherwise
18
+ // hit GitHub releases, which is slow/blocked on many networks. Default to our
19
+ // own Cloudflare R2 mirror — the three platform zips we ship are mirrored at
20
+ // <ELECTRON_MIRROR>v<version>/electron-v<version>-<platform>.zip (+ SHASUMS256.txt),
21
+ // the exact layout @electron/get expects. Overridable via the env var.
22
+ const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://r2.deepfetch.de5.net/electron/";
20
23
  const NPM_REGISTRY = process.env.CICY_NPM_REGISTRY || process.env.npm_config_registry || "https://registry.npmmirror.com";
21
24
  const MASTER_ENTRY = path.join(PACKAGE_ROOT, "src", "master", "master-main.js");
22
25
 
@@ -73,32 +76,70 @@ function globalElectronDir() {
73
76
  } catch { return null; }
74
77
  }
75
78
 
76
- // Make sure a USABLE electron exists before spawning the worker. Prefer a shared
77
- // GLOBAL electron (downloaded once, survives npx-cache eviction, and
78
- // resolveElectronSpawn() picks it first). If nothing healthy is found anywhere,
79
- // auto-install the PINNED version GLOBALLY with the npmmirror mirror i.e. do the
80
- // `npm i -g electron@<pin>` step for the user, so a fresh `npx cicy-desktop`
81
- // self-provisions electron and starts first-try. Runs once per process.
79
+ // The per-platform Electron artifact (mirror zip name + the in-dist executable
80
+ // that path.txt must point at).
81
+ function electronArtifact() {
82
+ const plat = process.platform === "win32" ? "win32" : process.platform === "darwin" ? "darwin" : "linux";
83
+ const arch = process.arch === "arm64" ? "arm64" : "x64";
84
+ return {
85
+ zip: `electron-v${ELECTRON_VERSION}-${plat}-${arch}.zip`,
86
+ exe: process.platform === "win32" ? "electron.exe"
87
+ : process.platform === "darwin" ? "Electron.app/Contents/MacOS/Electron"
88
+ : "electron",
89
+ };
90
+ }
91
+
92
+ // Provision the Electron binary INTO the bundled npx copy ourselves: download the
93
+ // platform zip from the mirror (R2) with curl, extract with the OS's own unzip,
94
+ // then write path.txt. We deliberately do NOT use electron's install.js /
95
+ // @electron/get: on Windows its bundled extract-zip only PARTIALLY unpacks
96
+ // electron 41 (leaves no path.txt → "Electron failed to install correctly") even
97
+ // though the downloaded zip is valid. curl + Expand-Archive/unzip is reliable,
98
+ // mirror-only (no GitHub), and writes a complete dist/.
99
+ function provisionElectronFromMirror(bundledDir) {
100
+ const { zip, exe } = electronArtifact();
101
+ const url = `${ELECTRON_MIRROR}v${ELECTRON_VERSION}/${zip}`;
102
+ const tmpZip = path.join(os.tmpdir(), `cicy-${zip}`);
103
+ const curl = isWindows ? "curl.exe" : "curl";
104
+ execFileSync(curl, ["-fL", "--retry", "5", "--retry-delay", "2", "--connect-timeout", "20", "-o", tmpZip, url], { stdio: "inherit" });
105
+ const distDir = path.join(bundledDir, "dist");
106
+ fs.rmSync(distDir, { recursive: true, force: true });
107
+ fs.mkdirSync(distDir, { recursive: true });
108
+ if (isWindows) {
109
+ execFileSync("powershell", ["-NoProfile", "-Command", `Expand-Archive -LiteralPath "${tmpZip}" -DestinationPath "${distDir}" -Force`], { stdio: "inherit" });
110
+ } else {
111
+ execFileSync("unzip", ["-o", "-q", tmpZip, "-d", distDir], { stdio: "inherit" });
112
+ try { fs.chmodSync(path.join(distDir, exe), 0o755); } catch {}
113
+ }
114
+ fs.writeFileSync(path.join(bundledDir, "path.txt"), exe);
115
+ try { fs.rmSync(tmpZip, { force: true }); } catch {}
116
+ }
117
+
118
+ // Make sure a USABLE electron exists before spawning the worker. A pre-existing
119
+ // GLOBAL electron is reused if present. Otherwise we provision the binary LOCALLY
120
+ // into the bundled npx copy straight from the R2 mirror — NO `npm i -g`, NO
121
+ // GitHub, NO @electron/get. This is what lets a single `npx cicy-desktop`
122
+ // self-provision electron and start first-try. Runs once per process.
82
123
  let _electronEnsured = false;
83
124
  function ensureElectron() {
84
125
  if (_electronEnsured) return;
85
126
  _electronEnsured = true;
86
- // Already usable (global, or the resolvable bundled/npx copy)? nothing to do.
127
+ // Already usable (a shared global, or the resolvable bundled/npx copy)? done.
87
128
  if (electronBinaryHealthy(globalElectronDir())) return;
88
129
  let bundledDir = null;
89
130
  try { bundledDir = path.dirname(require.resolve("electron/package.json", { paths: [PACKAGE_ROOT] })); } catch {}
90
131
  if (electronBinaryHealthy(bundledDir)) return;
91
- // None usable → install the pinned electron globally (with the mirror).
92
- console.log(`⚙️ No usable Electron installing electron@${ELECTRON_VERSION} -g (mirror), one-time…`);
132
+ if (!bundledDir) {
133
+ console.warn(`⚠️ electron package not resolvable under ${PACKAGE_ROOT}; cannot self-provision.`);
134
+ return;
135
+ }
136
+ console.log(`⚙️ Fetching Electron ${ELECTRON_VERSION} from mirror (one-time)…`);
93
137
  try {
94
- execSync(`npm i -g electron@${ELECTRON_VERSION} --registry=${NPM_REGISTRY}`, {
95
- stdio: "inherit",
96
- env: { ...process.env, ELECTRON_MIRROR, npm_config_registry: NPM_REGISTRY },
97
- });
98
- _globalElectronBin = undefined; // bust the memo so resolveElectronSpawn re-finds the new global bin
138
+ provisionElectronFromMirror(bundledDir);
139
+ if (electronBinaryHealthy(bundledDir)) return;
140
+ console.warn(`⚠️ Electron provisioned but health check still failing.`);
99
141
  } catch (e) {
100
- console.warn(`⚠️ Global electron install failed: ${e.message}`);
101
- console.warn(` Fix manually: set ELECTRON_MIRROR=${ELECTRON_MIRROR} && npm i -g electron@${ELECTRON_VERSION}`);
142
+ console.warn(`⚠️ Electron provision from mirror failed: ${e.message}`);
102
143
  }
103
144
  }
104
145
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.91",
3
+ "version": "2.1.93",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -128,16 +128,16 @@
128
128
  "swagger-jsdoc": "^6.2.8",
129
129
  "swagger-ui-express": "^5.0.1",
130
130
  "zod": "3.25",
131
- "electron-updater": "6.6.2",
132
- "electron": "41.0.3"
131
+ "electron-updater": "6.6.2"
133
132
  },
134
133
  "//optionalDependencies": "Runtime Bundle v1 (主人指令): platform binaries delivered by `npm i -g cicy-desktop` itself — npm installs only the current-platform subpackage (os/cpu pinned in each), so first start seeds the runtime store with ZERO network, ZERO npx. Windows packages are named *-windows-* (npm spam filter 403s new names containing win32). cicy-msys2 added once published.",
135
134
  "optionalDependencies": {
136
- "cicy-code-darwin-x64": "2.3.11",
137
- "cicy-code-darwin-arm64": "2.3.11",
138
- "cicy-code-linux-x64": "2.3.11",
139
- "cicy-code-linux-arm64": "2.3.11",
140
- "cicy-code-windows-x64": "2.3.11",
135
+ "electron": "41.0.3",
136
+ "cicy-code-darwin-x64": "2.3.12",
137
+ "cicy-code-darwin-arm64": "2.3.12",
138
+ "cicy-code-linux-x64": "2.3.12",
139
+ "cicy-code-linux-arm64": "2.3.12",
140
+ "cicy-code-windows-x64": "2.3.12",
141
141
  "cicy-mihomo-darwin-x64": "1.10.4",
142
142
  "cicy-mihomo-darwin-arm64": "1.10.4",
143
143
  "cicy-mihomo-linux-x64": "1.10.4",
@@ -149,6 +149,7 @@
149
149
  "@babel/core": "^7.29.0",
150
150
  "@babel/preset-env": "^7.29.0",
151
151
  "axios": "^1.13.5",
152
+ "electron": "41.0.3",
152
153
  "electron-builder": "^26.7.0",
153
154
  "jest": "^29.7.0",
154
155
  "prettier": "^3.8.1",
@@ -64,12 +64,26 @@ async function execShell(command, opts = {}) {
64
64
 
65
65
  contextBridge.exposeInMainWorld("electronRPC", (tool, args) => rpc(tool, args));
66
66
 
67
- // i18n: expose t(key, opts) and current locale to render. Resources are
68
- // loaded once in the main process and shipped synchronously over IPC. Since
69
- // preload runs in the main process for context-isolated windows, we can
70
- // require the i18n module directly.
67
+ // i18n: expose t(key, opts) and current locale to render.
68
+ //
69
+ // IMPORTANT: this preload runs in the RENDERER process, so `require("../i18n")`
70
+ // gives a SEPARATE i18n instance from the main process — it has no idea what
71
+ // locale the app resolved. Left to its own lazy init() it would fall back to
72
+ // English (pickLocale(undefined) → FALLBACK), which is exactly why the homepage
73
+ // (e.g. the first-run terms gate) showed English even on zh-CN systems. So we
74
+ // pull the resolved locale from main over a synchronous IPC and init THIS
75
+ // instance with it. i18next.init() with inline resources is synchronous, so the
76
+ // language is set immediately and `locale` below reads the correct value.
71
77
  let __i18n;
72
- try { __i18n = require("../i18n"); } catch (e) { __i18n = null; }
78
+ try {
79
+ __i18n = require("../i18n");
80
+ let mainLng = "";
81
+ try { mainLng = ipcRenderer.sendSync("i18n:locale"); } catch (_) {}
82
+ __i18n.init(mainLng || undefined);
83
+ if (mainLng && __i18n.i18next.language !== __i18n.pickLocale(mainLng)) {
84
+ __i18n.i18next.changeLanguage(__i18n.pickLocale(mainLng));
85
+ }
86
+ } catch (e) { __i18n = null; }
73
87
  contextBridge.exposeInMainWorld("cicyI18n", {
74
88
  t: (key, opts) => {
75
89
  if (!__i18n) return key;
package/src/main.js CHANGED
@@ -81,6 +81,18 @@ const __initialLocale = (() => {
81
81
  })();
82
82
  i18n.init(__initialLocale);
83
83
 
84
+ // Synchronous bridge so the homepage preload — which runs in the RENDERER
85
+ // process and therefore holds its OWN separate i18n module instance — can
86
+ // resolve the same locale the main process picked. Without this the preload's
87
+ // lazy init() falls back to English regardless of OS language, so
88
+ // window.cicyI18n.t() returned English even on zh-CN systems. Read live at call
89
+ // time, so it reflects the ready-time changeLanguage() below.
90
+ try {
91
+ require("electron").ipcMain.on("i18n:locale", (e) => {
92
+ e.returnValue = (i18n.i18next && i18n.i18next.language) || i18n.FALLBACK || "en";
93
+ });
94
+ } catch {}
95
+
84
96
  // Single-instance lock: only one cicy-desktop process can hold the primary
85
97
  // instance. A second launch sends `second-instance` with argv to the primary
86
98
  // and exits itself. The primary focuses its homepage so the user sees the