@elementspanel/epanel-plugin-instance 1.0.5 → 1.0.7
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/daemon/backend/index.cjs
CHANGED
|
@@ -226,9 +226,26 @@ function commandStringToArray(cmd) {
|
|
|
226
226
|
|
|
227
227
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
228
228
|
exports.ChildProcessAdapter = void 0;
|
|
229
|
+
exports.instanceProcessHost = instanceProcessHost;
|
|
230
|
+
exports.spawnInstanceProcess = spawnInstanceProcess;
|
|
229
231
|
exports.waitForSpawn = waitForSpawn;
|
|
230
232
|
const events_1 = __webpack_require__("events");
|
|
231
233
|
const mcsmanager_common_1 = __webpack_require__("../common/src/index.ts");
|
|
234
|
+
const INSTANCE_HOST = Symbol.for("elementspanel.instance-process-host");
|
|
235
|
+
function instanceProcessHost() {
|
|
236
|
+
return globalThis[INSTANCE_HOST];
|
|
237
|
+
}
|
|
238
|
+
function spawnInstanceProcess(instance, fallback, file, args, options, metadata = {}) {
|
|
239
|
+
const host = instanceProcessHost();
|
|
240
|
+
return host
|
|
241
|
+
? host.spawn(instance.instanceUuid, file, args, options, {
|
|
242
|
+
startTimestamp: instance.startTimestamp,
|
|
243
|
+
startCount: instance.startCount,
|
|
244
|
+
autoRestartCount: instance.autoRestartCount,
|
|
245
|
+
...metadata
|
|
246
|
+
})
|
|
247
|
+
: fallback(file, args, options);
|
|
248
|
+
}
|
|
232
249
|
/** spawn() reports failures asynchronously, including a missing executable. */
|
|
233
250
|
function waitForSpawn(process) {
|
|
234
251
|
return new Promise((resolve, reject) => {
|
|
@@ -266,7 +283,9 @@ class ChildProcessAdapter extends events_1.EventEmitter {
|
|
|
266
283
|
super();
|
|
267
284
|
this.child = child;
|
|
268
285
|
this.pid = child.pid;
|
|
269
|
-
child.once("close", () => {
|
|
286
|
+
child.once("close", () => {
|
|
287
|
+
this.closed = true;
|
|
288
|
+
});
|
|
270
289
|
// close follows the final stdout/stderr data; exit can arrive before it.
|
|
271
290
|
child.on("close", this.onExit);
|
|
272
291
|
child.on("error", this.onError);
|
|
@@ -292,6 +311,9 @@ class ChildProcessAdapter extends events_1.EventEmitter {
|
|
|
292
311
|
if (this.pid)
|
|
293
312
|
return (0, mcsmanager_common_1.killProcess)(this.pid, this.child, signal);
|
|
294
313
|
}
|
|
314
|
+
snapshot(metadata) {
|
|
315
|
+
this.child.setMetadata?.(metadata);
|
|
316
|
+
}
|
|
295
317
|
destroy() {
|
|
296
318
|
if (this.disposed)
|
|
297
319
|
return;
|
|
@@ -326,6 +348,7 @@ class ChildProcessAdapter extends events_1.EventEmitter {
|
|
|
326
348
|
this.child.stdin?.destroy();
|
|
327
349
|
this.child.stdout?.destroy();
|
|
328
350
|
this.child.stderr?.destroy();
|
|
351
|
+
this.child.release?.();
|
|
329
352
|
}
|
|
330
353
|
}
|
|
331
354
|
}
|
|
@@ -1030,20 +1053,19 @@ class GeneralStartCommand extends start_1.default {
|
|
|
1030
1053
|
instance.println("INFO", (0, i18n_1.$t)("TXT_CODE_ba09da46", { name: runAsConfig.runAsName }));
|
|
1031
1054
|
}
|
|
1032
1055
|
// create child process
|
|
1033
|
-
const subProcess = (0, child_process_1.spawn
|
|
1056
|
+
const subProcess = (0, process_adapter_1.spawnInstanceProcess)(instance, child_process_1.spawn, commandExeFile, commandParameters, {
|
|
1034
1057
|
...runAsConfig,
|
|
1035
1058
|
cwd: instance.absoluteCwdPath(),
|
|
1036
1059
|
stdio: "pipe",
|
|
1037
1060
|
windowsHide: true,
|
|
1038
1061
|
env: instance.generateEnv(),
|
|
1039
|
-
// Do not detach the child process;
|
|
1040
|
-
// otherwise, an abnormal exit of the parent process may cause the child process to continue running,
|
|
1041
|
-
// leading to an abnormal instance state.
|
|
1042
1062
|
detached: false
|
|
1043
1063
|
});
|
|
1044
1064
|
const processAdapter = new process_adapter_1.ChildProcessAdapter(subProcess);
|
|
1045
1065
|
try {
|
|
1046
1066
|
await (0, process_adapter_1.waitForSpawn)(subProcess);
|
|
1067
|
+
processAdapter.pid = subProcess.pid;
|
|
1068
|
+
subProcess.setMetadata?.({ pid: subProcess.pid });
|
|
1047
1069
|
instance.started(processAdapter);
|
|
1048
1070
|
}
|
|
1049
1071
|
catch (error) {
|
|
@@ -1470,17 +1492,14 @@ class PtyStartCommand extends start_1.default {
|
|
|
1470
1492
|
}
|
|
1471
1493
|
instance.println("INFO", "> " + commandList.join(" "));
|
|
1472
1494
|
// create pty child process
|
|
1473
|
-
const subProcess = (0, child_process_1.spawn
|
|
1495
|
+
const subProcess = (0, process_adapter_1.spawnInstanceProcess)(instance, child_process_1.spawn, const_1.PTY_PATH, ptyParameter, {
|
|
1474
1496
|
...runAsConfig,
|
|
1475
1497
|
cwd: path_1.default.dirname(const_1.PTY_PATH),
|
|
1476
1498
|
stdio: "pipe",
|
|
1477
1499
|
windowsHide: true,
|
|
1478
1500
|
env: instance.generateEnv(),
|
|
1479
|
-
// Do not detach the child process;
|
|
1480
|
-
// otherwise, an abnormal exit of the parent process may cause the child process to continue running,
|
|
1481
|
-
// leading to an abnormal instance state.
|
|
1482
1501
|
detached: false
|
|
1483
|
-
});
|
|
1502
|
+
}, { pipeName });
|
|
1484
1503
|
const processAdapter = new GoPtyProcessAdapter(subProcess, subProcess.pid, pipeName);
|
|
1485
1504
|
try {
|
|
1486
1505
|
const [, config] = await Promise.all([
|
|
@@ -1488,6 +1507,7 @@ class PtyStartCommand extends start_1.default {
|
|
|
1488
1507
|
this.readPtySubProcessConfig(subProcess)
|
|
1489
1508
|
]);
|
|
1490
1509
|
processAdapter.pid = config.pid;
|
|
1510
|
+
subProcess.setMetadata?.({ pid: config.pid });
|
|
1491
1511
|
if (subProcess.exitCode !== null || subProcess.signalCode !== null)
|
|
1492
1512
|
throw new StartupError((0, i18n_1.$t)("TXT_CODE_pty_start.instanceStartErr"));
|
|
1493
1513
|
instance.started(processAdapter);
|
|
@@ -1547,6 +1567,17 @@ class AbsStartCommand extends command_1.default {
|
|
|
1547
1567
|
// Claim the state before the first await so two starts cannot both proceed.
|
|
1548
1568
|
instance.setLock(true);
|
|
1549
1569
|
instance.status(instance_1.default.STATUS_STARTING);
|
|
1570
|
+
const starting = this.start(instance);
|
|
1571
|
+
instance.starting = starting;
|
|
1572
|
+
try {
|
|
1573
|
+
return await starting;
|
|
1574
|
+
}
|
|
1575
|
+
finally {
|
|
1576
|
+
instance.starting = undefined;
|
|
1577
|
+
instance.setLock(false);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
async start(instance) {
|
|
1550
1581
|
try {
|
|
1551
1582
|
if (!fs_extra_1.default.existsSync(instance.absoluteCwdPath())) {
|
|
1552
1583
|
await fs_extra_1.default.mkdirs(instance.absoluteCwdPath());
|
|
@@ -1595,9 +1626,6 @@ class AbsStartCommand extends command_1.default {
|
|
|
1595
1626
|
instance.startTimestamp = 0;
|
|
1596
1627
|
instance.failure(error);
|
|
1597
1628
|
}
|
|
1598
|
-
finally {
|
|
1599
|
-
instance.setLock(false);
|
|
1600
|
-
}
|
|
1601
1629
|
}
|
|
1602
1630
|
}
|
|
1603
1631
|
exports["default"] = AbsStartCommand;
|
|
@@ -2138,6 +2166,7 @@ class Instance extends events_1.EventEmitter {
|
|
|
2138
2166
|
};
|
|
2139
2167
|
watchers = new Map();
|
|
2140
2168
|
process;
|
|
2169
|
+
starting;
|
|
2141
2170
|
outputLoopTask;
|
|
2142
2171
|
outputBuffer;
|
|
2143
2172
|
stopping;
|
|
@@ -2336,9 +2365,22 @@ class Instance extends events_1.EventEmitter {
|
|
|
2336
2365
|
}
|
|
2337
2366
|
// function that must be executed after the instance starts
|
|
2338
2367
|
// Trigger the open event and bind the data and exit events, etc.
|
|
2339
|
-
started(process) {
|
|
2340
|
-
this.startTimestamp = this.config.lastDatetime = Date.now();
|
|
2341
|
-
|
|
2368
|
+
started(process, previous) {
|
|
2369
|
+
this.startTimestamp = this.config.lastDatetime = previous?.startTimestamp || Date.now();
|
|
2370
|
+
if (previous) {
|
|
2371
|
+
this.startCount = previous.startCount;
|
|
2372
|
+
this.autoRestartCount = previous.autoRestartCount;
|
|
2373
|
+
}
|
|
2374
|
+
this.userRequestedStop = previous?.userRequestedStop ?? false;
|
|
2375
|
+
if (this.userRequestedStop)
|
|
2376
|
+
this.ignoreEventTaskOnce();
|
|
2377
|
+
process.snapshot?.({
|
|
2378
|
+
pid: Number(process.pid),
|
|
2379
|
+
startTimestamp: this.startTimestamp,
|
|
2380
|
+
startCount: this.startCount,
|
|
2381
|
+
autoRestartCount: this.autoRestartCount,
|
|
2382
|
+
userRequestedStop: this.userRequestedStop
|
|
2383
|
+
});
|
|
2342
2384
|
const outputCode = this.config.terminalOption.pty ? "utf-8" : this.config.oe;
|
|
2343
2385
|
process.on("data", (text) => {
|
|
2344
2386
|
this.pushOutput(iconv_lite_1.default.decode(text, outputCode));
|
|
@@ -2442,6 +2484,7 @@ class Instance extends events_1.EventEmitter {
|
|
|
2442
2484
|
this.stopOutputLoop();
|
|
2443
2485
|
this.reloading = true;
|
|
2444
2486
|
this.watchers.clear();
|
|
2487
|
+
await this.starting?.catch(() => { });
|
|
2445
2488
|
await this.lifeCycleTaskManager.clearLifeCycleTask();
|
|
2446
2489
|
}
|
|
2447
2490
|
async resumeAfterReload(dispatcher) {
|
|
@@ -2479,6 +2522,7 @@ class Instance extends events_1.EventEmitter {
|
|
|
2479
2522
|
}
|
|
2480
2523
|
setUserRequestedStop(value) {
|
|
2481
2524
|
this.userRequestedStop = value;
|
|
2525
|
+
this.process?.snapshot?.({ userRequestedStop: value });
|
|
2482
2526
|
}
|
|
2483
2527
|
getUserRequestedStop() {
|
|
2484
2528
|
return this.userRequestedStop;
|
|
@@ -2995,6 +3039,17 @@ function apply(ctx) {
|
|
|
2995
3039
|
let preserving = false;
|
|
2996
3040
|
let subsystem;
|
|
2997
3041
|
let disposeSchedules;
|
|
3042
|
+
let initializing;
|
|
3043
|
+
ctx.on("process/starting", async () => {
|
|
3044
|
+
if (!initializing)
|
|
3045
|
+
throw new Error("Instance services failed to initialize.");
|
|
3046
|
+
await initializing;
|
|
3047
|
+
await subsystem?.waitForTakeover();
|
|
3048
|
+
});
|
|
3049
|
+
ctx.on("process/restarting", async () => {
|
|
3050
|
+
disposeSchedules?.();
|
|
3051
|
+
await subsystem?.prepareForRestart();
|
|
3052
|
+
});
|
|
2998
3053
|
ctx.on("plugins/reloading", async () => {
|
|
2999
3054
|
const inventory = ctx.get("plugins")?.inventory();
|
|
3000
3055
|
preserving = inventory?.some((plugin) => plugin.id === "instance" && plugin.enabled) ?? true;
|
|
@@ -3009,7 +3064,11 @@ function apply(ctx) {
|
|
|
3009
3064
|
// are available on the next turn rather than synchronously in this function.
|
|
3010
3065
|
// Restoring instances before that point makes the legacy dispatcher read
|
|
3011
3066
|
// missing `presets` and `instanceLifecycle` services and emit warnings.
|
|
3012
|
-
ctx.inject(["tasks", "instanceLifecycle", "presets", "schedules"],
|
|
3067
|
+
ctx.inject(["tasks", "instanceLifecycle", "presets", "schedules"], (instanceCtx) => {
|
|
3068
|
+
initializing = initialize(instanceCtx);
|
|
3069
|
+
return initializing;
|
|
3070
|
+
});
|
|
3071
|
+
async function initialize(instanceCtx) {
|
|
3013
3072
|
// The modules are required only after the context has been set and the
|
|
3014
3073
|
// instance registries are ready. Keeping these synchronous requires keeps
|
|
3015
3074
|
// webpack's output self-contained and avoids a second copy of the context.
|
|
@@ -3063,7 +3122,12 @@ function apply(ctx) {
|
|
|
3063
3122
|
await disposeInstanceEvents?.();
|
|
3064
3123
|
InstanceControlSubsystem.dispose();
|
|
3065
3124
|
router_1.routerApp.dispose();
|
|
3066
|
-
if (
|
|
3125
|
+
if (runtime.restarting) {
|
|
3126
|
+
// The launcher owns general/PTY processes; Docker is reattached at
|
|
3127
|
+
// startup. Disposing adapters here would terminate those live owners.
|
|
3128
|
+
await InstanceSubsystem.prepareForRestart();
|
|
3129
|
+
}
|
|
3130
|
+
else if ((preserving || recovering) && !runtime.stopping && ctx.root.scope.isActive) {
|
|
3067
3131
|
await InstanceSubsystem.suspendForReload();
|
|
3068
3132
|
retain();
|
|
3069
3133
|
}
|
|
@@ -3161,7 +3225,7 @@ function apply(ctx) {
|
|
|
3161
3225
|
// Also allow recovery by enabling/retrying only the instance plugin.
|
|
3162
3226
|
instanceCtx.on("ready", resume);
|
|
3163
3227
|
}
|
|
3164
|
-
}
|
|
3228
|
+
}
|
|
3165
3229
|
}
|
|
3166
3230
|
|
|
3167
3231
|
|
|
@@ -5551,14 +5615,19 @@ function setPluginContext(value) {
|
|
|
5551
5615
|
context: value,
|
|
5552
5616
|
settings: value.settings,
|
|
5553
5617
|
storage: value.storage,
|
|
5554
|
-
stopping: false
|
|
5618
|
+
stopping: false,
|
|
5619
|
+
restarting: false
|
|
5555
5620
|
};
|
|
5556
5621
|
host[INSTANCE_RUNTIME] = runtime = state;
|
|
5622
|
+
host.on("process/restarting", () => {
|
|
5623
|
+
state.restarting = true;
|
|
5624
|
+
});
|
|
5557
5625
|
host.on("dispose", async () => {
|
|
5558
5626
|
state.stopping = true;
|
|
5559
5627
|
// A replacement may fail before it can claim the retained processes.
|
|
5560
5628
|
// The daemon still owns them and must stop them during real shutdown.
|
|
5561
|
-
|
|
5629
|
+
if (!state.restarting)
|
|
5630
|
+
await state.handover?.shutdown();
|
|
5562
5631
|
state.handover = undefined;
|
|
5563
5632
|
});
|
|
5564
5633
|
}
|
|
@@ -7356,6 +7425,7 @@ const sleep_1 = __webpack_require__("../../plugins/instance/daemon/src/backend/u
|
|
|
7356
7425
|
const log_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/service/log.ts"));
|
|
7357
7426
|
const system_instance_control_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/service/system_instance_control.ts"));
|
|
7358
7427
|
const takeover_container_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/service/takeover_container.ts"));
|
|
7428
|
+
const takeover_processes_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/service/takeover_processes.ts"));
|
|
7359
7429
|
class InstanceSubsystem extends events_1.default {
|
|
7360
7430
|
GLOBAL_INSTANCE = "__MCSM_GLOBAL_INSTANCE__";
|
|
7361
7431
|
GLOBAL_INSTANCE_UUID = "global0001";
|
|
@@ -7464,14 +7534,26 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7464
7534
|
processType: globalConfig.processType
|
|
7465
7535
|
}, true, // allow persistence
|
|
7466
7536
|
this.GLOBAL_INSTANCE_UUID);
|
|
7467
|
-
this.takeover = (
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7537
|
+
this.takeover = (async () => {
|
|
7538
|
+
await (0, takeover_processes_1.default)(() => !this.startupCancelled);
|
|
7539
|
+
try {
|
|
7540
|
+
await (0, takeover_container_1.default)(() => !this.startupCancelled);
|
|
7471
7541
|
}
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7542
|
+
catch (error) {
|
|
7543
|
+
const reason = error.message || error;
|
|
7544
|
+
if (typeof reason == "string" && reason.includes("connect ENOENT"))
|
|
7545
|
+
return;
|
|
7546
|
+
log_1.default.error(`${(0, i18n_1.$t)("TXT_CODE_8d4c8f7e")}: ${reason}`);
|
|
7547
|
+
}
|
|
7548
|
+
})();
|
|
7549
|
+
void this.takeover
|
|
7550
|
+
.then(() => {
|
|
7551
|
+
// A worker replacement must also preserve intentionally stopped
|
|
7552
|
+
// instances, even when their next full launcher startup is automatic.
|
|
7553
|
+
if (process.env.EPANEL_RESTARTED !== "1")
|
|
7554
|
+
return this.autoStart();
|
|
7555
|
+
})
|
|
7556
|
+
.catch((error) => log_1.default.error(error));
|
|
7475
7557
|
}
|
|
7476
7558
|
createInstance(cfg, persistence = true, uuid) {
|
|
7477
7559
|
const newUuid = uuid || (0, uuid_1.v4)().replace(/-/gim, "");
|
|
@@ -7546,6 +7628,22 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7546
7628
|
await Promise.all([...this.instances.values()].map((instance) => instance.suspendForReload()));
|
|
7547
7629
|
await this.takeover;
|
|
7548
7630
|
}
|
|
7631
|
+
async waitForTakeover() {
|
|
7632
|
+
await this.takeover;
|
|
7633
|
+
}
|
|
7634
|
+
async prepareForRestart() {
|
|
7635
|
+
await this.suspendForReload();
|
|
7636
|
+
for (const instance of this.instances.values()) {
|
|
7637
|
+
system_storage_1.default.store("InstanceConfig", instance.instanceUuid, instance.config);
|
|
7638
|
+
instance.process?.snapshot?.({
|
|
7639
|
+
pid: Number(instance.process.pid),
|
|
7640
|
+
startTimestamp: instance.startTimestamp,
|
|
7641
|
+
startCount: instance.startCount,
|
|
7642
|
+
autoRestartCount: instance.autoRestartCount,
|
|
7643
|
+
userRequestedStop: instance.getUserRequestedStop()
|
|
7644
|
+
});
|
|
7645
|
+
}
|
|
7646
|
+
}
|
|
7549
7647
|
/** Transfer the existing owners, including ordinary child/PTY/Docker handles. */
|
|
7550
7648
|
adoptInstances(instances) {
|
|
7551
7649
|
this.startupCancelled = true;
|
|
@@ -8131,6 +8229,46 @@ async function takeoverContainer(isActive = () => true) {
|
|
|
8131
8229
|
}
|
|
8132
8230
|
|
|
8133
8231
|
|
|
8232
|
+
/***/ },
|
|
8233
|
+
|
|
8234
|
+
/***/ "../../plugins/instance/daemon/src/backend/service/takeover_processes.ts"
|
|
8235
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
8236
|
+
|
|
8237
|
+
"use strict";
|
|
8238
|
+
|
|
8239
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8240
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8241
|
+
};
|
|
8242
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
8243
|
+
exports["default"] = takeoverProcesses;
|
|
8244
|
+
const process_adapter_1 = __webpack_require__("../../plugins/instance/daemon/src/backend/entity/commands/base/process_adapter.ts");
|
|
8245
|
+
const pty_start_1 = __webpack_require__("../../plugins/instance/daemon/src/backend/entity/commands/pty/pty_start.ts");
|
|
8246
|
+
const system_instance_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/service/system_instance.ts"));
|
|
8247
|
+
/** Reattach the launcher's live pipes; do not execute the instance start command. */
|
|
8248
|
+
async function takeoverProcesses(isActive = () => true) {
|
|
8249
|
+
const host = (0, process_adapter_1.instanceProcessHost)();
|
|
8250
|
+
if (!host)
|
|
8251
|
+
return;
|
|
8252
|
+
for (const snapshot of await host.list()) {
|
|
8253
|
+
if (!isActive())
|
|
8254
|
+
return;
|
|
8255
|
+
const instance = system_instance_1.default.getInstance(snapshot.id);
|
|
8256
|
+
const child = host.connect(snapshot);
|
|
8257
|
+
const { pipeName, pid } = snapshot.metadata;
|
|
8258
|
+
const adapter = pipeName
|
|
8259
|
+
? new pty_start_1.GoPtyProcessAdapter(child, pid, pipeName)
|
|
8260
|
+
: new process_adapter_1.ChildProcessAdapter(child);
|
|
8261
|
+
if (!instance || instance.process) {
|
|
8262
|
+
await adapter.destroy();
|
|
8263
|
+
continue;
|
|
8264
|
+
}
|
|
8265
|
+
instance.started(adapter, snapshot.metadata);
|
|
8266
|
+
if (pipeName)
|
|
8267
|
+
adapter.attachOutput();
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
|
|
8271
|
+
|
|
8134
8272
|
/***/ },
|
|
8135
8273
|
|
|
8136
8274
|
/***/ "../../plugins/instance/daemon/src/backend/service/version_adapter.ts"
|
|
@@ -8634,6 +8772,7 @@ Object.defineProperty(exports, "arrayUnique", ({ enumerable: true, get: function
|
|
|
8634
8772
|
__exportStar(__webpack_require__("../common/src/plugin_contract.ts"), exports);
|
|
8635
8773
|
__exportStar(__webpack_require__("../common/src/plugin_overrides.ts"), exports);
|
|
8636
8774
|
__exportStar(__webpack_require__("../common/src/plugin_lifecycle.ts"), exports);
|
|
8775
|
+
__exportStar(__webpack_require__("../common/src/process_restart.ts"), exports);
|
|
8637
8776
|
__exportStar(__webpack_require__("../common/src/plugin_revision.ts"), exports);
|
|
8638
8777
|
__exportStar(__webpack_require__("../common/src/plugin_modules.ts"), exports);
|
|
8639
8778
|
__exportStar(__webpack_require__("../common/src/plugin_watch.ts"), exports);
|
|
@@ -9529,6 +9668,39 @@ async function loadPluginModule(entry, directory, revision) {
|
|
|
9529
9668
|
|
|
9530
9669
|
"use strict";
|
|
9531
9670
|
|
|
9671
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9672
|
+
if (k2 === undefined) k2 = k;
|
|
9673
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9674
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9675
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9676
|
+
}
|
|
9677
|
+
Object.defineProperty(o, k2, desc);
|
|
9678
|
+
}) : (function(o, m, k, k2) {
|
|
9679
|
+
if (k2 === undefined) k2 = k;
|
|
9680
|
+
o[k2] = m[k];
|
|
9681
|
+
}));
|
|
9682
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
9683
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
9684
|
+
}) : function(o, v) {
|
|
9685
|
+
o["default"] = v;
|
|
9686
|
+
});
|
|
9687
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
9688
|
+
var ownKeys = function(o) {
|
|
9689
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
9690
|
+
var ar = [];
|
|
9691
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
9692
|
+
return ar;
|
|
9693
|
+
};
|
|
9694
|
+
return ownKeys(o);
|
|
9695
|
+
};
|
|
9696
|
+
return function (mod) {
|
|
9697
|
+
if (mod && mod.__esModule) return mod;
|
|
9698
|
+
var result = {};
|
|
9699
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
9700
|
+
__setModuleDefault(result, mod);
|
|
9701
|
+
return result;
|
|
9702
|
+
};
|
|
9703
|
+
})();
|
|
9532
9704
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9533
9705
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9534
9706
|
};
|
|
@@ -9547,6 +9719,8 @@ const path_1 = __importDefault(__webpack_require__("path"));
|
|
|
9547
9719
|
const module_1 = __webpack_require__("module");
|
|
9548
9720
|
const plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
|
|
9549
9721
|
const plugin_contract_1 = __webpack_require__("../common/src/plugin_contract.ts");
|
|
9722
|
+
// @ts-ignore Shared by the source runtime and the published npm launcher.
|
|
9723
|
+
const npmPlugins = __importStar(__webpack_require__("../../scripts/runtime/npm-plugins.cjs"));
|
|
9550
9724
|
const PLUGIN_ID = /^[a-z][a-z0-9_-]{1,63}$/;
|
|
9551
9725
|
const PACKAGE_NAME = /^(?:@[a-z0-9_.-]+\/)?epanel-plugin-[a-z0-9_.-]+$/;
|
|
9552
9726
|
const VERSION = /^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,63}$/;
|
|
@@ -9589,9 +9763,18 @@ function currentProjectRoot(packageRoot = currentPackageRoot()) {
|
|
|
9589
9763
|
}
|
|
9590
9764
|
function npmPluginRoots(options = {}) {
|
|
9591
9765
|
const packageRoot = path_1.default.resolve(options.packageRoot ?? currentPackageRoot());
|
|
9766
|
+
const projectRoot = path_1.default.resolve(options.projectRoot ?? currentProjectRoot(packageRoot));
|
|
9767
|
+
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
9768
|
+
const side = options.side ||
|
|
9769
|
+
stringValue(objectValue(host?.elementspanel)?.side) ||
|
|
9770
|
+
path_1.default.basename(packageRoot);
|
|
9592
9771
|
return {
|
|
9593
9772
|
packageRoot,
|
|
9594
|
-
projectRoot
|
|
9773
|
+
projectRoot,
|
|
9774
|
+
dependencyRoot: path_1.default.dirname(npmPlugins.projectManifestFile(packageRoot, side, {
|
|
9775
|
+
projectRoot,
|
|
9776
|
+
runtimeDirectory: process.env.EPANEL_RUNTIME_ROOT
|
|
9777
|
+
}))
|
|
9595
9778
|
};
|
|
9596
9779
|
}
|
|
9597
9780
|
function projectManifestFile(projectRoot) {
|
|
@@ -9619,15 +9802,10 @@ function pluginRecord(pluginId, name, source, packageRoot, directory, folder, ve
|
|
|
9619
9802
|
function readPackageEntry(packageRoot, name) {
|
|
9620
9803
|
// Read package boundaries directly: Node caches require.resolve() paths even
|
|
9621
9804
|
// after npm moves an installation between nested and hoisted node_modules.
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
.map((directory) => path_1.default.join(directory, name, "package.json"))
|
|
9627
|
-
.find((filename) => fs_extra_1.default.existsSync(filename));
|
|
9628
|
-
if (manifestPath)
|
|
9629
|
-
break;
|
|
9630
|
-
}
|
|
9805
|
+
const localRequire = (0, module_1.createRequire)(path_1.default.join(packageRoot, "package.json"));
|
|
9806
|
+
let manifestPath = (localRequire.resolve.paths(name) || [])
|
|
9807
|
+
.map((directory) => path_1.default.join(directory, name, "package.json"))
|
|
9808
|
+
.find((filename) => fs_extra_1.default.existsSync(filename));
|
|
9631
9809
|
if (!manifestPath)
|
|
9632
9810
|
throw new Error(`Missing npm plugin package: ${name}.`);
|
|
9633
9811
|
manifestPath = fs_extra_1.default.realpathSync(manifestPath);
|
|
@@ -9674,83 +9852,26 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
|
|
|
9674
9852
|
/**
|
|
9675
9853
|
* Every npm plugin the runtime can load for a side, built-in and project-installed.
|
|
9676
9854
|
*
|
|
9677
|
-
* Built-in package names
|
|
9678
|
-
*
|
|
9679
|
-
* A project package that does not describe the requested side is skipped instead of failing, which
|
|
9680
|
-
* is what lets a two-sided project share one dependency list between sides.
|
|
9855
|
+
* Built-in package names reserve their plugin ids. The side manifest selects
|
|
9856
|
+
* the installed versions; a package without that side is skipped.
|
|
9681
9857
|
*/
|
|
9682
9858
|
function listNpmPlugins(side, options = {}) {
|
|
9683
|
-
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
9684
|
-
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
9685
|
-
if (!host)
|
|
9686
|
-
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
9687
|
-
const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
|
|
9688
|
-
? null
|
|
9689
|
-
: readProjectManifest(projectRoot);
|
|
9690
|
-
const hostMappings = mappingsOf(host);
|
|
9691
|
-
const projectMappings = { ...mappingsOf(project) };
|
|
9692
|
-
for (const name of Object.keys(dependenciesOf(project))) {
|
|
9693
|
-
if (!PACKAGE_NAME.test(name) || Object.values(projectMappings).includes(name))
|
|
9694
|
-
continue;
|
|
9695
|
-
try {
|
|
9696
|
-
const entry = readPackageEntry(projectRoot, name);
|
|
9697
|
-
const id = stringValue(objectValue(entry.metadata.elementspanel)?.id);
|
|
9698
|
-
if (PLUGIN_ID.test(id) && projectMappings[id] === undefined)
|
|
9699
|
-
projectMappings[id] = name;
|
|
9700
|
-
}
|
|
9701
|
-
catch {
|
|
9702
|
-
/* npm may still be writing the package. */
|
|
9703
|
-
}
|
|
9704
|
-
}
|
|
9705
|
-
const mappings = new Map();
|
|
9706
|
-
for (const [pluginId, name] of Object.entries(projectMappings)) {
|
|
9707
|
-
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
9708
|
-
continue;
|
|
9709
|
-
mappings.set(pluginId, { name, source: "project" });
|
|
9710
|
-
}
|
|
9711
|
-
for (const [pluginId, name] of Object.entries(hostMappings)) {
|
|
9712
|
-
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
9713
|
-
continue;
|
|
9714
|
-
if (dependenciesOf(host)[name] === undefined)
|
|
9715
|
-
continue;
|
|
9716
|
-
mappings.set(pluginId, {
|
|
9717
|
-
name,
|
|
9718
|
-
source: dependenciesOf(project)[name] === undefined ? "bundled" : "project"
|
|
9719
|
-
});
|
|
9720
|
-
}
|
|
9721
|
-
const hostDependencies = dependenciesOf(host);
|
|
9722
|
-
const projectDependencies = dependenciesOf(project);
|
|
9723
|
-
const folders = new Set();
|
|
9859
|
+
const { packageRoot, projectRoot, dependencyRoot } = npmPluginRoots({ ...options, side });
|
|
9724
9860
|
const records = [];
|
|
9725
|
-
for (const
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
entry = readPackageEntry(mapping.source === "project" ? projectRoot : packageRoot, mapping.name);
|
|
9735
|
-
}
|
|
9736
|
-
catch (error) {
|
|
9737
|
-
if (options.skipInvalid)
|
|
9738
|
-
continue;
|
|
9739
|
-
throw error;
|
|
9740
|
-
}
|
|
9741
|
-
const record = recordFromPackage(pluginId, mapping.name, mapping.source, entry.root, entry.metadata, side);
|
|
9861
|
+
for (const item of npmPlugins.resolveNpmPlugins(packageRoot, side, {
|
|
9862
|
+
projectRoot,
|
|
9863
|
+
runtimeDirectory: dependencyRoot,
|
|
9864
|
+
skipInvalid: options.skipInvalid
|
|
9865
|
+
})) {
|
|
9866
|
+
const metadata = readJson(path_1.default.join(item.packageRoot, "package.json"));
|
|
9867
|
+
if (!metadata)
|
|
9868
|
+
continue;
|
|
9869
|
+
const record = recordFromPackage(item.id, item.name, item.source, item.packageRoot, metadata, side);
|
|
9742
9870
|
if (!record) {
|
|
9743
|
-
if (options.skipInvalid || mapping.source === "project")
|
|
9744
|
-
continue;
|
|
9745
|
-
throw new Error(`Invalid ${side ?? "plugin"} entry in npm plugin ${mapping.name}.`);
|
|
9746
|
-
}
|
|
9747
|
-
const folder = record.folder;
|
|
9748
|
-
if (folders.has(folder)) {
|
|
9749
9871
|
if (options.skipInvalid)
|
|
9750
9872
|
continue;
|
|
9751
|
-
throw new Error(`
|
|
9873
|
+
throw new Error(`Invalid ${side ?? "plugin"} entry in npm plugin ${item.name}.`);
|
|
9752
9874
|
}
|
|
9753
|
-
folders.add(folder);
|
|
9754
9875
|
records.push(record);
|
|
9755
9876
|
}
|
|
9756
9877
|
return records;
|
|
@@ -9847,10 +9968,9 @@ async function acquireProjectLock(projectRoot) {
|
|
|
9847
9968
|
}
|
|
9848
9969
|
}
|
|
9849
9970
|
}
|
|
9850
|
-
function npmPluginOperationPending() {
|
|
9851
|
-
const {
|
|
9852
|
-
return
|
|
9853
|
-
[packageRoot, projectRoot].some((root) => fs_extra_1.default.existsSync(path_1.default.join(root, LOCK_NAME))));
|
|
9971
|
+
function npmPluginOperationPending(side) {
|
|
9972
|
+
const { dependencyRoot } = npmPluginRoots({ side });
|
|
9973
|
+
return operations.size > 0 || fs_extra_1.default.existsSync(path_1.default.join(dependencyRoot, LOCK_NAME));
|
|
9854
9974
|
}
|
|
9855
9975
|
function withProjectLock(projectRoot, operation) {
|
|
9856
9976
|
const key = process.platform === "win32" ? projectRoot.toLowerCase() : projectRoot;
|
|
@@ -9885,18 +10005,15 @@ async function installNpmPlugin(options) {
|
|
|
9885
10005
|
throw new Error(`Invalid plugin id: ${options.pluginId}`);
|
|
9886
10006
|
if (!VERSION.test(version))
|
|
9887
10007
|
throw new Error(`Invalid plugin version: ${version}`);
|
|
9888
|
-
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
9889
|
-
return withProjectLock(
|
|
10008
|
+
const { packageRoot, projectRoot, dependencyRoot } = npmPluginRoots(options);
|
|
10009
|
+
return withProjectLock(dependencyRoot, async () => {
|
|
9890
10010
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
9891
10011
|
if (!host)
|
|
9892
10012
|
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
9893
|
-
const hostId = Object.entries(mappingsOf(host)).find(([, value]) => value === name
|
|
9894
|
-
const targetRoot =
|
|
9895
|
-
? packageRoot
|
|
9896
|
-
: projectRoot;
|
|
10013
|
+
const hostId = Object.entries(mappingsOf(host)).find(([, value]) => value === name)?.[0];
|
|
10014
|
+
const targetRoot = dependencyRoot;
|
|
9897
10015
|
if (options.pluginId &&
|
|
9898
10016
|
mappingsOf(host)[options.pluginId] &&
|
|
9899
|
-
dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
|
|
9900
10017
|
mappingsOf(host)[options.pluginId] !== name)
|
|
9901
10018
|
throw new Error(`BUNDLED:${options.pluginId}`);
|
|
9902
10019
|
const original = readProjectManifest(targetRoot);
|
|
@@ -9925,24 +10042,33 @@ async function installNpmPlugin(options) {
|
|
|
9925
10042
|
"--save-exact",
|
|
9926
10043
|
"--no-fund",
|
|
9927
10044
|
"--no-audit",
|
|
9928
|
-
"--ignore-scripts"
|
|
10045
|
+
"--ignore-scripts",
|
|
10046
|
+
"--workspaces=false"
|
|
9929
10047
|
], targetRoot);
|
|
9930
10048
|
const entry = readPackageEntry(targetRoot, name);
|
|
9931
10049
|
const pluginId = options.pluginId || stringValue(objectValue(entry.metadata.elementspanel)?.id);
|
|
9932
10050
|
if (!PLUGIN_ID.test(pluginId))
|
|
9933
10051
|
throw new Error(`Invalid plugin id in ${name}.`);
|
|
9934
|
-
if (mappingsOf(host)[pluginId] &&
|
|
9935
|
-
dependenciesOf(host)[String(mappingsOf(host)[pluginId])] !== undefined &&
|
|
9936
|
-
mappingsOf(host)[pluginId] !== name)
|
|
10052
|
+
if (mappingsOf(host)[pluginId] && mappingsOf(host)[pluginId] !== name)
|
|
9937
10053
|
throw new Error(`Plugin id ${pluginId} belongs to a bundled dependency.`);
|
|
9938
10054
|
if (hostId && pluginId !== hostId)
|
|
9939
10055
|
throw new Error(`The package ${name} changed its plugin id from ${hostId} to ${pluginId}.`);
|
|
9940
10056
|
const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
|
|
9941
10057
|
if (mapped !== undefined && mapped !== name)
|
|
9942
10058
|
throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
|
|
9943
|
-
const record = recordFromPackage(pluginId, name,
|
|
10059
|
+
const record = recordFromPackage(pluginId, name, hostId ? "bundled" : "project", entry.root, entry.metadata, side);
|
|
9944
10060
|
if (!record)
|
|
9945
10061
|
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
10062
|
+
if (side === "panel") {
|
|
10063
|
+
const expected = objectValue(objectValue(entry.metadata.elementspanel)?.panel)
|
|
10064
|
+
?.frontendAssets;
|
|
10065
|
+
const available = objectValue(host.elementspanel)?.frontendAssets;
|
|
10066
|
+
if ((hostId || expected !== undefined) &&
|
|
10067
|
+
(!Array.isArray(expected) ||
|
|
10068
|
+
!Array.isArray(available) ||
|
|
10069
|
+
expected.some((file) => typeof file !== "string" || !available.includes(file))))
|
|
10070
|
+
throw new Error(`Frontend assets in ${name} do not match this panel. Update the panel or install a matching plugin version.`);
|
|
10071
|
+
}
|
|
9946
10072
|
if (/^[0-9]/.test(version) && record.version !== version)
|
|
9947
10073
|
throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
|
|
9948
10074
|
if (!process.env.EPANEL_PACKAGE_ROOT &&
|
|
@@ -9986,7 +10112,8 @@ async function installNpmPlugin(options) {
|
|
|
9986
10112
|
"--save",
|
|
9987
10113
|
"--no-fund",
|
|
9988
10114
|
"--no-audit",
|
|
9989
|
-
"--ignore-scripts"
|
|
10115
|
+
"--ignore-scripts",
|
|
10116
|
+
"--workspaces=false"
|
|
9990
10117
|
]
|
|
9991
10118
|
: [
|
|
9992
10119
|
"--prefix",
|
|
@@ -9995,7 +10122,8 @@ async function installNpmPlugin(options) {
|
|
|
9995
10122
|
name,
|
|
9996
10123
|
"--no-fund",
|
|
9997
10124
|
"--no-audit",
|
|
9998
|
-
"--ignore-scripts"
|
|
10125
|
+
"--ignore-scripts",
|
|
10126
|
+
"--workspaces=false"
|
|
9999
10127
|
], targetRoot);
|
|
10000
10128
|
}
|
|
10001
10129
|
catch (rollback) {
|
|
@@ -10019,22 +10147,29 @@ async function uninstallNpmPlugin(pluginId, options = {}) {
|
|
|
10019
10147
|
throw new Error(`Invalid plugin id: ${pluginId}`);
|
|
10020
10148
|
if (isEssentialNpmPlugin(pluginId))
|
|
10021
10149
|
throw new Error(`The essential plugin "${pluginId}" cannot be uninstalled.`);
|
|
10022
|
-
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
10023
|
-
return withProjectLock(
|
|
10024
|
-
const
|
|
10025
|
-
const bundledName = mappingsOf(host)[pluginId];
|
|
10026
|
-
const targetRoot = typeof bundledName === "string" &&
|
|
10027
|
-
dependenciesOf(host)[bundledName] !== undefined &&
|
|
10028
|
-
dependenciesOf(readProjectManifest(projectRoot))[bundledName] === undefined
|
|
10029
|
-
? packageRoot
|
|
10030
|
-
: projectRoot;
|
|
10150
|
+
const { packageRoot, projectRoot, dependencyRoot } = npmPluginRoots(options);
|
|
10151
|
+
return withProjectLock(dependencyRoot, async () => {
|
|
10152
|
+
const targetRoot = dependencyRoot;
|
|
10031
10153
|
const manifest = readProjectManifest(targetRoot);
|
|
10032
10154
|
const name = mappingsOf(manifest)[pluginId] ??
|
|
10033
|
-
listNpmPlugins(
|
|
10155
|
+
listNpmPlugins(options.side, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId)?.name;
|
|
10034
10156
|
if (typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
10035
10157
|
return null;
|
|
10036
|
-
const record = listNpmPlugins(
|
|
10037
|
-
|
|
10158
|
+
const record = listNpmPlugins(options.side, {
|
|
10159
|
+
packageRoot,
|
|
10160
|
+
projectRoot,
|
|
10161
|
+
skipInvalid: true
|
|
10162
|
+
}).find((item) => item.pluginId === pluginId);
|
|
10163
|
+
await runNpm([
|
|
10164
|
+
"--prefix",
|
|
10165
|
+
targetRoot,
|
|
10166
|
+
"uninstall",
|
|
10167
|
+
name,
|
|
10168
|
+
"--no-fund",
|
|
10169
|
+
"--no-audit",
|
|
10170
|
+
"--ignore-scripts",
|
|
10171
|
+
"--workspaces=false"
|
|
10172
|
+
], targetRoot);
|
|
10038
10173
|
const updated = readProjectManifest(targetRoot);
|
|
10039
10174
|
const plugins = { ...mappingsOf(updated) };
|
|
10040
10175
|
delete plugins[pluginId];
|
|
@@ -10208,11 +10343,11 @@ const plugin_npm_1 = __webpack_require__("../common/src/plugin_npm.ts");
|
|
|
10208
10343
|
/** Poll package boundaries, allowing npm to finish replacing files before reconciliation. */
|
|
10209
10344
|
function watchNpmPlugins(ctx, side, reload, changed) {
|
|
10210
10345
|
const stamp = () => {
|
|
10211
|
-
const { packageRoot,
|
|
10346
|
+
const { packageRoot, dependencyRoot } = (0, plugin_npm_1.npmPluginRoots)({ side });
|
|
10212
10347
|
if (!node_fs_1.default.existsSync(node_path_1.default.join(packageRoot, "package.json")))
|
|
10213
10348
|
return "";
|
|
10214
10349
|
const files = [];
|
|
10215
|
-
for (const root of [packageRoot,
|
|
10350
|
+
for (const root of [packageRoot, dependencyRoot])
|
|
10216
10351
|
for (const name of [
|
|
10217
10352
|
"package.json",
|
|
10218
10353
|
"package-lock.json",
|
|
@@ -10236,7 +10371,7 @@ function watchNpmPlugins(ctx, side, reload, changed) {
|
|
|
10236
10371
|
let candidate = applied;
|
|
10237
10372
|
let busy = false;
|
|
10238
10373
|
ctx.setInterval(() => {
|
|
10239
|
-
if (busy || (0, plugin_npm_1.npmPluginOperationPending)())
|
|
10374
|
+
if (busy || (0, plugin_npm_1.npmPluginOperationPending)(side))
|
|
10240
10375
|
return;
|
|
10241
10376
|
try {
|
|
10242
10377
|
const next = stamp();
|
|
@@ -10267,6 +10402,50 @@ function watchNpmPlugins(ctx, side, reload, changed) {
|
|
|
10267
10402
|
}
|
|
10268
10403
|
|
|
10269
10404
|
|
|
10405
|
+
/***/ },
|
|
10406
|
+
|
|
10407
|
+
/***/ "../common/src/process_restart.ts"
|
|
10408
|
+
(__unused_webpack_module, exports) {
|
|
10409
|
+
|
|
10410
|
+
"use strict";
|
|
10411
|
+
|
|
10412
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
10413
|
+
exports.isProcessRestartPending = isProcessRestartPending;
|
|
10414
|
+
exports.requestProcessRestart = requestProcessRestart;
|
|
10415
|
+
const RESTART = Symbol.for("elementspanel.process.restart");
|
|
10416
|
+
function isProcessRestartPending(ctx) {
|
|
10417
|
+
return ctx.root[RESTART]?.pending === true;
|
|
10418
|
+
}
|
|
10419
|
+
/** The CLI retains instance processes and starts a fresh worker after this one exits. */
|
|
10420
|
+
function requestProcessRestart(ctx, settle, ready = async () => { }) {
|
|
10421
|
+
if (isProcessRestartPending(ctx))
|
|
10422
|
+
return true;
|
|
10423
|
+
if (process.env.EPANEL_SUPERVISED !== "1" || !process.connected || !process.send)
|
|
10424
|
+
return false;
|
|
10425
|
+
const state = { pending: true };
|
|
10426
|
+
ctx.root[RESTART] = state;
|
|
10427
|
+
const retainProcesses = () => new Promise((resolve, reject) => process.send({ type: "elementspanel:prepare-restart" }, (error) => error ? reject(error) : resolve()));
|
|
10428
|
+
setImmediate(() => {
|
|
10429
|
+
void (async () => {
|
|
10430
|
+
await ready();
|
|
10431
|
+
// Detach output consumers before cleanup so output and exits during the
|
|
10432
|
+
// restart are retained for the next daemon generation.
|
|
10433
|
+
await retainProcesses();
|
|
10434
|
+
await ctx.parallel("process/restarting");
|
|
10435
|
+
// A pending start can finish while restart hooks are awaiting it.
|
|
10436
|
+
await retainProcesses();
|
|
10437
|
+
await ctx.stop();
|
|
10438
|
+
await settle();
|
|
10439
|
+
process.send({ type: "elementspanel:restart" }, (error) => process.exit(error ? 1 : 0));
|
|
10440
|
+
})().catch((error) => {
|
|
10441
|
+
ctx.logger.error("Failed to restart the process:", error);
|
|
10442
|
+
process.exit(1);
|
|
10443
|
+
});
|
|
10444
|
+
});
|
|
10445
|
+
return true;
|
|
10446
|
+
}
|
|
10447
|
+
|
|
10448
|
+
|
|
10270
10449
|
/***/ },
|
|
10271
10450
|
|
|
10272
10451
|
/***/ "../common/src/process_tools.ts"
|
|
@@ -11277,6 +11456,226 @@ module.exports = require("stream/promises");
|
|
|
11277
11456
|
"use strict";
|
|
11278
11457
|
module.exports = require("util");
|
|
11279
11458
|
|
|
11459
|
+
/***/ },
|
|
11460
|
+
|
|
11461
|
+
/***/ "../../scripts/runtime/npm-plugins.cjs"
|
|
11462
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
11463
|
+
|
|
11464
|
+
const fs = __webpack_require__("node:fs");
|
|
11465
|
+
const path = __webpack_require__("node:path");
|
|
11466
|
+
const { createRequire } = __webpack_require__("node:module");
|
|
11467
|
+
|
|
11468
|
+
const readJson = (filename) => JSON.parse(fs.readFileSync(filename, "utf8"));
|
|
11469
|
+
const readOptional = (filename) => {
|
|
11470
|
+
try {
|
|
11471
|
+
return readJson(filename);
|
|
11472
|
+
} catch (error) {
|
|
11473
|
+
if (error.code === "ENOENT") return null;
|
|
11474
|
+
throw error;
|
|
11475
|
+
}
|
|
11476
|
+
};
|
|
11477
|
+
const validId = /^[a-z][a-z0-9_-]{1,63}$/;
|
|
11478
|
+
const validName = /^(?:@[a-z0-9_.-]+\/)?epanel-plugin-[a-z0-9_.-]+$/;
|
|
11479
|
+
|
|
11480
|
+
function packageManifest(root, name) {
|
|
11481
|
+
const localRequire = createRequire(path.join(root, "package.json"));
|
|
11482
|
+
const filename = (localRequire.resolve.paths(name) || [])
|
|
11483
|
+
.map((directory) => path.join(directory, name, "package.json"))
|
|
11484
|
+
.find((candidate) => fs.existsSync(candidate));
|
|
11485
|
+
if (!filename) throw new Error(`Missing npm package: ${name}`);
|
|
11486
|
+
return fs.realpathSync(filename);
|
|
11487
|
+
}
|
|
11488
|
+
|
|
11489
|
+
function within(root, filename) {
|
|
11490
|
+
const relative = path.relative(root, filename);
|
|
11491
|
+
return (
|
|
11492
|
+
relative === "" ||
|
|
11493
|
+
(relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))
|
|
11494
|
+
);
|
|
11495
|
+
}
|
|
11496
|
+
|
|
11497
|
+
function projectRoot(packageRoot) {
|
|
11498
|
+
if (process.env.EPANEL_PROJECT_ROOT) return path.resolve(process.env.EPANEL_PROJECT_ROOT);
|
|
11499
|
+
const parent = path.resolve(packageRoot, "..");
|
|
11500
|
+
if (path.basename(parent) === "core") return path.dirname(parent);
|
|
11501
|
+
// An installed runtime sits at <project>/node_modules/@scope/<side>; the
|
|
11502
|
+
// project itself is three levels above the package. A development checkout
|
|
11503
|
+
// sits below core/ and is handled above.
|
|
11504
|
+
const scope = parent;
|
|
11505
|
+
const nodeModules = path.dirname(scope);
|
|
11506
|
+
if (path.basename(nodeModules) === "node_modules" && path.basename(scope).startsWith("@")) {
|
|
11507
|
+
return path.dirname(nodeModules);
|
|
11508
|
+
}
|
|
11509
|
+
return parent;
|
|
11510
|
+
}
|
|
11511
|
+
|
|
11512
|
+
function projectManifestFile(packageRoot, side, options = {}) {
|
|
11513
|
+
const selectedSide =
|
|
11514
|
+
side ||
|
|
11515
|
+
readJson(path.join(packageRoot, "package.json")).elementspanel?.side ||
|
|
11516
|
+
path.basename(packageRoot);
|
|
11517
|
+
if (!["panel", "daemon"].includes(selectedSide)) throw new Error("Missing runtime side.");
|
|
11518
|
+
const directory =
|
|
11519
|
+
options.runtimeDirectory ||
|
|
11520
|
+
(!options.projectRoot && process.env.EPANEL_RUNTIME_ROOT) ||
|
|
11521
|
+
path.join(options.projectRoot || projectRoot(packageRoot), selectedSide);
|
|
11522
|
+
return path.join(path.resolve(directory), "package.json");
|
|
11523
|
+
}
|
|
11524
|
+
|
|
11525
|
+
function mappingOf(manifest) {
|
|
11526
|
+
const plugins = manifest?.elementspanel?.plugins;
|
|
11527
|
+
return plugins && typeof plugins === "object" && !Array.isArray(plugins) ? plugins : {};
|
|
11528
|
+
}
|
|
11529
|
+
|
|
11530
|
+
function dependenciesOf(manifest) {
|
|
11531
|
+
const dependencies = manifest?.dependencies;
|
|
11532
|
+
return dependencies && typeof dependencies === "object" ? dependencies : {};
|
|
11533
|
+
}
|
|
11534
|
+
|
|
11535
|
+
/**
|
|
11536
|
+
* Resolve plugins declared in the side's package.json, including hoisted npm
|
|
11537
|
+
* installations. The host catalogue reserves built-in ids without owning their
|
|
11538
|
+
* versions. Other explicitly mapped plugins remain dormant until enabled.
|
|
11539
|
+
* Runtime startup passes skipInvalid so an optional package being removed does
|
|
11540
|
+
* not stop the host; the caller separately validates the small set of core
|
|
11541
|
+
* plugins.
|
|
11542
|
+
*/
|
|
11543
|
+
function resolveNpmPlugins(packageRoot, side, options = {}) {
|
|
11544
|
+
const skipInvalid = options.skipInvalid === true;
|
|
11545
|
+
const host = readJson(path.join(packageRoot, "package.json"));
|
|
11546
|
+
const selectedSide = side || host.elementspanel?.side || path.basename(packageRoot);
|
|
11547
|
+
const projectFile = projectManifestFile(packageRoot, selectedSide, options);
|
|
11548
|
+
const project = readOptional(projectFile);
|
|
11549
|
+
const dependencyRoot = path.dirname(projectFile);
|
|
11550
|
+
|
|
11551
|
+
// The side manifest owns every dependency; the host only reserves identities.
|
|
11552
|
+
const configured = new Map();
|
|
11553
|
+
for (const [id, name] of Object.entries(mappingOf(project)))
|
|
11554
|
+
configured.set(id, { name, source: "project" });
|
|
11555
|
+
for (const name of Object.keys(dependenciesOf(project))) {
|
|
11556
|
+
if (!validName.test(name) || [...configured.values()].some((item) => item.name === name))
|
|
11557
|
+
continue;
|
|
11558
|
+
try {
|
|
11559
|
+
const metadata = readJson(packageManifest(dependencyRoot, name));
|
|
11560
|
+
const id = metadata.elementspanel?.id;
|
|
11561
|
+
if (validId.test(id) && !configured.has(id))
|
|
11562
|
+
configured.set(id, { name, source: "project", automatic: true });
|
|
11563
|
+
} catch {
|
|
11564
|
+
/* Ignore incomplete npm installations until the next scan. */
|
|
11565
|
+
}
|
|
11566
|
+
}
|
|
11567
|
+
for (const [id, name] of Object.entries(mappingOf(host))) {
|
|
11568
|
+
if (dependenciesOf(project)[name] !== undefined)
|
|
11569
|
+
configured.set(id, {
|
|
11570
|
+
name,
|
|
11571
|
+
source: "bundled",
|
|
11572
|
+
automatic: true
|
|
11573
|
+
});
|
|
11574
|
+
else if (configured.has(id)) {
|
|
11575
|
+
if (!skipInvalid) throw new Error(`Plugin id ${id} belongs to built-in package ${name}.`);
|
|
11576
|
+
configured.delete(id);
|
|
11577
|
+
}
|
|
11578
|
+
}
|
|
11579
|
+
|
|
11580
|
+
const projectDependencies = dependenciesOf(project);
|
|
11581
|
+
const folders = new Set();
|
|
11582
|
+
const records = [];
|
|
11583
|
+
|
|
11584
|
+
for (const [id, mapping] of configured) {
|
|
11585
|
+
const name = mapping.name;
|
|
11586
|
+
if (
|
|
11587
|
+
!validId.test(id) ||
|
|
11588
|
+
typeof name !== "string" ||
|
|
11589
|
+
!validName.test(name) ||
|
|
11590
|
+
projectDependencies[name] === undefined
|
|
11591
|
+
) {
|
|
11592
|
+
if (skipInvalid) continue;
|
|
11593
|
+
throw new Error(`Invalid npm plugin dependency: ${id}.`);
|
|
11594
|
+
}
|
|
11595
|
+
|
|
11596
|
+
let manifestPath;
|
|
11597
|
+
let failure;
|
|
11598
|
+
try {
|
|
11599
|
+
manifestPath = packageManifest(dependencyRoot, name);
|
|
11600
|
+
} catch (error) {
|
|
11601
|
+
failure = error;
|
|
11602
|
+
}
|
|
11603
|
+
if (!manifestPath) {
|
|
11604
|
+
if (skipInvalid) continue;
|
|
11605
|
+
throw new Error(`Missing npm plugin ${name}. Run npm install in ${dependencyRoot}.`, {
|
|
11606
|
+
cause: failure
|
|
11607
|
+
});
|
|
11608
|
+
}
|
|
11609
|
+
let metadata;
|
|
11610
|
+
try {
|
|
11611
|
+
metadata = readJson(manifestPath);
|
|
11612
|
+
} catch (error) {
|
|
11613
|
+
if (skipInvalid) continue;
|
|
11614
|
+
throw error;
|
|
11615
|
+
}
|
|
11616
|
+
const definition = metadata.elementspanel?.[selectedSide];
|
|
11617
|
+
// A project dependency may only ship one side; the other side simply does
|
|
11618
|
+
// not see it. A built-in plugin declared by the runtime must carry this side.
|
|
11619
|
+
if (!definition) {
|
|
11620
|
+
if (mapping.source === "project" || skipInvalid) continue;
|
|
11621
|
+
throw new Error(`Invalid ${selectedSide} entry in npm plugin ${name}.`);
|
|
11622
|
+
}
|
|
11623
|
+
const pluginRoot = path.dirname(manifestPath);
|
|
11624
|
+
if (
|
|
11625
|
+
typeof definition.directory !== "string" ||
|
|
11626
|
+
typeof definition.folder !== "string" ||
|
|
11627
|
+
!validId.test(definition.folder) ||
|
|
11628
|
+
path.isAbsolute(definition.directory)
|
|
11629
|
+
) {
|
|
11630
|
+
if (skipInvalid) continue;
|
|
11631
|
+
throw new Error(`Npm plugin entry escapes its package: ${name}.`);
|
|
11632
|
+
}
|
|
11633
|
+
const directory = path.resolve(pluginRoot, definition.directory);
|
|
11634
|
+
if (directory === pluginRoot || !within(pluginRoot, directory)) {
|
|
11635
|
+
if (skipInvalid) continue;
|
|
11636
|
+
throw new Error(`Npm plugin entry escapes its package: ${name}.`);
|
|
11637
|
+
}
|
|
11638
|
+
let entries;
|
|
11639
|
+
try {
|
|
11640
|
+
entries = readJson(path.join(directory, "plugin.json"));
|
|
11641
|
+
} catch (error) {
|
|
11642
|
+
if (skipInvalid) continue;
|
|
11643
|
+
throw error;
|
|
11644
|
+
}
|
|
11645
|
+
if (
|
|
11646
|
+
metadata.elementspanel?.id !== id ||
|
|
11647
|
+
typeof metadata.version !== "string" ||
|
|
11648
|
+
!metadata.version.trim() ||
|
|
11649
|
+
!entries ||
|
|
11650
|
+
typeof entries !== "object" ||
|
|
11651
|
+
Array.isArray(entries)
|
|
11652
|
+
) {
|
|
11653
|
+
if (skipInvalid) continue;
|
|
11654
|
+
throw new Error(`Invalid npm plugin identity or version in package.json: ${name}.`);
|
|
11655
|
+
}
|
|
11656
|
+
if (folders.has(definition.folder)) {
|
|
11657
|
+
if (skipInvalid) continue;
|
|
11658
|
+
throw new Error(`Duplicate npm plugin asset directory: ${definition.folder}.`);
|
|
11659
|
+
}
|
|
11660
|
+
folders.add(definition.folder);
|
|
11661
|
+
records.push({
|
|
11662
|
+
id,
|
|
11663
|
+
name,
|
|
11664
|
+
version: metadata.version,
|
|
11665
|
+
source: mapping.source,
|
|
11666
|
+
defaultEnabled: mapping.automatic === true || mapping.source !== "project",
|
|
11667
|
+
packageRoot: pluginRoot,
|
|
11668
|
+
directory,
|
|
11669
|
+
folder: definition.folder,
|
|
11670
|
+
dependencies: Object.keys(metadata.dependencies || {})
|
|
11671
|
+
});
|
|
11672
|
+
}
|
|
11673
|
+
return records;
|
|
11674
|
+
}
|
|
11675
|
+
|
|
11676
|
+
module.exports = { resolveNpmPlugins, projectManifestFile };
|
|
11677
|
+
|
|
11678
|
+
|
|
11280
11679
|
/***/ },
|
|
11281
11680
|
|
|
11282
11681
|
/***/ "../../scripts/workspace-layout.cjs"
|