@elementspanel/epanel-plugin-config 1.0.0 → 1.0.2

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.
@@ -1,10 +1,107 @@
1
1
  /******/ (() => { // webpackBootstrap
2
- /******/ "use strict";
3
2
  /******/ var __webpack_modules__ = ({
4
3
 
4
+ /***/ "../../plugins/config/daemon/src/backend/settings.ts"
5
+ (__unused_webpack_module, exports, __webpack_require__) {
6
+
7
+ "use strict";
8
+
9
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10
+ exports.SettingsFormService = void 0;
11
+ const mcsmanager_common_1 = __webpack_require__("../common/src/index.ts");
12
+ const cordis_1 = __webpack_require__("cordis");
13
+ const cosmokit_1 = __webpack_require__("cosmokit");
14
+ /** Configuration declarations are owned by the daemon config plugin. */
15
+ class SettingsFormService extends cordis_1.Service {
16
+ declarations = [];
17
+ constructor(ctx) {
18
+ super(ctx, "settingsForm", true);
19
+ }
20
+ declare(declaration) {
21
+ const entry = { id: this.ctx.name, declaration };
22
+ return this.ctx.effect(() => {
23
+ this.declarations.push(entry);
24
+ return () => (0, cosmokit_1.remove)(this.declarations, entry);
25
+ });
26
+ }
27
+ declared() {
28
+ return this.declarations.map((entry) => entry.id);
29
+ }
30
+ async read(id) {
31
+ const entry = this.declarations.find((item) => item.id === id);
32
+ if (!entry) {
33
+ const schema = this.ctx.plugins.configuration(id);
34
+ return schema.fields.length ? schema : null;
35
+ }
36
+ return { id, fields: entry.declaration.fields(), values: await entry.declaration.read() };
37
+ }
38
+ async write(id, values) {
39
+ const entry = this.declarations.find((item) => item.id === id);
40
+ if (!entry)
41
+ return (await this.ctx.plugins.configure(id, values)).result;
42
+ return this.ctx.plugins.runExclusive(async () => {
43
+ (0, mcsmanager_common_1.validatePluginSettings)(entry.declaration.fields(), values);
44
+ await entry.declaration.write(values);
45
+ return {
46
+ saved: true,
47
+ application: entry.declaration.restartRequired ? "restart-required" : "applied"
48
+ };
49
+ });
50
+ }
51
+ }
52
+ exports.SettingsFormService = SettingsFormService;
53
+
54
+
55
+ /***/ },
56
+
57
+ /***/ "../../plugins/config/daemon/src/i18n/index.ts"
58
+ (__unused_webpack_module, exports, __webpack_require__) {
59
+
60
+ "use strict";
61
+
62
+ var __importDefault = (this && this.__importDefault) || function (mod) {
63
+ return (mod && mod.__esModule) ? mod : { "default": mod };
64
+ };
65
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
66
+ exports.localeMessages = void 0;
67
+ const de_DE_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/de_DE.json"));
68
+ const en_US_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/en_US.json"));
69
+ const es_ES_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/es_ES.json"));
70
+ const fr_FR_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/fr_FR.json"));
71
+ const ja_JP_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/ja_JP.json"));
72
+ const ko_KR_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/ko_KR.json"));
73
+ const pt_BR_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/pt_BR.json"));
74
+ const ru_RU_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/ru_RU.json"));
75
+ const th_TH_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/th_TH.json"));
76
+ const tr_TR_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/tr_TR.json"));
77
+ const zh_CN_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/zh_CN.json"));
78
+ const zh_TW_json_1 = __importDefault(__webpack_require__("../../plugins/config/daemon/src/i18n/zh_TW.json"));
79
+ // The refusals this plugin's protocol handlers throw. The panel surfaces them
80
+ // verbatim on its plugin manager page, so they are translated here rather than
81
+ // in the panel: this daemon knows which of its own plugins may not be switched
82
+ // off remotely, and it answers in the language it was told to speak.
83
+ exports.localeMessages = {
84
+ de_de: de_DE_json_1.default,
85
+ en_us: en_US_json_1.default,
86
+ es_es: es_ES_json_1.default,
87
+ fr_fr: fr_FR_json_1.default,
88
+ ja_jp: ja_JP_json_1.default,
89
+ ko_kr: ko_KR_json_1.default,
90
+ pt_br: pt_BR_json_1.default,
91
+ ru_ru: ru_RU_json_1.default,
92
+ th_th: th_TH_json_1.default,
93
+ tr_tr: tr_TR_json_1.default,
94
+ zh_cn: zh_CN_json_1.default,
95
+ zh_tw: zh_TW_json_1.default
96
+ };
97
+
98
+
99
+ /***/ },
100
+
5
101
  /***/ "../common/src/array.ts"
6
102
  (__unused_webpack_module, exports) {
7
103
 
104
+ "use strict";
8
105
 
9
106
  Object.defineProperty(exports, "__esModule", ({ value: true }));
10
107
  exports.arrayUnique = arrayUnique;
@@ -21,6 +118,7 @@ function arrayUnique(arr, felidName) {
21
118
  /***/ "../common/src/docker_utils.ts"
22
119
  (__unused_webpack_module, exports) {
23
120
 
121
+ "use strict";
24
122
 
25
123
  Object.defineProperty(exports, "__esModule", ({ value: true }));
26
124
  exports.normalizeDockerArchitecture = normalizeDockerArchitecture;
@@ -63,6 +161,7 @@ function normalizeDockerPlatform(platform) {
63
161
  /***/ "../common/src/global_variable.ts"
64
162
  (__unused_webpack_module, exports) {
65
163
 
164
+ "use strict";
66
165
 
67
166
  Object.defineProperty(exports, "__esModule", ({ value: true }));
68
167
  class GlobalVariable {
@@ -90,6 +189,7 @@ exports["default"] = GlobalVariable;
90
189
  /***/ "../common/src/index.ts"
91
190
  (__unused_webpack_module, exports, __webpack_require__) {
92
191
 
192
+ "use strict";
93
193
 
94
194
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
95
195
  if (k2 === undefined) k2 = k;
@@ -109,7 +209,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
109
209
  return (mod && mod.__esModule) ? mod : { "default": mod };
110
210
  };
111
211
  Object.defineProperty(exports, "__esModule", ({ value: true }));
112
- exports.StorageSubsystem = exports.MCServerStatus = exports.InstanceStreamListener = exports.GlobalVariable = exports.normalizeDockerPlatform = exports.normalizeDockerOS = exports.normalizeDockerArchitecture = exports.removeTrail = exports.uninstallNpmPlugin = exports.npmPluginRoots = exports.listNpmPlugins = exports.installNpmPlugin = exports.currentProjectRoot = exports.currentPackageRoot = exports.sortPlugins = exports.resolvePluginEntry = exports.readPluginManifest = exports.createFrontendPluginMetadata = exports.discoverPluginsFromRoots = exports.discoverPlugins = exports.arrayUnique = exports.toText = exports.toNumber = exports.toBoolean = exports.supposeValue = exports.isEmpty = exports.configureEntityParams = exports.systemInfo = exports.QueryWrapper = exports.QueryMapWrapper = exports.MySqlSource = exports.LocalFileSource = exports.killProcess = exports.ProcessWrapper = void 0;
212
+ exports.StorageSubsystem = exports.MCServerStatus = exports.InstanceStreamListener = exports.GlobalVariable = exports.normalizeDockerPlatform = exports.normalizeDockerOS = exports.normalizeDockerArchitecture = exports.removeTrail = exports.uninstallNpmPlugin = exports.isEssentialNpmPlugin = exports.npmPluginOperationPending = exports.npmPluginRoots = exports.listNpmPlugins = exports.installNpmPlugin = exports.currentProjectRoot = exports.currentPackageRoot = exports.sortPlugins = exports.resolvePluginEntry = exports.readPluginManifest = exports.createFrontendPluginMetadata = exports.discoverPluginsFromRoots = exports.discoverPlugins = exports.arrayUnique = exports.toText = exports.toNumber = exports.toBoolean = exports.supposeValue = exports.isEmpty = exports.configureEntityParams = exports.systemInfo = exports.QueryWrapper = exports.QueryMapWrapper = exports.MySqlSource = exports.LocalFileSource = exports.killProcess = exports.ProcessWrapper = void 0;
113
213
  const global_variable_1 = __importDefault(__webpack_require__("../common/src/global_variable.ts"));
114
214
  exports.GlobalVariable = global_variable_1.default;
115
215
  const instance_stream_1 = __importDefault(__webpack_require__("../common/src/instance_stream.ts"));
@@ -141,6 +241,8 @@ __exportStar(__webpack_require__("../common/src/plugin_contract.ts"), exports);
141
241
  __exportStar(__webpack_require__("../common/src/plugin_overrides.ts"), exports);
142
242
  __exportStar(__webpack_require__("../common/src/plugin_lifecycle.ts"), exports);
143
243
  __exportStar(__webpack_require__("../common/src/plugin_revision.ts"), exports);
244
+ __exportStar(__webpack_require__("../common/src/plugin_modules.ts"), exports);
245
+ __exportStar(__webpack_require__("../common/src/plugin_watch.ts"), exports);
144
246
  __exportStar(__webpack_require__("../common/src/runtime_paths.ts"), exports);
145
247
  var plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
146
248
  Object.defineProperty(exports, "discoverPlugins", ({ enumerable: true, get: function () { return plugin_manifest_1.discoverPlugins; } }));
@@ -155,6 +257,8 @@ Object.defineProperty(exports, "currentProjectRoot", ({ enumerable: true, get: f
155
257
  Object.defineProperty(exports, "installNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.installNpmPlugin; } }));
156
258
  Object.defineProperty(exports, "listNpmPlugins", ({ enumerable: true, get: function () { return plugin_npm_1.listNpmPlugins; } }));
157
259
  Object.defineProperty(exports, "npmPluginRoots", ({ enumerable: true, get: function () { return plugin_npm_1.npmPluginRoots; } }));
260
+ Object.defineProperty(exports, "npmPluginOperationPending", ({ enumerable: true, get: function () { return plugin_npm_1.npmPluginOperationPending; } }));
261
+ Object.defineProperty(exports, "isEssentialNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.isEssentialNpmPlugin; } }));
158
262
  Object.defineProperty(exports, "uninstallNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.uninstallNpmPlugin; } }));
159
263
  var string_utils_1 = __webpack_require__("../common/src/string_utils.ts");
160
264
  Object.defineProperty(exports, "removeTrail", ({ enumerable: true, get: function () { return string_utils_1.removeTrail; } }));
@@ -169,6 +273,7 @@ Object.defineProperty(exports, "normalizeDockerPlatform", ({ enumerable: true, g
169
273
  /***/ "../common/src/instance_stream.ts"
170
274
  (__unused_webpack_module, exports) {
171
275
 
276
+ "use strict";
172
277
 
173
278
  Object.defineProperty(exports, "__esModule", ({ value: true }));
174
279
  // Application instance data stream forwarding adapter
@@ -228,6 +333,7 @@ exports["default"] = InstanceStreamListener;
228
333
  /***/ "../common/src/mcping.ts"
229
334
  (__unused_webpack_module, exports, __webpack_require__) {
230
335
 
336
+ "use strict";
231
337
 
232
338
  var __importDefault = (this && this.__importDefault) || function (mod) {
233
339
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -346,6 +452,7 @@ exports["default"] = PingMinecraftServer;
346
452
  /***/ "../common/src/plugin_contract.ts"
347
453
  (__unused_webpack_module, exports) {
348
454
 
455
+ "use strict";
349
456
 
350
457
  Object.defineProperty(exports, "__esModule", ({ value: true }));
351
458
  exports.PLUGIN_SDK_VERSION = exports.PLUGIN_API_VERSION = void 0;
@@ -512,6 +619,7 @@ function validatePluginSettings(fields, values) {
512
619
  /***/ "../common/src/plugin_lifecycle.ts"
513
620
  (__unused_webpack_module, exports) {
514
621
 
622
+ "use strict";
515
623
 
516
624
  Object.defineProperty(exports, "__esModule", ({ value: true }));
517
625
  exports.trackPluginDisposal = trackPluginDisposal;
@@ -578,6 +686,7 @@ function trackPluginDisposal(ctx) {
578
686
  /***/ "../common/src/plugin_manifest.ts"
579
687
  (__unused_webpack_module, exports, __webpack_require__) {
580
688
 
689
+ "use strict";
581
690
 
582
691
  var __importDefault = (this && this.__importDefault) || function (mod) {
583
692
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -622,7 +731,52 @@ function createFrontendPluginMetadata(manifest, extras = {}) {
622
731
  };
623
732
  }
624
733
  const MANIFEST_FILES = ["plugin.json", "manifest.json", "package.json"];
625
- /** Reads the manifest of one plugin directory, or null if it has none. */
734
+ const ENTRY_FIELDS = ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"];
735
+ function objectValue(value) {
736
+ return value && typeof value === "object" && !Array.isArray(value)
737
+ ? value
738
+ : {};
739
+ }
740
+ /**
741
+ * Source plugins keep package.json beside plugin.json. Published npm plugins
742
+ * keep it at the package root, with runtime metadata under elementspanel.<side>.
743
+ * Stop at the first package boundary so a host's metadata cannot reach a plugin.
744
+ */
745
+ function readPackageMetadata(directory) {
746
+ const pluginDirectory = node_path_1.default.resolve(directory);
747
+ let current = pluginDirectory;
748
+ while (true) {
749
+ const filename = node_path_1.default.join(current, "package.json");
750
+ if (node_fs_1.default.existsSync(filename)) {
751
+ const { elementspanel, ...metadata } = objectValue(JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")));
752
+ const { id: packageId, panel, daemon, ...shared } = objectValue(elementspanel);
753
+ const definition = [panel, daemon]
754
+ .map(objectValue)
755
+ .find((side) => typeof side.directory === "string" &&
756
+ node_path_1.default.resolve(current, side.directory) === pluginDirectory);
757
+ if (current !== pluginDirectory && (typeof packageId !== "string" || !definition))
758
+ return {};
759
+ const { directory: _directory, folder: _folder, ...sideMetadata } = definition ?? {};
760
+ const result = {
761
+ ...metadata,
762
+ ...shared,
763
+ ...sideMetadata,
764
+ ...(packageId === undefined ? {} : { id: packageId })
765
+ };
766
+ if (metadata.version !== undefined)
767
+ result.version = metadata.version;
768
+ // npm's main/entry fields do not define a side's execution entry.
769
+ for (const field of ENTRY_FIELDS)
770
+ delete result[field];
771
+ return result;
772
+ }
773
+ const parent = node_path_1.default.dirname(current);
774
+ if (parent === current)
775
+ return {};
776
+ current = parent;
777
+ }
778
+ }
779
+ /** Combines execution entries with package metadata. Legacy manifests still load. */
626
780
  function readPluginManifest(directory, onWarning, fallbackId = node_path_1.default.basename(directory)) {
627
781
  for (const file of MANIFEST_FILES) {
628
782
  const filePath = node_path_1.default.join(directory, file);
@@ -632,10 +786,26 @@ function readPluginManifest(directory, onWarning, fallbackId = node_path_1.defau
632
786
  const value = JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
633
787
  if (!value || typeof value !== "object" || Array.isArray(value))
634
788
  return null;
635
- const id = typeof value.id === "string" ? value.id : fallbackId;
789
+ const packageMetadata = readPackageMetadata(directory);
790
+ const id = typeof packageMetadata.id === "string"
791
+ ? packageMetadata.id
792
+ : typeof value.id === "string"
793
+ ? value.id
794
+ : fallbackId;
636
795
  if (!id.trim())
637
796
  return null;
638
- return { ...value, id: id.trim() };
797
+ const manifest = {
798
+ ...value,
799
+ ...packageMetadata,
800
+ id: id.trim()
801
+ };
802
+ // Only execution paths come from plugin.json; package.json owns identity,
803
+ // version, dual status and all other metadata.
804
+ for (const field of ENTRY_FIELDS) {
805
+ if (value[field] !== undefined)
806
+ manifest[field] = value[field];
807
+ }
808
+ return manifest;
639
809
  }
640
810
  catch (error) {
641
811
  onWarning?.(`Failed to read plugin manifest: ${filePath}`, error);
@@ -733,6 +903,13 @@ function discoverPluginsFromRoots(roots, options) {
733
903
  discovered.push({
734
904
  ...plugin,
735
905
  manifest,
906
+ ...(root.packageRoot
907
+ ? {
908
+ packageRoot: root.packageRoot,
909
+ packageName: root.packageName,
910
+ npmSource: root.npmSource
911
+ }
912
+ : {}),
736
913
  ...(root.folder ? { folder: root.folder } : {})
737
914
  });
738
915
  }
@@ -746,11 +923,184 @@ function sortPlugins(plugins) {
746
923
  }
747
924
 
748
925
 
926
+ /***/ },
927
+
928
+ /***/ "../common/src/plugin_modules.ts"
929
+ (__unused_webpack_module, exports, __webpack_require__) {
930
+
931
+ "use strict";
932
+
933
+ var __importDefault = (this && this.__importDefault) || function (mod) {
934
+ return (mod && mod.__esModule) ? mod : { "default": mod };
935
+ };
936
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
937
+ exports.pluginBackendRevision = pluginBackendRevision;
938
+ exports.forgetPluginModules = forgetPluginModules;
939
+ exports.loadPluginModule = loadPluginModule;
940
+ const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
941
+ const node_path_1 = __importDefault(__webpack_require__("node:path"));
942
+ const node_url_1 = __webpack_require__("node:url");
943
+ const node_crypto_1 = __webpack_require__("node:crypto");
944
+ const node_module_1 = __webpack_require__("node:module");
945
+ const plugin_revision_1 = __webpack_require__("../common/src/plugin_revision.ts");
946
+ const REVISION = "epanelPluginRevision";
947
+ const graphs = new Map();
948
+ const previousRoots = new Map();
949
+ let hooksInstalled = false;
950
+ const sharedModules = new Set([
951
+ "cordis",
952
+ "@cordisjs/core",
953
+ "@cordisjs/logger",
954
+ "@cordisjs/timer",
955
+ "@cordisjs/schema",
956
+ "cosmokit",
957
+ "schemastery",
958
+ "reggol"
959
+ ]);
960
+ function dependencyRoots(directory) {
961
+ const visited = new Set([directory]);
962
+ const walk = (root) => {
963
+ const filename = node_path_1.default.join(root, "package.json");
964
+ if (!node_fs_1.default.existsSync(filename))
965
+ return;
966
+ const metadata = JSON.parse(node_fs_1.default.readFileSync(filename, "utf8"));
967
+ const localRequire = (0, node_module_1.createRequire)(filename);
968
+ for (const name of Object.keys({
969
+ ...metadata.dependencies,
970
+ ...metadata.optionalDependencies
971
+ })) {
972
+ if (sharedModules.has(name))
973
+ continue;
974
+ const target = (localRequire.resolve.paths(name) || [])
975
+ .map((directory) => node_path_1.default.join(directory, name, "package.json"))
976
+ .find((candidate) => node_fs_1.default.existsSync(candidate));
977
+ if (!target)
978
+ continue;
979
+ const dependency = node_fs_1.default.realpathSync(node_path_1.default.dirname(target));
980
+ if (visited.has(dependency))
981
+ continue;
982
+ visited.add(dependency);
983
+ walk(dependency);
984
+ }
985
+ };
986
+ walk(directory);
987
+ visited.delete(directory);
988
+ return [...visited];
989
+ }
990
+ /** Include transitive dependency versions so npm updates replace the affected graph. */
991
+ function pluginBackendRevision(entry, directory) {
992
+ const hash = (0, node_crypto_1.createHash)("sha256").update((0, plugin_revision_1.pluginDirectoryRevision)(node_path_1.default.dirname(entry)));
993
+ for (const root of [directory, ...dependencyRoots(directory)]) {
994
+ const filename = node_path_1.default.join(root, "package.json");
995
+ if (node_fs_1.default.existsSync(filename))
996
+ hash.update(root).update(node_fs_1.default.readFileSync(filename));
997
+ }
998
+ return hash.digest("hex").slice(0, 24);
999
+ }
1000
+ function within(root, filename) {
1001
+ const relative = node_path_1.default.relative(root, filename);
1002
+ return (relative === "" ||
1003
+ (!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== ".." && !node_path_1.default.isAbsolute(relative)));
1004
+ }
1005
+ /** Clear the plugin's complete CJS graph, keeping shared host modules intact. */
1006
+ function forgetPluginModules(directory) {
1007
+ const runtimeRequire = eval("require");
1008
+ const owned = [directory, ...(previousRoots.get(directory) || []), ...dependencyRoots(directory)];
1009
+ previousRoots.delete(directory);
1010
+ const belongs = (filename) => {
1011
+ const parts = filename.split(`${node_path_1.default.sep}node_modules${node_path_1.default.sep}`);
1012
+ if (parts.slice(1).some((part) => {
1013
+ const name = part.startsWith("@")
1014
+ ? part.split(node_path_1.default.sep).slice(0, 2).join("/")
1015
+ : part.split(node_path_1.default.sep)[0];
1016
+ return sharedModules.has(name);
1017
+ }))
1018
+ return false;
1019
+ return owned.some((root) => within(root, filename));
1020
+ };
1021
+ for (const filename of Object.keys(runtimeRequire.cache)) {
1022
+ if (belongs(filename))
1023
+ delete runtimeRequire.cache[filename];
1024
+ }
1025
+ for (const mod of Object.values(runtimeRequire.cache)) {
1026
+ if (mod)
1027
+ mod.children = mod.children.filter((child) => !belongs(child.filename));
1028
+ }
1029
+ // npm can move a dependency or change its main entry. Node's CJS resolver
1030
+ // retains those paths separately from require.cache.
1031
+ const pathCache = runtimeRequire("node:module")._pathCache;
1032
+ for (const [key, filename] of Object.entries(pathCache)) {
1033
+ if (belongs(filename) || owned.some((root) => key.includes(root)))
1034
+ delete pathCache[key];
1035
+ }
1036
+ }
1037
+ function isEsm(entry) {
1038
+ if (entry.endsWith(".mjs"))
1039
+ return true;
1040
+ if (entry.endsWith(".cjs"))
1041
+ return false;
1042
+ let directory = node_path_1.default.dirname(entry);
1043
+ while (true) {
1044
+ const filename = node_path_1.default.join(directory, "package.json");
1045
+ if (node_fs_1.default.existsSync(filename))
1046
+ return JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")).type === "module";
1047
+ const parent = node_path_1.default.dirname(directory);
1048
+ if (parent === directory)
1049
+ return false;
1050
+ directory = parent;
1051
+ }
1052
+ }
1053
+ /** Give an ESM entry and its local imports the same immutable revision URL. */
1054
+ async function loadPluginModule(entry, directory, revision) {
1055
+ const runtimeRequire = eval("require");
1056
+ previousRoots.set(directory, dependencyRoots(directory));
1057
+ if (!isEsm(entry)) {
1058
+ try {
1059
+ return runtimeRequire(entry);
1060
+ }
1061
+ catch (error) {
1062
+ if (error?.code !== "ERR_REQUIRE_ESM")
1063
+ throw error;
1064
+ }
1065
+ }
1066
+ graphs.set(revision, [directory, ...dependencyRoots(directory)]);
1067
+ if (!hooksInstalled) {
1068
+ const { registerHooks } = runtimeRequire("node:module");
1069
+ if (typeof registerHooks !== "function")
1070
+ throw new Error("ESM plugin updates require Node.js 22.15 or newer.");
1071
+ registerHooks({
1072
+ resolve(specifier, context, nextResolve) {
1073
+ const resolved = nextResolve(specifier, context);
1074
+ if (!context.parentURL?.startsWith("file:") || !resolved.url.startsWith("file:"))
1075
+ return resolved;
1076
+ const parent = new URL(context.parentURL);
1077
+ const parentRevision = parent.searchParams.get(REVISION);
1078
+ if (!parentRevision)
1079
+ return resolved;
1080
+ const roots = graphs.get(parentRevision);
1081
+ if (!roots?.some((root) => within(root, (0, node_url_1.fileURLToPath)(parent))) ||
1082
+ !roots.some((root) => within(root, (0, node_url_1.fileURLToPath)(resolved.url))))
1083
+ return resolved;
1084
+ const url = new URL(resolved.url);
1085
+ url.searchParams.set(REVISION, parentRevision);
1086
+ return { ...resolved, url: url.href };
1087
+ }
1088
+ });
1089
+ hooksInstalled = true;
1090
+ }
1091
+ const url = (0, node_url_1.pathToFileURL)(entry);
1092
+ url.searchParams.set(REVISION, revision);
1093
+ const dynamicImport = new Function("specifier", "return import(specifier)");
1094
+ return dynamicImport(url.href);
1095
+ }
1096
+
1097
+
749
1098
  /***/ },
750
1099
 
751
1100
  /***/ "../common/src/plugin_npm.ts"
752
1101
  (__unused_webpack_module, exports, __webpack_require__) {
753
1102
 
1103
+ "use strict";
754
1104
 
755
1105
  var __importDefault = (this && this.__importDefault) || function (mod) {
756
1106
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -760,15 +1110,19 @@ exports.currentPackageRoot = currentPackageRoot;
760
1110
  exports.currentProjectRoot = currentProjectRoot;
761
1111
  exports.npmPluginRoots = npmPluginRoots;
762
1112
  exports.listNpmPlugins = listNpmPlugins;
1113
+ exports.npmPluginOperationPending = npmPluginOperationPending;
1114
+ exports.isEssentialNpmPlugin = isEssentialNpmPlugin;
763
1115
  exports.installNpmPlugin = installNpmPlugin;
764
1116
  exports.uninstallNpmPlugin = uninstallNpmPlugin;
765
1117
  const child_process_1 = __webpack_require__("child_process");
766
1118
  const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
767
1119
  const path_1 = __importDefault(__webpack_require__("path"));
768
1120
  const module_1 = __webpack_require__("module");
1121
+ const plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
1122
+ const plugin_contract_1 = __webpack_require__("../common/src/plugin_contract.ts");
769
1123
  const PLUGIN_ID = /^[a-z][a-z0-9_-]{1,63}$/;
770
1124
  const PACKAGE_NAME = /^(?:@[a-z0-9_.-]+\/)?epanel-plugin-[a-z0-9_.-]+$/;
771
- const VERSION = /^[0-9][^\s/]{0,63}$/;
1125
+ const VERSION = /^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,63}$/;
772
1126
  function readJson(filename) {
773
1127
  try {
774
1128
  const parsed = JSON.parse(fs_extra_1.default.readFileSync(filename, "utf8"));
@@ -791,7 +1145,7 @@ function stringValue(value) {
791
1145
  return typeof value === "string" ? value : "";
792
1146
  }
793
1147
  function currentPackageRoot() {
794
- return path_1.default.resolve(process.env.EPANEL_PACKAGE_ROOT || process.cwd());
1148
+ return path_1.default.resolve(process.env.EPANEL_PACKAGE_ROOT || process.env.EPANEL_SOURCE_ROOT || process.cwd());
795
1149
  }
796
1150
  function currentProjectRoot(packageRoot = currentPackageRoot()) {
797
1151
  // The npm worker sets EPANEL_PROJECT_ROOT to the installation directory. In a
@@ -799,6 +1153,8 @@ function currentProjectRoot(packageRoot = currentPackageRoot()) {
799
1153
  if (process.env.EPANEL_PROJECT_ROOT)
800
1154
  return path_1.default.resolve(process.env.EPANEL_PROJECT_ROOT);
801
1155
  const scope = path_1.default.resolve(packageRoot, "..");
1156
+ if (path_1.default.basename(scope) === "core")
1157
+ return path_1.default.dirname(scope);
802
1158
  const nodeModules = path_1.default.dirname(scope);
803
1159
  if (path_1.default.basename(nodeModules) === "node_modules" && path_1.default.basename(scope).startsWith("@"))
804
1160
  return path_1.default.dirname(nodeModules);
@@ -834,8 +1190,20 @@ function pluginRecord(pluginId, name, source, packageRoot, directory, folder, ve
834
1190
  return { pluginId, name, version, sides: [], directory, folder, packageRoot, source };
835
1191
  }
836
1192
  function readPackageEntry(packageRoot, name) {
837
- const hostRequire = (0, module_1.createRequire)(path_1.default.join(packageRoot, "package.json"));
838
- const manifestPath = hostRequire.resolve(`${name}/package.json`);
1193
+ // Read package boundaries directly: Node caches require.resolve() paths even
1194
+ // after npm moves an installation between nested and hoisted node_modules.
1195
+ let manifestPath;
1196
+ for (const root of [packageRoot, currentProjectRoot(packageRoot)]) {
1197
+ const localRequire = (0, module_1.createRequire)(path_1.default.join(root, "package.json"));
1198
+ manifestPath = (localRequire.resolve.paths(name) || [])
1199
+ .map((directory) => path_1.default.join(directory, name, "package.json"))
1200
+ .find((filename) => fs_extra_1.default.existsSync(filename));
1201
+ if (manifestPath)
1202
+ break;
1203
+ }
1204
+ if (!manifestPath)
1205
+ throw new Error(`Missing npm plugin package: ${name}.`);
1206
+ manifestPath = fs_extra_1.default.realpathSync(manifestPath);
839
1207
  const metadata = readJson(manifestPath);
840
1208
  if (!metadata)
841
1209
  throw new Error(`Missing package metadata for ${name}.`);
@@ -849,7 +1217,10 @@ function sideDirectory(packageRoot, metadata, side) {
849
1217
  return undefined;
850
1218
  const target = path_1.default.resolve(packageRoot, directory);
851
1219
  const relative = path_1.default.relative(packageRoot, target);
852
- if (relative === "" || relative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(relative))
1220
+ if (relative === "" ||
1221
+ relative === ".." ||
1222
+ relative.startsWith(`..${path_1.default.sep}`) ||
1223
+ path_1.default.isAbsolute(relative))
853
1224
  return undefined;
854
1225
  return target;
855
1226
  }
@@ -866,7 +1237,7 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
866
1237
  return undefined;
867
1238
  const directory = sideDirectory(packageRoot, metadata, target);
868
1239
  const folder = stringValue(objectValue(elements[target])?.folder);
869
- if (!directory || !folder)
1240
+ if (!directory || !PLUGIN_ID.test(folder))
870
1241
  return undefined;
871
1242
  return {
872
1243
  ...pluginRecord(pluginId, name, source, packageRoot, directory, folder, version),
@@ -886,9 +1257,24 @@ function listNpmPlugins(side, options = {}) {
886
1257
  const host = readJson(path_1.default.join(packageRoot, "package.json"));
887
1258
  if (!host)
888
1259
  throw new Error(`Missing runtime package metadata: ${packageRoot}`);
889
- const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot) ? null : readProjectManifest(projectRoot);
1260
+ const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
1261
+ ? null
1262
+ : readProjectManifest(projectRoot);
890
1263
  const hostMappings = mappingsOf(host);
891
- const projectMappings = mappingsOf(project);
1264
+ const projectMappings = { ...mappingsOf(project) };
1265
+ for (const name of Object.keys(dependenciesOf(project))) {
1266
+ if (!PACKAGE_NAME.test(name) || Object.values(projectMappings).includes(name))
1267
+ continue;
1268
+ try {
1269
+ const entry = readPackageEntry(projectRoot, name);
1270
+ const id = stringValue(objectValue(entry.metadata.elementspanel)?.id);
1271
+ if (PLUGIN_ID.test(id) && projectMappings[id] === undefined)
1272
+ projectMappings[id] = name;
1273
+ }
1274
+ catch {
1275
+ /* npm may still be writing the package. */
1276
+ }
1277
+ }
892
1278
  const mappings = new Map();
893
1279
  for (const [pluginId, name] of Object.entries(projectMappings)) {
894
1280
  if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
@@ -898,6 +1284,8 @@ function listNpmPlugins(side, options = {}) {
898
1284
  for (const [pluginId, name] of Object.entries(hostMappings)) {
899
1285
  if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
900
1286
  continue;
1287
+ if (dependenciesOf(host)[name] === undefined)
1288
+ continue;
901
1289
  mappings.set(pluginId, { name, source: "bundled" });
902
1290
  }
903
1291
  const hostDependencies = dependenciesOf(host);
@@ -913,7 +1301,7 @@ function listNpmPlugins(side, options = {}) {
913
1301
  }
914
1302
  let entry;
915
1303
  try {
916
- entry = readPackageEntry(packageRoot, mapping.name);
1304
+ entry = readPackageEntry(mapping.source === "project" ? projectRoot : packageRoot, mapping.name);
917
1305
  }
918
1306
  catch (error) {
919
1307
  if (options.skipInvalid)
@@ -945,7 +1333,12 @@ function runNpm(args, cwd) {
945
1333
  : process.platform === "win32"
946
1334
  ? {
947
1335
  command: process.env.ComSpec || "cmd.exe",
948
- args: ["/d", "/s", "/c", `npm ${args.map((item) => `"${item.replace(/"/g, '""')}"`).join(" ")}`],
1336
+ args: [
1337
+ "/d",
1338
+ "/s",
1339
+ "/c",
1340
+ `npm ${args.map((item) => `"${item.replace(/"/g, '""')}"`).join(" ")}`
1341
+ ],
949
1342
  shell: false
950
1343
  }
951
1344
  : { command: "npm", args, shell: false };
@@ -964,9 +1357,10 @@ function runNpm(args, cwd) {
964
1357
  };
965
1358
  child.stdout?.on("data", append);
966
1359
  child.stderr?.on("data", append);
1360
+ const action = args.find((argument) => argument === "install" || argument === "uninstall") || "operation";
967
1361
  const timer = setTimeout(() => {
968
1362
  child.kill("SIGKILL");
969
- reject(new Error(`npm ${args[0] ?? ""} timed out.`));
1363
+ reject(new Error(`npm ${action} timed out.`));
970
1364
  }, 10 * 60 * 1000);
971
1365
  timer.unref?.();
972
1366
  child.once("error", (error) => {
@@ -977,15 +1371,69 @@ function runNpm(args, cwd) {
977
1371
  clearTimeout(timer);
978
1372
  if (code === 0)
979
1373
  return resolve(output);
980
- reject(new Error(`npm ${args[0] ?? ""} failed (${code}): ${output.trim()}`));
1374
+ reject(new Error(`npm ${action} failed (${code}): ${output.trim()}`));
981
1375
  });
982
1376
  });
983
1377
  }
984
- const operations = new Map();
1378
+ const operationsKey = Symbol.for("elementspanel.npm.operations");
1379
+ const shared = globalThis;
1380
+ const operations = (shared[operationsKey] ??= new Map());
1381
+ const LOCK_NAME = ".elementspanel-npm.lock";
1382
+ async function acquireProjectLock(projectRoot) {
1383
+ await fs_extra_1.default.ensureDir(projectRoot);
1384
+ const directory = path_1.default.join(projectRoot, LOCK_NAME);
1385
+ const deadline = Date.now() + 11 * 60 * 1000;
1386
+ while (true) {
1387
+ try {
1388
+ await fs_extra_1.default.mkdir(directory);
1389
+ await fs_extra_1.default.writeJson(path_1.default.join(directory, "owner.json"), { pid: process.pid });
1390
+ return () => fs_extra_1.default.remove(directory);
1391
+ }
1392
+ catch (error) {
1393
+ if (error.code !== "EEXIST")
1394
+ throw error;
1395
+ try {
1396
+ const owner = await fs_extra_1.default.readJson(path_1.default.join(directory, "owner.json"));
1397
+ try {
1398
+ process.kill(owner.pid, 0);
1399
+ }
1400
+ catch (failure) {
1401
+ if (failure.code === "ESRCH") {
1402
+ await fs_extra_1.default.remove(directory);
1403
+ continue;
1404
+ }
1405
+ }
1406
+ }
1407
+ catch {
1408
+ if (Date.now() - ((await fs_extra_1.default.stat(directory).catch(() => null))?.mtimeMs ?? Date.now()) >
1409
+ 60_000) {
1410
+ await fs_extra_1.default.remove(directory);
1411
+ continue;
1412
+ }
1413
+ }
1414
+ if (Date.now() > deadline)
1415
+ throw new Error("Another npm plugin operation is still running.");
1416
+ await new Promise((resolve) => setTimeout(resolve, 200));
1417
+ }
1418
+ }
1419
+ }
1420
+ function npmPluginOperationPending() {
1421
+ const { packageRoot, projectRoot } = npmPluginRoots();
1422
+ return (operations.size > 0 ||
1423
+ [packageRoot, projectRoot].some((root) => fs_extra_1.default.existsSync(path_1.default.join(root, LOCK_NAME))));
1424
+ }
985
1425
  function withProjectLock(projectRoot, operation) {
986
1426
  const key = process.platform === "win32" ? projectRoot.toLowerCase() : projectRoot;
987
1427
  const previous = operations.get(key) ?? Promise.resolve();
988
- const result = previous.then(operation);
1428
+ const result = previous.then(async () => {
1429
+ const release = await acquireProjectLock(projectRoot);
1430
+ try {
1431
+ return await operation();
1432
+ }
1433
+ finally {
1434
+ await release();
1435
+ }
1436
+ });
989
1437
  const settled = result.then(() => { }, () => { });
990
1438
  operations.set(key, settled);
991
1439
  return result.finally(() => {
@@ -993,18 +1441,18 @@ function withProjectLock(projectRoot, operation) {
993
1441
  operations.delete(key);
994
1442
  });
995
1443
  }
996
- /**
997
- * Installs (or updates) one npm plugin as a project dependency.
998
- *
999
- * The version is pinned with `--save-exact`: the panel performs updates, so a
1000
- * later bare `npm install` must not silently move the plugin to another release.
1001
- */
1444
+ /** Core services keep the connection and management interface available. */
1445
+ function isEssentialNpmPlugin(pluginId) {
1446
+ return ["i18n", "storage", "runtime", "server", "console", "config"].includes(pluginId);
1447
+ }
1448
+ /** npm changes dependencies and lockfiles; only append our plugin mapping afterwards. */
1002
1449
  async function installNpmPlugin(options) {
1003
- const { name, pluginId, version, side } = options;
1004
- if (!PLUGIN_ID.test(pluginId))
1005
- throw new Error(`Invalid plugin id: ${pluginId}`);
1450
+ const { name, side } = options;
1451
+ const version = options.version || "latest";
1006
1452
  if (!PACKAGE_NAME.test(name))
1007
1453
  throw new Error(`Invalid npm plugin package: ${name}`);
1454
+ if (options.pluginId && !PLUGIN_ID.test(options.pluginId))
1455
+ throw new Error(`Invalid plugin id: ${options.pluginId}`);
1008
1456
  if (!VERSION.test(version))
1009
1457
  throw new Error(`Invalid plugin version: ${version}`);
1010
1458
  const { packageRoot, projectRoot } = npmPluginRoots(options);
@@ -1012,64 +1460,154 @@ async function installNpmPlugin(options) {
1012
1460
  const host = readJson(path_1.default.join(packageRoot, "package.json"));
1013
1461
  if (!host)
1014
1462
  throw new Error(`Missing runtime package metadata: ${packageRoot}`);
1015
- if (mappingsOf(host)[pluginId] !== undefined)
1016
- throw new Error(`BUNDLED:${pluginId}`);
1017
- const existing = mappingsOf(readProjectManifest(projectRoot))[pluginId];
1018
- if (existing !== undefined && existing !== name)
1019
- throw new Error(`Plugin id ${pluginId} is already installed as ${String(existing)}.`);
1020
- await fs_extra_1.default.ensureDir(projectRoot);
1021
- await runNpm(["install", `${name}@${version}`, "--save", "--save-exact", "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot);
1022
- let entry;
1463
+ const hostId = Object.entries(mappingsOf(host)).find(([, value]) => value === name && dependenciesOf(host)[name] !== undefined)?.[0];
1464
+ const targetRoot = hostId ? packageRoot : projectRoot;
1465
+ if (hostId && isEssentialNpmPlugin(hostId))
1466
+ throw new Error(`The essential plugin "${hostId}" cannot be replaced while running.`);
1467
+ if (options.pluginId &&
1468
+ mappingsOf(host)[options.pluginId] &&
1469
+ dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
1470
+ mappingsOf(host)[options.pluginId] !== name)
1471
+ throw new Error(`BUNDLED:${options.pluginId}`);
1472
+ const original = readProjectManifest(targetRoot);
1473
+ const previous = dependenciesOf(original)[name];
1474
+ let previousVersion = previous;
1475
+ if (previous) {
1476
+ try {
1477
+ previousVersion = readPackageEntry(targetRoot, name).metadata.version || previous;
1478
+ }
1479
+ catch {
1480
+ /* Keep the declared spec if the previous package is incomplete. */
1481
+ }
1482
+ }
1483
+ const backups = new Map();
1484
+ for (const filename of ["package.json", "package-lock.json", "npm-shrinkwrap.json"]) {
1485
+ const target = path_1.default.join(targetRoot, filename);
1486
+ backups.set(target, fs_extra_1.default.existsSync(target) ? fs_extra_1.default.readFileSync(target) : null);
1487
+ }
1023
1488
  try {
1024
- entry = readPackageEntry(packageRoot, name);
1489
+ await runNpm([
1490
+ "--prefix",
1491
+ targetRoot,
1492
+ "install",
1493
+ `${name}@${version}`,
1494
+ "--save",
1495
+ "--save-exact",
1496
+ "--no-fund",
1497
+ "--no-audit",
1498
+ "--ignore-scripts"
1499
+ ], targetRoot);
1500
+ const entry = readPackageEntry(targetRoot, name);
1501
+ const pluginId = options.pluginId || stringValue(objectValue(entry.metadata.elementspanel)?.id);
1502
+ if (!PLUGIN_ID.test(pluginId))
1503
+ throw new Error(`Invalid plugin id in ${name}.`);
1504
+ if (mappingsOf(host)[pluginId] &&
1505
+ dependenciesOf(host)[String(mappingsOf(host)[pluginId])] !== undefined &&
1506
+ mappingsOf(host)[pluginId] !== name)
1507
+ throw new Error(`Plugin id ${pluginId} belongs to a bundled dependency.`);
1508
+ if (hostId && pluginId !== hostId)
1509
+ throw new Error(`The package ${name} changed its plugin id from ${hostId} to ${pluginId}.`);
1510
+ const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
1511
+ if (mapped !== undefined && mapped !== name)
1512
+ throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
1513
+ const record = recordFromPackage(pluginId, name, hostId ? "bundled" : "project", entry.root, entry.metadata, side);
1514
+ if (!record)
1515
+ throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
1516
+ if (/^[0-9]/.test(version) && record.version !== version)
1517
+ throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
1518
+ if (!process.env.EPANEL_PACKAGE_ROOT &&
1519
+ (0, plugin_manifest_1.discoverPlugins)(path_1.default.join(packageRoot, "plugins"), {
1520
+ entryFields: [],
1521
+ includeDisabled: true
1522
+ }).some((plugin) => plugin.manifest.id === pluginId))
1523
+ throw new Error(`Source plugin ${pluginId} cannot be replaced with an npm dependency.`);
1524
+ const manifest = (0, plugin_manifest_1.readPluginManifest)(record.directory);
1525
+ if (!manifest || manifest.id !== pluginId || manifest.version !== record.version)
1526
+ throw new Error(`The plugin manifest in ${name} does not match its package.`);
1527
+ (0, plugin_contract_1.validatePluginCompatibility)(manifest.elements);
1528
+ for (const field of ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"]) {
1529
+ if (typeof manifest[field] === "string" &&
1530
+ !(0, plugin_manifest_1.resolvePluginEntry)(record.directory, manifest, { entryFields: [field] }))
1531
+ throw new Error(`Invalid ${field} entry in ${name}.`);
1532
+ }
1533
+ const others = listNpmPlugins(side, { packageRoot, projectRoot, skipInvalid: true });
1534
+ if (others.some((plugin) => plugin.pluginId === pluginId && plugin.name !== name))
1535
+ throw new Error(`Plugin id ${pluginId} is already provided by another dependency.`);
1536
+ const collision = others.find((plugin) => plugin.pluginId !== pluginId && plugin.folder === record.folder);
1537
+ if (collision)
1538
+ throw new Error(`Plugin asset directory ${record.folder} is already used by ${collision.name}.`);
1539
+ const updated = readProjectManifest(targetRoot);
1540
+ updated.dependencies = { ...dependenciesOf(updated), [name]: record.version };
1541
+ updated.elementspanel = {
1542
+ ...objectValue(updated.elementspanel),
1543
+ plugins: { ...mappingsOf(updated), [pluginId]: name }
1544
+ };
1545
+ writeProjectManifest(targetRoot, updated);
1546
+ return record;
1025
1547
  }
1026
1548
  catch (error) {
1027
- await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
1549
+ try {
1550
+ await runNpm(previous
1551
+ ? [
1552
+ "--prefix",
1553
+ targetRoot,
1554
+ "install",
1555
+ `${name}@${String(previousVersion)}`,
1556
+ "--save",
1557
+ "--no-fund",
1558
+ "--no-audit",
1559
+ "--ignore-scripts"
1560
+ ]
1561
+ : [
1562
+ "--prefix",
1563
+ targetRoot,
1564
+ "uninstall",
1565
+ name,
1566
+ "--no-fund",
1567
+ "--no-audit",
1568
+ "--ignore-scripts"
1569
+ ], targetRoot);
1570
+ }
1571
+ catch (rollback) {
1572
+ throw new Error(`${String(error)}; npm rollback failed: ${String(rollback)}`);
1573
+ }
1574
+ finally {
1575
+ for (const [filename, content] of backups) {
1576
+ if (content === null)
1577
+ await fs_extra_1.default.remove(filename);
1578
+ else
1579
+ await fs_extra_1.default.writeFile(filename, content);
1580
+ }
1581
+ }
1028
1582
  throw error;
1029
1583
  }
1030
- const record = recordFromPackage(pluginId, name, "project", entry.root, entry.metadata, side);
1031
- if (!record) {
1032
- await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
1033
- throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
1034
- }
1035
- if (record.version !== version) {
1036
- await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
1037
- throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
1038
- }
1039
- const manifest = readProjectManifest(projectRoot);
1040
- const dependencies = { ...dependenciesOf(manifest), [name]: version };
1041
- const elementspanel = { ...(objectValue(manifest.elementspanel) ?? {}) };
1042
- const plugins = { ...mappingsOf(manifest), [pluginId]: name };
1043
- manifest.dependencies = dependencies;
1044
- manifest.elementspanel = { ...elementspanel, plugins };
1045
- writeProjectManifest(projectRoot, manifest);
1046
- return record;
1047
1584
  });
1048
1585
  }
1049
- /** Removes one project-installed npm plugin. Built-in packages are left alone. */
1586
+ /** Remove the dependency from the manifest that owns it, preserving user configuration. */
1050
1587
  async function uninstallNpmPlugin(pluginId, options = {}) {
1051
1588
  if (!PLUGIN_ID.test(pluginId))
1052
1589
  throw new Error(`Invalid plugin id: ${pluginId}`);
1590
+ if (isEssentialNpmPlugin(pluginId))
1591
+ throw new Error(`The essential plugin "${pluginId}" cannot be uninstalled.`);
1053
1592
  const { packageRoot, projectRoot } = npmPluginRoots(options);
1054
1593
  return withProjectLock(projectRoot, async () => {
1055
1594
  const host = readJson(path_1.default.join(packageRoot, "package.json"));
1056
- if (host && mappingsOf(host)[pluginId] !== undefined)
1057
- throw new Error(`BUNDLED:${pluginId}`);
1058
- const manifest = readProjectManifest(projectRoot);
1059
- const name = mappingsOf(manifest)[pluginId];
1595
+ const bundledName = mappingsOf(host)[pluginId];
1596
+ const targetRoot = typeof bundledName === "string" && dependenciesOf(host)[bundledName] !== undefined
1597
+ ? packageRoot
1598
+ : projectRoot;
1599
+ const manifest = readProjectManifest(targetRoot);
1600
+ const name = mappingsOf(manifest)[pluginId] ??
1601
+ listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId)?.name;
1060
1602
  if (typeof name !== "string" || !PACKAGE_NAME.test(name))
1061
1603
  return null;
1062
1604
  const record = listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId);
1063
- await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot);
1064
- const plugins = { ...mappingsOf(manifest) };
1605
+ await runNpm(["--prefix", targetRoot, "uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], targetRoot);
1606
+ const updated = readProjectManifest(targetRoot);
1607
+ const plugins = { ...mappingsOf(updated) };
1065
1608
  delete plugins[pluginId];
1066
- const elementspanel = { ...(objectValue(manifest.elementspanel) ?? {}) };
1067
- if (Object.keys(plugins).length)
1068
- elementspanel.plugins = plugins;
1069
- else
1070
- delete elementspanel.plugins;
1071
- manifest.elementspanel = elementspanel;
1072
- writeProjectManifest(projectRoot, manifest);
1609
+ updated.elementspanel = { ...objectValue(updated.elementspanel), plugins };
1610
+ writeProjectManifest(targetRoot, updated);
1073
1611
  return record ?? null;
1074
1612
  });
1075
1613
  }
@@ -1080,6 +1618,7 @@ async function uninstallNpmPlugin(pluginId, options = {}) {
1080
1618
  /***/ "../common/src/plugin_overrides.ts"
1081
1619
  (__unused_webpack_module, exports, __webpack_require__) {
1082
1620
 
1621
+ "use strict";
1083
1622
 
1084
1623
  var __importDefault = (this && this.__importDefault) || function (mod) {
1085
1624
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -1168,6 +1707,7 @@ function writePluginOverride(id, patch, filename = pluginOverridesPath()) {
1168
1707
  /***/ "../common/src/plugin_revision.ts"
1169
1708
  (__unused_webpack_module, exports, __webpack_require__) {
1170
1709
 
1710
+ "use strict";
1171
1711
 
1172
1712
  var __importDefault = (this && this.__importDefault) || function (mod) {
1173
1713
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -1218,11 +1758,89 @@ function pluginDirectoryRevision(directory) {
1218
1758
  }
1219
1759
 
1220
1760
 
1761
+ /***/ },
1762
+
1763
+ /***/ "../common/src/plugin_watch.ts"
1764
+ (__unused_webpack_module, exports, __webpack_require__) {
1765
+
1766
+ "use strict";
1767
+
1768
+ var __importDefault = (this && this.__importDefault) || function (mod) {
1769
+ return (mod && mod.__esModule) ? mod : { "default": mod };
1770
+ };
1771
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1772
+ exports.watchNpmPlugins = watchNpmPlugins;
1773
+ const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
1774
+ const node_path_1 = __importDefault(__webpack_require__("node:path"));
1775
+ const plugin_npm_1 = __webpack_require__("../common/src/plugin_npm.ts");
1776
+ /** Poll package boundaries, allowing npm to finish replacing files before reconciliation. */
1777
+ function watchNpmPlugins(ctx, side, reload, changed) {
1778
+ const stamp = () => {
1779
+ const { packageRoot, projectRoot } = (0, plugin_npm_1.npmPluginRoots)();
1780
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(packageRoot, "package.json")))
1781
+ return "";
1782
+ const files = [];
1783
+ for (const root of [packageRoot, projectRoot])
1784
+ for (const name of [
1785
+ "package.json",
1786
+ "package-lock.json",
1787
+ "npm-shrinkwrap.json",
1788
+ "node_modules/.package-lock.json"
1789
+ ])
1790
+ files.push(node_path_1.default.join(root, name));
1791
+ for (const plugin of (0, plugin_npm_1.listNpmPlugins)(side, { skipInvalid: true }))
1792
+ files.push(node_path_1.default.join(plugin.packageRoot, "package.json"), node_path_1.default.join(plugin.directory, "plugin.json"));
1793
+ return JSON.stringify(files.map((filename) => {
1794
+ try {
1795
+ const stat = node_fs_1.default.statSync(filename);
1796
+ return [filename, stat.size, stat.mtimeMs, stat.ctimeMs];
1797
+ }
1798
+ catch {
1799
+ return [filename, null];
1800
+ }
1801
+ }));
1802
+ };
1803
+ let applied = stamp();
1804
+ let candidate = applied;
1805
+ let busy = false;
1806
+ ctx.setInterval(() => {
1807
+ if (busy || (0, plugin_npm_1.npmPluginOperationPending)())
1808
+ return;
1809
+ try {
1810
+ const next = stamp();
1811
+ if (next === applied) {
1812
+ candidate = next;
1813
+ return;
1814
+ }
1815
+ if (next !== candidate) {
1816
+ candidate = next;
1817
+ return;
1818
+ }
1819
+ busy = true;
1820
+ void reload()
1821
+ .then(() => {
1822
+ applied = next;
1823
+ candidate = next;
1824
+ changed?.();
1825
+ })
1826
+ .catch((error) => ctx.logger.warn("Failed to synchronize npm plugins", error))
1827
+ .finally(() => {
1828
+ busy = false;
1829
+ });
1830
+ }
1831
+ catch (error) {
1832
+ ctx.logger.warn("Failed to inspect npm plugins", error);
1833
+ }
1834
+ }, 2000);
1835
+ }
1836
+
1837
+
1221
1838
  /***/ },
1222
1839
 
1223
1840
  /***/ "../common/src/process_tools.ts"
1224
1841
  (__unused_webpack_module, exports, __webpack_require__) {
1225
1842
 
1843
+ "use strict";
1226
1844
 
1227
1845
  var __importDefault = (this && this.__importDefault) || function (mod) {
1228
1846
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -1387,6 +2005,7 @@ function killProcess(pid, process, signal) {
1387
2005
  /***/ "../common/src/query_wrapper.ts"
1388
2006
  (__unused_webpack_module, exports) {
1389
2007
 
2008
+ "use strict";
1390
2009
 
1391
2010
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1392
2011
  exports.QueryWrapper = exports.LocalFileSource = exports.MySqlSource = exports.QueryMapWrapper = void 0;
@@ -1500,6 +2119,7 @@ exports.QueryWrapper = QueryWrapper;
1500
2119
  /***/ "../common/src/runtime_paths.ts"
1501
2120
  (__unused_webpack_module, exports, __webpack_require__) {
1502
2121
 
2122
+ "use strict";
1503
2123
 
1504
2124
  var __importDefault = (this && this.__importDefault) || function (mod) {
1505
2125
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -1514,22 +2134,24 @@ exports.ensureExecutable = ensureExecutable;
1514
2134
  const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
1515
2135
  const node_os_1 = __importDefault(__webpack_require__("node:os"));
1516
2136
  const node_path_1 = __importDefault(__webpack_require__("node:path"));
2137
+ // @ts-ignore Shared source layout is also consumed by the Node build tools.
2138
+ const workspace_layout_cjs_1 = __webpack_require__("../../scripts/workspace-layout.cjs");
1517
2139
  /** Set by the npm worker before any application module is loaded. */
1518
2140
  function isPackagedRuntime() {
1519
2141
  return Boolean(process.env.EPANEL_PACKAGE_ROOT);
1520
2142
  }
1521
2143
  /** Code and bundled assets belong to the package; cwd always belongs to the user. */
1522
2144
  function packagePath(...parts) {
1523
- return node_path_1.default.resolve(process.env.EPANEL_PACKAGE_ROOT || process.cwd(), ...parts);
2145
+ return node_path_1.default.resolve(process.env.EPANEL_PACKAGE_ROOT || process.env.EPANEL_SOURCE_ROOT || process.cwd(), ...parts);
1524
2146
  }
1525
2147
  function npmPluginRoots(side) {
1526
2148
  // The same resolver is used by the CLI before the bundled application starts.
1527
- // A development checkout keeps it in the repository, beside the side packages,
2149
+ // A development checkout keeps it in the repository above core/<side>,
1528
2150
  // so project-installed npm plugins work there as well.
1529
2151
  const runtimeRequire = eval("require");
1530
2152
  const resolver = isPackagedRuntime()
1531
2153
  ? packagePath("runtime/npm-plugins.cjs")
1532
- : node_path_1.default.resolve(packagePath(), "..", "scripts", "runtime", "npm-plugins.cjs");
2154
+ : node_path_1.default.resolve(packagePath(), "../..", "scripts", "runtime", "npm-plugins.cjs");
1533
2155
  // A development process may run from a fixture directory without a runtime
1534
2156
  // package; there is nothing to resolve in that case.
1535
2157
  if (!node_fs_1.default.existsSync(node_path_1.default.join(packagePath(), "package.json")))
@@ -1539,6 +2161,9 @@ function npmPluginRoots(side) {
1539
2161
  const { resolveNpmPlugins } = runtimeRequire(resolver);
1540
2162
  return resolveNpmPlugins(packagePath(), side, { skipInvalid: true }).map((record) => ({
1541
2163
  directory: record.directory,
2164
+ packageRoot: record.packageRoot,
2165
+ packageName: record.name,
2166
+ npmSource: record.source,
1542
2167
  ...(record.folder ? { folder: record.folder } : {}),
1543
2168
  // Built-in packages are part of the runtime's default configuration. A
1544
2169
  // project dependency is installed but remains dormant until the user
@@ -1547,9 +2172,16 @@ function npmPluginRoots(side) {
1547
2172
  }));
1548
2173
  }
1549
2174
  function packagePluginRoots(side) {
2175
+ const sourceCheckout = process.env.EPANEL_SOURCE_ROOT || node_path_1.default.basename(node_path_1.default.dirname(packagePath())) === "core";
1550
2176
  const roots = isPackagedRuntime()
1551
2177
  ? []
1552
- : [{ directory: packagePath("plugins"), defaultEnabled: true }];
2178
+ : !sourceCheckout
2179
+ ? [{ directory: packagePath("plugins"), defaultEnabled: true }]
2180
+ : (0, workspace_layout_cjs_1.sourcePlugins)(side, node_path_1.default.resolve(packagePath(), "../..")).map((plugin) => ({
2181
+ directory: plugin.directory,
2182
+ folder: plugin.folder,
2183
+ defaultEnabled: true
2184
+ }));
1553
2185
  // Project-installed npm plugins load in development too: they are dependencies
1554
2186
  // of the development project, not of the published runtime package.
1555
2187
  roots.push(...npmPluginRoots(side));
@@ -1570,7 +2202,7 @@ function bundledToolPath(name) {
1570
2202
  // Published daemon packages download native tools into the installation's
1571
2203
  // writable lib directory on first start. Development checkouts use the same
1572
2204
  // repository-local sibling of data and logs.
1573
- const root = isPackagedRuntime() ? node_path_1.default.resolve(process.cwd(), "lib") : packagePath("lib");
2205
+ const root = node_path_1.default.resolve(process.cwd(), "lib");
1574
2206
  const primary = node_path_1.default.join(root, filename);
1575
2207
  // Zip-Tools names its Intel macOS binary "amd64", while Node reports "x64".
1576
2208
  if (!node_fs_1.default.existsSync(primary) && name === "file_zip" && platform === "darwin" && arch === "x64") {
@@ -1598,6 +2230,7 @@ function ensureExecutable(filename) {
1598
2230
  /***/ "../common/src/string_utils.ts"
1599
2231
  (__unused_webpack_module, exports) {
1600
2232
 
2233
+ "use strict";
1601
2234
 
1602
2235
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1603
2236
  exports.removeTrail = removeTrail;
@@ -1616,6 +2249,7 @@ function removeTrail(origin, trail) {
1616
2249
  /***/ "../common/src/system_info.ts"
1617
2250
  (__unused_webpack_module, exports, __webpack_require__) {
1618
2251
 
2252
+ "use strict";
1619
2253
 
1620
2254
  var __importDefault = (this && this.__importDefault) || function (mod) {
1621
2255
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -1708,6 +2342,7 @@ function systemInfo() {
1708
2342
  /***/ "../common/src/system_storage.ts"
1709
2343
  (__unused_webpack_module, exports, __webpack_require__) {
1710
2344
 
2345
+ "use strict";
1711
2346
 
1712
2347
  var __importDefault = (this && this.__importDefault) || function (mod) {
1713
2348
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -1839,6 +2474,7 @@ exports["default"] = StorageSubsystem;
1839
2474
  /***/ "../common/src/typecheck.ts"
1840
2475
  (__unused_webpack_module, exports) {
1841
2476
 
2477
+ "use strict";
1842
2478
 
1843
2479
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1844
2480
  exports.configureEntityParams = configureEntityParams;
@@ -1913,113 +2549,12 @@ function supposeValue(v, def) {
1913
2549
  }
1914
2550
 
1915
2551
 
1916
- /***/ },
1917
-
1918
- /***/ "./plugins/config/src/backend/settings.ts"
1919
- (__unused_webpack_module, exports, __webpack_require__) {
1920
-
1921
-
1922
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1923
- exports.SettingsFormService = void 0;
1924
- const mcsmanager_common_1 = __webpack_require__("../common/src/index.ts");
1925
- const cordis_1 = __webpack_require__("cordis");
1926
- const cosmokit_1 = __webpack_require__("cosmokit");
1927
- /** Configuration declarations are owned by the daemon config plugin. */
1928
- class SettingsFormService extends cordis_1.Service {
1929
- declarations = [];
1930
- constructor(ctx) {
1931
- super(ctx, "settingsForm", true);
1932
- }
1933
- declare(declaration) {
1934
- const entry = { id: this.ctx.name, declaration };
1935
- return this.ctx.effect(() => {
1936
- this.declarations.push(entry);
1937
- return () => (0, cosmokit_1.remove)(this.declarations, entry);
1938
- });
1939
- }
1940
- declared() {
1941
- return this.declarations.map((entry) => entry.id);
1942
- }
1943
- async read(id) {
1944
- const entry = this.declarations.find((item) => item.id === id);
1945
- if (!entry) {
1946
- const schema = this.ctx.plugins.configuration(id);
1947
- return schema.fields.length ? schema : null;
1948
- }
1949
- return { id, fields: entry.declaration.fields(), values: await entry.declaration.read() };
1950
- }
1951
- async write(id, values) {
1952
- const entry = this.declarations.find((item) => item.id === id);
1953
- if (!entry)
1954
- return (await this.ctx.plugins.configure(id, values)).result;
1955
- return this.ctx.plugins.runExclusive(async () => {
1956
- (0, mcsmanager_common_1.validatePluginSettings)(entry.declaration.fields(), values);
1957
- await entry.declaration.write(values);
1958
- return {
1959
- saved: true,
1960
- application: entry.declaration.restartRequired ? "restart-required" : "applied"
1961
- };
1962
- });
1963
- }
1964
- }
1965
- exports.SettingsFormService = SettingsFormService;
1966
-
1967
-
1968
- /***/ },
1969
-
1970
- /***/ "./plugins/config/src/i18n/index.ts"
1971
- (__unused_webpack_module, exports, __webpack_require__) {
1972
-
1973
-
1974
- var __importDefault = (this && this.__importDefault) || function (mod) {
1975
- return (mod && mod.__esModule) ? mod : { "default": mod };
1976
- };
1977
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1978
- exports.localeMessages = void 0;
1979
- const de_DE_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/de_DE.json"));
1980
- const en_US_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/en_US.json"));
1981
- const es_ES_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/es_ES.json"));
1982
- const fr_FR_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/fr_FR.json"));
1983
- const ja_JP_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/ja_JP.json"));
1984
- const ko_KR_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/ko_KR.json"));
1985
- const pt_BR_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/pt_BR.json"));
1986
- const ru_RU_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/ru_RU.json"));
1987
- const th_TH_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/th_TH.json"));
1988
- const tr_TR_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/tr_TR.json"));
1989
- const zh_CN_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/zh_CN.json"));
1990
- const zh_TW_json_1 = __importDefault(__webpack_require__("./plugins/config/src/i18n/zh_TW.json"));
1991
- // The refusals this plugin's protocol handlers throw. The panel surfaces them
1992
- // verbatim on its plugin manager page, so they are translated here rather than
1993
- // in the panel: this daemon knows which of its own plugins may not be switched
1994
- // off remotely, and it answers in the language it was told to speak.
1995
- exports.localeMessages = {
1996
- de_de: de_DE_json_1.default,
1997
- en_us: en_US_json_1.default,
1998
- es_es: es_ES_json_1.default,
1999
- fr_fr: fr_FR_json_1.default,
2000
- ja_jp: ja_JP_json_1.default,
2001
- ko_kr: ko_KR_json_1.default,
2002
- pt_br: pt_BR_json_1.default,
2003
- ru_ru: ru_RU_json_1.default,
2004
- th_th: th_TH_json_1.default,
2005
- tr_tr: tr_TR_json_1.default,
2006
- zh_cn: zh_CN_json_1.default,
2007
- zh_tw: zh_TW_json_1.default
2008
- };
2009
-
2010
-
2011
- /***/ },
2012
-
2013
- /***/ "events"
2014
- (module) {
2015
-
2016
- module.exports = require("events");
2017
-
2018
2552
  /***/ },
2019
2553
 
2020
2554
  /***/ "fs-extra"
2021
2555
  (module) {
2022
2556
 
2557
+ "use strict";
2023
2558
  module.exports = require("fs-extra");
2024
2559
 
2025
2560
  /***/ },
@@ -2027,6 +2562,7 @@ module.exports = require("fs-extra");
2027
2562
  /***/ "iconv-lite"
2028
2563
  (module) {
2029
2564
 
2565
+ "use strict";
2030
2566
  module.exports = require("iconv-lite");
2031
2567
 
2032
2568
  /***/ },
@@ -2034,6 +2570,7 @@ module.exports = require("iconv-lite");
2034
2570
  /***/ "os-utils"
2035
2571
  (module) {
2036
2572
 
2573
+ "use strict";
2037
2574
  module.exports = require("os-utils");
2038
2575
 
2039
2576
  /***/ },
@@ -2041,6 +2578,7 @@ module.exports = require("os-utils");
2041
2578
  /***/ "cordis"
2042
2579
  (module) {
2043
2580
 
2581
+ "use strict";
2044
2582
  module.exports = require("cordis");
2045
2583
 
2046
2584
  /***/ },
@@ -2048,6 +2586,7 @@ module.exports = require("cordis");
2048
2586
  /***/ "cosmokit"
2049
2587
  (module) {
2050
2588
 
2589
+ "use strict";
2051
2590
  module.exports = require("cosmokit");
2052
2591
 
2053
2592
  /***/ },
@@ -2055,6 +2594,7 @@ module.exports = require("cosmokit");
2055
2594
  /***/ "child_process"
2056
2595
  (module) {
2057
2596
 
2597
+ "use strict";
2058
2598
  module.exports = require("child_process");
2059
2599
 
2060
2600
  /***/ },
@@ -2062,13 +2602,23 @@ module.exports = require("child_process");
2062
2602
  /***/ "crypto"
2063
2603
  (module) {
2064
2604
 
2605
+ "use strict";
2065
2606
  module.exports = require("crypto");
2066
2607
 
2067
2608
  /***/ },
2068
2609
 
2610
+ /***/ "events"
2611
+ (module) {
2612
+
2613
+ "use strict";
2614
+ module.exports = require("events");
2615
+
2616
+ /***/ },
2617
+
2069
2618
  /***/ "fs"
2070
2619
  (module) {
2071
2620
 
2621
+ "use strict";
2072
2622
  module.exports = require("fs");
2073
2623
 
2074
2624
  /***/ },
@@ -2076,6 +2626,7 @@ module.exports = require("fs");
2076
2626
  /***/ "module"
2077
2627
  (module) {
2078
2628
 
2629
+ "use strict";
2079
2630
  module.exports = require("module");
2080
2631
 
2081
2632
  /***/ },
@@ -2083,6 +2634,7 @@ module.exports = require("module");
2083
2634
  /***/ "net"
2084
2635
  (module) {
2085
2636
 
2637
+ "use strict";
2086
2638
  module.exports = require("net");
2087
2639
 
2088
2640
  /***/ },
@@ -2090,6 +2642,7 @@ module.exports = require("net");
2090
2642
  /***/ "node:crypto"
2091
2643
  (module) {
2092
2644
 
2645
+ "use strict";
2093
2646
  module.exports = require("node:crypto");
2094
2647
 
2095
2648
  /***/ },
@@ -2097,13 +2650,23 @@ module.exports = require("node:crypto");
2097
2650
  /***/ "node:fs"
2098
2651
  (module) {
2099
2652
 
2653
+ "use strict";
2100
2654
  module.exports = require("node:fs");
2101
2655
 
2102
2656
  /***/ },
2103
2657
 
2658
+ /***/ "node:module"
2659
+ (module) {
2660
+
2661
+ "use strict";
2662
+ module.exports = require("node:module");
2663
+
2664
+ /***/ },
2665
+
2104
2666
  /***/ "node:os"
2105
2667
  (module) {
2106
2668
 
2669
+ "use strict";
2107
2670
  module.exports = require("node:os");
2108
2671
 
2109
2672
  /***/ },
@@ -2111,13 +2674,23 @@ module.exports = require("node:os");
2111
2674
  /***/ "node:path"
2112
2675
  (module) {
2113
2676
 
2677
+ "use strict";
2114
2678
  module.exports = require("node:path");
2115
2679
 
2116
2680
  /***/ },
2117
2681
 
2682
+ /***/ "node:url"
2683
+ (module) {
2684
+
2685
+ "use strict";
2686
+ module.exports = require("node:url");
2687
+
2688
+ /***/ },
2689
+
2118
2690
  /***/ "os"
2119
2691
  (module) {
2120
2692
 
2693
+ "use strict";
2121
2694
  module.exports = require("os");
2122
2695
 
2123
2696
  /***/ },
@@ -2125,6 +2698,7 @@ module.exports = require("os");
2125
2698
  /***/ "path"
2126
2699
  (module) {
2127
2700
 
2701
+ "use strict";
2128
2702
  module.exports = require("path");
2129
2703
 
2130
2704
  /***/ },
@@ -2132,91 +2706,158 @@ module.exports = require("path");
2132
2706
  /***/ "process"
2133
2707
  (module) {
2134
2708
 
2709
+ "use strict";
2135
2710
  module.exports = require("process");
2136
2711
 
2137
2712
  /***/ },
2138
2713
 
2139
- /***/ "./plugins/config/src/i18n/de_DE.json"
2714
+ /***/ "../../scripts/workspace-layout.cjs"
2715
+ (module, __unused_webpack_exports, __webpack_require__) {
2716
+
2717
+ const fs = __webpack_require__("node:fs");
2718
+ const path = __webpack_require__("node:path");
2719
+
2720
+ const repository = path.resolve(__dirname, "..");
2721
+ const coreDirectory = (side, root = repository) => path.join(root, "core", side);
2722
+ const developmentDirectory = (side, root = repository) => path.join(root, "data", side);
2723
+
2724
+ /** Source packages use the same per-side metadata as their published npm packages. */
2725
+ function sourcePlugins(side, root = repository) {
2726
+ if (!["panel", "daemon"].includes(side)) throw new Error(`Unknown plugin side: ${side}`);
2727
+ const directory = path.join(root, "plugins");
2728
+ if (!fs.existsSync(directory)) return [];
2729
+ const plugins = [];
2730
+ for (const item of fs.readdirSync(directory, { withFileTypes: true })) {
2731
+ if (!item.isDirectory()) continue;
2732
+ const packageRoot = path.join(directory, item.name);
2733
+ const filename = path.join(packageRoot, "package.json");
2734
+ if (!fs.existsSync(filename)) continue;
2735
+ const metadata = JSON.parse(fs.readFileSync(filename, "utf8"));
2736
+ const definition = metadata.elementspanel?.[side];
2737
+ if (!definition) continue;
2738
+ const id = metadata.elementspanel.id;
2739
+ if (typeof id !== "string" || typeof definition.directory !== "string")
2740
+ throw new Error(`Invalid ${side} plugin metadata: ${filename}`);
2741
+ const pluginDirectory = path.resolve(packageRoot, definition.directory);
2742
+ const relative = path.relative(packageRoot, pluginDirectory);
2743
+ if (
2744
+ !relative ||
2745
+ relative === ".." ||
2746
+ relative.startsWith(`..${path.sep}`) ||
2747
+ path.isAbsolute(relative)
2748
+ )
2749
+ throw new Error(`Plugin directory must stay inside its package: ${filename}`);
2750
+ if (!fs.existsSync(path.join(pluginDirectory, "plugin.json")))
2751
+ throw new Error(`Missing ${side} plugin manifest: ${pluginDirectory}`);
2752
+ plugins.push({
2753
+ id,
2754
+ folder: definition.folder || id,
2755
+ directory: pluginDirectory,
2756
+ packageRoot,
2757
+ metadata
2758
+ });
2759
+ }
2760
+ return plugins.sort((a, b) => a.id.localeCompare(b.id));
2761
+ }
2762
+
2763
+ module.exports = { repository, coreDirectory, developmentDirectory, sourcePlugins };
2764
+
2765
+
2766
+ /***/ },
2767
+
2768
+ /***/ "../../plugins/config/daemon/src/i18n/de_DE.json"
2140
2769
  (module) {
2141
2770
 
2142
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Der Webserver kann nicht aus der Ferne deaktiviert werden: Das Panel würde die Verbindung verlieren, über die es diesen Daemon verwaltet. Setze enabled auf dem Host in seiner plugin.json auf false.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Eine Plugin-ID ist erforderlich.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Die Plugin-Verwaltung kann sich nicht selbst deaktivieren: Das Panel würde die Events verlieren, mit denen es diesen Daemon verwaltet. Setze enabled auf dem Host in seiner plugin.json auf false."}');
2771
+ "use strict";
2772
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Der Webserver kann nicht aus der Ferne deaktiviert werden: Das Panel würde die Verbindung verlieren, über die es diesen Daemon verwaltet. Setze plugins.server.enabled auf dem Host in seiner data/plugin-overrides.json auf false.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Eine Plugin-ID ist erforderlich.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Die Plugin-Verwaltung kann sich nicht selbst deaktivieren: Das Panel würde die Events verlieren, mit denen es diesen Daemon verwaltet. Setze plugins.config.enabled auf dem Host in seiner data/plugin-overrides.json auf false."}');
2143
2773
 
2144
2774
  /***/ },
2145
2775
 
2146
- /***/ "./plugins/config/src/i18n/en_US.json"
2776
+ /***/ "../../plugins/config/daemon/src/i18n/en_US.json"
2147
2777
  (module) {
2148
2778
 
2149
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"The web server cannot be disabled remotely: the panel would lose the connection it manages this daemon over. Set enabled to false in its plugin.json on the host instead.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"A plugin id is required.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Plugin management cannot disable itself: the panel would lose the events it administers this daemon with. Set enabled to false in its plugin.json on the host instead."}');
2779
+ "use strict";
2780
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"The web server cannot be disabled remotely: the panel would lose the connection it manages this daemon over. Set plugins.server.enabled to false in its data/plugin-overrides.json on the host instead.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"A plugin id is required.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Plugin management cannot disable itself: the panel would lose the events it administers this daemon with. Set plugins.config.enabled to false in its data/plugin-overrides.json on the host instead."}');
2150
2781
 
2151
2782
  /***/ },
2152
2783
 
2153
- /***/ "./plugins/config/src/i18n/es_ES.json"
2784
+ /***/ "../../plugins/config/daemon/src/i18n/es_ES.json"
2154
2785
  (module) {
2155
2786
 
2156
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"El servidor web no puede desactivarse de forma remota: el panel perdería la conexión con la que administra este demonio. Establece enabled en false en su plugin.json en el host.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Se requiere un id de complemento.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"La gestión de complementos no puede desactivarse a sí misma: el panel perdería los eventos con los que administra este demonio. Establece enabled en false en su plugin.json en el host."}');
2787
+ "use strict";
2788
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"El servidor web no puede desactivarse de forma remota: el panel perdería la conexión con la que administra este demonio. Establece plugins.server.enabled en false en su data/plugin-overrides.json en el host.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Se requiere un id de complemento.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"La gestión de complementos no puede desactivarse a sí misma: el panel perdería los eventos con los que administra este demonio. Establece plugins.config.enabled en false en su data/plugin-overrides.json en el host."}');
2157
2789
 
2158
2790
  /***/ },
2159
2791
 
2160
- /***/ "./plugins/config/src/i18n/fr_FR.json"
2792
+ /***/ "../../plugins/config/daemon/src/i18n/fr_FR.json"
2161
2793
  (module) {
2162
2794
 
2163
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Le serveur web ne peut pas être désactivé à distance : le panneau perdrait la connexion par laquelle il administre ce démon. Définissez enabled à false dans son plugin.json sur l hôte.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Un identifiant d extension est requis.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Le gestionnaire d extensions ne peut pas se désactiver lui-même : le panneau perdrait les événements par lesquels il administre ce démon. Définissez enabled à false dans son plugin.json sur l hôte."}');
2795
+ "use strict";
2796
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Le serveur web ne peut pas être désactivé à distance : le panneau perdrait la connexion par laquelle il administre ce démon. Définissez plugins.server.enabled à false dans son data/plugin-overrides.json sur l hôte.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Un identifiant d extension est requis.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Le gestionnaire d extensions ne peut pas se désactiver lui-même : le panneau perdrait les événements par lesquels il administre ce démon. Définissez plugins.config.enabled à false dans son data/plugin-overrides.json sur l hôte."}');
2164
2797
 
2165
2798
  /***/ },
2166
2799
 
2167
- /***/ "./plugins/config/src/i18n/ja_JP.json"
2800
+ /***/ "../../plugins/config/daemon/src/i18n/ja_JP.json"
2168
2801
  (module) {
2169
2802
 
2170
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web サーバーはリモートから無効にできません。パネルがこのデーモンを管理している接続を失います。ホスト上で plugin.json の enabled を false に設定してください。","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"プラグイン ID を指定してください。","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"プラグイン管理は自身を無効にできません。パネルがこのデーモンを管理しているイベントを失います。ホスト上で plugin.json の enabled を false に設定してください。"}');
2803
+ "use strict";
2804
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web サーバーはリモートから無効にできません。パネルがこのデーモンを管理している接続を失います。ホスト上で data/plugin-overrides.json の plugins.server.enabled を false に設定してください。","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"プラグイン ID を指定してください。","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"プラグイン管理は自身を無効にできません。パネルがこのデーモンを管理しているイベントを失います。ホスト上で data/plugin-overrides.json の plugins.config.enabled を false に設定してください。"}');
2171
2805
 
2172
2806
  /***/ },
2173
2807
 
2174
- /***/ "./plugins/config/src/i18n/ko_KR.json"
2808
+ /***/ "../../plugins/config/daemon/src/i18n/ko_KR.json"
2175
2809
  (module) {
2176
2810
 
2177
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"웹 서버는 원격으로 비활성화할 수 없습니다. 패널이 이 데몬을 관리하는 연결을 잃게 됩니다. 호스트에서 plugin.json의 enabled 를 false 로 설정하세요.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"플러그인 ID가 필요합니다.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"플러그인 관리는 자신을 비활성화할 수 없습니다. 패널이 이 데몬을 관리하는 이벤트를 잃게 됩니다. 호스트에서 plugin.json의 enabled 를 false 로 설정하세요."}');
2811
+ "use strict";
2812
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"웹 서버는 원격으로 비활성화할 수 없습니다. 패널이 이 데몬을 관리하는 연결을 잃게 됩니다. 호스트에서 data/plugin-overrides.json의 plugins.server.enabled 를 false 로 설정하세요.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"플러그인 ID가 필요합니다.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"플러그인 관리는 자신을 비활성화할 수 없습니다. 패널이 이 데몬을 관리하는 이벤트를 잃게 됩니다. 호스트에서 data/plugin-overrides.json의 plugins.config.enabled 를 false 로 설정하세요."}');
2178
2813
 
2179
2814
  /***/ },
2180
2815
 
2181
- /***/ "./plugins/config/src/i18n/pt_BR.json"
2816
+ /***/ "../../plugins/config/daemon/src/i18n/pt_BR.json"
2182
2817
  (module) {
2183
2818
 
2184
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"O servidor web não pode ser desativado remotamente: o painel perderia a conexão pela qual administra este daemon. Defina enabled como false no plugin.json dele no host.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"É necessário informar o id do plugin.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"O gerenciador de plugins não pode desativar a si mesmo: o painel perderia os eventos pelos quais administra este daemon. Defina enabled como false no plugin.json dele no host."}');
2819
+ "use strict";
2820
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"O servidor web não pode ser desativado remotamente: o painel perderia a conexão pela qual administra este daemon. Defina plugins.server.enabled como false no data/plugin-overrides.json dele no host.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"É necessário informar o id do plugin.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"O gerenciador de plugins não pode desativar a si mesmo: o painel perderia os eventos pelos quais administra este daemon. Defina plugins.config.enabled como false no data/plugin-overrides.json dele no host."}');
2185
2821
 
2186
2822
  /***/ },
2187
2823
 
2188
- /***/ "./plugins/config/src/i18n/ru_RU.json"
2824
+ /***/ "../../plugins/config/daemon/src/i18n/ru_RU.json"
2189
2825
  (module) {
2190
2826
 
2191
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Веб-сервер нельзя отключить удалённо: панель потеряет соединение, через которое управляет этим демоном. Укажите enabled: false в его plugin.json на хосте.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Требуется идентификатор плагина.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Менеджер плагинов не может отключить сам себя: панель потеряет события, через которые управляет этим демоном. Укажите enabled: false в его plugin.json на хосте."}');
2827
+ "use strict";
2828
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Веб-сервер нельзя отключить удалённо: панель потеряет соединение, через которое управляет этим демоном. Укажите plugins.server.enabled: false в его data/plugin-overrides.json на хосте.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Требуется идентификатор плагина.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Менеджер плагинов не может отключить сам себя: панель потеряет события, через которые управляет этим демоном. Укажите plugins.config.enabled: false в его data/plugin-overrides.json на хосте."}');
2192
2829
 
2193
2830
  /***/ },
2194
2831
 
2195
- /***/ "./plugins/config/src/i18n/th_TH.json"
2832
+ /***/ "../../plugins/config/daemon/src/i18n/th_TH.json"
2196
2833
  (module) {
2197
2834
 
2198
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"ไม่สามารถปิดใช้งานเว็บเซิร์ฟเวอร์จากระยะไกลได้ แผงควบคุมจะเสียการเชื่อมต่อที่ใช้จัดการดีมอนนี้ กรุณาตั้ง enabled เป็น false ใน plugin.json ของมันบนเครื่องโฮสต์","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"ต้องระบุรหัสปลั๊กอิน","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"ตัวจัดการปลั๊กอินปิดใช้งานตัวเองไม่ได้ แผงควบคุมจะเสียอีเวนต์ที่ใช้จัดการดีมอนนี้ กรุณาตั้ง enabled เป็น false ใน plugin.json ของมันบนเครื่องโฮสต์"}');
2835
+ "use strict";
2836
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"ไม่สามารถปิดใช้งานเว็บเซิร์ฟเวอร์จากระยะไกลได้ แผงควบคุมจะเสียการเชื่อมต่อที่ใช้จัดการดีมอนนี้ กรุณาตั้ง plugins.server.enabled เป็น false ใน data/plugin-overrides.json ของมันบนเครื่องโฮสต์","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"ต้องระบุรหัสปลั๊กอิน","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"ตัวจัดการปลั๊กอินปิดใช้งานตัวเองไม่ได้ แผงควบคุมจะเสียอีเวนต์ที่ใช้จัดการดีมอนนี้ กรุณาตั้ง plugins.config.enabled เป็น false ใน data/plugin-overrides.json ของมันบนเครื่องโฮสต์"}');
2199
2837
 
2200
2838
  /***/ },
2201
2839
 
2202
- /***/ "./plugins/config/src/i18n/tr_TR.json"
2840
+ /***/ "../../plugins/config/daemon/src/i18n/tr_TR.json"
2203
2841
  (module) {
2204
2842
 
2205
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web sunucusu uzaktan devre dışı bırakılamaz: panel bu daemon u yönettiği bağlantıyı kaybeder. Bunun yerine ana makinede plugin.json dosyasında enabled değerini false yapın.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Bir eklenti kimliği gerekli.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Eklenti yöneticisi kendisini devre dışı bırakamaz: panel bu daemon u yönettiği olayları kaybeder. Bunun yerine ana makinede plugin.json dosyasında enabled değerini false yapın."}');
2843
+ "use strict";
2844
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web sunucusu uzaktan devre dışı bırakılamaz: panel bu daemon u yönettiği bağlantıyı kaybeder. Bunun yerine ana makinede data/plugin-overrides.json dosyasında plugins.server.enabled değerini false yapın.","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"Bir eklenti kimliği gerekli.","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"Eklenti yöneticisi kendisini devre dışı bırakamaz: panel bu daemon u yönettiği olayları kaybeder. Bunun yerine ana makinede data/plugin-overrides.json dosyasında plugins.config.enabled değerini false yapın."}');
2206
2845
 
2207
2846
  /***/ },
2208
2847
 
2209
- /***/ "./plugins/config/src/i18n/zh_CN.json"
2848
+ /***/ "../../plugins/config/daemon/src/i18n/zh_CN.json"
2210
2849
  (module) {
2211
2850
 
2212
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web 服务器不能远程禁用:面板会失去用来管理这个守护进程的连接。请在宿主机上把它的 plugin.json 里的 enabled 设为 false。","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"必须提供插件 ID。","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"插件管理无法禁用自己:面板会失去用来管理这个守护进程的事件。请在宿主机上把它的 plugin.json 里的 enabled 设为 false。"}');
2851
+ "use strict";
2852
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web 服务器不能远程禁用:面板会失去用来管理这个守护进程的连接。请在宿主机上把它的 data/plugin-overrides.json 里的 plugins.server.enabled 设为 false。","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"必须提供插件 ID。","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"插件管理无法禁用自己:面板会失去用来管理这个守护进程的事件。请在宿主机上把它的 data/plugin-overrides.json 里的 plugins.config.enabled 设为 false。"}');
2213
2853
 
2214
2854
  /***/ },
2215
2855
 
2216
- /***/ "./plugins/config/src/i18n/zh_TW.json"
2856
+ /***/ "../../plugins/config/daemon/src/i18n/zh_TW.json"
2217
2857
  (module) {
2218
2858
 
2219
- module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web 伺服器不能遠端停用:面板會失去用來管理這個守護行程的連線。請在主機上把它的 plugin.json 裡的 enabled 設為 false。","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"必須提供外掛 ID。","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"外掛管理無法停用自己:面板會失去用來管理這個守護行程的事件。請在主機上把它的 plugin.json 裡的 enabled 設為 false。"}');
2859
+ "use strict";
2860
+ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DISABLE":"Web 伺服器不能遠端停用:面板會失去用來管理這個守護行程的連線。請在主機上把它的 data/plugin-overrides.json 裡的 plugins.server.enabled 設為 false。","TXT_CODE_DAEMON_PLUGIN_ID_REQUIRED":"必須提供外掛 ID。","TXT_CODE_DAEMON_PLUGIN_SELF_DISABLE":"外掛管理無法停用自己:面板會失去用來管理這個守護行程的事件。請在主機上把它的 data/plugin-overrides.json 裡的 plugins.config.enabled 設為 false。"}');
2220
2861
 
2221
2862
  /***/ }
2222
2863
 
@@ -2248,16 +2889,18 @@ module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_DAEMON_PLUGIN_ESSENTIAL_DIS
2248
2889
  /******/
2249
2890
  /************************************************************************/
2250
2891
  var __webpack_exports__ = {};
2251
- // This entry needs to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
2892
+ // This entry needs to be wrapped in an IIFE because it needs to be in strict mode.
2252
2893
  (() => {
2894
+ "use strict";
2253
2895
  var exports = __webpack_exports__;
2254
2896
 
2255
2897
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2256
2898
  exports.inject = void 0;
2257
2899
  exports.apply = apply;
2258
2900
  const mcsmanager_common_1 = __webpack_require__("../common/src/index.ts");
2259
- const i18n_1 = __webpack_require__("./plugins/config/src/i18n/index.ts");
2260
- const settings_1 = __webpack_require__("./plugins/config/src/backend/settings.ts");
2901
+ const i18n_1 = __webpack_require__("../../plugins/config/daemon/src/i18n/index.ts");
2902
+ const settings_1 = __webpack_require__("../../plugins/config/daemon/src/backend/settings.ts");
2903
+ const mcsmanager_common_2 = __webpack_require__("../common/src/index.ts");
2261
2904
  // Daemon side of the plugin manager. The daemon core owns the mechanism —
2262
2905
  // `ctx.plugins` lists what is installed and persists the enable switch — and
2263
2906
  // this plugin only exposes it over the protocol, because the panel's plugin
@@ -2376,11 +3019,10 @@ function apply(ctx) {
2376
3019
  ctx.protocol.on("plugin/npm/install", async (routerCtx, data) => {
2377
3020
  try {
2378
3021
  const payload = (data ?? {});
2379
- const record = await (0, mcsmanager_common_1.installNpmPlugin)({
3022
+ const record = await ctx.plugins.install({
2380
3023
  name: String(payload.name ?? ""),
2381
- pluginId: String(payload.pluginId ?? ""),
2382
- version: String(payload.version ?? ""),
2383
- side: "daemon"
3024
+ pluginId: payload.pluginId ? String(payload.pluginId) : undefined,
3025
+ version: payload.version ? String(payload.version) : undefined
2384
3026
  });
2385
3027
  ctx.protocol.response(routerCtx, record);
2386
3028
  }
@@ -2391,16 +3033,18 @@ function apply(ctx) {
2391
3033
  ctx.protocol.on("plugin/npm/uninstall", async (routerCtx, data) => {
2392
3034
  try {
2393
3035
  const pluginId = String(data?.pluginId ?? "");
2394
- const removed = await (0, mcsmanager_common_1.uninstallNpmPlugin)(pluginId);
2395
- ctx.protocol.response(routerCtx, { removed, pluginId });
3036
+ await ctx.plugins.remove(pluginId);
3037
+ if (data?.removeConfig === true) {
3038
+ await ctx.plugins.runExclusive(async () => (0, mcsmanager_common_2.writePluginOverride)(pluginId, { config: {} }));
3039
+ }
3040
+ ctx.protocol.response(routerCtx, { removed: true, pluginId });
2396
3041
  }
2397
3042
  catch (error) {
2398
3043
  ctx.protocol.responseError(routerCtx, error);
2399
3044
  }
2400
3045
  });
2401
- // Re-scan the plugin directories for a plugin that arrived while the daemon
2402
- // was running. The panel reconnects after this response so protocol handlers
2403
- // from a newly loaded plugin are bound to a fresh socket.
3046
+ // Reconcile dependencies and make their current handlers available on
3047
+ // existing authenticated sockets.
2404
3048
  ctx.protocol.on("plugin/reload", async (routerCtx) => {
2405
3049
  try {
2406
3050
  await ctx.plugins.reload();