ee-bin 5.0.0-beta.2 → 5.0.0-beta.5
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/dist/cjs/config/bin_default.js +69 -29
- package/dist/cjs/config/bin_default.js.map +1 -1
- package/dist/cjs/index.js +125 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/extend.js +49 -15
- package/dist/cjs/lib/extend.js.map +1 -1
- package/dist/cjs/lib/helpers.js +71 -22
- package/dist/cjs/lib/helpers.js.map +1 -1
- package/dist/cjs/lib/utils.js +71 -30
- package/dist/cjs/lib/utils.js.map +1 -1
- package/dist/cjs/plugins/bundle_registry_plugin.js +156 -0
- package/dist/cjs/plugins/bundle_registry_plugin.js.map +1 -0
- package/dist/cjs/tools/encrypt.js +190 -29
- package/dist/cjs/tools/encrypt.js.map +1 -1
- package/dist/cjs/tools/iconGen.js +118 -30
- package/dist/cjs/tools/iconGen.js.map +1 -1
- package/dist/cjs/tools/incrUpdater.js +95 -33
- package/dist/cjs/tools/incrUpdater.js.map +1 -1
- package/dist/cjs/tools/move.js +71 -11
- package/dist/cjs/tools/move.js.map +1 -1
- package/dist/cjs/tools/serve.js +406 -81
- package/dist/cjs/tools/serve.js.map +1 -1
- package/dist/cjs/types/config.js +13 -0
- package/dist/cjs/types/config.js.map +1 -0
- package/dist/esm/config/bin_default.d.ts +19 -147
- package/dist/esm/config/bin_default.d.ts.map +1 -1
- package/dist/esm/config/bin_default.js +69 -29
- package/dist/esm/config/bin_default.js.map +1 -1
- package/dist/esm/index.d.ts +20 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +125 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/extend.d.ts +33 -0
- package/dist/esm/lib/extend.d.ts.map +1 -1
- package/dist/esm/lib/extend.js +49 -15
- package/dist/esm/lib/extend.js.map +1 -1
- package/dist/esm/lib/helpers.d.ts +44 -3
- package/dist/esm/lib/helpers.d.ts.map +1 -1
- package/dist/esm/lib/helpers.js +71 -22
- package/dist/esm/lib/helpers.js.map +1 -1
- package/dist/esm/lib/utils.d.ts +57 -3
- package/dist/esm/lib/utils.d.ts.map +1 -1
- package/dist/esm/lib/utils.js +71 -30
- package/dist/esm/lib/utils.js.map +1 -1
- package/dist/esm/plugins/bundle_registry_plugin.d.ts +33 -0
- package/dist/esm/plugins/bundle_registry_plugin.d.ts.map +1 -0
- package/dist/esm/plugins/bundle_registry_plugin.js +156 -0
- package/dist/esm/plugins/bundle_registry_plugin.js.map +1 -0
- package/dist/esm/tools/encrypt.d.ts +37 -1
- package/dist/esm/tools/encrypt.d.ts.map +1 -1
- package/dist/esm/tools/encrypt.js +190 -29
- package/dist/esm/tools/encrypt.js.map +1 -1
- package/dist/esm/tools/iconGen.d.ts +27 -1
- package/dist/esm/tools/iconGen.d.ts.map +1 -1
- package/dist/esm/tools/iconGen.js +118 -30
- package/dist/esm/tools/iconGen.js.map +1 -1
- package/dist/esm/tools/incrUpdater.d.ts +60 -13
- package/dist/esm/tools/incrUpdater.d.ts.map +1 -1
- package/dist/esm/tools/incrUpdater.js +95 -33
- package/dist/esm/tools/incrUpdater.js.map +1 -1
- package/dist/esm/tools/move.d.ts +41 -0
- package/dist/esm/tools/move.d.ts.map +1 -1
- package/dist/esm/tools/move.js +71 -11
- package/dist/esm/tools/move.js.map +1 -1
- package/dist/esm/tools/serve.d.ts +162 -25
- package/dist/esm/tools/serve.d.ts.map +1 -1
- package/dist/esm/tools/serve.js +406 -81
- package/dist/esm/tools/serve.js.map +1 -1
- package/dist/esm/types/config.d.ts +211 -0
- package/dist/esm/types/config.d.ts.map +1 -0
- package/dist/esm/types/config.js +13 -0
- package/dist/esm/types/config.js.map +1 -0
- package/package.json +16 -13
package/dist/cjs/tools/serve.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dev/Build/Start Manager — ee-bin's core dispatcher
|
|
4
|
+
*
|
|
5
|
+
* The ServeProcess class manages the full dev/build/start/exec lifecycle and is the
|
|
6
|
+
* most complex module in ee-bin. Core responsibilities:
|
|
7
|
+
* 1. dev — Start frontend dev server + Electron process, optional watch mode with auto-rebuild
|
|
8
|
+
* 2. build — Bundle Electron code + execute electron-builder platform build commands
|
|
9
|
+
* 3. start — Start Electron in production mode
|
|
10
|
+
* 4. exec — Execute user-defined custom commands
|
|
11
|
+
*
|
|
12
|
+
* Process management strategy:
|
|
13
|
+
* - execProcess only tracks async ChildProcess instances (sync executions are already
|
|
14
|
+
* complete, so there's no process to manage)
|
|
15
|
+
* - SIGINT/SIGTERM signal handlers close all child processes and restore package.json main field
|
|
16
|
+
* - In watch mode, debounce + tree-kill terminate the old Electron process before restarting
|
|
17
|
+
*
|
|
18
|
+
* Bundling strategy:
|
|
19
|
+
* - bundle mode: esbuild bundles into a single file + virtual registry plugin
|
|
20
|
+
* - copy mode: directly copies the entire electron/ directory (for non-bundling scenarios)
|
|
21
|
+
* - After bundling, switches package.json main field to point to ./public/electron/main.js
|
|
22
|
+
*/
|
|
2
23
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
24
|
if (k2 === undefined) k2 = k;
|
|
4
25
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -41,20 +62,26 @@ const helpers_js_1 = require("../lib/helpers.js");
|
|
|
41
62
|
const path_1 = __importDefault(require("path"));
|
|
42
63
|
const fs_1 = __importDefault(require("fs"));
|
|
43
64
|
const esbuild_1 = require("esbuild");
|
|
44
|
-
const
|
|
65
|
+
const globby_1 = require("globby");
|
|
66
|
+
const chokidar_1 = require("chokidar");
|
|
45
67
|
const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
46
68
|
const process_1 = __importDefault(require("process"));
|
|
47
69
|
const cross_spawn_1 = __importStar(require("cross-spawn"));
|
|
48
70
|
const utils_js_1 = require("../lib/utils.js");
|
|
71
|
+
const bundle_registry_plugin_js_1 = require("../plugins/bundle_registry_plugin.js");
|
|
49
72
|
const log = (0, helpers_js_1.createDebug)('ee-bin:serve');
|
|
73
|
+
/** Maximum buffer size for child processes (1GB), prevents large-output build commands from being truncated */
|
|
74
|
+
const MAX_BUFFER = 1024 * 1024 * 1024;
|
|
75
|
+
const ELECTRON_DIR = './electron';
|
|
76
|
+
const BUNDLE_DIR = './public/electron';
|
|
77
|
+
const PKG_PATH = './package.json';
|
|
50
78
|
class ServeProcess {
|
|
51
79
|
constructor() {
|
|
52
80
|
this.execProcess = {};
|
|
53
|
-
this.
|
|
54
|
-
this.bundleDir = './public/electron';
|
|
55
|
-
this.pkgPath = './package.json';
|
|
81
|
+
this.originalPkgMain = undefined;
|
|
56
82
|
this._init();
|
|
57
83
|
}
|
|
84
|
+
/** Register SIGINT/SIGTERM signal handlers to ensure child processes are closed and config is restored on exit */
|
|
58
85
|
_init() {
|
|
59
86
|
process_1.default.on('SIGINT', () => {
|
|
60
87
|
console.log(helpers_js_1.chalk.blue('[ee-bin] ') + 'Received SIGINT. Closing processes...');
|
|
@@ -65,28 +92,47 @@ class ServeProcess {
|
|
|
65
92
|
this._closeProcess();
|
|
66
93
|
});
|
|
67
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Close all child processes, restore package.json, then exit
|
|
97
|
+
*
|
|
98
|
+
* Flow: kill all child processes → restore pkgMain → sleep 500ms → process.exit(0)
|
|
99
|
+
* NOTE: The 500ms sleep is a compromise. The ideal approach would be to listen for each
|
|
100
|
+
* child process's exit event before exiting, but that's complex to implement (multi-process
|
|
101
|
+
* racing, nested processes, etc.). If a child process doesn't close in time, it may be orphaned.
|
|
102
|
+
*/
|
|
68
103
|
async _closeProcess() {
|
|
69
|
-
const currentProcess = [];
|
|
70
104
|
const keys = Object.keys(this.execProcess);
|
|
71
105
|
for (const key of keys) {
|
|
72
106
|
const p = this.execProcess[key];
|
|
73
107
|
if (p && p.pid) {
|
|
74
|
-
|
|
108
|
+
(0, tree_kill_1.default)(p.pid);
|
|
109
|
+
log('Kill %s server, pid: %d', helpers_js_1.chalk.blue(key), p.pid);
|
|
75
110
|
}
|
|
76
111
|
}
|
|
112
|
+
this._restorePkgMain();
|
|
77
113
|
await this.sleep(500);
|
|
78
|
-
for (const p of currentProcess) {
|
|
79
|
-
(0, tree_kill_1.default)(p.pid);
|
|
80
|
-
log('Kill %s server, pid: %d', helpers_js_1.chalk.blue(p.name), p.pid);
|
|
81
|
-
}
|
|
82
114
|
process_1.default.exit(0);
|
|
83
115
|
}
|
|
84
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Dev mode — start frontend dev server + Electron process
|
|
118
|
+
*
|
|
119
|
+
* Complete flow:
|
|
120
|
+
* 1. Set NODE_ENV=dev
|
|
121
|
+
* 2. Load config, parse command names to start
|
|
122
|
+
* 3. If electron command is included:
|
|
123
|
+
* a. First bundle Electron code (via esbuild)
|
|
124
|
+
* b. Switch package.json main field
|
|
125
|
+
* c. If electron.watch=true, watch electron/ directory for changes
|
|
126
|
+
* → on change: debounce → re-bundle → kill old process → re-spawn
|
|
127
|
+
* 4. multiExec starts all commands (frontend + Electron)
|
|
128
|
+
*/
|
|
129
|
+
async dev(options = {}) {
|
|
85
130
|
process_1.default.env.NODE_ENV = 'dev';
|
|
86
131
|
const { config, serve } = options;
|
|
87
132
|
const binCfg = (0, utils_js_1.loadConfig)(config);
|
|
88
133
|
const binCmd = 'dev';
|
|
89
|
-
const binCmdConfig =
|
|
134
|
+
const binCmdConfig = binCfg.dev;
|
|
135
|
+
// Default to starting all commands defined in dev config when none specified
|
|
90
136
|
let command = serve;
|
|
91
137
|
if (!command) {
|
|
92
138
|
command = Object.keys(binCmdConfig).join(',');
|
|
@@ -97,52 +143,64 @@ class ServeProcess {
|
|
|
97
143
|
command: command || '',
|
|
98
144
|
};
|
|
99
145
|
const cmds = (0, helpers_js_1.formatCmds)(command || '');
|
|
100
|
-
if (cmds.
|
|
146
|
+
if (cmds.includes('electron')) {
|
|
101
147
|
const electronConfig = binCmdConfig.electron;
|
|
102
|
-
|
|
103
|
-
this.
|
|
148
|
+
// Electron process needs bundled code, so bundle first before starting
|
|
149
|
+
await this.bundle(binCfg.build.electron);
|
|
150
|
+
this._switchPkgMain();
|
|
151
|
+
// Watch mode: monitor electron directory for changes, auto-rebuild + restart
|
|
104
152
|
if (electronConfig?.watch) {
|
|
105
153
|
let debounceTimer = null;
|
|
106
154
|
const cmd = 'electron';
|
|
107
|
-
const watcher = chokidar_1.
|
|
155
|
+
const watcher = (0, chokidar_1.watch)([ELECTRON_DIR], { persistent: true });
|
|
108
156
|
watcher.on('change', async (f) => {
|
|
109
157
|
console.log(helpers_js_1.chalk.blue('[ee-bin] [dev] ') + `File [${helpers_js_1.chalk.cyan(f)}] has been changed`);
|
|
158
|
+
// Debounce: rapid successive file changes only trigger one rebuild
|
|
110
159
|
if (debounceTimer) {
|
|
111
160
|
clearTimeout(debounceTimer);
|
|
112
161
|
}
|
|
113
162
|
debounceTimer = setTimeout(async () => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
163
|
+
try {
|
|
164
|
+
console.log(helpers_js_1.chalk.blue('[ee-bin] [dev] ') + `Restart ${cmd}`);
|
|
165
|
+
await this.bundle(binCfg.build.electron);
|
|
166
|
+
const subProcess = this.execProcess[cmd];
|
|
167
|
+
if (subProcess && subProcess.pid) {
|
|
168
|
+
// Kill old Electron process (SIGKILL for forced termination), then re-spawn on success
|
|
169
|
+
(0, tree_kill_1.default)(subProcess.pid, 'SIGKILL', (err) => {
|
|
170
|
+
if (err) {
|
|
171
|
+
console.log(helpers_js_1.chalk.red('[ee-bin] [dev] ') + `Restart failed, error: ${err}`);
|
|
172
|
+
process_1.default.exit(-1);
|
|
173
|
+
}
|
|
174
|
+
delete this.execProcess[cmd];
|
|
175
|
+
const onlyElectronOpt = {
|
|
176
|
+
binCmd,
|
|
177
|
+
binCmdConfig,
|
|
178
|
+
command: cmd,
|
|
179
|
+
};
|
|
180
|
+
this.multiExec(onlyElectronOpt);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
console.log(helpers_js_1.chalk.red('[ee-bin] [dev] ') + `Re-bundle failed: ${e instanceof Error ? e.message : e}`);
|
|
131
186
|
}
|
|
132
187
|
}, electronConfig.delay);
|
|
133
188
|
});
|
|
134
189
|
}
|
|
135
|
-
this.bundle(binCfg.build?.electron);
|
|
136
190
|
}
|
|
137
191
|
this.multiExec(opt);
|
|
138
192
|
}
|
|
139
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Production start — directly run the Electron process (no bundling)
|
|
195
|
+
* Prerequisite: the project has already been built via the build command
|
|
196
|
+
*/
|
|
197
|
+
async start(options = {}) {
|
|
140
198
|
process_1.default.env.NODE_ENV = 'prod';
|
|
141
199
|
const { config } = options;
|
|
142
200
|
const binCfg = (0, utils_js_1.loadConfig)(config);
|
|
143
201
|
const binCmd = 'start';
|
|
144
202
|
const binCmdConfig = {
|
|
145
|
-
start: binCfg
|
|
203
|
+
start: binCfg.start,
|
|
146
204
|
};
|
|
147
205
|
const opt = {
|
|
148
206
|
binCmd,
|
|
@@ -151,28 +209,45 @@ class ServeProcess {
|
|
|
151
209
|
};
|
|
152
210
|
this.multiExec(opt);
|
|
153
211
|
}
|
|
212
|
+
/** Helper: sleep for the specified number of milliseconds */
|
|
154
213
|
sleep(ms) {
|
|
155
214
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
156
215
|
}
|
|
157
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Build mode — bundle Electron code + execute platform build commands
|
|
218
|
+
*
|
|
219
|
+
* Complete flow:
|
|
220
|
+
* 1. Set NODE_ENV=prod (or user-specified environment)
|
|
221
|
+
* 2. If cmds includes 'electron': bundle first → remove electron from command list → switch pkgMain
|
|
222
|
+
* 3. multiExec executes remaining commands (e.g. frontend, win64, mac, etc.)
|
|
223
|
+
* 4. After build completes, restore package.json main field
|
|
224
|
+
*
|
|
225
|
+
* The 'electron' command only triggers bundling, not an Electron process launch,
|
|
226
|
+
* so it's removed from the command list and not processed by multiExec
|
|
227
|
+
*/
|
|
228
|
+
async build(options = {}) {
|
|
158
229
|
const { config, env } = options;
|
|
159
230
|
let { cmds } = options;
|
|
160
231
|
process_1.default.env.NODE_ENV = env || 'prod';
|
|
161
232
|
const binCfg = (0, utils_js_1.loadConfig)(config);
|
|
162
233
|
const binCmd = 'build';
|
|
163
|
-
|
|
234
|
+
// build.electron is BundleConfig; other keys are ExecConfig.
|
|
235
|
+
// electron is always removed from commands before passing to multiExec, so this cast is safe
|
|
236
|
+
const binCmdConfig = binCfg.build;
|
|
164
237
|
if (!cmds || cmds === '') {
|
|
165
238
|
const tip = helpers_js_1.chalk.bgYellow('Warning') + ' Please modify the ' + helpers_js_1.chalk.blue('build') + ' property in the bin file';
|
|
166
239
|
console.log(tip);
|
|
167
240
|
return;
|
|
168
241
|
}
|
|
169
242
|
const commands = (0, helpers_js_1.formatCmds)(cmds);
|
|
170
|
-
if (commands.
|
|
171
|
-
this.bundle(binCfg.build
|
|
243
|
+
if (commands.includes('electron')) {
|
|
244
|
+
await this.bundle(binCfg.build.electron);
|
|
245
|
+
// Remove 'electron' from the command list — it only triggers bundling,
|
|
246
|
+
// not a subprocess launch
|
|
172
247
|
const index = commands.indexOf('electron');
|
|
173
248
|
commands.splice(index, 1);
|
|
174
249
|
cmds = commands.join(',');
|
|
175
|
-
this._switchPkgMain(
|
|
250
|
+
this._switchPkgMain();
|
|
176
251
|
}
|
|
177
252
|
const opt = {
|
|
178
253
|
binCmd,
|
|
@@ -180,12 +255,15 @@ class ServeProcess {
|
|
|
180
255
|
command: cmds || '',
|
|
181
256
|
};
|
|
182
257
|
this.multiExec(opt);
|
|
258
|
+
// Restore package.json after build completes (dev mode restores on SIGINT/SIGTERM)
|
|
259
|
+
this._restorePkgMain();
|
|
183
260
|
}
|
|
261
|
+
/** Execute user-defined custom commands from the "exec" config section */
|
|
184
262
|
exec(options = {}) {
|
|
185
263
|
const { config, cmds } = options;
|
|
186
264
|
const binCfg = (0, utils_js_1.loadConfig)(config);
|
|
187
265
|
const binCmd = 'exec';
|
|
188
|
-
const binCmdConfig =
|
|
266
|
+
const binCmdConfig = binCfg.exec;
|
|
189
267
|
const opt = {
|
|
190
268
|
binCmd,
|
|
191
269
|
binCmdConfig,
|
|
@@ -193,6 +271,15 @@ class ServeProcess {
|
|
|
193
271
|
};
|
|
194
272
|
this.multiExec(opt);
|
|
195
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Execute multiple commands — iterate the command list and start a subprocess for each
|
|
276
|
+
*
|
|
277
|
+
* Design decisions:
|
|
278
|
+
* - Frontend file:// protocol is skipped in dev mode (frontend is already served via HTTP)
|
|
279
|
+
* - sync mode uses crossSpawnSync for blocking execution; result is not stored in execProcess (process already complete)
|
|
280
|
+
* - async mode uses crossSpawn for non-blocking execution; stored in execProcess for later kill
|
|
281
|
+
* - async processes listen for exit events; in dev mode, a message is logged when a process exits
|
|
282
|
+
*/
|
|
196
283
|
multiExec(opt) {
|
|
197
284
|
const { binCmd, binCmdConfig, command } = opt;
|
|
198
285
|
const commands = (0, helpers_js_1.formatCmds)(command || '');
|
|
@@ -201,36 +288,47 @@ class ServeProcess {
|
|
|
201
288
|
if (!cfg) {
|
|
202
289
|
continue;
|
|
203
290
|
}
|
|
291
|
+
// In dev mode, skip frontend startup when protocol is 'file://'
|
|
292
|
+
// (frontend files are already served via HTTP dev server, no separate file:// process needed)
|
|
204
293
|
if (binCmd === 'dev' && cmd === 'frontend' && cfg.protocol === 'file://') {
|
|
205
294
|
continue;
|
|
206
295
|
}
|
|
207
296
|
console.log(helpers_js_1.chalk.blue(`[ee-bin] [${binCmd}] `) + `Run ${helpers_js_1.chalk.green(cmd)} command`);
|
|
208
297
|
console.log(helpers_js_1.chalk.blue(`[ee-bin] [${binCmd}] `) + helpers_js_1.chalk.magenta('Config:'), JSON.stringify(cfg));
|
|
209
298
|
const execDir = path_1.default.join(process_1.default.cwd(), cfg.directory);
|
|
210
|
-
const execArgs =
|
|
299
|
+
const execArgs = (0, utils_js_1.toArray)(cfg.args);
|
|
211
300
|
const stdioOpt = cfg.stdio || 'inherit';
|
|
212
301
|
if (cfg.sync) {
|
|
213
|
-
|
|
302
|
+
// Sync execution: blocks until the command completes, no process tracking needed
|
|
303
|
+
const syncResult = (0, cross_spawn_1.sync)(cfg.cmd, execArgs, {
|
|
214
304
|
stdio: stdioOpt,
|
|
215
305
|
cwd: execDir,
|
|
216
|
-
maxBuffer:
|
|
306
|
+
maxBuffer: MAX_BUFFER,
|
|
217
307
|
});
|
|
308
|
+
if (syncResult.error) {
|
|
309
|
+
throw new Error(`[ee-bin] [${binCmd}] Command "${cfg.cmd}" failed to spawn: ${syncResult.error.message}`);
|
|
310
|
+
}
|
|
311
|
+
if (syncResult.status !== 0 && syncResult.status !== null) {
|
|
312
|
+
throw new Error(`[ee-bin] [${binCmd}] Command "${cfg.cmd} ${execArgs.join(' ')}" exited with code ${syncResult.status}`);
|
|
313
|
+
}
|
|
218
314
|
}
|
|
219
315
|
else {
|
|
220
|
-
|
|
316
|
+
// Async execution: starts a child process and tracks it for SIGINT/SIGTERM kill
|
|
317
|
+
const childProc = (0, cross_spawn_1.default)(cfg.cmd, execArgs, {
|
|
221
318
|
stdio: stdioOpt,
|
|
222
319
|
cwd: execDir,
|
|
223
|
-
maxBuffer:
|
|
320
|
+
maxBuffer: MAX_BUFFER,
|
|
224
321
|
});
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.execProcess[cmd].on('exit', () => {
|
|
322
|
+
this.execProcess[cmd] = childProc;
|
|
323
|
+
childProc.on('error', (err) => {
|
|
324
|
+
console.log(helpers_js_1.chalk.red(`[ee-bin] [${binCmd}] `) + `Command "${cmd}" failed to spawn: ${err.message}`);
|
|
325
|
+
delete this.execProcess[cmd];
|
|
326
|
+
});
|
|
327
|
+
childProc.on('exit', () => {
|
|
232
328
|
if (binCmd === 'dev') {
|
|
233
329
|
console.log(helpers_js_1.chalk.blue(`[ee-bin] [${binCmd}] `) + `The ${helpers_js_1.chalk.green(cmd)} process is exiting`);
|
|
330
|
+
// On Windows, Electron exit doesn't always terminate the parent process,
|
|
331
|
+
// so remind the user to press Ctrl+C
|
|
234
332
|
if (process_1.default.platform === 'win32' && cmd === 'electron') {
|
|
235
333
|
console.log(helpers_js_1.chalk.blue(`[ee-bin] [${binCmd}] `) + helpers_js_1.chalk.green('Press "CTRL+C" to exit'));
|
|
236
334
|
}
|
|
@@ -239,45 +337,272 @@ class ServeProcess {
|
|
|
239
337
|
console.log(helpers_js_1.chalk.blue(`[ee-bin] [${binCmd}] `) + `The ${helpers_js_1.chalk.green(cmd)} command has been executed and exited`);
|
|
240
338
|
});
|
|
241
339
|
}
|
|
340
|
+
console.log(helpers_js_1.chalk.blue(`[ee-bin] [${binCmd}] `) +
|
|
341
|
+
'The ' +
|
|
342
|
+
helpers_js_1.chalk.green(`${cmd}`) +
|
|
343
|
+
` command is ${cfg.sync ? 'run completed' : 'running'}`);
|
|
242
344
|
}
|
|
243
345
|
}
|
|
244
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Bundle Electron main process code
|
|
348
|
+
*
|
|
349
|
+
* Two modes:
|
|
350
|
+
* - 'bundle': Use esbuild + bundleRegistryPlugin to bundle into a single file
|
|
351
|
+
* - 'copy': Directly copy the entire electron/ directory to public/electron/
|
|
352
|
+
*
|
|
353
|
+
* Clears the output directory (rm outdir) before bundling to ensure a clean build
|
|
354
|
+
*/
|
|
355
|
+
async bundle(bundleConfig) {
|
|
245
356
|
if (!bundleConfig)
|
|
246
357
|
return;
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
(0, helpers_js_1.copyDirSync)(
|
|
358
|
+
const cwd = process_1.default.cwd();
|
|
359
|
+
const outdir = path_1.default.join(cwd, BUNDLE_DIR);
|
|
360
|
+
// Clean output directory to ensure fresh build results
|
|
361
|
+
(0, utils_js_1.rm)(outdir);
|
|
362
|
+
if (bundleConfig.bundleType === 'copy') {
|
|
363
|
+
(0, helpers_js_1.copyDirSync)(path_1.default.join(cwd, ELECTRON_DIR), outdir);
|
|
253
364
|
}
|
|
254
365
|
else {
|
|
255
|
-
|
|
256
|
-
const esbuildOptions = bundleConfig[type];
|
|
257
|
-
if (esbuildOptions) {
|
|
258
|
-
log('esbuild options:%O', esbuildOptions);
|
|
259
|
-
(0, esbuild_1.buildSync)(esbuildOptions);
|
|
260
|
-
}
|
|
366
|
+
await this._bundleWithRegistry(bundleConfig);
|
|
261
367
|
}
|
|
262
368
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
369
|
+
/**
|
|
370
|
+
* Resolve the esbuild options shared by the main bundle and the per-file copy transpile.
|
|
371
|
+
*
|
|
372
|
+
* Both the bundled main.js and the separately-transpiled jobs/copy files must use identical
|
|
373
|
+
* compilation settings, otherwise main process code and job code would diverge (e.g. main.js
|
|
374
|
+
* minified but jobs not, or different module format / target / define). This method centralizes
|
|
375
|
+
* everything that should be consistent; callers add only the mode-specific keys (bundle,
|
|
376
|
+
* entryPoints/outfile, externals, plugins, banner, logLevel).
|
|
377
|
+
*
|
|
378
|
+
* sourcemap auto mode: dev → inline (debuggable), prod → off (smaller output).
|
|
379
|
+
*/
|
|
380
|
+
_resolveBaseBuildOptions(bundleConfig) {
|
|
381
|
+
const isDev = process_1.default.env.NODE_ENV === 'dev' || process_1.default.env.NODE_ENV === 'local';
|
|
382
|
+
let sourcemap;
|
|
383
|
+
if (bundleConfig.sourcemap === 'inline' || bundleConfig.sourcemap === true) {
|
|
384
|
+
sourcemap = 'inline';
|
|
270
385
|
}
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
(0, utils_js_1.writeJsonSync)(pkgPath, pkg);
|
|
386
|
+
else if (bundleConfig.sourcemap === 'external') {
|
|
387
|
+
sourcemap = true;
|
|
274
388
|
}
|
|
275
389
|
else {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
390
|
+
sourcemap = isDev ? 'inline' : false;
|
|
391
|
+
}
|
|
392
|
+
const format = bundleConfig.format || 'cjs';
|
|
393
|
+
return {
|
|
394
|
+
platform: 'node',
|
|
395
|
+
target: 'node20',
|
|
396
|
+
format,
|
|
397
|
+
sourcemap,
|
|
398
|
+
minify: bundleConfig.minify ?? false,
|
|
399
|
+
keepNames: bundleConfig.keepNames ?? false,
|
|
400
|
+
...(bundleConfig.drop ? { drop: bundleConfig.drop } : {}),
|
|
401
|
+
...(bundleConfig.legalComments ? { legalComments: bundleConfig.legalComments } : {}),
|
|
402
|
+
define: {
|
|
403
|
+
...(bundleConfig.define || {}),
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Bundle Electron code using esbuild + registry plugin
|
|
409
|
+
*
|
|
410
|
+
* esbuild configuration strategy:
|
|
411
|
+
* - entryPoints: Virtual module 'app:bundle-entry' (generated by the plugin)
|
|
412
|
+
* - format: Default cjs (recommended for Electron), optional esm
|
|
413
|
+
* - sourcemap: Auto-inferred (dev→inline, prod→off), user can override
|
|
414
|
+
* - external: Framework externals (ee-core/electron/better-sqlite3 etc.) + user-defined
|
|
415
|
+
* - banner: Injects process.env.EE_BUNDLED = "true" so ee-core detects bundle mode
|
|
416
|
+
* - packages: 'external' tells esbuild to automatically exclude all node_modules packages
|
|
417
|
+
*
|
|
418
|
+
* Post-bundle steps:
|
|
419
|
+
* 1. Rename output file (app_bundle-entry.js → main.js)
|
|
420
|
+
* 2. Copy non-bundlable files (jobs directory, preload/bridge.js, user-defined copy targets)
|
|
421
|
+
*/
|
|
422
|
+
async _bundleWithRegistry(bundleConfig) {
|
|
423
|
+
const cwd = process_1.default.cwd();
|
|
424
|
+
const controllerDir = path_1.default.join(cwd, ELECTRON_DIR, 'controller');
|
|
425
|
+
const configDir = path_1.default.join(cwd, ELECTRON_DIR, 'config');
|
|
426
|
+
const mainJsPath = path_1.default.join(cwd, ELECTRON_DIR, 'main.js');
|
|
427
|
+
const mainTsPath = path_1.default.join(cwd, ELECTRON_DIR, 'main.ts');
|
|
428
|
+
// Detect TypeScript entry (affects esbuild resolution and output format inference)
|
|
429
|
+
const isTypeScript = fs_1.default.existsSync(mainTsPath);
|
|
430
|
+
const entryMain = isTypeScript ? mainTsPath : mainJsPath;
|
|
431
|
+
const outdir = path_1.default.join(cwd, BUNDLE_DIR);
|
|
432
|
+
const outfile = path_1.default.join(outdir, 'main.js');
|
|
433
|
+
// Framework internal externals: these packages must be loaded from node_modules at runtime,
|
|
434
|
+
// not bundled into main.js. Reasons:
|
|
435
|
+
// - ee-core: child_process.fork() needs its entry point as a real file on disk
|
|
436
|
+
// - electron/better-sqlite3: native modules that cannot be bundled by esbuild
|
|
437
|
+
// - pino-roll/pino-pretty: rely on fs operations that don't work after bundling
|
|
438
|
+
const frameworkExternal = [
|
|
439
|
+
'ee-core',
|
|
440
|
+
'ee-bin',
|
|
441
|
+
'electron',
|
|
442
|
+
'better-sqlite3',
|
|
443
|
+
'proxy-agent',
|
|
444
|
+
'pino-roll',
|
|
445
|
+
'pino-pretty',
|
|
446
|
+
];
|
|
447
|
+
const userExternal = bundleConfig.external || [];
|
|
448
|
+
const plugin = (0, bundle_registry_plugin_js_1.bundleRegistryPlugin)(controllerDir, entryMain, configDir);
|
|
449
|
+
const options = {
|
|
450
|
+
// Shared compilation settings (format/target/sourcemap/minify/keepNames/drop/legalComments/define)
|
|
451
|
+
// — kept identical to the per-file copy transpile so main.js and jobs/copy code never diverge
|
|
452
|
+
...this._resolveBaseBuildOptions(bundleConfig),
|
|
453
|
+
// Mode-specific: bundle everything reachable from the virtual entry into a single file
|
|
454
|
+
entryPoints: ['app:bundle-entry'],
|
|
455
|
+
bundle: true,
|
|
456
|
+
// packages: 'external' tells esbuild to treat all npm packages as external
|
|
457
|
+
// (already refined by the explicit external list above)
|
|
458
|
+
packages: 'external',
|
|
459
|
+
outdir,
|
|
460
|
+
external: [
|
|
461
|
+
...frameworkExternal,
|
|
462
|
+
...userExternal,
|
|
463
|
+
],
|
|
464
|
+
// Banner injects EE_BUNDLED marker: ee-core checks this value to use the registry
|
|
465
|
+
// instead of filesystem scanning when in bundle mode
|
|
466
|
+
banner: {
|
|
467
|
+
js: 'process.env.EE_BUNDLED = "true";',
|
|
468
|
+
},
|
|
469
|
+
plugins: [plugin],
|
|
470
|
+
logLevel: 'info',
|
|
471
|
+
};
|
|
472
|
+
log('_bundleWithRegistry options:%O', options);
|
|
473
|
+
await (0, esbuild_1.build)(options);
|
|
474
|
+
// esbuild replaces ':' in virtual module name 'app:bundle-entry' with '_',
|
|
475
|
+
// so the output file is named 'app_bundle-entry.js' — rename it to 'main.js'
|
|
476
|
+
const bundleEntryFile = path_1.default.join(outdir, 'app_bundle-entry.js');
|
|
477
|
+
if (fs_1.default.existsSync(bundleEntryFile)) {
|
|
478
|
+
fs_1.default.renameSync(bundleEntryFile, path_1.default.join(outdir, 'main.js'));
|
|
479
|
+
}
|
|
480
|
+
// Also rename the sourcemap file if it exists
|
|
481
|
+
const bundleEntryMap = path_1.default.join(outdir, 'app_bundle-entry.js.map');
|
|
482
|
+
if (fs_1.default.existsSync(bundleEntryMap)) {
|
|
483
|
+
fs_1.default.renameSync(bundleEntryMap, path_1.default.join(outdir, 'main.js.map'));
|
|
484
|
+
}
|
|
485
|
+
// Copy non-bundlable files (child_process.fork and BrowserWindow preload need separate files)
|
|
486
|
+
await this._copyUnbundledFiles(cwd, outdir, bundleConfig);
|
|
487
|
+
console.log(helpers_js_1.chalk.blue('[ee-bin] ') + `Bundle output: ${outfile}`);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Copy a directory or single file from electron/ to the bundle output WITH per-file transpilation.
|
|
491
|
+
*
|
|
492
|
+
* Script files (.ts/.js/.mts/.cts/.tsx/.jsx) are compiled to Node-loadable .js using
|
|
493
|
+
* esbuild with bundle:false, so their imports stay as runtime require()/import calls:
|
|
494
|
+
* - relative imports (./foo) resolve to the sibling transpiled .js
|
|
495
|
+
* - ee-core/* and other packages resolve from node_modules at runtime
|
|
496
|
+
* Non-script files (e.g. .json) are copied verbatim. Directory structure is preserved.
|
|
497
|
+
*
|
|
498
|
+
* @param src Absolute path to a source directory or file
|
|
499
|
+
* @param dest Absolute path to the destination directory (for a dir src) or file (for a file src)
|
|
500
|
+
* @param baseOptions Shared esbuild options from _resolveBaseBuildOptions — same compilation
|
|
501
|
+
* settings (format/target/minify/define/...) as the main bundle, so copied
|
|
502
|
+
* code stays consistent with main.js. bundle:false is forced for per-file output.
|
|
503
|
+
*/
|
|
504
|
+
async _transpileDir(src, dest, baseOptions) {
|
|
505
|
+
if (!fs_1.default.existsSync(src))
|
|
506
|
+
return;
|
|
507
|
+
const scriptExts = new Set(['.ts', '.js', '.mts', '.cts', '.tsx', '.jsx']);
|
|
508
|
+
const transpileFile = async (srcFile, destFile) => {
|
|
509
|
+
const ext = path_1.default.extname(srcFile);
|
|
510
|
+
if (!scriptExts.has(ext)) {
|
|
511
|
+
// Non-script asset (e.g. .json): copy verbatim
|
|
512
|
+
fs_1.default.mkdirSync(path_1.default.dirname(destFile), { recursive: true });
|
|
513
|
+
fs_1.default.copyFileSync(srcFile, destFile);
|
|
514
|
+
return;
|
|
280
515
|
}
|
|
516
|
+
// Output as sibling .js, preserving the directory structure. Per-file transpile (bundle:false)
|
|
517
|
+
// so imports stay as runtime require()/import calls resolved on disk.
|
|
518
|
+
await (0, esbuild_1.build)({
|
|
519
|
+
...baseOptions,
|
|
520
|
+
entryPoints: [srcFile],
|
|
521
|
+
outfile: destFile.slice(0, -ext.length) + '.js',
|
|
522
|
+
bundle: false,
|
|
523
|
+
logLevel: 'silent',
|
|
524
|
+
});
|
|
525
|
+
};
|
|
526
|
+
// Single file: dest is the target file path
|
|
527
|
+
if (fs_1.default.statSync(src).isFile()) {
|
|
528
|
+
await transpileFile(src, dest);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
// Directory: walk every file, preserving the relative structure under dest
|
|
532
|
+
const entries = (0, globby_1.globbySync)('**/*', { cwd: src, onlyFiles: true });
|
|
533
|
+
for (const rel of entries) {
|
|
534
|
+
await transpileFile(path_1.default.join(src, rel), path_1.default.join(dest, rel));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Copy non-bundlable files — two-tier strategy
|
|
539
|
+
*
|
|
540
|
+
* 1. preload/bridge.js (BrowserWindow preload script must be a separate file, loaded directly by Electron)
|
|
541
|
+
* 2. Copy targets: framework defaults (jobs/) + user-defined (bundleConfig.copy), all handled
|
|
542
|
+
* per-file by _transpileDir — script files transpiled to CJS .js (so Node's require()/
|
|
543
|
+
* child_process.fork() can load them), other files copied verbatim, structure preserved
|
|
544
|
+
*/
|
|
545
|
+
async _copyUnbundledFiles(cwd, outdir, bundleConfig) {
|
|
546
|
+
// Shared esbuild options (format/target/minify/define/...) so unbundled output stays
|
|
547
|
+
// consistent with main.js. Per-file transpile (bundle:false) is forced inside _transpileDir.
|
|
548
|
+
const baseOptions = this._resolveBaseBuildOptions(bundleConfig);
|
|
549
|
+
// preload/bridge.*: BrowserWindow's preload script is loaded directly from disk by Electron.
|
|
550
|
+
// It cannot be bundled into main.js (bundled path would be wrong, and Electron requires
|
|
551
|
+
// preload scripts to be separate files). The source may be .ts/.js/.mts/... — resolve whichever
|
|
552
|
+
// exists and transpile it to bridge.js (a plain copy would break for TypeScript sources).
|
|
553
|
+
const bridgeMatches = (0, globby_1.globbySync)('preload/bridge.{ts,js,mts,cts,tsx,jsx}', { cwd: path_1.default.join(cwd, ELECTRON_DIR) });
|
|
554
|
+
if (bridgeMatches.length > 0) {
|
|
555
|
+
const bridgeRel = bridgeMatches[0];
|
|
556
|
+
const bridgeSrc = path_1.default.join(cwd, ELECTRON_DIR, bridgeRel);
|
|
557
|
+
// dest mirrors the source basename so _transpileDir derives the sibling .js correctly
|
|
558
|
+
const bridgeDest = path_1.default.join(outdir, bridgeRel);
|
|
559
|
+
await this._transpileDir(bridgeSrc, bridgeDest, baseOptions);
|
|
560
|
+
}
|
|
561
|
+
// Copy targets kept out of main.js. jobs/ is a framework default (its files run in forked
|
|
562
|
+
// child processes loaded by ee-core's require()-based loader, which cannot execute .ts), then
|
|
563
|
+
// user-defined entries from bundleConfig.copy. De-duplicated so an explicit 'jobs' won't run twice.
|
|
564
|
+
// Script files are transpiled with the SAME esbuild options as main.js (format/target/minify/
|
|
565
|
+
// define/...), other files (assets, .json) copied verbatim — all handled per-file by _transpileDir.
|
|
566
|
+
const copyTargets = [...new Set(['jobs', ...(bundleConfig.copy || [])])];
|
|
567
|
+
for (const target of copyTargets) {
|
|
568
|
+
const src = path_1.default.join(cwd, ELECTRON_DIR, target);
|
|
569
|
+
const dest = path_1.default.join(outdir, target);
|
|
570
|
+
if (!fs_1.default.existsSync(src))
|
|
571
|
+
continue;
|
|
572
|
+
await this._transpileDir(src, dest, baseOptions);
|
|
573
|
+
console.log(helpers_js_1.chalk.blue('[ee-bin] ') + `Copied: ${dest}`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Switch package.json main field
|
|
578
|
+
*
|
|
579
|
+
* After bundling, Electron needs to point to ./public/electron/main.js instead of
|
|
580
|
+
* ./electron/main.js, because the bundle output is in the public/electron/ directory.
|
|
581
|
+
* The original value is saved before switching so it can be restored later.
|
|
582
|
+
*/
|
|
583
|
+
_switchPkgMain() {
|
|
584
|
+
const pkgPath = path_1.default.join(process_1.default.cwd(), PKG_PATH);
|
|
585
|
+
const pkg = (0, utils_js_1.readJsonSync)(pkgPath);
|
|
586
|
+
const bundleMainPath = BUNDLE_DIR + '/main.js';
|
|
587
|
+
if (pkg.main !== bundleMainPath) {
|
|
588
|
+
this.originalPkgMain = pkg.main;
|
|
589
|
+
pkg.main = bundleMainPath;
|
|
590
|
+
(0, utils_js_1.writeJsonSync)(pkgPath, pkg);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Restore package.json main field
|
|
595
|
+
*
|
|
596
|
+
* Restores the original main value after build completes or on SIGINT/SIGTERM,
|
|
597
|
+
* preventing package.json from being permanently modified (which would break dev mode)
|
|
598
|
+
*/
|
|
599
|
+
_restorePkgMain() {
|
|
600
|
+
if (this.originalPkgMain !== undefined) {
|
|
601
|
+
const pkgPath = path_1.default.join(process_1.default.cwd(), PKG_PATH);
|
|
602
|
+
const pkg = (0, utils_js_1.readJsonSync)(pkgPath);
|
|
603
|
+
pkg.main = this.originalPkgMain;
|
|
604
|
+
(0, utils_js_1.writeJsonSync)(pkgPath, pkg);
|
|
605
|
+
this.originalPkgMain = undefined;
|
|
281
606
|
}
|
|
282
607
|
}
|
|
283
608
|
}
|