electron-updater-for-render 1.0.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.
package/Readme.md ADDED
@@ -0,0 +1,356 @@
1
+ # electron-updater-for-render
2
+
3
+ [中文文档](./README.zh-CN.md)
4
+
5
+ A highly robust, hybrid (auto-prompt & API-driven) **incremental OTA updater for Electron renderer processes** (Vue / React / plain HTML).
6
+
7
+ Unlike `electron-updater` which re-downloads the entire app installer, this library updates only the renderer assets (HTML/CSS/JS) using `.asar` files — **no elevated permissions, no installer popups, just a fast and silent hot-patch.**
8
+
9
+ ---
10
+
11
+ ## 🚀 Features
12
+
13
+ - **Dual-Track Update Mode**: Built-in native OS dialogs *or* a fully custom frontend progress UI via IPC — your choice
14
+ - **Force Update**: Push `forceUpdate: 'prompt'` or `'silent'` from your server for P0 hotfixes that bypass the "Later" button entirely
15
+ - **Graceful Restart Hook**: `onBeforeRestart` async hook lets the renderer save unsaved state before `app.relaunch()`
16
+ - **Concurrency Lock**: Prevents race conditions between auto-check-on-boot and manual-trigger-on-click
17
+ - **Smart Cache Pruning**: `maxVersionsToKeep` automatically removes outdated ASAR bundles while retaining rollback copies
18
+ - **Stream Pipeline Download**: Node.js `stream/promises pipeline` + `Transform` — zero memory leaks, full backpressure support
19
+ - **Zero-config Version Detection**: Auto-reads version from `process.cwd()/package.json` — no extra config required
20
+ - **Separate Build Commands**: Normal `npm run build` is untouched. Only `npm run build:update` triggers ASAR packaging
21
+
22
+ ---
23
+
24
+ ## 📦 Installation
25
+
26
+ ```bash
27
+ npm install electron-updater-for-render
28
+
29
+ # asar is required for the build step (in your renderer project)
30
+ npm install -D asar cross-env
31
+ ```
32
+
33
+ ---
34
+
35
+ ## 🗺️ How It Works
36
+
37
+ ```
38
+ ┌─────────────────────────────────────────────────────┐
39
+ │ Renderer Project (e.g. Vue) │
40
+ │ npm run build:update │
41
+ │ → vite build → pack ASAR → write latest.json │
42
+ └───────────────┬─────────────────────────────────────┘
43
+ │ Upload dist_updates/ to server
44
+
45
+ ┌─────────────────────────────────────────────────────┐
46
+ │ Update Server (Nginx / S3 / CDN) │
47
+ │ serves: latest.json + /1.0.2/renderer.asar │
48
+ └───────────────┬─────────────────────────────────────┘
49
+ │ HTTP fetch on app startup
50
+
51
+ ┌─────────────────────────────────────────────────────┐
52
+ │ Electron Main Process │
53
+ │ updater.checkForUpdatesAndNotify() │
54
+ │ → check version → download ASAR → relaunch │
55
+ └─────────────────────────────────────────────────────┘
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 🛠️ Complete Setup Guide
61
+
62
+ ### Step 1 — Renderer Project: Install & Configure Vite Plugin
63
+
64
+ ```typescript
65
+ // vite.config.ts
66
+ import { defineConfig } from 'vite'
67
+ import { electronRenderUpdater } from 'electron-updater-for-render/vite'
68
+
69
+ export default defineConfig({
70
+ plugins: [
71
+ electronRenderUpdater({
72
+ outDir: './dist', // Required: Vite build output directory
73
+ updatesDir: './dist_updates', // Optional: where to write update packages (default: './dist_updates')
74
+ // version: '1.2.3' // Optional: explicit version (overrides package.json)
75
+ // packageJsonPath: './package.json' // Optional: custom package.json path
76
+ // forceUpdate: 'prompt' // Optional: 'prompt' | 'silent' — for P0 mandatory rollouts ONLY
77
+ })
78
+ ]
79
+ })
80
+ ```
81
+
82
+ **Version resolution priority:**
83
+ 1. `version` field (explicit)
84
+ 2. `packageJsonPath` → read `version` from that file
85
+ 3. Auto-detect: `process.cwd()/package.json` *(default — zero config)*
86
+
87
+ ### Step 2 — Renderer Project: Add Build Script
88
+
89
+ Add a dedicated `build:update` script to your `package.json`. This is **separate from your normal `build`** — it triggers ASAR packaging only when you explicitly want to release an update.
90
+
91
+ ```json
92
+ {
93
+ "scripts": {
94
+ "dev": "vite",
95
+ "build": "vite build",
96
+ "build:update": "cross-env ELECTRON_PACK_UPDATE=1 vite build"
97
+ }
98
+ }
99
+ ```
100
+
101
+ > `npm run build` — normal frontend build, **no ASAR packing**
102
+ > `npm run build:update` — builds frontend + packs ASAR + writes `latest.json`
103
+
104
+ ### Step 3 — Deploy Update Files
105
+
106
+ After running `npm run build:update`, the `dist_updates/` directory will contain:
107
+
108
+ ```
109
+ dist_updates/
110
+ ├── latest.json ← version manifest (fetched by the client)
111
+ └── 1.0.2/
112
+ └── renderer.asar ← the actual update payload
113
+ ```
114
+
115
+ **Upload everything in `dist_updates/` to your update server** (Nginx, S3, CDN, GitHub Releases, etc.).
116
+
117
+ Example upload:
118
+ ```bash
119
+ # Example: sync to S3 bucket
120
+ aws s3 sync dist_updates/ s3://your-bucket/auto-updates/
121
+
122
+ # Example: upload to Nginx server
123
+ rsync -avz dist_updates/ user@your-server:/var/www/auto-updates/
124
+ ```
125
+
126
+ The server must serve these files over HTTP(S). The client will fetch:
127
+ - `GET https://your-server.com/latest.json`
128
+ - `GET https://your-server.com/1.0.2/renderer.asar`
129
+
130
+ ---
131
+
132
+ ### Step 4 — Electron Main Process: Configure Runtime
133
+
134
+ ```typescript
135
+ // src/main/index.ts
136
+ import { app, BrowserWindow, ipcMain } from 'electron'
137
+ import { join } from 'path'
138
+ import { RenderUpdater } from 'electron-updater-for-render'
139
+
140
+ const updater = new RenderUpdater({
141
+ updateUrl: 'https://your-server.com/auto-updates', // Required: base URL for updates
142
+ versionsDir: join(app.getPath('userData'), 'versions'), // Required: local version storage
143
+ maxVersionsToKeep: 2, // Optional: keep 2 old versions for rollback (default: 2)
144
+
145
+ // Optional: give the renderer time to save state before restart
146
+ onBeforeRestart: async () => {
147
+ const [win] = BrowserWindow.getAllWindows()
148
+ if (win) {
149
+ win.webContents.send('updater:before-restart')
150
+ await new Promise(resolve => setTimeout(resolve, 2000))
151
+ }
152
+ }
153
+ })
154
+
155
+ app.whenReady().then(async () => {
156
+ const mainWindow = new BrowserWindow({
157
+ webPreferences: { preload: join(__dirname, '../preload/index.js') }
158
+ // ...
159
+ })
160
+
161
+ // Load the latest downloaded ASAR, fallback to dev server if no update downloaded yet
162
+ const loadUrl = updater.getLoadUrl()
163
+ mainWindow.loadURL(loadUrl || 'http://localhost:5173')
164
+
165
+ // Auto-check on startup (respects forceUpdate, shows dialogs if autoPrompt: true)
166
+ setTimeout(() => updater.checkForUpdatesAndNotify(), 3000)
167
+ })
168
+
169
+ // Expose manual update controls for custom frontend UI
170
+ ipcMain.handle('updater:check', () => updater.check())
171
+ ipcMain.handle('updater:download', () =>
172
+ updater.download((percent) => {
173
+ BrowserWindow.getAllWindows()[0]?.webContents.send('updater:progress', percent)
174
+ })
175
+ )
176
+ ipcMain.handle('updater:install', () => {
177
+ app.relaunch()
178
+ app.quit()
179
+ })
180
+ ```
181
+
182
+ ---
183
+
184
+ ### Step 5 — Preload Script
185
+
186
+ ```typescript
187
+ // src/preload/index.ts
188
+ import { contextBridge, ipcRenderer } from 'electron'
189
+
190
+ contextBridge.exposeInMainWorld('updater', {
191
+ check: () => ipcRenderer.invoke('updater:check'),
192
+ download: () => ipcRenderer.invoke('updater:download'),
193
+ install: () => ipcRenderer.invoke('updater:install'),
194
+
195
+ onProgress: (callback: (percent: number) => void) => {
196
+ const fn = (_: any, p: number) => callback(p)
197
+ ipcRenderer.on('updater:progress', fn)
198
+ return () => ipcRenderer.removeListener('updater:progress', fn)
199
+ },
200
+
201
+ onBeforeRestart: (callback: () => void) => {
202
+ const fn = () => callback()
203
+ ipcRenderer.on('updater:before-restart', fn)
204
+ return () => ipcRenderer.removeListener('updater:before-restart', fn)
205
+ }
206
+ })
207
+ ```
208
+
209
+ ---
210
+
211
+ ### Step 6 — Renderer UI (Vue Example)
212
+
213
+ ```vue
214
+ <script setup lang="ts">
215
+ import { ref, onMounted, onUnmounted } from 'vue'
216
+
217
+ const status = ref('')
218
+ const progress = ref(0)
219
+ const hasUpdate = ref(false)
220
+
221
+ let removeProgress: (() => void) | null = null
222
+ let removeRestart: (() => void) | null = null
223
+
224
+ onMounted(() => {
225
+ // Save state before restart
226
+ removeRestart = window.updater.onBeforeRestart(() => {
227
+ status.value = 'Restarting...'
228
+ localStorage.setItem('draft', JSON.stringify({ /* your state */ }))
229
+ })
230
+ })
231
+
232
+ onUnmounted(() => {
233
+ removeProgress?.()
234
+ removeRestart?.()
235
+ })
236
+
237
+ const checkUpdate = async () => {
238
+ status.value = 'Checking...'
239
+ const result = await window.updater.check()
240
+ if (result.updateAvailable) {
241
+ hasUpdate.value = true
242
+ status.value = `Update available: v${result.version}`
243
+ } else {
244
+ status.value = 'Already up to date'
245
+ }
246
+ }
247
+
248
+ const downloadUpdate = async () => {
249
+ status.value = 'Downloading...'
250
+ removeProgress = window.updater.onProgress((p) => {
251
+ progress.value = p
252
+ status.value = `Downloading... ${p.toFixed(0)}%`
253
+ })
254
+ await window.updater.download()
255
+ removeProgress?.()
256
+ status.value = 'Download complete'
257
+ }
258
+
259
+ const installUpdate = () => {
260
+ window.updater.install()
261
+ }
262
+ </script>
263
+
264
+ <template>
265
+ <div>
266
+ <p>{{ status }}</p>
267
+ <progress :value="progress" max="100" v-if="progress > 0" />
268
+ <button @click="checkUpdate">Check for updates</button>
269
+ <button @click="downloadUpdate" v-if="hasUpdate">Download</button>
270
+ <button @click="installUpdate">Install & Restart</button>
271
+ </div>
272
+ </template>
273
+ ```
274
+
275
+ ---
276
+
277
+ ## ⚙️ API Reference
278
+
279
+ ### `BuilderOptions` (Vite Plugin)
280
+
281
+ | Option | Type | Required | Description |
282
+ |---|---|---|---|
283
+ | `outDir` | `string` | ✅ | Vite build output directory |
284
+ | `updatesDir` | `string` | — | Output directory for update packages. Default: `./dist_updates` |
285
+ | `version` | `string` | — | Explicit version string, overrides `packageJsonPath` |
286
+ | `packageJsonPath` | `string` | — | Custom path to `package.json`. Defaults to `process.cwd()/package.json` |
287
+ | `asarName` | `string` | — | ASAR filename. Default: `renderer.asar` |
288
+ | `privateKeyPath` | `string` | — | Path to RSA private key for signing |
289
+ | `releaseNotesPath` | `string` | — | Path to release notes markdown file |
290
+ | `forceUpdate` | `'prompt' \| 'silent'` | — | Mandatory update mode. `'prompt'` = blocking dialog; `'silent'` = fully invisible |
291
+
292
+ ### `UpdaterOptions` (Main Process)
293
+
294
+ | Option | Type | Required | Description |
295
+ |---|---|---|---|
296
+ | `updateUrl` | `string` | ✅ | Base URL where update files are hosted |
297
+ | `versionsDir` | `string` | ✅ | Local directory to store downloaded ASAR versions |
298
+ | `publicKey` | `string` | — | RSA public key (PEM) for signature verification |
299
+ | `autoDownload` | `boolean` | — | Auto-download without asking. Default: `false` |
300
+ | `autoPrompt` | `boolean` | — | Show built-in native dialogs. Default: `true` |
301
+ | `maxVersionsToKeep` | `number` | — | Number of old versions to retain for rollback. Default: `2` |
302
+ | `onUpdateAvailable` | `function` | — | Custom hook: `(info, doDownload) => void` |
303
+ | `onDownloadProgress` | `function` | — | Progress callback: `(percent: number) => void` |
304
+ | `onDownloadComplete` | `function` | — | Completion hook: `(info, doInstall) => void` |
305
+ | `onError` | `function` | — | Error handler: `(error: Error) => void` |
306
+ | `onBeforeRestart` | `async function` | — | Called before `app.relaunch()`. Await-able for graceful shutdown |
307
+
308
+ ---
309
+
310
+ ## 🛡️ Force Update
311
+
312
+ For P0 incidents where deferral is not an option:
313
+
314
+ ```typescript
315
+ // vite.config.ts
316
+ electronRenderUpdater({
317
+ outDir: './dist',
318
+ forceUpdate: 'prompt' // or 'silent'
319
+ })
320
+ ```
321
+
322
+ | Mode | Behavior |
323
+ |---|---|
324
+ | `'prompt'` | Native OS warning dialog with only one button ("Confirm"). No "Later" option. Download starts immediately. |
325
+ | `'silent'` | Zero UI. Downloads and restarts entirely in the background. |
326
+
327
+ > ⚠️ Use only for genuine P0 incidents. This permanently removes the user's ability to defer.
328
+
329
+ ---
330
+
331
+ ## 🔐 RSA Signature Verification (Optional)
332
+
333
+ ```bash
334
+ # Generate key pair
335
+ openssl genrsa -out private.pem 2048
336
+ openssl rsa -in private.pem -pubout -out public.pem
337
+ ```
338
+
339
+ ```typescript
340
+ // vite.config.ts
341
+ electronRenderUpdater({ outDir: './dist', privateKeyPath: './private.pem' })
342
+
343
+ // main/index.ts
344
+ import { readFileSync } from 'fs'
345
+ new RenderUpdater({
346
+ updateUrl: '...',
347
+ versionsDir: '...',
348
+ publicKey: readFileSync('./public.pem', 'utf-8')
349
+ })
350
+ ```
351
+
352
+ ---
353
+
354
+ ## License
355
+
356
+ MIT
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/builder/index.ts
31
+ var builder_exports = {};
32
+ __export(builder_exports, {
33
+ createUpdatePackage: () => createUpdatePackage
34
+ });
35
+ module.exports = __toCommonJS(builder_exports);
36
+ var import_fs = __toESM(require("fs"), 1);
37
+ var import_path = __toESM(require("path"), 1);
38
+ var import_crypto = __toESM(require("crypto"), 1);
39
+ async function createUpdatePackage(options) {
40
+ const asar = await import("asar").catch(() => {
41
+ throw new Error('Please install "asar" as a devDependency to use the builder.');
42
+ });
43
+ const {
44
+ outDir,
45
+ updatesDir = import_path.default.resolve(process.cwd(), "dist_updates"),
46
+ asarName = "renderer.asar",
47
+ version: explicitVersion,
48
+ packageJsonPath,
49
+ privateKeyPath,
50
+ releaseNotesPath,
51
+ forceUpdate
52
+ } = options;
53
+ if (!import_fs.default.existsSync(outDir)) {
54
+ throw new Error(`Output directory not found at ${outDir}`);
55
+ }
56
+ if (!import_fs.default.existsSync(updatesDir)) {
57
+ import_fs.default.mkdirSync(updatesDir, { recursive: true });
58
+ }
59
+ let version;
60
+ if (explicitVersion) {
61
+ version = explicitVersion;
62
+ } else {
63
+ const pkgPath = packageJsonPath ?? import_path.default.resolve(process.cwd(), "package.json");
64
+ if (!import_fs.default.existsSync(pkgPath)) {
65
+ throw new Error(
66
+ `Cannot resolve version: package.json not found at ${pkgPath}. Please set "version" or "packageJsonPath" in the plugin options.`
67
+ );
68
+ }
69
+ const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
70
+ version = pkg.version || "0.0.0";
71
+ }
72
+ const versionDir = import_path.default.join(updatesDir, version);
73
+ if (!import_fs.default.existsSync(versionDir)) {
74
+ import_fs.default.mkdirSync(versionDir, { recursive: true });
75
+ }
76
+ const asarPath = import_path.default.join(versionDir, asarName);
77
+ await asar.createPackage(outDir, asarPath);
78
+ const fileBuffer = import_fs.default.readFileSync(asarPath);
79
+ const hashSum = import_crypto.default.createHash("sha256");
80
+ hashSum.update(fileBuffer);
81
+ const sha256 = hashSum.digest("hex");
82
+ let signature = "";
83
+ if (privateKeyPath && import_fs.default.existsSync(privateKeyPath)) {
84
+ const privateKey = import_fs.default.readFileSync(privateKeyPath, "utf8");
85
+ const sign = import_crypto.default.createSign("RSA-SHA256");
86
+ sign.update(fileBuffer);
87
+ sign.end();
88
+ signature = sign.sign(privateKey, "base64");
89
+ }
90
+ let releaseNotesContent = "No release notes provided.";
91
+ if (releaseNotesPath && import_fs.default.existsSync(releaseNotesPath)) {
92
+ releaseNotesContent = import_fs.default.readFileSync(releaseNotesPath, "utf-8");
93
+ }
94
+ const updateInfo = {
95
+ version,
96
+ path: `${version}/${asarName}`,
97
+ sha256,
98
+ ...signature ? { signature } : {},
99
+ date: (/* @__PURE__ */ new Date()).toISOString(),
100
+ releaseNotes: releaseNotesContent,
101
+ ...forceUpdate ? { forceUpdate } : {}
102
+ };
103
+ const jsonPath = import_path.default.join(updatesDir, "latest.json");
104
+ import_fs.default.writeFileSync(jsonPath, JSON.stringify(updateInfo, null, 2));
105
+ console.log(`[RenderUpdater Builder] Successfully created package for v${version}`);
106
+ console.log(`[RenderUpdater Builder] ASAR: ${asarPath}`);
107
+ console.log(`[RenderUpdater Builder] Info: ${jsonPath}`);
108
+ }
109
+ // Annotate the CommonJS export names for ESM import in node:
110
+ 0 && (module.exports = {
111
+ createUpdatePackage
112
+ });
@@ -0,0 +1,2 @@
1
+ import type { BuilderOptions } from '../types';
2
+ export declare function createUpdatePackage(options: BuilderOptions): Promise<void>;
@@ -0,0 +1,77 @@
1
+ // src/builder/index.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import crypto from "crypto";
5
+ async function createUpdatePackage(options) {
6
+ const asar = await import("asar").catch(() => {
7
+ throw new Error('Please install "asar" as a devDependency to use the builder.');
8
+ });
9
+ const {
10
+ outDir,
11
+ updatesDir = path.resolve(process.cwd(), "dist_updates"),
12
+ asarName = "renderer.asar",
13
+ version: explicitVersion,
14
+ packageJsonPath,
15
+ privateKeyPath,
16
+ releaseNotesPath,
17
+ forceUpdate
18
+ } = options;
19
+ if (!fs.existsSync(outDir)) {
20
+ throw new Error(`Output directory not found at ${outDir}`);
21
+ }
22
+ if (!fs.existsSync(updatesDir)) {
23
+ fs.mkdirSync(updatesDir, { recursive: true });
24
+ }
25
+ let version;
26
+ if (explicitVersion) {
27
+ version = explicitVersion;
28
+ } else {
29
+ const pkgPath = packageJsonPath ?? path.resolve(process.cwd(), "package.json");
30
+ if (!fs.existsSync(pkgPath)) {
31
+ throw new Error(
32
+ `Cannot resolve version: package.json not found at ${pkgPath}. Please set "version" or "packageJsonPath" in the plugin options.`
33
+ );
34
+ }
35
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
36
+ version = pkg.version || "0.0.0";
37
+ }
38
+ const versionDir = path.join(updatesDir, version);
39
+ if (!fs.existsSync(versionDir)) {
40
+ fs.mkdirSync(versionDir, { recursive: true });
41
+ }
42
+ const asarPath = path.join(versionDir, asarName);
43
+ await asar.createPackage(outDir, asarPath);
44
+ const fileBuffer = fs.readFileSync(asarPath);
45
+ const hashSum = crypto.createHash("sha256");
46
+ hashSum.update(fileBuffer);
47
+ const sha256 = hashSum.digest("hex");
48
+ let signature = "";
49
+ if (privateKeyPath && fs.existsSync(privateKeyPath)) {
50
+ const privateKey = fs.readFileSync(privateKeyPath, "utf8");
51
+ const sign = crypto.createSign("RSA-SHA256");
52
+ sign.update(fileBuffer);
53
+ sign.end();
54
+ signature = sign.sign(privateKey, "base64");
55
+ }
56
+ let releaseNotesContent = "No release notes provided.";
57
+ if (releaseNotesPath && fs.existsSync(releaseNotesPath)) {
58
+ releaseNotesContent = fs.readFileSync(releaseNotesPath, "utf-8");
59
+ }
60
+ const updateInfo = {
61
+ version,
62
+ path: `${version}/${asarName}`,
63
+ sha256,
64
+ ...signature ? { signature } : {},
65
+ date: (/* @__PURE__ */ new Date()).toISOString(),
66
+ releaseNotes: releaseNotesContent,
67
+ ...forceUpdate ? { forceUpdate } : {}
68
+ };
69
+ const jsonPath = path.join(updatesDir, "latest.json");
70
+ fs.writeFileSync(jsonPath, JSON.stringify(updateInfo, null, 2));
71
+ console.log(`[RenderUpdater Builder] Successfully created package for v${version}`);
72
+ console.log(`[RenderUpdater Builder] ASAR: ${asarPath}`);
73
+ console.log(`[RenderUpdater Builder] Info: ${jsonPath}`);
74
+ }
75
+ export {
76
+ createUpdatePackage
77
+ };
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/builder/vite-plugin.ts
31
+ var vite_plugin_exports = {};
32
+ __export(vite_plugin_exports, {
33
+ electronRenderUpdater: () => electronRenderUpdater
34
+ });
35
+ module.exports = __toCommonJS(vite_plugin_exports);
36
+
37
+ // src/builder/index.ts
38
+ var import_fs = __toESM(require("fs"), 1);
39
+ var import_path = __toESM(require("path"), 1);
40
+ var import_crypto = __toESM(require("crypto"), 1);
41
+ async function createUpdatePackage(options) {
42
+ const asar = await import("asar").catch(() => {
43
+ throw new Error('Please install "asar" as a devDependency to use the builder.');
44
+ });
45
+ const {
46
+ outDir,
47
+ updatesDir = import_path.default.resolve(process.cwd(), "dist_updates"),
48
+ asarName = "renderer.asar",
49
+ version: explicitVersion,
50
+ packageJsonPath,
51
+ privateKeyPath,
52
+ releaseNotesPath,
53
+ forceUpdate
54
+ } = options;
55
+ if (!import_fs.default.existsSync(outDir)) {
56
+ throw new Error(`Output directory not found at ${outDir}`);
57
+ }
58
+ if (!import_fs.default.existsSync(updatesDir)) {
59
+ import_fs.default.mkdirSync(updatesDir, { recursive: true });
60
+ }
61
+ let version;
62
+ if (explicitVersion) {
63
+ version = explicitVersion;
64
+ } else {
65
+ const pkgPath = packageJsonPath ?? import_path.default.resolve(process.cwd(), "package.json");
66
+ if (!import_fs.default.existsSync(pkgPath)) {
67
+ throw new Error(
68
+ `Cannot resolve version: package.json not found at ${pkgPath}. Please set "version" or "packageJsonPath" in the plugin options.`
69
+ );
70
+ }
71
+ const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
72
+ version = pkg.version || "0.0.0";
73
+ }
74
+ const versionDir = import_path.default.join(updatesDir, version);
75
+ if (!import_fs.default.existsSync(versionDir)) {
76
+ import_fs.default.mkdirSync(versionDir, { recursive: true });
77
+ }
78
+ const asarPath = import_path.default.join(versionDir, asarName);
79
+ await asar.createPackage(outDir, asarPath);
80
+ const fileBuffer = import_fs.default.readFileSync(asarPath);
81
+ const hashSum = import_crypto.default.createHash("sha256");
82
+ hashSum.update(fileBuffer);
83
+ const sha256 = hashSum.digest("hex");
84
+ let signature = "";
85
+ if (privateKeyPath && import_fs.default.existsSync(privateKeyPath)) {
86
+ const privateKey = import_fs.default.readFileSync(privateKeyPath, "utf8");
87
+ const sign = import_crypto.default.createSign("RSA-SHA256");
88
+ sign.update(fileBuffer);
89
+ sign.end();
90
+ signature = sign.sign(privateKey, "base64");
91
+ }
92
+ let releaseNotesContent = "No release notes provided.";
93
+ if (releaseNotesPath && import_fs.default.existsSync(releaseNotesPath)) {
94
+ releaseNotesContent = import_fs.default.readFileSync(releaseNotesPath, "utf-8");
95
+ }
96
+ const updateInfo = {
97
+ version,
98
+ path: `${version}/${asarName}`,
99
+ sha256,
100
+ ...signature ? { signature } : {},
101
+ date: (/* @__PURE__ */ new Date()).toISOString(),
102
+ releaseNotes: releaseNotesContent,
103
+ ...forceUpdate ? { forceUpdate } : {}
104
+ };
105
+ const jsonPath = import_path.default.join(updatesDir, "latest.json");
106
+ import_fs.default.writeFileSync(jsonPath, JSON.stringify(updateInfo, null, 2));
107
+ console.log(`[RenderUpdater Builder] Successfully created package for v${version}`);
108
+ console.log(`[RenderUpdater Builder] ASAR: ${asarPath}`);
109
+ console.log(`[RenderUpdater Builder] Info: ${jsonPath}`);
110
+ }
111
+
112
+ // src/builder/vite-plugin.ts
113
+ function electronRenderUpdater(options) {
114
+ return {
115
+ name: "vite-plugin-electron-render-updater",
116
+ apply: "build",
117
+ closeBundle: async () => {
118
+ if (!process.env.ELECTRON_PACK_UPDATE) {
119
+ return;
120
+ }
121
+ console.log("\n[vite-plugin-electron-render-updater] Build finished, packing ASAR...");
122
+ try {
123
+ await createUpdatePackage(options);
124
+ } catch (err) {
125
+ console.error("[vite-plugin-electron-render-updater] Failed to pack ASAR:", err);
126
+ }
127
+ }
128
+ };
129
+ }
130
+ // Annotate the CommonJS export names for ESM import in node:
131
+ 0 && (module.exports = {
132
+ electronRenderUpdater
133
+ });
@@ -0,0 +1,5 @@
1
+ import type { BuilderOptions } from '../types';
2
+ /**
3
+ * Vite plugin to automatically pack the renderer asar after build.
4
+ */
5
+ export declare function electronRenderUpdater(options: BuilderOptions): any;