electron-incremental-update 0.8.4 → 0.8.5

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.
Files changed (4) hide show
  1. package/README.md +233 -234
  2. package/dist/vite.js +13 -10
  3. package/dist/vite.mjs +13 -10
  4. package/package.json +10 -9
package/README.md CHANGED
@@ -1,234 +1,233 @@
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
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 { getEntryVersion, getProductAsarPath, getProductVersion } from 'electron-incremental-update'
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(`\tentry: ${getEntryVersion()}`)
186
+ console.log(`\tapp: ${getProductVersion(name)}`)
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
+ ```ts
213
+ // db.ts
214
+ import { requireNative } from 'electron-incremental-update'
215
+
216
+ const Database = requireNative<typeof import('better-sqlite3')>('better-sqlite3')
217
+ const db = new Database(':memory:')
218
+ db.exec(
219
+ 'DROP TABLE IF EXISTS employees; '
220
+ + 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
221
+ )
222
+
223
+ db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
224
+ n: 'James',
225
+ s: 50000,
226
+ })
227
+
228
+ const r = db.prepare('SELECT * from employees').all()
229
+ console.log(r)
230
+ // [ { name: 'James', salary: 50000 } ]
231
+
232
+ db.close()
233
+ ```
package/dist/vite.js CHANGED
@@ -200,7 +200,6 @@ var import_ci_info = require("ci-info");
200
200
  // src/build-plugins/key.ts
201
201
  var import_node_fs3 = require("fs");
202
202
  var import_node_path2 = require("path");
203
- var import_node_os = require("os");
204
203
  var import_selfsigned = require("selfsigned");
205
204
  function generateKeyPair(keyLength, subject, days, privateKeyPath, certPath) {
206
205
  const privateKeyDir = (0, import_node_path2.dirname)(privateKeyPath);
@@ -217,15 +216,18 @@ function generateKeyPair(keyLength, subject, days, privateKeyPath, certPath) {
217
216
  }
218
217
  function writeCertToMain(entryPath, cert) {
219
218
  const file = (0, import_node_fs3.readFileSync)(entryPath, "utf-8");
220
- const regex = /const SIGNATURE_CERT\s*=\s*['"`][\s\S]*?['"`]/;
221
- const replacement = `const SIGNATURE_CERT = \`${cert}\``;
219
+ const initRegex = /(?<=const SIGNATURE_CERT\s*=\s*)['"]{2}/m;
220
+ const existRegex = /(?<=const SIGNATURE_CERT\s*=\s*)(['"]-----BEGIN CERTIFICATE-----[\s\S]*-----END CERTIFICATE-----\\n['"])/m;
221
+ const eol = file.includes("\r") ? "\r\n" : "\n";
222
+ const replacement = cert.split("\n").filter(Boolean).map((s) => `'${s}\\n'`).join(`${eol}+ `);
222
223
  let replaced = file;
223
- const signaturePubExists = regex.test(file);
224
- if (signaturePubExists) {
225
- replaced = file.replace(regex, replacement);
224
+ if (initRegex.test(file)) {
225
+ replaced = file.replace(initRegex, replacement);
226
+ } else if (existRegex.test(file)) {
227
+ replaced = file.replace(existRegex, replacement);
226
228
  } else {
227
- const lines = file.split(import_node_os.EOL);
228
- const r = `${import_node_os.EOL}${replacement}${import_node_os.EOL}`;
229
+ const lines = file.split(eol);
230
+ const r = `${eol}const SIGNATURE_CERT = ${replacement}${eol}`;
229
231
  let isMatched = false;
230
232
  for (let i = 0; i < lines.length; i++) {
231
233
  const line = lines[i];
@@ -236,9 +238,10 @@ function writeCertToMain(entryPath, cert) {
236
238
  }
237
239
  }
238
240
  !isMatched && lines.push(r);
239
- replaced = lines.join(import_node_os.EOL);
241
+ replaced = lines.join(eol);
240
242
  }
241
- (0, import_node_fs3.writeFileSync)(entryPath, replaced.replace(/\r\n?/g, "\n"));
243
+ console.log(JSON.stringify(replaced));
244
+ (0, import_node_fs3.writeFileSync)(entryPath, replaced);
242
245
  }
243
246
  function parseKeys({
244
247
  keyLength,
package/dist/vite.mjs CHANGED
@@ -103,7 +103,6 @@ import { isCI } from "ci-info";
103
103
  // src/build-plugins/key.ts
104
104
  import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
105
105
  import { dirname } from "node:path";
106
- import { EOL } from "node:os";
107
106
  import { generate } from "selfsigned";
108
107
  function generateKeyPair(keyLength, subject, days, privateKeyPath, certPath) {
109
108
  const privateKeyDir = dirname(privateKeyPath);
@@ -120,15 +119,18 @@ function generateKeyPair(keyLength, subject, days, privateKeyPath, certPath) {
120
119
  }
121
120
  function writeCertToMain(entryPath, cert) {
122
121
  const file = readFileSync(entryPath, "utf-8");
123
- const regex = /const SIGNATURE_CERT\s*=\s*['"`][\s\S]*?['"`]/;
124
- const replacement = `const SIGNATURE_CERT = \`${cert}\``;
122
+ const initRegex = /(?<=const SIGNATURE_CERT\s*=\s*)['"]{2}/m;
123
+ const existRegex = /(?<=const SIGNATURE_CERT\s*=\s*)(['"]-----BEGIN CERTIFICATE-----[\s\S]*-----END CERTIFICATE-----\\n['"])/m;
124
+ const eol = file.includes("\r") ? "\r\n" : "\n";
125
+ const replacement = cert.split("\n").filter(Boolean).map((s) => `'${s}\\n'`).join(`${eol}+ `);
125
126
  let replaced = file;
126
- const signaturePubExists = regex.test(file);
127
- if (signaturePubExists) {
128
- replaced = file.replace(regex, replacement);
127
+ if (initRegex.test(file)) {
128
+ replaced = file.replace(initRegex, replacement);
129
+ } else if (existRegex.test(file)) {
130
+ replaced = file.replace(existRegex, replacement);
129
131
  } else {
130
- const lines = file.split(EOL);
131
- const r = `${EOL}${replacement}${EOL}`;
132
+ const lines = file.split(eol);
133
+ const r = `${eol}const SIGNATURE_CERT = ${replacement}${eol}`;
132
134
  let isMatched = false;
133
135
  for (let i = 0; i < lines.length; i++) {
134
136
  const line = lines[i];
@@ -139,9 +141,10 @@ function writeCertToMain(entryPath, cert) {
139
141
  }
140
142
  }
141
143
  !isMatched && lines.push(r);
142
- replaced = lines.join(EOL);
144
+ replaced = lines.join(eol);
143
145
  }
144
- writeFileSync(entryPath, replaced.replace(/\r\n?/g, "\n"));
146
+ console.log(JSON.stringify(replaced));
147
+ writeFileSync(entryPath, replaced);
145
148
  }
146
149
  function parseKeys({
147
150
  keyLength,
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "electron-incremental-update",
3
3
  "author": "subframe7536",
4
- "version": "0.8.4",
4
+ "version": "0.8.5",
5
5
  "description": "electron incremental update tools, powered by vite",
6
6
  "scripts": {
7
7
  "build": "tsup && node fix-module.js",
8
8
  "release": "pnpm test && pnpm run build && bumpp --all && npm publish",
9
- "test": "vitest --run"
9
+ "test": "vitest --run",
10
+ "lint": "eslint . --fix"
10
11
  },
11
12
  "publishConfig": {
12
13
  "access": "public",
@@ -43,14 +44,14 @@
43
44
  "updater"
44
45
  ],
45
46
  "devDependencies": {
46
- "@subframe7536/eslint-config": "^0.2.5",
47
- "@types/node": "^20.5.1",
48
- "bumpp": "^9.1.1",
49
- "electron": "^26.0.0",
50
- "eslint": "^8.47.0",
47
+ "@subframe7536/eslint-config": "^0.2.9",
48
+ "@types/node": "^20.6.0",
49
+ "bumpp": "^9.2.0",
50
+ "electron": "^26.2.0",
51
+ "eslint": "^8.49.0",
51
52
  "tsup": "^7.2.0",
52
- "typescript": "^5.1.6",
53
- "vitest": "^0.34.2"
53
+ "typescript": "^5.2.2",
54
+ "vitest": "^0.34.4"
54
55
  },
55
56
  "dependencies": {
56
57
  "@electron/asar": "^3.2.4",