@zenbujs/core 0.0.5 → 0.0.8

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 (72) hide show
  1. package/dist/{advice-config-QYB2qEd_.mjs → advice-config-DXSIo0sg.mjs} +40 -39
  2. package/dist/advice.d.mts +8 -8
  3. package/dist/advice.mjs +2 -2
  4. package/dist/{base-window-BbFRRhKP.mjs → base-window-BxBZ2md_.mjs} +51 -7
  5. package/dist/{transforms-CuTODvDx.d.mts → build-config-Dzg2frpk.d.mts} +98 -28
  6. package/dist/build-config-pWdmLnrk.mjs +53 -0
  7. package/dist/{build-electron-CNJ0dLND.mjs → build-electron-Dsbb1EMl.mjs} +308 -120
  8. package/dist/{build-source-C2puqEVr.mjs → build-source-d1J3shV8.mjs} +62 -27
  9. package/dist/cli/bin.mjs +7 -7
  10. package/dist/cli/build.d.mts +2 -2
  11. package/dist/cli/build.mjs +2 -3
  12. package/dist/cli/resolve-config.mjs +1 -1
  13. package/dist/{cli-C3R1LBMY.mjs → cli-kL6mPgBE.mjs} +2 -2
  14. package/dist/config.d.mts +3 -3
  15. package/dist/config.mjs +2 -3
  16. package/dist/{db-xjvahRFJ.mjs → db-Bc292RYo.mjs} +2 -2
  17. package/dist/db.d.mts +1 -1
  18. package/dist/dev-B2emj0HZ.mjs +301 -0
  19. package/dist/env-bootstrap.d.mts +1 -1
  20. package/dist/events.d.mts +19 -0
  21. package/dist/events.mjs +1 -0
  22. package/dist/host-version-BIrF8tX7.mjs +65 -0
  23. package/dist/index-w5QyDjuf.d.mts +780 -0
  24. package/dist/index.d.mts +5 -6
  25. package/dist/index.mjs +2 -2
  26. package/dist/installing-preload.cjs +60 -0
  27. package/dist/launcher.mjs +2615 -122
  28. package/dist/{link-c0_aLWQ3.mjs → link-glX89NV5.mjs} +215 -89
  29. package/dist/{load-config-xMf2wxH8.mjs → load-config-C4Oe2qZO.mjs} +5 -1
  30. package/dist/loaders/zenbu.mjs +102 -0
  31. package/dist/node-loader.mjs +1 -1
  32. package/dist/{publish-source-Dill72NS.mjs → publish-source-Dq2c0iOw.mjs} +2 -2
  33. package/dist/react.d.mts +55 -6
  34. package/dist/react.mjs +116 -5
  35. package/dist/registry-CMp8FYgS.d.mts +47 -0
  36. package/dist/registry-generated.d.mts +26 -0
  37. package/dist/registry-generated.mjs +1 -0
  38. package/dist/registry.d.mts +2 -2
  39. package/dist/{reloader-DzEO8kJr.mjs → reloader-B22UiNA2.mjs} +2 -4
  40. package/dist/{renderer-host-Cau9JK0v.mjs → renderer-host-DD16MXhI.mjs} +152 -43
  41. package/dist/{rpc-JfGv-Wuw.mjs → rpc-C4_NQmpT.mjs} +5 -4
  42. package/dist/{runtime-pCeVzj--.d.mts → runtime-BQWntcOb.d.mts} +85 -48
  43. package/dist/runtime.d.mts +2 -2
  44. package/dist/runtime.mjs +139 -83
  45. package/dist/{schema-Dl85YjXW.d.mts → schema-CjrMVk36.d.mts} +3 -3
  46. package/dist/schema.d.mts +1 -1
  47. package/dist/schema.mjs +1 -1
  48. package/dist/{server-y3PPbh3l.mjs → server-CZLMF8Dj.mjs} +1 -3
  49. package/dist/services/default.d.mts +3 -3
  50. package/dist/services/default.mjs +14 -13
  51. package/dist/services/index.d.mts +2 -280
  52. package/dist/services/index.mjs +8 -7
  53. package/dist/setup-gate.d.mts +1 -1
  54. package/dist/setup-gate.mjs +117 -24
  55. package/dist/{transform-CmFYPmt8.mjs → transform-BzrwkEdf.mjs} +22 -916
  56. package/dist/updater-DCkz9M1c.mjs +1008 -0
  57. package/dist/{vite-plugins-Do7liKi_.mjs → vite-plugins-tt6KAtyE.mjs} +26 -25
  58. package/dist/vite.d.mts +3 -3
  59. package/dist/vite.mjs +1 -1
  60. package/dist/{window-o2NGUsIb.mjs → window-YFKvAM0l.mjs} +30 -16
  61. package/package.json +15 -2
  62. package/dist/build-config-C3a-o3_B.mjs +0 -23
  63. package/dist/dev-Dazhu66l.mjs +0 -85
  64. package/dist/registry-eX6e2oql.d.mts +0 -61
  65. package/dist/transforms-htxfTwsY.mjs +0 -47
  66. /package/dist/{config-DXRCDUxG.mjs → config-BK78JDRI.mjs} +0 -0
  67. /package/dist/{env-bootstrap-DW2hVhSO.d.mts → env-bootstrap-rTs8KR3-.d.mts} +0 -0
  68. /package/dist/{index-M_lSNBrq.d.mts → index-DeDxePAa.d.mts} +0 -0
  69. /package/dist/{mirror-sync-PDzxhf1w.mjs → mirror-sync-pYU6f3-c.mjs} +0 -0
  70. /package/dist/{monorepo-3avKJwzJ.mjs → monorepo-Dct-kkbQ.mjs} +0 -0
  71. /package/dist/{node-_8xShqxr.mjs → node-BhfLKYCi.mjs} +0 -0
  72. /package/dist/{setup-gate-Dcy8gGPJ.d.mts → setup-gate-BQq0QgZH.d.mts} +0 -0
@@ -1,4 +1,5 @@
1
- import { t as loadConfig } from "./load-config-xMf2wxH8.mjs";
1
+ import { r as writeHostVersion, t as HOST_VERSION_FILENAME } from "./host-version-BIrF8tX7.mjs";
2
+ import { t as loadConfig } from "./load-config-C4Oe2qZO.mjs";
2
3
  import { createRequire } from "node:module";
3
4
  import fs from "node:fs";
4
5
  import os from "node:os";
@@ -12,50 +13,29 @@ import { promisify } from "node:util";
12
13
  //#region src/cli/lib/toolchain.ts
13
14
  const execFileAsync = promisify(execFile);
14
15
  /**
15
- * Hardcoded bun + pnpm versions that the .app bundles for its first-launch
16
- * `pnpm install`. We download these from upstream releases on demand (cached
17
- * globally per-version), verify against pinned sha256, and stage them into
18
- * the build's `extraResources/toolchain/` directory.
16
+ * Framework-pinned bun. Used as the runtime bun (the `node`-shim that hosts
17
+ * `npm install`'s lifecycle scripts and JS-package PMs like npm/yarn) when
18
+ * the user picks a non-bun PM.
19
19
  *
20
- * Bumping a version: update the entry, paste the upstream release tarball's
21
- * sha256, and the next `zen build:electron` will re-fetch into a new cache
22
- * directory automatically.
23
- *
24
- * Currently darwin-only — Linux/Windows support is a future-PR concern.
20
+ * When the user picks bun as the PM, we provision bun at `spec.version`
21
+ * instead and skip this constant entirely. The runtime bun and the PM bun
22
+ * collapse into a single binary at the user's chosen version.
25
23
  */
26
- const TOOLCHAIN = {
27
- bun: {
28
- version: "1.3.12",
29
- releaseTag: "bun-v1.3.12",
30
- targets: {
31
- "darwin-aarch64": {
32
- asset: "bun-darwin-aarch64.zip",
33
- sha256: "6c4bb87dd013ed1a8d6a16e357a3d094959fd5530b4d7061f7f3680c3c7cea1c"
34
- },
35
- "darwin-x64": {
36
- asset: "bun-darwin-x64.zip",
37
- sha256: "0f58c53a3e7947f1e626d2f8d285f97c14b7cadcca9c09ebafc0ae9d35b58c3d"
38
- }
39
- }
40
- },
41
- pnpm: {
42
- version: "10.33.0",
43
- releaseTag: "v10.33.0",
44
- targets: {
45
- "darwin-arm64": {
46
- asset: "pnpm-macos-arm64",
47
- sha256: "ed8a1f140f4de457b01ebe0be3ae28e9a7e28863315dcd53d22ff1e5a32d63ae"
48
- },
49
- "darwin-x64": {
50
- asset: "pnpm-macos-x64",
51
- sha256: "c31e29554b0e3f4e03f4617195c949595e4dca36085922003de4896c3ca4057d"
52
- }
24
+ const PINNED_BUN = {
25
+ version: "1.3.12",
26
+ targets: {
27
+ "darwin-aarch64": {
28
+ asset: "bun-darwin-aarch64.zip",
29
+ sha256: "6c4bb87dd013ed1a8d6a16e357a3d094959fd5530b4d7061f7f3680c3c7cea1c"
30
+ },
31
+ "darwin-x64": {
32
+ asset: "bun-darwin-x64.zip",
33
+ sha256: "0f58c53a3e7947f1e626d2f8d285f97c14b7cadcca9c09ebafc0ae9d35b58c3d"
53
34
  }
54
35
  }
55
36
  };
56
- TOOLCHAIN.pnpm.version;
57
- TOOLCHAIN.bun.version;
58
- function cacheRoot() {
37
+ PINNED_BUN.version;
38
+ function defaultCacheRoot() {
59
39
  return path.join(os.homedir(), ".zenbu", "cache", "toolchain");
60
40
  }
61
41
  function bunTarget() {
@@ -63,11 +43,6 @@ function bunTarget() {
63
43
  if (process.arch === "x64") return "darwin-x64";
64
44
  throw new Error(`zenbu toolchain: unsupported architecture ${process.arch}`);
65
45
  }
66
- function pnpmTarget() {
67
- if (process.arch === "arm64") return "darwin-arm64";
68
- if (process.arch === "x64") return "darwin-x64";
69
- throw new Error(`zenbu toolchain: unsupported architecture ${process.arch}`);
70
- }
71
46
  function download(url, dest) {
72
47
  return new Promise((resolve, reject) => {
73
48
  https.get(url, (res) => {
@@ -88,6 +63,29 @@ function download(url, dest) {
88
63
  }).on("error", reject);
89
64
  });
90
65
  }
66
+ function fetchText(url) {
67
+ return new Promise((resolve, reject) => {
68
+ https.get(url, { headers: {
69
+ "User-Agent": "zenbu-toolchain",
70
+ Accept: "application/json, text/plain, */*"
71
+ } }, (res) => {
72
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
73
+ res.resume();
74
+ fetchText(new URL(res.headers.location, url).href).then(resolve, reject);
75
+ return;
76
+ }
77
+ if (res.statusCode !== 200) {
78
+ reject(/* @__PURE__ */ new Error(`GET ${url} -> ${res.statusCode}`));
79
+ res.resume();
80
+ return;
81
+ }
82
+ const chunks = [];
83
+ res.on("data", (chunk) => chunks.push(chunk));
84
+ res.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
85
+ res.on("error", reject);
86
+ }).on("error", reject);
87
+ });
88
+ }
91
89
  async function sha256(filePath) {
92
90
  const hash = crypto.createHash("sha256");
93
91
  await new Promise((resolve, reject) => {
@@ -98,9 +96,41 @@ async function sha256(filePath) {
98
96
  });
99
97
  return hash.digest("hex");
100
98
  }
101
- async function verify(filePath, expected) {
99
+ async function sha512(filePath) {
100
+ const hash = crypto.createHash("sha512");
101
+ await new Promise((resolve, reject) => {
102
+ const stream = fs.createReadStream(filePath);
103
+ stream.on("data", (chunk) => hash.update(chunk));
104
+ stream.on("end", () => resolve());
105
+ stream.on("error", reject);
106
+ });
107
+ return hash.digest();
108
+ }
109
+ async function verifySha256(filePath, expected) {
102
110
  const actual = await sha256(filePath);
103
- if (actual !== expected) throw new Error(`zenbu toolchain: sha256 mismatch for ${path.basename(filePath)} (expected ${expected}, got ${actual})`);
111
+ if (actual.toLowerCase() !== expected.toLowerCase()) throw new Error(`zenbu toolchain: sha256 mismatch for ${path.basename(filePath)} (expected ${expected}, got ${actual})`);
112
+ }
113
+ /**
114
+ * Verify a file against an SRI-format integrity string from the npm registry,
115
+ * e.g. `sha512-LB7QO/<base64>=`. We only support the algorithms npm currently
116
+ * publishes (sha512 + sha1 fallback for ancient packages).
117
+ */
118
+ async function verifyIntegrity(filePath, integrity) {
119
+ const [algo, b64] = integrity.split("-", 2);
120
+ if (!algo || !b64) throw new Error(`zenbu toolchain: malformed integrity "${integrity}"`);
121
+ let actual;
122
+ if (algo === "sha512") actual = (await sha512(filePath)).toString("base64");
123
+ else if (algo === "sha256") {
124
+ const hash = crypto.createHash("sha256");
125
+ await new Promise((resolve, reject) => {
126
+ const s = fs.createReadStream(filePath);
127
+ s.on("data", (c) => hash.update(c));
128
+ s.on("end", () => resolve());
129
+ s.on("error", reject);
130
+ });
131
+ actual = hash.digest("base64");
132
+ } else throw new Error(`zenbu toolchain: unsupported integrity algo "${algo}"`);
133
+ if (actual !== b64) throw new Error(`zenbu toolchain: ${algo} integrity mismatch for ${path.basename(filePath)}`);
104
134
  }
105
135
  async function findExecutable(dir, name) {
106
136
  const entries = await fsp.readdir(dir, { withFileTypes: true });
@@ -113,20 +143,28 @@ async function findExecutable(dir, name) {
113
143
  }
114
144
  return null;
115
145
  }
116
- async function ensureBunCached() {
146
+ async function ensureBunCached(spec, cacheRoot) {
117
147
  const target = bunTarget();
118
- const info = TOOLCHAIN.bun.targets[target];
119
- const dir = path.join(cacheRoot(), `bun-${TOOLCHAIN.bun.version}-${target}`);
148
+ const dir = path.join(cacheRoot, `bun-${spec.version}-${target}`);
120
149
  const cached = path.join(dir, "bun");
121
150
  if (fs.existsSync(cached)) return cached;
122
151
  await fsp.mkdir(dir, { recursive: true });
123
152
  const tmp = await fsp.mkdtemp(path.join(os.tmpdir(), "zenbu-bun-"));
124
153
  try {
125
- const zipPath = path.join(tmp, info.asset);
126
- const url = `https://github.com/oven-sh/bun/releases/download/${TOOLCHAIN.bun.releaseTag}/${info.asset}`;
127
- console.log(` → downloading bun ${TOOLCHAIN.bun.version} (${target})`);
154
+ const asset = `bun-${target}.zip`;
155
+ const releaseTag = `bun-v${spec.version}`;
156
+ const zipPath = path.join(tmp, asset);
157
+ const url = `https://github.com/oven-sh/bun/releases/download/${releaseTag}/${asset}`;
158
+ console.log(` → downloading bun ${spec.version} (${target})`);
128
159
  await download(url, zipPath);
129
- await verify(zipPath, info.sha256);
160
+ let expected = spec.pinnedSha256;
161
+ if (!expected) {
162
+ const sumsUrl = `https://github.com/oven-sh/bun/releases/download/${releaseTag}/SHASUMS256.txt`;
163
+ const fromFile = parseShasumsFile(await fetchText(sumsUrl), asset);
164
+ if (!fromFile) throw new Error(`zenbu toolchain: could not locate sha256 for ${asset} in ${sumsUrl}`);
165
+ expected = fromFile;
166
+ }
167
+ await verifySha256(zipPath, expected);
130
168
  await execFileAsync("unzip", [
131
169
  "-q",
132
170
  zipPath,
@@ -134,7 +172,7 @@ async function ensureBunCached() {
134
172
  tmp
135
173
  ]);
136
174
  const extracted = await findExecutable(tmp, "bun");
137
- if (!extracted) throw new Error(`zenbu toolchain: could not find bun in ${info.asset}`);
175
+ if (!extracted) throw new Error(`zenbu toolchain: could not find bun in ${asset}`);
138
176
  await fsp.copyFile(extracted, cached);
139
177
  await fsp.chmod(cached, 493);
140
178
  } finally {
@@ -145,53 +183,174 @@ async function ensureBunCached() {
145
183
  }
146
184
  return cached;
147
185
  }
148
- async function ensurePnpmCached() {
149
- const target = pnpmTarget();
150
- const info = TOOLCHAIN.pnpm.targets[target];
151
- const dir = path.join(cacheRoot(), `pnpm-${TOOLCHAIN.pnpm.version}-${target}`);
152
- const cached = path.join(dir, "pnpm");
186
+ function parseShasumsFile(contents, target) {
187
+ for (const line of contents.split("\n")) {
188
+ const trimmed = line.trim();
189
+ if (!trimmed) continue;
190
+ const [hash, name] = trimmed.split(/\s+/, 2);
191
+ if (!hash || !name) continue;
192
+ if (name === target || name === `./${target}` || name === `*${target}`) return hash;
193
+ }
194
+ return null;
195
+ }
196
+ async function fetchRegistryDist(pkg, version) {
197
+ const text = await fetchText(`https://registry.npmjs.org/${pkg}/${version}`);
198
+ const meta = JSON.parse(text);
199
+ if (!meta.dist?.tarball) throw new Error(`zenbu toolchain: registry response for ${pkg}@${version} has no dist.tarball`);
200
+ return meta.dist;
201
+ }
202
+ async function ensureNpmRegistryPackageCached(pkg, version, cacheRoot) {
203
+ const dir = path.join(cacheRoot, `${pkg}-${version}`);
204
+ const ready = path.join(dir, ".ready");
205
+ const pkgRoot = path.join(dir, "package");
206
+ if (fs.existsSync(ready) && fs.existsSync(pkgRoot)) return pkgRoot;
207
+ await fsp.rm(dir, {
208
+ recursive: true,
209
+ force: true
210
+ });
211
+ await fsp.mkdir(dir, { recursive: true });
212
+ const dist = await fetchRegistryDist(pkg, version);
213
+ const tarball = path.join(dir, "tarball.tgz");
214
+ console.log(` → downloading ${pkg}@${version} tarball`);
215
+ await download(dist.tarball, tarball);
216
+ if (dist.integrity) await verifyIntegrity(tarball, dist.integrity);
217
+ else if (dist.shasum) {
218
+ const hash = crypto.createHash("sha1");
219
+ await new Promise((resolve, reject) => {
220
+ const s = fs.createReadStream(tarball);
221
+ s.on("data", (c) => hash.update(c));
222
+ s.on("end", () => resolve());
223
+ s.on("error", reject);
224
+ });
225
+ const actual = hash.digest("hex");
226
+ if (actual !== dist.shasum) throw new Error(`zenbu toolchain: sha1 mismatch for ${pkg}@${version} (expected ${dist.shasum}, got ${actual})`);
227
+ } else throw new Error(`zenbu toolchain: registry response for ${pkg}@${version} carries neither integrity nor shasum`);
228
+ await execFileAsync("tar", [
229
+ "-xzf",
230
+ tarball,
231
+ "-C",
232
+ dir
233
+ ]);
234
+ await fsp.unlink(tarball);
235
+ if (!fs.existsSync(pkgRoot)) throw new Error(`zenbu toolchain: extracted ${pkg}@${version} tarball but no package/ dir found`);
236
+ await fsp.writeFile(ready, "");
237
+ return pkgRoot;
238
+ }
239
+ /**
240
+ * Yarn 2+ does not publish a discoverable per-version checksum endpoint
241
+ * (see corepack's hash table for context). We download from the official
242
+ * `repo.yarnpkg.com` over HTTPS and record the file's sha256 in a sibling
243
+ * `.sha256` file in the cache so subsequent provisions are content-stable.
244
+ *
245
+ * The verification script in `scripts/verify-package-managers.ts` exercises
246
+ * the resulting file by actually running an install with it — so a tampered
247
+ * payload would fail loudly there.
248
+ */
249
+ async function ensureYarnBerryCached(version, cacheRoot) {
250
+ const dir = path.join(cacheRoot, `yarn-berry-${version}`);
251
+ const cached = path.join(dir, "yarn.cjs");
153
252
  if (fs.existsSync(cached)) return cached;
154
253
  await fsp.mkdir(dir, { recursive: true });
254
+ const url = `https://repo.yarnpkg.com/${version}/packages/yarnpkg-cli/bin/yarn.js`;
255
+ console.log(` → downloading yarn ${version} (berry, https-only verify)`);
155
256
  const tmp = path.join(dir, ".download");
156
- const url = `https://github.com/pnpm/pnpm/releases/download/${TOOLCHAIN.pnpm.releaseTag}/${info.asset}`;
157
- console.log(` → downloading pnpm ${TOOLCHAIN.pnpm.version} (${target})`);
158
257
  await download(url, tmp);
159
- await verify(tmp, info.sha256);
160
- await fsp.chmod(tmp, 493);
258
+ const stat = await fsp.stat(tmp);
259
+ if (stat.size < 1e5) throw new Error(`zenbu toolchain: yarn berry download for ${version} is suspiciously small (${stat.size} bytes)`);
260
+ const head = await fsp.readFile(tmp, {
261
+ encoding: "utf8",
262
+ flag: "r"
263
+ }).then((contents) => contents.slice(0, 200), () => "");
264
+ if (!/^#!\/usr\/bin\/env node|^"use strict"|^\(\(\)=>/.test(head)) throw new Error(`zenbu toolchain: yarn berry download for ${version} does not look like a JS file (head: ${head.slice(0, 80)}...)`);
265
+ await fsp.chmod(tmp, 420);
161
266
  await fsp.rename(tmp, cached);
162
267
  return cached;
163
268
  }
164
269
  /**
165
- * Stage hardcoded bun + pnpm into `<stagingDir>/{bun, pnpm}` so the build
166
- * can wire them into electron-builder as extraResources. Returns absolute
167
- * paths to the staged binaries.
270
+ * Stage bun + the user-configured PM into `<stagingDir>/...` so the build
271
+ * can wire them into electron-builder as extraResources.
272
+ *
273
+ * Layout produced inside `stagingDir`:
274
+ * bun (always — runtime + node-shim)
275
+ * node (always — symlink → bun)
276
+ * pnpm (when packageManager.type === "pnpm")
277
+ * npm/ (when packageManager.type === "npm" — extracted tarball)
278
+ * yarn/ (when packageManager.type === "yarn" + classic)
279
+ * yarn.cjs (when packageManager.type === "yarn" + berry)
280
+ * (nothing extra when packageManager.type === "bun" — bun IS the PM)
168
281
  *
169
- * The sibling `node` symlink (-> `bun`) is created so that any npm
170
- * lifecycle script that does `#!/usr/bin/env node` can resolve to bun
171
- * inside the launched .app.
282
+ * The launcher reads `packageManager.type` from app-config.json and
283
+ * dispatches to the matching codepath using this same convention.
172
284
  */
173
- async function provisionToolchain(stagingDir) {
285
+ async function provisionToolchain(stagingDir, opts) {
174
286
  if (process.platform !== "darwin") throw new Error(`zenbu toolchain: only darwin is supported today (got ${process.platform})`);
175
287
  await fsp.mkdir(stagingDir, { recursive: true });
176
- const cachedBun = await ensureBunCached();
177
- const cachedPnpm = await ensurePnpmCached();
288
+ const cacheRoot = opts.cacheRoot ?? defaultCacheRoot();
289
+ const pm = opts.packageManager;
290
+ const bunSpec = pm.type === "bun" ? { version: pm.version } : (() => {
291
+ const target = bunTarget();
292
+ return {
293
+ version: PINNED_BUN.version,
294
+ pinnedSha256: PINNED_BUN.targets[target].sha256
295
+ };
296
+ })();
297
+ const cachedBun = await ensureBunCached(bunSpec, cacheRoot);
178
298
  const bunOut = path.join(stagingDir, "bun");
179
- const pnpmOut = path.join(stagingDir, "pnpm");
180
- const nodeOut = path.join(stagingDir, "node");
181
299
  await fsp.copyFile(cachedBun, bunOut);
182
300
  await fsp.chmod(bunOut, 493);
183
- await fsp.copyFile(cachedPnpm, pnpmOut);
184
- await fsp.chmod(pnpmOut, 493);
301
+ const nodeOut = path.join(stagingDir, "node");
185
302
  try {
186
303
  await fsp.unlink(nodeOut);
187
304
  } catch {}
188
305
  await fsp.symlink("bun", nodeOut);
306
+ switch (pm.type) {
307
+ case "pnpm":
308
+ await copyDir(await ensureNpmRegistryPackageCached("pnpm", pm.version, cacheRoot), path.join(stagingDir, "pnpm"));
309
+ break;
310
+ case "npm":
311
+ await copyDir(await ensureNpmRegistryPackageCached("npm", pm.version, cacheRoot), path.join(stagingDir, "npm"));
312
+ break;
313
+ case "yarn":
314
+ if (isYarnBerry(pm.version)) {
315
+ const cached = await ensureYarnBerryCached(pm.version, cacheRoot);
316
+ const out = path.join(stagingDir, "yarn.cjs");
317
+ await fsp.copyFile(cached, out);
318
+ await fsp.chmod(out, 420);
319
+ } else await copyDir(await ensureNpmRegistryPackageCached("yarn", pm.version, cacheRoot), path.join(stagingDir, "yarn"));
320
+ break;
321
+ case "bun": break;
322
+ }
189
323
  return {
190
324
  bun: bunOut,
191
- pnpm: pnpmOut,
192
- node: nodeOut
325
+ node: nodeOut,
326
+ bunVersion: bunSpec.version,
327
+ packageManager: pm
193
328
  };
194
329
  }
330
+ function isYarnBerry(version) {
331
+ const major = parseInt(version.split(".")[0] ?? "", 10);
332
+ return Number.isFinite(major) && major >= 2;
333
+ }
334
+ async function copyDir(src, dest) {
335
+ await fsp.rm(dest, {
336
+ recursive: true,
337
+ force: true
338
+ });
339
+ await fsp.mkdir(dest, { recursive: true });
340
+ for (const entry of await fsp.readdir(src, { withFileTypes: true })) {
341
+ const s = path.join(src, entry.name);
342
+ const d = path.join(dest, entry.name);
343
+ if (entry.isDirectory()) await copyDir(s, d);
344
+ else if (entry.isSymbolicLink()) {
345
+ const target = await fsp.readlink(s);
346
+ await fsp.symlink(target, d);
347
+ } else {
348
+ await fsp.copyFile(s, d);
349
+ const stat = await fsp.stat(s);
350
+ await fsp.chmod(d, stat.mode & 511);
351
+ }
352
+ }
353
+ }
195
354
  //#endregion
196
355
  //#region src/cli/commands/build-electron.ts
197
356
  const ELECTRON_BUILDER_CONFIG_NAMES = [
@@ -251,43 +410,27 @@ function expandMirrorUrl(target) {
251
410
  if (/^[\w.-]+\/[\w.-]+$/.test(target)) return `https://github.com/${target}.git`;
252
411
  return target;
253
412
  }
254
- function resolveCoreVersion() {
255
- try {
256
- const pkg = readJson(createRequire(import.meta.url).resolve("@zenbujs/core/package.json"));
257
- if (pkg.version) return pkg.version;
258
- } catch {}
259
- try {
260
- const here = fileURLToPath(import.meta.url);
261
- let dir = path.dirname(here);
262
- while (dir !== path.dirname(dir)) {
263
- const candidate = path.join(dir, "package.json");
264
- if (fs.existsSync(candidate)) {
265
- const pkg = readJson(candidate);
266
- if (pkg.name === "@zenbujs/core" && pkg.version) return pkg.version;
267
- if (pkg.version) return pkg.version;
268
- }
269
- dir = path.dirname(dir);
270
- }
271
- } catch {}
272
- return "0.0.0";
273
- }
274
413
  /**
275
- * Find the bundled `launcher.mjs` shipped inside `@zenbujs/core/dist/`. We
276
- * resolve it through Node's resolution from the user's project so that the
277
- * launcher matches the version of `@zenbujs/core` actually installed in the
278
- * app's `node_modules` (which is what runs in the bundled .app).
414
+ * Find a sibling file inside `@zenbujs/core/dist/`. Resolved through
415
+ * Node's resolution from the user's project so the artifact matches the
416
+ * `@zenbujs/core` actually installed in their `node_modules` (which is
417
+ * what runs in the bundled .app). Falls back to walking up from this
418
+ * file's location for the in-monorepo dev case.
279
419
  */
280
- function resolveLauncher(projectDir) {
420
+ function resolveCoreDistFile(projectDir, fileName) {
281
421
  const localRequire = createRequire(path.join(projectDir, "package.json"));
282
422
  try {
283
423
  const pkgPath = localRequire.resolve("@zenbujs/core/package.json");
284
- const launcher = path.join(path.dirname(pkgPath), "dist", "launcher.mjs");
285
- if (fs.existsSync(launcher)) return launcher;
424
+ const candidate = path.join(path.dirname(pkgPath), "dist", fileName);
425
+ if (fs.existsSync(candidate)) return candidate;
286
426
  } catch {}
287
427
  const here = fileURLToPath(import.meta.url);
288
- const candidate = path.resolve(path.dirname(here), "..", "launcher.mjs");
428
+ const candidate = path.resolve(path.dirname(here), "..", fileName);
289
429
  if (fs.existsSync(candidate)) return candidate;
290
- throw new Error("zen build:electron: cannot locate `@zenbujs/core/dist/launcher.mjs`. Make sure @zenbujs/core is installed in this project.");
430
+ throw new Error(`zen build:electron: cannot locate \`@zenbujs/core/dist/${fileName}\`. Make sure @zenbujs/core is installed in this project.`);
431
+ }
432
+ function resolveLauncher(projectDir) {
433
+ return resolveCoreDistFile(projectDir, "launcher.mjs");
291
434
  }
292
435
  function resolveElectronBuilder(projectDir) {
293
436
  const candidates = [path.join(projectDir, "node_modules", ".bin", "electron-builder"), path.join(projectDir, "node_modules", "electron-builder", "out", "cli", "cli.js")];
@@ -382,7 +525,7 @@ function mergeElectronBuilderConfig(userConfig, overlay) {
382
525
  output: overlay.output
383
526
  };
384
527
  merged.files = overlay.bundleFiles;
385
- merged.extraResources = [...Array.isArray(userConfig.extraResources) ? userConfig.extraResources : [], overlay.extraResource];
528
+ merged.extraResources = [...Array.isArray(userConfig.extraResources) ? userConfig.extraResources : [], ...overlay.extraResources];
386
529
  if (userConfig.npmRebuild !== false) merged.npmRebuild = false;
387
530
  if (userConfig.asar === void 0) merged.asar = false;
388
531
  return merged;
@@ -391,6 +534,19 @@ async function copyFile(src, dest) {
391
534
  await fsp.mkdir(path.dirname(dest), { recursive: true });
392
535
  await fsp.copyFile(src, dest);
393
536
  }
537
+ /**
538
+ * Stage the user's `installing.html` plus the framework's built-in
539
+ * `installing-preload.cjs` into the bundle dir. The launcher loads both
540
+ * from the .app's `Resources/` (next to `toolchain/`) before the user's
541
+ * source has been cloned. Only the two canonical files; sibling assets
542
+ * (CSS, fonts, images referenced from installing.html) are the user's
543
+ * responsibility via their own `electron-builder.json#extraResources`.
544
+ */
545
+ async function stageInstallingArtifacts(args) {
546
+ await copyFile(args.installingSrc, args.installingHtmlOut);
547
+ await copyFile(resolveCoreDistFile(args.projectDir, "installing-preload.cjs"), args.installingPreloadOut);
548
+ return true;
549
+ }
394
550
  async function runBuildElectron(argv) {
395
551
  const projectDir = resolveProjectDir();
396
552
  const flags = parseFlags(argv);
@@ -409,25 +565,39 @@ async function runBuildElectron(argv) {
409
565
  const bundlePkgOut = path.join(bundleDir, "package.json");
410
566
  const appConfigOut = path.join(bundleDir, "app-config.json");
411
567
  const mergedConfigPath = path.join(bundleDir, "electron-builder.merged.json");
568
+ const installingHtmlOut = path.join(bundleDir, "installing.html");
569
+ const installingPreloadOut = path.join(bundleDir, "installing-preload.cjs");
412
570
  const sourceSha = currentSourceSha(projectDir);
571
+ const packageManager = config.packageManager;
572
+ const pmLabel = `${packageManager.type}@${packageManager.version}`;
413
573
  console.log(`\n zen build:electron`);
414
574
  console.log(` name: ${appName}`);
415
575
  console.log(` version: ${appVersion}`);
576
+ console.log(` host: ${config.hostVersion}`);
416
577
  console.log(` source: ${sourceSha === "uncommitted" ? "uncommitted" : sourceSha.slice(0, 7)}`);
417
578
  console.log(` mirror: ${mirrorTarget} (${mirrorBranch})`);
579
+ console.log(` pm: ${pmLabel}`);
418
580
  console.log(` bundle: ${bundleDir}`);
419
581
  console.log(" → staging launcher.mjs");
420
582
  await copyFile(resolveLauncher(projectDir), launcherOut);
421
- console.log(" → provisioning bundled toolchain (bun + pnpm)");
422
- await provisionToolchain(toolchainDir);
423
- console.log(" → writing bundle package.json + app-config.json");
424
- const host = resolveCoreVersion();
583
+ const stagedInstalling = resolved.installingPath ? await stageInstallingArtifacts({
584
+ projectDir,
585
+ installingSrc: resolved.installingPath,
586
+ installingHtmlOut,
587
+ installingPreloadOut
588
+ }) : null;
589
+ if (stagedInstalling) {
590
+ console.log(` → staging installing.html (${path.relative(projectDir, resolved.installingPath)})`);
591
+ console.log(` → staging installing-preload.cjs`);
592
+ }
593
+ console.log(` → provisioning bundled toolchain (bun + ${pmLabel})`);
594
+ await provisionToolchain(toolchainDir, { packageManager });
595
+ console.log(" → writing bundle package.json + app-config.json + host.json");
425
596
  const bundlePkg = {
426
597
  name: appName,
427
598
  version: appVersion,
428
599
  main: "launcher.mjs",
429
600
  type: "module",
430
- zenbu: { host },
431
601
  repository: {
432
602
  type: "git",
433
603
  url: mirrorUrl
@@ -439,34 +609,52 @@ async function runBuildElectron(argv) {
439
609
  mirrorUrl,
440
610
  branch: mirrorBranch,
441
611
  version: appVersion,
442
- host
612
+ packageManager,
613
+ ...stagedInstalling ? {
614
+ installingHtml: "installing.html",
615
+ installingPreload: "installing-preload.cjs"
616
+ } : {}
443
617
  };
444
618
  await fsp.writeFile(appConfigOut, JSON.stringify(appConfig, null, 2) + "\n");
619
+ writeHostVersion(bundleDir, config.hostVersion);
445
620
  const userConfig = readElectronBuilderConfig(projectDir);
446
621
  const userOutput = userConfig.directories?.output ?? "dist";
447
622
  const resolvedOutput = path.isAbsolute(userOutput) ? userOutput : path.resolve(projectDir, userOutput);
623
+ const overlayExtraResources = [{
624
+ from: toolchainDir,
625
+ to: "toolchain"
626
+ }];
627
+ if (stagedInstalling) overlayExtraResources.push({
628
+ from: installingHtmlOut,
629
+ to: "installing.html"
630
+ }, {
631
+ from: installingPreloadOut,
632
+ to: "installing-preload.cjs"
633
+ });
448
634
  const merged = mergeElectronBuilderConfig(userConfig, {
449
635
  appDir: bundleDir,
450
636
  output: resolvedOutput,
451
637
  bundleFiles: [
452
638
  "package.json",
453
639
  "app-config.json",
640
+ HOST_VERSION_FILENAME,
454
641
  "launcher.mjs",
455
642
  "!node_modules",
456
643
  "!**/node_modules",
457
644
  "!**/node_modules/**"
458
645
  ],
459
- extraResource: {
460
- from: toolchainDir,
461
- to: "toolchain"
462
- }
646
+ extraResources: overlayExtraResources
463
647
  });
464
648
  await fsp.writeFile(mergedConfigPath, JSON.stringify(merged, null, 2) + "\n");
465
649
  console.log(" → injected into electron-builder config:");
466
650
  console.log(` directories.app = ${bundleDir}`);
467
651
  console.log(` directories.output = ${resolvedOutput}`);
468
- console.log(` files = [launcher + app-config + bundle pkg]`);
652
+ console.log(` files = [launcher + app-config + bundle pkg + ${HOST_VERSION_FILENAME}]`);
469
653
  console.log(` extraResources += { from: <bundle>/toolchain, to: toolchain }`);
654
+ if (stagedInstalling) {
655
+ console.log(` extraResources += { from: installing.html, to: installing.html }`);
656
+ console.log(` extraResources += { from: installing-preload.cjs, to: installing-preload.cjs }`);
657
+ }
470
658
  console.log(` asar = ${merged.asar !== void 0 ? merged.asar : "(unset)"}`);
471
659
  console.log(` npmRebuild = false`);
472
660
  console.log(" → invoking electron-builder");