electron-incremental-update 0.8.4 → 0.8.6

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,234 +1,240 @@
1
- ## electron incremental updater
2
-
3
- This project provide a vite plugin, `Updater` class and some useful functions to generate incremental update.
4
-
5
- There will be two asar in production, `app.asar` and `main.asar` (if "main" is your app's name).
6
-
7
- The `app.asar` is used to load `main.asar` and initialize the `updater`. Also, all the **native modules**, which are set as `dependencies` in `package.json`, will be packaged into `app.asar` by `electron-builder`, [see usage](#use-native-modules).
8
-
9
- The new `main.asar` downloaded from remote will be verified by presigned RSA + Signature. When pass the check and restart, the old `main.asar` will be replaced by the new one. Hooks like `beforeDoUpdate` are provided.
10
-
11
- - inspired by Obsidian's update strategy
12
-
13
- ### notice
14
-
15
- - this plugin is developed with [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron), and may be effect in other electron vite frameworks
16
- - **all options are documented in the jsdoc**
17
- - entry file's EOL will force to `\n`
18
-
19
- ## install
20
-
21
- ### npm
22
- ```bash
23
- npm install electron-incremental-update
24
- ```
25
- ### yarn
26
- ```bash
27
- yarn add electron-incremental-update
28
- ```
29
- ### pnpm
30
- ```bash
31
- pnpm add electron-incremental-update
32
- ```
33
-
34
- ## setup
35
-
36
- base on [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
37
-
38
- ```
39
- electron
40
- ├── app.ts // <- add app entry file
41
- ├── electron-env.d.ts
42
- ├── main
43
- │ ├── db.ts
44
- │ ├── index.ts
45
- └── preload
46
- └── index.ts
47
- src
48
- └── ...
49
- ```
50
-
51
- ### setup app
52
-
53
- ```ts
54
- // electron/app.ts
55
- import { getGithubReleaseCdnGroup, initApp, parseGithubCdnURL } from 'electron-incremental-update'
56
- import { name, repository } from '../package.json'
57
-
58
- const SIGNATURE_CERT = '' // auto generate certificate when start app
59
-
60
- const { cdnPrefix: asarPrefix } = getGithubReleaseCdnGroup()[0]
61
- const { cdnPrefix: jsonPrefix } = getGithubFileCdnGroup()[0]
62
- initApp({ onStart: console.log })
63
- // can be updater option or function that return updater
64
- .setUpdater({
65
- SIGNATURE_CERT,
66
- productName: name,
67
- repository,
68
- updateJsonURL: parseGithubCdnURL(repository, jsonPrefix, 'version.json'),
69
- releaseAsarURL: parseGithubCdnURL(repository, asarPrefix, `download/latest/${name}.asar.gz`),
70
- receiveBeta: true
71
- })
72
- ```
73
-
74
- ### setup vite.config.ts
75
-
76
- make sure the plugin is set in the **last** build task
77
-
78
- - for `vite-plugin-electron`, set it to `preload` (the second object in the plugin option array)
79
-
80
- ```ts
81
- // vite.config.ts
82
- export default defineConfig(({ command }) => {
83
-
84
- const isBuild = command === 'build'
85
- // ...
86
-
87
- return {
88
- plugins: [
89
- electron([
90
- // main
91
- {
92
- // ...
93
- },
94
- // preload
95
- {
96
- // ...
97
- vite: {
98
- plugins: [
99
- updater({
100
- productName: pkg.name,
101
- version: pkg.version,
102
- isBuild,
103
- }),
104
- ],
105
- // ...
106
- }
107
- },
108
- // when using vite-plugin-electron-renderer
109
- {
110
- // ...
111
- }
112
- ]),
113
- // ...
114
- ],
115
- // ...
116
- }
117
- })
118
- ```
119
-
120
- ### modify package.json
121
-
122
- ```json
123
- {
124
- // ...
125
- "main": "app.js" // <- app entry file
126
- }
127
- ```
128
-
129
- ### config electron-builder
130
-
131
- ```js
132
- const { name } = require('./package.json')
133
-
134
- const target = `${name}.asar`
135
- /**
136
- * @type {import('electron-builder').Configuration}
137
- */
138
- module.exports = {
139
- appId: 'YourAppID',
140
- productName: name,
141
- files: [
142
- 'app.js', // <- app entry file
143
- '!**/{.eslintignore,.eslintrc.cjs,.editorconfig,.prettierignore,.prettierrc.yaml,dev-app-update.yml,LICENSE,.nvmrc,.npmrc}',
144
- '!**/{tsconfig.json,tsconfig.node.json,tsconfig.web.json}',
145
- '!**/*debug*.*',
146
- '!**/*.{md,zip,map}',
147
- '!**/*.{c,cpp,h,hpp,cc,hh,cxx,hxx,gypi,gyp,sh}',
148
- '!**/.{github,vscode}',
149
- '!node_modules/**/better-sqlite3/deps/**',
150
- ],
151
- asarUnpack: [
152
- '**/*.{node,dll}',
153
- ],
154
- directories: {
155
- output: 'release',
156
- },
157
- extraResources: [
158
- { from: `release/${target}`, to: target }, // <- asar file
159
- ],
160
- publish: null, // <- disable publish
161
- // ...
162
- }
163
- ```
164
-
165
- ## Usage
166
-
167
- ### use in main process
168
-
169
- To use electron's `net` module for updating, the `checkUpdate` and `download` functions must be called after the app is ready by default.
170
-
171
- However, you have the option to customize the download function when creating the updater.
172
-
173
- **NOTE: There can only be one function and should be default export in the entry file**
174
-
175
- ```ts
176
- // electron/main/index.ts
177
- import type { StartupWithUpdater, Updater } from 'electron-incremental-update'
178
- import { getEntryVersion, getProductAsarPath, getProductVersion } from 'electron-incremental-update'
179
- import { app } from 'electron'
180
- import { name } from '../../package.json'
181
-
182
- const startup: StartupWithUpdater = (updater: Updater) => {
183
- await app.whenReady()
184
- console.log('\ncurrent:')
185
- console.log(`\tasar path: ${getProductAsarPath(name)}`)
186
- console.log(`\tentry: ${getEntryVersion()}`)
187
- console.log(`\tapp: ${getProductVersion(name)}`)
188
- updater.onDownloading = ({ percent }) => {
189
- console.log(percent)
190
- }
191
- updater.logger = console
192
- updater.checkUpdate().then(async (result) => {
193
- if (result === undefined) {
194
- console.log('Update Unavailable')
195
- } else if (result instanceof Error) {
196
- console.error(result)
197
- } else {
198
- console.log('new version: ', result.version)
199
- const { response } = await dialog.showMessageBox({
200
- type: 'info',
201
- buttons: ['Download', 'Later'],
202
- message: 'Application update available!',
203
- })
204
- response === 0 && console.log(await updater.download())
205
- }
206
- })
207
- }
208
- export default startup
209
- ```
210
-
211
- ### use native modules
212
-
213
- ```ts
214
- // db.ts
215
- import { requireNative } from 'electron-incremental-update'
216
-
217
- const Database = requireNative<typeof import('better-sqlite3')>('better-sqlite3')
218
- const db = new Database(':memory:')
219
- db.exec(
220
- 'DROP TABLE IF EXISTS employees; '
221
- + 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
222
- )
223
-
224
- db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
225
- n: 'James',
226
- s: 50000,
227
- })
228
-
229
- const r = db.prepare('SELECT * from employees').all()
230
- console.log(r)
231
- // [ { name: 'James', salary: 50000 } ]
232
-
233
- db.close()
234
- ```
1
+ ## electron incremental updater
2
+
3
+ This project provide a vite plugin, `Updater` class and some useful functions to generate incremental update.
4
+
5
+ There will be two asar in production, `app.asar` and `main.asar` (if "main" is your app's name).
6
+
7
+ The `app.asar` is used to load `main.asar` and initialize the `updater`. Also, all the **native modules**, which are set as `dependencies` in `package.json`, will be packaged into `app.asar` by `electron-builder`, [see usage](#use-native-modules).
8
+
9
+ The new `main.asar` downloaded from remote will be verified by presigned RSA + Signature. When pass the check and restart, the old `main.asar` will be replaced by the new one. Hooks like `beforeDoUpdate` are provided.
10
+
11
+ - inspired by Obsidian's update strategy
12
+
13
+ ### notice
14
+
15
+ - this plugin is developed with [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron), and may be effect in other electron vite frameworks
16
+ - **all options are documented in the jsdoc**
17
+
18
+ ## install
19
+
20
+ ### npm
21
+ ```bash
22
+ npm install electron-incremental-update
23
+ ```
24
+ ### yarn
25
+ ```bash
26
+ yarn add electron-incremental-update
27
+ ```
28
+ ### pnpm
29
+ ```bash
30
+ pnpm add electron-incremental-update
31
+ ```
32
+
33
+ ## setup
34
+
35
+ base on [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
36
+
37
+ ```
38
+ electron
39
+ ├── app.ts // <- add app entry file
40
+ ├── electron-env.d.ts
41
+ ├── main
42
+ ├── db.ts
43
+ │ ├── index.ts
44
+ └── preload
45
+ └── index.ts
46
+ src
47
+ └── ...
48
+ ```
49
+
50
+ ### setup app
51
+
52
+ ```ts
53
+ // electron/app.ts
54
+ import { getGithubReleaseCdnGroup, initApp, parseGithubCdnURL } from 'electron-incremental-update'
55
+ import { name, repository } from '../package.json'
56
+
57
+ const SIGNATURE_CERT = '' // auto generate certificate when start app
58
+
59
+ const { cdnPrefix: asarPrefix } = getGithubReleaseCdnGroup()[0]
60
+ const { cdnPrefix: jsonPrefix } = getGithubFileCdnGroup()[0]
61
+ initApp({ onStart: console.log })
62
+ // can be updater option or function that return updater
63
+ .setUpdater({
64
+ SIGNATURE_CERT,
65
+ productName: name,
66
+ repository,
67
+ updateJsonURL: parseGithubCdnURL(repository, jsonPrefix, 'version.json'),
68
+ releaseAsarURL: parseGithubCdnURL(repository, asarPrefix, `download/latest/${name}.asar.gz`),
69
+ receiveBeta: true
70
+ })
71
+ ```
72
+
73
+ ### setup vite.config.ts
74
+
75
+ make sure the plugin is set in the **last** build task
76
+
77
+ - for `vite-plugin-electron`, set it to `preload` (the second object in the plugin option array)
78
+
79
+ ```ts
80
+ // vite.config.ts
81
+ export default defineConfig(({ command }) => {
82
+
83
+ const isBuild = command === 'build'
84
+ // ...
85
+
86
+ return {
87
+ plugins: [
88
+ electron([
89
+ // main
90
+ {
91
+ // ...
92
+ },
93
+ // preload
94
+ {
95
+ // ...
96
+ vite: {
97
+ plugins: [
98
+ updater({
99
+ productName: pkg.name,
100
+ version: pkg.version,
101
+ isBuild,
102
+ }),
103
+ ],
104
+ // ...
105
+ }
106
+ },
107
+ // when using vite-plugin-electron-renderer
108
+ {
109
+ // ...
110
+ }
111
+ ]),
112
+ // ...
113
+ ],
114
+ // ...
115
+ }
116
+ })
117
+ ```
118
+
119
+ ### modify package.json
120
+
121
+ ```json
122
+ {
123
+ // ...
124
+ "main": "app.js" // <- app entry file path
125
+ }
126
+ ```
127
+
128
+ ### config electron-builder
129
+
130
+ ```js
131
+ const { name } = require('./package.json')
132
+
133
+ const target = `${name}.asar`
134
+ /**
135
+ * @type {import('electron-builder').Configuration}
136
+ */
137
+ module.exports = {
138
+ appId: 'YourAppID',
139
+ productName: name,
140
+ files: [
141
+ 'app.js', // <- app entry file
142
+ '!**/{.eslintignore,.eslintrc.cjs,.editorconfig,.prettierignore,.prettierrc.yaml,dev-app-update.yml,LICENSE,.nvmrc,.npmrc}',
143
+ '!**/{tsconfig.json,tsconfig.node.json,tsconfig.web.json}',
144
+ '!**/*debug*.*',
145
+ '!**/*.{md,zip,map}',
146
+ '!**/*.{c,cpp,h,hpp,cc,hh,cxx,hxx,gypi,gyp,sh}',
147
+ '!**/.{github,vscode}',
148
+ '!node_modules/**/better-sqlite3/deps/**',
149
+ ],
150
+ asarUnpack: [
151
+ '**/*.{node,dll}',
152
+ ],
153
+ directories: {
154
+ output: 'release',
155
+ },
156
+ extraResources: [
157
+ { from: `release/${target}`, to: target }, // <- asar file
158
+ ],
159
+ publish: null, // <- disable publish
160
+ // ...
161
+ }
162
+ ```
163
+
164
+ ## Usage
165
+
166
+ ### use in main process
167
+
168
+ To use electron's `net` module for updating, the `checkUpdate` and `download` functions must be called after the app is ready by default.
169
+
170
+ However, you have the option to customize the download function when creating the updater.
171
+
172
+ **NOTE: There can only be one function and should be default export in the entry file**
173
+
174
+ ```ts
175
+ // electron/main/index.ts
176
+ import type { StartupWithUpdater, Updater } from 'electron-incremental-update'
177
+ import { getAppVersion, getElectronVersion, getProductAsarPath } from 'electron-incremental-update/utils'
178
+ import { app } from 'electron'
179
+ import { name } from '../../package.json'
180
+
181
+ const startup: StartupWithUpdater = (updater: Updater) => {
182
+ await app.whenReady()
183
+ console.log('\ncurrent:')
184
+ console.log(`\tasar path: ${getProductAsarPath(name)}`)
185
+ console.log(`\tapp: ${getAppVersion(name)}`)
186
+ console.log(`\telectron: ${getElectronVersion()}`)
187
+ updater.onDownloading = ({ percent }) => {
188
+ console.log(percent)
189
+ }
190
+ updater.logger = console
191
+ updater.checkUpdate().then(async (result) => {
192
+ if (result === undefined) {
193
+ console.log('Update Unavailable')
194
+ } else if (result instanceof Error) {
195
+ console.error(result)
196
+ } else {
197
+ console.log('new version: ', result.version)
198
+ const { response } = await dialog.showMessageBox({
199
+ type: 'info',
200
+ buttons: ['Download', 'Later'],
201
+ message: 'Application update available!',
202
+ })
203
+ response === 0 && console.log(await updater.download())
204
+ }
205
+ })
206
+ }
207
+ export default startup
208
+ ```
209
+
210
+ ### use native modules
211
+
212
+ the native modules is packed in `app.asar`, so you cannot directly access it when in production
213
+
214
+ to use it, you can prebundle native modules, or use `requireNative` to load.
215
+
216
+ ```ts
217
+ // db.ts
218
+ import { isNoSuchNativeModuleError, requireNative } from 'electron-incremental-update/utils'
219
+
220
+ const Database = requireNative<typeof import('better-sqlite3')>('better-sqlite3')
221
+ if (isNoSuchNativeModuleError(Database)) {
222
+ // ...
223
+ }
224
+ const db = new Database(':memory:')
225
+ db.exec(
226
+ 'DROP TABLE IF EXISTS employees; '
227
+ + 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
228
+ )
229
+
230
+ db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
231
+ n: 'James',
232
+ s: 50000,
233
+ })
234
+
235
+ const r = db.prepare('SELECT * from employees').all()
236
+ console.log(r)
237
+ // [ { name: 'James', salary: 50000 } ]
238
+
239
+ db.close()
240
+ ```
@@ -30,7 +30,14 @@ var verify = (buffer, signature2, cert) => {
30
30
  }
31
31
  };
32
32
 
33
+ // src/updateJson.ts
34
+ function isUpdateJSON(json) {
35
+ const is = (j) => "signature" in j && "version" in j && "size" in j && "minimumVersion" in j;
36
+ return is(json) && "beta" in json && is(json.beta);
37
+ }
38
+
33
39
  export {
34
40
  signature,
35
- verify
41
+ verify,
42
+ isUpdateJSON
36
43
  };
@@ -0,0 +1,75 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/utils/version.ts
10
+ function parseVersion(version) {
11
+ const semver = /^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9\.-]+))?/i;
12
+ const match = semver.exec(version);
13
+ if (!match) {
14
+ throw new TypeError(`invalid version: ${version}`);
15
+ }
16
+ const [major, minor, patch] = match.slice(1, 4).map(Number);
17
+ const ret = {
18
+ major,
19
+ minor,
20
+ patch,
21
+ stage: "",
22
+ stageVersion: -1
23
+ };
24
+ if (match[4]) {
25
+ let [stage, _v] = match[4].split(".");
26
+ ret.stage = stage;
27
+ ret.stageVersion = Number(_v) || -1;
28
+ }
29
+ if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || Number.isNaN(ret.stageVersion)) {
30
+ throw new TypeError(`invalid version: ${version}`);
31
+ }
32
+ return ret;
33
+ }
34
+
35
+ // src/utils/zip.ts
36
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
37
+ import { gunzip, gzip } from "node:zlib";
38
+ async function unzipFile(gzipPath, targetFilePath = gzipPath.slice(0, -3)) {
39
+ if (!existsSync(gzipPath)) {
40
+ throw new Error(`path to zipped file not exist: ${gzipPath}`);
41
+ }
42
+ const compressedBuffer = readFileSync(gzipPath);
43
+ return new Promise((resolve, reject) => {
44
+ gunzip(compressedBuffer, (err, buffer) => {
45
+ rmSync(gzipPath);
46
+ if (err) {
47
+ reject(err);
48
+ }
49
+ writeFileSync(targetFilePath, buffer);
50
+ resolve(null);
51
+ });
52
+ });
53
+ }
54
+ async function zipFile(filePath, targetFilePath = `${filePath}.gz`) {
55
+ if (!existsSync(filePath)) {
56
+ throw new Error(`path to be zipped not exist: ${filePath}`);
57
+ }
58
+ const buffer = readFileSync(filePath);
59
+ return new Promise((resolve, reject) => {
60
+ gzip(buffer, (err, buffer2) => {
61
+ if (err) {
62
+ reject(err);
63
+ }
64
+ writeFileSync(targetFilePath, buffer2);
65
+ resolve(null);
66
+ });
67
+ });
68
+ }
69
+
70
+ export {
71
+ __require,
72
+ parseVersion,
73
+ unzipFile,
74
+ zipFile
75
+ };