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