electron-incremental-update 2.4.3 → 3.0.0-beta.3
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 +48 -40
- package/dist/download-BN4uMS4_.d.mts +39 -0
- package/dist/download-DO7iuxEJ.d.cts +39 -0
- package/dist/electron-DH-Uyikp.cjs +321 -0
- package/dist/electron-OKQIYbcw.mjs +181 -0
- package/dist/index.cjs +261 -331
- package/dist/index.d.cts +179 -169
- package/dist/index.d.mts +204 -0
- package/dist/index.mjs +273 -0
- package/dist/provider.cjs +142 -330
- package/dist/provider.d.cts +113 -114
- package/dist/provider.d.mts +133 -0
- package/dist/provider.mjs +152 -0
- package/dist/types-BM9Jfu7q.d.cts +154 -0
- package/dist/types-DASqEPXE.d.mts +154 -0
- package/dist/utils.cjs +43 -381
- package/dist/utils.d.cts +120 -85
- package/dist/utils.d.mts +164 -0
- package/dist/utils.mjs +5 -0
- package/dist/version--eVB2A7n.mjs +72 -0
- package/dist/version-aPrLuz_-.cjs +129 -0
- package/dist/vite.d.mts +521 -0
- package/dist/vite.mjs +1094 -0
- package/dist/zip-BCC7FAQ_.cjs +264 -0
- package/dist/zip-Dwm7s1C9.mjs +185 -0
- package/package.json +66 -64
- package/dist/chunk-AAAM44NW.js +0 -70
- package/dist/chunk-IVHNGRZY.js +0 -122
- package/dist/chunk-PD4EV4MM.js +0 -147
- package/dist/index.d.ts +0 -194
- package/dist/index.js +0 -309
- package/dist/provider.d.ts +0 -134
- package/dist/provider.js +0 -152
- package/dist/types-CU7GyVez.d.cts +0 -151
- package/dist/types-CU7GyVez.d.ts +0 -151
- package/dist/utils.d.ts +0 -129
- package/dist/utils.js +0 -3
- package/dist/vite.d.ts +0 -533
- package/dist/vite.js +0 -945
- package/dist/zip-Blmn2vzE.d.cts +0 -71
- package/dist/zip-CnSv_Njj.d.ts +0 -71
- package/provider.d.ts +0 -1
- package/provider.js +0 -1
- package/utils.d.ts +0 -1
- package/utils.js +0 -1
- package/vite.d.ts +0 -1
- package/vite.js +0 -1
package/dist/vite.mjs
ADDED
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
import { builtinModules, createRequire } from "node:module";
|
|
2
|
+
import * as babel from "@babel/core";
|
|
3
|
+
import { getPackageInfoSync, loadPackageJSON } from "local-pkg";
|
|
4
|
+
import MagicString from "magic-string";
|
|
5
|
+
import cp from "node:child_process";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { build, createFilter, createLogger, mergeConfig, normalizePath, version } from "vite";
|
|
9
|
+
import { isCI } from "ci-info";
|
|
10
|
+
import { createPackage } from "@electron/asar";
|
|
11
|
+
import crypto from "node:crypto";
|
|
12
|
+
import zlib from "node:zlib";
|
|
13
|
+
import { generate } from "selfsigned";
|
|
14
|
+
|
|
15
|
+
//#region src/utils/version.ts
|
|
16
|
+
/**
|
|
17
|
+
* Parse version string to {@link Version}, like `0.2.0-beta.1`
|
|
18
|
+
* @param version version string
|
|
19
|
+
*/
|
|
20
|
+
function parseVersion(version) {
|
|
21
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([a-z0-9.-]+))?/i.exec(version);
|
|
22
|
+
if (!match) throw new TypeError(`invalid version: ${version}`);
|
|
23
|
+
const [major, minor, patch] = match.slice(1, 4).map(Number);
|
|
24
|
+
const ret = {
|
|
25
|
+
major,
|
|
26
|
+
minor,
|
|
27
|
+
patch,
|
|
28
|
+
stage: "",
|
|
29
|
+
stageVersion: -1
|
|
30
|
+
};
|
|
31
|
+
if (match[4]) {
|
|
32
|
+
let [stage, _v] = match[4].split(".");
|
|
33
|
+
ret.stage = stage;
|
|
34
|
+
ret.stageVersion = Number(_v) || -1;
|
|
35
|
+
}
|
|
36
|
+
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || Number.isNaN(ret.stageVersion)) throw new TypeError(`Invalid version: ${version}`);
|
|
37
|
+
return ret;
|
|
38
|
+
}
|
|
39
|
+
const is = (j) => !!(j && j.minimumVersion && j.signature && j.version);
|
|
40
|
+
/**
|
|
41
|
+
* Check is `UpdateJSON`
|
|
42
|
+
* @param json any variable
|
|
43
|
+
*/
|
|
44
|
+
function isUpdateJSON(json) {
|
|
45
|
+
return is(json) && is(json?.beta);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Default function to generate `UpdateJSON`
|
|
49
|
+
* @param existingJson exising update json
|
|
50
|
+
* @param signature sigature
|
|
51
|
+
* @param version target version
|
|
52
|
+
* @param minimumVersion minimum version
|
|
53
|
+
*/
|
|
54
|
+
function defaultVersionJsonGenerator(existingJson, signature, version, minimumVersion) {
|
|
55
|
+
existingJson.beta = {
|
|
56
|
+
version,
|
|
57
|
+
minimumVersion,
|
|
58
|
+
signature
|
|
59
|
+
};
|
|
60
|
+
if (!parseVersion(version).stage) {
|
|
61
|
+
existingJson.version = version;
|
|
62
|
+
existingJson.minimumVersion = minimumVersion;
|
|
63
|
+
existingJson.signature = signature;
|
|
64
|
+
}
|
|
65
|
+
return existingJson;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/vite/constant.ts
|
|
70
|
+
const id = "electron-incremental-updater";
|
|
71
|
+
const bytecodeId = `${id}-bytecode`;
|
|
72
|
+
const log = createLogger("info", { prefix: `[${id}]` });
|
|
73
|
+
const bytecodeLog = createLogger("info", { prefix: `[${bytecodeId}]` });
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/vite/bytecode/code.ts
|
|
77
|
+
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";
|
|
78
|
+
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";
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/vite/bytecode/utils.ts
|
|
82
|
+
const electronModule = getPackageInfoSync("electron");
|
|
83
|
+
const electronMajorVersion = parseVersion(electronModule.version).major;
|
|
84
|
+
const useStrict = "'use strict';";
|
|
85
|
+
const bytecodeModuleLoader = "__loader__.js";
|
|
86
|
+
function getElectronPath() {
|
|
87
|
+
const electronModulePath = electronModule.rootPath;
|
|
88
|
+
let electronExecPath = process.env.ELECTRON_EXEC_PATH || "";
|
|
89
|
+
if (!electronExecPath) {
|
|
90
|
+
if (!electronModulePath) throw new Error("Electron is not installed");
|
|
91
|
+
const pathFile = path.join(electronModulePath, "path.txt");
|
|
92
|
+
let executablePath;
|
|
93
|
+
if (fs.existsSync(pathFile)) executablePath = fs.readFileSync(pathFile, "utf-8");
|
|
94
|
+
if (executablePath) {
|
|
95
|
+
electronExecPath = path.join(electronModulePath, "dist", executablePath);
|
|
96
|
+
process.env.ELECTRON_EXEC_PATH = electronExecPath;
|
|
97
|
+
} else throw new Error("Electron executable file is not existed");
|
|
98
|
+
}
|
|
99
|
+
return electronExecPath;
|
|
100
|
+
}
|
|
101
|
+
function getBytecodeCompilerPath() {
|
|
102
|
+
const scriptPath = path.join(electronModule.rootPath, "EIU_bytenode.cjs");
|
|
103
|
+
if (!fs.existsSync(scriptPath)) fs.writeFileSync(scriptPath, bytecodeGeneratorScript);
|
|
104
|
+
return scriptPath;
|
|
105
|
+
}
|
|
106
|
+
function toRelativePath(filename, importer) {
|
|
107
|
+
const relPath = path.posix.relative(path.dirname(importer), filename);
|
|
108
|
+
return relPath.startsWith(".") ? relPath : `./${relPath}`;
|
|
109
|
+
}
|
|
110
|
+
const logErr = (...args) => bytecodeLog.error(args.join(" "), { timestamp: true });
|
|
111
|
+
function compileToBytecode(code, electronPath = getElectronPath()) {
|
|
112
|
+
let data = Buffer.from([]);
|
|
113
|
+
const bytecodePath = getBytecodeCompilerPath();
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const proc = cp.spawn(electronPath, [bytecodePath], {
|
|
116
|
+
env: { ELECTRON_RUN_AS_NODE: "1" },
|
|
117
|
+
stdio: [
|
|
118
|
+
"pipe",
|
|
119
|
+
"pipe",
|
|
120
|
+
"pipe",
|
|
121
|
+
"ipc"
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
if (proc.stdin) {
|
|
125
|
+
proc.stdin.write(code);
|
|
126
|
+
proc.stdin.end();
|
|
127
|
+
}
|
|
128
|
+
if (proc.stdout) {
|
|
129
|
+
proc.stdout.on("data", (chunk) => data = Buffer.concat([data, chunk]));
|
|
130
|
+
proc.stdout.on("error", (err) => logErr(err));
|
|
131
|
+
proc.stdout.on("end", () => resolve(data));
|
|
132
|
+
}
|
|
133
|
+
if (proc.stderr) {
|
|
134
|
+
proc.stderr.on("data", (chunk) => logErr("Error: ", chunk.toString()));
|
|
135
|
+
proc.stderr.on("error", (err) => logErr("Error: ", err));
|
|
136
|
+
}
|
|
137
|
+
proc.addListener("error", (err) => logErr(err));
|
|
138
|
+
proc.on("error", (err) => reject(err));
|
|
139
|
+
proc.on("exit", () => resolve(data));
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function convertArrowFunctionAndTemplate(code) {
|
|
143
|
+
const result = babel.transform(code, { plugins: ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-template-literals"] });
|
|
144
|
+
return {
|
|
145
|
+
code: result?.code || code,
|
|
146
|
+
map: result?.map
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const decodeFn = ";function _0xstr_(a,b){return String.fromCharCode.apply(0,a.map(function(x){return x-b}))};";
|
|
150
|
+
function obfuscateString(input, offset = ~~(Math.random() * 16) + 1) {
|
|
151
|
+
return `_0xstr_([${input.split("").map((c) => `0x${(c.charCodeAt(0) + offset).toString(16)}`).join(",")}],${offset})`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Obfuscate string
|
|
155
|
+
* @param code source code
|
|
156
|
+
* @param sourcemap whether to generate sourcemap
|
|
157
|
+
* @param offset custom offset
|
|
158
|
+
*/
|
|
159
|
+
function convertLiteral(code, sourcemap, offset) {
|
|
160
|
+
const s = new MagicString(code);
|
|
161
|
+
let hasTransformed = false;
|
|
162
|
+
const ast = babel.parse(code, { ast: true });
|
|
163
|
+
if (!ast) throw new Error("Cannot parse code");
|
|
164
|
+
babel.traverse(ast, { StringLiteral(path) {
|
|
165
|
+
const parent = path.parent;
|
|
166
|
+
const node = path.node;
|
|
167
|
+
if (parent.type === "CallExpression") {
|
|
168
|
+
if (parent.callee.type === "Identifier" && parent.callee.name === "require") return;
|
|
169
|
+
if (parent.callee.type === "Import") return;
|
|
170
|
+
}
|
|
171
|
+
if (parent.type.startsWith("Export")) return;
|
|
172
|
+
if (parent.type.startsWith("Import")) return;
|
|
173
|
+
if (parent.type === "ObjectMethod" && parent.key === node) return;
|
|
174
|
+
if (parent.type === "ObjectProperty" && parent.key === node) {
|
|
175
|
+
const result = `[${obfuscateString(node.value, offset)}]`;
|
|
176
|
+
const start = node.start;
|
|
177
|
+
const end = node.end;
|
|
178
|
+
if (start && end) {
|
|
179
|
+
s.overwrite(start, end, result);
|
|
180
|
+
hasTransformed = true;
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!node.value.trim()) return;
|
|
185
|
+
const result = obfuscateString(node.value, offset);
|
|
186
|
+
const start = node.start;
|
|
187
|
+
const end = node.end;
|
|
188
|
+
if (start && end) {
|
|
189
|
+
s.overwrite(start, end, result);
|
|
190
|
+
hasTransformed = true;
|
|
191
|
+
}
|
|
192
|
+
} });
|
|
193
|
+
if (hasTransformed) s.append("\n").append(decodeFn);
|
|
194
|
+
return {
|
|
195
|
+
code: s.toString(),
|
|
196
|
+
map: sourcemap ? s.generateMap({ hires: true }) : void 0
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/vite/utils.ts
|
|
202
|
+
function readableSize(size) {
|
|
203
|
+
const units = [
|
|
204
|
+
"B",
|
|
205
|
+
"KB",
|
|
206
|
+
"MB",
|
|
207
|
+
"GB"
|
|
208
|
+
];
|
|
209
|
+
let i = 0;
|
|
210
|
+
while (size >= 1024 && i < units.length - 1) {
|
|
211
|
+
size /= 1024;
|
|
212
|
+
i++;
|
|
213
|
+
}
|
|
214
|
+
return `${size.toFixed(2)} ${units[i]}`;
|
|
215
|
+
}
|
|
216
|
+
function copyAndSkipIfExist(from, to, skipIfExist) {
|
|
217
|
+
if (!skipIfExist || !fs.existsSync(to)) try {
|
|
218
|
+
fs.cpSync(from, to, { recursive: true });
|
|
219
|
+
} catch (error) {
|
|
220
|
+
log.warn(`Copy failed: ${error}`, { timestamp: true });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/vite/build.ts
|
|
226
|
+
async function buildAsar({ version, asarOutputPath, gzipPath, electronDistPath, rendererDistPath, generateGzipFile }) {
|
|
227
|
+
await fs.promises.rename(rendererDistPath, path.join(electronDistPath, "renderer"));
|
|
228
|
+
fs.writeFileSync(path.join(electronDistPath, "version"), version);
|
|
229
|
+
await createPackage(electronDistPath, asarOutputPath);
|
|
230
|
+
const buf = await generateGzipFile(fs.readFileSync(asarOutputPath));
|
|
231
|
+
fs.writeFileSync(gzipPath, buf);
|
|
232
|
+
log.info(`Build update asar to '${gzipPath}' [${readableSize(buf.length)}]`, { timestamp: true });
|
|
233
|
+
return buf;
|
|
234
|
+
}
|
|
235
|
+
async function buildUpdateJson({ versionPath, privateKey, cert, version, minimumVersion, generateSignature, generateUpdateJson }, asarBuffer) {
|
|
236
|
+
let _json = {
|
|
237
|
+
beta: {
|
|
238
|
+
minimumVersion: version,
|
|
239
|
+
signature: "",
|
|
240
|
+
version
|
|
241
|
+
},
|
|
242
|
+
minimumVersion: version,
|
|
243
|
+
signature: "",
|
|
244
|
+
version
|
|
245
|
+
};
|
|
246
|
+
if (fs.existsSync(versionPath)) try {
|
|
247
|
+
const oldVersionJson = JSON.parse(fs.readFileSync(versionPath, "utf-8"));
|
|
248
|
+
if (isUpdateJSON(oldVersionJson)) _json = oldVersionJson;
|
|
249
|
+
else log.warn("Old version json is invalid, ignore it", { timestamp: true });
|
|
250
|
+
} catch {}
|
|
251
|
+
const sig = await generateSignature(asarBuffer, privateKey, cert, version);
|
|
252
|
+
_json = await generateUpdateJson(_json, sig, version, minimumVersion);
|
|
253
|
+
if (!isUpdateJSON(_json)) throw new Error("Invalid update json");
|
|
254
|
+
fs.writeFileSync(versionPath, JSON.stringify(_json, null, 2));
|
|
255
|
+
log.info(`build update json to '${versionPath}'`, { timestamp: true });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/vite/bytecode/index.ts
|
|
260
|
+
function getBytecodeLoaderBlock(chunkFileName) {
|
|
261
|
+
return `require("${toRelativePath(bytecodeModuleLoader, normalizePath(chunkFileName))}");`;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Compile to v8 bytecode to protect source code.
|
|
265
|
+
*/
|
|
266
|
+
function bytecodePlugin(env, options) {
|
|
267
|
+
const { enable, preload = false, electronPath, beforeCompile } = options;
|
|
268
|
+
if (!enable) return null;
|
|
269
|
+
if (!preload && env === "preload") {
|
|
270
|
+
bytecodeLog.warn("`bytecodePlugin` is skiped in preload. To enable in preload, please manually set the \"enablePreload\" option to true and set `sandbox: false` when creating the window", { timestamp: true });
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
const filter = createFilter(/\.(m?[jt]s|[jt]sx)$/);
|
|
274
|
+
let config;
|
|
275
|
+
let bytecodeRequired = false;
|
|
276
|
+
let bytecodeFiles = [];
|
|
277
|
+
return {
|
|
278
|
+
name: `${bytecodeId}-${env}`,
|
|
279
|
+
apply: "build",
|
|
280
|
+
enforce: "post",
|
|
281
|
+
configResolved(resolvedConfig) {
|
|
282
|
+
config = resolvedConfig;
|
|
283
|
+
},
|
|
284
|
+
transform(code, id) {
|
|
285
|
+
if (!filter(id)) return convertLiteral(code, !!config.build.sourcemap);
|
|
286
|
+
},
|
|
287
|
+
generateBundle(options) {
|
|
288
|
+
if (options.format !== "es" && bytecodeRequired) this.emitFile({
|
|
289
|
+
type: "asset",
|
|
290
|
+
source: `${bytecodeModuleLoaderCode}\n`,
|
|
291
|
+
name: "Bytecode Loader File",
|
|
292
|
+
fileName: bytecodeModuleLoader
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
renderChunk(code, chunk, options) {
|
|
296
|
+
if (options.format === "es") {
|
|
297
|
+
bytecodeLog.warn("`bytecodePlugin` does not support ES module, please set \"build.rollupOptions.output.format\" option to \"cjs\"", { timestamp: true });
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
if (chunk.type === "chunk") {
|
|
301
|
+
bytecodeRequired = true;
|
|
302
|
+
return convertArrowFunctionAndTemplate(code);
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
},
|
|
306
|
+
async writeBundle(options, output) {
|
|
307
|
+
if (options.format === "es" || !bytecodeRequired) return;
|
|
308
|
+
const outDir = options.dir;
|
|
309
|
+
bytecodeFiles = [];
|
|
310
|
+
const bundles = Object.keys(output);
|
|
311
|
+
const chunks = Object.values(output).filter((chunk) => chunk.type === "chunk" && chunk.fileName !== bytecodeModuleLoader);
|
|
312
|
+
const bytecodeChunks = new Set(chunks.map((chunk) => chunk.fileName));
|
|
313
|
+
const pattern = chunks.filter((chunk) => !chunk.isEntry).map((chunk) => path.basename(chunk.fileName)).map((chunk) => `(${chunk})`).join("|");
|
|
314
|
+
const bytecodeRE = pattern ? new RegExp(`require\\(\\S*(?=(${pattern})\\S*\\))`, "g") : null;
|
|
315
|
+
await Promise.all(bundles.map(async (name) => {
|
|
316
|
+
const chunk = output[name];
|
|
317
|
+
if (chunk.type === "chunk") {
|
|
318
|
+
let _code = chunk.code;
|
|
319
|
+
const chunkFilePath = path.resolve(outDir, name);
|
|
320
|
+
if (beforeCompile) {
|
|
321
|
+
const cbResult = await beforeCompile(_code, chunkFilePath);
|
|
322
|
+
if (cbResult) _code = cbResult;
|
|
323
|
+
}
|
|
324
|
+
if (bytecodeRE && _code.match(bytecodeRE)) {
|
|
325
|
+
let match;
|
|
326
|
+
const s = new MagicString(_code);
|
|
327
|
+
while (match = bytecodeRE.exec(_code)) {
|
|
328
|
+
const [prefix, chunkName] = match;
|
|
329
|
+
const len = prefix.length + chunkName.length;
|
|
330
|
+
s.overwrite(match.index, match.index + len, `${prefix + chunkName}c`, { contentOnly: true });
|
|
331
|
+
}
|
|
332
|
+
_code = s.toString();
|
|
333
|
+
}
|
|
334
|
+
if (bytecodeChunks.has(name)) {
|
|
335
|
+
const bytecodeBuffer = await compileToBytecode(_code, electronPath);
|
|
336
|
+
fs.writeFileSync(`${chunkFilePath}c`, bytecodeBuffer);
|
|
337
|
+
if (chunk.isEntry) {
|
|
338
|
+
const code = `${useStrict}\n${getBytecodeLoaderBlock(chunk.fileName)}\nmodule.exports=${`require("./${`${path.basename(name)}c`}");`}\n`;
|
|
339
|
+
fs.writeFileSync(chunkFilePath, code);
|
|
340
|
+
} else fs.unlinkSync(chunkFilePath);
|
|
341
|
+
bytecodeFiles.push({
|
|
342
|
+
name: `${name}c`,
|
|
343
|
+
size: bytecodeBuffer.length
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
if (chunk.isEntry) {
|
|
347
|
+
let hasBytecodeMoudle = false;
|
|
348
|
+
const idsToHandle = new Set([...chunk.imports, ...chunk.dynamicImports]);
|
|
349
|
+
for (const moduleId of idsToHandle) {
|
|
350
|
+
if (bytecodeChunks.has(moduleId)) {
|
|
351
|
+
hasBytecodeMoudle = true;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
const moduleInfo = this.getModuleInfo(moduleId);
|
|
355
|
+
if (moduleInfo) {
|
|
356
|
+
const { importers, dynamicImporters } = moduleInfo;
|
|
357
|
+
for (const importerId of importers) idsToHandle.add(importerId);
|
|
358
|
+
for (const importerId of dynamicImporters) idsToHandle.add(importerId);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const bytecodeLoaderBlock = getBytecodeLoaderBlock(chunk.fileName);
|
|
362
|
+
_code = hasBytecodeMoudle ? _code.replace(new RegExp(`(${useStrict})|("use strict";)`), `${useStrict}\n${bytecodeLoaderBlock}`) : _code;
|
|
363
|
+
}
|
|
364
|
+
fs.writeFileSync(chunkFilePath, _code);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}));
|
|
368
|
+
},
|
|
369
|
+
closeBundle() {
|
|
370
|
+
const outDir = `${normalizePath(path.relative(config.root, path.resolve(config.root, config.build.outDir)))}/`;
|
|
371
|
+
bytecodeFiles.forEach((file) => {
|
|
372
|
+
bytecodeLog.info(`${outDir}${file.name} [${readableSize(file.size)}]`, { timestamp: true });
|
|
373
|
+
});
|
|
374
|
+
bytecodeLog.info(`${bytecodeFiles.length} bundles compiled into bytecode.`, { timestamp: true });
|
|
375
|
+
bytecodeFiles = [];
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/vite/electron/utils.ts
|
|
382
|
+
/** Resolve the default Vite's `InlineConfig` for build Electron-Main */
|
|
383
|
+
function resolveViteConfig(isESM, options) {
|
|
384
|
+
return mergeConfig({
|
|
385
|
+
configFile: false,
|
|
386
|
+
publicDir: false,
|
|
387
|
+
build: {
|
|
388
|
+
lib: options.entry && {
|
|
389
|
+
entry: options.entry,
|
|
390
|
+
formats: isESM ? ["es"] : ["cjs"],
|
|
391
|
+
fileName: () => "[name].js"
|
|
392
|
+
},
|
|
393
|
+
outDir: "dist-electron",
|
|
394
|
+
emptyOutDir: false
|
|
395
|
+
},
|
|
396
|
+
resolve: {
|
|
397
|
+
conditions: ["node"],
|
|
398
|
+
mainFields: [
|
|
399
|
+
"module",
|
|
400
|
+
"jsnext:main",
|
|
401
|
+
"jsnext"
|
|
402
|
+
]
|
|
403
|
+
},
|
|
404
|
+
define: { "process.env": "process.env" }
|
|
405
|
+
}, options?.vite || {});
|
|
406
|
+
}
|
|
407
|
+
/** @see https://github.com/vitejs/vite/blob/v5.4.9/packages/vite/src/node/build.ts#L489-L504 */
|
|
408
|
+
function resolveInput(config) {
|
|
409
|
+
const options = config.build;
|
|
410
|
+
const { root } = config;
|
|
411
|
+
const libOptions = options.lib;
|
|
412
|
+
const resolve = (p) => path.resolve(root, p);
|
|
413
|
+
const input = libOptions ? options.rolldownOptions?.input || (typeof libOptions.entry === "string" ? resolve(libOptions.entry) : Array.isArray(libOptions.entry) ? libOptions.entry.map(resolve) : Object.fromEntries(Object.entries(libOptions.entry).map(([alias, file]) => [alias, resolve(file)]))) : options.rolldownOptions?.input;
|
|
414
|
+
if (input) return input;
|
|
415
|
+
const indexHtml = resolve("index.html");
|
|
416
|
+
return fs.existsSync(indexHtml) ? indexHtml : void 0;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* When run the `vite build` command, there must be an entry file.
|
|
420
|
+
* If the user does not need Renderer, we need to create a temporary entry file to avoid Vite throw error.
|
|
421
|
+
* @see https://github.com/vitejs/vite/blob/v5.4.9/packages/vite/src/node/config.ts#L1234-L1236
|
|
422
|
+
*/
|
|
423
|
+
async function mockIndexHtml(config) {
|
|
424
|
+
const { root, build } = config;
|
|
425
|
+
const output = path.resolve(root, build.outDir);
|
|
426
|
+
const content = `
|
|
427
|
+
<!doctype html>
|
|
428
|
+
<html lang="en">
|
|
429
|
+
<head>
|
|
430
|
+
<title>vite-plugin-electron</title>
|
|
431
|
+
</head>
|
|
432
|
+
<body>
|
|
433
|
+
<div>An entry file for electron renderer process.</div>
|
|
434
|
+
</body>
|
|
435
|
+
</html>
|
|
436
|
+
`.trim();
|
|
437
|
+
const index = "index.html";
|
|
438
|
+
const filepath = path.join(root, index);
|
|
439
|
+
const distpath = path.join(output, index);
|
|
440
|
+
await fs.promises.writeFile(filepath, content);
|
|
441
|
+
return {
|
|
442
|
+
async remove() {
|
|
443
|
+
await fs.promises.unlink(filepath);
|
|
444
|
+
await fs.promises.unlink(distpath);
|
|
445
|
+
},
|
|
446
|
+
filepath,
|
|
447
|
+
distpath
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Inspired `tree-kill`, implemented based on sync-api. #168
|
|
452
|
+
* @see https://github.com/pkrumins/node-tree-kill/blob/v1.2.2/index.js
|
|
453
|
+
*/
|
|
454
|
+
function treeKillSync(pid) {
|
|
455
|
+
if (process.platform === "win32") cp.execSync(`taskkill /pid ${pid} /T /F`);
|
|
456
|
+
else killTree(pidTree({
|
|
457
|
+
pid,
|
|
458
|
+
ppid: process.pid
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
function pidTree(tree) {
|
|
462
|
+
const command = process.platform === "darwin" ? `pgrep -P ${tree.pid}` : `ps -o pid --no-headers --ppid ${tree.ppid}`;
|
|
463
|
+
try {
|
|
464
|
+
const childs = cp.execSync(command, { encoding: "utf8" }).match(/\d+/g)?.map((id) => +id);
|
|
465
|
+
if (childs) tree.children = childs.map((cid) => pidTree({
|
|
466
|
+
pid: cid,
|
|
467
|
+
ppid: tree.pid
|
|
468
|
+
}));
|
|
469
|
+
} catch {}
|
|
470
|
+
return tree;
|
|
471
|
+
}
|
|
472
|
+
function killTree(tree) {
|
|
473
|
+
if (tree.children) for (const child of tree.children) killTree(child);
|
|
474
|
+
try {
|
|
475
|
+
process.kill(tree.pid);
|
|
476
|
+
} catch {}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/vite/electron/core.ts
|
|
481
|
+
function build$1(isESM, options) {
|
|
482
|
+
return build(resolveViteConfig(isESM, options));
|
|
483
|
+
}
|
|
484
|
+
function electron(isESM, options) {
|
|
485
|
+
const optionsArray = Array.isArray(options) ? options : [options];
|
|
486
|
+
let userConfig;
|
|
487
|
+
let configEnv;
|
|
488
|
+
let mockdInput;
|
|
489
|
+
if (!version.startsWith("8.")) throw new Error(`[vite-plugin-electron] Vite v${version} does not support \`rolldownOptions\`, please install \`vite@>=8\` or use an earlier version of \`vite-plugin-electron\`.`);
|
|
490
|
+
return [{
|
|
491
|
+
name: "vite-plugin-electron:dev",
|
|
492
|
+
apply: "serve",
|
|
493
|
+
configureServer(server) {
|
|
494
|
+
server.httpServer?.once("listening", () => {
|
|
495
|
+
Object.assign(process.env, { VITE_DEV_SERVER_URL: server.resolvedUrls?.local[0] });
|
|
496
|
+
const entryCount = optionsArray.length;
|
|
497
|
+
let closeBundleCount = 0;
|
|
498
|
+
for (const options of optionsArray) {
|
|
499
|
+
options.vite ??= {};
|
|
500
|
+
options.vite.mode ??= server.config.mode;
|
|
501
|
+
options.vite.root ??= server.config.root;
|
|
502
|
+
options.vite.envDir ??= server.config.envDir;
|
|
503
|
+
options.vite.envPrefix ??= server.config.envPrefix;
|
|
504
|
+
options.vite.build ??= {};
|
|
505
|
+
if (!Object.keys(options.vite.build).includes("watch")) options.vite.build.watch = {};
|
|
506
|
+
options.vite.build.minify ??= false;
|
|
507
|
+
options.vite.plugins ??= [];
|
|
508
|
+
options.vite.plugins.push({
|
|
509
|
+
name: ":startup",
|
|
510
|
+
closeBundle() {
|
|
511
|
+
if (++closeBundleCount < entryCount) return;
|
|
512
|
+
if (options.onstart) options.onstart.call(this, {
|
|
513
|
+
startup,
|
|
514
|
+
reload() {
|
|
515
|
+
if (process.electronApp) {
|
|
516
|
+
(server.hot || server.ws).send({ type: "full-reload" });
|
|
517
|
+
startup.send("electron-vite&type=hot-reload");
|
|
518
|
+
} else startup();
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
else startup();
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
build$1(isESM, options);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}, {
|
|
529
|
+
name: "vite-plugin-electron:prod",
|
|
530
|
+
apply: "build",
|
|
531
|
+
config(config, env) {
|
|
532
|
+
userConfig = config;
|
|
533
|
+
configEnv = env;
|
|
534
|
+
config.base ??= "./";
|
|
535
|
+
},
|
|
536
|
+
async configResolved(config) {
|
|
537
|
+
if (resolveInput(config) == null) mockdInput = await mockIndexHtml(config);
|
|
538
|
+
},
|
|
539
|
+
async closeBundle() {
|
|
540
|
+
mockdInput?.remove();
|
|
541
|
+
for (const options of optionsArray) {
|
|
542
|
+
options.vite ??= {};
|
|
543
|
+
options.vite.mode ??= configEnv.mode;
|
|
544
|
+
options.vite.root ??= userConfig.root;
|
|
545
|
+
options.vite.envDir ??= userConfig.envDir;
|
|
546
|
+
options.vite.envPrefix ??= userConfig.envPrefix;
|
|
547
|
+
await build$1(isESM, options);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}];
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Electron App startup function.
|
|
554
|
+
* It will mount the Electron App child-process to `process.electronApp`.
|
|
555
|
+
* @param argv default value `['.', '--no-sandbox']`
|
|
556
|
+
* @param options options for `child_process.spawn`
|
|
557
|
+
* @param customElectronPkg custom electron package name (default: 'electron')
|
|
558
|
+
*/
|
|
559
|
+
const startup = async (argv = [".", "--no-sandbox"], options, customElectronPkg) => {
|
|
560
|
+
const { spawn } = await import("node:child_process");
|
|
561
|
+
const electron = await import(customElectronPkg ?? "electron");
|
|
562
|
+
const electronPath = electron.default ?? electron;
|
|
563
|
+
await startup.exit();
|
|
564
|
+
const stdio = process.platform === "linux" ? [
|
|
565
|
+
"inherit",
|
|
566
|
+
"inherit",
|
|
567
|
+
"inherit",
|
|
568
|
+
"ignore",
|
|
569
|
+
"ipc"
|
|
570
|
+
] : [
|
|
571
|
+
"inherit",
|
|
572
|
+
"inherit",
|
|
573
|
+
"inherit",
|
|
574
|
+
"ipc"
|
|
575
|
+
];
|
|
576
|
+
process.electronApp = spawn(electronPath, argv, {
|
|
577
|
+
stdio,
|
|
578
|
+
...options
|
|
579
|
+
});
|
|
580
|
+
process.electronApp.once("exit", process.exit);
|
|
581
|
+
if (!startup.hookedProcessExit) {
|
|
582
|
+
startup.hookedProcessExit = true;
|
|
583
|
+
process.once("exit", startup.exit);
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
startup.send = (message) => {
|
|
587
|
+
if (process.electronApp) process.electronApp.send?.(message);
|
|
588
|
+
};
|
|
589
|
+
startup.hookedProcessExit = false;
|
|
590
|
+
startup.exit = async () => {
|
|
591
|
+
if (process.electronApp) await new Promise((resolve) => {
|
|
592
|
+
process.electronApp.removeAllListeners();
|
|
593
|
+
process.electronApp.once("exit", resolve);
|
|
594
|
+
treeKillSync(process.electronApp.pid);
|
|
595
|
+
});
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/vite/electron/plugin.ts
|
|
600
|
+
/**
|
|
601
|
+
* @see https://github.com/vitejs/vite/blob/v4.4.7/packages/vite/src/node/utils.ts#L140
|
|
602
|
+
*/
|
|
603
|
+
const bareImportRE = /^(?![a-zA-Z]:)[\w@](?!.*:\/\/)/;
|
|
604
|
+
const nodeModulesRE = /\/node_modules\//;
|
|
605
|
+
/**
|
|
606
|
+
* During dev, we exclude the `cjs` npm-pkg from bundle, mush like Vite :)
|
|
607
|
+
*/
|
|
608
|
+
function notBundle(options = {}) {
|
|
609
|
+
const externalIds = /* @__PURE__ */ new Set();
|
|
610
|
+
return {
|
|
611
|
+
name: "vite-plugin-electron:not-bundle",
|
|
612
|
+
enforce: "pre",
|
|
613
|
+
apply: "serve",
|
|
614
|
+
resolveId: {
|
|
615
|
+
filter: { id: bareImportRE },
|
|
616
|
+
async handler(source, importer) {
|
|
617
|
+
if (!importer || importer.includes("node_modules/")) return;
|
|
618
|
+
if (externalIds.has(source)) return {
|
|
619
|
+
id: source,
|
|
620
|
+
external: true
|
|
621
|
+
};
|
|
622
|
+
const id = (await this.resolve(source, importer, { skipSelf: true }))?.id;
|
|
623
|
+
if (!id || !nodeModulesRE.test(id) || options.filter?.(id) === false) return;
|
|
624
|
+
try {
|
|
625
|
+
createRequire(importer).resolve(source);
|
|
626
|
+
} catch {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
externalIds.add(source);
|
|
630
|
+
return {
|
|
631
|
+
id: source,
|
|
632
|
+
external: true,
|
|
633
|
+
moduleSideEffects: false
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
//#endregion
|
|
641
|
+
//#region src/utils/crypto.ts
|
|
642
|
+
function hashBuffer(data, length) {
|
|
643
|
+
const hash = crypto.createHash("SHA256").update(data).digest("binary");
|
|
644
|
+
return Buffer.from(hash).subarray(0, length);
|
|
645
|
+
}
|
|
646
|
+
function aesEncrypt(plainText, key, iv) {
|
|
647
|
+
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
|
648
|
+
return cipher.update(plainText, "utf8", "base64url") + cipher.final("base64url");
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Default function to generate asar signature, returns generated signature
|
|
652
|
+
* @param buffer file buffer
|
|
653
|
+
* @param privateKey primary key
|
|
654
|
+
* @param cert certificate
|
|
655
|
+
* @param version target version
|
|
656
|
+
*/
|
|
657
|
+
function defaultSignature(buffer, privateKey, cert, version) {
|
|
658
|
+
return aesEncrypt(`${crypto.createSign("RSA-SHA256").update(buffer).sign(crypto.createPrivateKey(privateKey), "base64")}%${version}`, hashBuffer(cert, 32), hashBuffer(buffer, 16));
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/utils/zip.ts
|
|
663
|
+
/**
|
|
664
|
+
* Default function to compress file using brotli
|
|
665
|
+
* @param buffer uncompressed file buffer
|
|
666
|
+
*/
|
|
667
|
+
async function defaultZipFile(buffer) {
|
|
668
|
+
return new Promise((resolve, reject) => {
|
|
669
|
+
zlib.brotliCompress(buffer, (err, buffer) => err ? reject(err) : resolve(buffer));
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/vite/key.ts
|
|
675
|
+
async function generateKeyPair(keyLength, subject, days, privateKeyPath, certPath) {
|
|
676
|
+
const privateKeyDir = path.dirname(privateKeyPath);
|
|
677
|
+
if (!fs.existsSync(privateKeyDir)) fs.mkdirSync(privateKeyDir, { recursive: true });
|
|
678
|
+
const certDir = path.dirname(certPath);
|
|
679
|
+
if (!fs.existsSync(certDir)) fs.mkdirSync(certDir, { recursive: true });
|
|
680
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
681
|
+
const endDate = new Date(startDate);
|
|
682
|
+
endDate.setDate(startDate.getDate() + days);
|
|
683
|
+
const { cert, private: privateKey } = await generate(subject, {
|
|
684
|
+
keySize: keyLength,
|
|
685
|
+
algorithm: "sha256",
|
|
686
|
+
notBeforeDate: startDate,
|
|
687
|
+
notAfterDate: endDate
|
|
688
|
+
});
|
|
689
|
+
fs.writeFileSync(privateKeyPath, privateKey.replace(/\r\n?/g, "\n"));
|
|
690
|
+
fs.writeFileSync(certPath, cert.replace(/\r\n?/g, "\n"));
|
|
691
|
+
}
|
|
692
|
+
async function parseKeys({ keyLength, privateKeyPath, certPath, subject, days }) {
|
|
693
|
+
const keysDir = path.dirname(privateKeyPath);
|
|
694
|
+
let privateKey = process.env.UPDATER_PK;
|
|
695
|
+
let cert = process.env.UPDATER_CERT;
|
|
696
|
+
if (privateKey && cert) {
|
|
697
|
+
log.info("Use `UPDATER_PK` and `UPDATER_CERT` from environment variables", { timestamp: true });
|
|
698
|
+
return {
|
|
699
|
+
privateKey,
|
|
700
|
+
cert
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (!fs.existsSync(keysDir)) fs.mkdirSync(keysDir);
|
|
704
|
+
if (!fs.existsSync(privateKeyPath) || !fs.existsSync(certPath)) {
|
|
705
|
+
log.info("No key pair found, generate new key pair", { timestamp: true });
|
|
706
|
+
await generateKeyPair(keyLength, parseSubjects(subject), days, privateKeyPath, certPath);
|
|
707
|
+
}
|
|
708
|
+
privateKey = fs.readFileSync(privateKeyPath, "utf-8");
|
|
709
|
+
cert = fs.readFileSync(certPath, "utf-8");
|
|
710
|
+
return {
|
|
711
|
+
privateKey,
|
|
712
|
+
cert
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function parseSubjects(subject) {
|
|
716
|
+
return Object.entries(subject).filter(([_, value]) => !!value).map(([name, value]) => ({
|
|
717
|
+
name,
|
|
718
|
+
value
|
|
719
|
+
}));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/vite/option.ts
|
|
724
|
+
async function parseOptions(pkg, options = {}) {
|
|
725
|
+
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 = {
|
|
726
|
+
commonName: pkg.name,
|
|
727
|
+
organizationName: `org.${pkg.name}`
|
|
728
|
+
}, days = 3650 } = {} } = {}, overrideGenerator: { generateGzipFile = defaultZipFile, generateSignature = defaultSignature, generateUpdateJson = defaultVersionJsonGenerator } = {} } = options;
|
|
729
|
+
const buildAsarOption = {
|
|
730
|
+
version: pkg.version,
|
|
731
|
+
asarOutputPath,
|
|
732
|
+
gzipPath,
|
|
733
|
+
electronDistPath,
|
|
734
|
+
rendererDistPath,
|
|
735
|
+
generateGzipFile
|
|
736
|
+
};
|
|
737
|
+
const { privateKey, cert } = await parseKeys({
|
|
738
|
+
keyLength,
|
|
739
|
+
privateKeyPath,
|
|
740
|
+
certPath,
|
|
741
|
+
subject,
|
|
742
|
+
days
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
buildAsarOption,
|
|
746
|
+
buildVersionOption: {
|
|
747
|
+
version: pkg.version,
|
|
748
|
+
minimumVersion,
|
|
749
|
+
privateKey,
|
|
750
|
+
cert,
|
|
751
|
+
versionPath,
|
|
752
|
+
generateSignature,
|
|
753
|
+
generateUpdateJson
|
|
754
|
+
},
|
|
755
|
+
cert,
|
|
756
|
+
entryOutDir
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/vite/core.ts
|
|
762
|
+
/**
|
|
763
|
+
* Startup function for debug
|
|
764
|
+
* @see {@link https://github.com/electron-vite/electron-vite-vue/blob/main/vite.config.ts electron-vite-vue template}
|
|
765
|
+
* @example
|
|
766
|
+
* import { debugStartup, buildElectronPluginOptions } from 'electron-incremental-update/vite'
|
|
767
|
+
* const options = buildElectronPluginOptions({
|
|
768
|
+
* // ...
|
|
769
|
+
* main: {
|
|
770
|
+
* // ...
|
|
771
|
+
* startup: debugStartup
|
|
772
|
+
* },
|
|
773
|
+
* })
|
|
774
|
+
*/
|
|
775
|
+
const debugStartup = async (args) => {
|
|
776
|
+
if (process.env.VSCODE_DEBUG) console.log("[startup] Electron App");
|
|
777
|
+
else await args.startup();
|
|
778
|
+
};
|
|
779
|
+
/**
|
|
780
|
+
* Startup function to filter unwanted error message
|
|
781
|
+
* @see {@link https://github.com/electron/electron/issues/46903#issuecomment-2848483520 reference}
|
|
782
|
+
* @example
|
|
783
|
+
* import { filterErrorMessageStartup, buildElectronPluginOptions } from 'electron-incremental-update/vite'
|
|
784
|
+
* const options = buildElectronPluginOptions({
|
|
785
|
+
* // ...
|
|
786
|
+
* main: {
|
|
787
|
+
* // ...
|
|
788
|
+
* startup: args => filterErrorMessageStartup(
|
|
789
|
+
* args,
|
|
790
|
+
* // ignore error message when function returns false
|
|
791
|
+
* msg => !/"code":-32601/.test(msg)
|
|
792
|
+
* )
|
|
793
|
+
* },
|
|
794
|
+
* })
|
|
795
|
+
*/
|
|
796
|
+
async function filterErrorMessageStartup(args, filter) {
|
|
797
|
+
const stdio = process.platform === "linux" ? [
|
|
798
|
+
"inherit",
|
|
799
|
+
"pipe",
|
|
800
|
+
"pipe",
|
|
801
|
+
"ignore",
|
|
802
|
+
"ipc"
|
|
803
|
+
] : [
|
|
804
|
+
"inherit",
|
|
805
|
+
"pipe",
|
|
806
|
+
"pipe",
|
|
807
|
+
"ipc"
|
|
808
|
+
];
|
|
809
|
+
await args.startup(void 0, { stdio });
|
|
810
|
+
const elec = process.electronApp;
|
|
811
|
+
elec.stdout.addListener("data", (data) => {
|
|
812
|
+
console.log(data.toString().trimEnd());
|
|
813
|
+
});
|
|
814
|
+
elec.stderr.addListener("data", (data) => {
|
|
815
|
+
const message = data.toString();
|
|
816
|
+
if (filter(message)) console.error(message);
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Startup function util to fix Windows terminal charset
|
|
821
|
+
* @example
|
|
822
|
+
* import { debugStartup, fixWinCharEncoding, buildElectronPluginOptions } from 'electron-incremental-update/vite'
|
|
823
|
+
* const options = buildElectronPluginOptions({
|
|
824
|
+
* // ...
|
|
825
|
+
* main: {
|
|
826
|
+
* // ...
|
|
827
|
+
* startup: fixWinCharEncoding(debugStartup)
|
|
828
|
+
* },
|
|
829
|
+
* })
|
|
830
|
+
*/
|
|
831
|
+
function fixWinCharEncoding(fn) {
|
|
832
|
+
return (async (...args) => {
|
|
833
|
+
if (process.platform === "win32") (await import("node:child_process")).spawnSync("chcp", ["65001"]);
|
|
834
|
+
await fn(...args);
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
function getMainFileBaseName(options) {
|
|
838
|
+
let mainFilePath;
|
|
839
|
+
if (typeof options === "string") mainFilePath = path.basename(options);
|
|
840
|
+
else if (Array.isArray(options)) mainFilePath = path.basename(options[0]);
|
|
841
|
+
else {
|
|
842
|
+
if (!(options?.index ?? options?.main)) throw new Error(`\`options.main.files\` (${options}) must have "index" or "main" key, like \`{ index: "./electron/main/index.ts" }\``);
|
|
843
|
+
mainFilePath = options?.index ? "index.js" : "main.js";
|
|
844
|
+
}
|
|
845
|
+
log.info(`Using "${mainFilePath}" as main file`, { timestamp: true });
|
|
846
|
+
return mainFilePath.replace(/\.[cm]?ts$/, ".js");
|
|
847
|
+
}
|
|
848
|
+
function parseVersionPath(versionPath) {
|
|
849
|
+
versionPath = normalizePath(versionPath);
|
|
850
|
+
if (!versionPath.startsWith("./")) versionPath = `./${versionPath}`;
|
|
851
|
+
return new URL(versionPath, "file://").pathname.slice(1);
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Base on `./electron/simple`
|
|
855
|
+
* - integrate with updater
|
|
856
|
+
* - no `renderer` config
|
|
857
|
+
* - remove old output file
|
|
858
|
+
* - externalize dependencies
|
|
859
|
+
* - auto restart when entry file changes
|
|
860
|
+
* - other configs in {@link https://github.com/electron-vite/electron-vite-vue/blob/main/vite.config.ts electron-vite-vue template}
|
|
861
|
+
*
|
|
862
|
+
* You can override all the vite configs, except output directories (use `options.updater.paths.electronDistPath` instead)
|
|
863
|
+
*
|
|
864
|
+
* @example
|
|
865
|
+
* ```ts
|
|
866
|
+
* import { defineConfig } from 'vite'
|
|
867
|
+
* import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
|
|
868
|
+
*
|
|
869
|
+
* export default defineConfig(async ({ command }) => {
|
|
870
|
+
* const isBuild = command === 'build'
|
|
871
|
+
* return {
|
|
872
|
+
* plugins: [
|
|
873
|
+
* electronWithUpdater({
|
|
874
|
+
* isBuild,
|
|
875
|
+
* main: {
|
|
876
|
+
* files: ['./electron/main/index.ts', './electron/main/worker.ts'],
|
|
877
|
+
* // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
|
|
878
|
+
* onstart: debugStartup,
|
|
879
|
+
* },
|
|
880
|
+
* preload: {
|
|
881
|
+
* files: './electron/preload/index.ts',
|
|
882
|
+
* },
|
|
883
|
+
* updater: {
|
|
884
|
+
* // options
|
|
885
|
+
* }
|
|
886
|
+
* }),
|
|
887
|
+
* ],
|
|
888
|
+
* server: process.env.VSCODE_DEBUG && (() => {
|
|
889
|
+
* const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
|
|
890
|
+
* return {
|
|
891
|
+
* host: url.hostname,
|
|
892
|
+
* port: +url.port,
|
|
893
|
+
* }
|
|
894
|
+
* })(),
|
|
895
|
+
* }
|
|
896
|
+
* })
|
|
897
|
+
* ```
|
|
898
|
+
*/
|
|
899
|
+
async function electronWithUpdater(options) {
|
|
900
|
+
let { isBuild, entry: _entry, main: _main, preload: _preload, sourcemap = !isBuild, minify = isBuild, buildVersionJson, updater, bytecode, useNotBundle = true } = options;
|
|
901
|
+
const pkg = await loadPackageJSON();
|
|
902
|
+
if (!pkg || !pkg.version || !pkg.name || !pkg.main) throw new Error("package.json not found or invalid, must contains version, name and main field");
|
|
903
|
+
log.info(`Clear cache files`, { timestamp: true });
|
|
904
|
+
const isESM = pkg.type === "module";
|
|
905
|
+
const external = [
|
|
906
|
+
...builtinModules,
|
|
907
|
+
"electron",
|
|
908
|
+
/^node:/,
|
|
909
|
+
/.*\.(node|dll|dylib|so)$/,
|
|
910
|
+
"original-fs",
|
|
911
|
+
...isBuild || _entry.postBuild ? [] : Object.keys(pkg.dependencies || {})
|
|
912
|
+
];
|
|
913
|
+
let bytecodeOptions = typeof bytecode === "object" ? bytecode : bytecode === true ? { enable: true } : void 0;
|
|
914
|
+
if (isESM && bytecodeOptions?.enable) throw new Error("`bytecodePlugin` does not support ES module, please remove \"type\": \"module\" in package.json");
|
|
915
|
+
const { buildAsarOption, buildVersionOption, cert, entryOutDir } = await parseOptions(pkg, updater);
|
|
916
|
+
sourcemap ??= isBuild || !!process.env.VSCODE_DEBUG;
|
|
917
|
+
try {
|
|
918
|
+
fs.rmSync(buildAsarOption.electronDistPath, {
|
|
919
|
+
recursive: true,
|
|
920
|
+
force: true
|
|
921
|
+
});
|
|
922
|
+
fs.rmSync(entryOutDir, {
|
|
923
|
+
recursive: true,
|
|
924
|
+
force: true
|
|
925
|
+
});
|
|
926
|
+
} catch {}
|
|
927
|
+
const define = {
|
|
928
|
+
__EIU_ASAR_BASE_NAME__: JSON.stringify(path.basename(buildAsarOption.asarOutputPath)),
|
|
929
|
+
__EIU_ELECTRON_DIST_PATH__: JSON.stringify(normalizePath(buildAsarOption.electronDistPath)),
|
|
930
|
+
__EIU_ENTRY_DIST_PATH__: JSON.stringify(normalizePath(entryOutDir)),
|
|
931
|
+
__EIU_IS_DEV__: JSON.stringify(!isBuild),
|
|
932
|
+
__EIU_IS_ESM__: JSON.stringify(isESM),
|
|
933
|
+
__EIU_MAIN_FILE__: JSON.stringify(getMainFileBaseName(_main.files)),
|
|
934
|
+
__EIU_SIGNATURE_CERT__: JSON.stringify(cert),
|
|
935
|
+
__EIU_VERSION_PATH__: JSON.stringify(parseVersionPath(normalizePath(buildVersionOption.versionPath)))
|
|
936
|
+
};
|
|
937
|
+
const _electronOptions = [{
|
|
938
|
+
entry: _main.files,
|
|
939
|
+
onstart: async (args) => {
|
|
940
|
+
if (_main.onstart) await _main.onstart(args);
|
|
941
|
+
else await args.startup();
|
|
942
|
+
},
|
|
943
|
+
vite: mergeConfig({
|
|
944
|
+
plugins: [!isBuild && useNotBundle && notBundle(), bytecodeOptions && bytecodePlugin("main", bytecodeOptions)],
|
|
945
|
+
build: {
|
|
946
|
+
sourcemap,
|
|
947
|
+
minify,
|
|
948
|
+
outDir: `${buildAsarOption.electronDistPath}/main`,
|
|
949
|
+
rolldownOptions: {
|
|
950
|
+
external,
|
|
951
|
+
platform: "node",
|
|
952
|
+
output: {
|
|
953
|
+
cleanDir: true,
|
|
954
|
+
polyfillRequire: false
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
},
|
|
958
|
+
define
|
|
959
|
+
}, _main.vite ?? {})
|
|
960
|
+
}];
|
|
961
|
+
if (_preload?.files) _electronOptions.push({
|
|
962
|
+
onstart(args) {
|
|
963
|
+
args.reload();
|
|
964
|
+
},
|
|
965
|
+
vite: mergeConfig({
|
|
966
|
+
plugins: [bytecodeOptions && bytecodePlugin("preload", bytecodeOptions)],
|
|
967
|
+
build: {
|
|
968
|
+
sourcemap: sourcemap ? "inline" : void 0,
|
|
969
|
+
minify,
|
|
970
|
+
outDir: `${buildAsarOption.electronDistPath}/preload`,
|
|
971
|
+
rolldownOptions: {
|
|
972
|
+
external,
|
|
973
|
+
input: _preload.files,
|
|
974
|
+
output: {
|
|
975
|
+
format: "cjs",
|
|
976
|
+
inlineDynamicImports: true,
|
|
977
|
+
polyfillRequire: false,
|
|
978
|
+
entryFileNames: `[name].${isESM ? "mjs" : "js"}`,
|
|
979
|
+
chunkFileNames: `[name].${isESM ? "mjs" : "js"}`,
|
|
980
|
+
assetFileNames: "[name].[ext]"
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
define
|
|
985
|
+
}, _preload?.vite ?? {})
|
|
986
|
+
});
|
|
987
|
+
_electronOptions.push({
|
|
988
|
+
entry: _entry.files,
|
|
989
|
+
vite: mergeConfig({
|
|
990
|
+
plugins: [bytecodeOptions && bytecodePlugin("main", bytecodeOptions), {
|
|
991
|
+
name: `${id}:entry`,
|
|
992
|
+
enforce: "post",
|
|
993
|
+
async closeBundle() {
|
|
994
|
+
log.info(`Build entry to '${entryOutDir}'`, { timestamp: true });
|
|
995
|
+
await _entry.postBuild?.({
|
|
996
|
+
isBuild,
|
|
997
|
+
getPathFromEntryOutputDir(...paths) {
|
|
998
|
+
return path.join(entryOutDir, ...paths);
|
|
999
|
+
},
|
|
1000
|
+
copyToEntryOutputDir({ from, to = path.basename(from), skipIfExist = true }) {
|
|
1001
|
+
if (!fs.existsSync(from)) {
|
|
1002
|
+
log.warn(`${from} not found`, { timestamp: true });
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
copyAndSkipIfExist(from, path.join(entryOutDir, to), skipIfExist);
|
|
1006
|
+
},
|
|
1007
|
+
copyModules({ modules, skipIfExist = true }) {
|
|
1008
|
+
const nodeModulesPath = path.join(entryOutDir, "node_modules");
|
|
1009
|
+
for (const m of modules) {
|
|
1010
|
+
const { rootPath } = getPackageInfoSync(m) || {};
|
|
1011
|
+
if (!rootPath) {
|
|
1012
|
+
log.warn(`Package '${m}' not found`, { timestamp: true });
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
copyAndSkipIfExist(rootPath, path.join(nodeModulesPath, m), skipIfExist);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
if (isBuild) try {
|
|
1020
|
+
const buffer = await buildAsar(buildAsarOption);
|
|
1021
|
+
if (!buildVersionJson && !isCI) log.warn("No `buildVersionJson` option setup, skip build version json. Only build in CI by default", { timestamp: true });
|
|
1022
|
+
else await buildUpdateJson(buildVersionOption, buffer);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
console.error(error);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}],
|
|
1028
|
+
build: {
|
|
1029
|
+
sourcemap,
|
|
1030
|
+
minify,
|
|
1031
|
+
outDir: entryOutDir,
|
|
1032
|
+
rolldownOptions: {
|
|
1033
|
+
external,
|
|
1034
|
+
platform: "node",
|
|
1035
|
+
output: { polyfillRequire: false }
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
define
|
|
1039
|
+
}, _entry.vite || {})
|
|
1040
|
+
});
|
|
1041
|
+
return electron(isESM, _electronOptions);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region src/vite/define.ts
|
|
1046
|
+
/**
|
|
1047
|
+
* Vite config helper
|
|
1048
|
+
* @see {@link electronWithUpdater}
|
|
1049
|
+
* @example
|
|
1050
|
+
* ```ts
|
|
1051
|
+
* import { defineElectronConfig } from 'electron-incremental-update/vite'
|
|
1052
|
+
*
|
|
1053
|
+
* export default defineElectronConfig({
|
|
1054
|
+
* main: {
|
|
1055
|
+
* files: ['./electron/main/index.ts', './electron/main/worker.ts'],
|
|
1056
|
+
* // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
|
|
1057
|
+
* onstart: debugStartup,
|
|
1058
|
+
* },
|
|
1059
|
+
* preload: {
|
|
1060
|
+
* files: './electron/preload/index.ts',
|
|
1061
|
+
* },
|
|
1062
|
+
* updater: {
|
|
1063
|
+
* // options
|
|
1064
|
+
* },
|
|
1065
|
+
* renderer: {
|
|
1066
|
+
* server: process.env.VSCODE_DEBUG && (() => {
|
|
1067
|
+
* const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
|
|
1068
|
+
* return {
|
|
1069
|
+
* host: url.hostname,
|
|
1070
|
+
* port: +url.port,
|
|
1071
|
+
* }
|
|
1072
|
+
* })(),
|
|
1073
|
+
* }
|
|
1074
|
+
* })
|
|
1075
|
+
* ```
|
|
1076
|
+
*/
|
|
1077
|
+
function defineElectronConfig(options) {
|
|
1078
|
+
return ({ command }) => {
|
|
1079
|
+
options.isBuild ??= command === "build";
|
|
1080
|
+
const electronPlugin = electronWithUpdater(options);
|
|
1081
|
+
const result = options.renderer ?? {};
|
|
1082
|
+
result.plugins ??= [];
|
|
1083
|
+
result.plugins.push(electronPlugin);
|
|
1084
|
+
const rendererDistPath = options.updater?.paths?.rendererDistPath;
|
|
1085
|
+
if (rendererDistPath) {
|
|
1086
|
+
result.build ??= {};
|
|
1087
|
+
result.build.outDir = rendererDistPath;
|
|
1088
|
+
}
|
|
1089
|
+
return result;
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
//#endregion
|
|
1094
|
+
export { convertLiteral, debugStartup, electronWithUpdater as default, electronWithUpdater, defineElectronConfig, filterErrorMessageStartup, fixWinCharEncoding };
|