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.zh-CN.md +359 -0
- package/Readme.md +356 -0
- package/dist/builder/index.cjs +112 -0
- package/dist/builder/index.d.ts +2 -0
- package/dist/builder/index.js +77 -0
- package/dist/builder/vite-plugin.cjs +133 -0
- package/dist/builder/vite-plugin.d.ts +5 -0
- package/dist/builder/vite-plugin.js +96 -0
- package/dist/main/index.cjs +372 -0
- package/dist/main/index.d.ts +43 -0
- package/dist/main/index.js +337 -0
- package/dist/types.d.ts +105 -0
- package/package.json +63 -0
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,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
|
+
});
|