electron-incremental-update 1.1.0 → 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
@@ -57,16 +57,17 @@ import { initApp } from 'electron-incremental-update'
57
57
  import { parseGithubCdnURL } from 'electron-incremental-update/utils'
58
58
  import { repository } from '../package.json'
59
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
- })
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
+ })
70
71
  ```
71
72
 
72
73
  - [some CDN resources](https://github.com/XIU2/UserScript/blob/master/GithubEnhanced-High-Speed-Download.user.js#L34):
@@ -83,14 +84,12 @@ in `vite.config.mts`
83
84
  ```ts
84
85
  import { defineConfig } from 'vite'
85
86
  import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
86
- import pkg from './package.json'
87
87
 
88
88
  export default defineConfig(async ({ command }) => {
89
89
  const isBuild = command === 'build'
90
90
  return {
91
91
  plugins: [
92
92
  electronWithUpdater({
93
- pkg,
94
93
  isBuild,
95
94
  logParsedOptions: true,
96
95
  main: {
@@ -296,11 +295,19 @@ module.exports = {
296
295
 
297
296
  ### Bytecode protection
298
297
 
299
- WIP
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
+ ```
300
306
 
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`.
307
+ #### Limitation
302
308
 
303
- try to wrap with [`Module.wrap`](https://github.com/bytenode/bytenode?tab=readme-ov-file#bytenodecompileelectroncodejavascriptcode-options--promisebuffer), but still fail.
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
304
311
 
305
312
  ### Types
306
313
 
@@ -316,7 +323,7 @@ type ElectronWithUpdaterOptions = {
316
323
  */
317
324
  isBuild: boolean
318
325
  /**
319
- * manullay setup package.json, read name, version and main
326
+ * manually setup package.json, read name, version and main
320
327
  * ```ts
321
328
  * import pkg from './package.json'
322
329
  * ```
@@ -330,6 +337,14 @@ type ElectronWithUpdaterOptions = {
330
337
  * whether to minify the code
331
338
  */
332
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
333
348
  /**
334
349
  * use NotBundle() plugin in main
335
350
  * @default true
@@ -337,8 +352,10 @@ type ElectronWithUpdaterOptions = {
337
352
  useNotBundle?: boolean
338
353
  /**
339
354
  * Whether to log parsed options
355
+ *
356
+ * to show certificate and private keys, set `logParsedOptions: { showKeys: true }`
340
357
  */
341
- logParsedOptions?: boolean
358
+ logParsedOptions?: boolean | { showKeys: boolean }
342
359
  /**
343
360
  * main options
344
361
  */
@@ -471,7 +488,7 @@ type BuildEntryOption = {
471
488
  */
472
489
  nativeModuleEntryMap?: Record<string, string>
473
490
  /**
474
- * custom options for esbuild
491
+ * override options for esbuild
475
492
  * ```ts
476
493
  * // default options
477
494
  * const options = {
@@ -490,6 +507,9 @@ type BuildEntryOption = {
490
507
  * loader: {
491
508
  * '.node': 'empty',
492
509
  * },
510
+ * define: {
511
+ * __SIGNATURE_CERT__: JSON.stringify(cert),
512
+ * },
493
513
  * }
494
514
  * ```
495
515
  */
@@ -0,0 +1,236 @@
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") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/utils/electron.ts
9
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
10
+ import { basename, dirname, join } from "node:path";
11
+ import { release } from "node:os";
12
+ import { app } from "electron";
13
+ var is = {
14
+ dev: !app.isPackaged,
15
+ win: process.platform === "win32",
16
+ mac: process.platform === "darwin",
17
+ linux: process.platform === "linux"
18
+ };
19
+ function getPathFromAppNameAsar(...path) {
20
+ return is.dev ? "DEV.asar" : join(dirname(app.getAppPath()), `${electron.app.name}.asar`, ...path);
21
+ }
22
+ function getVersions() {
23
+ const platform = is.win ? "Windows" : is.mac ? "MacOS" : process.platform.toUpperCase();
24
+ return {
25
+ appVersion: is.dev ? app.getVersion() : readFileSync(getPathFromAppNameAsar("version"), "utf-8"),
26
+ entryVersion: app.getVersion(),
27
+ electronVersion: process.versions.electron,
28
+ nodeVersion: process.versions.node,
29
+ systemVersion: `${platform} ${release()}`
30
+ };
31
+ }
32
+ function loadNativeModuleFromEntry(devEntryDirPath = "../../dist-entry", entryDirPath = join(app.getAppPath(), basename(devEntryDirPath))) {
33
+ const path = is.dev ? devEntryDirPath : entryDirPath;
34
+ return (moduleName) => {
35
+ try {
36
+ return __require(join(path, moduleName));
37
+ } catch (error) {
38
+ console.error("fail to load module", error);
39
+ }
40
+ };
41
+ }
42
+ function restartApp() {
43
+ app.relaunch();
44
+ app.quit();
45
+ }
46
+ function setAppUserModelId(id) {
47
+ app.setAppUserModelId(is.dev ? process.execPath : id ?? `org.${electron.app.name}`);
48
+ }
49
+ function disableHWAccForWin7() {
50
+ if (release().startsWith("6.1")) {
51
+ app.disableHardwareAcceleration();
52
+ }
53
+ }
54
+ function singleInstance(window) {
55
+ const result = app.requestSingleInstanceLock();
56
+ result ? app.on("second-instance", () => {
57
+ if (window) {
58
+ window.show();
59
+ if (window.isMinimized()) {
60
+ window.restore();
61
+ }
62
+ window.focus();
63
+ }
64
+ }) : app.quit();
65
+ return result;
66
+ }
67
+ function setPortableAppDataPath(dirName = "data") {
68
+ const portablePath = join(dirname(app.getPath("exe")), dirName);
69
+ if (!existsSync(portablePath)) {
70
+ mkdirSync(portablePath);
71
+ }
72
+ app.setPath("appData", portablePath);
73
+ }
74
+ function waitAppReady(timeout = 1e3) {
75
+ return app.isReady() ? Promise.resolve() : new Promise((resolve, reject) => {
76
+ const _ = setTimeout(() => {
77
+ reject(new Error("app is not ready"));
78
+ }, timeout);
79
+ app.whenReady().then(() => {
80
+ clearTimeout(_);
81
+ resolve();
82
+ });
83
+ });
84
+ }
85
+ function getPaths(entryDirName = "dist-entry") {
86
+ const root = join(__dirname, "..");
87
+ const mainDirPath = join(root, "main");
88
+ const preloadDirPath = join(root, "preload");
89
+ const rendererDirPath = join(root, "renderer");
90
+ const devServerURL = process.env.VITE_DEV_SERVER_URL;
91
+ const indexHTMLPath = join(rendererDirPath, "index.html");
92
+ const publicDirPath = devServerURL ? join(root, "../public") : rendererDirPath;
93
+ return {
94
+ /**
95
+ * @example
96
+ * ```ts
97
+ * devServerURL && win.loadURL(devServerURL)
98
+ * ```
99
+ */
100
+ devServerURL,
101
+ /**
102
+ * @example
103
+ * ```ts
104
+ * win.loadFile(indexHTMLPath)
105
+ * ```
106
+ */
107
+ indexHTMLPath,
108
+ /**
109
+ * get path inside entry asar
110
+ * @param paths joined path
111
+ */
112
+ getPathFromEntryAsar(...paths) {
113
+ return join(app.getAppPath(), entryDirName, ...paths);
114
+ },
115
+ /**
116
+ * get path inside `${electron.app.name}.asar/main`
117
+ * @param paths joined path
118
+ */
119
+ getPathFromMain(...paths) {
120
+ return join(mainDirPath, ...paths);
121
+ },
122
+ /**
123
+ * get path inside `${electron.app.name}.asar/preload`
124
+ * @param paths joined path
125
+ */
126
+ getPathFromPreload(...paths) {
127
+ return join(preloadDirPath, ...paths);
128
+ },
129
+ /**
130
+ * get path inside public dir
131
+ * @param paths joined path
132
+ */
133
+ getPathFromPublic(...paths) {
134
+ return join(publicDirPath, ...paths);
135
+ }
136
+ };
137
+ }
138
+
139
+ // src/utils/zip.ts
140
+ import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync, writeFileSync } from "node:fs";
141
+ import { brotliCompress, brotliDecompress } from "node:zlib";
142
+ async function unzipFile(gzipPath, targetFilePath = gzipPath.slice(0, -3)) {
143
+ if (!existsSync2(gzipPath)) {
144
+ throw new Error(`path to zipped file not exist: ${gzipPath}`);
145
+ }
146
+ const compressedBuffer = readFileSync2(gzipPath);
147
+ return new Promise((resolve, reject) => {
148
+ brotliDecompress(compressedBuffer, (err, buffer) => {
149
+ rmSync(gzipPath);
150
+ if (err) {
151
+ reject(err);
152
+ }
153
+ writeFileSync(targetFilePath, buffer);
154
+ resolve(null);
155
+ });
156
+ });
157
+ }
158
+ async function zipFile(filePath, targetFilePath = `${filePath}.gz`) {
159
+ if (!existsSync2(filePath)) {
160
+ throw new Error(`path to be zipped not exist: ${filePath}`);
161
+ }
162
+ const buffer = readFileSync2(filePath);
163
+ return new Promise((resolve, reject) => {
164
+ brotliCompress(buffer, (err, buffer2) => {
165
+ if (err) {
166
+ reject(err);
167
+ }
168
+ writeFileSync(targetFilePath, buffer2);
169
+ resolve(null);
170
+ });
171
+ });
172
+ }
173
+
174
+ // src/utils/pure.ts
175
+ function parseGithubCdnURL(originRepoURL, cdnPrefix, relativeFilePath) {
176
+ if (!originRepoURL.startsWith("https://github.com/")) {
177
+ throw new Error("origin url must start with https://github.com/");
178
+ }
179
+ originRepoURL = originRepoURL.trim().replace(/\/?$/, "/").trim();
180
+ relativeFilePath = relativeFilePath.trim().replace(/^\/|\/?$/g, "").trim();
181
+ cdnPrefix = cdnPrefix.trim().replace(/^\/?|\/?$/g, "").trim();
182
+ return originRepoURL.replace("github.com", cdnPrefix) + relativeFilePath;
183
+ }
184
+ function handleUnexpectedErrors(callback) {
185
+ process.on("uncaughtException", callback);
186
+ process.on("unhandledRejection", callback);
187
+ }
188
+ function parseVersion(version) {
189
+ const semver = /^(\d+)\.(\d+)\.(\d+)(?:-([a-z0-9.-]+))?/i;
190
+ const match = semver.exec(version);
191
+ if (!match) {
192
+ throw new TypeError(`invalid version: ${version}`);
193
+ }
194
+ const [major, minor, patch] = match.slice(1, 4).map(Number);
195
+ const ret = {
196
+ major,
197
+ minor,
198
+ patch,
199
+ stage: "",
200
+ stageVersion: -1
201
+ };
202
+ if (match[4]) {
203
+ let [stage, _v] = match[4].split(".");
204
+ ret.stage = stage;
205
+ ret.stageVersion = Number(_v) || -1;
206
+ }
207
+ if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || Number.isNaN(ret.stageVersion)) {
208
+ throw new TypeError(`invalid version: ${version}`);
209
+ }
210
+ return ret;
211
+ }
212
+ function isUpdateJSON(json) {
213
+ const is2 = (j) => !!(j && j.minimumVersion && j.signature && j.size && j.version);
214
+ return is2(json) && is2(json?.beta);
215
+ }
216
+
217
+ export {
218
+ __require,
219
+ is,
220
+ getPathFromAppNameAsar,
221
+ getVersions,
222
+ loadNativeModuleFromEntry,
223
+ restartApp,
224
+ setAppUserModelId,
225
+ disableHWAccForWin7,
226
+ singleInstance,
227
+ setPortableAppDataPath,
228
+ waitAppReady,
229
+ getPaths,
230
+ unzipFile,
231
+ zipFile,
232
+ parseGithubCdnURL,
233
+ handleUnexpectedErrors,
234
+ parseVersion,
235
+ isUpdateJSON
236
+ };
@@ -0,0 +1,236 @@
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") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/utils/electron.ts
9
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
10
+ import { basename, dirname, join } from "node:path";
11
+ import { release } from "node:os";
12
+ import { app } from "electron";
13
+ var is = {
14
+ dev: !app.isPackaged,
15
+ win: process.platform === "win32",
16
+ mac: process.platform === "darwin",
17
+ linux: process.platform === "linux"
18
+ };
19
+ function getPathFromAppNameAsar(...path) {
20
+ return is.dev ? "DEV.asar" : join(dirname(app.getAppPath()), `${app.name}.asar`, ...path);
21
+ }
22
+ function getVersions() {
23
+ const platform = is.win ? "Windows" : is.mac ? "MacOS" : process.platform.toUpperCase();
24
+ return {
25
+ appVersion: is.dev ? app.getVersion() : readFileSync(getPathFromAppNameAsar("version"), "utf-8"),
26
+ entryVersion: app.getVersion(),
27
+ electronVersion: process.versions.electron,
28
+ nodeVersion: process.versions.node,
29
+ systemVersion: `${platform} ${release()}`
30
+ };
31
+ }
32
+ function loadNativeModuleFromEntry(devEntryDirPath = "../../dist-entry", entryDirPath = join(app.getAppPath(), basename(devEntryDirPath))) {
33
+ const path = is.dev ? devEntryDirPath : entryDirPath;
34
+ return (moduleName) => {
35
+ try {
36
+ return __require(join(path, moduleName));
37
+ } catch (error) {
38
+ console.error("fail to load module", error);
39
+ }
40
+ };
41
+ }
42
+ function restartApp() {
43
+ app.relaunch();
44
+ app.quit();
45
+ }
46
+ function setAppUserModelId(id) {
47
+ app.setAppUserModelId(is.dev ? process.execPath : id ?? `org.${app.name}`);
48
+ }
49
+ function disableHWAccForWin7() {
50
+ if (release().startsWith("6.1")) {
51
+ app.disableHardwareAcceleration();
52
+ }
53
+ }
54
+ function singleInstance(window) {
55
+ const result = app.requestSingleInstanceLock();
56
+ result ? app.on("second-instance", () => {
57
+ if (window) {
58
+ window.show();
59
+ if (window.isMinimized()) {
60
+ window.restore();
61
+ }
62
+ window.focus();
63
+ }
64
+ }) : app.quit();
65
+ return result;
66
+ }
67
+ function setPortableAppDataPath(dirName = "data") {
68
+ const portablePath = join(dirname(app.getPath("exe")), dirName);
69
+ if (!existsSync(portablePath)) {
70
+ mkdirSync(portablePath);
71
+ }
72
+ app.setPath("appData", portablePath);
73
+ }
74
+ function waitAppReady(timeout = 1e3) {
75
+ return app.isReady() ? Promise.resolve() : new Promise((resolve, reject) => {
76
+ const _ = setTimeout(() => {
77
+ reject(new Error("app is not ready"));
78
+ }, timeout);
79
+ app.whenReady().then(() => {
80
+ clearTimeout(_);
81
+ resolve();
82
+ });
83
+ });
84
+ }
85
+ function getPaths(entryDirName = "dist-entry") {
86
+ const root = join(__dirname, "..");
87
+ const mainDirPath = join(root, "main");
88
+ const preloadDirPath = join(root, "preload");
89
+ const rendererDirPath = join(root, "renderer");
90
+ const devServerURL = process.env.VITE_DEV_SERVER_URL;
91
+ const indexHTMLPath = join(rendererDirPath, "index.html");
92
+ const publicDirPath = devServerURL ? join(root, "../public") : rendererDirPath;
93
+ return {
94
+ /**
95
+ * @example
96
+ * ```ts
97
+ * devServerURL && win.loadURL(devServerURL)
98
+ * ```
99
+ */
100
+ devServerURL,
101
+ /**
102
+ * @example
103
+ * ```ts
104
+ * win.loadFile(indexHTMLPath)
105
+ * ```
106
+ */
107
+ indexHTMLPath,
108
+ /**
109
+ * get path inside entry asar
110
+ * @param paths joined path
111
+ */
112
+ getPathFromEntryAsar(...paths) {
113
+ return join(app.getAppPath(), entryDirName, ...paths);
114
+ },
115
+ /**
116
+ * get path inside `${app.name}.asar/main`
117
+ * @param paths joined path
118
+ */
119
+ getPathFromMain(...paths) {
120
+ return join(mainDirPath, ...paths);
121
+ },
122
+ /**
123
+ * get path inside `${app.name}.asar/preload`
124
+ * @param paths joined path
125
+ */
126
+ getPathFromPreload(...paths) {
127
+ return join(preloadDirPath, ...paths);
128
+ },
129
+ /**
130
+ * get path inside public dir
131
+ * @param paths joined path
132
+ */
133
+ getPathFromPublic(...paths) {
134
+ return join(publicDirPath, ...paths);
135
+ }
136
+ };
137
+ }
138
+
139
+ // src/utils/zip.ts
140
+ import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync, writeFileSync } from "node:fs";
141
+ import { brotliCompress, brotliDecompress } from "node:zlib";
142
+ async function unzipFile(gzipPath, targetFilePath = gzipPath.slice(0, -3)) {
143
+ if (!existsSync2(gzipPath)) {
144
+ throw new Error(`path to zipped file not exist: ${gzipPath}`);
145
+ }
146
+ const compressedBuffer = readFileSync2(gzipPath);
147
+ return new Promise((resolve, reject) => {
148
+ brotliDecompress(compressedBuffer, (err, buffer) => {
149
+ rmSync(gzipPath);
150
+ if (err) {
151
+ reject(err);
152
+ }
153
+ writeFileSync(targetFilePath, buffer);
154
+ resolve(null);
155
+ });
156
+ });
157
+ }
158
+ async function zipFile(filePath, targetFilePath = `${filePath}.gz`) {
159
+ if (!existsSync2(filePath)) {
160
+ throw new Error(`path to be zipped not exist: ${filePath}`);
161
+ }
162
+ const buffer = readFileSync2(filePath);
163
+ return new Promise((resolve, reject) => {
164
+ brotliCompress(buffer, (err, buffer2) => {
165
+ if (err) {
166
+ reject(err);
167
+ }
168
+ writeFileSync(targetFilePath, buffer2);
169
+ resolve(null);
170
+ });
171
+ });
172
+ }
173
+
174
+ // src/utils/pure.ts
175
+ function parseGithubCdnURL(originRepoURL, cdnPrefix, relativeFilePath) {
176
+ if (!originRepoURL.startsWith("https://github.com/")) {
177
+ throw new Error("origin url must start with https://github.com/");
178
+ }
179
+ originRepoURL = originRepoURL.trim().replace(/\/?$/, "/").trim();
180
+ relativeFilePath = relativeFilePath.trim().replace(/^\/|\/?$/g, "").trim();
181
+ cdnPrefix = cdnPrefix.trim().replace(/^\/?|\/?$/g, "").trim();
182
+ return originRepoURL.replace("github.com", cdnPrefix) + relativeFilePath;
183
+ }
184
+ function handleUnexpectedErrors(callback) {
185
+ process.on("uncaughtException", callback);
186
+ process.on("unhandledRejection", callback);
187
+ }
188
+ function parseVersion(version) {
189
+ const semver = /^(\d+)\.(\d+)\.(\d+)(?:-([a-z0-9.-]+))?/i;
190
+ const match = semver.exec(version);
191
+ if (!match) {
192
+ throw new TypeError(`invalid version: ${version}`);
193
+ }
194
+ const [major, minor, patch] = match.slice(1, 4).map(Number);
195
+ const ret = {
196
+ major,
197
+ minor,
198
+ patch,
199
+ stage: "",
200
+ stageVersion: -1
201
+ };
202
+ if (match[4]) {
203
+ let [stage, _v] = match[4].split(".");
204
+ ret.stage = stage;
205
+ ret.stageVersion = Number(_v) || -1;
206
+ }
207
+ if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || Number.isNaN(ret.stageVersion)) {
208
+ throw new TypeError(`invalid version: ${version}`);
209
+ }
210
+ return ret;
211
+ }
212
+ function isUpdateJSON(json) {
213
+ const is2 = (j) => !!(j && j.minimumVersion && j.signature && j.size && j.version);
214
+ return is2(json) && is2(json?.beta);
215
+ }
216
+
217
+ export {
218
+ __require,
219
+ is,
220
+ getPathFromAppNameAsar,
221
+ getVersions,
222
+ loadNativeModuleFromEntry,
223
+ restartApp,
224
+ setAppUserModelId,
225
+ disableHWAccForWin7,
226
+ singleInstance,
227
+ setPortableAppDataPath,
228
+ waitAppReady,
229
+ getPaths,
230
+ unzipFile,
231
+ zipFile,
232
+ parseGithubCdnURL,
233
+ handleUnexpectedErrors,
234
+ parseVersion,
235
+ isUpdateJSON
236
+ };