@justin06lee/subaru 0.1.0

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.
@@ -0,0 +1,428 @@
1
+ import {
2
+ allowedUrl,
3
+ fetchLatestRelease,
4
+ fileStorage,
5
+ findChecksumAsset,
6
+ isNewer,
7
+ parseChecksums,
8
+ pickAsset
9
+ } from "./chunk-CPOOJ2JD.js";
10
+ import {
11
+ createUpdater
12
+ } from "./chunk-SRQ3JWJK.js";
13
+ import {
14
+ pathWithTools,
15
+ sourceAdapter
16
+ } from "./chunk-MI26ZEPP.js";
17
+
18
+ // src/electron.ts
19
+ function electronAdapter(autoUpdater, options = {}) {
20
+ autoUpdater.autoDownload = false;
21
+ autoUpdater.autoInstallOnAppQuit = true;
22
+ autoUpdater.allowPrerelease = options.allowPrerelease ?? false;
23
+ return {
24
+ kind: "electron",
25
+ name: "electron",
26
+ storage: options.storagePath ? fileStorage(options.storagePath) : void 0,
27
+ async check() {
28
+ const result = await autoUpdater.checkForUpdates();
29
+ if (!result || !result.isUpdateAvailable) return null;
30
+ const info = result.updateInfo;
31
+ const release = { tag: info.version };
32
+ const notes = notesToString(info.releaseNotes);
33
+ if (notes) release.notes = notes;
34
+ if (info.releaseDate) release.date = info.releaseDate;
35
+ return release;
36
+ },
37
+ async install(_release, { relaunch, onProgress }) {
38
+ const onProgressEvent = (p) => onProgress(p.percent / 100);
39
+ autoUpdater.on("download-progress", onProgressEvent);
40
+ try {
41
+ await autoUpdater.downloadUpdate();
42
+ } finally {
43
+ autoUpdater.off("download-progress", onProgressEvent);
44
+ }
45
+ if (relaunch) {
46
+ autoUpdater.quitAndInstall();
47
+ return "restarting";
48
+ }
49
+ return "ready";
50
+ },
51
+ async restart() {
52
+ autoUpdater.quitAndInstall();
53
+ }
54
+ };
55
+ }
56
+ function notesToString(notes) {
57
+ if (!notes) return void 0;
58
+ if (typeof notes === "string") return notes;
59
+ return notes.map((n) => n.note ? `${n.version}
60
+ ${n.note}` : n.version).join("\n\n");
61
+ }
62
+ var IPC = {
63
+ state: "subaru:state",
64
+ get: "subaru:get",
65
+ check: "subaru:check",
66
+ install: "subaru:install",
67
+ restart: "subaru:restart",
68
+ skip: "subaru:skip",
69
+ dismiss: "subaru:dismiss"
70
+ };
71
+ function serveUpdater(updater, ipcMain, targets) {
72
+ ipcMain.handle(IPC.get, () => updater.getState());
73
+ ipcMain.on(IPC.check, () => void updater.check());
74
+ ipcMain.on(IPC.install, () => void updater.install());
75
+ ipcMain.on(IPC.restart, () => void updater.restart());
76
+ ipcMain.on(IPC.skip, () => void updater.skip());
77
+ ipcMain.on(IPC.dismiss, () => updater.dismiss());
78
+ return updater.subscribe((state) => {
79
+ for (const wc of targets()) {
80
+ if (wc.isDestroyed?.()) continue;
81
+ wc.send(IPC.state, state);
82
+ }
83
+ });
84
+ }
85
+ function nodeRunner(options = {}) {
86
+ return async ({ argv, cwd, detach }) => {
87
+ const { spawn } = await import("child_process");
88
+ const base = options.env ?? process.env;
89
+ const env = { ...base, PATH: pathWithTools(base.PATH, base.HOME) };
90
+ const [program, ...args] = argv;
91
+ if (!program) return { code: 1, output: "empty command" };
92
+ if (detach) {
93
+ const fs = await import("fs");
94
+ const os = await import("os");
95
+ const path = await import("path");
96
+ const log = options.log ?? path.join(os.tmpdir(), "subaru-update.log");
97
+ const fd = fs.openSync(log, "a");
98
+ fs.writeSync(fd, `
99
+ === ${(/* @__PURE__ */ new Date()).toISOString()} ${argv.join(" ")} (in ${cwd}) ===
100
+ `);
101
+ try {
102
+ const child = spawn(program, args, { cwd, env, detached: true, stdio: ["ignore", fd, fd] });
103
+ child.unref();
104
+ return await new Promise((resolve) => {
105
+ child.once("spawn", () => resolve({ code: 0, output: `started; output in ${log}` }));
106
+ child.once("error", (e) => resolve({ code: 1, output: String(e) }));
107
+ });
108
+ } finally {
109
+ fs.closeSync(fd);
110
+ }
111
+ }
112
+ return new Promise((resolve) => {
113
+ let output = "";
114
+ const child = spawn(program, args, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
115
+ child.stdout?.on("data", (d) => output += d.toString());
116
+ child.stderr?.on("data", (d) => output += d.toString());
117
+ child.once("error", (e) => resolve({ code: 1, output: output + String(e) }));
118
+ child.once("close", (code) => resolve({ code: code ?? 1, output }));
119
+ });
120
+ };
121
+ }
122
+ function bundleAdapter(options) {
123
+ const platform = options.platform ?? process.platform;
124
+ const arch = options.arch ?? process.arch;
125
+ const run = options.run ?? nodeRunner();
126
+ const env = options.env ?? process.env;
127
+ const signing = options.signing ?? "unsigned";
128
+ let held = null;
129
+ const app = () => {
130
+ if (!options.app) throw new Error("subaru: bundleAdapter needs app (pass { app } from electron)");
131
+ return options.app;
132
+ };
133
+ const binName = () => options.name ?? app().getName().toLowerCase();
134
+ const currentVersion = () => options.currentVersion ?? app().getVersion();
135
+ function target() {
136
+ if (platform === "darwin") {
137
+ if (options.bundlePath) return { form: "app", path: options.bundlePath };
138
+ const exe2 = app().getPath("exe");
139
+ const i = exe2.indexOf(".app/");
140
+ if (i < 0) throw new Error(`subaru: ${exe2} is not inside a .app bundle`);
141
+ return { form: "app", path: exe2.slice(0, i + 4) };
142
+ }
143
+ if (env.APPIMAGE) return { form: "appimage", path: env.APPIMAGE };
144
+ if (options.bundlePath) return { form: "dir", path: options.bundlePath };
145
+ const exe = app().getPath("exe");
146
+ return { form: "dir", path: exe.slice(0, exe.lastIndexOf("/")) || "/" };
147
+ }
148
+ function pick(rel) {
149
+ const os = platform === "darwin" ? "darwin" : "linux";
150
+ const asset = pickBundleAsset(rel.assets, binName(), os, arch, platform === "linux" && env.APPIMAGE ? "appimage" : "archive");
151
+ if (!asset) throw new Error(`subaru: ${rel.tag} has no ${platform === "linux" && env.APPIMAGE ? "AppImage" : "archive"} for ${binName()} ${os}/${arch}`);
152
+ return asset;
153
+ }
154
+ async function sh(argv, cwd) {
155
+ const r = await run({ argv, cwd });
156
+ if (r.code !== 0) throw new Error(`${argv[0]} failed: ${r.output.trim() || `exit ${r.code}`}`);
157
+ return r.output;
158
+ }
159
+ function relaunchInto(t) {
160
+ if (t.form === "appimage") app().relaunch({ execPath: t.path });
161
+ else app().relaunch();
162
+ app().exit(0);
163
+ }
164
+ return {
165
+ kind: "electron",
166
+ name: `bundle:${options.repo}`,
167
+ async check(signal) {
168
+ if (platform !== "darwin" && platform !== "linux") {
169
+ throw new Error(`subaru: bundle updates are implemented for macOS and Linux (this is ${platform}); use electronAdapter(autoUpdater)`);
170
+ }
171
+ const rel = await fetchLatestRelease(options.repo, { fetch: options.fetch, api: options.api }, signal);
172
+ if (!rel || !isNewer(rel.tag, currentVersion())) {
173
+ held = null;
174
+ return null;
175
+ }
176
+ held = { release: rel, asset: pick(rel) };
177
+ const out = { tag: rel.tag, version: rel.version };
178
+ if (rel.notes) out.notes = rel.notes;
179
+ if (rel.url) out.url = rel.url;
180
+ if (rel.date) out.date = rel.date;
181
+ return out;
182
+ },
183
+ async install(release, { relaunch, onProgress }) {
184
+ const fs = await import("fs");
185
+ const fsp = await import("fs/promises");
186
+ const path = await import("path");
187
+ const os = await import("os");
188
+ const crypto = await import("crypto");
189
+ if (!held || held.release.tag !== release.tag) {
190
+ const fresh = await fetchLatestRelease(options.repo, { fetch: options.fetch, api: options.api });
191
+ if (!fresh || fresh.tag !== release.tag) throw new Error(`subaru: ${release.tag} is no longer the latest release`);
192
+ held = { release: fresh, asset: pick(fresh) };
193
+ }
194
+ const { release: rel, asset } = held;
195
+ const t = target();
196
+ if (t.form === "dir") await refuseSetuidSandbox(t.path, fsp, path);
197
+ const requirement = platform === "darwin" && signing === "signed" ? await designatedRequirement(t.path, run) : null;
198
+ if (!options.allowAnyHost && !allowedUrl(asset.url)) throw new Error(`subaru: refusing to download ${asset.url}: not a GitHub URL`);
199
+ const doFetch = options.fetch ?? ((input, init) => fetch(input, init));
200
+ const work = await fsp.mkdtemp(path.join(os.tmpdir(), "subaru-"));
201
+ const parent = path.dirname(t.path);
202
+ let stage;
203
+ try {
204
+ stage = await fsp.mkdtemp(path.join(parent, `.${path.basename(t.path)}.subaru-`));
205
+ } catch (e) {
206
+ await fsp.rm(work, { recursive: true, force: true }).catch(() => {
207
+ });
208
+ throw new Error(`subaru: cannot write next to ${t.path} (${e.code ?? e}); an install owned by a package manager updates through it`);
209
+ }
210
+ try {
211
+ const file = path.join(work, asset.name);
212
+ const res = await doFetch(asset.url, { headers: { accept: "application/octet-stream" } });
213
+ if (!res.ok || !res.body) throw new Error(`subaru: download ${asset.name}: ${res.status}`);
214
+ if (!options.allowAnyHost && !allowedUrl(res.url || asset.url)) throw new Error(`subaru: redirected off GitHub to ${res.url}`);
215
+ const total = Number(res.headers.get("content-length")) || asset.size || 0;
216
+ const hash = crypto.createHash("sha256");
217
+ const out = fs.createWriteStream(file);
218
+ let got = 0;
219
+ onProgress(total ? 0 : null);
220
+ for await (const chunk of res.body) {
221
+ hash.update(chunk);
222
+ got += chunk.length;
223
+ if (!out.write(chunk)) await new Promise((r) => out.once("drain", r));
224
+ onProgress(total ? Math.min(got / total, 1) : null);
225
+ }
226
+ await new Promise((resolve, reject) => out.end((e) => e ? reject(e) : resolve()));
227
+ if (!got) throw new Error(`subaru: ${asset.name} is empty`);
228
+ const sum = hash.digest("hex");
229
+ const ca = findChecksumAsset(rel.assets, asset.name);
230
+ if (ca) {
231
+ if (!options.allowAnyHost && !allowedUrl(ca.url)) throw new Error(`subaru: refusing to download ${ca.url}: not a GitHub URL`);
232
+ const cres = await doFetch(ca.url);
233
+ if (!cres.ok) throw new Error(`subaru: download ${ca.name}: ${cres.status}`);
234
+ const want = parseChecksums(await cres.text(), asset.name);
235
+ if (!want) throw new Error(`subaru: ${ca.name} does not list ${asset.name}`);
236
+ if (want !== sum) throw new Error(`subaru: checksum mismatch for ${asset.name}`);
237
+ } else if (options.requireChecksum) {
238
+ throw new Error(`subaru: release ${rel.tag} ships no checksum file for ${asset.name}`);
239
+ }
240
+ let fresh;
241
+ if (t.form === "appimage") {
242
+ fresh = path.join(stage, path.basename(t.path));
243
+ await fsp.copyFile(file, fresh);
244
+ await fsp.chmod(fresh, 493);
245
+ } else {
246
+ const lower = asset.name.toLowerCase();
247
+ if (lower.endsWith(".zip")) await sh(platform === "darwin" ? ["ditto", "-x", "-k", file, stage] : ["unzip", "-q", file, "-d", stage], work);
248
+ else if (lower.endsWith(".tar.gz") || lower.endsWith(".tgz")) await sh(["tar", "-xzf", file, "-C", stage], work);
249
+ else throw new Error(`subaru: ${asset.name} is neither a .zip nor a .tar.gz`);
250
+ if (t.form === "app") {
251
+ const found = await findBundle(stage, fsp, path);
252
+ if (!found) throw new Error(`subaru: ${asset.name} holds no .app bundle`);
253
+ fresh = found;
254
+ } else {
255
+ const exe = path.basename(app().getPath("exe"));
256
+ const found = await findDirWith(stage, exe, fsp, path);
257
+ if (!found) throw new Error(`subaru: ${asset.name} holds no directory with ${exe} in it`);
258
+ fresh = found;
259
+ }
260
+ }
261
+ if (t.form === "app") {
262
+ await run({ argv: ["xattr", "-dr", "com.apple.quarantine", fresh], cwd: work });
263
+ if (requirement) {
264
+ const v = await run({ argv: ["codesign", "--verify", "--deep", "--strict", `-R=${requirement}`, fresh], cwd: work });
265
+ if (v.code !== 0) {
266
+ throw new Error(`subaru: ${asset.name} is not signed by the identity this app is signed with, so its permission grants would not carry over; refusing it (${v.output.trim() || `exit ${v.code}`})`);
267
+ }
268
+ }
269
+ }
270
+ if (t.form === "appimage") {
271
+ await fsp.rename(fresh, t.path);
272
+ } else {
273
+ const aside = path.join(parent, `.${path.basename(t.path)}.subaru-old`);
274
+ await fsp.rm(aside, { recursive: true, force: true });
275
+ await fsp.rename(t.path, aside);
276
+ try {
277
+ await fsp.rename(fresh, t.path);
278
+ } catch (e) {
279
+ await fsp.rename(aside, t.path);
280
+ throw e;
281
+ }
282
+ await fsp.rm(aside, { recursive: true, force: true }).catch(() => {
283
+ });
284
+ }
285
+ if (t.form === "app" && signing === "unsigned" && options.resetPermissions?.length) {
286
+ const id = options.bundleId ?? await readBundleId(t.path, run);
287
+ if (id) {
288
+ for (const service of options.resetPermissions) await run({ argv: ["tccutil", "reset", service, id], cwd: work });
289
+ }
290
+ }
291
+ } finally {
292
+ await fsp.rm(work, { recursive: true, force: true }).catch(() => {
293
+ });
294
+ await fsp.rm(stage, { recursive: true, force: true }).catch(() => {
295
+ });
296
+ }
297
+ if (relaunch) {
298
+ relaunchInto(t);
299
+ return "restarting";
300
+ }
301
+ return "ready";
302
+ },
303
+ async restart() {
304
+ relaunchInto(target());
305
+ }
306
+ };
307
+ }
308
+ function pickBundleAsset(assets, name, os, arch, form) {
309
+ if (form === "appimage") {
310
+ const images = assets.filter((a) => a.name.toLowerCase().endsWith(".appimage"));
311
+ const byStem = new Map(images.map((a) => [a.name.slice(0, -".appimage".length), a]));
312
+ const hit = pickAsset(images.map((a) => ({ ...a, name: a.name.slice(0, -".appimage".length) })), name, os, arch);
313
+ return hit ? byStem.get(hit.name) ?? null : null;
314
+ }
315
+ const archives = assets.filter((a) => /\.(zip|tar\.gz|tgz)$/i.test(a.name));
316
+ return pickAsset(archives, name, os, arch);
317
+ }
318
+ async function findBundle(dir, fsp, path) {
319
+ const entries = await fsp.readdir(dir, { withFileTypes: true });
320
+ for (const e of entries) if (e.isDirectory() && e.name.endsWith(".app")) return path.join(dir, e.name);
321
+ for (const e of entries) {
322
+ if (!e.isDirectory() || e.name.startsWith("__MACOSX")) continue;
323
+ const nested = await findBundle(path.join(dir, e.name), fsp, path);
324
+ if (nested) return nested;
325
+ }
326
+ return null;
327
+ }
328
+ async function findDirWith(dir, exe, fsp, path, depth = 0) {
329
+ const entries = await fsp.readdir(dir, { withFileTypes: true });
330
+ if (entries.some((e) => e.isFile() && e.name === exe)) return dir;
331
+ if (depth >= 3) return null;
332
+ for (const e of entries) {
333
+ if (!e.isDirectory()) continue;
334
+ const nested = await findDirWith(path.join(dir, e.name), exe, fsp, path, depth + 1);
335
+ if (nested) return nested;
336
+ }
337
+ return null;
338
+ }
339
+ async function refuseSetuidSandbox(dir, fsp, path) {
340
+ const st = await fsp.stat(path.join(dir, "chrome-sandbox")).catch(() => null);
341
+ if (st && st.mode & 2048) {
342
+ throw new Error(`subaru: ${dir}/chrome-sandbox is setuid root, which an update cannot reproduce; update this install with the package manager that made it`);
343
+ }
344
+ }
345
+ async function designatedRequirement(bundle, run) {
346
+ const r = await run({ argv: ["codesign", "--display", "-r-", bundle], cwd: "/" });
347
+ const line = r.output.split("\n").find((l) => /^(# )?designated => /.test(l));
348
+ const req = line?.replace(/^(# )?designated => /, "").trim();
349
+ if (r.code !== 0 || !req) {
350
+ throw new Error(`subaru: signing is 'signed' but ${bundle} has no code signature (${r.output.trim() || `exit ${r.code}`}); use signing: 'unsigned'`);
351
+ }
352
+ if (/^cdhash\b/.test(req)) {
353
+ throw new Error(`subaru: signing is 'signed' but ${bundle} is only ad-hoc signed, so no new build can keep its grants; sign with a stable identity or use signing: 'unsigned'`);
354
+ }
355
+ return req;
356
+ }
357
+ async function readBundleId(bundle, run) {
358
+ const r = await run({ argv: ["defaults", "read", `${bundle}/Contents/Info`, "CFBundleIdentifier"], cwd: "/" });
359
+ return r.code === 0 ? r.output.trim() || null : null;
360
+ }
361
+ async function subaru(repo, options = {}) {
362
+ const electron = options.electron ?? await import("electron");
363
+ const app = electron.app;
364
+ const path = await import("path");
365
+ const adapter = options.adapter ?? (options.checkout ? sourceAdapter({ checkout: options.checkout, run: options.source?.run ?? nodeRunner(), ...options.source }) : bundleAdapter({ repo, app, signing: options.signing, resetPermissions: options.resetPermissions, bundleId: options.bundleId, requireChecksum: options.requireChecksum }));
366
+ const updater = createUpdater({
367
+ adapter,
368
+ policy: options.policy ?? (app.isPackaged ? "prompt" : "off"),
369
+ storage: fileStorage(path.join(app.getPath("userData"), "subaru.json")),
370
+ debug: options.debug
371
+ });
372
+ if (options.ipcMain) serveUpdater(updater, options.ipcMain, options.windows ?? (() => []));
373
+ if (options.dialog !== false && electron.dialog) {
374
+ const dialog = electron.dialog;
375
+ const name = options.name ?? app.getName();
376
+ let asked = "";
377
+ updater.subscribe((s) => {
378
+ const key = `${s.status}:${s.release?.tag ?? ""}`;
379
+ if (s.dismissed || asked === key) return;
380
+ if (s.status === "available" && s.policy === "prompt" && !s.skipped) {
381
+ asked = key;
382
+ void app.whenReady().then(async () => {
383
+ const { response } = await dialog.showMessageBox({
384
+ type: "info",
385
+ message: `${name} ${s.release?.version ?? s.release?.tag} is available.`,
386
+ detail: s.release?.notes?.slice(0, 2e3),
387
+ buttons: ["Update now", "Skip this version", "Later"],
388
+ defaultId: 0,
389
+ cancelId: 2
390
+ });
391
+ if (response === 0) await updater.install();
392
+ else if (response === 1) await updater.skip();
393
+ else updater.dismiss();
394
+ });
395
+ } else if (s.status === "ready") {
396
+ asked = key;
397
+ void app.whenReady().then(async () => {
398
+ const { response } = await dialog.showMessageBox({
399
+ type: "info",
400
+ message: `${name} ${s.release?.version ?? s.release?.tag ?? ""} is ready. Restart to finish.`.replace(" ", " "),
401
+ buttons: ["Restart now", "Later"],
402
+ defaultId: 0,
403
+ cancelId: 1
404
+ });
405
+ if (response === 0) await updater.restart();
406
+ else updater.dismiss();
407
+ });
408
+ } else if (s.status === "error" && s.error) {
409
+ asked = key;
410
+ void app.whenReady().then(
411
+ () => dialog.showMessageBox({ type: "warning", message: `${name} could not update.`, detail: s.error ?? void 0, buttons: ["OK"] })
412
+ );
413
+ }
414
+ });
415
+ }
416
+ updater.start();
417
+ return updater;
418
+ }
419
+ export {
420
+ IPC,
421
+ bundleAdapter,
422
+ electronAdapter,
423
+ nodeRunner,
424
+ pickBundleAsset,
425
+ serveUpdater,
426
+ subaru
427
+ };
428
+ //# sourceMappingURL=electron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/electron.ts"],"sourcesContent":["/**\n * Electron, main-process side.\n *\n * subaru('owner/repo') — the whole integration in one call: the\n * bundle adapter below, a native dialog,\n * and the daily check.\n * bundleAdapter({ repo }) — an Adapter that downloads the app from\n * GitHub Releases and swaps it in place:\n * the .app on macOS, the AppImage or the\n * unpacked directory on Linux. Signed or\n * unsigned; no electron-updater, no source\n * checkout.\n * electronAdapter(autoUpdater) — an Adapter over electron-updater, which\n * downloads and applies releases published\n * by electron-builder (Windows, or apps\n * already on it).\n * serveUpdater(updater, ipc) — mirrors the updater over IPC so the\n * renderer can show the same prompt through\n * \"@justin06lee/subaru/electron/renderer\".\n * nodeRunner() — runs git and make for sourceAdapter\n * (source mode: unsigned apps that a\n * Makefile builds and installs).\n */\nimport { createUpdater, type Adapter, type InstallOutcome, type Policy, type Release, type Updater, type UpdaterState } from './core';\nimport { allowedUrl, fetchLatestRelease, findChecksumAsset, isNewer, parseChecksums, pickAsset, type FetchLike, type GitHubAsset, type GitHubRelease } from './github';\nimport { pathWithTools, sourceAdapter, type Runner, type SourceAdapterOptions } from './source';\nimport { fileStorage } from './storage';\n\n/** The slice of electron-updater's AppUpdater subaru uses. */\nexport interface ElectronAutoUpdater {\n autoDownload: boolean;\n autoInstallOnAppQuit: boolean;\n allowPrerelease: boolean;\n checkForUpdates(): Promise<{ isUpdateAvailable: boolean; updateInfo: ElectronUpdateInfo } | null>;\n downloadUpdate(): Promise<unknown>;\n quitAndInstall(isSilent?: boolean, isForceRunAfter?: boolean): void;\n on(event: 'download-progress', listener: (progress: { percent: number }) => void): unknown;\n off(event: 'download-progress', listener: (progress: { percent: number }) => void): unknown;\n}\n\nexport interface ElectronUpdateInfo {\n version: string;\n releaseName?: string | null;\n releaseNotes?: string | Array<{ version: string; note: string | null }> | null;\n releaseDate?: string;\n}\n\nexport interface ElectronAdapterOptions {\n allowPrerelease?: boolean;\n /** Where to keep state. Default: no persistence beyond the process; pass fileStorage(join(app.getPath('userData'), 'subaru.json')). */\n storagePath?: string;\n}\n\nexport function electronAdapter(autoUpdater: ElectronAutoUpdater, options: ElectronAdapterOptions = {}): Adapter {\n autoUpdater.autoDownload = false;\n autoUpdater.autoInstallOnAppQuit = true;\n autoUpdater.allowPrerelease = options.allowPrerelease ?? false;\n\n return {\n kind: 'electron',\n name: 'electron',\n storage: options.storagePath ? fileStorage(options.storagePath) : undefined,\n async check() {\n const result = await autoUpdater.checkForUpdates();\n if (!result || !result.isUpdateAvailable) return null;\n const info = result.updateInfo;\n const release: Release = { tag: info.version };\n const notes = notesToString(info.releaseNotes);\n if (notes) release.notes = notes;\n if (info.releaseDate) release.date = info.releaseDate;\n return release;\n },\n async install(_release, { relaunch, onProgress }) {\n const onProgressEvent = (p: { percent: number }) => onProgress(p.percent / 100);\n autoUpdater.on('download-progress', onProgressEvent);\n try {\n await autoUpdater.downloadUpdate();\n } finally {\n autoUpdater.off('download-progress', onProgressEvent);\n }\n if (relaunch) {\n autoUpdater.quitAndInstall();\n return 'restarting';\n }\n return 'ready';\n },\n async restart() {\n autoUpdater.quitAndInstall();\n },\n };\n}\n\nfunction notesToString(notes: ElectronUpdateInfo['releaseNotes']): string | undefined {\n if (!notes) return undefined;\n if (typeof notes === 'string') return notes;\n return notes\n .map((n) => (n.note ? `${n.version}\\n${n.note}` : n.version))\n .join('\\n\\n');\n}\n\n/** The IPC surface subaru uses, so tests can pass a fake and apps pass Electron's. */\nexport interface IpcMainLike {\n handle(channel: string, listener: (event: unknown, ...args: unknown[]) => unknown): void;\n on(channel: string, listener: (event: unknown, ...args: unknown[]) => void): unknown;\n}\n\nexport interface WebContentsLike {\n send(channel: string, ...args: unknown[]): void;\n isDestroyed?(): boolean;\n}\n\nexport const IPC = {\n state: 'subaru:state',\n get: 'subaru:get',\n check: 'subaru:check',\n install: 'subaru:install',\n restart: 'subaru:restart',\n skip: 'subaru:skip',\n dismiss: 'subaru:dismiss',\n} as const;\n\n/**\n * Publish the updater to renderers: state changes are pushed on\n * \"subaru:state\", and the renderer's actions arrive on the other channels.\n * `targets` returns the webContents that should receive state (usually every\n * BrowserWindow's).\n */\nexport function serveUpdater(updater: Updater, ipcMain: IpcMainLike, targets: () => WebContentsLike[]): () => void {\n ipcMain.handle(IPC.get, () => updater.getState());\n ipcMain.on(IPC.check, () => void updater.check());\n ipcMain.on(IPC.install, () => void updater.install());\n ipcMain.on(IPC.restart, () => void updater.restart());\n ipcMain.on(IPC.skip, () => void updater.skip());\n ipcMain.on(IPC.dismiss, () => updater.dismiss());\n return updater.subscribe((state: UpdaterState) => {\n for (const wc of targets()) {\n if (wc.isDestroyed?.()) continue;\n wc.send(IPC.state, state);\n }\n });\n}\n\nexport interface NodeRunnerOptions {\n /** Where a detached `make update` writes its output. Default: subaru-update.log in the temp dir. */\n log?: string;\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A Runner on Node's child_process for sourceAdapter. Detached requests are\n * started in their own session with output appended to a log file, so\n * `make update` keeps going after it has quit this program. PATH gains the\n * usual tool directories a Finder-launched app lacks (Homebrew, bun, go,\n * cargo).\n */\nexport function nodeRunner(options: NodeRunnerOptions = {}): Runner {\n return async ({ argv, cwd, detach }) => {\n const { spawn } = await import('node:child_process');\n const base = options.env ?? process.env;\n const env = { ...base, PATH: pathWithTools(base.PATH, base.HOME) } as Record<string, string>;\n const [program, ...args] = argv;\n if (!program) return { code: 1, output: 'empty command' };\n if (detach) {\n const fs = await import('node:fs');\n const os = await import('node:os');\n const path = await import('node:path');\n const log = options.log ?? path.join(os.tmpdir(), 'subaru-update.log');\n const fd = fs.openSync(log, 'a');\n fs.writeSync(fd, `\\n=== ${new Date().toISOString()} ${argv.join(' ')} (in ${cwd}) ===\\n`);\n try {\n const child = spawn(program, args, { cwd, env, detached: true, stdio: ['ignore', fd, fd] });\n child.unref();\n return await new Promise<{ code: number; output: string }>((resolve) => {\n child.once('spawn', () => resolve({ code: 0, output: `started; output in ${log}` }));\n child.once('error', (e) => resolve({ code: 1, output: String(e) }));\n });\n } finally {\n fs.closeSync(fd);\n }\n }\n return new Promise((resolve) => {\n let output = '';\n const child = spawn(program, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });\n child.stdout?.on('data', (d: Buffer) => (output += d.toString()));\n child.stderr?.on('data', (d: Buffer) => (output += d.toString()));\n child.once('error', (e) => resolve({ code: 1, output: output + String(e) }));\n child.once('close', (code) => resolve({ code: code ?? 1, output }));\n });\n };\n}\n\n// Bundle swap: release mode, macOS and Linux ----------------------------------------\n\n/** The slice of Electron's `app` the bundle adapter and subaru() use. */\nexport interface AppLike {\n getName(): string;\n getVersion(): string;\n getPath(name: 'exe' | 'userData' | 'temp'): string;\n relaunch(options?: { execPath?: string; args?: string[] }): void;\n exit(code?: number): void;\n isPackaged: boolean;\n whenReady(): Promise<void>;\n}\n\n/**\n * How the macOS app is signed, which decides what happens to its permission\n * grants (Accessibility, Screen Recording, ...) across an update.\n *\n * 'unsigned' — ad-hoc or no signature. Every build is a new identity to\n * macOS, so grants cannot survive; subaru resets the stale\n * ones (resetPermissions) so the new build asks again.\n * 'signed' — a stable signing identity (Developer ID, or any certificate\n * you sign every build with). The new bundle must satisfy the\n * running bundle's designated requirement, the same check\n * macOS uses for grants, or it is refused before anything is\n * replaced; grants carry over and nothing is reset.\n *\n * Ignored on Linux, which has no equivalent.\n */\nexport type Signing = 'unsigned' | 'signed';\n\nexport interface BundleAdapterOptions {\n /** \"owner/name\" on GitHub. */\n repo: string;\n app?: AppLike;\n /** Program name used to pick the asset (`<name>-darwin-arm64.zip`). Default: the app name, lowercased. */\n name?: string;\n /** Running version. Default app.getVersion(). */\n currentVersion?: string;\n /** Default 'unsigned'. */\n signing?: Signing;\n /**\n * macOS permission services to reset before relaunching an unsigned\n * build, so it asks again instead of sitting on a stale grant: e.g.\n * [\"Accessibility\", \"ScreenCapture\", \"ListenEvent\"]. Needs the bundle id\n * (read from the bundle when not given). Not used when signing is\n * 'signed': the grants are still valid.\n */\n resetPermissions?: string[];\n bundleId?: string;\n requireChecksum?: boolean;\n fetch?: FetchLike;\n /** Runs ditto, tar, unzip, xattr, codesign, tccutil. Default: nodeRunner(). */\n run?: Runner;\n /** Test hooks. */\n bundlePath?: string;\n platform?: string;\n arch?: string;\n api?: string;\n allowAnyHost?: boolean;\n env?: Record<string, string | undefined>;\n}\n\n/** What is being replaced: a .app, an AppImage file, or an unpacked Linux directory. */\ninterface Target {\n form: 'app' | 'appimage' | 'dir';\n path: string;\n}\n\n/**\n * Installs releases the way Tauri's updater does, with nothing but an\n * archive on GitHub Releases.\n *\n * macOS `<name>-darwin-<arch>.zip` (or .tar.gz) holding the .app,\n * unpacked with ditto so symlinks and signatures survive, swapped\n * in place of the running bundle.\n * Linux run as an AppImage: `<name>-linux-<arch>.AppImage` replaces the\n * file $APPIMAGE points at. Run from an unpacked directory\n * (electron-builder's linux-unpacked, a tarball in ~/.local or\n * /opt): `<name>-linux-<arch>.tar.gz` (or .zip) replaces the\n * directory the executable lives in.\n *\n * The published checksum is verified first, the new copy is staged next to\n * the old one so the swap is a rename on one filesystem, and a failure at\n * any step leaves the running app where it was.\n */\nexport function bundleAdapter(options: BundleAdapterOptions): Adapter {\n const platform = options.platform ?? process.platform;\n const arch = options.arch ?? process.arch;\n const run = options.run ?? nodeRunner();\n const env = options.env ?? process.env;\n const signing = options.signing ?? 'unsigned';\n let held: { release: GitHubRelease; asset: GitHubAsset } | null = null;\n\n const app = () => {\n if (!options.app) throw new Error('subaru: bundleAdapter needs app (pass { app } from electron)');\n return options.app;\n };\n const binName = () => options.name ?? app().getName().toLowerCase();\n const currentVersion = () => options.currentVersion ?? app().getVersion();\n\n function target(): Target {\n if (platform === 'darwin') {\n if (options.bundlePath) return { form: 'app', path: options.bundlePath };\n const exe = app().getPath('exe');\n const i = exe.indexOf('.app/');\n if (i < 0) throw new Error(`subaru: ${exe} is not inside a .app bundle`);\n return { form: 'app', path: exe.slice(0, i + 4) };\n }\n if (env.APPIMAGE) return { form: 'appimage', path: env.APPIMAGE };\n if (options.bundlePath) return { form: 'dir', path: options.bundlePath };\n const exe = app().getPath('exe');\n return { form: 'dir', path: exe.slice(0, exe.lastIndexOf('/')) || '/' };\n }\n\n function pick(rel: GitHubRelease): GitHubAsset {\n const os = platform === 'darwin' ? 'darwin' : 'linux';\n const asset = pickBundleAsset(rel.assets, binName(), os, arch, platform === 'linux' && env.APPIMAGE ? 'appimage' : 'archive');\n if (!asset) throw new Error(`subaru: ${rel.tag} has no ${platform === 'linux' && env.APPIMAGE ? 'AppImage' : 'archive'} for ${binName()} ${os}/${arch}`);\n return asset;\n }\n\n async function sh(argv: string[], cwd: string): Promise<string> {\n const r = await run({ argv, cwd });\n if (r.code !== 0) throw new Error(`${argv[0]} failed: ${r.output.trim() || `exit ${r.code}`}`);\n return r.output;\n }\n\n function relaunchInto(t: Target) {\n // An AppImage runs from a mount that disappears with the process, so the\n // relaunch has to name the file itself.\n if (t.form === 'appimage') app().relaunch({ execPath: t.path });\n else app().relaunch();\n app().exit(0);\n }\n\n return {\n kind: 'electron',\n name: `bundle:${options.repo}`,\n async check(signal) {\n if (platform !== 'darwin' && platform !== 'linux') {\n throw new Error(`subaru: bundle updates are implemented for macOS and Linux (this is ${platform}); use electronAdapter(autoUpdater)`);\n }\n const rel = await fetchLatestRelease(options.repo, { fetch: options.fetch, api: options.api }, signal);\n if (!rel || !isNewer(rel.tag, currentVersion())) {\n held = null;\n return null;\n }\n held = { release: rel, asset: pick(rel) };\n const out: Release = { tag: rel.tag, version: rel.version };\n if (rel.notes) out.notes = rel.notes;\n if (rel.url) out.url = rel.url;\n if (rel.date) out.date = rel.date;\n return out;\n },\n async install(release, { relaunch, onProgress }): Promise<InstallOutcome> {\n const fs = await import('node:fs');\n const fsp = await import('node:fs/promises');\n const path = await import('node:path');\n const os = await import('node:os');\n const crypto = await import('node:crypto');\n if (!held || held.release.tag !== release.tag) {\n const fresh = await fetchLatestRelease(options.repo, { fetch: options.fetch, api: options.api });\n if (!fresh || fresh.tag !== release.tag) throw new Error(`subaru: ${release.tag} is no longer the latest release`);\n held = { release: fresh, asset: pick(fresh) };\n }\n const { release: rel, asset } = held;\n // Everything that can be known before the download is checked first,\n // so a misconfigured app fails fast instead of after 100 MB.\n const t = target();\n if (t.form === 'dir') await refuseSetuidSandbox(t.path, fsp, path);\n const requirement = platform === 'darwin' && signing === 'signed' ? await designatedRequirement(t.path, run) : null;\n if (!options.allowAnyHost && !allowedUrl(asset.url)) throw new Error(`subaru: refusing to download ${asset.url}: not a GitHub URL`);\n const doFetch = options.fetch ?? ((input: string, init?: RequestInit) => fetch(input, init));\n\n const work = await fsp.mkdtemp(path.join(os.tmpdir(), 'subaru-'));\n // Staged beside the target, so the swap is a rename on one filesystem\n // (Linux /tmp is often tmpfs) and an unwritable install fails here.\n const parent = path.dirname(t.path);\n let stage: string;\n try {\n stage = await fsp.mkdtemp(path.join(parent, `.${path.basename(t.path)}.subaru-`));\n } catch (e) {\n await fsp.rm(work, { recursive: true, force: true }).catch(() => {});\n throw new Error(`subaru: cannot write next to ${t.path} (${(e as NodeJS.ErrnoException).code ?? e}); an install owned by a package manager updates through it`);\n }\n try {\n // 1. download with progress and a running hash\n const file = path.join(work, asset.name);\n const res = await doFetch(asset.url, { headers: { accept: 'application/octet-stream' } });\n if (!res.ok || !res.body) throw new Error(`subaru: download ${asset.name}: ${res.status}`);\n if (!options.allowAnyHost && !allowedUrl(res.url || asset.url)) throw new Error(`subaru: redirected off GitHub to ${res.url}`);\n const total = Number(res.headers.get('content-length')) || asset.size || 0;\n const hash = crypto.createHash('sha256');\n const out = fs.createWriteStream(file);\n let got = 0;\n onProgress(total ? 0 : null);\n for await (const chunk of res.body as AsyncIterable<Uint8Array>) {\n hash.update(chunk);\n got += chunk.length;\n if (!out.write(chunk)) await new Promise((r) => out.once('drain', r));\n onProgress(total ? Math.min(got / total, 1) : null);\n }\n await new Promise<void>((resolve, reject) => out.end((e?: Error | null) => (e ? reject(e) : resolve())));\n if (!got) throw new Error(`subaru: ${asset.name} is empty`);\n const sum = hash.digest('hex');\n\n // 2. checksum\n const ca = findChecksumAsset(rel.assets, asset.name);\n if (ca) {\n if (!options.allowAnyHost && !allowedUrl(ca.url)) throw new Error(`subaru: refusing to download ${ca.url}: not a GitHub URL`);\n const cres = await doFetch(ca.url);\n if (!cres.ok) throw new Error(`subaru: download ${ca.name}: ${cres.status}`);\n const want = parseChecksums(await cres.text(), asset.name);\n if (!want) throw new Error(`subaru: ${ca.name} does not list ${asset.name}`);\n if (want !== sum) throw new Error(`subaru: checksum mismatch for ${asset.name}`);\n } else if (options.requireChecksum) {\n throw new Error(`subaru: release ${rel.tag} ships no checksum file for ${asset.name}`);\n }\n\n // 3. unpack into the stage, keeping symlinks, permissions and signatures\n let fresh: string;\n if (t.form === 'appimage') {\n fresh = path.join(stage, path.basename(t.path));\n await fsp.copyFile(file, fresh);\n await fsp.chmod(fresh, 0o755);\n } else {\n const lower = asset.name.toLowerCase();\n if (lower.endsWith('.zip')) await sh(platform === 'darwin' ? ['ditto', '-x', '-k', file, stage] : ['unzip', '-q', file, '-d', stage], work);\n else if (lower.endsWith('.tar.gz') || lower.endsWith('.tgz')) await sh(['tar', '-xzf', file, '-C', stage], work);\n else throw new Error(`subaru: ${asset.name} is neither a .zip nor a .tar.gz`);\n if (t.form === 'app') {\n const found = await findBundle(stage, fsp, path);\n if (!found) throw new Error(`subaru: ${asset.name} holds no .app bundle`);\n fresh = found;\n } else {\n const exe = path.basename(app().getPath('exe'));\n const found = await findDirWith(stage, exe, fsp, path);\n if (!found) throw new Error(`subaru: ${asset.name} holds no directory with ${exe} in it`);\n fresh = found;\n }\n }\n\n // 4. macOS: the new bundle must launch cleanly and, when signed, be\n // the same identity the grants were given to.\n if (t.form === 'app') {\n await run({ argv: ['xattr', '-dr', 'com.apple.quarantine', fresh], cwd: work });\n if (requirement) {\n const v = await run({ argv: ['codesign', '--verify', '--deep', '--strict', `-R=${requirement}`, fresh], cwd: work });\n if (v.code !== 0) {\n throw new Error(`subaru: ${asset.name} is not signed by the identity this app is signed with, so its permission grants would not carry over; refusing it (${v.output.trim() || `exit ${v.code}`})`);\n }\n }\n }\n\n // 5. swap: the running copy steps aside, the new one takes its place\n if (t.form === 'appimage') {\n await fsp.rename(fresh, t.path);\n } else {\n const aside = path.join(parent, `.${path.basename(t.path)}.subaru-old`);\n await fsp.rm(aside, { recursive: true, force: true });\n await fsp.rename(t.path, aside);\n try {\n await fsp.rename(fresh, t.path);\n } catch (e) {\n await fsp.rename(aside, t.path);\n throw e;\n }\n await fsp.rm(aside, { recursive: true, force: true }).catch(() => {});\n }\n\n // 6. unsigned macOS: the grants belonged to the old build, so clear them\n // and let the new build ask again\n if (t.form === 'app' && signing === 'unsigned' && options.resetPermissions?.length) {\n const id = options.bundleId ?? (await readBundleId(t.path, run));\n if (id) {\n for (const service of options.resetPermissions) await run({ argv: ['tccutil', 'reset', service, id], cwd: work });\n }\n }\n } finally {\n await fsp.rm(work, { recursive: true, force: true }).catch(() => {});\n await fsp.rm(stage, { recursive: true, force: true }).catch(() => {});\n }\n if (relaunch) {\n relaunchInto(t);\n return 'restarting';\n }\n return 'ready';\n },\n async restart() {\n relaunchInto(target());\n },\n };\n}\n\n/**\n * The asset for this platform in the shape the install needs: an AppImage,\n * or a .zip / .tar.gz archive. Raw binaries (a Go program's assets in the\n * same release) are never picked.\n */\nexport function pickBundleAsset(assets: GitHubAsset[], name: string, os: string, arch: string, form: 'appimage' | 'archive'): GitHubAsset | null {\n if (form === 'appimage') {\n const images = assets.filter((a) => a.name.toLowerCase().endsWith('.appimage'));\n // pickAsset skips AppImages (the Go module can't run one), so match on the name without its extension\n const byStem = new Map(images.map((a) => [a.name.slice(0, -'.appimage'.length), a]));\n const hit = pickAsset(images.map((a) => ({ ...a, name: a.name.slice(0, -'.appimage'.length) })), name, os, arch);\n return hit ? byStem.get(hit.name) ?? null : null;\n }\n const archives = assets.filter((a) => /\\.(zip|tar\\.gz|tgz)$/i.test(a.name));\n return pickAsset(archives, name, os, arch);\n}\n\nasync function findBundle(dir: string, fsp: typeof import('node:fs/promises'), path: typeof import('node:path')): Promise<string | null> {\n const entries = await fsp.readdir(dir, { withFileTypes: true });\n for (const e of entries) if (e.isDirectory() && e.name.endsWith('.app')) return path.join(dir, e.name);\n for (const e of entries) {\n if (!e.isDirectory() || e.name.startsWith('__MACOSX')) continue;\n const nested = await findBundle(path.join(dir, e.name), fsp, path);\n if (nested) return nested;\n }\n return null;\n}\n\n/** The directory in an unpacked Linux archive that holds the app's executable, at the top or a few levels down. */\nasync function findDirWith(dir: string, exe: string, fsp: typeof import('node:fs/promises'), path: typeof import('node:path'), depth = 0): Promise<string | null> {\n const entries = await fsp.readdir(dir, { withFileTypes: true });\n if (entries.some((e) => e.isFile() && e.name === exe)) return dir;\n if (depth >= 3) return null;\n for (const e of entries) {\n if (!e.isDirectory()) continue;\n const nested = await findDirWith(path.join(dir, e.name), exe, fsp, path, depth + 1);\n if (nested) return nested;\n }\n return null;\n}\n\n/**\n * A chrome-sandbox that is setuid root was set up by a package manager or\n * by hand with sudo; a copy unpacked by the user cannot be, and Electron\n * would refuse to start with it. Better to say so before replacing anything.\n */\nasync function refuseSetuidSandbox(dir: string, fsp: typeof import('node:fs/promises'), path: typeof import('node:path')) {\n const st = await fsp.stat(path.join(dir, 'chrome-sandbox')).catch(() => null);\n if (st && st.mode & 0o4000) {\n throw new Error(`subaru: ${dir}/chrome-sandbox is setuid root, which an update cannot reproduce; update this install with the package manager that made it`);\n }\n}\n\n/**\n * The running bundle's designated requirement: what macOS checks a new\n * build against to decide whether the grants still apply. An ad-hoc\n * signature's requirement is its own hash, which no other build can meet,\n * so it means the app is not really signed.\n */\nasync function designatedRequirement(bundle: string, run: Runner): Promise<string> {\n const r = await run({ argv: ['codesign', '--display', '-r-', bundle], cwd: '/' });\n // an implicit requirement is printed commented out (\"# designated => cdhash ...\")\n const line = r.output.split('\\n').find((l) => /^(# )?designated => /.test(l));\n const req = line?.replace(/^(# )?designated => /, '').trim();\n if (r.code !== 0 || !req) {\n throw new Error(`subaru: signing is 'signed' but ${bundle} has no code signature (${r.output.trim() || `exit ${r.code}`}); use signing: 'unsigned'`);\n }\n if (/^cdhash\\b/.test(req)) {\n throw new Error(`subaru: signing is 'signed' but ${bundle} is only ad-hoc signed, so no new build can keep its grants; sign with a stable identity or use signing: 'unsigned'`);\n }\n return req;\n}\n\nasync function readBundleId(bundle: string, run: Runner): Promise<string | null> {\n const r = await run({ argv: ['defaults', 'read', `${bundle}/Contents/Info`, 'CFBundleIdentifier'], cwd: '/' });\n return r.code === 0 ? r.output.trim() || null : null;\n}\n\n// One call ----------------------------------------------------------------------------\n\nexport interface ElectronAutoOptions {\n policy?: Policy;\n /** Shown in the dialog. Default app.getName(). */\n name?: string;\n /** Source mode instead of GitHub Releases: the app's git checkout, fast-forwarded and rebuilt with make. */\n checkout?: string;\n source?: Omit<SourceAdapterOptions, 'checkout' | 'run'> & { run?: Runner };\n /** Passed to bundleAdapter. 'signed' keeps macOS permission grants across updates; default 'unsigned'. */\n signing?: Signing;\n resetPermissions?: string[];\n bundleId?: string;\n requireChecksum?: boolean;\n /** false: no native dialog; drive the UI yourself (serveUpdater + the card in the renderer). */\n dialog?: boolean;\n /** Mirror state to renderers too. */\n ipcMain?: IpcMainLike;\n windows?: () => WebContentsLike[];\n debug?: (message: string) => void;\n /** Test hooks: Electron's app and dialog. */\n electron?: { app: AppLike; dialog?: DialogLike };\n adapter?: Adapter;\n}\n\nexport interface DialogLike {\n showMessageBox(options: { type?: string; message: string; detail?: string; buttons: string[]; defaultId?: number; cancelId?: number }): Promise<{ response: number }>;\n}\n\n/**\n * The whole integration in one call, from the main process:\n *\n * import { subaru } from '@justin06lee/subaru/electron';\n * subaru('justin06lee/ruri');\n *\n * Once a day it looks at the repository's latest release. When there is a\n * newer one it asks with a native dialog (Update now / Skip this version /\n * Later), downloads the release for this platform, swaps it in and\n * relaunches. Nothing needs publishing beyond a GitHub release; signing is\n * optional, and { signing: 'signed' } keeps macOS permission grants.\n * Pass { checkout } for source mode instead. Development runs (app.isPackaged\n * false) stay off unless a policy is given.\n */\nexport async function subaru(repo: string, options: ElectronAutoOptions = {}): Promise<Updater> {\n const electron = options.electron ?? ((await import('electron')) as unknown as { app: AppLike; dialog: DialogLike });\n const app = electron.app;\n const path = await import('node:path');\n const adapter =\n options.adapter ??\n (options.checkout\n ? sourceAdapter({ checkout: options.checkout, run: options.source?.run ?? nodeRunner(), ...options.source })\n : bundleAdapter({ repo, app, signing: options.signing, resetPermissions: options.resetPermissions, bundleId: options.bundleId, requireChecksum: options.requireChecksum }));\n const updater = createUpdater({\n adapter,\n policy: options.policy ?? (app.isPackaged ? 'prompt' : 'off'),\n storage: fileStorage(path.join(app.getPath('userData'), 'subaru.json')),\n debug: options.debug,\n });\n if (options.ipcMain) serveUpdater(updater, options.ipcMain, options.windows ?? (() => []));\n if (options.dialog !== false && electron.dialog) {\n const dialog = electron.dialog;\n const name = options.name ?? app.getName();\n let asked = '';\n updater.subscribe((s) => {\n const key = `${s.status}:${s.release?.tag ?? ''}`;\n if (s.dismissed || asked === key) return;\n if (s.status === 'available' && s.policy === 'prompt' && !s.skipped) {\n asked = key;\n void app.whenReady().then(async () => {\n const { response } = await dialog.showMessageBox({\n type: 'info',\n message: `${name} ${s.release?.version ?? s.release?.tag} is available.`,\n detail: s.release?.notes?.slice(0, 2000),\n buttons: ['Update now', 'Skip this version', 'Later'],\n defaultId: 0,\n cancelId: 2,\n });\n if (response === 0) await updater.install();\n else if (response === 1) await updater.skip();\n else updater.dismiss();\n });\n } else if (s.status === 'ready') {\n asked = key;\n void app.whenReady().then(async () => {\n const { response } = await dialog.showMessageBox({\n type: 'info',\n message: `${name} ${s.release?.version ?? s.release?.tag ?? ''} is ready. Restart to finish.`.replace(' ', ' '),\n buttons: ['Restart now', 'Later'],\n defaultId: 0,\n cancelId: 1,\n });\n if (response === 0) await updater.restart();\n else updater.dismiss();\n });\n } else if (s.status === 'error' && s.error) {\n asked = key;\n void app.whenReady().then(() =>\n dialog.showMessageBox({ type: 'warning', message: `${name} could not update.`, detail: s.error ?? undefined, buttons: ['OK'] }),\n );\n }\n });\n }\n updater.start();\n return updater;\n}\n"],"mappings":"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","names":["exe"]}
@@ -0,0 +1,37 @@
1
+ import { R as Release } from './core-DmEjLKLp.js';
2
+
3
+ /**
4
+ * GitHub Releases, the same way the Go module reads them: the latest
5
+ * non-draft, non-prerelease release, its assets matched to a platform by
6
+ * name tokens, and its checksum file when it publishes one.
7
+ */
8
+
9
+ interface GitHubAsset {
10
+ name: string;
11
+ url: string;
12
+ size: number;
13
+ }
14
+ interface GitHubRelease extends Release {
15
+ assets: GitHubAsset[];
16
+ }
17
+ type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
18
+ interface GitHubOptions {
19
+ fetch?: FetchLike;
20
+ token?: string;
21
+ api?: string;
22
+ }
23
+ declare function splitRepo(repo: string): [string, string];
24
+ declare function fetchLatestRelease(repo: string, options?: GitHubOptions, signal?: AbortSignal): Promise<GitHubRelease | null>;
25
+ /**
26
+ * Pick the asset built for this program on this platform by tokenising the
27
+ * name on "-", "_", "." and " " and requiring one OS token and one
28
+ * architecture token (aliases understood). The asset that starts with the
29
+ * program name and has the fewest unexplained words wins, so
30
+ * "app-helper-darwin-arm64" loses to "app-darwin-arm64". Darwin also accepts
31
+ * "universal".
32
+ */
33
+ declare function pickAsset(assets: GitHubAsset[], binName: string, os: string, arch: string): GitHubAsset | null;
34
+ /** Is `latest` strictly newer than `current`? Unknown current means yes; unknown latest means no. */
35
+ declare function isNewer(latest: string, current: string | undefined): boolean;
36
+
37
+ export { type FetchLike as F, type GitHubAsset as G, type GitHubRelease as a, fetchLatestRelease as f, isNewer as i, pickAsset as p, splitRepo as s };
@@ -0,0 +1,14 @@
1
+ import { S as StateStorage } from './core-DmEjLKLp.js';
2
+ export { A as Adapter, c as AdapterKind, I as InstallOptions, d as InstallOutcome, P as Policy, R as Release, e as Status, b as Updater, a as UpdaterLike, f as UpdaterOptions, U as UpdaterState, g as createUpdater, m as memoryStorage, p as parsePolicy, w as webStorage } from './core-DmEjLKLp.js';
3
+ export { RunRequest, RunResult, Runner, SourceAdapterOptions, pathWithTools, sourceAdapter } from './source.js';
4
+ export { G as GitHubAsset, a as GitHubRelease, f as fetchLatestRelease, i as isNewer, p as pickAsset, s as splitRepo } from './github-Ds4DWDq9.js';
5
+ export { C as CardLabels, M as MountCardOptions, d as defaultCardLabels, m as mountCard } from './card-BFhpWbA5.js';
6
+ export { C as CARD_CSS, a as CARD_POSITIONS, b as CARD_TOKENS, c as CardPosition, d as CardStyleOptions, e as CardTheme, f as CardToken, g as CardVars, h as cardVars, i as injectStyles } from './styles-zfa0Pc0l.js';
7
+
8
+ /**
9
+ * A JSON file, for Electron's main process (point it at app.getPath('userData'))
10
+ * or any Node program. Writes are atomic: temp file, then rename.
11
+ */
12
+ declare function fileStorage(path: string): StateStorage;
13
+
14
+ export { StateStorage, fileStorage };
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ import {
2
+ defaultCardLabels,
3
+ mountCard
4
+ } from "./chunk-K7IY5EW6.js";
5
+ import {
6
+ fetchLatestRelease,
7
+ fileStorage,
8
+ isNewer,
9
+ pickAsset,
10
+ splitRepo
11
+ } from "./chunk-CPOOJ2JD.js";
12
+ import {
13
+ createUpdater,
14
+ memoryStorage,
15
+ parsePolicy,
16
+ webStorage
17
+ } from "./chunk-SRQ3JWJK.js";
18
+ import {
19
+ pathWithTools,
20
+ sourceAdapter
21
+ } from "./chunk-MI26ZEPP.js";
22
+ import {
23
+ CARD_CSS,
24
+ CARD_POSITIONS,
25
+ CARD_TOKENS,
26
+ cardVars,
27
+ injectStyles
28
+ } from "./chunk-POOH3Z7G.js";
29
+ export {
30
+ CARD_CSS,
31
+ CARD_POSITIONS,
32
+ CARD_TOKENS,
33
+ cardVars,
34
+ createUpdater,
35
+ defaultCardLabels,
36
+ fetchLatestRelease,
37
+ fileStorage,
38
+ injectStyles,
39
+ isNewer,
40
+ memoryStorage,
41
+ mountCard,
42
+ parsePolicy,
43
+ pathWithTools,
44
+ pickAsset,
45
+ sourceAdapter,
46
+ splitRepo,
47
+ webStorage
48
+ };
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}