@elementspanel/epanel-plugin-file 1.0.0 → 1.0.1

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.
@@ -109,7 +109,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
109
109
  return (mod && mod.__esModule) ? mod : { "default": mod };
110
110
  };
111
111
  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;
112
+ 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
113
  const global_variable_1 = __importDefault(__webpack_require__("../common/src/global_variable.ts"));
114
114
  exports.GlobalVariable = global_variable_1.default;
115
115
  const instance_stream_1 = __importDefault(__webpack_require__("../common/src/instance_stream.ts"));
@@ -141,6 +141,8 @@ __exportStar(__webpack_require__("../common/src/plugin_contract.ts"), exports);
141
141
  __exportStar(__webpack_require__("../common/src/plugin_overrides.ts"), exports);
142
142
  __exportStar(__webpack_require__("../common/src/plugin_lifecycle.ts"), exports);
143
143
  __exportStar(__webpack_require__("../common/src/plugin_revision.ts"), exports);
144
+ __exportStar(__webpack_require__("../common/src/plugin_modules.ts"), exports);
145
+ __exportStar(__webpack_require__("../common/src/plugin_watch.ts"), exports);
144
146
  __exportStar(__webpack_require__("../common/src/runtime_paths.ts"), exports);
145
147
  var plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
146
148
  Object.defineProperty(exports, "discoverPlugins", ({ enumerable: true, get: function () { return plugin_manifest_1.discoverPlugins; } }));
@@ -155,6 +157,8 @@ Object.defineProperty(exports, "currentProjectRoot", ({ enumerable: true, get: f
155
157
  Object.defineProperty(exports, "installNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.installNpmPlugin; } }));
156
158
  Object.defineProperty(exports, "listNpmPlugins", ({ enumerable: true, get: function () { return plugin_npm_1.listNpmPlugins; } }));
157
159
  Object.defineProperty(exports, "npmPluginRoots", ({ enumerable: true, get: function () { return plugin_npm_1.npmPluginRoots; } }));
160
+ Object.defineProperty(exports, "npmPluginOperationPending", ({ enumerable: true, get: function () { return plugin_npm_1.npmPluginOperationPending; } }));
161
+ Object.defineProperty(exports, "isEssentialNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.isEssentialNpmPlugin; } }));
158
162
  Object.defineProperty(exports, "uninstallNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.uninstallNpmPlugin; } }));
159
163
  var string_utils_1 = __webpack_require__("../common/src/string_utils.ts");
160
164
  Object.defineProperty(exports, "removeTrail", ({ enumerable: true, get: function () { return string_utils_1.removeTrail; } }));
@@ -622,7 +626,45 @@ function createFrontendPluginMetadata(manifest, extras = {}) {
622
626
  };
623
627
  }
624
628
  const MANIFEST_FILES = ["plugin.json", "manifest.json", "package.json"];
625
- /** Reads the manifest of one plugin directory, or null if it has none. */
629
+ const ENTRY_FIELDS = ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"];
630
+ function objectValue(value) {
631
+ return value && typeof value === "object" && !Array.isArray(value)
632
+ ? value
633
+ : {};
634
+ }
635
+ /**
636
+ * Source plugins keep package.json beside plugin.json. Published npm plugins
637
+ * keep it at the package root, with runtime metadata under elementspanel.<side>.
638
+ * Stop at the first package boundary so a host's metadata cannot reach a plugin.
639
+ */
640
+ function readPackageMetadata(directory, id) {
641
+ const pluginDirectory = node_path_1.default.resolve(directory);
642
+ let current = pluginDirectory;
643
+ while (true) {
644
+ const filename = node_path_1.default.join(current, "package.json");
645
+ if (node_fs_1.default.existsSync(filename)) {
646
+ const { elementspanel, ...metadata } = objectValue(JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")));
647
+ const { id: packageId, panel, daemon, ...shared } = objectValue(elementspanel);
648
+ const definition = [panel, daemon]
649
+ .map(objectValue)
650
+ .find((side) => typeof side.directory === "string" &&
651
+ node_path_1.default.resolve(current, side.directory) === pluginDirectory);
652
+ if (current !== pluginDirectory && (packageId !== id || !definition))
653
+ return {};
654
+ const { directory: _directory, folder: _folder, ...sideMetadata } = definition ?? {};
655
+ const result = { ...metadata, ...shared, ...sideMetadata };
656
+ // npm's main/entry fields do not define a side's execution entry.
657
+ for (const field of ["dual", ...ENTRY_FIELDS])
658
+ delete result[field];
659
+ return result;
660
+ }
661
+ const parent = node_path_1.default.dirname(current);
662
+ if (parent === current)
663
+ return {};
664
+ current = parent;
665
+ }
666
+ }
667
+ /** Merges package metadata with a plugin's identity and entries. Legacy manifests still load. */
626
668
  function readPluginManifest(directory, onWarning, fallbackId = node_path_1.default.basename(directory)) {
627
669
  for (const file of MANIFEST_FILES) {
628
670
  const filePath = node_path_1.default.join(directory, file);
@@ -635,7 +677,18 @@ function readPluginManifest(directory, onWarning, fallbackId = node_path_1.defau
635
677
  const id = typeof value.id === "string" ? value.id : fallbackId;
636
678
  if (!id.trim())
637
679
  return null;
638
- return { ...value, id: id.trim() };
680
+ const manifest = {
681
+ ...value,
682
+ ...readPackageMetadata(directory, id.trim()),
683
+ id: id.trim()
684
+ };
685
+ // Identity and execution paths always belong to plugin.json. Package
686
+ // metadata wins for descriptions, priority, configuration and dependencies.
687
+ for (const field of ["version", "dual", ...ENTRY_FIELDS]) {
688
+ if (value[field] !== undefined)
689
+ manifest[field] = value[field];
690
+ }
691
+ return manifest;
639
692
  }
640
693
  catch (error) {
641
694
  onWarning?.(`Failed to read plugin manifest: ${filePath}`, error);
@@ -733,6 +786,13 @@ function discoverPluginsFromRoots(roots, options) {
733
786
  discovered.push({
734
787
  ...plugin,
735
788
  manifest,
789
+ ...(root.packageRoot
790
+ ? {
791
+ packageRoot: root.packageRoot,
792
+ packageName: root.packageName,
793
+ npmSource: root.npmSource
794
+ }
795
+ : {}),
736
796
  ...(root.folder ? { folder: root.folder } : {})
737
797
  });
738
798
  }
@@ -746,6 +806,177 @@ function sortPlugins(plugins) {
746
806
  }
747
807
 
748
808
 
809
+ /***/ },
810
+
811
+ /***/ "../common/src/plugin_modules.ts"
812
+ (__unused_webpack_module, exports, __webpack_require__) {
813
+
814
+
815
+ var __importDefault = (this && this.__importDefault) || function (mod) {
816
+ return (mod && mod.__esModule) ? mod : { "default": mod };
817
+ };
818
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
819
+ exports.pluginBackendRevision = pluginBackendRevision;
820
+ exports.forgetPluginModules = forgetPluginModules;
821
+ exports.loadPluginModule = loadPluginModule;
822
+ const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
823
+ const node_path_1 = __importDefault(__webpack_require__("node:path"));
824
+ const node_url_1 = __webpack_require__("node:url");
825
+ const node_crypto_1 = __webpack_require__("node:crypto");
826
+ const node_module_1 = __webpack_require__("node:module");
827
+ const plugin_revision_1 = __webpack_require__("../common/src/plugin_revision.ts");
828
+ const REVISION = "epanelPluginRevision";
829
+ const graphs = new Map();
830
+ const previousRoots = new Map();
831
+ let hooksInstalled = false;
832
+ const sharedModules = new Set([
833
+ "cordis",
834
+ "@cordisjs/core",
835
+ "@cordisjs/logger",
836
+ "@cordisjs/timer",
837
+ "@cordisjs/schema",
838
+ "cosmokit",
839
+ "schemastery",
840
+ "reggol"
841
+ ]);
842
+ function dependencyRoots(directory) {
843
+ const visited = new Set([directory]);
844
+ const walk = (root) => {
845
+ const filename = node_path_1.default.join(root, "package.json");
846
+ if (!node_fs_1.default.existsSync(filename))
847
+ return;
848
+ const metadata = JSON.parse(node_fs_1.default.readFileSync(filename, "utf8"));
849
+ const localRequire = (0, node_module_1.createRequire)(filename);
850
+ for (const name of Object.keys({
851
+ ...metadata.dependencies,
852
+ ...metadata.optionalDependencies
853
+ })) {
854
+ if (sharedModules.has(name))
855
+ continue;
856
+ const target = (localRequire.resolve.paths(name) || [])
857
+ .map((directory) => node_path_1.default.join(directory, name, "package.json"))
858
+ .find((candidate) => node_fs_1.default.existsSync(candidate));
859
+ if (!target)
860
+ continue;
861
+ const dependency = node_fs_1.default.realpathSync(node_path_1.default.dirname(target));
862
+ if (visited.has(dependency))
863
+ continue;
864
+ visited.add(dependency);
865
+ walk(dependency);
866
+ }
867
+ };
868
+ walk(directory);
869
+ visited.delete(directory);
870
+ return [...visited];
871
+ }
872
+ /** Include transitive dependency versions so npm updates replace the affected graph. */
873
+ function pluginBackendRevision(entry, directory) {
874
+ const hash = (0, node_crypto_1.createHash)("sha256").update((0, plugin_revision_1.pluginDirectoryRevision)(node_path_1.default.dirname(entry)));
875
+ for (const root of [directory, ...dependencyRoots(directory)]) {
876
+ const filename = node_path_1.default.join(root, "package.json");
877
+ if (node_fs_1.default.existsSync(filename))
878
+ hash.update(root).update(node_fs_1.default.readFileSync(filename));
879
+ }
880
+ return hash.digest("hex").slice(0, 24);
881
+ }
882
+ function within(root, filename) {
883
+ const relative = node_path_1.default.relative(root, filename);
884
+ return (relative === "" ||
885
+ (!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== ".." && !node_path_1.default.isAbsolute(relative)));
886
+ }
887
+ /** Clear the plugin's complete CJS graph, keeping shared host modules intact. */
888
+ function forgetPluginModules(directory) {
889
+ const runtimeRequire = eval("require");
890
+ const owned = [directory, ...(previousRoots.get(directory) || []), ...dependencyRoots(directory)];
891
+ previousRoots.delete(directory);
892
+ const belongs = (filename) => {
893
+ const parts = filename.split(`${node_path_1.default.sep}node_modules${node_path_1.default.sep}`);
894
+ if (parts.slice(1).some((part) => {
895
+ const name = part.startsWith("@")
896
+ ? part.split(node_path_1.default.sep).slice(0, 2).join("/")
897
+ : part.split(node_path_1.default.sep)[0];
898
+ return sharedModules.has(name);
899
+ }))
900
+ return false;
901
+ return owned.some((root) => within(root, filename));
902
+ };
903
+ for (const filename of Object.keys(runtimeRequire.cache)) {
904
+ if (belongs(filename))
905
+ delete runtimeRequire.cache[filename];
906
+ }
907
+ for (const mod of Object.values(runtimeRequire.cache)) {
908
+ if (mod)
909
+ mod.children = mod.children.filter((child) => !belongs(child.filename));
910
+ }
911
+ // npm can move a dependency or change its main entry. Node's CJS resolver
912
+ // retains those paths separately from require.cache.
913
+ const pathCache = runtimeRequire("node:module")._pathCache;
914
+ for (const [key, filename] of Object.entries(pathCache)) {
915
+ if (belongs(filename) || owned.some((root) => key.includes(root)))
916
+ delete pathCache[key];
917
+ }
918
+ }
919
+ function isEsm(entry) {
920
+ if (entry.endsWith(".mjs"))
921
+ return true;
922
+ if (entry.endsWith(".cjs"))
923
+ return false;
924
+ let directory = node_path_1.default.dirname(entry);
925
+ while (true) {
926
+ const filename = node_path_1.default.join(directory, "package.json");
927
+ if (node_fs_1.default.existsSync(filename))
928
+ return JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")).type === "module";
929
+ const parent = node_path_1.default.dirname(directory);
930
+ if (parent === directory)
931
+ return false;
932
+ directory = parent;
933
+ }
934
+ }
935
+ /** Give an ESM entry and its local imports the same immutable revision URL. */
936
+ async function loadPluginModule(entry, directory, revision) {
937
+ const runtimeRequire = eval("require");
938
+ previousRoots.set(directory, dependencyRoots(directory));
939
+ if (!isEsm(entry)) {
940
+ try {
941
+ return runtimeRequire(entry);
942
+ }
943
+ catch (error) {
944
+ if (error?.code !== "ERR_REQUIRE_ESM")
945
+ throw error;
946
+ }
947
+ }
948
+ graphs.set(revision, [directory, ...dependencyRoots(directory)]);
949
+ if (!hooksInstalled) {
950
+ const { registerHooks } = runtimeRequire("node:module");
951
+ if (typeof registerHooks !== "function")
952
+ throw new Error("ESM plugin updates require Node.js 22.15 or newer.");
953
+ registerHooks({
954
+ resolve(specifier, context, nextResolve) {
955
+ const resolved = nextResolve(specifier, context);
956
+ if (!context.parentURL?.startsWith("file:") || !resolved.url.startsWith("file:"))
957
+ return resolved;
958
+ const parent = new URL(context.parentURL);
959
+ const parentRevision = parent.searchParams.get(REVISION);
960
+ if (!parentRevision)
961
+ return resolved;
962
+ const roots = graphs.get(parentRevision);
963
+ if (!roots?.some((root) => within(root, (0, node_url_1.fileURLToPath)(parent))) ||
964
+ !roots.some((root) => within(root, (0, node_url_1.fileURLToPath)(resolved.url))))
965
+ return resolved;
966
+ const url = new URL(resolved.url);
967
+ url.searchParams.set(REVISION, parentRevision);
968
+ return { ...resolved, url: url.href };
969
+ }
970
+ });
971
+ hooksInstalled = true;
972
+ }
973
+ const url = (0, node_url_1.pathToFileURL)(entry);
974
+ url.searchParams.set(REVISION, revision);
975
+ const dynamicImport = new Function("specifier", "return import(specifier)");
976
+ return dynamicImport(url.href);
977
+ }
978
+
979
+
749
980
  /***/ },
750
981
 
751
982
  /***/ "../common/src/plugin_npm.ts"
@@ -760,15 +991,19 @@ exports.currentPackageRoot = currentPackageRoot;
760
991
  exports.currentProjectRoot = currentProjectRoot;
761
992
  exports.npmPluginRoots = npmPluginRoots;
762
993
  exports.listNpmPlugins = listNpmPlugins;
994
+ exports.npmPluginOperationPending = npmPluginOperationPending;
995
+ exports.isEssentialNpmPlugin = isEssentialNpmPlugin;
763
996
  exports.installNpmPlugin = installNpmPlugin;
764
997
  exports.uninstallNpmPlugin = uninstallNpmPlugin;
765
998
  const child_process_1 = __webpack_require__("child_process");
766
999
  const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
767
1000
  const path_1 = __importDefault(__webpack_require__("path"));
768
1001
  const module_1 = __webpack_require__("module");
1002
+ const plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
1003
+ const plugin_contract_1 = __webpack_require__("../common/src/plugin_contract.ts");
769
1004
  const PLUGIN_ID = /^[a-z][a-z0-9_-]{1,63}$/;
770
1005
  const PACKAGE_NAME = /^(?:@[a-z0-9_.-]+\/)?epanel-plugin-[a-z0-9_.-]+$/;
771
- const VERSION = /^[0-9][^\s/]{0,63}$/;
1006
+ const VERSION = /^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,63}$/;
772
1007
  function readJson(filename) {
773
1008
  try {
774
1009
  const parsed = JSON.parse(fs_extra_1.default.readFileSync(filename, "utf8"));
@@ -834,8 +1069,20 @@ function pluginRecord(pluginId, name, source, packageRoot, directory, folder, ve
834
1069
  return { pluginId, name, version, sides: [], directory, folder, packageRoot, source };
835
1070
  }
836
1071
  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`);
1072
+ // Read package boundaries directly: Node caches require.resolve() paths even
1073
+ // after npm moves an installation between nested and hoisted node_modules.
1074
+ let manifestPath;
1075
+ for (const root of [packageRoot, currentProjectRoot(packageRoot)]) {
1076
+ const localRequire = (0, module_1.createRequire)(path_1.default.join(root, "package.json"));
1077
+ manifestPath = (localRequire.resolve.paths(name) || [])
1078
+ .map((directory) => path_1.default.join(directory, name, "package.json"))
1079
+ .find((filename) => fs_extra_1.default.existsSync(filename));
1080
+ if (manifestPath)
1081
+ break;
1082
+ }
1083
+ if (!manifestPath)
1084
+ throw new Error(`Missing npm plugin package: ${name}.`);
1085
+ manifestPath = fs_extra_1.default.realpathSync(manifestPath);
839
1086
  const metadata = readJson(manifestPath);
840
1087
  if (!metadata)
841
1088
  throw new Error(`Missing package metadata for ${name}.`);
@@ -849,7 +1096,10 @@ function sideDirectory(packageRoot, metadata, side) {
849
1096
  return undefined;
850
1097
  const target = path_1.default.resolve(packageRoot, directory);
851
1098
  const relative = path_1.default.relative(packageRoot, target);
852
- if (relative === "" || relative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(relative))
1099
+ if (relative === "" ||
1100
+ relative === ".." ||
1101
+ relative.startsWith(`..${path_1.default.sep}`) ||
1102
+ path_1.default.isAbsolute(relative))
853
1103
  return undefined;
854
1104
  return target;
855
1105
  }
@@ -866,7 +1116,7 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
866
1116
  return undefined;
867
1117
  const directory = sideDirectory(packageRoot, metadata, target);
868
1118
  const folder = stringValue(objectValue(elements[target])?.folder);
869
- if (!directory || !folder)
1119
+ if (!directory || !PLUGIN_ID.test(folder))
870
1120
  return undefined;
871
1121
  return {
872
1122
  ...pluginRecord(pluginId, name, source, packageRoot, directory, folder, version),
@@ -886,9 +1136,24 @@ function listNpmPlugins(side, options = {}) {
886
1136
  const host = readJson(path_1.default.join(packageRoot, "package.json"));
887
1137
  if (!host)
888
1138
  throw new Error(`Missing runtime package metadata: ${packageRoot}`);
889
- const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot) ? null : readProjectManifest(projectRoot);
1139
+ const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
1140
+ ? null
1141
+ : readProjectManifest(projectRoot);
890
1142
  const hostMappings = mappingsOf(host);
891
- const projectMappings = mappingsOf(project);
1143
+ const projectMappings = { ...mappingsOf(project) };
1144
+ for (const name of Object.keys(dependenciesOf(project))) {
1145
+ if (!PACKAGE_NAME.test(name) || Object.values(projectMappings).includes(name))
1146
+ continue;
1147
+ try {
1148
+ const entry = readPackageEntry(projectRoot, name);
1149
+ const id = stringValue(objectValue(entry.metadata.elementspanel)?.id);
1150
+ if (PLUGIN_ID.test(id) && projectMappings[id] === undefined)
1151
+ projectMappings[id] = name;
1152
+ }
1153
+ catch {
1154
+ /* npm may still be writing the package. */
1155
+ }
1156
+ }
892
1157
  const mappings = new Map();
893
1158
  for (const [pluginId, name] of Object.entries(projectMappings)) {
894
1159
  if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
@@ -898,6 +1163,8 @@ function listNpmPlugins(side, options = {}) {
898
1163
  for (const [pluginId, name] of Object.entries(hostMappings)) {
899
1164
  if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
900
1165
  continue;
1166
+ if (dependenciesOf(host)[name] === undefined)
1167
+ continue;
901
1168
  mappings.set(pluginId, { name, source: "bundled" });
902
1169
  }
903
1170
  const hostDependencies = dependenciesOf(host);
@@ -913,7 +1180,7 @@ function listNpmPlugins(side, options = {}) {
913
1180
  }
914
1181
  let entry;
915
1182
  try {
916
- entry = readPackageEntry(packageRoot, mapping.name);
1183
+ entry = readPackageEntry(mapping.source === "project" ? projectRoot : packageRoot, mapping.name);
917
1184
  }
918
1185
  catch (error) {
919
1186
  if (options.skipInvalid)
@@ -945,7 +1212,12 @@ function runNpm(args, cwd) {
945
1212
  : process.platform === "win32"
946
1213
  ? {
947
1214
  command: process.env.ComSpec || "cmd.exe",
948
- args: ["/d", "/s", "/c", `npm ${args.map((item) => `"${item.replace(/"/g, '""')}"`).join(" ")}`],
1215
+ args: [
1216
+ "/d",
1217
+ "/s",
1218
+ "/c",
1219
+ `npm ${args.map((item) => `"${item.replace(/"/g, '""')}"`).join(" ")}`
1220
+ ],
949
1221
  shell: false
950
1222
  }
951
1223
  : { command: "npm", args, shell: false };
@@ -964,9 +1236,10 @@ function runNpm(args, cwd) {
964
1236
  };
965
1237
  child.stdout?.on("data", append);
966
1238
  child.stderr?.on("data", append);
1239
+ const action = args.find((argument) => argument === "install" || argument === "uninstall") || "operation";
967
1240
  const timer = setTimeout(() => {
968
1241
  child.kill("SIGKILL");
969
- reject(new Error(`npm ${args[0] ?? ""} timed out.`));
1242
+ reject(new Error(`npm ${action} timed out.`));
970
1243
  }, 10 * 60 * 1000);
971
1244
  timer.unref?.();
972
1245
  child.once("error", (error) => {
@@ -977,15 +1250,69 @@ function runNpm(args, cwd) {
977
1250
  clearTimeout(timer);
978
1251
  if (code === 0)
979
1252
  return resolve(output);
980
- reject(new Error(`npm ${args[0] ?? ""} failed (${code}): ${output.trim()}`));
1253
+ reject(new Error(`npm ${action} failed (${code}): ${output.trim()}`));
981
1254
  });
982
1255
  });
983
1256
  }
984
- const operations = new Map();
1257
+ const operationsKey = Symbol.for("elementspanel.npm.operations");
1258
+ const shared = globalThis;
1259
+ const operations = (shared[operationsKey] ??= new Map());
1260
+ const LOCK_NAME = ".elementspanel-npm.lock";
1261
+ async function acquireProjectLock(projectRoot) {
1262
+ await fs_extra_1.default.ensureDir(projectRoot);
1263
+ const directory = path_1.default.join(projectRoot, LOCK_NAME);
1264
+ const deadline = Date.now() + 11 * 60 * 1000;
1265
+ while (true) {
1266
+ try {
1267
+ await fs_extra_1.default.mkdir(directory);
1268
+ await fs_extra_1.default.writeJson(path_1.default.join(directory, "owner.json"), { pid: process.pid });
1269
+ return () => fs_extra_1.default.remove(directory);
1270
+ }
1271
+ catch (error) {
1272
+ if (error.code !== "EEXIST")
1273
+ throw error;
1274
+ try {
1275
+ const owner = await fs_extra_1.default.readJson(path_1.default.join(directory, "owner.json"));
1276
+ try {
1277
+ process.kill(owner.pid, 0);
1278
+ }
1279
+ catch (failure) {
1280
+ if (failure.code === "ESRCH") {
1281
+ await fs_extra_1.default.remove(directory);
1282
+ continue;
1283
+ }
1284
+ }
1285
+ }
1286
+ catch {
1287
+ if (Date.now() - ((await fs_extra_1.default.stat(directory).catch(() => null))?.mtimeMs ?? Date.now()) >
1288
+ 60_000) {
1289
+ await fs_extra_1.default.remove(directory);
1290
+ continue;
1291
+ }
1292
+ }
1293
+ if (Date.now() > deadline)
1294
+ throw new Error("Another npm plugin operation is still running.");
1295
+ await new Promise((resolve) => setTimeout(resolve, 200));
1296
+ }
1297
+ }
1298
+ }
1299
+ function npmPluginOperationPending() {
1300
+ const { packageRoot, projectRoot } = npmPluginRoots();
1301
+ return (operations.size > 0 ||
1302
+ [packageRoot, projectRoot].some((root) => fs_extra_1.default.existsSync(path_1.default.join(root, LOCK_NAME))));
1303
+ }
985
1304
  function withProjectLock(projectRoot, operation) {
986
1305
  const key = process.platform === "win32" ? projectRoot.toLowerCase() : projectRoot;
987
1306
  const previous = operations.get(key) ?? Promise.resolve();
988
- const result = previous.then(operation);
1307
+ const result = previous.then(async () => {
1308
+ const release = await acquireProjectLock(projectRoot);
1309
+ try {
1310
+ return await operation();
1311
+ }
1312
+ finally {
1313
+ await release();
1314
+ }
1315
+ });
989
1316
  const settled = result.then(() => { }, () => { });
990
1317
  operations.set(key, settled);
991
1318
  return result.finally(() => {
@@ -993,18 +1320,18 @@ function withProjectLock(projectRoot, operation) {
993
1320
  operations.delete(key);
994
1321
  });
995
1322
  }
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
- */
1323
+ /** Core services keep the connection and management interface available. */
1324
+ function isEssentialNpmPlugin(pluginId) {
1325
+ return ["i18n", "storage", "runtime", "server", "console", "config"].includes(pluginId);
1326
+ }
1327
+ /** npm changes dependencies and lockfiles; only append our plugin mapping afterwards. */
1002
1328
  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}`);
1329
+ const { name, side } = options;
1330
+ const version = options.version || "latest";
1006
1331
  if (!PACKAGE_NAME.test(name))
1007
1332
  throw new Error(`Invalid npm plugin package: ${name}`);
1333
+ if (options.pluginId && !PLUGIN_ID.test(options.pluginId))
1334
+ throw new Error(`Invalid plugin id: ${options.pluginId}`);
1008
1335
  if (!VERSION.test(version))
1009
1336
  throw new Error(`Invalid plugin version: ${version}`);
1010
1337
  const { packageRoot, projectRoot } = npmPluginRoots(options);
@@ -1012,64 +1339,154 @@ async function installNpmPlugin(options) {
1012
1339
  const host = readJson(path_1.default.join(packageRoot, "package.json"));
1013
1340
  if (!host)
1014
1341
  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;
1342
+ const hostId = Object.entries(mappingsOf(host)).find(([, value]) => value === name && dependenciesOf(host)[name] !== undefined)?.[0];
1343
+ const targetRoot = hostId ? packageRoot : projectRoot;
1344
+ if (hostId && isEssentialNpmPlugin(hostId))
1345
+ throw new Error(`The essential plugin "${hostId}" cannot be replaced while running.`);
1346
+ if (options.pluginId &&
1347
+ mappingsOf(host)[options.pluginId] &&
1348
+ dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
1349
+ mappingsOf(host)[options.pluginId] !== name)
1350
+ throw new Error(`BUNDLED:${options.pluginId}`);
1351
+ const original = readProjectManifest(targetRoot);
1352
+ const previous = dependenciesOf(original)[name];
1353
+ let previousVersion = previous;
1354
+ if (previous) {
1355
+ try {
1356
+ previousVersion = readPackageEntry(targetRoot, name).metadata.version || previous;
1357
+ }
1358
+ catch {
1359
+ /* Keep the declared spec if the previous package is incomplete. */
1360
+ }
1361
+ }
1362
+ const backups = new Map();
1363
+ for (const filename of ["package.json", "package-lock.json", "npm-shrinkwrap.json"]) {
1364
+ const target = path_1.default.join(targetRoot, filename);
1365
+ backups.set(target, fs_extra_1.default.existsSync(target) ? fs_extra_1.default.readFileSync(target) : null);
1366
+ }
1023
1367
  try {
1024
- entry = readPackageEntry(packageRoot, name);
1368
+ await runNpm([
1369
+ "--prefix",
1370
+ targetRoot,
1371
+ "install",
1372
+ `${name}@${version}`,
1373
+ "--save",
1374
+ "--save-exact",
1375
+ "--no-fund",
1376
+ "--no-audit",
1377
+ "--ignore-scripts"
1378
+ ], targetRoot);
1379
+ const entry = readPackageEntry(targetRoot, name);
1380
+ const pluginId = options.pluginId || stringValue(objectValue(entry.metadata.elementspanel)?.id);
1381
+ if (!PLUGIN_ID.test(pluginId))
1382
+ throw new Error(`Invalid plugin id in ${name}.`);
1383
+ if (mappingsOf(host)[pluginId] &&
1384
+ dependenciesOf(host)[String(mappingsOf(host)[pluginId])] !== undefined &&
1385
+ mappingsOf(host)[pluginId] !== name)
1386
+ throw new Error(`Plugin id ${pluginId} belongs to a bundled dependency.`);
1387
+ if (hostId && pluginId !== hostId)
1388
+ throw new Error(`The package ${name} changed its plugin id from ${hostId} to ${pluginId}.`);
1389
+ const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
1390
+ if (mapped !== undefined && mapped !== name)
1391
+ throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
1392
+ const record = recordFromPackage(pluginId, name, hostId ? "bundled" : "project", entry.root, entry.metadata, side);
1393
+ if (!record)
1394
+ throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
1395
+ if (/^[0-9]/.test(version) && record.version !== version)
1396
+ throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
1397
+ if (!process.env.EPANEL_PACKAGE_ROOT &&
1398
+ (0, plugin_manifest_1.discoverPlugins)(path_1.default.join(packageRoot, "plugins"), {
1399
+ entryFields: [],
1400
+ includeDisabled: true
1401
+ }).some((plugin) => plugin.manifest.id === pluginId))
1402
+ throw new Error(`Source plugin ${pluginId} cannot be replaced with an npm dependency.`);
1403
+ const manifest = (0, plugin_manifest_1.readPluginManifest)(record.directory);
1404
+ if (!manifest || manifest.id !== pluginId || manifest.version !== record.version)
1405
+ throw new Error(`The plugin manifest in ${name} does not match its package.`);
1406
+ (0, plugin_contract_1.validatePluginCompatibility)(manifest.elements);
1407
+ for (const field of ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"]) {
1408
+ if (typeof manifest[field] === "string" &&
1409
+ !(0, plugin_manifest_1.resolvePluginEntry)(record.directory, manifest, { entryFields: [field] }))
1410
+ throw new Error(`Invalid ${field} entry in ${name}.`);
1411
+ }
1412
+ const others = listNpmPlugins(side, { packageRoot, projectRoot, skipInvalid: true });
1413
+ if (others.some((plugin) => plugin.pluginId === pluginId && plugin.name !== name))
1414
+ throw new Error(`Plugin id ${pluginId} is already provided by another dependency.`);
1415
+ const collision = others.find((plugin) => plugin.pluginId !== pluginId && plugin.folder === record.folder);
1416
+ if (collision)
1417
+ throw new Error(`Plugin asset directory ${record.folder} is already used by ${collision.name}.`);
1418
+ const updated = readProjectManifest(targetRoot);
1419
+ updated.dependencies = { ...dependenciesOf(updated), [name]: record.version };
1420
+ updated.elementspanel = {
1421
+ ...objectValue(updated.elementspanel),
1422
+ plugins: { ...mappingsOf(updated), [pluginId]: name }
1423
+ };
1424
+ writeProjectManifest(targetRoot, updated);
1425
+ return record;
1025
1426
  }
1026
1427
  catch (error) {
1027
- await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
1428
+ try {
1429
+ await runNpm(previous
1430
+ ? [
1431
+ "--prefix",
1432
+ targetRoot,
1433
+ "install",
1434
+ `${name}@${String(previousVersion)}`,
1435
+ "--save",
1436
+ "--no-fund",
1437
+ "--no-audit",
1438
+ "--ignore-scripts"
1439
+ ]
1440
+ : [
1441
+ "--prefix",
1442
+ targetRoot,
1443
+ "uninstall",
1444
+ name,
1445
+ "--no-fund",
1446
+ "--no-audit",
1447
+ "--ignore-scripts"
1448
+ ], targetRoot);
1449
+ }
1450
+ catch (rollback) {
1451
+ throw new Error(`${String(error)}; npm rollback failed: ${String(rollback)}`);
1452
+ }
1453
+ finally {
1454
+ for (const [filename, content] of backups) {
1455
+ if (content === null)
1456
+ await fs_extra_1.default.remove(filename);
1457
+ else
1458
+ await fs_extra_1.default.writeFile(filename, content);
1459
+ }
1460
+ }
1028
1461
  throw error;
1029
1462
  }
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
1463
  });
1048
1464
  }
1049
- /** Removes one project-installed npm plugin. Built-in packages are left alone. */
1465
+ /** Remove the dependency from the manifest that owns it, preserving user configuration. */
1050
1466
  async function uninstallNpmPlugin(pluginId, options = {}) {
1051
1467
  if (!PLUGIN_ID.test(pluginId))
1052
1468
  throw new Error(`Invalid plugin id: ${pluginId}`);
1469
+ if (isEssentialNpmPlugin(pluginId))
1470
+ throw new Error(`The essential plugin "${pluginId}" cannot be uninstalled.`);
1053
1471
  const { packageRoot, projectRoot } = npmPluginRoots(options);
1054
1472
  return withProjectLock(projectRoot, async () => {
1055
1473
  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];
1474
+ const bundledName = mappingsOf(host)[pluginId];
1475
+ const targetRoot = typeof bundledName === "string" && dependenciesOf(host)[bundledName] !== undefined
1476
+ ? packageRoot
1477
+ : projectRoot;
1478
+ const manifest = readProjectManifest(targetRoot);
1479
+ const name = mappingsOf(manifest)[pluginId] ??
1480
+ listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId)?.name;
1060
1481
  if (typeof name !== "string" || !PACKAGE_NAME.test(name))
1061
1482
  return null;
1062
1483
  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) };
1484
+ await runNpm(["--prefix", targetRoot, "uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], targetRoot);
1485
+ const updated = readProjectManifest(targetRoot);
1486
+ const plugins = { ...mappingsOf(updated) };
1065
1487
  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);
1488
+ updated.elementspanel = { ...objectValue(updated.elementspanel), plugins };
1489
+ writeProjectManifest(targetRoot, updated);
1073
1490
  return record ?? null;
1074
1491
  });
1075
1492
  }
@@ -1218,6 +1635,82 @@ function pluginDirectoryRevision(directory) {
1218
1635
  }
1219
1636
 
1220
1637
 
1638
+ /***/ },
1639
+
1640
+ /***/ "../common/src/plugin_watch.ts"
1641
+ (__unused_webpack_module, exports, __webpack_require__) {
1642
+
1643
+
1644
+ var __importDefault = (this && this.__importDefault) || function (mod) {
1645
+ return (mod && mod.__esModule) ? mod : { "default": mod };
1646
+ };
1647
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1648
+ exports.watchNpmPlugins = watchNpmPlugins;
1649
+ const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
1650
+ const node_path_1 = __importDefault(__webpack_require__("node:path"));
1651
+ const plugin_npm_1 = __webpack_require__("../common/src/plugin_npm.ts");
1652
+ /** Poll package boundaries, allowing npm to finish replacing files before reconciliation. */
1653
+ function watchNpmPlugins(ctx, side, reload, changed) {
1654
+ const stamp = () => {
1655
+ const { packageRoot, projectRoot } = (0, plugin_npm_1.npmPluginRoots)();
1656
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(packageRoot, "package.json")))
1657
+ return "";
1658
+ const files = [];
1659
+ for (const root of [packageRoot, projectRoot])
1660
+ for (const name of [
1661
+ "package.json",
1662
+ "package-lock.json",
1663
+ "npm-shrinkwrap.json",
1664
+ "node_modules/.package-lock.json"
1665
+ ])
1666
+ files.push(node_path_1.default.join(root, name));
1667
+ for (const plugin of (0, plugin_npm_1.listNpmPlugins)(side, { skipInvalid: true }))
1668
+ files.push(node_path_1.default.join(plugin.packageRoot, "package.json"), node_path_1.default.join(plugin.directory, "plugin.json"));
1669
+ return JSON.stringify(files.map((filename) => {
1670
+ try {
1671
+ const stat = node_fs_1.default.statSync(filename);
1672
+ return [filename, stat.size, stat.mtimeMs, stat.ctimeMs];
1673
+ }
1674
+ catch {
1675
+ return [filename, null];
1676
+ }
1677
+ }));
1678
+ };
1679
+ let applied = stamp();
1680
+ let candidate = applied;
1681
+ let busy = false;
1682
+ ctx.setInterval(() => {
1683
+ if (busy || (0, plugin_npm_1.npmPluginOperationPending)())
1684
+ return;
1685
+ try {
1686
+ const next = stamp();
1687
+ if (next === applied) {
1688
+ candidate = next;
1689
+ return;
1690
+ }
1691
+ if (next !== candidate) {
1692
+ candidate = next;
1693
+ return;
1694
+ }
1695
+ busy = true;
1696
+ void reload()
1697
+ .then(() => {
1698
+ applied = next;
1699
+ candidate = next;
1700
+ changed?.();
1701
+ })
1702
+ .catch((error) => ctx.logger.warn("Failed to synchronize npm plugins", error))
1703
+ .finally(() => {
1704
+ busy = false;
1705
+ });
1706
+ }
1707
+ catch (error) {
1708
+ ctx.logger.warn("Failed to inspect npm plugins", error);
1709
+ }
1710
+ }, 2000);
1711
+ }
1712
+
1713
+
1221
1714
  /***/ },
1222
1715
 
1223
1716
  /***/ "../common/src/process_tools.ts"
@@ -1539,6 +2032,9 @@ function npmPluginRoots(side) {
1539
2032
  const { resolveNpmPlugins } = runtimeRequire(resolver);
1540
2033
  return resolveNpmPlugins(packagePath(), side, { skipInvalid: true }).map((record) => ({
1541
2034
  directory: record.directory,
2035
+ packageRoot: record.packageRoot,
2036
+ packageName: record.name,
2037
+ npmSource: record.source,
1542
2038
  ...(record.folder ? { folder: record.folder } : {}),
1543
2039
  // Built-in packages are part of the runtime's default configuration. A
1544
2040
  // project dependency is installed but remains dormant until the user
@@ -3728,6 +4224,13 @@ module.exports = require("node:fs");
3728
4224
 
3729
4225
  /***/ },
3730
4226
 
4227
+ /***/ "node:module"
4228
+ (module) {
4229
+
4230
+ module.exports = require("node:module");
4231
+
4232
+ /***/ },
4233
+
3731
4234
  /***/ "node:os"
3732
4235
  (module) {
3733
4236
 
@@ -3742,6 +4245,13 @@ module.exports = require("node:path");
3742
4245
 
3743
4246
  /***/ },
3744
4247
 
4248
+ /***/ "node:url"
4249
+ (module) {
4250
+
4251
+ module.exports = require("node:url");
4252
+
4253
+ /***/ },
4254
+
3745
4255
  /***/ "os"
3746
4256
  (module) {
3747
4257
 
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "id": "file",
3
- "name": "File Manager",
4
- "version": "1.0.0",
3
+ "version": "1.0.1",
5
4
  "dual": true,
6
- "description": "Owns the daemon's file subsystem: the sandboxed FileManager, chunked uploads, the file/* protocol events and the upload/download HTTP routes. Provides ctx.files, so instance creation, the Java manager, SteamCMD and the mod service reach instance files through it.",
7
- "priority": 30,
8
5
  "backend": "backend/index.cjs"
9
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementspanel/epanel-plugin-file",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Owns the file manager: every /api/files route, the instance file manager card and its Desktop window, the file editor, the image viewer, the upload queue and the upload/download dialogs.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Csituka_D <694428809@qq.com> (https://github.com/JessDaodao)",
@@ -28,10 +28,22 @@
28
28
  "elementspanel": {
29
29
  "id": "file",
30
30
  "panel": {
31
+ "priority": 120,
32
+ "frontendInject": [
33
+ "console",
34
+ "i18n"
35
+ ],
36
+ "styles": [
37
+ "frontend/assets/style-0-style-9-_panel-plugin-build-entry_file-eb11377f.css"
38
+ ],
39
+ "description": "Owns the file manager: every /api/files route, the instance file manager card and its Desktop window, the file editor, the image viewer, the upload queue and the upload/download dialogs.",
31
40
  "directory": "panel",
32
41
  "folder": "file"
33
42
  },
34
43
  "daemon": {
44
+ "priority": 30,
45
+ "displayName": "File Manager",
46
+ "description": "Owns the daemon's file subsystem: the sandboxed FileManager, chunked uploads, the file/* protocol events and the upload/download HTTP routes. Provides ctx.files, so instance creation, the Java manager, SteamCMD and the mod service reach instance files through it.",
35
47
  "directory": "daemon",
36
48
  "folder": "file"
37
49
  }
package/panel/plugin.json CHANGED
@@ -1,16 +1,7 @@
1
1
  {
2
2
  "id": "file",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "dual": true,
5
- "description": "Owns the file manager: every /api/files route, the instance file manager card and its Desktop window, the file editor, the image viewer, the upload queue and the upload/download dialogs.",
6
- "priority": 120,
7
- "frontendInject": [
8
- "console",
9
- "i18n"
10
- ],
11
5
  "backend": "backend/index.cjs",
12
- "frontend": "frontend/panel-plugin-file-b9399327.js",
13
- "styles": [
14
- "frontend/assets/style-0-style-9-_panel-plugin-build-entry_file-eb11377f.css"
15
- ]
6
+ "frontend": "frontend/panel-plugin-file-b9399327.js"
16
7
  }