@elementspanel/epanel-plugin-instance 1.0.2 → 1.0.4
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 +325 -95
- package/package.json +3 -3
- package/panel/backend/index.cjs +14 -9
package/daemon/backend/index.cjs
CHANGED
|
@@ -2142,6 +2142,10 @@ class Instance extends events_1.EventEmitter {
|
|
|
2142
2142
|
outputBuffer;
|
|
2143
2143
|
stopping;
|
|
2144
2144
|
resourceCleanup;
|
|
2145
|
+
dispatcher;
|
|
2146
|
+
reloading = false;
|
|
2147
|
+
restartPending = false;
|
|
2148
|
+
shuttingDown = false;
|
|
2145
2149
|
// Track whether the current stop was initiated by a user action (stop/kill command)
|
|
2146
2150
|
userRequestedStop = false;
|
|
2147
2151
|
// When initializing an instance, the instance must be initialized through uuid and configuration class, otherwise the instance will be unavailable
|
|
@@ -2169,19 +2173,19 @@ class Instance extends events_1.EventEmitter {
|
|
|
2169
2173
|
if (!this.isStoppedOrBusy())
|
|
2170
2174
|
throw new Error((0, i18n_1.$t)("TXT_CODE_instanceConf.cantModifyInstanceType"));
|
|
2171
2175
|
(0, mcsmanager_common_1.configureEntityParams)(this.config, cfg, "type", String);
|
|
2172
|
-
this.forceExec(new dispatcher_1.default());
|
|
2176
|
+
this.forceExec(this.dispatcher ?? new dispatcher_1.default());
|
|
2173
2177
|
}
|
|
2174
2178
|
if (cfg?.enableRcon != null && cfg?.enableRcon !== this.config.enableRcon) {
|
|
2175
2179
|
if (!this.isStoppedOrBusy())
|
|
2176
2180
|
throw new Error((0, i18n_1.$t)("TXT_CODE_bdfa3457"));
|
|
2177
2181
|
(0, mcsmanager_common_1.configureEntityParams)(this.config, cfg, "enableRcon", Boolean);
|
|
2178
|
-
this.forceExec(new dispatcher_1.default());
|
|
2182
|
+
this.forceExec(this.dispatcher ?? new dispatcher_1.default());
|
|
2179
2183
|
}
|
|
2180
2184
|
if (cfg?.processType && cfg?.processType !== this.config.processType) {
|
|
2181
2185
|
if (!this.isStoppedOrBusy())
|
|
2182
2186
|
throw new Error((0, i18n_1.$t)("TXT_CODE_instanceConf.cantModifyProcessType"));
|
|
2183
2187
|
(0, mcsmanager_common_1.configureEntityParams)(this.config, cfg, "processType", String);
|
|
2184
|
-
this.forceExec(new dispatcher_1.default());
|
|
2188
|
+
this.forceExec(this.dispatcher ?? new dispatcher_1.default());
|
|
2185
2189
|
}
|
|
2186
2190
|
// If the terminal type is changed, the default command must be reset
|
|
2187
2191
|
if (cfg?.terminalOption?.pty != null &&
|
|
@@ -2189,7 +2193,7 @@ class Instance extends events_1.EventEmitter {
|
|
|
2189
2193
|
if (!this.isStoppedOrBusy())
|
|
2190
2194
|
throw new Error((0, i18n_1.$t)("TXT_CODE_instanceConf.cantModifyPtyModel"));
|
|
2191
2195
|
(0, mcsmanager_common_1.configureEntityParams)(this.config.terminalOption, cfg.terminalOption, "pty", Boolean);
|
|
2192
|
-
this.forceExec(new dispatcher_1.default());
|
|
2196
|
+
this.forceExec(this.dispatcher ?? new dispatcher_1.default());
|
|
2193
2197
|
}
|
|
2194
2198
|
// Only allow some configuration items to be modified when the server is stopped
|
|
2195
2199
|
if (this.isStoppedOrBusy() && cfg.terminalOption) {
|
|
@@ -2350,10 +2354,19 @@ class Instance extends events_1.EventEmitter {
|
|
|
2350
2354
|
});
|
|
2351
2355
|
this.process = process;
|
|
2352
2356
|
this.instanceStatus = Instance.STATUS_RUNNING;
|
|
2357
|
+
if (this.shuttingDown) {
|
|
2358
|
+
this.userRequestedStop = true;
|
|
2359
|
+
void Promise.resolve()
|
|
2360
|
+
.then(() => process.kill("SIGKILL"))
|
|
2361
|
+
.catch((error) => log_1.default.error(`Instance ${this.instanceUuid} failed to stop:`, error));
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2353
2364
|
this.emit("open", this);
|
|
2354
2365
|
// start all lifecycle tasks
|
|
2355
|
-
|
|
2356
|
-
|
|
2366
|
+
if (!this.reloading) {
|
|
2367
|
+
void this.lifeCycleTaskManager.execLifeCycleTask(1);
|
|
2368
|
+
this.startOutputLoop();
|
|
2369
|
+
}
|
|
2357
2370
|
}
|
|
2358
2371
|
// If the instance performs any operation exception, it must throw an exception through this function
|
|
2359
2372
|
// trigger failure event
|
|
@@ -2393,22 +2406,11 @@ class Instance extends events_1.EventEmitter {
|
|
|
2393
2406
|
this.println("ERROR", (0, i18n_1.$t)("TXT_CODE_aae2918f"));
|
|
2394
2407
|
// Release this stop before a restarted process can emit its own exit.
|
|
2395
2408
|
this.stopping = undefined;
|
|
2396
|
-
if (shouldRestart) {
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
this.autoRestartCount++;
|
|
2402
|
-
this.println((0, i18n_1.$t)("TXT_CODE_instanceConf.info"), (0, i18n_1.$t)("TXT_CODE_instanceConf.autoRestart"));
|
|
2403
|
-
this.emit("autoRestarted", { count: this.autoRestartCount });
|
|
2404
|
-
})
|
|
2405
|
-
.catch((err) => {
|
|
2406
|
-
this.println((0, i18n_1.$t)("TXT_CODE_instanceConf.error"), (0, i18n_1.$t)("TXT_CODE_instanceConf.autoRestartErr", { err }));
|
|
2407
|
-
});
|
|
2408
|
-
}
|
|
2409
|
-
else {
|
|
2410
|
-
this.println((0, i18n_1.$t)("TXT_CODE_instanceConf.error"), (0, i18n_1.$t)("TXT_CODE_894b8e52"));
|
|
2411
|
-
}
|
|
2409
|
+
if (shouldRestart && !this.shuttingDown) {
|
|
2410
|
+
if (this.reloading)
|
|
2411
|
+
this.restartPending = true;
|
|
2412
|
+
else
|
|
2413
|
+
this.restartAfterCrash();
|
|
2412
2414
|
}
|
|
2413
2415
|
}).finally(() => {
|
|
2414
2416
|
if (this.stopping === operation)
|
|
@@ -2417,6 +2419,60 @@ class Instance extends events_1.EventEmitter {
|
|
|
2417
2419
|
this.stopping = operation;
|
|
2418
2420
|
return operation;
|
|
2419
2421
|
}
|
|
2422
|
+
restartAfterCrash() {
|
|
2423
|
+
if (this.shuttingDown)
|
|
2424
|
+
return;
|
|
2425
|
+
const maxAutoRestartCount = this.config.eventTask.autoRestartMaxTimes;
|
|
2426
|
+
if (maxAutoRestartCount !== -1 && this.autoRestartCount >= maxAutoRestartCount) {
|
|
2427
|
+
this.println((0, i18n_1.$t)("TXT_CODE_instanceConf.error"), (0, i18n_1.$t)("TXT_CODE_894b8e52"));
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
void this.execPreset("start")
|
|
2431
|
+
.then(() => {
|
|
2432
|
+
this.autoRestartCount++;
|
|
2433
|
+
this.println((0, i18n_1.$t)("TXT_CODE_instanceConf.info"), (0, i18n_1.$t)("TXT_CODE_instanceConf.autoRestart"));
|
|
2434
|
+
this.emit("autoRestarted", { count: this.autoRestartCount });
|
|
2435
|
+
})
|
|
2436
|
+
.catch((err) => {
|
|
2437
|
+
this.println((0, i18n_1.$t)("TXT_CODE_instanceConf.error"), (0, i18n_1.$t)("TXT_CODE_instanceConf.autoRestartErr", { err }));
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
/** Keep the process and its pipe readers alive while plugin services change. */
|
|
2441
|
+
async suspendForReload() {
|
|
2442
|
+
this.stopOutputLoop();
|
|
2443
|
+
this.reloading = true;
|
|
2444
|
+
this.watchers.clear();
|
|
2445
|
+
await this.lifeCycleTaskManager.clearLifeCycleTask();
|
|
2446
|
+
}
|
|
2447
|
+
async resumeAfterReload(dispatcher) {
|
|
2448
|
+
if (this.shuttingDown)
|
|
2449
|
+
return;
|
|
2450
|
+
await this.stopping;
|
|
2451
|
+
if (this.shuttingDown)
|
|
2452
|
+
return;
|
|
2453
|
+
this.dispatcher = dispatcher;
|
|
2454
|
+
await this.forceExec(dispatcher);
|
|
2455
|
+
if (this.shuttingDown)
|
|
2456
|
+
return;
|
|
2457
|
+
this.reloading = false;
|
|
2458
|
+
this.flushOutputBuffer();
|
|
2459
|
+
if (this.status() === Instance.STATUS_RUNNING) {
|
|
2460
|
+
await this.lifeCycleTaskManager.execLifeCycleTask(1);
|
|
2461
|
+
if (!this.shuttingDown && this.status() === Instance.STATUS_RUNNING) {
|
|
2462
|
+
this.startOutputLoop(false);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
else if (this.status() === Instance.STATUS_STOP && this.restartPending) {
|
|
2466
|
+
this.restartPending = false;
|
|
2467
|
+
this.restartAfterCrash();
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
beginShutdown() {
|
|
2471
|
+
this.restartPending = false;
|
|
2472
|
+
this.reloading = false;
|
|
2473
|
+
this.shuttingDown = true;
|
|
2474
|
+
this.ignoreEventTaskOnce();
|
|
2475
|
+
}
|
|
2420
2476
|
ignoreEventTaskOnce() {
|
|
2421
2477
|
if (this.config.eventTask)
|
|
2422
2478
|
this.config.eventTask.ignore = true;
|
|
@@ -2430,11 +2486,14 @@ class Instance extends events_1.EventEmitter {
|
|
|
2430
2486
|
// custom output method, formatting
|
|
2431
2487
|
println(level, text) {
|
|
2432
2488
|
const str = `[${level}] ${text}\n`;
|
|
2433
|
-
this.
|
|
2489
|
+
this.print(str);
|
|
2434
2490
|
}
|
|
2435
2491
|
// custom output method
|
|
2436
2492
|
print(data) {
|
|
2437
|
-
this.
|
|
2493
|
+
if (this.reloading)
|
|
2494
|
+
this.pushOutput(String(data));
|
|
2495
|
+
else
|
|
2496
|
+
this.emit("data", data);
|
|
2438
2497
|
}
|
|
2439
2498
|
// Release resources (mainly release process-related resources)
|
|
2440
2499
|
releaseResources() {
|
|
@@ -2572,6 +2631,8 @@ class Instance extends events_1.EventEmitter {
|
|
|
2572
2631
|
this.outputBuffer.pushLog(data);
|
|
2573
2632
|
}
|
|
2574
2633
|
flushOutputBuffer(shouldClear = false) {
|
|
2634
|
+
if (this.reloading)
|
|
2635
|
+
return;
|
|
2575
2636
|
const { items, wasDeleted } = this.outputBuffer.getCache();
|
|
2576
2637
|
if (!items.length)
|
|
2577
2638
|
return;
|
|
@@ -2581,8 +2642,11 @@ class Instance extends events_1.EventEmitter {
|
|
|
2581
2642
|
this.outputBuffer.clear();
|
|
2582
2643
|
this.emit("data", items.join(""));
|
|
2583
2644
|
}
|
|
2584
|
-
startOutputLoop() {
|
|
2585
|
-
|
|
2645
|
+
startOutputLoop(resetBuffer = true) {
|
|
2646
|
+
if (this.outputLoopTask)
|
|
2647
|
+
return;
|
|
2648
|
+
if (resetBuffer)
|
|
2649
|
+
this.outputBuffer = new string_cache_1.CircularBuffer(config_1.globalConfiguration.config.outputBufferSize || 256);
|
|
2586
2650
|
this.outputLoopTask = setInterval(() => {
|
|
2587
2651
|
this.flushOutputBuffer();
|
|
2588
2652
|
}, 50);
|
|
@@ -2927,7 +2991,18 @@ function apply(ctx) {
|
|
|
2927
2991
|
ctx.plugin(registries_1.LifecycleService);
|
|
2928
2992
|
ctx.plugin(registries_1.PresetsService);
|
|
2929
2993
|
ctx.plugin(registries_1.SchedulesService);
|
|
2930
|
-
(0, runtime_1.setPluginContext)(ctx);
|
|
2994
|
+
const runtime = (0, runtime_1.setPluginContext)(ctx);
|
|
2995
|
+
let preserving = false;
|
|
2996
|
+
let subsystem;
|
|
2997
|
+
let disposeSchedules;
|
|
2998
|
+
ctx.on("plugins/reloading", async () => {
|
|
2999
|
+
const inventory = ctx.get("plugins")?.inventory();
|
|
3000
|
+
preserving = inventory?.some((plugin) => plugin.id === "instance" && plugin.enabled) ?? true;
|
|
3001
|
+
if (preserving) {
|
|
3002
|
+
disposeSchedules?.();
|
|
3003
|
+
await subsystem?.suspendForReload();
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
2931
3006
|
(0, version_adapter_1.migrateConfig)(ctx);
|
|
2932
3007
|
void (0, steam_cmd_1.initSteamCmd)(ctx);
|
|
2933
3008
|
// The registry services above are created as child plugins, so their values
|
|
@@ -2946,6 +3021,57 @@ function apply(ctx) {
|
|
|
2946
3021
|
const { commandStringToArray } = __webpack_require__("../../plugins/instance/daemon/src/backend/entity/commands/base/command_parser.ts");
|
|
2947
3022
|
const { default: FunctionDispatcher } = __webpack_require__("../../plugins/instance/daemon/src/backend/entity/commands/dispatcher.ts");
|
|
2948
3023
|
const { DockerManager } = __webpack_require__("../../plugins/instance/daemon/src/backend/service/docker_service.ts");
|
|
3024
|
+
const { default: InstanceControlSubsystem } = __webpack_require__("../../plugins/instance/daemon/src/backend/service/system_instance_control.ts");
|
|
3025
|
+
subsystem = InstanceSubsystem;
|
|
3026
|
+
disposeSchedules = () => InstanceControlSubsystem.dispose();
|
|
3027
|
+
const previous = runtime.handover;
|
|
3028
|
+
let recovering = Boolean(previous);
|
|
3029
|
+
let disposeInstanceEvents;
|
|
3030
|
+
let shuttingDown;
|
|
3031
|
+
const config = instanceCtx.settings.config;
|
|
3032
|
+
const shutdown = () => (shuttingDown ??= (async () => {
|
|
3033
|
+
for (const instance of InstanceSubsystem.instances.values())
|
|
3034
|
+
instance.beginShutdown();
|
|
3035
|
+
try {
|
|
3036
|
+
if (config.enableSoftShutdown) {
|
|
3037
|
+
await InstanceSubsystem.softExit(Boolean(config.softShutdownSkipDocker), Number(config.softShutdownWaitSeconds) || 10);
|
|
3038
|
+
}
|
|
3039
|
+
else {
|
|
3040
|
+
await InstanceSubsystem.exit(true);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
catch (error) {
|
|
3044
|
+
instanceCtx.logger.error("Failed to stop application instances:", error);
|
|
3045
|
+
}
|
|
3046
|
+
})());
|
|
3047
|
+
const retain = () => {
|
|
3048
|
+
runtime.handover = {
|
|
3049
|
+
instances: [...InstanceSubsystem.instances.values()],
|
|
3050
|
+
shutdown,
|
|
3051
|
+
release: () => InstanceSubsystem.releaseAfterReload()
|
|
3052
|
+
};
|
|
3053
|
+
};
|
|
3054
|
+
// Own the processes before any asynchronous initialization. A failed Java
|
|
3055
|
+
// manager or replacement must leave them available for another attempt.
|
|
3056
|
+
if (previous) {
|
|
3057
|
+
InstanceControlSubsystem.dispose();
|
|
3058
|
+
InstanceSubsystem.adoptInstances(previous.instances);
|
|
3059
|
+
retain();
|
|
3060
|
+
previous.release();
|
|
3061
|
+
}
|
|
3062
|
+
instanceCtx.on("dispose", async () => {
|
|
3063
|
+
await disposeInstanceEvents?.();
|
|
3064
|
+
InstanceControlSubsystem.dispose();
|
|
3065
|
+
router_1.routerApp.dispose();
|
|
3066
|
+
if ((preserving || recovering) && !runtime.stopping && ctx.root.scope.isActive) {
|
|
3067
|
+
await InstanceSubsystem.suspendForReload();
|
|
3068
|
+
retain();
|
|
3069
|
+
}
|
|
3070
|
+
else {
|
|
3071
|
+
await shutdown();
|
|
3072
|
+
}
|
|
3073
|
+
InstanceSubsystem.detachListeners();
|
|
3074
|
+
});
|
|
2949
3075
|
const services = {
|
|
2950
3076
|
subsystem: InstanceSubsystem,
|
|
2951
3077
|
Instance,
|
|
@@ -2987,11 +3113,10 @@ function apply(ctx) {
|
|
|
2987
3113
|
const { registerInstanceEvents } = __webpack_require__("../../plugins/instance/daemon/src/backend/routers/instance_event_router.ts");
|
|
2988
3114
|
__webpack_require__("../../plugins/instance/daemon/src/backend/routers/schedule_router.ts");
|
|
2989
3115
|
__webpack_require__("../../plugins/instance/daemon/src/backend/routers/environment_router.ts");
|
|
2990
|
-
|
|
2991
|
-
const { default: InstanceControlSubsystem } = __webpack_require__("../../plugins/instance/daemon/src/backend/service/system_instance_control.ts");
|
|
2992
|
-
instanceCtx.on("dispose", () => router_1.routerApp.dispose());
|
|
3116
|
+
disposeInstanceEvents = registerInstanceEvents();
|
|
2993
3117
|
try {
|
|
2994
|
-
|
|
3118
|
+
if (!previous)
|
|
3119
|
+
InstanceSubsystem.loadInstances();
|
|
2995
3120
|
instanceCtx.logger.info(`Loaded ${InstanceSubsystem.getInstances().length} application instances.`);
|
|
2996
3121
|
}
|
|
2997
3122
|
catch (error) {
|
|
@@ -3018,22 +3143,24 @@ function apply(ctx) {
|
|
|
3018
3143
|
dockerPlatforms
|
|
3019
3144
|
};
|
|
3020
3145
|
});
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3146
|
+
if (previous) {
|
|
3147
|
+
let resuming;
|
|
3148
|
+
const resume = () => (resuming ??= (async () => {
|
|
3149
|
+
if (runtime.stopping || !instanceCtx.scope.isActive || !ctx.root.scope.isActive)
|
|
3150
|
+
return;
|
|
3151
|
+
await InstanceSubsystem.resumeAfterReload();
|
|
3152
|
+
if (runtime.stopping || !instanceCtx.scope.isActive || !ctx.root.scope.isActive)
|
|
3153
|
+
return;
|
|
3154
|
+
InstanceControlSubsystem.loadSchedules();
|
|
3155
|
+
recovering = false;
|
|
3156
|
+
runtime.handover = undefined;
|
|
3157
|
+
})());
|
|
3158
|
+
// Rebuild commands after other plugins have registered their presets and
|
|
3159
|
+
// lifecycle tasks, and before the replacement server starts listening.
|
|
3160
|
+
instanceCtx.on("plugins/reloaded", resume);
|
|
3161
|
+
// Also allow recovery by enabling/retrying only the instance plugin.
|
|
3162
|
+
instanceCtx.on("ready", resume);
|
|
3163
|
+
}
|
|
3037
3164
|
});
|
|
3038
3165
|
}
|
|
3039
3166
|
|
|
@@ -5218,29 +5345,41 @@ const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
|
5218
5345
|
const ctx_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/entity/ctx.ts"));
|
|
5219
5346
|
const protocol = __importStar(__webpack_require__("../../plugins/instance/daemon/src/backend/service/protocol.ts"));
|
|
5220
5347
|
const system_instance_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/service/system_instance.ts"));
|
|
5348
|
+
const log_1 = __importDefault(__webpack_require__("../../plugins/instance/daemon/src/backend/service/log.ts"));
|
|
5221
5349
|
const MAX_LOG_SIZE = 512;
|
|
5222
|
-
const buffer = new Map();
|
|
5223
5350
|
function registerInstanceEvents() {
|
|
5224
|
-
const
|
|
5351
|
+
const buffer = new Map();
|
|
5352
|
+
const pending = new Map();
|
|
5353
|
+
const flush = async () => {
|
|
5225
5354
|
buffer.forEach((buf, instanceUuid) => {
|
|
5226
5355
|
if (!buf || !instanceUuid)
|
|
5227
5356
|
return;
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
fs_extra_1.default.
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5357
|
+
buffer.delete(instanceUuid);
|
|
5358
|
+
const work = (pending.get(instanceUuid) ?? Promise.resolve())
|
|
5359
|
+
.then(async () => {
|
|
5360
|
+
const logFilePath = path_1.default.join(system_instance_1.default.LOG_DIR, `${instanceUuid}.log`);
|
|
5361
|
+
await fs_extra_1.default.ensureDir(system_instance_1.default.LOG_DIR);
|
|
5362
|
+
try {
|
|
5363
|
+
const fileInfo = await fs_extra_1.default.stat(logFilePath);
|
|
5364
|
+
if (fileInfo.size > 1024 * MAX_LOG_SIZE)
|
|
5365
|
+
await fs_extra_1.default.remove(logFilePath);
|
|
5366
|
+
}
|
|
5367
|
+
catch (err) {
|
|
5368
|
+
if (err.code !== "ENOENT")
|
|
5369
|
+
throw err;
|
|
5370
|
+
}
|
|
5371
|
+
await fs_extra_1.default.writeFile(logFilePath, buf, { encoding: "utf-8", flag: "a" });
|
|
5372
|
+
})
|
|
5373
|
+
.catch((error) => log_1.default.error(`Failed to write instance ${instanceUuid} output:`, error))
|
|
5374
|
+
.finally(() => {
|
|
5375
|
+
if (pending.get(instanceUuid) === work)
|
|
5376
|
+
pending.delete(instanceUuid);
|
|
5241
5377
|
});
|
|
5378
|
+
pending.set(instanceUuid, work);
|
|
5242
5379
|
});
|
|
5243
|
-
|
|
5380
|
+
await Promise.all(pending.values());
|
|
5381
|
+
};
|
|
5382
|
+
const flushTimer = setInterval(() => void flush(), 500);
|
|
5244
5383
|
const outputLog = (instanceUuid, text) => {
|
|
5245
5384
|
const buf = (buffer.get(instanceUuid) ?? "") + text;
|
|
5246
5385
|
if (buf.length > 1024 * 1024)
|
|
@@ -5301,13 +5440,14 @@ function registerInstanceEvents() {
|
|
|
5301
5440
|
});
|
|
5302
5441
|
};
|
|
5303
5442
|
system_instance_1.default.on("autoRestarted", onAutoRestarted);
|
|
5304
|
-
return () => {
|
|
5443
|
+
return async () => {
|
|
5305
5444
|
clearInterval(flushTimer);
|
|
5306
5445
|
system_instance_1.default.off("data", onData);
|
|
5307
5446
|
system_instance_1.default.off("exit", onExit);
|
|
5308
5447
|
system_instance_1.default.off("open", onOpen);
|
|
5309
5448
|
system_instance_1.default.off("failure", onFailure);
|
|
5310
5449
|
system_instance_1.default.off("autoRestarted", onAutoRestarted);
|
|
5450
|
+
await flush();
|
|
5311
5451
|
buffer.clear();
|
|
5312
5452
|
};
|
|
5313
5453
|
}
|
|
@@ -5399,17 +5539,45 @@ exports.core = core;
|
|
|
5399
5539
|
const os_1 = __importDefault(__webpack_require__("os"));
|
|
5400
5540
|
const path_1 = __importDefault(__webpack_require__("path"));
|
|
5401
5541
|
const mcsmanager_common_1 = __webpack_require__("../common/src/index.ts");
|
|
5402
|
-
|
|
5542
|
+
const INSTANCE_RUNTIME = Symbol.for("elementspanel.daemon.instance-runtime");
|
|
5543
|
+
let runtime;
|
|
5403
5544
|
function setPluginContext(value) {
|
|
5404
|
-
|
|
5545
|
+
// Retained process callbacks can come from an evicted module. Give every
|
|
5546
|
+
// generation the same bridge, owned by this daemon rather than the module.
|
|
5547
|
+
const host = value.root;
|
|
5548
|
+
runtime = host[INSTANCE_RUNTIME];
|
|
5549
|
+
if (!runtime) {
|
|
5550
|
+
const state = {
|
|
5551
|
+
context: value,
|
|
5552
|
+
settings: value.settings,
|
|
5553
|
+
storage: value.storage,
|
|
5554
|
+
stopping: false
|
|
5555
|
+
};
|
|
5556
|
+
host[INSTANCE_RUNTIME] = runtime = state;
|
|
5557
|
+
host.on("dispose", async () => {
|
|
5558
|
+
state.stopping = true;
|
|
5559
|
+
// A replacement may fail before it can claim the retained processes.
|
|
5560
|
+
// The daemon still owns them and must stop them during real shutdown.
|
|
5561
|
+
await state.handover?.shutdown();
|
|
5562
|
+
state.handover = undefined;
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5565
|
+
if (runtime.stopping)
|
|
5566
|
+
throw new Error("The daemon is shutting down.");
|
|
5567
|
+
runtime.context = value;
|
|
5568
|
+
runtime.settings = value.settings;
|
|
5569
|
+
runtime.storage = value.storage;
|
|
5570
|
+
return runtime;
|
|
5405
5571
|
}
|
|
5406
5572
|
function core() {
|
|
5407
|
-
if (!
|
|
5573
|
+
if (!runtime)
|
|
5408
5574
|
throw new Error("The instance plugin has not been initialized yet.");
|
|
5409
|
-
return context;
|
|
5575
|
+
return runtime.context;
|
|
5410
5576
|
}
|
|
5411
5577
|
function required(name) {
|
|
5412
|
-
const value = core().get(name)
|
|
5578
|
+
const value = core().get(name) ??
|
|
5579
|
+
// Process exit and shutdown can happen between foundation generations.
|
|
5580
|
+
(name === "settings" ? runtime?.settings : name === "storage" ? runtime?.storage : undefined);
|
|
5413
5581
|
if (value === undefined)
|
|
5414
5582
|
throw new Error(`Daemon service is unavailable: ${name}`);
|
|
5415
5583
|
return value;
|
|
@@ -7195,6 +7363,9 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7195
7363
|
instances = new Map();
|
|
7196
7364
|
instanceStream = new mcsmanager_common_1.InstanceStreamListener();
|
|
7197
7365
|
instanceDataDir = "";
|
|
7366
|
+
startupCancelled = false;
|
|
7367
|
+
takeover;
|
|
7368
|
+
instanceListeners = new Map();
|
|
7198
7369
|
constructor() {
|
|
7199
7370
|
super();
|
|
7200
7371
|
// init instance default install path
|
|
@@ -7214,6 +7385,8 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7214
7385
|
async autoStart() {
|
|
7215
7386
|
await (0, sleep_1.sleep)(1000 * 5);
|
|
7216
7387
|
for (const instance of this.instances.values()) {
|
|
7388
|
+
if (this.startupCancelled)
|
|
7389
|
+
return;
|
|
7217
7390
|
if (instance.config.eventTask.autoStart && instance.status() == instance_1.default.STATUS_STOP) {
|
|
7218
7391
|
instance
|
|
7219
7392
|
.execPreset("start")
|
|
@@ -7236,6 +7409,7 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7236
7409
|
}
|
|
7237
7410
|
// init all instances from local files
|
|
7238
7411
|
loadInstances() {
|
|
7412
|
+
this.startupCancelled = false;
|
|
7239
7413
|
const instanceConfigs = system_storage_1.default.list("InstanceConfig");
|
|
7240
7414
|
instanceConfigs.forEach((uuid) => {
|
|
7241
7415
|
if (uuid === this.GLOBAL_INSTANCE_UUID)
|
|
@@ -7290,15 +7464,14 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7290
7464
|
processType: globalConfig.processType
|
|
7291
7465
|
}, true, // allow persistence
|
|
7292
7466
|
this.GLOBAL_INSTANCE_UUID);
|
|
7293
|
-
(0, takeover_container_1.default)()
|
|
7294
|
-
.catch((error) => {
|
|
7467
|
+
this.takeover = (0, takeover_container_1.default)(() => !this.startupCancelled).catch((error) => {
|
|
7295
7468
|
const reason = error.message || error;
|
|
7296
7469
|
if (typeof reason == "string" && reason.includes("connect ENOENT")) {
|
|
7297
7470
|
return;
|
|
7298
7471
|
}
|
|
7299
7472
|
log_1.default.error(`${(0, i18n_1.$t)("TXT_CODE_8d4c8f7e")}: ${reason}`);
|
|
7300
|
-
})
|
|
7301
|
-
|
|
7473
|
+
});
|
|
7474
|
+
void this.takeover.then(() => this.autoStart());
|
|
7302
7475
|
}
|
|
7303
7476
|
createInstance(cfg, persistence = true, uuid) {
|
|
7304
7477
|
const newUuid = uuid || (0, uuid_1.v4)().replace(/-/gim, "");
|
|
@@ -7325,37 +7498,74 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7325
7498
|
}
|
|
7326
7499
|
this.instances.set(instance.instanceUuid, instance);
|
|
7327
7500
|
// Dynamically monitor the newly added instance output stream and pass it to its own event stream
|
|
7328
|
-
|
|
7501
|
+
const onData = (...arr) => {
|
|
7329
7502
|
this.emit("data", instance.instanceUuid, ...arr);
|
|
7330
|
-
}
|
|
7331
|
-
|
|
7503
|
+
};
|
|
7504
|
+
const onExit = (exitData) => {
|
|
7332
7505
|
this.emit("exit", {
|
|
7333
7506
|
instanceUuid: instance.instanceUuid,
|
|
7334
7507
|
instanceName: instance.config.nickname,
|
|
7335
7508
|
exitCode: exitData.code,
|
|
7336
7509
|
isCrash: exitData.isCrash
|
|
7337
7510
|
});
|
|
7338
|
-
}
|
|
7339
|
-
|
|
7511
|
+
};
|
|
7512
|
+
const onOpen = (...arr) => {
|
|
7340
7513
|
this.emit("open", {
|
|
7341
7514
|
instanceUuid: instance.instanceUuid,
|
|
7342
7515
|
instanceName: instance.config.nickname
|
|
7343
7516
|
}, ...arr);
|
|
7344
|
-
}
|
|
7345
|
-
|
|
7517
|
+
};
|
|
7518
|
+
const onFailure = (...arr) => {
|
|
7346
7519
|
this.emit("failure", {
|
|
7347
7520
|
instanceUuid: instance.instanceUuid,
|
|
7348
7521
|
instanceName: instance.config.nickname
|
|
7349
7522
|
}, ...arr);
|
|
7350
|
-
}
|
|
7351
|
-
|
|
7523
|
+
};
|
|
7524
|
+
const onAutoRestarted = (data) => {
|
|
7352
7525
|
this.emit("autoRestarted", {
|
|
7353
7526
|
instanceUuid: instance.instanceUuid,
|
|
7354
7527
|
instanceName: instance.config.nickname,
|
|
7355
7528
|
restartCount: data.count
|
|
7356
7529
|
});
|
|
7530
|
+
};
|
|
7531
|
+
instance.on("data", onData);
|
|
7532
|
+
instance.on("exit", onExit);
|
|
7533
|
+
instance.on("open", onOpen);
|
|
7534
|
+
instance.on("failure", onFailure);
|
|
7535
|
+
instance.on("autoRestarted", onAutoRestarted);
|
|
7536
|
+
this.instanceListeners.set(instance, () => {
|
|
7537
|
+
instance.off("data", onData);
|
|
7538
|
+
instance.off("exit", onExit);
|
|
7539
|
+
instance.off("open", onOpen);
|
|
7540
|
+
instance.off("failure", onFailure);
|
|
7541
|
+
instance.off("autoRestarted", onAutoRestarted);
|
|
7357
7542
|
});
|
|
7358
7543
|
}
|
|
7544
|
+
async suspendForReload() {
|
|
7545
|
+
this.startupCancelled = true;
|
|
7546
|
+
await Promise.all([...this.instances.values()].map((instance) => instance.suspendForReload()));
|
|
7547
|
+
await this.takeover;
|
|
7548
|
+
}
|
|
7549
|
+
/** Transfer the existing owners, including ordinary child/PTY/Docker handles. */
|
|
7550
|
+
adoptInstances(instances) {
|
|
7551
|
+
this.startupCancelled = true;
|
|
7552
|
+
for (const instance of instances)
|
|
7553
|
+
this.addInstance(instance);
|
|
7554
|
+
}
|
|
7555
|
+
async resumeAfterReload() {
|
|
7556
|
+
for (const instance of this.instances.values()) {
|
|
7557
|
+
await instance.resumeAfterReload(new dispatcher_1.default());
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
detachListeners() {
|
|
7561
|
+
for (const dispose of this.instanceListeners.values())
|
|
7562
|
+
dispose();
|
|
7563
|
+
this.instanceListeners.clear();
|
|
7564
|
+
}
|
|
7565
|
+
releaseAfterReload() {
|
|
7566
|
+
this.detachListeners();
|
|
7567
|
+
this.instances.clear();
|
|
7568
|
+
}
|
|
7359
7569
|
async removeInstance(instanceUuid, deleteFile) {
|
|
7360
7570
|
const instance = this.getInstance(instanceUuid);
|
|
7361
7571
|
if (instance) {
|
|
@@ -7374,6 +7584,8 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7374
7584
|
system_instance_control_1.default.deleteInstanceAllTask(instanceUuid);
|
|
7375
7585
|
system_storage_1.default.delete("InstanceConfig", instanceUuid);
|
|
7376
7586
|
await instance.destroy();
|
|
7587
|
+
this.instanceListeners.get(instance)?.();
|
|
7588
|
+
this.instanceListeners.delete(instance);
|
|
7377
7589
|
this.instances.delete(instanceUuid);
|
|
7378
7590
|
return true;
|
|
7379
7591
|
}
|
|
@@ -7415,8 +7627,14 @@ class InstanceSubsystem extends events_1.default {
|
|
|
7415
7627
|
return this.instances.has(instanceUuid);
|
|
7416
7628
|
}
|
|
7417
7629
|
async exitInstance(instance, force = true) {
|
|
7418
|
-
if (!this.isGlobalInstance(instance))
|
|
7419
|
-
|
|
7630
|
+
if (!this.isGlobalInstance(instance)) {
|
|
7631
|
+
try {
|
|
7632
|
+
system_storage_1.default.store("InstanceConfig", instance.instanceUuid, instance.config);
|
|
7633
|
+
}
|
|
7634
|
+
catch (error) {
|
|
7635
|
+
log_1.default.error(`Failed to save instance ${instance.instanceUuid} before shutdown:`, error);
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7420
7638
|
if (instance.status() === instance_1.default.STATUS_BUSY) {
|
|
7421
7639
|
log_1.default.info(`Killing ${instance.config.nickname} (${instance.instanceUuid})...`);
|
|
7422
7640
|
await instance.execPreset("kill");
|
|
@@ -7606,6 +7824,11 @@ class InstanceControlSubsystem {
|
|
|
7606
7824
|
taskMap = new Map();
|
|
7607
7825
|
taskJobMap = new Map();
|
|
7608
7826
|
constructor() {
|
|
7827
|
+
this.loadSchedules();
|
|
7828
|
+
}
|
|
7829
|
+
loadSchedules() {
|
|
7830
|
+
if (this.taskMap.size)
|
|
7831
|
+
return;
|
|
7609
7832
|
// Initialize all persistent data and load into memory one by one
|
|
7610
7833
|
system_storage_1.default.list("TaskConfig").forEach((uuid) => {
|
|
7611
7834
|
try {
|
|
@@ -7876,7 +8099,7 @@ async function killContainer(containerInfo) {
|
|
|
7876
8099
|
container.kill().catch(() => { });
|
|
7877
8100
|
container.remove().catch(() => { });
|
|
7878
8101
|
}
|
|
7879
|
-
async function takeoverContainer() {
|
|
8102
|
+
async function takeoverContainer(isActive = () => true) {
|
|
7880
8103
|
const docker = new docker_service_1.DefaultDocker();
|
|
7881
8104
|
const containers = new Map();
|
|
7882
8105
|
(await docker.listContainers()).forEach((containerInfo) => {
|
|
@@ -7890,10 +8113,12 @@ async function takeoverContainer() {
|
|
|
7890
8113
|
containers.set(label, containerInfo);
|
|
7891
8114
|
});
|
|
7892
8115
|
for (const item of containers) {
|
|
8116
|
+
if (!isActive())
|
|
8117
|
+
return;
|
|
7893
8118
|
const [uuid, container] = item;
|
|
7894
8119
|
const instance = system_instance_1.default.getInstance(uuid);
|
|
7895
8120
|
try {
|
|
7896
|
-
if (instance && instance.config.processType == "docker") {
|
|
8121
|
+
if (instance && !instance.process && instance.config.processType == "docker") {
|
|
7897
8122
|
await instance.forceExec(new docker_takeover_1.default(container));
|
|
7898
8123
|
}
|
|
7899
8124
|
}
|
|
@@ -9449,9 +9674,9 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
|
|
|
9449
9674
|
/**
|
|
9450
9675
|
* Every npm plugin the runtime can load for a side, built-in and project-installed.
|
|
9451
9676
|
*
|
|
9452
|
-
* Built-in
|
|
9453
|
-
*
|
|
9454
|
-
* that does not describe the requested side is skipped instead of failing, which
|
|
9677
|
+
* Built-in package names win over a project mapping for the same id. An explicit
|
|
9678
|
+
* project dependency on that same package selects the user's installed version.
|
|
9679
|
+
* A project package that does not describe the requested side is skipped instead of failing, which
|
|
9455
9680
|
* is what lets a two-sided project share one dependency list between sides.
|
|
9456
9681
|
*/
|
|
9457
9682
|
function listNpmPlugins(side, options = {}) {
|
|
@@ -9488,7 +9713,10 @@ function listNpmPlugins(side, options = {}) {
|
|
|
9488
9713
|
continue;
|
|
9489
9714
|
if (dependenciesOf(host)[name] === undefined)
|
|
9490
9715
|
continue;
|
|
9491
|
-
mappings.set(pluginId, {
|
|
9716
|
+
mappings.set(pluginId, {
|
|
9717
|
+
name,
|
|
9718
|
+
source: dependenciesOf(project)[name] === undefined ? "bundled" : "project"
|
|
9719
|
+
});
|
|
9492
9720
|
}
|
|
9493
9721
|
const hostDependencies = dependenciesOf(host);
|
|
9494
9722
|
const projectDependencies = dependenciesOf(project);
|
|
@@ -9663,9 +9891,9 @@ async function installNpmPlugin(options) {
|
|
|
9663
9891
|
if (!host)
|
|
9664
9892
|
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
9665
9893
|
const hostId = Object.entries(mappingsOf(host)).find(([, value]) => value === name && dependenciesOf(host)[name] !== undefined)?.[0];
|
|
9666
|
-
const targetRoot = hostId
|
|
9667
|
-
|
|
9668
|
-
|
|
9894
|
+
const targetRoot = hostId && dependenciesOf(readProjectManifest(projectRoot))[name] === undefined
|
|
9895
|
+
? packageRoot
|
|
9896
|
+
: projectRoot;
|
|
9669
9897
|
if (options.pluginId &&
|
|
9670
9898
|
mappingsOf(host)[options.pluginId] &&
|
|
9671
9899
|
dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
|
|
@@ -9712,7 +9940,7 @@ async function installNpmPlugin(options) {
|
|
|
9712
9940
|
const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
|
|
9713
9941
|
if (mapped !== undefined && mapped !== name)
|
|
9714
9942
|
throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
|
|
9715
|
-
const record = recordFromPackage(pluginId, name,
|
|
9943
|
+
const record = recordFromPackage(pluginId, name, targetRoot === packageRoot ? "bundled" : "project", entry.root, entry.metadata, side);
|
|
9716
9944
|
if (!record)
|
|
9717
9945
|
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
9718
9946
|
if (/^[0-9]/.test(version) && record.version !== version)
|
|
@@ -9795,7 +10023,9 @@ async function uninstallNpmPlugin(pluginId, options = {}) {
|
|
|
9795
10023
|
return withProjectLock(projectRoot, async () => {
|
|
9796
10024
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
9797
10025
|
const bundledName = mappingsOf(host)[pluginId];
|
|
9798
|
-
const targetRoot = typeof bundledName === "string" &&
|
|
10026
|
+
const targetRoot = typeof bundledName === "string" &&
|
|
10027
|
+
dependenciesOf(host)[bundledName] !== undefined &&
|
|
10028
|
+
dependenciesOf(readProjectManifest(projectRoot))[bundledName] === undefined
|
|
9799
10029
|
? packageRoot
|
|
9800
10030
|
: projectRoot;
|
|
9801
10031
|
const manifest = readProjectManifest(targetRoot);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementspanel/epanel-plugin-instance",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Application instance and Java runtime management.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Csituka_D <694428809@qq.com> (https://github.com/JessDaodao)",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@elementspanel/panel": "1.0.
|
|
54
|
-
"@elementspanel/daemon": "1.0.
|
|
53
|
+
"@elementspanel/panel": "^1.0.2",
|
|
54
|
+
"@elementspanel/daemon": "^1.0.2"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
57
|
"@elementspanel/panel": {
|
package/panel/backend/index.cjs
CHANGED
|
@@ -10536,9 +10536,9 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
|
|
|
10536
10536
|
/**
|
|
10537
10537
|
* Every npm plugin the runtime can load for a side, built-in and project-installed.
|
|
10538
10538
|
*
|
|
10539
|
-
* Built-in
|
|
10540
|
-
*
|
|
10541
|
-
* that does not describe the requested side is skipped instead of failing, which
|
|
10539
|
+
* Built-in package names win over a project mapping for the same id. An explicit
|
|
10540
|
+
* project dependency on that same package selects the user's installed version.
|
|
10541
|
+
* A project package that does not describe the requested side is skipped instead of failing, which
|
|
10542
10542
|
* is what lets a two-sided project share one dependency list between sides.
|
|
10543
10543
|
*/
|
|
10544
10544
|
function listNpmPlugins(side, options = {}) {
|
|
@@ -10576,7 +10576,10 @@ function listNpmPlugins(side, options = {}) {
|
|
|
10576
10576
|
continue;
|
|
10577
10577
|
if (dependenciesOf(host)[name] === undefined)
|
|
10578
10578
|
continue;
|
|
10579
|
-
mappings.set(pluginId, {
|
|
10579
|
+
mappings.set(pluginId, {
|
|
10580
|
+
name,
|
|
10581
|
+
source: dependenciesOf(project)[name] === undefined ? "bundled" : "project"
|
|
10582
|
+
});
|
|
10580
10583
|
}
|
|
10581
10584
|
const hostDependencies = dependenciesOf(host);
|
|
10582
10585
|
const projectDependencies = dependenciesOf(project);
|
|
@@ -10755,9 +10758,9 @@ async function installNpmPlugin(options) {
|
|
|
10755
10758
|
if (!host)
|
|
10756
10759
|
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
10757
10760
|
const hostId = (_a = Object.entries(mappingsOf(host)).find(([, value]) => value === name && dependenciesOf(host)[name] !== undefined)) === null || _a === void 0 ? void 0 : _a[0];
|
|
10758
|
-
const targetRoot = hostId
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
+
const targetRoot = hostId && dependenciesOf(readProjectManifest(projectRoot))[name] === undefined
|
|
10762
|
+
? packageRoot
|
|
10763
|
+
: projectRoot;
|
|
10761
10764
|
if (options.pluginId &&
|
|
10762
10765
|
mappingsOf(host)[options.pluginId] &&
|
|
10763
10766
|
dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
|
|
@@ -10804,7 +10807,7 @@ async function installNpmPlugin(options) {
|
|
|
10804
10807
|
const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
|
|
10805
10808
|
if (mapped !== undefined && mapped !== name)
|
|
10806
10809
|
throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
|
|
10807
|
-
const record = recordFromPackage(pluginId, name,
|
|
10810
|
+
const record = recordFromPackage(pluginId, name, targetRoot === packageRoot ? "bundled" : "project", entry.root, entry.metadata, side);
|
|
10808
10811
|
if (!record)
|
|
10809
10812
|
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
10810
10813
|
if (/^[0-9]/.test(version) && record.version !== version)
|
|
@@ -10888,7 +10891,9 @@ async function uninstallNpmPlugin(pluginId, options = {}) {
|
|
|
10888
10891
|
var _a, _b;
|
|
10889
10892
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
10890
10893
|
const bundledName = mappingsOf(host)[pluginId];
|
|
10891
|
-
const targetRoot = typeof bundledName === "string" &&
|
|
10894
|
+
const targetRoot = typeof bundledName === "string" &&
|
|
10895
|
+
dependenciesOf(host)[bundledName] !== undefined &&
|
|
10896
|
+
dependenciesOf(readProjectManifest(projectRoot))[bundledName] === undefined
|
|
10892
10897
|
? packageRoot
|
|
10893
10898
|
: projectRoot;
|
|
10894
10899
|
const manifest = readProjectManifest(targetRoot);
|