electron-incremental-update 3.0.0-beta.5 → 3.0.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 +338 -373
- package/dist/crypto-BSky88mL.cjs +259 -0
- package/dist/crypto-DZzMmoz2.mjs +164 -0
- package/dist/{download-BN4uMS4_.d.mts → download-BVmLiAvB.d.cts} +2 -2
- package/dist/download-BdX4fZYM.mjs +130 -0
- package/dist/{download-DO7iuxEJ.d.cts → download-GONr15zK.d.mts} +2 -2
- package/dist/download-KySXUyWC.cjs +165 -0
- package/dist/{electron-BJCk7uxG.mjs → electron-BrIF1urZ.mjs} +38 -13
- package/dist/electron-CaS0I3S2.cjs +346 -0
- package/dist/index.cjs +66 -34
- package/dist/index.d.cts +10 -13
- package/dist/index.d.mts +10 -13
- package/dist/index.mjs +62 -31
- package/dist/local-C5jw-7o5.mjs +105 -0
- package/dist/local-DbXBG1D9.cjs +118 -0
- package/dist/provider.cjs +24 -43
- package/dist/provider.d.cts +58 -20
- package/dist/provider.d.mts +58 -20
- package/dist/provider.mjs +12 -33
- package/dist/{types-BM9Jfu7q.d.cts → types-q78spjKB.d.cts} +22 -7
- package/dist/{types-DASqEPXE.d.mts → types-q78spjKB.d.mts} +22 -7
- package/dist/utils.cjs +22 -22
- package/dist/utils.d.cts +6 -6
- package/dist/utils.d.mts +6 -6
- package/dist/utils.mjs +4 -5
- package/dist/vite.d.mts +99 -110
- package/dist/vite.mjs +656 -732
- package/package.json +30 -35
- package/dist/electron-C-qmVhAt.cjs +0 -321
- package/dist/version--eVB2A7n.mjs +0 -72
- package/dist/version-aPrLuz_-.cjs +0 -129
- package/dist/zip-BCC7FAQ_.cjs +0 -264
- package/dist/zip-Dwm7s1C9.mjs +0 -185
package/dist/vite.mjs
CHANGED
|
@@ -1,51 +1,30 @@
|
|
|
1
|
-
import { builtinModules
|
|
2
|
-
import { isCI } from "ci-info";
|
|
3
|
-
import { getPackageInfoSync, loadPackageJSON } from "local-pkg";
|
|
1
|
+
import { builtinModules } from "node:module";
|
|
4
2
|
import fs from "node:fs";
|
|
5
3
|
import path from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import crypto from "node:crypto";
|
|
4
|
+
import { isCI } from "ci-info";
|
|
5
|
+
import { createLogger, mergeConfig, normalizePath } from "vite";
|
|
6
|
+
import { electronPluginFactory } from "vite-plugin-electron/multi-env";
|
|
7
|
+
import { esmShim } from "vite-plugin-electron/plugin";
|
|
11
8
|
import zlib from "node:zlib";
|
|
9
|
+
import crypto from "node:crypto";
|
|
10
|
+
import cp from "node:child_process";
|
|
11
|
+
import * as babel from "@babel/core";
|
|
12
|
+
import { copyFile, cp as cp$1, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { createPackage, extractFile } from "@electron/asar";
|
|
12
15
|
import { generate } from "selfsigned";
|
|
13
|
-
import { createPackage } from "@electron/asar";
|
|
14
|
-
|
|
15
16
|
//#region src/vite/startup.ts
|
|
16
17
|
/**
|
|
17
|
-
* Debug mode startup function
|
|
18
|
-
* Automatically starts Electron in VSCode debug mode
|
|
19
|
-
* @param args - Startup arguments
|
|
20
|
-
*/
|
|
21
|
-
async function debugStartup(args) {
|
|
22
|
-
if (process.env.VSCODE_DEBUG) console.log("[startup] Electron App");
|
|
23
|
-
else await args.startup();
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
18
|
* Filter error messages from stdout/stderr during startup
|
|
27
19
|
* @param args - Startup arguments
|
|
28
20
|
* @param filter - Filter function to determine which messages to show
|
|
29
21
|
*/
|
|
30
|
-
async function filterErrorMessageStartup(
|
|
31
|
-
const stdio = process.platform === "linux" ? [
|
|
32
|
-
"inherit",
|
|
33
|
-
"pipe",
|
|
34
|
-
"pipe",
|
|
35
|
-
"ignore",
|
|
36
|
-
"ipc"
|
|
37
|
-
] : [
|
|
38
|
-
"inherit",
|
|
39
|
-
"pipe",
|
|
40
|
-
"pipe",
|
|
41
|
-
"ipc"
|
|
42
|
-
];
|
|
43
|
-
await args.startup(void 0, { stdio });
|
|
22
|
+
async function filterErrorMessageStartup(filter) {
|
|
44
23
|
const elec = process.electronApp;
|
|
45
|
-
elec.stdout
|
|
24
|
+
elec.stdout?.addListener("data", (data) => {
|
|
46
25
|
console.log(data.toString().trimEnd());
|
|
47
26
|
});
|
|
48
|
-
elec.stderr
|
|
27
|
+
elec.stderr?.addListener("data", (data) => {
|
|
49
28
|
const message = data.toString();
|
|
50
29
|
if (filter(message)) console.error(message);
|
|
51
30
|
});
|
|
@@ -61,62 +40,51 @@ function fixWinCharEncoding(fn) {
|
|
|
61
40
|
await fn(...args);
|
|
62
41
|
});
|
|
63
42
|
}
|
|
64
|
-
|
|
65
43
|
//#endregion
|
|
66
|
-
//#region src/
|
|
67
|
-
const id = "electron-incremental-updater";
|
|
68
|
-
const bytecodeId = `${id}-bytecode`;
|
|
69
|
-
const log = createLogger("info", { prefix: `[${id}]` });
|
|
70
|
-
const bytecodeLog = createLogger("info", { prefix: `[${bytecodeId}]` });
|
|
71
|
-
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region src/vite/utils/file.ts
|
|
44
|
+
//#region src/utils/compress.ts
|
|
74
45
|
/**
|
|
75
|
-
*
|
|
76
|
-
* @param
|
|
77
|
-
* @returns Human-readable size string (e.g., "1.23 MB")
|
|
46
|
+
* Default function to compress file using brotli
|
|
47
|
+
* @param buffer uncompressed file buffer
|
|
78
48
|
*/
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
49
|
+
async function defaultCompressFile(buffer) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
zlib.brotliCompress(buffer, (err, buffer) => err ? reject(err) : resolve(buffer));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/utils/crypto.ts
|
|
56
|
+
function hashBuffer(data, length) {
|
|
57
|
+
const hash = crypto.createHash("SHA256").update(data).digest("binary");
|
|
58
|
+
return Buffer.from(hash).subarray(0, length);
|
|
59
|
+
}
|
|
60
|
+
function aesEncrypt(plainText, key, iv) {
|
|
61
|
+
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
|
62
|
+
return cipher.update(plainText, "utf8", "base64url") + cipher.final("base64url");
|
|
92
63
|
}
|
|
93
64
|
/**
|
|
94
|
-
*
|
|
95
|
-
* @param
|
|
96
|
-
* @param
|
|
97
|
-
* @param
|
|
65
|
+
* Default function to generate asar signature, returns generated signature
|
|
66
|
+
* @param buffer file buffer
|
|
67
|
+
* @param privateKey primary key
|
|
68
|
+
* @param cert certificate
|
|
69
|
+
* @param version target version
|
|
98
70
|
*/
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
fs.cpSync(from, to, { recursive: true });
|
|
102
|
-
} catch (error) {
|
|
103
|
-
log.warn(`Copy failed: ${error}`, { timestamp: true });
|
|
104
|
-
}
|
|
71
|
+
function defaultSignature(buffer, privateKey, cert, version) {
|
|
72
|
+
return aesEncrypt(`${crypto.createSign("RSA-SHA256").update(buffer).sign(crypto.createPrivateKey(privateKey), "base64")}%${version}`, hashBuffer(cert, 32), hashBuffer(buffer, 16));
|
|
105
73
|
}
|
|
106
|
-
|
|
107
|
-
//#endregion
|
|
108
|
-
//#region src/vite/bytecode/code.ts
|
|
109
|
-
const bytecodeGeneratorScript = "const vm = require('vm')\nconst v8 = require('v8')\nconst wrap = require('module').wrap\nv8.setFlagsFromString('--no-lazy')\nv8.setFlagsFromString('--no-flush-bytecode')\nlet code = ''\nprocess.stdin.setEncoding('utf-8')\nprocess.stdin.on('readable', () => {\n const data = process.stdin.read()\n if (data !== null) {\n code += data\n }\n})\nprocess.stdin.on('end', () => {\n try {\n if (typeof code !== 'string') {\n throw new Error('javascript code must be string.')\n }\n const script = new vm.Script(wrap(code), { produceCachedData: true })\n const bytecodeBuffer = script.createCachedData()\n process.stdout.write(bytecodeBuffer)\n } catch (error) {\n console.error(error)\n }\n})\n";
|
|
110
|
-
const bytecodeModuleLoaderCode = "\"use strict\";\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst vm = require(\"vm\");\nconst v8 = require(\"v8\");\nconst Module = require(\"module\");\nv8.setFlagsFromString(\"--no-lazy\");\nv8.setFlagsFromString(\"--no-flush-bytecode\");\nconst FLAG_HASH_OFFSET = 12;\nconst SOURCE_HASH_OFFSET = 8;\nlet dummyBytecode;\nfunction setFlagHashHeader(bytecodeBuffer) {\n if (!dummyBytecode) {\n const script = new vm.Script(\"\", {\n produceCachedData: true\n });\n dummyBytecode = script.createCachedData();\n }\n dummyBytecode.slice(FLAG_HASH_OFFSET, FLAG_HASH_OFFSET + 4).copy(bytecodeBuffer, FLAG_HASH_OFFSET);\n};\nfunction getSourceHashHeader(bytecodeBuffer) {\n return bytecodeBuffer.slice(SOURCE_HASH_OFFSET, SOURCE_HASH_OFFSET + 4);\n};\nfunction buffer2Number(buffer) {\n let ret = 0;\n ret |= buffer[3] << 24;\n ret |= buffer[2] << 16;\n ret |= buffer[1] << 8;\n ret |= buffer[0];\n return ret;\n};\nModule._extensions[\".jsc\"] = Module._extensions[\".cjsc\"] = function (module, filename) {\n const bytecodeBuffer = fs.readFileSync(filename);\n if (!Buffer.isBuffer(bytecodeBuffer)) {\n throw new Error(\"BytecodeBuffer must be a buffer object.\");\n }\n setFlagHashHeader(bytecodeBuffer);\n const length = buffer2Number(getSourceHashHeader(bytecodeBuffer));\n let dummyCode = \"\";\n if (length > 1) {\n dummyCode = \"\\\"\" + \"\\u200b\".repeat(length - 2) + \"\\\"\";\n }\n const script = new vm.Script(dummyCode, {\n filename: filename,\n lineOffset: 0,\n displayErrors: true,\n cachedData: bytecodeBuffer\n });\n if (script.cachedDataRejected) {\n throw new Error(\"Invalid or incompatible cached data (cachedDataRejected)\");\n }\n const require = function (id) {\n return module.require(id);\n };\n require.resolve = function (request, options) {\n return Module._resolveFilename(request, module, false, options);\n };\n if (process.mainModule) {\n require.main = process.mainModule;\n }\n require.extensions = Module._extensions;\n require.cache = Module._cache;\n const compiledWrapper = script.runInThisContext({\n filename: filename,\n lineOffset: 0,\n columnOffset: 0,\n displayErrors: true\n });\n const dirname = path.dirname(filename);\n const args = [module.exports, require, module, filename, dirname, process, global];\n return compiledWrapper.apply(module.exports, args);\n};\n";
|
|
111
|
-
|
|
112
74
|
//#endregion
|
|
113
75
|
//#region src/utils/version.ts
|
|
76
|
+
const REG_VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([a-z0-9]+)(?:\.(\d+))?)?$/i;
|
|
114
77
|
/**
|
|
115
78
|
* Parse version string to {@link Version}, like `0.2.0-beta.1`
|
|
79
|
+
*
|
|
80
|
+
* **Supported format**: `major.minor.patch[-stage[.stageVersion]]`
|
|
81
|
+
*
|
|
82
|
+
* Build metadata (`+build`) and complex semver prerelease identifiers
|
|
83
|
+
* (e.g. `1.0.0-beta.1.2`) are not supported yet.
|
|
116
84
|
* @param version version string
|
|
117
85
|
*/
|
|
118
86
|
function parseVersion(version) {
|
|
119
|
-
const match =
|
|
87
|
+
const match = REG_VERSION.exec(version);
|
|
120
88
|
if (!match) throw new TypeError(`invalid version: ${version}`);
|
|
121
89
|
const [major, minor, patch] = match.slice(1, 4).map(Number);
|
|
122
90
|
const ret = {
|
|
@@ -127,9 +95,8 @@ function parseVersion(version) {
|
|
|
127
95
|
stageVersion: -1
|
|
128
96
|
};
|
|
129
97
|
if (match[4]) {
|
|
130
|
-
|
|
131
|
-
ret.
|
|
132
|
-
ret.stageVersion = Number(_v) || -1;
|
|
98
|
+
ret.stage = match[4];
|
|
99
|
+
ret.stageVersion = match[5] === void 0 ? -1 : Number(match[5]);
|
|
133
100
|
}
|
|
134
101
|
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || Number.isNaN(ret.stageVersion)) throw new TypeError(`Invalid version: ${version}`);
|
|
135
102
|
return ret;
|
|
@@ -140,7 +107,7 @@ const is = (j) => !!(j && j.minimumVersion && j.signature && j.version);
|
|
|
140
107
|
* @param json any variable
|
|
141
108
|
*/
|
|
142
109
|
function isUpdateJSON(json) {
|
|
143
|
-
return is(json) && is(json
|
|
110
|
+
return json && is(json) && is(json.beta);
|
|
144
111
|
}
|
|
145
112
|
/**
|
|
146
113
|
* Default function to generate `UpdateJSON`
|
|
@@ -162,74 +129,141 @@ function defaultVersionJsonGenerator(existingJson, signature, version, minimumVe
|
|
|
162
129
|
}
|
|
163
130
|
return existingJson;
|
|
164
131
|
}
|
|
165
|
-
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/vite/constant.ts
|
|
134
|
+
const id = "electron-incremental-update";
|
|
135
|
+
const bytecodeId = `${id}-bytecode`;
|
|
136
|
+
const log = createLogger("info", { prefix: `[${id}]` });
|
|
137
|
+
const bytecodeLog = createLogger("info", { prefix: `[${bytecodeId}]` });
|
|
138
|
+
const defaultExternal = [
|
|
139
|
+
...builtinModules,
|
|
140
|
+
"electron",
|
|
141
|
+
"electron/common",
|
|
142
|
+
"electron/main",
|
|
143
|
+
"electron/renderer",
|
|
144
|
+
"electron/utility",
|
|
145
|
+
/^node:/,
|
|
146
|
+
/.*\.(node|dll|dylib|so)$/,
|
|
147
|
+
"original-fs"
|
|
148
|
+
];
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/vite/bytecode/code.ts
|
|
151
|
+
const bytecodeGeneratorScript = "const vm = require('vm')\nconst v8 = require('v8')\nconst wrap = require('module').wrap\nv8.setFlagsFromString('--no-lazy')\nv8.setFlagsFromString('--no-flush-bytecode')\nlet code = ''\nprocess.stdin.setEncoding('utf-8')\nprocess.stdin.on('readable', () => {\n const data = process.stdin.read()\n if (data !== null) {\n code += data\n }\n})\nprocess.stdin.on('end', () => {\n try {\n if (typeof code !== 'string') {\n throw new Error('javascript code must be string.')\n }\n const script = new vm.Script(wrap(code), { produceCachedData: true })\n const bytecodeBuffer = script.createCachedData()\n process.stdout.write(bytecodeBuffer)\n } catch (error) {\n console.error(error)\n }\n})\n";
|
|
152
|
+
const bytecodeModuleLoaderCode = "\"use strict\";\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst vm = require(\"vm\");\nconst v8 = require(\"v8\");\nconst Module = require(\"module\");\nv8.setFlagsFromString(\"--no-lazy\");\nv8.setFlagsFromString(\"--no-flush-bytecode\");\nconst FLAG_HASH_OFFSET = 12;\nconst SOURCE_HASH_OFFSET = 8;\nlet dummyBytecode;\nfunction setFlagHashHeader(bytecodeBuffer) {\n if (!dummyBytecode) {\n const script = new vm.Script(\"\", {\n produceCachedData: true\n });\n dummyBytecode = script.createCachedData();\n }\n dummyBytecode.slice(FLAG_HASH_OFFSET, FLAG_HASH_OFFSET + 4).copy(bytecodeBuffer, FLAG_HASH_OFFSET);\n};\nfunction getSourceHashHeader(bytecodeBuffer) {\n return bytecodeBuffer.slice(SOURCE_HASH_OFFSET, SOURCE_HASH_OFFSET + 4);\n};\nfunction buffer2Number(buffer) {\n let ret = 0;\n ret |= buffer[3] << 24;\n ret |= buffer[2] << 16;\n ret |= buffer[1] << 8;\n ret |= buffer[0];\n return ret;\n};\nModule._extensions[\".jsc\"] = Module._extensions[\".cjsc\"] = function (module, filename) {\n const bytecodeBuffer = fs.readFileSync(filename);\n if (!Buffer.isBuffer(bytecodeBuffer)) {\n throw new Error(\"BytecodeBuffer must be a buffer object.\");\n }\n setFlagHashHeader(bytecodeBuffer);\n const length = buffer2Number(getSourceHashHeader(bytecodeBuffer));\n let dummyCode = \"\";\n if (length > 1) {\n dummyCode = \"\\\"\" + \"\\u200b\".repeat(length - 2) + \"\\\"\";\n }\n const script = new vm.Script(dummyCode, {\n filename: filename,\n lineOffset: 0,\n displayErrors: true,\n cachedData: bytecodeBuffer\n });\n if (script.cachedDataRejected) {\n throw new Error(\"Invalid or incompatible cached data (cachedDataRejected)\");\n }\n const require = function (id) {\n return module.require(id);\n };\n require.resolve = function (request, options) {\n return Module._resolveFilename(request, module, false, options);\n };\n if (process.mainModule) {\n require.main = process.mainModule;\n }\n require.extensions = Module._extensions;\n require.cache = Module._cache;\n const compiledWrapper = script.runInThisContext({\n filename: filename,\n lineOffset: 0,\n columnOffset: 0,\n displayErrors: true\n });\n const dirname = path.dirname(filename);\n const args = [module.exports, require, module, filename, dirname, process, global];\n return compiledWrapper.apply(module.exports, args);\n};\n";
|
|
166
153
|
//#endregion
|
|
167
154
|
//#region src/vite/bytecode/utils.ts
|
|
168
|
-
const electronModule = getPackageInfoSync("electron");
|
|
169
|
-
const electronMajorVersion = parseVersion(electronModule.version).major;
|
|
170
155
|
const useStrict = "'use strict';";
|
|
171
156
|
const bytecodeModuleLoader = "__loader__.js";
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (!
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (executablePath) {
|
|
181
|
-
electronExecPath = path.join(electronModulePath, "dist", executablePath);
|
|
182
|
-
process.env.ELECTRON_EXEC_PATH = electronExecPath;
|
|
183
|
-
} else throw new Error("Electron executable file is not existed");
|
|
184
|
-
}
|
|
185
|
-
return electronExecPath;
|
|
157
|
+
async function resolvePaths(customPath) {
|
|
158
|
+
if (!customPath || !process.CACHED_ELECTRON_PATH) process.CACHED_ELECTRON_PATH = (await import("electron")).default;
|
|
159
|
+
if (!process.CACHED_BYTECODE_COMPILER_PATH) process.CACHED_BYTECODE_COMPILER_PATH = path.join(path.dirname(process.CACHED_ELECTRON_PATH), "EIU_bytenode.cjs");
|
|
160
|
+
if (!fs.existsSync(process.CACHED_BYTECODE_COMPILER_PATH)) fs.writeFileSync(process.CACHED_BYTECODE_COMPILER_PATH, bytecodeGeneratorScript);
|
|
161
|
+
return {
|
|
162
|
+
electronPath: customPath || process.CACHED_ELECTRON_PATH,
|
|
163
|
+
bytecodePath: process.CACHED_BYTECODE_COMPILER_PATH
|
|
164
|
+
};
|
|
186
165
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
166
|
+
async function compileToBytecode(code, name, customElectronPath) {
|
|
167
|
+
try {
|
|
168
|
+
const { bytecodePath, electronPath } = await resolvePaths(customElectronPath);
|
|
169
|
+
return await new Promise((resolve, reject) => {
|
|
170
|
+
const proc = cp.spawn(electronPath, [bytecodePath], {
|
|
171
|
+
env: {
|
|
172
|
+
...process.env,
|
|
173
|
+
ELECTRON_RUN_AS_NODE: "1"
|
|
174
|
+
},
|
|
175
|
+
stdio: [
|
|
176
|
+
"pipe",
|
|
177
|
+
"pipe",
|
|
178
|
+
"pipe",
|
|
179
|
+
"ipc"
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
const stdoutChunks = [];
|
|
183
|
+
const stderrChunks = [];
|
|
184
|
+
if (proc.stdin) {
|
|
185
|
+
proc.stdin.write(code);
|
|
186
|
+
proc.stdin.end();
|
|
187
|
+
}
|
|
188
|
+
if (proc.stdout) proc.stdout.on("data", (chunk_2) => stdoutChunks.push(chunk_2)).on("error", (err) => reject(err));
|
|
189
|
+
if (proc.stderr) proc.stderr.on("data", (chunk_3) => stderrChunks.push(chunk_3)).on("error", (err_1) => reject(err_1));
|
|
190
|
+
proc.on("error", (err_2) => reject(err_2));
|
|
191
|
+
proc.on("close", (exitCode) => {
|
|
192
|
+
const stdout = Buffer.concat(stdoutChunks);
|
|
193
|
+
const errorMessage = Buffer.concat(stderrChunks).toString("utf-8");
|
|
194
|
+
if (exitCode !== 0 || stdout.length === 0) {
|
|
195
|
+
reject(/* @__PURE__ */ new Error(`Bytecode generation process exited with code ${exitCode}. Error: ${errorMessage}`));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
resolve(stdout);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return `Failed to generate bytecode of [${name}], ${e}`;
|
|
203
|
+
}
|
|
191
204
|
}
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
205
|
+
const decodeFnBody = babel.parse(";function _0xstr_(a,b){return String.fromCharCode.apply(0,a.map(function(x){return x-b}))};")?.program.body ?? [];
|
|
206
|
+
function createObfuscatedStringCall(input, offset) {
|
|
207
|
+
const resolvedOffset = offset ?? ~~(Math.random() * 16) + 1;
|
|
208
|
+
const elements = input.split("").map((char) => {
|
|
209
|
+
const value = char.codePointAt(0) + resolvedOffset;
|
|
210
|
+
const node = babel.types.numericLiteral(value);
|
|
211
|
+
node.extra = {
|
|
212
|
+
raw: `0x${value.toString(16)}`,
|
|
213
|
+
rawValue: value
|
|
214
|
+
};
|
|
215
|
+
return node;
|
|
216
|
+
});
|
|
217
|
+
return babel.types.callExpression(babel.types.identifier("_0xstr_"), [babel.types.arrayExpression(elements), babel.types.numericLiteral(resolvedOffset)]);
|
|
218
|
+
}
|
|
219
|
+
function createPrepareContext(bytecodeFileNames) {
|
|
220
|
+
const requireRewrites = {};
|
|
221
|
+
for (const fileName of bytecodeFileNames) {
|
|
222
|
+
if (!fileName.endsWith(".js") && !fileName.endsWith(".cjs")) continue;
|
|
223
|
+
const baseName = path.posix.basename(fileName);
|
|
224
|
+
requireRewrites[baseName] = `${baseName}c`;
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
requireRewrites,
|
|
228
|
+
hasRequireRewrites: Object.keys(requireRewrites).length > 0
|
|
229
|
+
};
|
|
195
230
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
});
|
|
209
|
-
if (proc.stdin) {
|
|
210
|
-
proc.stdin.write(code);
|
|
211
|
-
proc.stdin.end();
|
|
212
|
-
}
|
|
213
|
-
if (proc.stdout) {
|
|
214
|
-
proc.stdout.on("data", (chunk) => data = Buffer.concat([data, chunk]));
|
|
215
|
-
proc.stdout.on("error", (err) => reject(err));
|
|
216
|
-
proc.stdout.on("end", () => resolve(data));
|
|
217
|
-
}
|
|
218
|
-
if (proc.stderr) {
|
|
219
|
-
proc.stderr.on("data", (chunk) => reject(chunk.toString()));
|
|
220
|
-
proc.stderr.on("error", (err) => reject(err));
|
|
221
|
-
}
|
|
222
|
-
proc.addListener("error", (err) => reject(err));
|
|
223
|
-
proc.on("error", (err) => reject(err));
|
|
224
|
-
proc.on("exit", () => resolve(data));
|
|
225
|
-
}).catch((e) => `Failed to generate bytecode of [${name}], ${e}`);
|
|
231
|
+
function rewriteRequirePath(requirePath, requireRewrites) {
|
|
232
|
+
const baseName = path.posix.basename(requirePath);
|
|
233
|
+
const newBaseName = requireRewrites[baseName];
|
|
234
|
+
if (!newBaseName) return;
|
|
235
|
+
return `${requirePath.slice(0, -baseName.length)}${newBaseName}`;
|
|
236
|
+
}
|
|
237
|
+
function rewriteSimpleRequireCalls(code, requireRewrites) {
|
|
238
|
+
if (!code.includes("require(")) return code;
|
|
239
|
+
return code.replace(/\brequire\(\s*(['"])([^'"]+)\1\s*\)/g, (match, quote, requirePath) => {
|
|
240
|
+
const newRequirePath = rewriteRequirePath(requirePath, requireRewrites);
|
|
241
|
+
return newRequirePath ? `require(${quote}${newRequirePath}${quote})` : match;
|
|
242
|
+
});
|
|
226
243
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
function obfuscateStringsPlugin(_, options) {
|
|
245
|
+
function transformProperty(path) {
|
|
246
|
+
const node = path.node;
|
|
247
|
+
if (node.computed || !babel.types.isIdentifier(node.property)) return;
|
|
248
|
+
if (path.findParent((parent) => parent.isFunctionDeclaration() && babel.types.isIdentifier(parent.node.id, { name: "_0xstr_" }))) return;
|
|
249
|
+
node.computed = true;
|
|
250
|
+
node.property = babel.types.stringLiteral(node.property.name);
|
|
251
|
+
}
|
|
252
|
+
return { visitor: {
|
|
253
|
+
MemberExpression(path) {
|
|
254
|
+
transformProperty(path);
|
|
255
|
+
},
|
|
256
|
+
OptionalMemberExpression(path) {
|
|
257
|
+
transformProperty(path);
|
|
258
|
+
},
|
|
259
|
+
ObjectProperty(path, state) {
|
|
260
|
+
const key = path.node.key;
|
|
261
|
+
if (path.node.computed || path.node.shorthand || !babel.types.isIdentifier(key) || key.name === "__proto__") return;
|
|
262
|
+
path.node.computed = true;
|
|
263
|
+
path.node.key = createObfuscatedStringCall(key.name, options.offset);
|
|
264
|
+
state.hasTransformed = true;
|
|
265
|
+
},
|
|
266
|
+
StringLiteral(path, state) {
|
|
233
267
|
const parent = path.parent;
|
|
234
268
|
const node = path.node;
|
|
235
269
|
if (parent.type === "CallExpression") {
|
|
@@ -239,409 +273,348 @@ function prepare(code, offset) {
|
|
|
239
273
|
if (parent.type.startsWith("Export")) return;
|
|
240
274
|
if (parent.type.startsWith("Import")) return;
|
|
241
275
|
if (parent.type === "ObjectMethod" && parent.key === node) {
|
|
242
|
-
|
|
243
|
-
path.
|
|
244
|
-
|
|
245
|
-
hasTransformed = true;
|
|
276
|
+
parent.computed = true;
|
|
277
|
+
path.replaceWith(createObfuscatedStringCall(node.value, options.offset));
|
|
278
|
+
state.hasTransformed = true;
|
|
246
279
|
return;
|
|
247
280
|
}
|
|
248
281
|
if (parent.type === "ObjectProperty" && parent.key === node) {
|
|
249
|
-
|
|
250
|
-
path.
|
|
251
|
-
|
|
252
|
-
hasTransformed = true;
|
|
282
|
+
parent.computed = true;
|
|
283
|
+
path.replaceWith(createObfuscatedStringCall(node.value, options.offset));
|
|
284
|
+
state.hasTransformed = true;
|
|
253
285
|
return;
|
|
254
286
|
}
|
|
255
287
|
if (!node.value.trim()) return;
|
|
256
|
-
path.replaceWith(
|
|
257
|
-
hasTransformed = true;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
288
|
+
path.replaceWith(createObfuscatedStringCall(node.value, options.offset));
|
|
289
|
+
state.hasTransformed = true;
|
|
290
|
+
},
|
|
291
|
+
Program: { exit(path, state) {
|
|
292
|
+
if (!state.hasTransformed) return;
|
|
293
|
+
path.unshiftContainer("body", decodeFnBody.map((node) => babel.types.cloneNode(node)));
|
|
294
|
+
} }
|
|
295
|
+
} };
|
|
296
|
+
}
|
|
297
|
+
function rewriteRequirePlugin(_, options) {
|
|
298
|
+
return { visitor: { CallExpression(path) {
|
|
299
|
+
if (!babel.types.isIdentifier(path.node.callee, { name: "require" }) || path.node.arguments.length === 0) return;
|
|
300
|
+
const arg = path.node.arguments[0];
|
|
301
|
+
if (!babel.types.isStringLiteral(arg)) return;
|
|
302
|
+
const newRequirePath = rewriteRequirePath(arg.value, options.requireRewrites);
|
|
303
|
+
if (newRequirePath) path.node.arguments[0] = babel.types.stringLiteral(newRequirePath);
|
|
304
|
+
} } };
|
|
305
|
+
}
|
|
306
|
+
function prepare(code, minify, context, offset) {
|
|
307
|
+
if (!code.includes("\"") && !code.includes("'") && !code.includes("`") && !code.includes("=>") && !/\.[A-Za-z_$]/.test(code) && !/[{,]\s*[A-Za-z_$][\w$]*\s*:/.test(code)) return { code };
|
|
308
|
+
if (context.hasRequireRewrites && !code.includes("`") && !code.includes("=>")) {
|
|
309
|
+
const codeWithoutSimpleRequires = code.replace(/\brequire\(\s*(['"])([^'"]+)\1\s*\)/g, "");
|
|
310
|
+
if (!codeWithoutSimpleRequires.includes("\"") && !codeWithoutSimpleRequires.includes("'")) return { code: rewriteSimpleRequireCalls(code, context.requireRewrites) };
|
|
311
|
+
}
|
|
312
|
+
return babel.transform(code, {
|
|
313
|
+
minified: minify,
|
|
314
|
+
plugins: [
|
|
315
|
+
"@babel/plugin-transform-arrow-functions",
|
|
316
|
+
"@babel/plugin-transform-template-literals",
|
|
317
|
+
[obfuscateStringsPlugin, { offset }],
|
|
318
|
+
[rewriteRequirePlugin, { requireRewrites: context.requireRewrites }]
|
|
319
|
+
]
|
|
320
|
+
});
|
|
265
321
|
}
|
|
266
|
-
|
|
267
322
|
//#endregion
|
|
268
323
|
//#region src/vite/bytecode/index.ts
|
|
269
324
|
function getBytecodeLoaderBlock(chunkFileName) {
|
|
270
|
-
|
|
325
|
+
const loaderFileName = path.posix.relative(path.posix.dirname(chunkFileName), bytecodeModuleLoader);
|
|
326
|
+
return `require("${loaderFileName.startsWith(".") ? loaderFileName : `./${loaderFileName}`}")`;
|
|
271
327
|
}
|
|
272
|
-
function
|
|
273
|
-
const queue = [...imports, ...dynamicImports];
|
|
274
|
-
const visited = /* @__PURE__ */ new Set();
|
|
275
|
-
while (queue.length > 0) {
|
|
276
|
-
const moduleId = queue.shift();
|
|
277
|
-
if (visited.has(moduleId)) continue;
|
|
278
|
-
visited.add(moduleId);
|
|
279
|
-
if (bytecodeChunks.has(moduleId)) return true;
|
|
280
|
-
const moduleInfo = getModuleInfo(moduleId);
|
|
281
|
-
if (moduleInfo) {
|
|
282
|
-
const { importers, dynamicImporters } = moduleInfo;
|
|
283
|
-
for (const importerId of importers) if (!visited.has(importerId)) queue.push(importerId);
|
|
284
|
-
for (const importerId of dynamicImporters) if (!visited.has(importerId)) queue.push(importerId);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Vite plugin that compiles JavaScript files to V8 bytecode for source code protection.
|
|
291
|
-
*
|
|
292
|
-
* This plugin transforms JavaScript files into bytecode format during build,
|
|
293
|
-
* making the source code more difficult to reverse engineer. The generated bytecode
|
|
294
|
-
* files (.jsc) can only be executed in compatible Electron environments.
|
|
295
|
-
*
|
|
296
|
-
* @param env - Environment type ('preload' | 'main')
|
|
297
|
-
* @param options - Bytecode compilation options
|
|
298
|
-
* @returns Vite plugin or null if bytecode compilation is disabled
|
|
299
|
-
*/
|
|
300
|
-
function bytecodePlugin(env, isESM, options) {
|
|
328
|
+
function bytecodePlugin(env, minify, isESM, options) {
|
|
301
329
|
const { enable, preload = false, electronPath, beforeCompile } = options;
|
|
302
330
|
if (!enable) return null;
|
|
303
|
-
if (
|
|
304
|
-
bytecodeLog.warn("`bytecodePlugin` is
|
|
331
|
+
if (env === "preload" && !preload) {
|
|
332
|
+
if (preload === void 0) bytecodeLog.warn("`bytecodePlugin` is skipped in preload. Set `bytecode: { preload: false }` to disable this warning, or `bytecode: { preload: true }` and set `sandbox: false` in BrowserWindow to enable bytecode in preload.", { timestamp: true });
|
|
305
333
|
return null;
|
|
306
334
|
}
|
|
307
|
-
if (isESM) throw new Error("`bytecodePlugin`
|
|
308
|
-
let
|
|
309
|
-
let bytecodeRequired = false;
|
|
310
|
-
let bytecodeFiles = [];
|
|
335
|
+
if (isESM) throw new Error("`bytecodePlugin` requires CommonJS. Set \"type\": \"commonjs\" in package.json and use .cjs extensions");
|
|
336
|
+
let hasJsChunks = false;
|
|
311
337
|
return {
|
|
312
|
-
name:
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
config = resolvedConfig;
|
|
317
|
-
},
|
|
318
|
-
renderChunk(_, chunk) {
|
|
319
|
-
if (chunk.type === "chunk") bytecodeRequired = true;
|
|
320
|
-
return null;
|
|
321
|
-
},
|
|
322
|
-
generateBundle() {
|
|
323
|
-
if (bytecodeRequired) this.emitFile({
|
|
338
|
+
name: bytecodeId,
|
|
339
|
+
async generateBundle(outputOptions, bundle) {
|
|
340
|
+
hasJsChunks = Object.values(bundle).some((file) => file.type === "chunk" && (file.fileName.endsWith(".js") || file.fileName.endsWith(".cjs")));
|
|
341
|
+
if (hasJsChunks) this.emitFile({
|
|
324
342
|
type: "asset",
|
|
325
343
|
source: `${bytecodeModuleLoaderCode}\n`,
|
|
326
|
-
name: "Bytecode Loader
|
|
344
|
+
name: "Bytecode Loader",
|
|
327
345
|
fileName: bytecodeModuleLoader
|
|
328
346
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (chunk.type === "chunk") {
|
|
342
|
-
let _code = prepare(chunk.code);
|
|
343
|
-
const chunkFilePath = path.resolve(outDir, name);
|
|
344
|
-
if (beforeCompile) {
|
|
345
|
-
const cbResult = await beforeCompile(_code, chunkFilePath);
|
|
346
|
-
if (cbResult) _code = cbResult;
|
|
347
|
-
}
|
|
348
|
-
if (bytecodeRE && _code.match(bytecodeRE)) {
|
|
349
|
-
let match;
|
|
350
|
-
const s = new MagicString(_code);
|
|
351
|
-
while (match = bytecodeRE.exec(_code)) {
|
|
352
|
-
const [prefix, chunkName] = match;
|
|
353
|
-
const len = prefix.length + chunkName.length;
|
|
354
|
-
s.overwrite(match.index, match.index + len, `${prefix + chunkName}c`, { contentOnly: true });
|
|
355
|
-
}
|
|
356
|
-
_code = s.toString();
|
|
357
|
-
}
|
|
358
|
-
if (bytecodeChunks.has(name)) {
|
|
359
|
-
const result = await compileToBytecode(_code, path.join(outDir, chunk.fileName), electronPath);
|
|
360
|
-
if (typeof result === "string") throw new Error(result);
|
|
361
|
-
fs.writeFileSync(`${chunkFilePath}c`, result);
|
|
362
|
-
if (chunk.isEntry) {
|
|
363
|
-
const code = `${useStrict}\n${getBytecodeLoaderBlock(chunk.fileName)}\nmodule.exports=${`require("./${`${path.basename(name)}c`}");`}\n`;
|
|
364
|
-
fs.writeFileSync(chunkFilePath, code);
|
|
365
|
-
} else fs.unlinkSync(chunkFilePath);
|
|
366
|
-
bytecodeFiles.push({
|
|
367
|
-
name: `${name}c`,
|
|
368
|
-
size: result.length
|
|
369
|
-
});
|
|
370
|
-
} else if (chunk.isEntry) {
|
|
371
|
-
if (checkHasBytecodeChunk(this.getModuleInfo, chunk.imports, chunk.dynamicImports, bytecodeChunks)) {
|
|
372
|
-
const bytecodeLoaderBlock = getBytecodeLoaderBlock(chunk.fileName);
|
|
373
|
-
_code = _code.replace(/use strict(["'];)/, `$1\n${bytecodeLoaderBlock}`);
|
|
374
|
-
}
|
|
375
|
-
fs.writeFileSync(chunkFilePath, _code);
|
|
376
|
-
} else fs.writeFileSync(chunkFilePath, _code);
|
|
347
|
+
if (!hasJsChunks) return;
|
|
348
|
+
const outputDir = outputOptions.dir ?? (outputOptions.file && path.dirname(outputOptions.file));
|
|
349
|
+
const prepareContext = createPrepareContext(Object.values(bundle).filter((f) => f.type === "chunk" && !f.isEntry).map((c) => path.posix.basename(c.fileName)));
|
|
350
|
+
await Promise.all(Object.entries(bundle).map(async ([fileName, item]) => {
|
|
351
|
+
if (item.type !== "chunk" || fileName === "__loader__.js") return;
|
|
352
|
+
const chunk = item;
|
|
353
|
+
const bytecodeFileName = `${fileName}c`;
|
|
354
|
+
const absPath = outputDir ? path.join(outputDir, fileName) : fileName;
|
|
355
|
+
let code = prepare(chunk.code, minify, prepareContext)?.code || chunk.code;
|
|
356
|
+
if (beforeCompile) {
|
|
357
|
+
const hookResult = await beforeCompile(code, absPath);
|
|
358
|
+
if (hookResult) code = hookResult;
|
|
377
359
|
}
|
|
360
|
+
const bytecode = await compileToBytecode(code, absPath, electronPath);
|
|
361
|
+
if (typeof bytecode === "string") throw new TypeError(bytecode);
|
|
362
|
+
this.emitFile({
|
|
363
|
+
type: "asset",
|
|
364
|
+
source: bytecode,
|
|
365
|
+
fileName: bytecodeFileName
|
|
366
|
+
});
|
|
367
|
+
if (chunk.isEntry) chunk.code = `${useStrict}\n${getBytecodeLoaderBlock(fileName)}\nmodule.exports=require("./${path.posix.basename(fileName)}c");\n`;
|
|
368
|
+
else delete bundle[fileName];
|
|
378
369
|
}));
|
|
379
|
-
},
|
|
380
|
-
closeBundle() {
|
|
381
|
-
const outDir = `${normalizePath(path.relative(config.root, path.resolve(config.root, config.build.outDir)))}/`;
|
|
382
|
-
bytecodeFiles.forEach((file) => {
|
|
383
|
-
bytecodeLog.info(`${outDir}${file.name} [${readableSize(file.size)}]`, { timestamp: true });
|
|
384
|
-
});
|
|
385
|
-
bytecodeLog.info(`${bytecodeFiles.length} bundles compiled into bytecode.`, { timestamp: true });
|
|
386
|
-
bytecodeFiles = [];
|
|
387
370
|
}
|
|
388
371
|
};
|
|
389
372
|
}
|
|
390
|
-
|
|
391
373
|
//#endregion
|
|
392
|
-
//#region src/vite/
|
|
393
|
-
|
|
394
|
-
function
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
outDir: "dist-electron",
|
|
405
|
-
emptyOutDir: false
|
|
406
|
-
},
|
|
407
|
-
resolve: {
|
|
408
|
-
conditions: ["node"],
|
|
409
|
-
mainFields: [
|
|
410
|
-
"module",
|
|
411
|
-
"jsnext:main",
|
|
412
|
-
"jsnext"
|
|
413
|
-
]
|
|
414
|
-
},
|
|
415
|
-
define: { "process.env": "process.env" }
|
|
416
|
-
}, options?.vite || {});
|
|
374
|
+
//#region src/vite/local-dev-update.ts
|
|
375
|
+
const LOCAL_DEV_SIGNATURE = "local-dev";
|
|
376
|
+
function resolveLocalDevUpdateOptions(root, options) {
|
|
377
|
+
if (!options) return;
|
|
378
|
+
const resolvedOptions = options === true ? {} : options;
|
|
379
|
+
return {
|
|
380
|
+
baseDir: path.resolve(root, resolvedOptions.baseDir ?? "release/local-update"),
|
|
381
|
+
installedAsarPath: path.resolve(root, "DEV.asar"),
|
|
382
|
+
packageJsonPath: resolvedOptions.packageJsonPath ? path.resolve(root, resolvedOptions.packageJsonPath) : void 0,
|
|
383
|
+
chunkSize: resolvedOptions.chunkSize,
|
|
384
|
+
chunkDelay: resolvedOptions.chunkDelay
|
|
385
|
+
};
|
|
417
386
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (process.platform === "win32") cp.execSync(`taskkill /pid ${pid} /T /F`);
|
|
424
|
-
else killTree(pidTree({
|
|
425
|
-
pid,
|
|
426
|
-
ppid: process.pid
|
|
427
|
-
}));
|
|
387
|
+
async function resolveLocalDevUpdatePackage(fallbackPkg, localDevUpdate) {
|
|
388
|
+
if (!localDevUpdate?.packageJsonPath) return fallbackPkg;
|
|
389
|
+
const pkg = JSON.parse(await readFile(localDevUpdate.packageJsonPath, "utf-8"));
|
|
390
|
+
if (!pkg.name || !pkg.version || !pkg.main) throw new Error("localDevUpdate.packageJsonPath must contain name, version and main fields");
|
|
391
|
+
return pkg;
|
|
428
392
|
}
|
|
429
|
-
function
|
|
430
|
-
const
|
|
393
|
+
function getNextPatchVersion(version) {
|
|
394
|
+
const parsed = parseVersion(version);
|
|
395
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
|
|
396
|
+
}
|
|
397
|
+
function isValidVersion(version) {
|
|
431
398
|
try {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
} catch {}
|
|
438
|
-
return tree;
|
|
399
|
+
parseVersion(version);
|
|
400
|
+
return true;
|
|
401
|
+
} catch {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
439
404
|
}
|
|
440
|
-
function
|
|
441
|
-
|
|
405
|
+
function readValidAsarVersion(asarPath) {
|
|
406
|
+
try {
|
|
407
|
+
const version = extractFile(asarPath, "version").toString("utf-8").trim();
|
|
408
|
+
return isValidVersion(version) ? version : void 0;
|
|
409
|
+
} catch {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function installPendingAsar(installedAsarPath) {
|
|
414
|
+
const pendingAsarPath = `${installedAsarPath}.tmp`;
|
|
415
|
+
const version = readValidAsarVersion(pendingAsarPath);
|
|
416
|
+
if (!version) {
|
|
417
|
+
await rm(pendingAsarPath, { force: true });
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
442
420
|
try {
|
|
443
|
-
|
|
421
|
+
await copyFile(pendingAsarPath, installedAsarPath);
|
|
422
|
+
await rm(pendingAsarPath, { force: true });
|
|
423
|
+
log.info(`Installed pending local dev update ${version}`, { timestamp: true });
|
|
424
|
+
return true;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (error.code === "ENOENT") return false;
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function readExistingUpdateJSON(versionPath, version) {
|
|
431
|
+
try {
|
|
432
|
+
const json = JSON.parse(await readFile(versionPath, "utf-8"));
|
|
433
|
+
if (isUpdateJSON(json)) return json;
|
|
444
434
|
} catch {}
|
|
435
|
+
return {
|
|
436
|
+
version,
|
|
437
|
+
minimumVersion: "0.0.0",
|
|
438
|
+
signature: LOCAL_DEV_SIGNATURE,
|
|
439
|
+
beta: {
|
|
440
|
+
version,
|
|
441
|
+
minimumVersion: "0.0.0",
|
|
442
|
+
signature: LOCAL_DEV_SIGNATURE
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
445
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
446
|
+
async function prepareLocalDevUpdateResource({ root, pkg, buildAsarOption, versionPath, minimumVersion, keepInstalledVersion, localDevUpdate }) {
|
|
447
|
+
const installedPendingUpdate = await installPendingAsar(localDevUpdate.installedAsarPath);
|
|
448
|
+
const installedVersion = readValidAsarVersion(localDevUpdate.installedAsarPath) ?? pkg.version;
|
|
449
|
+
const targetVersion = keepInstalledVersion && installedPendingUpdate ? installedVersion : getNextPatchVersion(installedVersion);
|
|
450
|
+
const workDir = await mkdtemp(path.join(tmpdir(), "eiu-local-dev-update-"));
|
|
451
|
+
const stagedElectronDistPath = path.join(workDir, "dist-electron");
|
|
452
|
+
const resolvedVersionPath = path.join(localDevUpdate.baseDir, versionPath);
|
|
453
|
+
const asarPath = path.join(localDevUpdate.baseDir, `${pkg.name}.asar`);
|
|
454
|
+
const compressedPath = path.join(localDevUpdate.baseDir, `${pkg.name}-${targetVersion}.asar.br`);
|
|
455
|
+
try {
|
|
456
|
+
await mkdir(localDevUpdate.baseDir, { recursive: true });
|
|
457
|
+
await mkdir(path.dirname(resolvedVersionPath), { recursive: true });
|
|
458
|
+
await cp$1(path.resolve(root, buildAsarOption.electronDistPath), stagedElectronDistPath, { recursive: true });
|
|
459
|
+
await writeFile(path.join(stagedElectronDistPath, "version"), targetVersion, "utf-8");
|
|
460
|
+
await createPackage(stagedElectronDistPath, asarPath);
|
|
461
|
+
await writeFile(compressedPath, await buildAsarOption.generateCompressedFile(await readFile(asarPath)));
|
|
462
|
+
const updateJSON = defaultVersionJsonGenerator(await readExistingUpdateJSON(resolvedVersionPath, targetVersion), LOCAL_DEV_SIGNATURE, targetVersion, minimumVersion);
|
|
463
|
+
if (!isUpdateJSON(updateJSON)) throw new Error("Invalid local dev update json");
|
|
464
|
+
await writeFile(resolvedVersionPath, JSON.stringify(updateJSON, null, 2), "utf-8");
|
|
465
|
+
log.info(`Prepared local dev update ${targetVersion}`, { timestamp: true });
|
|
466
|
+
return targetVersion;
|
|
467
|
+
} finally {
|
|
468
|
+
await rm(workDir, {
|
|
469
|
+
recursive: true,
|
|
470
|
+
force: true
|
|
471
|
+
});
|
|
472
|
+
}
|
|
451
473
|
}
|
|
452
|
-
function
|
|
453
|
-
|
|
454
|
-
let
|
|
455
|
-
let
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
474
|
+
function createLocalDevUpdateOnstart(args) {
|
|
475
|
+
let restartRequested = false;
|
|
476
|
+
let startupArgs;
|
|
477
|
+
let managedElectronApp;
|
|
478
|
+
function createStartupArgs(onstartArgs) {
|
|
479
|
+
return {
|
|
480
|
+
...onstartArgs,
|
|
481
|
+
startup(argv, options, customElectronPkg) {
|
|
482
|
+
const env = {
|
|
483
|
+
...process.env,
|
|
484
|
+
...options?.env
|
|
485
|
+
};
|
|
486
|
+
delete env.ELECTRON_RUN_AS_NODE;
|
|
487
|
+
return onstartArgs.startup(argv, {
|
|
488
|
+
...options,
|
|
489
|
+
env
|
|
490
|
+
}, customElectronPkg);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
459
493
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
options.vite.build.minify ??= false;
|
|
481
|
-
options.vite.plugins ??= [];
|
|
482
|
-
options.vite.plugins.push({
|
|
483
|
-
name: ":startup",
|
|
484
|
-
closeBundle() {
|
|
485
|
-
if (++closeBundleCount < entryCount) return;
|
|
486
|
-
if (options.onstart) options.onstart.call(this, {
|
|
487
|
-
async startup(args = defaultArgs, ...opt) {
|
|
488
|
-
await startup(args, ...opt);
|
|
489
|
-
},
|
|
490
|
-
reload() {
|
|
491
|
-
if (process.electronApp) {
|
|
492
|
-
(server.hot || server.ws).send({ type: "full-reload" });
|
|
493
|
-
startup.send("electron-vite&type=hot-reload");
|
|
494
|
-
} else startup(defaultArgs);
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
else startup(defaultArgs);
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
return options;
|
|
501
|
-
}));
|
|
502
|
-
});
|
|
494
|
+
async function start(onstartArgs, options = {}) {
|
|
495
|
+
const safeStartupArgs = createStartupArgs(onstartArgs);
|
|
496
|
+
startupArgs = safeStartupArgs;
|
|
497
|
+
await prepareLocalDevUpdateResource({
|
|
498
|
+
...args,
|
|
499
|
+
keepInstalledVersion: options.keepInstalledVersion
|
|
500
|
+
});
|
|
501
|
+
if (args.userOnstart) await args.userOnstart(safeStartupArgs);
|
|
502
|
+
else await safeStartupArgs.startup();
|
|
503
|
+
const electronApp = process.electronApp;
|
|
504
|
+
removeManagedListeners();
|
|
505
|
+
managedElectronApp = electronApp;
|
|
506
|
+
managedElectronApp?.removeListener("exit", process.exit);
|
|
507
|
+
managedElectronApp?.on("message", handleMessage);
|
|
508
|
+
managedElectronApp?.once("exit", handleExit);
|
|
509
|
+
}
|
|
510
|
+
function handleMessage(message) {
|
|
511
|
+
if (message === "eiu:restart") {
|
|
512
|
+
restartRequested = true;
|
|
513
|
+
managedElectronApp?.send?.("eiu:restart-ready");
|
|
503
514
|
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
async closeBundle() {
|
|
513
|
-
await parallelBuild(optionsArray.map((options) => {
|
|
514
|
-
options.vite ??= {};
|
|
515
|
-
options.vite.mode ??= configEnv.mode;
|
|
516
|
-
options.vite.root ??= userConfig.root;
|
|
517
|
-
options.vite.envDir ??= userConfig.envDir;
|
|
518
|
-
options.vite.envPrefix ??= userConfig.envPrefix;
|
|
519
|
-
return options;
|
|
520
|
-
}));
|
|
515
|
+
}
|
|
516
|
+
function handleExit() {
|
|
517
|
+
if (restartRequested) {
|
|
518
|
+
restartRequested = false;
|
|
519
|
+
setTimeout(() => {
|
|
520
|
+
if (startupArgs) start(startupArgs, { keepInstalledVersion: true });
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
521
523
|
}
|
|
522
|
-
|
|
524
|
+
process.exit();
|
|
525
|
+
}
|
|
526
|
+
function removeManagedListeners() {
|
|
527
|
+
managedElectronApp?.removeListener("message", handleMessage);
|
|
528
|
+
managedElectronApp?.removeListener("exit", handleExit);
|
|
529
|
+
}
|
|
530
|
+
return (onstartArgs) => start(onstartArgs);
|
|
523
531
|
}
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/vite/utils/file.ts
|
|
524
534
|
/**
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
* @
|
|
528
|
-
* @param options options for `child_process.spawn`
|
|
529
|
-
* @param customElectronPkg custom electron package name (default: 'electron')
|
|
535
|
+
* Convert byte size to human-readable format
|
|
536
|
+
* @param size - Size in bytes
|
|
537
|
+
* @returns Human-readable size string (e.g., "1.23 MB")
|
|
530
538
|
*/
|
|
531
|
-
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
"inherit",
|
|
538
|
-
"inherit",
|
|
539
|
-
"inherit",
|
|
540
|
-
"ignore",
|
|
541
|
-
"ipc"
|
|
542
|
-
] : [
|
|
543
|
-
"inherit",
|
|
544
|
-
"inherit",
|
|
545
|
-
"inherit",
|
|
546
|
-
"ipc"
|
|
539
|
+
function readableSize(size) {
|
|
540
|
+
const units = [
|
|
541
|
+
"B",
|
|
542
|
+
"KB",
|
|
543
|
+
"MB",
|
|
544
|
+
"GB"
|
|
547
545
|
];
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
process.electronApp.once("exit", process.exit);
|
|
553
|
-
if (!startup.hookedProcessExit) {
|
|
554
|
-
startup.hookedProcessExit = true;
|
|
555
|
-
process.once("exit", startup.exit);
|
|
546
|
+
let i = 0;
|
|
547
|
+
while (size >= 1024 && i < units.length - 1) {
|
|
548
|
+
size /= 1024;
|
|
549
|
+
i++;
|
|
556
550
|
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (process.electronApp) process.electronApp.send?.(message);
|
|
560
|
-
};
|
|
561
|
-
startup.hookedProcessExit = false;
|
|
562
|
-
startup.exit = async () => {
|
|
563
|
-
if (process.electronApp) await new Promise((resolve) => {
|
|
564
|
-
process.electronApp.removeAllListeners();
|
|
565
|
-
process.electronApp.once("exit", resolve);
|
|
566
|
-
treeKillSync(process.electronApp.pid);
|
|
567
|
-
});
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
//#endregion
|
|
571
|
-
//#region src/vite/electron/plugin.ts
|
|
572
|
-
/**
|
|
573
|
-
* @see https://github.com/vitejs/vite/blob/v4.4.7/packages/vite/src/node/utils.ts#L140
|
|
574
|
-
*/
|
|
575
|
-
const bareImportRE = /^(?![a-zA-Z]:)[\w@](?!.*:\/\/)/;
|
|
576
|
-
const nodeModulesRE = /\/node_modules\//;
|
|
551
|
+
return `${size.toFixed(2)} ${units[i]}`;
|
|
552
|
+
}
|
|
577
553
|
/**
|
|
578
|
-
*
|
|
554
|
+
* Copy file/directory, skipping if target exists
|
|
555
|
+
* @param from - Source path
|
|
556
|
+
* @param to - Destination path
|
|
557
|
+
* @param skipIfExist - Skip copy if destination exists
|
|
579
558
|
*/
|
|
580
|
-
function
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
resolveId: {
|
|
587
|
-
filter: { id: bareImportRE },
|
|
588
|
-
async handler(source, importer) {
|
|
589
|
-
if (!importer || importer.includes("node_modules/")) return;
|
|
590
|
-
if (externalIds.has(source)) return {
|
|
591
|
-
id: source,
|
|
592
|
-
external: true
|
|
593
|
-
};
|
|
594
|
-
const id = (await this.resolve(source, importer, { skipSelf: true }))?.id;
|
|
595
|
-
if (!id || !nodeModulesRE.test(id) || options.filter?.(id) === false) return;
|
|
596
|
-
try {
|
|
597
|
-
createRequire(importer).resolve(source);
|
|
598
|
-
} catch {
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
externalIds.add(source);
|
|
602
|
-
return {
|
|
603
|
-
id: source,
|
|
604
|
-
external: true,
|
|
605
|
-
moduleSideEffects: false
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
};
|
|
559
|
+
function copyAndSkipIfExist(from, to, skipIfExist) {
|
|
560
|
+
if (!skipIfExist || !fs.existsSync(to)) try {
|
|
561
|
+
fs.cpSync(from, to, { recursive: true });
|
|
562
|
+
} catch (error) {
|
|
563
|
+
log.warn(`Copy failed: ${error}`, { timestamp: true });
|
|
564
|
+
}
|
|
610
565
|
}
|
|
611
|
-
|
|
612
566
|
//#endregion
|
|
613
|
-
//#region src/utils/
|
|
614
|
-
function hashBuffer(data, length) {
|
|
615
|
-
const hash = crypto.createHash("SHA256").update(data).digest("binary");
|
|
616
|
-
return Buffer.from(hash).subarray(0, length);
|
|
617
|
-
}
|
|
618
|
-
function aesEncrypt(plainText, key, iv) {
|
|
619
|
-
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
|
620
|
-
return cipher.update(plainText, "utf8", "base64url") + cipher.final("base64url");
|
|
621
|
-
}
|
|
567
|
+
//#region src/vite/utils/build.ts
|
|
622
568
|
/**
|
|
623
|
-
*
|
|
624
|
-
* @param
|
|
625
|
-
* @
|
|
626
|
-
* @param cert certificate
|
|
627
|
-
* @param version target version
|
|
569
|
+
* Build asar file and update package
|
|
570
|
+
* @param options - Asar build options
|
|
571
|
+
* @returns Buffer of the built asar file
|
|
628
572
|
*/
|
|
629
|
-
function
|
|
630
|
-
|
|
573
|
+
async function buildAsar(root, { version, asarOutputPath, electronDistPath, rendererDistPath, compressedPath, generateCompressedFile }) {
|
|
574
|
+
electronDistPath = path.resolve(root, electronDistPath);
|
|
575
|
+
asarOutputPath = path.resolve(root, asarOutputPath);
|
|
576
|
+
rendererDistPath = path.resolve(root, rendererDistPath);
|
|
577
|
+
compressedPath = path.resolve(root, compressedPath);
|
|
578
|
+
const rPath = path.join(electronDistPath, "renderer");
|
|
579
|
+
await fs.promises.cp(rendererDistPath, rPath, { recursive: true });
|
|
580
|
+
fs.writeFileSync(path.join(electronDistPath, "version"), version);
|
|
581
|
+
await fs.promises.mkdir(path.dirname(asarOutputPath), { recursive: true });
|
|
582
|
+
await createPackage(electronDistPath, asarOutputPath);
|
|
583
|
+
const buf = await generateCompressedFile(fs.readFileSync(asarOutputPath));
|
|
584
|
+
await fs.promises.mkdir(path.dirname(compressedPath), { recursive: true });
|
|
585
|
+
fs.writeFileSync(compressedPath, buf);
|
|
586
|
+
log.info(`Build update asar to '${compressedPath}' [${readableSize(buf.length)}]`, { timestamp: true });
|
|
587
|
+
return buf;
|
|
631
588
|
}
|
|
632
|
-
|
|
633
|
-
//#endregion
|
|
634
|
-
//#region src/utils/zip.ts
|
|
635
589
|
/**
|
|
636
|
-
*
|
|
637
|
-
* @param
|
|
590
|
+
* Build update.json file with signature and version information
|
|
591
|
+
* @param options - Version build options
|
|
592
|
+
* @param asarBuffer - Buffer of the asar file to sign
|
|
638
593
|
*/
|
|
639
|
-
async function
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
594
|
+
async function buildUpdateJson(root, { versionPath, privateKey, cert, version, minimumVersion, generateSignature, generateUpdateJson }, asarBuffer) {
|
|
595
|
+
const resolvedVersionPath = path.resolve(root, versionPath);
|
|
596
|
+
let _json = {
|
|
597
|
+
beta: {
|
|
598
|
+
minimumVersion: version,
|
|
599
|
+
signature: "",
|
|
600
|
+
version
|
|
601
|
+
},
|
|
602
|
+
minimumVersion: version,
|
|
603
|
+
signature: "",
|
|
604
|
+
version
|
|
605
|
+
};
|
|
606
|
+
if (fs.existsSync(versionPath)) try {
|
|
607
|
+
const oldVersionJson = JSON.parse(fs.readFileSync(versionPath, "utf-8"));
|
|
608
|
+
if (isUpdateJSON(oldVersionJson)) _json = oldVersionJson;
|
|
609
|
+
else log.warn("Old version json is invalid, ignore it", { timestamp: true });
|
|
610
|
+
} catch {}
|
|
611
|
+
const sig = await generateSignature(asarBuffer, privateKey, cert, version);
|
|
612
|
+
_json = await generateUpdateJson(_json, sig, version, minimumVersion);
|
|
613
|
+
if (!isUpdateJSON(_json)) throw new Error("Invalid update json");
|
|
614
|
+
await fs.promises.mkdir(path.dirname(resolvedVersionPath), { recursive: true });
|
|
615
|
+
fs.writeFileSync(resolvedVersionPath, JSON.stringify(_json, null, 2));
|
|
616
|
+
log.info(`Build update json to '${resolvedVersionPath}'`, { timestamp: true });
|
|
643
617
|
}
|
|
644
|
-
|
|
645
618
|
//#endregion
|
|
646
619
|
//#region src/vite/utils/key.ts
|
|
647
620
|
/**
|
|
@@ -708,31 +681,32 @@ function parseSubjects(subject) {
|
|
|
708
681
|
value
|
|
709
682
|
}));
|
|
710
683
|
}
|
|
711
|
-
|
|
712
684
|
//#endregion
|
|
713
|
-
//#region src/vite/
|
|
714
|
-
async function
|
|
715
|
-
const { minimumVersion = "0.0.0", paths: { asarOutputPath = `release/${pkg.name}.asar`,
|
|
685
|
+
//#region src/vite/core.ts
|
|
686
|
+
async function resolveUpdaterOption(root, pkg, options = {}, resolveSignatureKeys = true) {
|
|
687
|
+
const { minimumVersion = "0.0.0", paths: { asarOutputPath = `release/${pkg.name}.asar`, compressedPath = `release/${pkg.name}-${pkg.version}.asar.br`, entryOutDir = "dist-entry", electronDistPath = "dist-electron", rendererDistPath = "dist", versionPath = "release/version.json" } = {}, keys: { privateKeyPath = "keys/private.pem", certPath = "keys/cert.pem", keyLength = 2048, certInfo: { subject = {
|
|
716
688
|
commonName: pkg.name,
|
|
717
689
|
organizationName: `org.${pkg.name}`
|
|
718
|
-
}, days = 3650 } = {} } = {}, overrideGenerator: {
|
|
719
|
-
const
|
|
720
|
-
version: pkg.version,
|
|
721
|
-
asarOutputPath,
|
|
722
|
-
gzipPath,
|
|
723
|
-
electronDistPath,
|
|
724
|
-
rendererDistPath,
|
|
725
|
-
generateGzipFile
|
|
726
|
-
};
|
|
727
|
-
const { privateKey, cert } = await parseKeys({
|
|
690
|
+
}, days = 3650 } = {} } = {}, overrideGenerator: { generateCompressedFile = defaultCompressFile, generateSignature = defaultSignature, generateUpdateJson = defaultVersionJsonGenerator } = {} } = options;
|
|
691
|
+
const { privateKey, cert } = resolveSignatureKeys ? await parseKeys({
|
|
728
692
|
keyLength,
|
|
729
|
-
privateKeyPath,
|
|
730
|
-
certPath,
|
|
693
|
+
privateKeyPath: path.resolve(root, privateKeyPath),
|
|
694
|
+
certPath: path.resolve(root, certPath),
|
|
731
695
|
subject,
|
|
732
696
|
days
|
|
733
|
-
})
|
|
697
|
+
}) : {
|
|
698
|
+
privateKey: "",
|
|
699
|
+
cert: ""
|
|
700
|
+
};
|
|
734
701
|
return {
|
|
735
|
-
buildAsarOption
|
|
702
|
+
buildAsarOption: {
|
|
703
|
+
version: pkg.version,
|
|
704
|
+
asarOutputPath,
|
|
705
|
+
compressedPath,
|
|
706
|
+
electronDistPath,
|
|
707
|
+
rendererDistPath,
|
|
708
|
+
generateCompressedFile
|
|
709
|
+
},
|
|
736
710
|
buildVersionOption: {
|
|
737
711
|
version: pkg.version,
|
|
738
712
|
minimumVersion,
|
|
@@ -742,269 +716,230 @@ async function parseUpdaterOption(pkg, options = {}) {
|
|
|
742
716
|
generateSignature,
|
|
743
717
|
generateUpdateJson
|
|
744
718
|
},
|
|
745
|
-
cert,
|
|
746
719
|
entryOutDir
|
|
747
720
|
};
|
|
748
721
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
* @returns Buffer of the built asar file
|
|
756
|
-
*/
|
|
757
|
-
async function buildAsar({ version, asarOutputPath, gzipPath, electronDistPath, rendererDistPath, generateGzipFile }) {
|
|
758
|
-
const rPath = path.join(electronDistPath, "renderer");
|
|
759
|
-
await fs.promises.cp(rendererDistPath, rPath, { recursive: true });
|
|
760
|
-
fs.writeFileSync(path.join(electronDistPath, "version"), version);
|
|
761
|
-
await createPackage(electronDistPath, asarOutputPath);
|
|
762
|
-
const buf = await generateGzipFile(fs.readFileSync(asarOutputPath));
|
|
763
|
-
fs.writeFileSync(gzipPath, buf);
|
|
764
|
-
log.info(`Build update asar to '${gzipPath}' [${readableSize(buf.length)}]`, { timestamp: true });
|
|
765
|
-
return buf;
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Build update.json file with signature and version information
|
|
769
|
-
* @param options - Version build options
|
|
770
|
-
* @param asarBuffer - Buffer of the asar file to sign
|
|
771
|
-
*/
|
|
772
|
-
async function buildUpdateJson({ versionPath, privateKey, cert, version, minimumVersion, generateSignature, generateUpdateJson }, asarBuffer) {
|
|
773
|
-
let _json = {
|
|
774
|
-
beta: {
|
|
775
|
-
minimumVersion: version,
|
|
776
|
-
signature: "",
|
|
777
|
-
version
|
|
778
|
-
},
|
|
779
|
-
minimumVersion: version,
|
|
780
|
-
signature: "",
|
|
781
|
-
version
|
|
782
|
-
};
|
|
783
|
-
if (fs.existsSync(versionPath)) try {
|
|
784
|
-
const oldVersionJson = JSON.parse(fs.readFileSync(versionPath, "utf-8"));
|
|
785
|
-
if (isUpdateJSON(oldVersionJson)) _json = oldVersionJson;
|
|
786
|
-
else log.warn("Old version json is invalid, ignore it", { timestamp: true });
|
|
787
|
-
} catch {}
|
|
788
|
-
const sig = await generateSignature(asarBuffer, privateKey, cert, version);
|
|
789
|
-
_json = await generateUpdateJson(_json, sig, version, minimumVersion);
|
|
790
|
-
if (!isUpdateJSON(_json)) throw new Error("Invalid update json");
|
|
791
|
-
fs.writeFileSync(versionPath, JSON.stringify(_json, null, 2));
|
|
792
|
-
log.info(`build update json to '${versionPath}'`, { timestamp: true });
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
//#endregion
|
|
796
|
-
//#region src/vite/core.ts
|
|
797
|
-
function getMainFileBaseName(options) {
|
|
798
|
-
let mainFilePath;
|
|
799
|
-
if (typeof options === "string") mainFilePath = path.basename(options);
|
|
800
|
-
else if (Array.isArray(options)) mainFilePath = path.basename(options[0]);
|
|
801
|
-
else {
|
|
802
|
-
if (!(options?.index ?? options?.main)) throw new Error(`\`options.main.files\` (${options}) must have "index" or "main" key, like \`{ index: "./electron/main/index.ts" }\``);
|
|
803
|
-
mainFilePath = options?.index ? "index.js" : "main.js";
|
|
722
|
+
function resolveEntryName(files) {
|
|
723
|
+
if (typeof files === "string") return path.parse(files).name;
|
|
724
|
+
if (Array.isArray(files)) {
|
|
725
|
+
const [firstInput] = files;
|
|
726
|
+
if (!firstInput) throw new Error("`options.main.files` must contain at least one main entry");
|
|
727
|
+
return path.parse(firstInput).name;
|
|
804
728
|
}
|
|
805
|
-
|
|
806
|
-
|
|
729
|
+
const firstEntry = Object.entries(files)[0];
|
|
730
|
+
if (!firstEntry) throw new Error("`options.main.files` must contain at least one main entry");
|
|
731
|
+
return firstEntry[0];
|
|
807
732
|
}
|
|
808
|
-
function
|
|
733
|
+
function normalizeVersionPath(versionPath) {
|
|
809
734
|
versionPath = normalizePath(versionPath);
|
|
810
735
|
if (!versionPath.startsWith("./")) versionPath = `./${versionPath}`;
|
|
811
736
|
return new URL(versionPath, "file://").pathname.slice(1);
|
|
812
737
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
/^node:/,
|
|
817
|
-
/.*\.(node|dll|dylib|so)$/,
|
|
818
|
-
"original-fs"
|
|
819
|
-
];
|
|
820
|
-
/**
|
|
821
|
-
* Base on `./electron/simple`
|
|
822
|
-
* - integrate with updater
|
|
823
|
-
* - no `renderer` config
|
|
824
|
-
* - remove old output file
|
|
825
|
-
* - externalize dependencies
|
|
826
|
-
* - auto restart when entry file changes
|
|
827
|
-
* - other configs in {@link https://github.com/electron-vite/electron-vite-vue/blob/main/vite.config.ts electron-vite-vue template}
|
|
828
|
-
*
|
|
829
|
-
* You can override all the vite configs, except output directories (use `options.updater.paths.electronDistPath` instead)
|
|
830
|
-
*
|
|
831
|
-
* @example
|
|
832
|
-
* ```ts
|
|
833
|
-
* import { defineConfig } from 'vite'
|
|
834
|
-
* import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
|
|
835
|
-
*
|
|
836
|
-
* export default defineConfig(async ({ command }) => {
|
|
837
|
-
* const isBuild = command === 'build'
|
|
838
|
-
* return {
|
|
839
|
-
* plugins: [
|
|
840
|
-
* electronWithUpdater({
|
|
841
|
-
* isBuild,
|
|
842
|
-
* main: {
|
|
843
|
-
* files: ['./electron/main/index.ts', './electron/main/worker.ts'],
|
|
844
|
-
* // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
|
|
845
|
-
* onstart: debugStartup,
|
|
846
|
-
* },
|
|
847
|
-
* preload: {
|
|
848
|
-
* files: './electron/preload/index.ts',
|
|
849
|
-
* },
|
|
850
|
-
* updater: {
|
|
851
|
-
* // options
|
|
852
|
-
* }
|
|
853
|
-
* }),
|
|
854
|
-
* ],
|
|
855
|
-
* server: process.env.VSCODE_DEBUG && (() => {
|
|
856
|
-
* const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
|
|
857
|
-
* return {
|
|
858
|
-
* host: url.hostname,
|
|
859
|
-
* port: +url.port,
|
|
860
|
-
* }
|
|
861
|
-
* })(),
|
|
862
|
-
* }
|
|
863
|
-
* })
|
|
864
|
-
* ```
|
|
865
|
-
*/
|
|
866
|
-
async function electronWithUpdater(options) {
|
|
867
|
-
let { isBuild, root = process.cwd(), external, entry: _entry, main: _main, preload: _preload, sourcemap = !isBuild || !!process.env.VSCODE_DEBUG, minify = isBuild, buildVersionJson, updater, bytecode, useNotBundle = true } = options;
|
|
868
|
-
const pkg = await loadPackageJSON(root);
|
|
738
|
+
async function createElectronOptions(options, context) {
|
|
739
|
+
const { entry, main, preload, sourcemap = context.isDev || !!process.env.VSCODE_DEBUG, minify = !context.isDev, buildVersionJson, notBundle = true, external, updater, bytecode, localDevUpdate } = options;
|
|
740
|
+
const pkg = context.packageJson;
|
|
869
741
|
if (!pkg || !pkg.version || !pkg.name || !pkg.main) throw new Error("package.json not found or invalid, must contains version, name and main field");
|
|
870
742
|
const isESM = pkg.type === "module";
|
|
871
|
-
const finalExternal = [...defaultExternal
|
|
872
|
-
|
|
743
|
+
const finalExternal = [...defaultExternal];
|
|
744
|
+
if (external === true) finalExternal.push(...Object.keys(pkg.dependencies || {}));
|
|
745
|
+
else if (Array.isArray(external)) finalExternal.push(...external);
|
|
746
|
+
const bytecodeOptions = typeof bytecode === "object" ? {
|
|
747
|
+
...bytecode,
|
|
748
|
+
enable: bytecode.enable ?? true
|
|
749
|
+
} : bytecode === true ? { enable: true } : void 0;
|
|
873
750
|
if (isESM && bytecodeOptions?.enable) throw new Error("`bytecodePlugin` does not support ES module, please remove \"type\": \"module\" in package.json");
|
|
874
|
-
const
|
|
751
|
+
const resolvedLocalDevUpdate = context.isDev ? resolveLocalDevUpdateOptions(context.root, localDevUpdate) : void 0;
|
|
752
|
+
const updatePkg = await resolveLocalDevUpdatePackage(pkg, resolvedLocalDevUpdate);
|
|
753
|
+
const { buildAsarOption, buildVersionOption, entryOutDir } = await resolveUpdaterOption(context.root, updatePkg, updater, !resolvedLocalDevUpdate);
|
|
754
|
+
const mainFileName = `${resolveEntryName(main.files)}.${isESM ? "mjs" : "js"}`;
|
|
755
|
+
log.info(`Using "${mainFileName}" as main file`, { timestamp: true });
|
|
875
756
|
log.info(`Clear cache files`, { timestamp: true });
|
|
876
757
|
await Promise.all([
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
758
|
+
buildAsarOption.rendererDistPath,
|
|
759
|
+
buildAsarOption.electronDistPath,
|
|
760
|
+
entryOutDir
|
|
761
|
+
].map((p) => fs.promises.rm(path.resolve(context.root, p), {
|
|
762
|
+
recursive: true,
|
|
763
|
+
force: true
|
|
764
|
+
}))).catch(() => {});
|
|
765
|
+
const outputNames = {
|
|
766
|
+
entryFileNames: `[name].${isESM ? "mjs" : "js"}`,
|
|
767
|
+
chunkFileNames: `[name].${isESM ? "mjs" : "js"}`,
|
|
768
|
+
assetFileNames: "[name].[ext]"
|
|
769
|
+
};
|
|
770
|
+
const versionPath = normalizeVersionPath(normalizePath(buildVersionOption.versionPath));
|
|
771
|
+
const mainOnstart = resolvedLocalDevUpdate ? createLocalDevUpdateOnstart({
|
|
772
|
+
root: context.root,
|
|
773
|
+
pkg: updatePkg,
|
|
774
|
+
buildAsarOption,
|
|
775
|
+
versionPath,
|
|
776
|
+
minimumVersion: buildVersionOption.minimumVersion,
|
|
777
|
+
localDevUpdate: resolvedLocalDevUpdate,
|
|
778
|
+
userOnstart: main.onstart
|
|
779
|
+
}) : main.onstart;
|
|
890
780
|
const define = {
|
|
891
781
|
__EIU_ASAR_BASE_NAME__: JSON.stringify(path.basename(buildAsarOption.asarOutputPath)),
|
|
892
782
|
__EIU_ELECTRON_DIST_PATH__: JSON.stringify(normalizePath(buildAsarOption.electronDistPath)),
|
|
893
783
|
__EIU_ENTRY_DIST_PATH__: JSON.stringify(normalizePath(entryOutDir)),
|
|
894
|
-
__EIU_IS_DEV__: JSON.stringify(
|
|
784
|
+
__EIU_IS_DEV__: JSON.stringify(context.isDev),
|
|
895
785
|
__EIU_IS_ESM__: JSON.stringify(isESM),
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
786
|
+
__EIU_LOCAL_DEV_UPDATE__: JSON.stringify(!!resolvedLocalDevUpdate),
|
|
787
|
+
__EIU_LOCAL_DEV_UPDATE_ASAR_PATH__: JSON.stringify(resolvedLocalDevUpdate?.installedAsarPath ?? ""),
|
|
788
|
+
__EIU_LOCAL_DEV_UPDATE_CHUNK_DELAY__: JSON.stringify(resolvedLocalDevUpdate?.chunkDelay) ?? "undefined",
|
|
789
|
+
__EIU_LOCAL_DEV_UPDATE_CHUNK_SIZE__: JSON.stringify(resolvedLocalDevUpdate?.chunkSize) ?? "undefined",
|
|
790
|
+
__EIU_LOCAL_DEV_UPDATE_DIR__: JSON.stringify(resolvedLocalDevUpdate?.baseDir ?? ""),
|
|
791
|
+
__EIU_MAIN_FILE__: JSON.stringify(mainFileName),
|
|
792
|
+
__EIU_SIGNATURE_CERT__: JSON.stringify(buildVersionOption.cert),
|
|
793
|
+
__EIU_VERSION_PATH__: JSON.stringify(versionPath)
|
|
899
794
|
};
|
|
900
795
|
const _electronOptions = [{
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
796
|
+
name: "main",
|
|
797
|
+
input: main.files,
|
|
798
|
+
onstart: mainOnstart,
|
|
799
|
+
notBundle,
|
|
800
|
+
plugins: [isESM && esmShim(), bytecodeOptions && bytecodePlugin("main", minify, isESM, bytecodeOptions)],
|
|
801
|
+
options: mergeConfig({
|
|
905
802
|
build: {
|
|
906
803
|
sourcemap,
|
|
907
804
|
minify,
|
|
908
805
|
outDir: `${buildAsarOption.electronDistPath}/main`,
|
|
909
806
|
rolldownOptions: {
|
|
910
807
|
external: finalExternal,
|
|
911
|
-
platform: "node",
|
|
912
808
|
output: {
|
|
913
|
-
|
|
914
|
-
|
|
809
|
+
format: isESM ? "es" : "cjs",
|
|
810
|
+
polyfillRequire: isESM,
|
|
811
|
+
...outputNames
|
|
915
812
|
}
|
|
916
813
|
}
|
|
917
814
|
},
|
|
918
815
|
define
|
|
919
|
-
},
|
|
816
|
+
}, main.options ?? {})
|
|
920
817
|
}];
|
|
921
|
-
if (
|
|
818
|
+
if (preload?.files) _electronOptions.push({
|
|
819
|
+
name: "preload",
|
|
922
820
|
onstart(args) {
|
|
923
821
|
args.reload();
|
|
924
822
|
},
|
|
925
|
-
|
|
926
|
-
|
|
823
|
+
notBundle,
|
|
824
|
+
input: preload.files,
|
|
825
|
+
plugins: [isESM && esmShim(), bytecodeOptions && bytecodePlugin("preload", minify, isESM, bytecodeOptions)],
|
|
826
|
+
options: mergeConfig({
|
|
927
827
|
build: {
|
|
928
828
|
sourcemap: sourcemap ? "inline" : void 0,
|
|
929
829
|
minify,
|
|
930
830
|
outDir: `${buildAsarOption.electronDistPath}/preload`,
|
|
931
831
|
rolldownOptions: {
|
|
932
832
|
external: finalExternal,
|
|
933
|
-
input: _preload.files,
|
|
934
833
|
output: {
|
|
935
834
|
format: "cjs",
|
|
936
835
|
codeSplitting: false,
|
|
937
836
|
polyfillRequire: false,
|
|
938
|
-
|
|
939
|
-
chunkFileNames: `[name].${isESM ? "mjs" : "js"}`,
|
|
940
|
-
assetFileNames: "[name].[ext]"
|
|
837
|
+
...outputNames
|
|
941
838
|
}
|
|
942
839
|
}
|
|
943
840
|
},
|
|
944
841
|
define
|
|
945
|
-
},
|
|
842
|
+
}, preload?.options ?? {})
|
|
946
843
|
});
|
|
947
844
|
_electronOptions.push({
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const nodeModulesPath = path.join(entryOutDir, "node_modules");
|
|
972
|
-
for (const m of modules) {
|
|
973
|
-
const { rootPath } = getPackageInfoSync(m) || {};
|
|
974
|
-
if (!rootPath) {
|
|
975
|
-
log.warn(`Package '${m}' not found`, { timestamp: true });
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
copyAndSkipIfExist(rootPath, path.join(nodeModulesPath, m), skipIfExist);
|
|
979
|
-
}
|
|
845
|
+
name: "entry",
|
|
846
|
+
input: entry.files,
|
|
847
|
+
async onstart(args) {
|
|
848
|
+
if (mainOnstart) await mainOnstart(args);
|
|
849
|
+
else await args.startup();
|
|
850
|
+
},
|
|
851
|
+
notBundle,
|
|
852
|
+
plugins: [
|
|
853
|
+
isESM && esmShim(),
|
|
854
|
+
bytecodeOptions && bytecodePlugin("entry", minify, isESM, bytecodeOptions),
|
|
855
|
+
{
|
|
856
|
+
name: `${id}:entry`,
|
|
857
|
+
async closeBundle() {
|
|
858
|
+
log.info(`Build entry to '${entryOutDir}'`, { timestamp: true });
|
|
859
|
+
await entry.postBuild?.({
|
|
860
|
+
isBuild: !context.isDev,
|
|
861
|
+
getPathFromEntryOutputDir(...paths) {
|
|
862
|
+
return path.join(entryOutDir, ...paths);
|
|
863
|
+
},
|
|
864
|
+
copyToEntryOutputDir({ from, to = path.basename(from), skipIfExist = true }) {
|
|
865
|
+
if (!fs.existsSync(from)) {
|
|
866
|
+
log.warn(`${from} not found`, { timestamp: true });
|
|
867
|
+
return;
|
|
980
868
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
else await buildUpdateJson(buildVersionOption, buffer);
|
|
986
|
-
} catch (error) {
|
|
987
|
-
console.error(error);
|
|
869
|
+
copyAndSkipIfExist(from, path.join(entryOutDir, to), skipIfExist);
|
|
870
|
+
},
|
|
871
|
+
copyModules() {
|
|
872
|
+
console.warn("`copyModules()` is deprecated. Will do nothing");
|
|
988
873
|
}
|
|
989
|
-
}
|
|
874
|
+
});
|
|
875
|
+
if (context.isDev) return;
|
|
876
|
+
const buffer = await buildAsar(context.root, buildAsarOption);
|
|
877
|
+
if (!buildVersionJson && !isCI) log.warn("No `buildVersionJson` option setup, skip build version json. Only build in CI by default", { timestamp: true });
|
|
878
|
+
else await buildUpdateJson(context.root, buildVersionOption, buffer);
|
|
990
879
|
}
|
|
991
|
-
|
|
880
|
+
}
|
|
881
|
+
],
|
|
882
|
+
options: mergeConfig({
|
|
992
883
|
build: {
|
|
993
884
|
sourcemap,
|
|
994
885
|
minify,
|
|
995
886
|
outDir: entryOutDir,
|
|
996
887
|
rolldownOptions: {
|
|
997
888
|
external: finalExternal,
|
|
998
|
-
|
|
999
|
-
|
|
889
|
+
output: {
|
|
890
|
+
format: isESM ? "es" : "cjs",
|
|
891
|
+
polyfillRequire: isESM,
|
|
892
|
+
...outputNames
|
|
893
|
+
}
|
|
1000
894
|
}
|
|
1001
895
|
},
|
|
1002
896
|
define
|
|
1003
|
-
},
|
|
897
|
+
}, entry.options || {})
|
|
898
|
+
});
|
|
899
|
+
return _electronOptions;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Base on `vite-plugin-electron/multi-env`
|
|
903
|
+
* - integrate with updater
|
|
904
|
+
* - no `renderer` config
|
|
905
|
+
* - remove old output file
|
|
906
|
+
* - externalize dependencies
|
|
907
|
+
* - auto restart when entry file changes
|
|
908
|
+
*
|
|
909
|
+
* You can override all the environment configs, except output directories (use `options.updater.paths.electronDistPath` instead)
|
|
910
|
+
*
|
|
911
|
+
* @example
|
|
912
|
+
* ```ts
|
|
913
|
+
* import { defineConfig } from 'vite'
|
|
914
|
+
* import { electronWithUpdater } from 'electron-incremental-update/vite'
|
|
915
|
+
*
|
|
916
|
+
* export default defineConfig(async ({ command }) => {
|
|
917
|
+
* const isBuild = command === 'build'
|
|
918
|
+
* return {
|
|
919
|
+
* plugins: [
|
|
920
|
+
* electronWithUpdater({
|
|
921
|
+
* isBuild,
|
|
922
|
+
* main: {
|
|
923
|
+
* files: ['./electron/main/index.ts', './electron/main/worker.ts'],
|
|
924
|
+
* },
|
|
925
|
+
* preload: {
|
|
926
|
+
* files: './electron/preload/index.ts',
|
|
927
|
+
* },
|
|
928
|
+
* updater: {
|
|
929
|
+
* // options
|
|
930
|
+
* }
|
|
931
|
+
* }),
|
|
932
|
+
* ],
|
|
933
|
+
* }
|
|
934
|
+
* })
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
async function electronWithUpdater(options) {
|
|
938
|
+
return electronPluginFactory((context) => {
|
|
939
|
+
process.CACHED_ELECTRON_OPTIONS ??= createElectronOptions(options, context);
|
|
940
|
+
return process.CACHED_ELECTRON_OPTIONS;
|
|
1004
941
|
});
|
|
1005
|
-
return electron(isESM, normalizePath(path.resolve(root)), _electronOptions);
|
|
1006
942
|
}
|
|
1007
|
-
|
|
1008
943
|
//#endregion
|
|
1009
944
|
//#region src/vite/define.ts
|
|
1010
945
|
/**
|
|
@@ -1015,10 +950,9 @@ async function electronWithUpdater(options) {
|
|
|
1015
950
|
* import { defineElectronConfig } from 'electron-incremental-update/vite'
|
|
1016
951
|
*
|
|
1017
952
|
* export default defineElectronConfig({
|
|
953
|
+
* // root: './apps'
|
|
1018
954
|
* main: {
|
|
1019
955
|
* files: ['./electron/main/index.ts', './electron/main/worker.ts'],
|
|
1020
|
-
* // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
|
|
1021
|
-
* onstart: debugStartup,
|
|
1022
956
|
* },
|
|
1023
957
|
* preload: {
|
|
1024
958
|
* files: './electron/preload/index.ts',
|
|
@@ -1027,33 +961,23 @@ async function electronWithUpdater(options) {
|
|
|
1027
961
|
* // options
|
|
1028
962
|
* },
|
|
1029
963
|
* renderer: {
|
|
1030
|
-
*
|
|
1031
|
-
* const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
|
|
1032
|
-
* return {
|
|
1033
|
-
* host: url.hostname,
|
|
1034
|
-
* port: +url.port,
|
|
1035
|
-
* }
|
|
1036
|
-
* })(),
|
|
964
|
+
* // plugins: []
|
|
1037
965
|
* }
|
|
1038
966
|
* })
|
|
1039
967
|
* ```
|
|
1040
968
|
*/
|
|
1041
969
|
function defineElectronConfig(options) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
}
|
|
1054
|
-
return result;
|
|
1055
|
-
};
|
|
970
|
+
const electronPlugin = electronWithUpdater(options);
|
|
971
|
+
const result = options.renderer ?? {};
|
|
972
|
+
result.plugins ??= [];
|
|
973
|
+
result.plugins.push(electronPlugin);
|
|
974
|
+
result.root = options.root;
|
|
975
|
+
const rendererDistPath = options.updater?.paths?.rendererDistPath;
|
|
976
|
+
if (rendererDistPath) {
|
|
977
|
+
result.build ??= {};
|
|
978
|
+
result.build.outDir = rendererDistPath;
|
|
979
|
+
}
|
|
980
|
+
return result;
|
|
1056
981
|
}
|
|
1057
|
-
|
|
1058
982
|
//#endregion
|
|
1059
|
-
export {
|
|
983
|
+
export { electronWithUpdater as default, electronWithUpdater, defineElectronConfig, filterErrorMessageStartup, fixWinCharEncoding };
|