electron-incremental-update 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 subframe7536
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,299 @@
1
+ ## electron incremental updater
2
+
3
+ inspired by Obsidian's update strategy, using RSA + Signature to sign the update asar and replace the old one when verified
4
+
5
+ develop with [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron), and may be effect in other electron vite frameworks
6
+
7
+ ### install
8
+
9
+ #### npm
10
+ ```bash
11
+ npm install electron-incremental-update
12
+ ```
13
+ #### yarn
14
+ ```bash
15
+ yarn add electron-incremental-update
16
+ ```
17
+ #### pnpm
18
+ ```bash
19
+ pnpm add electron-incremental-update
20
+ ```
21
+
22
+ ### usage
23
+
24
+ base on [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
25
+
26
+ ```
27
+ electron
28
+ ├── app.ts // <- add app entry file
29
+ ├── electron-env.d.ts
30
+ ├── main
31
+ │ ├── db.ts
32
+ │ ├── index.ts
33
+ └── preload
34
+ └── index.ts
35
+ src
36
+ └── ...
37
+ ```
38
+
39
+ #### setup app
40
+
41
+ ```ts
42
+ // electron/app.ts
43
+ import { createUpdater, initApp } from 'electron-incremental-update'
44
+ import { name, repository } from '../package.json'
45
+
46
+ const SIGNATURE_PUB = '' // auto generate RSA public key when start app
47
+
48
+ const updater = createUpdater({
49
+ SIGNATURE_PUB,
50
+ githubRepository: repository,
51
+ productName: name,
52
+ })
53
+ initApp(name, updater)
54
+ ```
55
+
56
+ #### setup main
57
+
58
+ ```ts
59
+ // electron/main/index.ts
60
+ import type { Updater } from 'electron-incremental-update'
61
+ import { getAppAsarPath, getAppVersion, getElectronVersion } from 'electron-incremental-update'
62
+ import { app } from 'electron'
63
+ import { name } from '../../package.json'
64
+
65
+ export default function (updater: Updater) {
66
+ console.log('\ncurrent:')
67
+ console.log(`\telectron: ${getElectronVersion()}`)
68
+ console.log(`\tasar path: ${getAppAsarPath(name)}`)
69
+ console.log(`\tapp: ${getAppVersion(name)}`)
70
+
71
+ updater.checkUpdate()
72
+ updater.on('checkResult', async (result, err) => {
73
+ switch (result) {
74
+ case 'success':
75
+ await dialog.showMessageBox({
76
+ type: 'info',
77
+ buttons: ['Restart', 'Later'],
78
+ message: 'Application successfully updated!',
79
+ }).then(({ response }) => {
80
+ if (response === 0) {
81
+ app.relaunch()
82
+ app.quit()
83
+ }
84
+ })
85
+ break
86
+ case 'unavailable':
87
+ console.log('Update Unavailable')
88
+ break
89
+ case 'fail':
90
+ console.error(err)
91
+ break
92
+ }
93
+ })
94
+ updater.on('downloadStart', console.log)
95
+ updater.on('downloading', console.log)
96
+ updater.on('downloadEnd', console.log)
97
+ updater.on('donwnloadError', console.error)
98
+
99
+ // app logics
100
+ app.whenReady().then(() => {
101
+ // ...
102
+ })
103
+ }
104
+ ```
105
+
106
+ #### use native modules
107
+
108
+ ```ts
109
+ // db.ts
110
+ import { requireNative } from 'electron-incremental-update'
111
+
112
+ const Database = requireNative<typeof import('better-sqlite3')>('better-sqlite3')
113
+ const db = new Database(':memory:')
114
+ db.exec(
115
+ 'DROP TABLE IF EXISTS employees; '
116
+ + 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
117
+ )
118
+
119
+ db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
120
+ n: 'James',
121
+ s: 50000,
122
+ })
123
+
124
+ const r = db.prepare('SELECT * from employees').all()
125
+ console.log(r)
126
+ // [ { name: 'James', salary: 50000 } ]
127
+
128
+ db.close()
129
+ ```
130
+
131
+ #### setup vite.config.ts
132
+
133
+ ```ts
134
+ import { rmSync } from 'node:fs'
135
+ import { defineConfig } from 'vite'
136
+ import electron from 'vite-plugin-electron'
137
+ import updater from 'electron-incremental-update/vite'
138
+ import pkg from './package.json'
139
+
140
+ // https://vitejs.dev/config/
141
+
142
+ export default defineConfig(({ command }) => {
143
+ rmSync('dist-electron', { recursive: true, force: true })
144
+
145
+ const isServe = command === 'serve'
146
+ const isBuild = command === 'build'
147
+ const sourcemap = isServe || !!process.env.VSCODE_DEBUG
148
+
149
+ return {
150
+ plugins: [
151
+ electron([
152
+ {
153
+ // Main-Process entry file of the Electron App.
154
+ entry: ['electron/main/index.ts'],
155
+ onstart(options) {
156
+ if (process.env.VSCODE_DEBUG) {
157
+ console.log(/* For `.vscode/.debug.script.mjs` */'[startup] Electron App')
158
+ } else {
159
+ options.startup()
160
+ }
161
+ },
162
+ vite: {
163
+ plugins: [
164
+ updater({ // options see below
165
+ productName: pkg.name,
166
+ version: pkg.version,
167
+ isBuild,
168
+ }),
169
+ ],
170
+ build: {
171
+ sourcemap,
172
+ minify: false,
173
+ outDir: 'dist-electron/main',
174
+ rollupOptions: {
175
+ external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),
176
+ treeshake: true,
177
+ },
178
+ },
179
+ },
180
+ },
181
+ {
182
+ // ...preload
183
+ },
184
+ ]),
185
+ // ... other plugins
186
+ ],
187
+ // ... other config
188
+ }
189
+ })
190
+ ```
191
+
192
+ ##### option
193
+
194
+ ```ts
195
+ type Options = {
196
+ /**
197
+ * whether is in build mode
198
+ */
199
+ isBuild: boolean
200
+ /**
201
+ * the name of you application
202
+ *
203
+ * you can set as 'name' in package.json
204
+ */
205
+ productName: string
206
+ /**
207
+ * the version of you application
208
+ *
209
+ * you can set as 'version' in package.json
210
+ */
211
+ version: string
212
+ /**
213
+ * Whether to minify
214
+ */
215
+ minify?: boolean
216
+ paths?: {
217
+ /**
218
+ * Path to app entry file
219
+ * @default 'electron/app.ts'
220
+ */
221
+ entryPath?: string
222
+ /**
223
+ * Path to app entry output file
224
+ * @default 'app.js'
225
+ */
226
+ entryOutputPath?: string
227
+ /**
228
+ * Path to app entry file
229
+ * @default `release/${ProductName}.asar`
230
+ */
231
+ asarOutputPath?: string
232
+ /**
233
+ * Path to electron build output
234
+ * @default `dist-electron`
235
+ */
236
+ electronDistPath?: string
237
+ /**
238
+ * Path to renderer build output
239
+ * @default `dist`
240
+ */
241
+ rendererDistPath?: string
242
+ }
243
+ keys?: {
244
+ /**
245
+ * Path to the pem file that contains private key
246
+ * if not ended with .pem, it will be appended
247
+ * @default 'public/private.pem'
248
+ */
249
+ privateKeyPath?: string
250
+ /**
251
+ * Path to the pem file that contains public key
252
+ * if not ended with .pem, it will be appended
253
+ * @default 'public/public.pem'
254
+ */
255
+ publicKeyPath?: string
256
+ /**
257
+ * Length of the key
258
+ * @default 2048
259
+ */
260
+ keyLength?: number
261
+ }
262
+ }
263
+ ```
264
+
265
+ #### electron-builder config
266
+
267
+ ```js
268
+ const { name } = require('./package.json')
269
+
270
+ const target = `${name}.asar`
271
+ /**
272
+ * @type {import('electron-builder').Configuration}
273
+ */
274
+ module.exports = {
275
+ appId: 'YourAppID',
276
+ productName: name,
277
+ files: [
278
+ 'app.js', // <- app entry file
279
+ '!**/{.eslintignore,.eslintrc.cjs,.editorconfig,.prettierignore,.prettierrc.yaml,dev-app-update.yml,LICENSE,.nvmrc,.npmrc}',
280
+ '!**/{tsconfig.json,tsconfig.node.json,tsconfig.web.json}',
281
+ '!**/*debug*.*',
282
+ '!**/*.{md,zip,map}',
283
+ '!**/*.{c,cpp,h,hpp,cc,hh,cxx,hxx,gypi,gyp,sh}',
284
+ '!**/.{github,vscode}',
285
+ '!node_modules/**/better-sqlite3/deps/**',
286
+ ],
287
+ asarUnpack: [
288
+ '**/*.{node,dll}',
289
+ ],
290
+ directories: {
291
+ output: 'release',
292
+ },
293
+ extraResources: [
294
+ { from: `release/${target}`, to: target }, // <- asar file
295
+ ],
296
+ publish: null,
297
+ // ...
298
+ }
299
+ ```
@@ -0,0 +1,11 @@
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 new Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ export {
10
+ __require
11
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ createUpdater: () => createUpdater,
34
+ getAppAsarPath: () => getAppAsarPath,
35
+ getAppVersion: () => getAppVersion,
36
+ getElectronVersion: () => getElectronVersion,
37
+ getReleaseDnsPrefix: () => getReleaseDnsPrefix,
38
+ initApp: () => initApp,
39
+ requireNative: () => requireNative
40
+ });
41
+ module.exports = __toCommonJS(src_exports);
42
+ var import_node_path2 = require("path");
43
+ var import_electron3 = require("electron");
44
+
45
+ // src/utils.ts
46
+ var import_node_fs = require("fs");
47
+ var import_node_path = require("path");
48
+ var import_electron = require("electron");
49
+ function getAppAsarPath(name) {
50
+ return import_electron.app.isPackaged ? (0, import_node_path.join)((0, import_node_path.dirname)(import_electron.app.getAppPath()), `${name}.asar`) : "dev";
51
+ }
52
+ function getElectronVersion() {
53
+ return import_electron.app.getVersion();
54
+ }
55
+ function getAppVersion(name) {
56
+ return import_electron.app.isPackaged ? (0, import_node_fs.readFileSync)((0, import_node_path.join)(getAppAsarPath(name), "version"), "utf-8").trim() : getElectronVersion();
57
+ }
58
+ function requireNative(packageName) {
59
+ const path = import_electron.app.isPackaged ? (0, import_node_path.join)(import_electron.app.getAppPath(), "node_modules", packageName) : packageName;
60
+ return require(path);
61
+ }
62
+ function getReleaseDnsPrefix() {
63
+ const hub = "https://github.com";
64
+ return [
65
+ { urlPrefix: `https://gh.gh2233.ml/${hub}`, maintainer: "@X.I.U/XIU2" },
66
+ { urlPrefix: `https://ghproxy.com/${hub}`, maintainer: "gh-proxy" },
67
+ { urlPrefix: `https://gh.ddlc.top/${hub}`, maintainer: "@mtr-static-official" },
68
+ { urlPrefix: `https://ghdl.feizhuqwq.cf/${hub}`, maintainer: "feizhuqwq.com" },
69
+ { urlPrefix: `https://slink.ltd/${hub}`, maintainer: "\u77E5\u4E86\u5C0F\u7AD9" },
70
+ { urlPrefix: `https://git.xfj0.cn/${hub}`, maintainer: "anonymous1" },
71
+ { urlPrefix: `https://gh.con.sh/${hub}`, maintainer: "anonymous2" },
72
+ { urlPrefix: `https://ghps.cc/${hub}`, maintainer: "anonymous3" },
73
+ { urlPrefix: "https://cors.isteed.cc/github.com", maintainer: "Lufs's" },
74
+ { urlPrefix: `https://hub.gitmirror.com/${hub}`, maintainer: "GitMirror" },
75
+ { urlPrefix: `https://js.xxooo.ml/${hub}`, maintainer: "\u996D\u592A\u786C" },
76
+ { urlPrefix: `https://proxy.freecdn.ml/?url=${hub}`, maintainer: "anonymous4" },
77
+ { urlPrefix: "https://download.njuu.cf", maintainer: "LibraryCloud-njuu" },
78
+ { urlPrefix: "https://download.yzuu.cf", maintainer: "LibraryCloud-yzuu" },
79
+ { urlPrefix: "https://download.nuaa.cf", maintainer: "LibraryCloud-nuaa" }
80
+ ];
81
+ }
82
+
83
+ // src/updater.ts
84
+ var import_node_events = require("events");
85
+ var import_node_buffer = require("buffer");
86
+ var import_node_crypto = require("crypto");
87
+ var import_node_zlib = require("zlib");
88
+ var import_node_fs2 = require("fs");
89
+ var import_promises = require("fs/promises");
90
+ var import_node_https = __toESM(require("https"), 1);
91
+ var import_electron2 = require("electron");
92
+ function createUpdater({
93
+ SIGNATURE_PUB,
94
+ githubRepository: repository,
95
+ productName,
96
+ releaseCdnPrefix
97
+ }) {
98
+ const updater = new import_node_events.EventEmitter();
99
+ async function download(url, format) {
100
+ const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36";
101
+ return await new Promise((resolve2, reject) => {
102
+ import_node_https.default.get(url, (res) => {
103
+ if (format === "json") {
104
+ let data = "";
105
+ res.setEncoding("utf8");
106
+ res.headers = {
107
+ Accept: "application/json",
108
+ UserAgent: ua
109
+ };
110
+ res.on("data", (chunk) => data += chunk);
111
+ res.on("end", () => {
112
+ resolve2(JSON.parse(data));
113
+ });
114
+ } else if (format === "buffer") {
115
+ let data = [];
116
+ res.headers = {
117
+ Accept: "application/octet-stream",
118
+ UserAgent: ua
119
+ };
120
+ res.on("data", (chunk) => {
121
+ updater.emit("downloading", chunk.length);
122
+ data.push(chunk);
123
+ });
124
+ res.on("end", () => {
125
+ updater.emit("downloadEnd", true);
126
+ resolve2(import_node_buffer.Buffer.concat(data));
127
+ });
128
+ }
129
+ }).on("error", (e) => {
130
+ e && updater.emit("donwnloadError", e);
131
+ reject(e);
132
+ });
133
+ });
134
+ }
135
+ async function extractFile(gzipFilePath) {
136
+ if (!gzipFilePath.endsWith(".asar.gz") || !(0, import_node_fs2.existsSync)(gzipFilePath)) {
137
+ return;
138
+ }
139
+ gzipFilePath = gzipFilePath.replace(".asar.gz", ".tmp.gz");
140
+ return new Promise((resolve2, reject) => {
141
+ const gunzip = (0, import_node_zlib.createGunzip)();
142
+ const input = (0, import_node_fs2.createReadStream)(gzipFilePath);
143
+ const outputFilePath = gzipFilePath.replace(".tmp.gz", ".asar");
144
+ const output = (0, import_node_fs2.createWriteStream)(outputFilePath);
145
+ input.pipe(gunzip).pipe(output).on("finish", async () => {
146
+ await (0, import_promises.rm)(gzipFilePath);
147
+ resolve2(outputFilePath);
148
+ }).on("error", async (err) => {
149
+ await (0, import_promises.rm)(gzipFilePath);
150
+ output.destroy(err);
151
+ reject(err);
152
+ });
153
+ });
154
+ }
155
+ function verify(buffer, signature) {
156
+ return (0, import_node_crypto.createVerify)("RSA-SHA256").update(buffer).verify(SIGNATURE_PUB, signature, "base64");
157
+ }
158
+ function needUpdate(version) {
159
+ const parseVersion = (version2) => {
160
+ const [major, minor, patch] = version2.split(".");
161
+ return ~~major * 100 + ~~minor * 10 + ~~patch;
162
+ };
163
+ return import_electron2.app.isPackaged && parseVersion(import_electron2.app.getVersion()) < parseVersion(version);
164
+ }
165
+ async function checkUpdate(releaseCdnPrefix2) {
166
+ const gzipPath = `../${productName}.asar.gz`;
167
+ const tmpFile = gzipPath.replace(".asar.gz", ".tmp.gz");
168
+ const base = repository.replace("https://github.com", "");
169
+ const updateJSONUrl = `https://cdn.jsdelivr.net/gh/${base}/version.json`;
170
+ const downloadUrl = `${releaseCdnPrefix2 ? `${releaseCdnPrefix2}/${base}` : repository}/releases/download/latest/${productName}.asar.gz`;
171
+ if ((0, import_node_fs2.existsSync)(tmpFile)) {
172
+ await (0, import_promises.rm)(tmpFile);
173
+ }
174
+ const json = await download(updateJSONUrl, "json");
175
+ if (!json) {
176
+ throw new Error("fetch update json failed");
177
+ }
178
+ const {
179
+ signature,
180
+ version,
181
+ size
182
+ } = json;
183
+ console.log(version, size, signature);
184
+ if (!needUpdate(version)) {
185
+ return "unavailable";
186
+ }
187
+ updater.emit("downloadStart", size);
188
+ const buffer = await download(downloadUrl, "buffer");
189
+ if (!verify(buffer, signature)) {
190
+ throw new Error("file broken, invalid signature!");
191
+ }
192
+ await (0, import_promises.writeFile)(gzipPath, buffer);
193
+ await extractFile(gzipPath);
194
+ return "success";
195
+ }
196
+ const onCheck = async () => {
197
+ try {
198
+ const result = await checkUpdate(releaseCdnPrefix);
199
+ updater.emit("checkResult", result);
200
+ } catch (error) {
201
+ updater.emit("checkResult", "fail", error);
202
+ }
203
+ };
204
+ updater.on("check", onCheck);
205
+ updater.checkUpdate = onCheck;
206
+ return updater;
207
+ }
208
+
209
+ // src/index.ts
210
+ function initApp(productName, updater, option) {
211
+ const {
212
+ electronDistPath = "dist-electron",
213
+ mainPath = "main/index.js"
214
+ } = option ?? {};
215
+ const mainDir = import_electron3.app.isPackaged ? `../${productName}.asar` : electronDistPath;
216
+ const entry = (0, import_node_path2.resolve)(__dirname, mainDir, mainPath);
217
+ return require(entry)(updater);
218
+ }
219
+ // Annotate the CommonJS export names for ESM import in node:
220
+ 0 && (module.exports = {
221
+ createUpdater,
222
+ getAppAsarPath,
223
+ getAppVersion,
224
+ getElectronVersion,
225
+ getReleaseDnsPrefix,
226
+ initApp,
227
+ requireNative
228
+ });
@@ -0,0 +1,60 @@
1
+ type CheckResultType = 'success' | 'fail' | 'unavailable';
2
+ type UpdateEvents = {
3
+ check: null;
4
+ checkResult: [data: CheckResultType, err?: unknown];
5
+ downloadStart: [size: number];
6
+ downloading: [current: number];
7
+ downloadEnd: [success: boolean];
8
+ donwnloadError: [error: unknown];
9
+ };
10
+ type MaybeArray<T> = T extends undefined | null | never ? [] : T extends any[] ? T['length'] extends 1 ? [data: T[0]] : T : [data: T];
11
+ interface TypedUpdater<T extends Record<string | symbol, MaybeArray<any>>, Event extends Exclude<keyof T, number> = Exclude<keyof T, number>> {
12
+ removeAllListeners<E extends Event>(event?: E): this;
13
+ listeners<E extends Event>(eventName: E): Function[];
14
+ eventNames(): (Event)[];
15
+ on<E extends Event>(eventName: E, listener: (...data: MaybeArray<T[E]>) => void): this;
16
+ once<E extends Event>(eventName: E, listener: (...args: MaybeArray<T[E]>) => void): this;
17
+ emit<E extends Event>(eventName: E, ...args: MaybeArray<T[E]>): boolean;
18
+ off<E extends Event>(eventName: E, listener: (...args: MaybeArray<T[E]>) => void): this;
19
+ checkUpdate(releaseCdnPrefix?: string): Promise<void>;
20
+ }
21
+ type Updater = TypedUpdater<UpdateEvents>;
22
+ interface Options {
23
+ SIGNATURE_PUB: string;
24
+ productName: string;
25
+ githubRepository: string;
26
+ releaseCdnPrefix?: string;
27
+ }
28
+ declare function createUpdater({ SIGNATURE_PUB, githubRepository: repository, productName, releaseCdnPrefix, }: Options): Updater;
29
+
30
+ declare function getAppAsarPath(name: string): string;
31
+ declare function getElectronVersion(): string;
32
+ declare function getAppVersion(name: string): string;
33
+ declare function requireNative<T = any>(packageName: string): T;
34
+ declare function getReleaseDnsPrefix(): {
35
+ urlPrefix: string;
36
+ maintainer: string;
37
+ }[];
38
+
39
+ interface PathConfig {
40
+ /**
41
+ * path of electron output dist
42
+ * @default 'dist-electron'
43
+ */
44
+ electronDistPath?: string;
45
+ /**
46
+ * relative path of main entry in electron dist
47
+ * @default 'main/index.js'
48
+ */
49
+ mainPath?: string;
50
+ }
51
+ /**
52
+ * Initialize application
53
+ * @param productName name of your application
54
+ * @param updater updater
55
+ * @param option options for entry, will be used to generate electron main path, default: `dist-electron/main/index.js`
56
+ * @returns a function to init your application with a updater
57
+ */
58
+ declare function initApp(productName: string, updater: Updater, option?: PathConfig): any;
59
+
60
+ export { Options, Updater, createUpdater, getAppAsarPath, getAppVersion, getElectronVersion, getReleaseDnsPrefix, initApp, requireNative };
package/dist/index.mjs ADDED
@@ -0,0 +1,191 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-AKU6F3WT.mjs";
4
+
5
+ // src/index.ts
6
+ import { resolve } from "node:path";
7
+ import { app as app3 } from "electron";
8
+
9
+ // src/utils.ts
10
+ import { readFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { app } from "electron";
13
+ function getAppAsarPath(name) {
14
+ return app.isPackaged ? join(dirname(app.getAppPath()), `${name}.asar`) : "dev";
15
+ }
16
+ function getElectronVersion() {
17
+ return app.getVersion();
18
+ }
19
+ function getAppVersion(name) {
20
+ return app.isPackaged ? readFileSync(join(getAppAsarPath(name), "version"), "utf-8").trim() : getElectronVersion();
21
+ }
22
+ function requireNative(packageName) {
23
+ const path = app.isPackaged ? join(app.getAppPath(), "node_modules", packageName) : packageName;
24
+ return __require(path);
25
+ }
26
+ function getReleaseDnsPrefix() {
27
+ const hub = "https://github.com";
28
+ return [
29
+ { urlPrefix: `https://gh.gh2233.ml/${hub}`, maintainer: "@X.I.U/XIU2" },
30
+ { urlPrefix: `https://ghproxy.com/${hub}`, maintainer: "gh-proxy" },
31
+ { urlPrefix: `https://gh.ddlc.top/${hub}`, maintainer: "@mtr-static-official" },
32
+ { urlPrefix: `https://ghdl.feizhuqwq.cf/${hub}`, maintainer: "feizhuqwq.com" },
33
+ { urlPrefix: `https://slink.ltd/${hub}`, maintainer: "\u77E5\u4E86\u5C0F\u7AD9" },
34
+ { urlPrefix: `https://git.xfj0.cn/${hub}`, maintainer: "anonymous1" },
35
+ { urlPrefix: `https://gh.con.sh/${hub}`, maintainer: "anonymous2" },
36
+ { urlPrefix: `https://ghps.cc/${hub}`, maintainer: "anonymous3" },
37
+ { urlPrefix: "https://cors.isteed.cc/github.com", maintainer: "Lufs's" },
38
+ { urlPrefix: `https://hub.gitmirror.com/${hub}`, maintainer: "GitMirror" },
39
+ { urlPrefix: `https://js.xxooo.ml/${hub}`, maintainer: "\u996D\u592A\u786C" },
40
+ { urlPrefix: `https://proxy.freecdn.ml/?url=${hub}`, maintainer: "anonymous4" },
41
+ { urlPrefix: "https://download.njuu.cf", maintainer: "LibraryCloud-njuu" },
42
+ { urlPrefix: "https://download.yzuu.cf", maintainer: "LibraryCloud-yzuu" },
43
+ { urlPrefix: "https://download.nuaa.cf", maintainer: "LibraryCloud-nuaa" }
44
+ ];
45
+ }
46
+
47
+ // src/updater.ts
48
+ import { EventEmitter } from "node:events";
49
+ import { Buffer } from "node:buffer";
50
+ import { createVerify } from "node:crypto";
51
+ import { createGunzip } from "node:zlib";
52
+ import { createReadStream, createWriteStream, existsSync } from "node:fs";
53
+ import { rm, writeFile } from "node:fs/promises";
54
+ import https from "node:https";
55
+ import { app as app2 } from "electron";
56
+ function createUpdater({
57
+ SIGNATURE_PUB,
58
+ githubRepository: repository,
59
+ productName,
60
+ releaseCdnPrefix
61
+ }) {
62
+ const updater = new EventEmitter();
63
+ async function download(url, format) {
64
+ const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36";
65
+ return await new Promise((resolve2, reject) => {
66
+ https.get(url, (res) => {
67
+ if (format === "json") {
68
+ let data = "";
69
+ res.setEncoding("utf8");
70
+ res.headers = {
71
+ Accept: "application/json",
72
+ UserAgent: ua
73
+ };
74
+ res.on("data", (chunk) => data += chunk);
75
+ res.on("end", () => {
76
+ resolve2(JSON.parse(data));
77
+ });
78
+ } else if (format === "buffer") {
79
+ let data = [];
80
+ res.headers = {
81
+ Accept: "application/octet-stream",
82
+ UserAgent: ua
83
+ };
84
+ res.on("data", (chunk) => {
85
+ updater.emit("downloading", chunk.length);
86
+ data.push(chunk);
87
+ });
88
+ res.on("end", () => {
89
+ updater.emit("downloadEnd", true);
90
+ resolve2(Buffer.concat(data));
91
+ });
92
+ }
93
+ }).on("error", (e) => {
94
+ e && updater.emit("donwnloadError", e);
95
+ reject(e);
96
+ });
97
+ });
98
+ }
99
+ async function extractFile(gzipFilePath) {
100
+ if (!gzipFilePath.endsWith(".asar.gz") || !existsSync(gzipFilePath)) {
101
+ return;
102
+ }
103
+ gzipFilePath = gzipFilePath.replace(".asar.gz", ".tmp.gz");
104
+ return new Promise((resolve2, reject) => {
105
+ const gunzip = createGunzip();
106
+ const input = createReadStream(gzipFilePath);
107
+ const outputFilePath = gzipFilePath.replace(".tmp.gz", ".asar");
108
+ const output = createWriteStream(outputFilePath);
109
+ input.pipe(gunzip).pipe(output).on("finish", async () => {
110
+ await rm(gzipFilePath);
111
+ resolve2(outputFilePath);
112
+ }).on("error", async (err) => {
113
+ await rm(gzipFilePath);
114
+ output.destroy(err);
115
+ reject(err);
116
+ });
117
+ });
118
+ }
119
+ function verify(buffer, signature) {
120
+ return createVerify("RSA-SHA256").update(buffer).verify(SIGNATURE_PUB, signature, "base64");
121
+ }
122
+ function needUpdate(version) {
123
+ const parseVersion = (version2) => {
124
+ const [major, minor, patch] = version2.split(".");
125
+ return ~~major * 100 + ~~minor * 10 + ~~patch;
126
+ };
127
+ return app2.isPackaged && parseVersion(app2.getVersion()) < parseVersion(version);
128
+ }
129
+ async function checkUpdate(releaseCdnPrefix2) {
130
+ const gzipPath = `../${productName}.asar.gz`;
131
+ const tmpFile = gzipPath.replace(".asar.gz", ".tmp.gz");
132
+ const base = repository.replace("https://github.com", "");
133
+ const updateJSONUrl = `https://cdn.jsdelivr.net/gh/${base}/version.json`;
134
+ const downloadUrl = `${releaseCdnPrefix2 ? `${releaseCdnPrefix2}/${base}` : repository}/releases/download/latest/${productName}.asar.gz`;
135
+ if (existsSync(tmpFile)) {
136
+ await rm(tmpFile);
137
+ }
138
+ const json = await download(updateJSONUrl, "json");
139
+ if (!json) {
140
+ throw new Error("fetch update json failed");
141
+ }
142
+ const {
143
+ signature,
144
+ version,
145
+ size
146
+ } = json;
147
+ console.log(version, size, signature);
148
+ if (!needUpdate(version)) {
149
+ return "unavailable";
150
+ }
151
+ updater.emit("downloadStart", size);
152
+ const buffer = await download(downloadUrl, "buffer");
153
+ if (!verify(buffer, signature)) {
154
+ throw new Error("file broken, invalid signature!");
155
+ }
156
+ await writeFile(gzipPath, buffer);
157
+ await extractFile(gzipPath);
158
+ return "success";
159
+ }
160
+ const onCheck = async () => {
161
+ try {
162
+ const result = await checkUpdate(releaseCdnPrefix);
163
+ updater.emit("checkResult", result);
164
+ } catch (error) {
165
+ updater.emit("checkResult", "fail", error);
166
+ }
167
+ };
168
+ updater.on("check", onCheck);
169
+ updater.checkUpdate = onCheck;
170
+ return updater;
171
+ }
172
+
173
+ // src/index.ts
174
+ function initApp(productName, updater, option) {
175
+ const {
176
+ electronDistPath = "dist-electron",
177
+ mainPath = "main/index.js"
178
+ } = option ?? {};
179
+ const mainDir = app3.isPackaged ? `../${productName}.asar` : electronDistPath;
180
+ const entry = resolve(__dirname, mainDir, mainPath);
181
+ return __require(entry)(updater);
182
+ }
183
+ export {
184
+ createUpdater,
185
+ getAppAsarPath,
186
+ getAppVersion,
187
+ getElectronVersion,
188
+ getReleaseDnsPrefix,
189
+ initApp,
190
+ requireNative
191
+ };
package/dist/vite.cjs ADDED
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/vite.ts
31
+ var vite_exports = {};
32
+ __export(vite_exports, {
33
+ default: () => vite_default
34
+ });
35
+ module.exports = __toCommonJS(vite_exports);
36
+ var import_vite = require("vite");
37
+
38
+ // src/build-entry.ts
39
+ var import_node_fs = require("fs");
40
+ var import_promises = require("fs/promises");
41
+ var import_node_crypto = require("crypto");
42
+ var import_node_os = require("os");
43
+ var import_ci_info = require("ci-info");
44
+ var import_esbuild = require("esbuild");
45
+ async function generateKey(privateKeyPath, publicKeyPath, length) {
46
+ const pair = (0, import_node_crypto.generateKeyPairSync)("rsa", { modulusLength: length });
47
+ const privateKey = pair.privateKey.export({ type: "pkcs1", format: "pem" });
48
+ const publicKey = pair.publicKey.export({ type: "pkcs1", format: "pem" });
49
+ await (0, import_promises.writeFile)(privateKeyPath, privateKey);
50
+ await (0, import_promises.writeFile)(publicKeyPath, publicKey);
51
+ }
52
+ async function writePublicKeyToMain(updatePath, publicKeyPath) {
53
+ const file = await (0, import_promises.readFile)(updatePath, "utf-8");
54
+ const key = await (0, import_promises.readFile)(publicKeyPath, "utf-8");
55
+ const regex = /const SIGNATURE_PUB = ['`][\s\S]*?['`]/;
56
+ const replacement = `const SIGNATURE_PUB = \`${key}\``;
57
+ let replaced = file;
58
+ const signaturePubExists = regex.test(file);
59
+ if (signaturePubExists) {
60
+ replaced = file.replace(regex, replacement);
61
+ } else {
62
+ const lines = file.split(import_node_os.EOL);
63
+ const r = `${import_node_os.EOL}${replacement}${import_node_os.EOL}`;
64
+ let isMatched = false;
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const line = lines[i];
67
+ if (!line.startsWith("import") && !line.startsWith("/")) {
68
+ lines.splice(i, 0, r);
69
+ isMatched = true;
70
+ break;
71
+ }
72
+ }
73
+ !isMatched && lines.push(r);
74
+ replaced = lines.join(import_node_os.EOL);
75
+ }
76
+ await (0, import_promises.writeFile)(updatePath, replaced);
77
+ }
78
+ async function buildEntry({
79
+ privateKeyPath,
80
+ publicKeyPath,
81
+ entryPath,
82
+ entryOutputPath: outfile,
83
+ minify,
84
+ keyLength
85
+ }) {
86
+ if (!import_ci_info.isCI) {
87
+ !(0, import_node_fs.existsSync)(privateKeyPath) && await generateKey(privateKeyPath, publicKeyPath, keyLength);
88
+ await writePublicKeyToMain(entryPath, publicKeyPath);
89
+ }
90
+ await (0, import_esbuild.build)({
91
+ entryPoints: [entryPath],
92
+ bundle: true,
93
+ platform: "node",
94
+ outfile,
95
+ minify,
96
+ external: ["electron"]
97
+ });
98
+ }
99
+
100
+ // src/build-asar.ts
101
+ var import_node_crypto2 = require("crypto");
102
+ var import_node_fs2 = require("fs");
103
+ var import_promises2 = require("fs/promises");
104
+ var import_node_zlib = __toESM(require("zlib"), 1);
105
+ var import_ci_info2 = require("ci-info");
106
+ function gzipFile(filePath) {
107
+ return new Promise((resolve, reject) => {
108
+ const gzip = import_node_zlib.default.createGzip();
109
+ const input = (0, import_node_fs2.createReadStream)(filePath);
110
+ const output = (0, import_node_fs2.createWriteStream)(`${filePath}.gz`);
111
+ input.pipe(gzip).pipe(output).on("finish", () => resolve(null)).on("error", (err) => reject(err));
112
+ });
113
+ }
114
+ function generateSignature(buffer, privateKey) {
115
+ return (0, import_node_crypto2.createSign)("RSA-SHA256").update(buffer).sign({
116
+ key: privateKey,
117
+ padding: import_node_crypto2.constants.RSA_PKCS1_PADDING,
118
+ saltLength: import_node_crypto2.constants.RSA_PSS_SALTLEN_DIGEST
119
+ }, "base64");
120
+ }
121
+ async function pack(dir, target) {
122
+ let asar = null;
123
+ try {
124
+ asar = await import("asar");
125
+ } catch (ignore) {
126
+ }
127
+ if (!asar) {
128
+ try {
129
+ asar = await import("@electron/asar");
130
+ } catch (ignore) {
131
+ }
132
+ }
133
+ if (!asar) {
134
+ throw new Error("no asar, please install @electron/asar");
135
+ }
136
+ await asar.createPackage(dir, target);
137
+ }
138
+ async function buildAsar({
139
+ version,
140
+ asarOutputPath,
141
+ privateKeyPath,
142
+ electronDistPath,
143
+ rendererDistPath
144
+ }) {
145
+ await (0, import_promises2.rename)(rendererDistPath, `${electronDistPath}/renderer`);
146
+ await (0, import_promises2.writeFile)(`${electronDistPath}/version`, version);
147
+ await pack(electronDistPath, asarOutputPath);
148
+ if (import_ci_info2.isCI) {
149
+ return;
150
+ }
151
+ await gzipFile(asarOutputPath);
152
+ const buffer = await (0, import_promises2.readFile)(`${asarOutputPath}.gz`);
153
+ const signature = generateSignature(buffer, await (0, import_promises2.readFile)(privateKeyPath, "utf-8"));
154
+ await (0, import_promises2.writeFile)("version.json", JSON.stringify({
155
+ signature,
156
+ version,
157
+ size: buffer.length
158
+ }, null, 2));
159
+ }
160
+
161
+ // src/option.ts
162
+ function parseOptions(options) {
163
+ const { isBuild, productName, version, minify = false, paths = {}, keys = {} } = options;
164
+ const {
165
+ entryPath = "electron/app.ts",
166
+ entryOutputPath = "app.js",
167
+ asarOutputPath = `release/${productName}.asar`,
168
+ electronDistPath = "dist-electron",
169
+ rendererDistPath = "dist"
170
+ } = paths;
171
+ const {
172
+ privateKeyPath = "public/private.pem",
173
+ publicKeyPath = "public/public.pem",
174
+ keyLength = 2048
175
+ } = keys;
176
+ const buildAsarOption = {
177
+ version,
178
+ productName,
179
+ asarOutputPath,
180
+ privateKeyPath,
181
+ electronDistPath,
182
+ rendererDistPath
183
+ };
184
+ const buildEntryOption = {
185
+ privateKeyPath,
186
+ publicKeyPath,
187
+ entryPath,
188
+ entryOutputPath,
189
+ minify,
190
+ keyLength
191
+ };
192
+ return { isBuild, buildAsarOption, buildEntryOption };
193
+ }
194
+
195
+ // src/vite.ts
196
+ function vite_default(options) {
197
+ const { isBuild, buildAsarOption, buildEntryOption } = parseOptions(options);
198
+ const { entryPath, entryOutputPath } = buildEntryOption;
199
+ const { asarOutputPath } = buildAsarOption;
200
+ const id = "electron-incremental-updater";
201
+ const log = (0, import_vite.createLogger)("info", { prefix: `[${id}]` });
202
+ return [
203
+ {
204
+ name: `vite-plugin-${id}-entry`,
205
+ async buildStart() {
206
+ log.info("build entry start");
207
+ await buildEntry(buildEntryOption);
208
+ log.info(`build entry end, ${entryPath} -> ${entryOutputPath}`);
209
+ }
210
+ },
211
+ {
212
+ name: `vite-plugin-${id}-asar`,
213
+ async closeBundle() {
214
+ if (!isBuild) {
215
+ return;
216
+ }
217
+ log.info("build asar start");
218
+ await buildAsar(buildAsarOption);
219
+ log.info(`build asar end, output to ${asarOutputPath}`);
220
+ }
221
+ }
222
+ ];
223
+ }
package/dist/vite.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ type Options = {
4
+ /**
5
+ * whether is in build mode
6
+ */
7
+ isBuild: boolean;
8
+ /**
9
+ * the name of you application
10
+ *
11
+ * you can set as 'name' in package.json
12
+ */
13
+ productName: string;
14
+ /**
15
+ * the version of you application
16
+ *
17
+ * you can set as 'version' in package.json
18
+ */
19
+ version: string;
20
+ /**
21
+ * Whether to minify
22
+ */
23
+ minify?: boolean;
24
+ paths?: {
25
+ /**
26
+ * Path to app entry file
27
+ * @default 'electron/app.ts'
28
+ */
29
+ entryPath?: string;
30
+ /**
31
+ * Path to app entry output file
32
+ * @default 'app.js'
33
+ */
34
+ entryOutputPath?: string;
35
+ /**
36
+ * Path to app entry file
37
+ * @default `release/${ProductName}.asar`
38
+ */
39
+ asarOutputPath?: string;
40
+ /**
41
+ * Path to electron build output
42
+ * @default `dist-electron`
43
+ */
44
+ electronDistPath?: string;
45
+ /**
46
+ * Path to renderer build output
47
+ * @default `dist`
48
+ */
49
+ rendererDistPath?: string;
50
+ };
51
+ keys?: {
52
+ /**
53
+ * Path to the pem file that contains private key
54
+ * if not ended with .pem, it will be appended
55
+ * @default 'public/private.pem'
56
+ */
57
+ privateKeyPath?: string;
58
+ /**
59
+ * Path to the pem file that contains public key
60
+ * if not ended with .pem, it will be appended
61
+ * @default 'public/public.pem'
62
+ */
63
+ publicKeyPath?: string;
64
+ /**
65
+ * Length of the key
66
+ * @default 2048
67
+ */
68
+ keyLength?: number;
69
+ };
70
+ };
71
+
72
+ declare function export_default(options: Options): Plugin[];
73
+
74
+ export { export_default as default };
package/dist/vite.mjs ADDED
@@ -0,0 +1,194 @@
1
+ import "./chunk-AKU6F3WT.mjs";
2
+
3
+ // src/vite.ts
4
+ import { createLogger } from "vite";
5
+
6
+ // src/build-entry.ts
7
+ import { existsSync } from "node:fs";
8
+ import { readFile, writeFile } from "node:fs/promises";
9
+ import { generateKeyPairSync } from "node:crypto";
10
+ import { EOL } from "node:os";
11
+ import { isCI } from "ci-info";
12
+ import { build } from "esbuild";
13
+ async function generateKey(privateKeyPath, publicKeyPath, length) {
14
+ const pair = generateKeyPairSync("rsa", { modulusLength: length });
15
+ const privateKey = pair.privateKey.export({ type: "pkcs1", format: "pem" });
16
+ const publicKey = pair.publicKey.export({ type: "pkcs1", format: "pem" });
17
+ await writeFile(privateKeyPath, privateKey);
18
+ await writeFile(publicKeyPath, publicKey);
19
+ }
20
+ async function writePublicKeyToMain(updatePath, publicKeyPath) {
21
+ const file = await readFile(updatePath, "utf-8");
22
+ const key = await readFile(publicKeyPath, "utf-8");
23
+ const regex = /const SIGNATURE_PUB = ['`][\s\S]*?['`]/;
24
+ const replacement = `const SIGNATURE_PUB = \`${key}\``;
25
+ let replaced = file;
26
+ const signaturePubExists = regex.test(file);
27
+ if (signaturePubExists) {
28
+ replaced = file.replace(regex, replacement);
29
+ } else {
30
+ const lines = file.split(EOL);
31
+ const r = `${EOL}${replacement}${EOL}`;
32
+ let isMatched = false;
33
+ for (let i = 0; i < lines.length; i++) {
34
+ const line = lines[i];
35
+ if (!line.startsWith("import") && !line.startsWith("/")) {
36
+ lines.splice(i, 0, r);
37
+ isMatched = true;
38
+ break;
39
+ }
40
+ }
41
+ !isMatched && lines.push(r);
42
+ replaced = lines.join(EOL);
43
+ }
44
+ await writeFile(updatePath, replaced);
45
+ }
46
+ async function buildEntry({
47
+ privateKeyPath,
48
+ publicKeyPath,
49
+ entryPath,
50
+ entryOutputPath: outfile,
51
+ minify,
52
+ keyLength
53
+ }) {
54
+ if (!isCI) {
55
+ !existsSync(privateKeyPath) && await generateKey(privateKeyPath, publicKeyPath, keyLength);
56
+ await writePublicKeyToMain(entryPath, publicKeyPath);
57
+ }
58
+ await build({
59
+ entryPoints: [entryPath],
60
+ bundle: true,
61
+ platform: "node",
62
+ outfile,
63
+ minify,
64
+ external: ["electron"]
65
+ });
66
+ }
67
+
68
+ // src/build-asar.ts
69
+ import { constants, createSign } from "node:crypto";
70
+ import { createReadStream, createWriteStream } from "node:fs";
71
+ import { readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
72
+ import zlib from "node:zlib";
73
+ import { isCI as isCI2 } from "ci-info";
74
+ function gzipFile(filePath) {
75
+ return new Promise((resolve, reject) => {
76
+ const gzip = zlib.createGzip();
77
+ const input = createReadStream(filePath);
78
+ const output = createWriteStream(`${filePath}.gz`);
79
+ input.pipe(gzip).pipe(output).on("finish", () => resolve(null)).on("error", (err) => reject(err));
80
+ });
81
+ }
82
+ function generateSignature(buffer, privateKey) {
83
+ return createSign("RSA-SHA256").update(buffer).sign({
84
+ key: privateKey,
85
+ padding: constants.RSA_PKCS1_PADDING,
86
+ saltLength: constants.RSA_PSS_SALTLEN_DIGEST
87
+ }, "base64");
88
+ }
89
+ async function pack(dir, target) {
90
+ let asar = null;
91
+ try {
92
+ asar = await import("asar");
93
+ } catch (ignore) {
94
+ }
95
+ if (!asar) {
96
+ try {
97
+ asar = await import("@electron/asar");
98
+ } catch (ignore) {
99
+ }
100
+ }
101
+ if (!asar) {
102
+ throw new Error("no asar, please install @electron/asar");
103
+ }
104
+ await asar.createPackage(dir, target);
105
+ }
106
+ async function buildAsar({
107
+ version,
108
+ asarOutputPath,
109
+ privateKeyPath,
110
+ electronDistPath,
111
+ rendererDistPath
112
+ }) {
113
+ await rename(rendererDistPath, `${electronDistPath}/renderer`);
114
+ await writeFile2(`${electronDistPath}/version`, version);
115
+ await pack(electronDistPath, asarOutputPath);
116
+ if (isCI2) {
117
+ return;
118
+ }
119
+ await gzipFile(asarOutputPath);
120
+ const buffer = await readFile2(`${asarOutputPath}.gz`);
121
+ const signature = generateSignature(buffer, await readFile2(privateKeyPath, "utf-8"));
122
+ await writeFile2("version.json", JSON.stringify({
123
+ signature,
124
+ version,
125
+ size: buffer.length
126
+ }, null, 2));
127
+ }
128
+
129
+ // src/option.ts
130
+ function parseOptions(options) {
131
+ const { isBuild, productName, version, minify = false, paths = {}, keys = {} } = options;
132
+ const {
133
+ entryPath = "electron/app.ts",
134
+ entryOutputPath = "app.js",
135
+ asarOutputPath = `release/${productName}.asar`,
136
+ electronDistPath = "dist-electron",
137
+ rendererDistPath = "dist"
138
+ } = paths;
139
+ const {
140
+ privateKeyPath = "public/private.pem",
141
+ publicKeyPath = "public/public.pem",
142
+ keyLength = 2048
143
+ } = keys;
144
+ const buildAsarOption = {
145
+ version,
146
+ productName,
147
+ asarOutputPath,
148
+ privateKeyPath,
149
+ electronDistPath,
150
+ rendererDistPath
151
+ };
152
+ const buildEntryOption = {
153
+ privateKeyPath,
154
+ publicKeyPath,
155
+ entryPath,
156
+ entryOutputPath,
157
+ minify,
158
+ keyLength
159
+ };
160
+ return { isBuild, buildAsarOption, buildEntryOption };
161
+ }
162
+
163
+ // src/vite.ts
164
+ function vite_default(options) {
165
+ const { isBuild, buildAsarOption, buildEntryOption } = parseOptions(options);
166
+ const { entryPath, entryOutputPath } = buildEntryOption;
167
+ const { asarOutputPath } = buildAsarOption;
168
+ const id = "electron-incremental-updater";
169
+ const log = createLogger("info", { prefix: `[${id}]` });
170
+ return [
171
+ {
172
+ name: `vite-plugin-${id}-entry`,
173
+ async buildStart() {
174
+ log.info("build entry start");
175
+ await buildEntry(buildEntryOption);
176
+ log.info(`build entry end, ${entryPath} -> ${entryOutputPath}`);
177
+ }
178
+ },
179
+ {
180
+ name: `vite-plugin-${id}-asar`,
181
+ async closeBundle() {
182
+ if (!isBuild) {
183
+ return;
184
+ }
185
+ log.info("build asar start");
186
+ await buildAsar(buildAsarOption);
187
+ log.info(`build asar end, output to ${asarOutputPath}`);
188
+ }
189
+ }
190
+ ];
191
+ }
192
+ export {
193
+ vite_default as default
194
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "electron-incremental-update",
3
+ "version": "0.1.0",
4
+ "description": "electron incremental update tools, powered by vite",
5
+ "scripts": {
6
+ "build": "tsup",
7
+ "release": "bumpp"
8
+ },
9
+ "type": "module",
10
+ "license": "MIT",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "main": "dist/index.cjs",
15
+ "module": "dist/index.mjs",
16
+ "types": "dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "require": "./dist/index.cjs",
20
+ "import": "./dist/index.mjs"
21
+ },
22
+ "./vite": {
23
+ "require": "./dist/vite.cjs",
24
+ "import": "./dist/vite.mjs"
25
+ }
26
+ },
27
+ "typesVersions": {
28
+ "*": {
29
+ ".": [
30
+ "dist/updater.d.ts"
31
+ ],
32
+ "vite": [
33
+ "dist/vite.d.ts"
34
+ ]
35
+ }
36
+ },
37
+ "keywords": [
38
+ "electron",
39
+ "incremental update",
40
+ "updater"
41
+ ],
42
+ "author": "",
43
+ "devDependencies": {
44
+ "@electron/asar": "^3.2.4",
45
+ "@subframe7536/eslint-config": "^0.1.9",
46
+ "@types/node": "^20.2.5",
47
+ "asar": "^3.2.0",
48
+ "bumpp": "^9.1.1",
49
+ "electron": "^25.0.1",
50
+ "eslint": "^8.42.0",
51
+ "fs-jetpack": "^5.1.0",
52
+ "tsup": "^6.7.0",
53
+ "typescript": "^5.1.3",
54
+ "vite": "^4.3.9"
55
+ },
56
+ "dependencies": {
57
+ "ci-info": "^3.8.0"
58
+ },
59
+ "peerDependencies": {
60
+ "@electron/asar": "*",
61
+ "asar": "*"
62
+ }
63
+ }