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