electron-incremental-update 1.0.2 → 1.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.
- package/README.md +549 -301
- package/dist/{chunk-OUZLSVQC.mjs → chunk-CXHA5TF7.js} +92 -4
- package/dist/{index.mjs → index.cjs} +172 -38
- package/dist/{index.d.mts → index.d.cts} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +53 -157
- package/dist/utils.cjs +270 -0
- package/dist/{utils.d.mts → utils.d.cts} +9 -11
- package/dist/utils.d.ts +9 -11
- package/dist/utils.js +21 -253
- package/dist/vite.d.ts +43 -23
- package/dist/vite.js +145 -146
- package/package.json +24 -27
- package/utils.js +1 -1
- package/dist/chunk-GB6VLKJZ.mjs +0 -95
- package/dist/chunk-GXZSAUBR.mjs +0 -36
- package/dist/utils.mjs +0 -40
- package/dist/vite.d.mts +0 -326
- package/dist/vite.mjs +0 -392
- /package/dist/{noDep-TvZoKVF8.d.mts → pure-GoN_3MEj.d.cts} +0 -0
- /package/dist/{noDep-TvZoKVF8.d.ts → pure-GoN_3MEj.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,301 +1,549 @@
|
|
|
1
|
-
## Electron Incremental Updater
|
|
2
|
-
|
|
3
|
-
This project is based on [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron), provide a plugin that build on top of `ElectronSimple`, an `Updater` class and some useful utils for Electron.
|
|
4
|
-
|
|
5
|
-
There will be two asar in production, `app.asar` and `${name}.asar` (
|
|
6
|
-
|
|
7
|
-
The `app.asar` is used to load `${name}.asar` and initialize the `Updater`.
|
|
8
|
-
|
|
9
|
-
The new `${name}.asar`, which can download from remote or load from buffer, will be verified by `Updater` using presigned RSA + Signature. While passing the check and restart, the old `${name}.asar` will be replaced by the new one. Hooks like `beforeDoUpdate` are provided.
|
|
10
|
-
|
|
11
|
-
All **native modules** should be packaged into `app.asar` to reduce `${name}.asar` file size, [see usage](#use-native-modules)
|
|
12
|
-
|
|
13
|
-
no `vite-plugin-electron-renderer` config
|
|
14
|
-
|
|
15
|
-
- inspired by [Obsidian](https://obsidian.md/)'s upgrade strategy
|
|
16
|
-
|
|
17
|
-
## Install
|
|
18
|
-
|
|
19
|
-
### npm
|
|
20
|
-
```bash
|
|
21
|
-
npm install -D vite-plugin-electron electron-incremental-update
|
|
22
|
-
```
|
|
23
|
-
### yarn
|
|
24
|
-
```bash
|
|
25
|
-
yarn add -D vite-plugin-electron electron-incremental-update
|
|
26
|
-
```
|
|
27
|
-
### pnpm
|
|
28
|
-
```bash
|
|
29
|
-
pnpm add -D vite-plugin-electron electron-incremental-update
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Getting started
|
|
33
|
-
|
|
34
|
-
### Project structure
|
|
35
|
-
|
|
36
|
-
base on [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
electron
|
|
40
|
-
├── entry.ts // <- entry file
|
|
41
|
-
├── main
|
|
42
|
-
│ └── index.ts
|
|
43
|
-
├── preload
|
|
44
|
-
│ └── index.ts
|
|
45
|
-
└── native // possible native modules
|
|
46
|
-
└── index.ts
|
|
47
|
-
src
|
|
48
|
-
└── ...
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Setup entry
|
|
52
|
-
|
|
53
|
-
in `electron/entry.ts` (build by `Esbuild`)
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
import { initApp } from 'electron-incremental-update'
|
|
57
|
-
import { parseGithubCdnURL } from 'electron-incremental-update/utils'
|
|
58
|
-
import { repository } from '../package.json'
|
|
59
|
-
|
|
60
|
-
const SIGNATURE_CERT = '' // auto generate certificate when start app
|
|
61
|
-
|
|
62
|
-
initApp({ onStart: console.log })
|
|
63
|
-
.setUpdater({
|
|
64
|
-
SIGNATURE_CERT,
|
|
65
|
-
// repository,
|
|
66
|
-
// updateJsonURL: parseGithubCdnURL(repository, 'https://your.cdn.url/', 'version.json'),
|
|
67
|
-
// releaseAsarURL: parseGithubCdnURL(repository, 'https://your.cdn.url/', `download/latest/${name}.asar.gz`),
|
|
68
|
-
// receiveBeta: true
|
|
69
|
-
})
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
- [some CDN resources](https://github.com/XIU2/UserScript/blob/master/GithubEnhanced-High-Speed-Download.user.js#L34):
|
|
73
|
-
|
|
74
|
-
### Setup `vite.config.ts`
|
|
75
|
-
|
|
76
|
-
All options are documented with JSDoc
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
- privatekey will read from `process.env.UPDATER_PK` first, if absend, read config
|
|
80
|
-
|
|
81
|
-
in `vite.config.mts`
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import { defineConfig } from 'vite'
|
|
85
|
-
import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
|
|
86
|
-
import pkg from './package.json'
|
|
87
|
-
|
|
88
|
-
export default defineConfig(async ({ command }) => {
|
|
89
|
-
const isBuild = command === 'build'
|
|
90
|
-
return {
|
|
91
|
-
plugins: [
|
|
92
|
-
electronWithUpdater({
|
|
93
|
-
pkg,
|
|
94
|
-
isBuild,
|
|
95
|
-
logParsedOptions: true,
|
|
96
|
-
main: {
|
|
97
|
-
files: ['./electron/main/index.ts', './electron/main/worker.ts'],
|
|
98
|
-
// see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
|
|
99
|
-
onstart: debugStartup,
|
|
100
|
-
},
|
|
101
|
-
preload: {
|
|
102
|
-
files: './electron/preload/index.ts',
|
|
103
|
-
},
|
|
104
|
-
updater: {
|
|
105
|
-
// options
|
|
106
|
-
}
|
|
107
|
-
}),
|
|
108
|
-
],
|
|
109
|
-
server: process.env.VSCODE_DEBUG && (() => {
|
|
110
|
-
const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
|
|
111
|
-
return {
|
|
112
|
-
host: url.hostname,
|
|
113
|
-
port: +url.port,
|
|
114
|
-
}
|
|
115
|
-
})(),
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Modify package.json
|
|
121
|
-
|
|
122
|
-
```json
|
|
123
|
-
{
|
|
124
|
-
"main": "dist-entry/entry.js" // <- entry file path
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Config electron-builder
|
|
129
|
-
|
|
130
|
-
```js
|
|
131
|
-
const { name } = require('./package.json')
|
|
132
|
-
|
|
133
|
-
const targetFile = `${name}.asar`
|
|
134
|
-
/**
|
|
135
|
-
* @type {import('electron-builder').Configuration}
|
|
136
|
-
*/
|
|
137
|
-
module.exports = {
|
|
138
|
-
appId: 'YourAppID',
|
|
139
|
-
productName: name,
|
|
140
|
-
files: [
|
|
141
|
-
// entry files
|
|
142
|
-
'dist-entry',
|
|
143
|
-
],
|
|
144
|
-
npmRebuild: false,
|
|
145
|
-
asarUnpack: [
|
|
146
|
-
'**/*.{node,dll,dylib,so}',
|
|
147
|
-
],
|
|
148
|
-
directories: {
|
|
149
|
-
output: 'release',
|
|
150
|
-
},
|
|
151
|
-
extraResources: [
|
|
152
|
-
{ from: `release/${targetFile}`, to: targetFile }, // <- asar file
|
|
153
|
-
],
|
|
154
|
-
// disable publish
|
|
155
|
-
publish: null,
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## Usage
|
|
160
|
-
|
|
161
|
-
###
|
|
162
|
-
|
|
163
|
-
To use electron's `net` module for updating, the `checkUpdate` and `download` functions must be called after the app is ready by default.
|
|
164
|
-
|
|
165
|
-
However, you have the option to customize the download function when creating the updater.
|
|
166
|
-
|
|
167
|
-
**NOTE: There should only one function and should be default export in the entry file**
|
|
168
|
-
|
|
169
|
-
in `electron/main/index.ts`
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
import { startupWithUpdater } from 'electron-incremental-update'
|
|
173
|
-
import { getPathFromAppNameAsar, getVersions } from 'electron-incremental-update/utils'
|
|
174
|
-
import { app } from 'electron'
|
|
175
|
-
|
|
176
|
-
export default startupWithUpdater((updater) => {
|
|
177
|
-
await app.whenReady()
|
|
178
|
-
|
|
179
|
-
const { appVersion, electronVersion, entryVersion } = getVersions()
|
|
180
|
-
console.log(`${app.name}.asar path`, getPathFromAppNameAsar())
|
|
181
|
-
console.log('app version:', appVersion)
|
|
182
|
-
console.log('entry (installer) version', entryVersion)
|
|
183
|
-
console.log('electron version', electronVersion)
|
|
184
|
-
|
|
185
|
-
updater.onDownloading = ({ percent }) => {
|
|
186
|
-
console.log(percent)
|
|
187
|
-
}
|
|
188
|
-
updater.logger = console
|
|
189
|
-
updater.checkUpdate().then(async (result) => {
|
|
190
|
-
if (result === undefined) {
|
|
191
|
-
console.log('Update Unavailable')
|
|
192
|
-
} else if (result instanceof Error) {
|
|
193
|
-
console.error(result)
|
|
194
|
-
} else {
|
|
195
|
-
console.log('new version: ', result.version)
|
|
196
|
-
const { response } = await dialog.showMessageBox({
|
|
197
|
-
type: 'info',
|
|
198
|
-
buttons: ['Download', 'Later'],
|
|
199
|
-
message: 'Application update available!',
|
|
200
|
-
})
|
|
201
|
-
if (response !== 0) {
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
const downloadResult = await updater.download()
|
|
205
|
-
if (downloadResult) {
|
|
206
|
-
updater.quitAndInstall()
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
})
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
###
|
|
214
|
-
|
|
215
|
-
All the **native modules** should be set as `dependency` in `package.json`. `electron-rebuild` only check dependencies inside `dependency` field.
|
|
216
|
-
|
|
217
|
-
If you are using `electron-builder` to build distributions, all the native modules with its **large relavent `node_modiles`** will be packaged into `app.asar` by default. You can setup `nativeModuleEntryMap` option to prebundle all the native modules and skip bundled by `electron-builder`
|
|
218
|
-
|
|
219
|
-
in `vite.config.ts`
|
|
220
|
-
|
|
221
|
-
```ts
|
|
222
|
-
const plugin = electronWithUpdater({
|
|
223
|
-
// options...
|
|
224
|
-
updater: {
|
|
225
|
-
entry: {
|
|
226
|
-
nativeModuleEntryMap: {
|
|
227
|
-
db: './electron/native/db.ts',
|
|
228
|
-
},
|
|
229
|
-
postBuild: async ({
|
|
230
|
-
// for better-sqlite3
|
|
231
|
-
|
|
232
|
-
from: './node_modules/better-sqlite3/build/Release/better_sqlite3.node',
|
|
233
|
-
skipIfExist: false,
|
|
234
|
-
})
|
|
235
|
-
// for @napi-rs/image
|
|
236
|
-
const startStr = '@napi-rs+image-'
|
|
237
|
-
const fileName = (await readdir('./node_modules/.pnpm')).filter(p => p.startsWith(startStr))[0]
|
|
238
|
-
const archName = fileName.substring(startStr.length).split('@')[0]
|
|
239
|
-
|
|
240
|
-
from: `./node_modules/.pnpm/${fileName}/node_modules/@napi-rs/image-${archName}/image.${archName}.node`,
|
|
241
|
-
})
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
})
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
in `electron/native/db.ts`
|
|
249
|
-
|
|
250
|
-
```ts
|
|
251
|
-
import Database from 'better-sqlite3'
|
|
252
|
-
import { getPaths } from 'electron-incremental-update/utils'
|
|
253
|
-
|
|
254
|
-
const db = new Database(':memory:', { nativeBinding: getPaths().getPathFromEntryAsar('better_sqlite3.node') })
|
|
255
|
-
|
|
256
|
-
export function test() {
|
|
257
|
-
db.exec(
|
|
258
|
-
'DROP TABLE IF EXISTS employees; '
|
|
259
|
-
+ 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
|
|
263
|
-
n: 'James',
|
|
264
|
-
s: 5000,
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
const r = db.prepare('SELECT * from employees').all()
|
|
268
|
-
console.log(r)
|
|
269
|
-
// [ { name: 'James', salary: 50000 } ]
|
|
270
|
-
|
|
271
|
-
db.close()
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
in `electron/main/service.ts`
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
|
-
import { loadNativeModuleFromEntry } from 'electron-incremental-update/utils'
|
|
279
|
-
|
|
280
|
-
const requireNative = loadNativeModuleFromEntry()
|
|
281
|
-
|
|
282
|
-
requireNative<typeof import('../native/db')>('db').test()
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
in `electron-builder.config.js`
|
|
286
|
-
|
|
287
|
-
```js
|
|
288
|
-
module.exports = {
|
|
289
|
-
files: [
|
|
290
|
-
'dist-entry',
|
|
291
|
-
// exclude
|
|
292
|
-
'!node_modules
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
1
|
+
## Electron Incremental Updater
|
|
2
|
+
|
|
3
|
+
This project is based on [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron), provide a plugin that build on top of `ElectronSimple`, an `Updater` class and some useful utils for Electron.
|
|
4
|
+
|
|
5
|
+
There will be two asar in production, `app.asar` and `${electron.app.name}.asar` (also as the `name` field in `package.json`).
|
|
6
|
+
|
|
7
|
+
The `app.asar` is used to load `${electron.app.name}.asar` and initialize the `Updater`.
|
|
8
|
+
|
|
9
|
+
The new `${electron.app.name}.asar`, which can download from remote or load from buffer, will be verified by `Updater` using presigned RSA + Signature. While passing the check and restart, the old `${electron.app.name}.asar` will be replaced by the new one. Hooks like `beforeDoUpdate` are provided.
|
|
10
|
+
|
|
11
|
+
All **native modules** should be packaged into `app.asar` to reduce `${electron.app.name}.asar` file size, [see usage](#use-native-modules). Therefore, auto upgrade of portable app is possible.
|
|
12
|
+
|
|
13
|
+
no `vite-plugin-electron-renderer` config
|
|
14
|
+
|
|
15
|
+
- inspired by [Obsidian](https://obsidian.md/)'s upgrade strategy
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
### npm
|
|
20
|
+
```bash
|
|
21
|
+
npm install -D vite-plugin-electron electron-incremental-update
|
|
22
|
+
```
|
|
23
|
+
### yarn
|
|
24
|
+
```bash
|
|
25
|
+
yarn add -D vite-plugin-electron electron-incremental-update
|
|
26
|
+
```
|
|
27
|
+
### pnpm
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add -D vite-plugin-electron electron-incremental-update
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Getting started
|
|
33
|
+
|
|
34
|
+
### Project structure
|
|
35
|
+
|
|
36
|
+
base on [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
electron
|
|
40
|
+
├── entry.ts // <- entry file
|
|
41
|
+
├── main
|
|
42
|
+
│ └── index.ts
|
|
43
|
+
├── preload
|
|
44
|
+
│ └── index.ts
|
|
45
|
+
└── native // <- possible native modules
|
|
46
|
+
└── index.ts
|
|
47
|
+
src
|
|
48
|
+
└── ...
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Setup entry
|
|
52
|
+
|
|
53
|
+
in `electron/entry.ts` (build by `Esbuild`)
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { initApp } from 'electron-incremental-update'
|
|
57
|
+
import { parseGithubCdnURL } from 'electron-incremental-update/utils'
|
|
58
|
+
import { repository } from '../package.json'
|
|
59
|
+
|
|
60
|
+
const SIGNATURE_CERT = '' // auto generate certificate when start app
|
|
61
|
+
|
|
62
|
+
initApp({ onStart: console.log })
|
|
63
|
+
.setUpdater({
|
|
64
|
+
SIGNATURE_CERT,
|
|
65
|
+
// repository,
|
|
66
|
+
// updateJsonURL: parseGithubCdnURL(repository, 'https://your.cdn.url/', 'version.json'),
|
|
67
|
+
// releaseAsarURL: parseGithubCdnURL(repository, 'https://your.cdn.url/', `download/latest/${name}.asar.gz`),
|
|
68
|
+
// receiveBeta: true
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- [some CDN resources](https://github.com/XIU2/UserScript/blob/master/GithubEnhanced-High-Speed-Download.user.js#L34):
|
|
73
|
+
|
|
74
|
+
### Setup `vite.config.ts`
|
|
75
|
+
|
|
76
|
+
All options are documented with JSDoc
|
|
77
|
+
|
|
78
|
+
- certificate will read from `process.env.UPDATER_CERT` first, if absend, read config
|
|
79
|
+
- privatekey will read from `process.env.UPDATER_PK` first, if absend, read config
|
|
80
|
+
|
|
81
|
+
in `vite.config.mts`
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { defineConfig } from 'vite'
|
|
85
|
+
import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
|
|
86
|
+
import pkg from './package.json'
|
|
87
|
+
|
|
88
|
+
export default defineConfig(async ({ command }) => {
|
|
89
|
+
const isBuild = command === 'build'
|
|
90
|
+
return {
|
|
91
|
+
plugins: [
|
|
92
|
+
electronWithUpdater({
|
|
93
|
+
pkg,
|
|
94
|
+
isBuild,
|
|
95
|
+
logParsedOptions: true,
|
|
96
|
+
main: {
|
|
97
|
+
files: ['./electron/main/index.ts', './electron/main/worker.ts'],
|
|
98
|
+
// see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
|
|
99
|
+
onstart: debugStartup,
|
|
100
|
+
},
|
|
101
|
+
preload: {
|
|
102
|
+
files: './electron/preload/index.ts',
|
|
103
|
+
},
|
|
104
|
+
updater: {
|
|
105
|
+
// options
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
],
|
|
109
|
+
server: process.env.VSCODE_DEBUG && (() => {
|
|
110
|
+
const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
|
|
111
|
+
return {
|
|
112
|
+
host: url.hostname,
|
|
113
|
+
port: +url.port,
|
|
114
|
+
}
|
|
115
|
+
})(),
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Modify package.json
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"main": "dist-entry/entry.js" // <- entry file path
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Config electron-builder
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
const { name } = require('./package.json')
|
|
132
|
+
|
|
133
|
+
const targetFile = `${name}.asar`
|
|
134
|
+
/**
|
|
135
|
+
* @type {import('electron-builder').Configuration}
|
|
136
|
+
*/
|
|
137
|
+
module.exports = {
|
|
138
|
+
appId: 'YourAppID',
|
|
139
|
+
productName: name,
|
|
140
|
+
files: [
|
|
141
|
+
// entry files
|
|
142
|
+
'dist-entry',
|
|
143
|
+
],
|
|
144
|
+
npmRebuild: false,
|
|
145
|
+
asarUnpack: [
|
|
146
|
+
'**/*.{node,dll,dylib,so}',
|
|
147
|
+
],
|
|
148
|
+
directories: {
|
|
149
|
+
output: 'release',
|
|
150
|
+
},
|
|
151
|
+
extraResources: [
|
|
152
|
+
{ from: `release/${targetFile}`, to: targetFile }, // <- asar file
|
|
153
|
+
],
|
|
154
|
+
// disable publish
|
|
155
|
+
publish: null,
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Usage
|
|
160
|
+
|
|
161
|
+
### Use in main process
|
|
162
|
+
|
|
163
|
+
To use electron's `net` module for updating, the `checkUpdate` and `download` functions must be called after the app is ready by default.
|
|
164
|
+
|
|
165
|
+
However, you have the option to customize the download function when creating the updater.
|
|
166
|
+
|
|
167
|
+
**NOTE: There should only one function and should be default export in the entry file**
|
|
168
|
+
|
|
169
|
+
in `electron/main/index.ts`
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { startupWithUpdater } from 'electron-incremental-update'
|
|
173
|
+
import { getPathFromAppNameAsar, getVersions } from 'electron-incremental-update/utils'
|
|
174
|
+
import { app } from 'electron'
|
|
175
|
+
|
|
176
|
+
export default startupWithUpdater((updater) => {
|
|
177
|
+
await app.whenReady()
|
|
178
|
+
|
|
179
|
+
const { appVersion, electronVersion, entryVersion } = getVersions()
|
|
180
|
+
console.log(`${app.name}.asar path`, getPathFromAppNameAsar())
|
|
181
|
+
console.log('app version:', appVersion)
|
|
182
|
+
console.log('entry (installer) version', entryVersion)
|
|
183
|
+
console.log('electron version', electronVersion)
|
|
184
|
+
|
|
185
|
+
updater.onDownloading = ({ percent }) => {
|
|
186
|
+
console.log(percent)
|
|
187
|
+
}
|
|
188
|
+
updater.logger = console
|
|
189
|
+
updater.checkUpdate().then(async (result) => {
|
|
190
|
+
if (result === undefined) {
|
|
191
|
+
console.log('Update Unavailable')
|
|
192
|
+
} else if (result instanceof Error) {
|
|
193
|
+
console.error(result)
|
|
194
|
+
} else {
|
|
195
|
+
console.log('new version: ', result.version)
|
|
196
|
+
const { response } = await dialog.showMessageBox({
|
|
197
|
+
type: 'info',
|
|
198
|
+
buttons: ['Download', 'Later'],
|
|
199
|
+
message: 'Application update available!',
|
|
200
|
+
})
|
|
201
|
+
if (response !== 0) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
const downloadResult = await updater.download()
|
|
205
|
+
if (downloadResult) {
|
|
206
|
+
updater.quitAndInstall()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Use native modules
|
|
214
|
+
|
|
215
|
+
All the **native modules** should be set as `dependency` in `package.json`. `electron-rebuild` only check dependencies inside `dependency` field.
|
|
216
|
+
|
|
217
|
+
If you are using `electron-builder` to build distributions, all the native modules with its **large relavent `node_modiles`** will be packaged into `app.asar` by default. You can setup `nativeModuleEntryMap` option to prebundle all the native modules and skip bundled by `electron-builder`
|
|
218
|
+
|
|
219
|
+
in `vite.config.ts`
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const plugin = electronWithUpdater({
|
|
223
|
+
// options...
|
|
224
|
+
updater: {
|
|
225
|
+
entry: {
|
|
226
|
+
nativeModuleEntryMap: {
|
|
227
|
+
db: './electron/native/db.ts',
|
|
228
|
+
},
|
|
229
|
+
postBuild: async ({ copyToEntryOutputDir }) => {
|
|
230
|
+
// for better-sqlite3
|
|
231
|
+
copyToEntryOutputDir({
|
|
232
|
+
from: './node_modules/better-sqlite3/build/Release/better_sqlite3.node',
|
|
233
|
+
skipIfExist: false,
|
|
234
|
+
})
|
|
235
|
+
// for @napi-rs/image
|
|
236
|
+
const startStr = '@napi-rs+image-'
|
|
237
|
+
const fileName = (await readdir('./node_modules/.pnpm')).filter(p => p.startsWith(startStr))[0]
|
|
238
|
+
const archName = fileName.substring(startStr.length).split('@')[0]
|
|
239
|
+
copyToEntryOutputDir({
|
|
240
|
+
from: `./node_modules/.pnpm/${fileName}/node_modules/@napi-rs/image-${archName}/image.${archName}.node`,
|
|
241
|
+
})
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
in `electron/native/db.ts`
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import Database from 'better-sqlite3'
|
|
252
|
+
import { getPaths } from 'electron-incremental-update/utils'
|
|
253
|
+
|
|
254
|
+
const db = new Database(':memory:', { nativeBinding: getPaths().getPathFromEntryAsar('better_sqlite3.node') })
|
|
255
|
+
|
|
256
|
+
export function test() {
|
|
257
|
+
db.exec(
|
|
258
|
+
'DROP TABLE IF EXISTS employees; '
|
|
259
|
+
+ 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
|
|
263
|
+
n: 'James',
|
|
264
|
+
s: 5000,
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const r = db.prepare('SELECT * from employees').all()
|
|
268
|
+
console.log(r)
|
|
269
|
+
// [ { name: 'James', salary: 50000 } ]
|
|
270
|
+
|
|
271
|
+
db.close()
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
in `electron/main/service.ts`
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { loadNativeModuleFromEntry } from 'electron-incremental-update/utils'
|
|
279
|
+
|
|
280
|
+
const requireNative = loadNativeModuleFromEntry()
|
|
281
|
+
|
|
282
|
+
requireNative<typeof import('../native/db')>('db').test()
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
in `electron-builder.config.js`
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
module.exports = {
|
|
289
|
+
files: [
|
|
290
|
+
'dist-entry',
|
|
291
|
+
// exclude dependencies in electron-builder config
|
|
292
|
+
'!node_modules/**',
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Bytecode protection
|
|
298
|
+
|
|
299
|
+
WIP
|
|
300
|
+
|
|
301
|
+
plan to use [electron-vite](https://github.com/alex8088/electron-vite/blob/master/src/plugins/bytecode.ts), but fail to load the default function in `${electron.app.name}.asar/dist-electron/index.js`.
|
|
302
|
+
|
|
303
|
+
try to wrap with [`Module.wrap`](https://github.com/bytenode/bytenode?tab=readme-ov-file#bytenodecompileelectroncodejavascriptcode-options--promisebuffer), but still fail.
|
|
304
|
+
|
|
305
|
+
### Types
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
type ElectronWithUpdaterOptions = {
|
|
309
|
+
/**
|
|
310
|
+
* whether is in build mode
|
|
311
|
+
* ```ts
|
|
312
|
+
* export default defineConfig(({ command }) => {
|
|
313
|
+
* const isBuild = command === 'build'
|
|
314
|
+
* })
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
isBuild: boolean
|
|
318
|
+
/**
|
|
319
|
+
* manullay setup package.json, read name, version and main
|
|
320
|
+
* ```ts
|
|
321
|
+
* import pkg from './package.json'
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
pkg?: PKG
|
|
325
|
+
/**
|
|
326
|
+
* whether to generate sourcemap
|
|
327
|
+
*/
|
|
328
|
+
sourcemap?: boolean
|
|
329
|
+
/**
|
|
330
|
+
* whether to minify the code
|
|
331
|
+
*/
|
|
332
|
+
minify?: boolean
|
|
333
|
+
/**
|
|
334
|
+
* use NotBundle() plugin in main
|
|
335
|
+
* @default true
|
|
336
|
+
*/
|
|
337
|
+
useNotBundle?: boolean
|
|
338
|
+
/**
|
|
339
|
+
* Whether to log parsed options
|
|
340
|
+
*/
|
|
341
|
+
logParsedOptions?: boolean
|
|
342
|
+
/**
|
|
343
|
+
* main options
|
|
344
|
+
*/
|
|
345
|
+
main: MakeRequiredAndReplaceKey<ElectronSimpleOptions['main'], 'entry', 'files'>
|
|
346
|
+
/**
|
|
347
|
+
* preload options
|
|
348
|
+
*/
|
|
349
|
+
preload: MakeRequiredAndReplaceKey<Exclude<ElectronSimpleOptions['preload'], undefined>, 'input', 'files'>
|
|
350
|
+
/**
|
|
351
|
+
* updater options
|
|
352
|
+
*/
|
|
353
|
+
updater?: ElectronUpdaterOptions
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
type ElectronUpdaterOptions = {
|
|
357
|
+
/**
|
|
358
|
+
* mini version of entry
|
|
359
|
+
* @default '0.0.0'
|
|
360
|
+
*/
|
|
361
|
+
minimumVersion?: string
|
|
362
|
+
/**
|
|
363
|
+
* config for entry (app.asar)
|
|
364
|
+
*/
|
|
365
|
+
entry?: BuildEntryOption
|
|
366
|
+
/**
|
|
367
|
+
* paths config
|
|
368
|
+
*/
|
|
369
|
+
paths?: {
|
|
370
|
+
/**
|
|
371
|
+
* Path to asar file
|
|
372
|
+
* @default `release/${app.name}.asar`
|
|
373
|
+
*/
|
|
374
|
+
asarOutputPath?: string
|
|
375
|
+
/**
|
|
376
|
+
* Path to version info output, content is {@link UpdateJSON}
|
|
377
|
+
* @default `version.json`
|
|
378
|
+
*/
|
|
379
|
+
versionPath?: string
|
|
380
|
+
/**
|
|
381
|
+
* Path to gzipped asar file
|
|
382
|
+
* @default `release/${app.name}-${version}.asar.gz`
|
|
383
|
+
*/
|
|
384
|
+
gzipPath?: string
|
|
385
|
+
/**
|
|
386
|
+
* Path to electron build output
|
|
387
|
+
* @default `dist-electron`
|
|
388
|
+
*/
|
|
389
|
+
electronDistPath?: string
|
|
390
|
+
/**
|
|
391
|
+
* Path to renderer build output
|
|
392
|
+
* @default `dist`
|
|
393
|
+
*/
|
|
394
|
+
rendererDistPath?: string
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* signature config
|
|
398
|
+
*/
|
|
399
|
+
keys?: {
|
|
400
|
+
/**
|
|
401
|
+
* path to the pem file that contains private key
|
|
402
|
+
* if not ended with .pem, it will be appended
|
|
403
|
+
*
|
|
404
|
+
* **if `UPDATER_PK` is set, will read it instead of read from `privateKeyPath`**
|
|
405
|
+
* @default 'keys/private.pem'
|
|
406
|
+
*/
|
|
407
|
+
privateKeyPath?: string
|
|
408
|
+
/**
|
|
409
|
+
* path to the pem file that contains public key
|
|
410
|
+
* if not ended with .pem, it will be appended
|
|
411
|
+
*
|
|
412
|
+
* **if `UPDATER_CERT` is set, will read it instead of read from `certPath`**
|
|
413
|
+
* @default 'keys/cert.pem'
|
|
414
|
+
*/
|
|
415
|
+
certPath?: string
|
|
416
|
+
/**
|
|
417
|
+
* length of the key
|
|
418
|
+
* @default 2048
|
|
419
|
+
*/
|
|
420
|
+
keyLength?: number
|
|
421
|
+
/**
|
|
422
|
+
* X509 certificate info
|
|
423
|
+
*
|
|
424
|
+
* only generate simple **self-signed** certificate **without extensions**
|
|
425
|
+
*/
|
|
426
|
+
certInfo?: {
|
|
427
|
+
/**
|
|
428
|
+
* the subject of the certificate
|
|
429
|
+
*
|
|
430
|
+
* @default { commonName: `${app.name}`, organizationName: `org.${app.name}` }
|
|
431
|
+
*/
|
|
432
|
+
subject?: DistinguishedName
|
|
433
|
+
/**
|
|
434
|
+
* expire days of the certificate
|
|
435
|
+
*
|
|
436
|
+
* @default 3650
|
|
437
|
+
*/
|
|
438
|
+
days?: number
|
|
439
|
+
}
|
|
440
|
+
overrideGenerator?: GeneratorOverrideFunctions
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
type BuildEntryOption = {
|
|
445
|
+
/**
|
|
446
|
+
* whether to minify
|
|
447
|
+
* @default isBuild
|
|
448
|
+
*/
|
|
449
|
+
minify?: boolean
|
|
450
|
+
/**
|
|
451
|
+
* whether to generate sourcemap
|
|
452
|
+
* @default isBuild
|
|
453
|
+
*/
|
|
454
|
+
sourcemap?: boolean
|
|
455
|
+
/**
|
|
456
|
+
* path to app entry output file
|
|
457
|
+
* @default 'dist-entry'
|
|
458
|
+
*/
|
|
459
|
+
entryOutputDirPath?: string
|
|
460
|
+
/**
|
|
461
|
+
* path to app entry file
|
|
462
|
+
* @default 'electron/entry.ts'
|
|
463
|
+
*/
|
|
464
|
+
appEntryPath?: string
|
|
465
|
+
/**
|
|
466
|
+
* esbuild path map of native modules in entry directory
|
|
467
|
+
*
|
|
468
|
+
* @default {}
|
|
469
|
+
* @example
|
|
470
|
+
* { db: './electron/native/db.ts' }
|
|
471
|
+
*/
|
|
472
|
+
nativeModuleEntryMap?: Record<string, string>
|
|
473
|
+
/**
|
|
474
|
+
* custom options for esbuild
|
|
475
|
+
* ```ts
|
|
476
|
+
* // default options
|
|
477
|
+
* const options = {
|
|
478
|
+
* entryPoints: {
|
|
479
|
+
* entry: appEntryPath,
|
|
480
|
+
* ...moduleEntryMap,
|
|
481
|
+
* },
|
|
482
|
+
* bundle: true,
|
|
483
|
+
* platform: 'node',
|
|
484
|
+
* outdir: entryOutputDirPath,
|
|
485
|
+
* minify,
|
|
486
|
+
* sourcemap,
|
|
487
|
+
* entryNames: '[dir]/[name]',
|
|
488
|
+
* assetNames: '[dir]/[name]',
|
|
489
|
+
* external: ['electron', 'original-fs'],
|
|
490
|
+
* loader: {
|
|
491
|
+
* '.node': 'empty',
|
|
492
|
+
* },
|
|
493
|
+
* }
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
overrideEsbuildOptions?: BuildOptions
|
|
497
|
+
/**
|
|
498
|
+
* resolve extra files on startup, such as `.node`
|
|
499
|
+
* @remark won't trigger will reload
|
|
500
|
+
*/
|
|
501
|
+
postBuild?: (args: {
|
|
502
|
+
/**
|
|
503
|
+
* get path from `entryOutputDirPath`
|
|
504
|
+
*/
|
|
505
|
+
getPathFromEntryOutputDir: (...paths: string[]) => string
|
|
506
|
+
/**
|
|
507
|
+
* check exist and copy file to `entryOutputDirPath`
|
|
508
|
+
*
|
|
509
|
+
* if `to` absent, set to `basename(from)`
|
|
510
|
+
*
|
|
511
|
+
* if `skipIfExist` absent, skip copy if `to` exist
|
|
512
|
+
*/
|
|
513
|
+
copyToEntryOutputDir: (options: {
|
|
514
|
+
from: string
|
|
515
|
+
to?: string
|
|
516
|
+
/**
|
|
517
|
+
* skip copy if `to` exist
|
|
518
|
+
* @default true
|
|
519
|
+
*/
|
|
520
|
+
skipIfExist?: boolean
|
|
521
|
+
}) => void
|
|
522
|
+
}) => Promisable<void>
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
type GeneratorOverrideFunctions = {
|
|
526
|
+
/**
|
|
527
|
+
* custom signature generate function
|
|
528
|
+
* @param buffer file buffer
|
|
529
|
+
* @param privateKey private key
|
|
530
|
+
* @param cert certificate string, **EOL must be '\n'**
|
|
531
|
+
* @param version current version
|
|
532
|
+
*/
|
|
533
|
+
generateSignature?: (buffer: Buffer, privateKey: string, cert: string, version: string) => string | Promise<string>
|
|
534
|
+
/**
|
|
535
|
+
* custom generate version json function
|
|
536
|
+
* @param existingJson The existing JSON object.
|
|
537
|
+
* @param buffer file buffer
|
|
538
|
+
* @param signature generated signature
|
|
539
|
+
* @param version current version
|
|
540
|
+
* @param minVersion The minimum version
|
|
541
|
+
* @returns The updated version json
|
|
542
|
+
*/
|
|
543
|
+
generateVersionJson?: (existingJson: UpdateJSON, buffer: Buffer, signature: string, version: string, minVersion: string) => UpdateJSON | Promise<UpdateJSON>
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## License
|
|
548
|
+
|
|
549
|
+
MIT
|