@elementspanel/epanel-plugin-market 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.
package/panel/backend/index.cjs
CHANGED
|
@@ -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; } }));
|
|
@@ -623,7 +627,45 @@ function createFrontendPluginMetadata(manifest, extras = {}) {
|
|
|
623
627
|
};
|
|
624
628
|
}
|
|
625
629
|
const MANIFEST_FILES = ["plugin.json", "manifest.json", "package.json"];
|
|
626
|
-
|
|
630
|
+
const ENTRY_FIELDS = ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"];
|
|
631
|
+
function objectValue(value) {
|
|
632
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
633
|
+
? value
|
|
634
|
+
: {};
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Source plugins keep package.json beside plugin.json. Published npm plugins
|
|
638
|
+
* keep it at the package root, with runtime metadata under elementspanel.<side>.
|
|
639
|
+
* Stop at the first package boundary so a host's metadata cannot reach a plugin.
|
|
640
|
+
*/
|
|
641
|
+
function readPackageMetadata(directory, id) {
|
|
642
|
+
const pluginDirectory = node_path_1.default.resolve(directory);
|
|
643
|
+
let current = pluginDirectory;
|
|
644
|
+
while (true) {
|
|
645
|
+
const filename = node_path_1.default.join(current, "package.json");
|
|
646
|
+
if (node_fs_1.default.existsSync(filename)) {
|
|
647
|
+
const { elementspanel, ...metadata } = objectValue(JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")));
|
|
648
|
+
const { id: packageId, panel, daemon, ...shared } = objectValue(elementspanel);
|
|
649
|
+
const definition = [panel, daemon]
|
|
650
|
+
.map(objectValue)
|
|
651
|
+
.find((side) => typeof side.directory === "string" &&
|
|
652
|
+
node_path_1.default.resolve(current, side.directory) === pluginDirectory);
|
|
653
|
+
if (current !== pluginDirectory && (packageId !== id || !definition))
|
|
654
|
+
return {};
|
|
655
|
+
const { directory: _directory, folder: _folder, ...sideMetadata } = definition !== null && definition !== void 0 ? definition : {};
|
|
656
|
+
const result = { ...metadata, ...shared, ...sideMetadata };
|
|
657
|
+
// npm's main/entry fields do not define a side's execution entry.
|
|
658
|
+
for (const field of ["dual", ...ENTRY_FIELDS])
|
|
659
|
+
delete result[field];
|
|
660
|
+
return result;
|
|
661
|
+
}
|
|
662
|
+
const parent = node_path_1.default.dirname(current);
|
|
663
|
+
if (parent === current)
|
|
664
|
+
return {};
|
|
665
|
+
current = parent;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/** Merges package metadata with a plugin's identity and entries. Legacy manifests still load. */
|
|
627
669
|
function readPluginManifest(directory, onWarning, fallbackId = node_path_1.default.basename(directory)) {
|
|
628
670
|
for (const file of MANIFEST_FILES) {
|
|
629
671
|
const filePath = node_path_1.default.join(directory, file);
|
|
@@ -636,7 +678,18 @@ function readPluginManifest(directory, onWarning, fallbackId = node_path_1.defau
|
|
|
636
678
|
const id = typeof value.id === "string" ? value.id : fallbackId;
|
|
637
679
|
if (!id.trim())
|
|
638
680
|
return null;
|
|
639
|
-
|
|
681
|
+
const manifest = {
|
|
682
|
+
...value,
|
|
683
|
+
...readPackageMetadata(directory, id.trim()),
|
|
684
|
+
id: id.trim()
|
|
685
|
+
};
|
|
686
|
+
// Identity and execution paths always belong to plugin.json. Package
|
|
687
|
+
// metadata wins for descriptions, priority, configuration and dependencies.
|
|
688
|
+
for (const field of ["version", "dual", ...ENTRY_FIELDS]) {
|
|
689
|
+
if (value[field] !== undefined)
|
|
690
|
+
manifest[field] = value[field];
|
|
691
|
+
}
|
|
692
|
+
return manifest;
|
|
640
693
|
}
|
|
641
694
|
catch (error) {
|
|
642
695
|
onWarning === null || onWarning === void 0 ? void 0 : onWarning(`Failed to read plugin manifest: ${filePath}`, error);
|
|
@@ -738,6 +791,13 @@ function discoverPluginsFromRoots(roots, options) {
|
|
|
738
791
|
discovered.push({
|
|
739
792
|
...plugin,
|
|
740
793
|
manifest,
|
|
794
|
+
...(root.packageRoot
|
|
795
|
+
? {
|
|
796
|
+
packageRoot: root.packageRoot,
|
|
797
|
+
packageName: root.packageName,
|
|
798
|
+
npmSource: root.npmSource
|
|
799
|
+
}
|
|
800
|
+
: {}),
|
|
741
801
|
...(root.folder ? { folder: root.folder } : {})
|
|
742
802
|
});
|
|
743
803
|
}
|
|
@@ -751,6 +811,178 @@ function sortPlugins(plugins) {
|
|
|
751
811
|
}
|
|
752
812
|
|
|
753
813
|
|
|
814
|
+
/***/ },
|
|
815
|
+
|
|
816
|
+
/***/ "../common/src/plugin_modules.ts"
|
|
817
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
821
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
822
|
+
};
|
|
823
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
824
|
+
exports.pluginBackendRevision = pluginBackendRevision;
|
|
825
|
+
exports.forgetPluginModules = forgetPluginModules;
|
|
826
|
+
exports.loadPluginModule = loadPluginModule;
|
|
827
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
828
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
829
|
+
const node_url_1 = __webpack_require__("node:url");
|
|
830
|
+
const node_crypto_1 = __webpack_require__("node:crypto");
|
|
831
|
+
const node_module_1 = __webpack_require__("node:module");
|
|
832
|
+
const plugin_revision_1 = __webpack_require__("../common/src/plugin_revision.ts");
|
|
833
|
+
const REVISION = "epanelPluginRevision";
|
|
834
|
+
const graphs = new Map();
|
|
835
|
+
const previousRoots = new Map();
|
|
836
|
+
let hooksInstalled = false;
|
|
837
|
+
const sharedModules = new Set([
|
|
838
|
+
"cordis",
|
|
839
|
+
"@cordisjs/core",
|
|
840
|
+
"@cordisjs/logger",
|
|
841
|
+
"@cordisjs/timer",
|
|
842
|
+
"@cordisjs/schema",
|
|
843
|
+
"cosmokit",
|
|
844
|
+
"schemastery",
|
|
845
|
+
"reggol"
|
|
846
|
+
]);
|
|
847
|
+
function dependencyRoots(directory) {
|
|
848
|
+
const visited = new Set([directory]);
|
|
849
|
+
const walk = (root) => {
|
|
850
|
+
const filename = node_path_1.default.join(root, "package.json");
|
|
851
|
+
if (!node_fs_1.default.existsSync(filename))
|
|
852
|
+
return;
|
|
853
|
+
const metadata = JSON.parse(node_fs_1.default.readFileSync(filename, "utf8"));
|
|
854
|
+
const localRequire = (0, node_module_1.createRequire)(filename);
|
|
855
|
+
for (const name of Object.keys({
|
|
856
|
+
...metadata.dependencies,
|
|
857
|
+
...metadata.optionalDependencies
|
|
858
|
+
})) {
|
|
859
|
+
if (sharedModules.has(name))
|
|
860
|
+
continue;
|
|
861
|
+
const target = (localRequire.resolve.paths(name) || [])
|
|
862
|
+
.map((directory) => node_path_1.default.join(directory, name, "package.json"))
|
|
863
|
+
.find((candidate) => node_fs_1.default.existsSync(candidate));
|
|
864
|
+
if (!target)
|
|
865
|
+
continue;
|
|
866
|
+
const dependency = node_fs_1.default.realpathSync(node_path_1.default.dirname(target));
|
|
867
|
+
if (visited.has(dependency))
|
|
868
|
+
continue;
|
|
869
|
+
visited.add(dependency);
|
|
870
|
+
walk(dependency);
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
walk(directory);
|
|
874
|
+
visited.delete(directory);
|
|
875
|
+
return [...visited];
|
|
876
|
+
}
|
|
877
|
+
/** Include transitive dependency versions so npm updates replace the affected graph. */
|
|
878
|
+
function pluginBackendRevision(entry, directory) {
|
|
879
|
+
const hash = (0, node_crypto_1.createHash)("sha256").update((0, plugin_revision_1.pluginDirectoryRevision)(node_path_1.default.dirname(entry)));
|
|
880
|
+
for (const root of [directory, ...dependencyRoots(directory)]) {
|
|
881
|
+
const filename = node_path_1.default.join(root, "package.json");
|
|
882
|
+
if (node_fs_1.default.existsSync(filename))
|
|
883
|
+
hash.update(root).update(node_fs_1.default.readFileSync(filename));
|
|
884
|
+
}
|
|
885
|
+
return hash.digest("hex").slice(0, 24);
|
|
886
|
+
}
|
|
887
|
+
function within(root, filename) {
|
|
888
|
+
const relative = node_path_1.default.relative(root, filename);
|
|
889
|
+
return (relative === "" ||
|
|
890
|
+
(!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== ".." && !node_path_1.default.isAbsolute(relative)));
|
|
891
|
+
}
|
|
892
|
+
/** Clear the plugin's complete CJS graph, keeping shared host modules intact. */
|
|
893
|
+
function forgetPluginModules(directory) {
|
|
894
|
+
const runtimeRequire = eval("require");
|
|
895
|
+
const owned = [directory, ...(previousRoots.get(directory) || []), ...dependencyRoots(directory)];
|
|
896
|
+
previousRoots.delete(directory);
|
|
897
|
+
const belongs = (filename) => {
|
|
898
|
+
const parts = filename.split(`${node_path_1.default.sep}node_modules${node_path_1.default.sep}`);
|
|
899
|
+
if (parts.slice(1).some((part) => {
|
|
900
|
+
const name = part.startsWith("@")
|
|
901
|
+
? part.split(node_path_1.default.sep).slice(0, 2).join("/")
|
|
902
|
+
: part.split(node_path_1.default.sep)[0];
|
|
903
|
+
return sharedModules.has(name);
|
|
904
|
+
}))
|
|
905
|
+
return false;
|
|
906
|
+
return owned.some((root) => within(root, filename));
|
|
907
|
+
};
|
|
908
|
+
for (const filename of Object.keys(runtimeRequire.cache)) {
|
|
909
|
+
if (belongs(filename))
|
|
910
|
+
delete runtimeRequire.cache[filename];
|
|
911
|
+
}
|
|
912
|
+
for (const mod of Object.values(runtimeRequire.cache)) {
|
|
913
|
+
if (mod)
|
|
914
|
+
mod.children = mod.children.filter((child) => !belongs(child.filename));
|
|
915
|
+
}
|
|
916
|
+
// npm can move a dependency or change its main entry. Node's CJS resolver
|
|
917
|
+
// retains those paths separately from require.cache.
|
|
918
|
+
const pathCache = runtimeRequire("node:module")._pathCache;
|
|
919
|
+
for (const [key, filename] of Object.entries(pathCache)) {
|
|
920
|
+
if (belongs(filename) || owned.some((root) => key.includes(root)))
|
|
921
|
+
delete pathCache[key];
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function isEsm(entry) {
|
|
925
|
+
if (entry.endsWith(".mjs"))
|
|
926
|
+
return true;
|
|
927
|
+
if (entry.endsWith(".cjs"))
|
|
928
|
+
return false;
|
|
929
|
+
let directory = node_path_1.default.dirname(entry);
|
|
930
|
+
while (true) {
|
|
931
|
+
const filename = node_path_1.default.join(directory, "package.json");
|
|
932
|
+
if (node_fs_1.default.existsSync(filename))
|
|
933
|
+
return JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")).type === "module";
|
|
934
|
+
const parent = node_path_1.default.dirname(directory);
|
|
935
|
+
if (parent === directory)
|
|
936
|
+
return false;
|
|
937
|
+
directory = parent;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/** Give an ESM entry and its local imports the same immutable revision URL. */
|
|
941
|
+
async function loadPluginModule(entry, directory, revision) {
|
|
942
|
+
const runtimeRequire = eval("require");
|
|
943
|
+
previousRoots.set(directory, dependencyRoots(directory));
|
|
944
|
+
if (!isEsm(entry)) {
|
|
945
|
+
try {
|
|
946
|
+
return runtimeRequire(entry);
|
|
947
|
+
}
|
|
948
|
+
catch (error) {
|
|
949
|
+
if ((error === null || error === void 0 ? void 0 : error.code) !== "ERR_REQUIRE_ESM")
|
|
950
|
+
throw error;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
graphs.set(revision, [directory, ...dependencyRoots(directory)]);
|
|
954
|
+
if (!hooksInstalled) {
|
|
955
|
+
const { registerHooks } = runtimeRequire("node:module");
|
|
956
|
+
if (typeof registerHooks !== "function")
|
|
957
|
+
throw new Error("ESM plugin updates require Node.js 22.15 or newer.");
|
|
958
|
+
registerHooks({
|
|
959
|
+
resolve(specifier, context, nextResolve) {
|
|
960
|
+
var _a;
|
|
961
|
+
const resolved = nextResolve(specifier, context);
|
|
962
|
+
if (!((_a = context.parentURL) === null || _a === void 0 ? void 0 : _a.startsWith("file:")) || !resolved.url.startsWith("file:"))
|
|
963
|
+
return resolved;
|
|
964
|
+
const parent = new URL(context.parentURL);
|
|
965
|
+
const parentRevision = parent.searchParams.get(REVISION);
|
|
966
|
+
if (!parentRevision)
|
|
967
|
+
return resolved;
|
|
968
|
+
const roots = graphs.get(parentRevision);
|
|
969
|
+
if (!(roots === null || roots === void 0 ? void 0 : roots.some((root) => within(root, (0, node_url_1.fileURLToPath)(parent)))) ||
|
|
970
|
+
!roots.some((root) => within(root, (0, node_url_1.fileURLToPath)(resolved.url))))
|
|
971
|
+
return resolved;
|
|
972
|
+
const url = new URL(resolved.url);
|
|
973
|
+
url.searchParams.set(REVISION, parentRevision);
|
|
974
|
+
return { ...resolved, url: url.href };
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
hooksInstalled = true;
|
|
978
|
+
}
|
|
979
|
+
const url = (0, node_url_1.pathToFileURL)(entry);
|
|
980
|
+
url.searchParams.set(REVISION, revision);
|
|
981
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
982
|
+
return dynamicImport(url.href);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
|
|
754
986
|
/***/ },
|
|
755
987
|
|
|
756
988
|
/***/ "../common/src/plugin_npm.ts"
|
|
@@ -760,20 +992,25 @@ function sortPlugins(plugins) {
|
|
|
760
992
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
761
993
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
762
994
|
};
|
|
995
|
+
var _a;
|
|
763
996
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
764
997
|
exports.currentPackageRoot = currentPackageRoot;
|
|
765
998
|
exports.currentProjectRoot = currentProjectRoot;
|
|
766
999
|
exports.npmPluginRoots = npmPluginRoots;
|
|
767
1000
|
exports.listNpmPlugins = listNpmPlugins;
|
|
1001
|
+
exports.npmPluginOperationPending = npmPluginOperationPending;
|
|
1002
|
+
exports.isEssentialNpmPlugin = isEssentialNpmPlugin;
|
|
768
1003
|
exports.installNpmPlugin = installNpmPlugin;
|
|
769
1004
|
exports.uninstallNpmPlugin = uninstallNpmPlugin;
|
|
770
1005
|
const child_process_1 = __webpack_require__("child_process");
|
|
771
1006
|
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
772
1007
|
const path_1 = __importDefault(__webpack_require__("path"));
|
|
773
1008
|
const module_1 = __webpack_require__("module");
|
|
1009
|
+
const plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
|
|
1010
|
+
const plugin_contract_1 = __webpack_require__("../common/src/plugin_contract.ts");
|
|
774
1011
|
const PLUGIN_ID = /^[a-z][a-z0-9_-]{1,63}$/;
|
|
775
1012
|
const PACKAGE_NAME = /^(?:@[a-z0-9_.-]+\/)?epanel-plugin-[a-z0-9_.-]+$/;
|
|
776
|
-
const VERSION = /^[
|
|
1013
|
+
const VERSION = /^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,63}$/;
|
|
777
1014
|
function readJson(filename) {
|
|
778
1015
|
try {
|
|
779
1016
|
const parsed = JSON.parse(fs_extra_1.default.readFileSync(filename, "utf8"));
|
|
@@ -843,8 +1080,20 @@ function pluginRecord(pluginId, name, source, packageRoot, directory, folder, ve
|
|
|
843
1080
|
return { pluginId, name, version, sides: [], directory, folder, packageRoot, source };
|
|
844
1081
|
}
|
|
845
1082
|
function readPackageEntry(packageRoot, name) {
|
|
846
|
-
|
|
847
|
-
|
|
1083
|
+
// Read package boundaries directly: Node caches require.resolve() paths even
|
|
1084
|
+
// after npm moves an installation between nested and hoisted node_modules.
|
|
1085
|
+
let manifestPath;
|
|
1086
|
+
for (const root of [packageRoot, currentProjectRoot(packageRoot)]) {
|
|
1087
|
+
const localRequire = (0, module_1.createRequire)(path_1.default.join(root, "package.json"));
|
|
1088
|
+
manifestPath = (localRequire.resolve.paths(name) || [])
|
|
1089
|
+
.map((directory) => path_1.default.join(directory, name, "package.json"))
|
|
1090
|
+
.find((filename) => fs_extra_1.default.existsSync(filename));
|
|
1091
|
+
if (manifestPath)
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
if (!manifestPath)
|
|
1095
|
+
throw new Error(`Missing npm plugin package: ${name}.`);
|
|
1096
|
+
manifestPath = fs_extra_1.default.realpathSync(manifestPath);
|
|
848
1097
|
const metadata = readJson(manifestPath);
|
|
849
1098
|
if (!metadata)
|
|
850
1099
|
throw new Error(`Missing package metadata for ${name}.`);
|
|
@@ -858,7 +1107,10 @@ function sideDirectory(packageRoot, metadata, side) {
|
|
|
858
1107
|
return undefined;
|
|
859
1108
|
const target = path_1.default.resolve(packageRoot, directory);
|
|
860
1109
|
const relative = path_1.default.relative(packageRoot, target);
|
|
861
|
-
if (relative === "" ||
|
|
1110
|
+
if (relative === "" ||
|
|
1111
|
+
relative === ".." ||
|
|
1112
|
+
relative.startsWith(`..${path_1.default.sep}`) ||
|
|
1113
|
+
path_1.default.isAbsolute(relative))
|
|
862
1114
|
return undefined;
|
|
863
1115
|
return target;
|
|
864
1116
|
}
|
|
@@ -876,7 +1128,7 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
|
|
|
876
1128
|
return undefined;
|
|
877
1129
|
const directory = sideDirectory(packageRoot, metadata, target);
|
|
878
1130
|
const folder = stringValue((_a = objectValue(elements[target])) === null || _a === void 0 ? void 0 : _a.folder);
|
|
879
|
-
if (!directory || !folder)
|
|
1131
|
+
if (!directory || !PLUGIN_ID.test(folder))
|
|
880
1132
|
return undefined;
|
|
881
1133
|
return {
|
|
882
1134
|
...pluginRecord(pluginId, name, source, packageRoot, directory, folder, version),
|
|
@@ -892,14 +1144,29 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
|
|
|
892
1144
|
* is what lets a two-sided project share one dependency list between sides.
|
|
893
1145
|
*/
|
|
894
1146
|
function listNpmPlugins(side, options = {}) {
|
|
895
|
-
var _a;
|
|
1147
|
+
var _a, _b;
|
|
896
1148
|
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
897
1149
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
898
1150
|
if (!host)
|
|
899
1151
|
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
900
|
-
const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
|
|
1152
|
+
const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
|
|
1153
|
+
? null
|
|
1154
|
+
: readProjectManifest(projectRoot);
|
|
901
1155
|
const hostMappings = mappingsOf(host);
|
|
902
|
-
const projectMappings = mappingsOf(project);
|
|
1156
|
+
const projectMappings = { ...mappingsOf(project) };
|
|
1157
|
+
for (const name of Object.keys(dependenciesOf(project))) {
|
|
1158
|
+
if (!PACKAGE_NAME.test(name) || Object.values(projectMappings).includes(name))
|
|
1159
|
+
continue;
|
|
1160
|
+
try {
|
|
1161
|
+
const entry = readPackageEntry(projectRoot, name);
|
|
1162
|
+
const id = stringValue((_a = objectValue(entry.metadata.elementspanel)) === null || _a === void 0 ? void 0 : _a.id);
|
|
1163
|
+
if (PLUGIN_ID.test(id) && projectMappings[id] === undefined)
|
|
1164
|
+
projectMappings[id] = name;
|
|
1165
|
+
}
|
|
1166
|
+
catch (_c) {
|
|
1167
|
+
/* npm may still be writing the package. */
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
903
1170
|
const mappings = new Map();
|
|
904
1171
|
for (const [pluginId, name] of Object.entries(projectMappings)) {
|
|
905
1172
|
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
@@ -909,6 +1176,8 @@ function listNpmPlugins(side, options = {}) {
|
|
|
909
1176
|
for (const [pluginId, name] of Object.entries(hostMappings)) {
|
|
910
1177
|
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
911
1178
|
continue;
|
|
1179
|
+
if (dependenciesOf(host)[name] === undefined)
|
|
1180
|
+
continue;
|
|
912
1181
|
mappings.set(pluginId, { name, source: "bundled" });
|
|
913
1182
|
}
|
|
914
1183
|
const hostDependencies = dependenciesOf(host);
|
|
@@ -916,7 +1185,7 @@ function listNpmPlugins(side, options = {}) {
|
|
|
916
1185
|
const folders = new Set();
|
|
917
1186
|
const records = [];
|
|
918
1187
|
for (const [pluginId, mapping] of mappings) {
|
|
919
|
-
const declared = (
|
|
1188
|
+
const declared = (_b = hostDependencies[mapping.name]) !== null && _b !== void 0 ? _b : projectDependencies[mapping.name];
|
|
920
1189
|
if (declared === undefined) {
|
|
921
1190
|
if (options.skipInvalid)
|
|
922
1191
|
continue;
|
|
@@ -924,7 +1193,7 @@ function listNpmPlugins(side, options = {}) {
|
|
|
924
1193
|
}
|
|
925
1194
|
let entry;
|
|
926
1195
|
try {
|
|
927
|
-
entry = readPackageEntry(packageRoot, mapping.name);
|
|
1196
|
+
entry = readPackageEntry(mapping.source === "project" ? projectRoot : packageRoot, mapping.name);
|
|
928
1197
|
}
|
|
929
1198
|
catch (error) {
|
|
930
1199
|
if (options.skipInvalid)
|
|
@@ -957,7 +1226,12 @@ function runNpm(args, cwd) {
|
|
|
957
1226
|
: process.platform === "win32"
|
|
958
1227
|
? {
|
|
959
1228
|
command: process.env.ComSpec || "cmd.exe",
|
|
960
|
-
args: [
|
|
1229
|
+
args: [
|
|
1230
|
+
"/d",
|
|
1231
|
+
"/s",
|
|
1232
|
+
"/c",
|
|
1233
|
+
`npm ${args.map((item) => `"${item.replace(/"/g, '""')}"`).join(" ")}`
|
|
1234
|
+
],
|
|
961
1235
|
shell: false
|
|
962
1236
|
}
|
|
963
1237
|
: { command: "npm", args, shell: false };
|
|
@@ -976,10 +1250,10 @@ function runNpm(args, cwd) {
|
|
|
976
1250
|
};
|
|
977
1251
|
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", append);
|
|
978
1252
|
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", append);
|
|
1253
|
+
const action = args.find((argument) => argument === "install" || argument === "uninstall") || "operation";
|
|
979
1254
|
const timer = setTimeout(() => {
|
|
980
|
-
var _a;
|
|
981
1255
|
child.kill("SIGKILL");
|
|
982
|
-
reject(new Error(`npm ${
|
|
1256
|
+
reject(new Error(`npm ${action} timed out.`));
|
|
983
1257
|
}, 10 * 60 * 1000);
|
|
984
1258
|
(_c = timer.unref) === null || _c === void 0 ? void 0 : _c.call(timer);
|
|
985
1259
|
child.once("error", (error) => {
|
|
@@ -987,20 +1261,74 @@ function runNpm(args, cwd) {
|
|
|
987
1261
|
reject(error);
|
|
988
1262
|
});
|
|
989
1263
|
child.once("close", (code) => {
|
|
990
|
-
var _a;
|
|
991
1264
|
clearTimeout(timer);
|
|
992
1265
|
if (code === 0)
|
|
993
1266
|
return resolve(output);
|
|
994
|
-
reject(new Error(`npm ${
|
|
1267
|
+
reject(new Error(`npm ${action} failed (${code}): ${output.trim()}`));
|
|
995
1268
|
});
|
|
996
1269
|
});
|
|
997
1270
|
}
|
|
998
|
-
const
|
|
1271
|
+
const operationsKey = Symbol.for("elementspanel.npm.operations");
|
|
1272
|
+
const shared = globalThis;
|
|
1273
|
+
const operations = ((_a = shared[operationsKey]) !== null && _a !== void 0 ? _a : (shared[operationsKey] = new Map()));
|
|
1274
|
+
const LOCK_NAME = ".elementspanel-npm.lock";
|
|
1275
|
+
async function acquireProjectLock(projectRoot) {
|
|
1276
|
+
var _a, _b;
|
|
1277
|
+
await fs_extra_1.default.ensureDir(projectRoot);
|
|
1278
|
+
const directory = path_1.default.join(projectRoot, LOCK_NAME);
|
|
1279
|
+
const deadline = Date.now() + 11 * 60 * 1000;
|
|
1280
|
+
while (true) {
|
|
1281
|
+
try {
|
|
1282
|
+
await fs_extra_1.default.mkdir(directory);
|
|
1283
|
+
await fs_extra_1.default.writeJson(path_1.default.join(directory, "owner.json"), { pid: process.pid });
|
|
1284
|
+
return () => fs_extra_1.default.remove(directory);
|
|
1285
|
+
}
|
|
1286
|
+
catch (error) {
|
|
1287
|
+
if (error.code !== "EEXIST")
|
|
1288
|
+
throw error;
|
|
1289
|
+
try {
|
|
1290
|
+
const owner = await fs_extra_1.default.readJson(path_1.default.join(directory, "owner.json"));
|
|
1291
|
+
try {
|
|
1292
|
+
process.kill(owner.pid, 0);
|
|
1293
|
+
}
|
|
1294
|
+
catch (failure) {
|
|
1295
|
+
if (failure.code === "ESRCH") {
|
|
1296
|
+
await fs_extra_1.default.remove(directory);
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
catch (_c) {
|
|
1302
|
+
if (Date.now() - ((_b = (_a = (await fs_extra_1.default.stat(directory).catch(() => null))) === null || _a === void 0 ? void 0 : _a.mtimeMs) !== null && _b !== void 0 ? _b : Date.now()) >
|
|
1303
|
+
60000) {
|
|
1304
|
+
await fs_extra_1.default.remove(directory);
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (Date.now() > deadline)
|
|
1309
|
+
throw new Error("Another npm plugin operation is still running.");
|
|
1310
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
function npmPluginOperationPending() {
|
|
1315
|
+
const { packageRoot, projectRoot } = npmPluginRoots();
|
|
1316
|
+
return (operations.size > 0 ||
|
|
1317
|
+
[packageRoot, projectRoot].some((root) => fs_extra_1.default.existsSync(path_1.default.join(root, LOCK_NAME))));
|
|
1318
|
+
}
|
|
999
1319
|
function withProjectLock(projectRoot, operation) {
|
|
1000
1320
|
var _a;
|
|
1001
1321
|
const key = process.platform === "win32" ? projectRoot.toLowerCase() : projectRoot;
|
|
1002
1322
|
const previous = (_a = operations.get(key)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
1003
|
-
const result = previous.then(
|
|
1323
|
+
const result = previous.then(async () => {
|
|
1324
|
+
const release = await acquireProjectLock(projectRoot);
|
|
1325
|
+
try {
|
|
1326
|
+
return await operation();
|
|
1327
|
+
}
|
|
1328
|
+
finally {
|
|
1329
|
+
await release();
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1004
1332
|
const settled = result.then(() => { }, () => { });
|
|
1005
1333
|
operations.set(key, settled);
|
|
1006
1334
|
return result.finally(() => {
|
|
@@ -1008,85 +1336,174 @@ function withProjectLock(projectRoot, operation) {
|
|
|
1008
1336
|
operations.delete(key);
|
|
1009
1337
|
});
|
|
1010
1338
|
}
|
|
1011
|
-
/**
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
*/
|
|
1339
|
+
/** Core services keep the connection and management interface available. */
|
|
1340
|
+
function isEssentialNpmPlugin(pluginId) {
|
|
1341
|
+
return ["i18n", "storage", "runtime", "server", "console", "config"].includes(pluginId);
|
|
1342
|
+
}
|
|
1343
|
+
/** npm changes dependencies and lockfiles; only append our plugin mapping afterwards. */
|
|
1017
1344
|
async function installNpmPlugin(options) {
|
|
1018
|
-
const { name,
|
|
1019
|
-
|
|
1020
|
-
throw new Error(`Invalid plugin id: ${pluginId}`);
|
|
1345
|
+
const { name, side } = options;
|
|
1346
|
+
const version = options.version || "latest";
|
|
1021
1347
|
if (!PACKAGE_NAME.test(name))
|
|
1022
1348
|
throw new Error(`Invalid npm plugin package: ${name}`);
|
|
1349
|
+
if (options.pluginId && !PLUGIN_ID.test(options.pluginId))
|
|
1350
|
+
throw new Error(`Invalid plugin id: ${options.pluginId}`);
|
|
1023
1351
|
if (!VERSION.test(version))
|
|
1024
1352
|
throw new Error(`Invalid plugin version: ${version}`);
|
|
1025
1353
|
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
1026
1354
|
return withProjectLock(projectRoot, async () => {
|
|
1027
|
-
var _a;
|
|
1355
|
+
var _a, _b;
|
|
1028
1356
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
1029
1357
|
if (!host)
|
|
1030
1358
|
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1359
|
+
const hostId = (_a = Object.entries(mappingsOf(host)).find(([, value]) => value === name && dependenciesOf(host)[name] !== undefined)) === null || _a === void 0 ? void 0 : _a[0];
|
|
1360
|
+
const targetRoot = hostId ? packageRoot : projectRoot;
|
|
1361
|
+
if (hostId && isEssentialNpmPlugin(hostId))
|
|
1362
|
+
throw new Error(`The essential plugin "${hostId}" cannot be replaced while running.`);
|
|
1363
|
+
if (options.pluginId &&
|
|
1364
|
+
mappingsOf(host)[options.pluginId] &&
|
|
1365
|
+
dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
|
|
1366
|
+
mappingsOf(host)[options.pluginId] !== name)
|
|
1367
|
+
throw new Error(`BUNDLED:${options.pluginId}`);
|
|
1368
|
+
const original = readProjectManifest(targetRoot);
|
|
1369
|
+
const previous = dependenciesOf(original)[name];
|
|
1370
|
+
let previousVersion = previous;
|
|
1371
|
+
if (previous) {
|
|
1372
|
+
try {
|
|
1373
|
+
previousVersion = readPackageEntry(targetRoot, name).metadata.version || previous;
|
|
1374
|
+
}
|
|
1375
|
+
catch (_c) {
|
|
1376
|
+
/* Keep the declared spec if the previous package is incomplete. */
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
const backups = new Map();
|
|
1380
|
+
for (const filename of ["package.json", "package-lock.json", "npm-shrinkwrap.json"]) {
|
|
1381
|
+
const target = path_1.default.join(targetRoot, filename);
|
|
1382
|
+
backups.set(target, fs_extra_1.default.existsSync(target) ? fs_extra_1.default.readFileSync(target) : null);
|
|
1383
|
+
}
|
|
1039
1384
|
try {
|
|
1040
|
-
|
|
1385
|
+
await runNpm([
|
|
1386
|
+
"--prefix",
|
|
1387
|
+
targetRoot,
|
|
1388
|
+
"install",
|
|
1389
|
+
`${name}@${version}`,
|
|
1390
|
+
"--save",
|
|
1391
|
+
"--save-exact",
|
|
1392
|
+
"--no-fund",
|
|
1393
|
+
"--no-audit",
|
|
1394
|
+
"--ignore-scripts"
|
|
1395
|
+
], targetRoot);
|
|
1396
|
+
const entry = readPackageEntry(targetRoot, name);
|
|
1397
|
+
const pluginId = options.pluginId || stringValue((_b = objectValue(entry.metadata.elementspanel)) === null || _b === void 0 ? void 0 : _b.id);
|
|
1398
|
+
if (!PLUGIN_ID.test(pluginId))
|
|
1399
|
+
throw new Error(`Invalid plugin id in ${name}.`);
|
|
1400
|
+
if (mappingsOf(host)[pluginId] &&
|
|
1401
|
+
dependenciesOf(host)[String(mappingsOf(host)[pluginId])] !== undefined &&
|
|
1402
|
+
mappingsOf(host)[pluginId] !== name)
|
|
1403
|
+
throw new Error(`Plugin id ${pluginId} belongs to a bundled dependency.`);
|
|
1404
|
+
if (hostId && pluginId !== hostId)
|
|
1405
|
+
throw new Error(`The package ${name} changed its plugin id from ${hostId} to ${pluginId}.`);
|
|
1406
|
+
const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
|
|
1407
|
+
if (mapped !== undefined && mapped !== name)
|
|
1408
|
+
throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
|
|
1409
|
+
const record = recordFromPackage(pluginId, name, hostId ? "bundled" : "project", entry.root, entry.metadata, side);
|
|
1410
|
+
if (!record)
|
|
1411
|
+
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
1412
|
+
if (/^[0-9]/.test(version) && record.version !== version)
|
|
1413
|
+
throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
|
|
1414
|
+
if (!process.env.EPANEL_PACKAGE_ROOT &&
|
|
1415
|
+
(0, plugin_manifest_1.discoverPlugins)(path_1.default.join(packageRoot, "plugins"), {
|
|
1416
|
+
entryFields: [],
|
|
1417
|
+
includeDisabled: true
|
|
1418
|
+
}).some((plugin) => plugin.manifest.id === pluginId))
|
|
1419
|
+
throw new Error(`Source plugin ${pluginId} cannot be replaced with an npm dependency.`);
|
|
1420
|
+
const manifest = (0, plugin_manifest_1.readPluginManifest)(record.directory);
|
|
1421
|
+
if (!manifest || manifest.id !== pluginId || manifest.version !== record.version)
|
|
1422
|
+
throw new Error(`The plugin manifest in ${name} does not match its package.`);
|
|
1423
|
+
(0, plugin_contract_1.validatePluginCompatibility)(manifest.elements);
|
|
1424
|
+
for (const field of ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"]) {
|
|
1425
|
+
if (typeof manifest[field] === "string" &&
|
|
1426
|
+
!(0, plugin_manifest_1.resolvePluginEntry)(record.directory, manifest, { entryFields: [field] }))
|
|
1427
|
+
throw new Error(`Invalid ${field} entry in ${name}.`);
|
|
1428
|
+
}
|
|
1429
|
+
const others = listNpmPlugins(side, { packageRoot, projectRoot, skipInvalid: true });
|
|
1430
|
+
if (others.some((plugin) => plugin.pluginId === pluginId && plugin.name !== name))
|
|
1431
|
+
throw new Error(`Plugin id ${pluginId} is already provided by another dependency.`);
|
|
1432
|
+
const collision = others.find((plugin) => plugin.pluginId !== pluginId && plugin.folder === record.folder);
|
|
1433
|
+
if (collision)
|
|
1434
|
+
throw new Error(`Plugin asset directory ${record.folder} is already used by ${collision.name}.`);
|
|
1435
|
+
const updated = readProjectManifest(targetRoot);
|
|
1436
|
+
updated.dependencies = { ...dependenciesOf(updated), [name]: record.version };
|
|
1437
|
+
updated.elementspanel = {
|
|
1438
|
+
...objectValue(updated.elementspanel),
|
|
1439
|
+
plugins: { ...mappingsOf(updated), [pluginId]: name }
|
|
1440
|
+
};
|
|
1441
|
+
writeProjectManifest(targetRoot, updated);
|
|
1442
|
+
return record;
|
|
1041
1443
|
}
|
|
1042
1444
|
catch (error) {
|
|
1043
|
-
|
|
1445
|
+
try {
|
|
1446
|
+
await runNpm(previous
|
|
1447
|
+
? [
|
|
1448
|
+
"--prefix",
|
|
1449
|
+
targetRoot,
|
|
1450
|
+
"install",
|
|
1451
|
+
`${name}@${String(previousVersion)}`,
|
|
1452
|
+
"--save",
|
|
1453
|
+
"--no-fund",
|
|
1454
|
+
"--no-audit",
|
|
1455
|
+
"--ignore-scripts"
|
|
1456
|
+
]
|
|
1457
|
+
: [
|
|
1458
|
+
"--prefix",
|
|
1459
|
+
targetRoot,
|
|
1460
|
+
"uninstall",
|
|
1461
|
+
name,
|
|
1462
|
+
"--no-fund",
|
|
1463
|
+
"--no-audit",
|
|
1464
|
+
"--ignore-scripts"
|
|
1465
|
+
], targetRoot);
|
|
1466
|
+
}
|
|
1467
|
+
catch (rollback) {
|
|
1468
|
+
throw new Error(`${String(error)}; npm rollback failed: ${String(rollback)}`);
|
|
1469
|
+
}
|
|
1470
|
+
finally {
|
|
1471
|
+
for (const [filename, content] of backups) {
|
|
1472
|
+
if (content === null)
|
|
1473
|
+
await fs_extra_1.default.remove(filename);
|
|
1474
|
+
else
|
|
1475
|
+
await fs_extra_1.default.writeFile(filename, content);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1044
1478
|
throw error;
|
|
1045
1479
|
}
|
|
1046
|
-
const record = recordFromPackage(pluginId, name, "project", entry.root, entry.metadata, side);
|
|
1047
|
-
if (!record) {
|
|
1048
|
-
await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
|
|
1049
|
-
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
1050
|
-
}
|
|
1051
|
-
if (record.version !== version) {
|
|
1052
|
-
await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
|
|
1053
|
-
throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
|
|
1054
|
-
}
|
|
1055
|
-
const manifest = readProjectManifest(projectRoot);
|
|
1056
|
-
const dependencies = { ...dependenciesOf(manifest), [name]: version };
|
|
1057
|
-
const elementspanel = { ...((_a = objectValue(manifest.elementspanel)) !== null && _a !== void 0 ? _a : {}) };
|
|
1058
|
-
const plugins = { ...mappingsOf(manifest), [pluginId]: name };
|
|
1059
|
-
manifest.dependencies = dependencies;
|
|
1060
|
-
manifest.elementspanel = { ...elementspanel, plugins };
|
|
1061
|
-
writeProjectManifest(projectRoot, manifest);
|
|
1062
|
-
return record;
|
|
1063
1480
|
});
|
|
1064
1481
|
}
|
|
1065
|
-
/**
|
|
1482
|
+
/** Remove the dependency from the manifest that owns it, preserving user configuration. */
|
|
1066
1483
|
async function uninstallNpmPlugin(pluginId, options = {}) {
|
|
1067
1484
|
if (!PLUGIN_ID.test(pluginId))
|
|
1068
1485
|
throw new Error(`Invalid plugin id: ${pluginId}`);
|
|
1486
|
+
if (isEssentialNpmPlugin(pluginId))
|
|
1487
|
+
throw new Error(`The essential plugin "${pluginId}" cannot be uninstalled.`);
|
|
1069
1488
|
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
1070
1489
|
return withProjectLock(projectRoot, async () => {
|
|
1071
|
-
var _a;
|
|
1490
|
+
var _a, _b;
|
|
1072
1491
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1492
|
+
const bundledName = mappingsOf(host)[pluginId];
|
|
1493
|
+
const targetRoot = typeof bundledName === "string" && dependenciesOf(host)[bundledName] !== undefined
|
|
1494
|
+
? packageRoot
|
|
1495
|
+
: projectRoot;
|
|
1496
|
+
const manifest = readProjectManifest(targetRoot);
|
|
1497
|
+
const name = (_a = mappingsOf(manifest)[pluginId]) !== null && _a !== void 0 ? _a : (_b = listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId)) === null || _b === void 0 ? void 0 : _b.name;
|
|
1077
1498
|
if (typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
1078
1499
|
return null;
|
|
1079
1500
|
const record = listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId);
|
|
1080
|
-
await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"],
|
|
1081
|
-
const
|
|
1501
|
+
await runNpm(["--prefix", targetRoot, "uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], targetRoot);
|
|
1502
|
+
const updated = readProjectManifest(targetRoot);
|
|
1503
|
+
const plugins = { ...mappingsOf(updated) };
|
|
1082
1504
|
delete plugins[pluginId];
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
elementspanel.plugins = plugins;
|
|
1086
|
-
else
|
|
1087
|
-
delete elementspanel.plugins;
|
|
1088
|
-
manifest.elementspanel = elementspanel;
|
|
1089
|
-
writeProjectManifest(projectRoot, manifest);
|
|
1505
|
+
updated.elementspanel = { ...objectValue(updated.elementspanel), plugins };
|
|
1506
|
+
writeProjectManifest(targetRoot, updated);
|
|
1090
1507
|
return record !== null && record !== void 0 ? record : null;
|
|
1091
1508
|
});
|
|
1092
1509
|
}
|
|
@@ -1235,6 +1652,82 @@ function pluginDirectoryRevision(directory) {
|
|
|
1235
1652
|
}
|
|
1236
1653
|
|
|
1237
1654
|
|
|
1655
|
+
/***/ },
|
|
1656
|
+
|
|
1657
|
+
/***/ "../common/src/plugin_watch.ts"
|
|
1658
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1662
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1663
|
+
};
|
|
1664
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1665
|
+
exports.watchNpmPlugins = watchNpmPlugins;
|
|
1666
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
1667
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
1668
|
+
const plugin_npm_1 = __webpack_require__("../common/src/plugin_npm.ts");
|
|
1669
|
+
/** Poll package boundaries, allowing npm to finish replacing files before reconciliation. */
|
|
1670
|
+
function watchNpmPlugins(ctx, side, reload, changed) {
|
|
1671
|
+
const stamp = () => {
|
|
1672
|
+
const { packageRoot, projectRoot } = (0, plugin_npm_1.npmPluginRoots)();
|
|
1673
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(packageRoot, "package.json")))
|
|
1674
|
+
return "";
|
|
1675
|
+
const files = [];
|
|
1676
|
+
for (const root of [packageRoot, projectRoot])
|
|
1677
|
+
for (const name of [
|
|
1678
|
+
"package.json",
|
|
1679
|
+
"package-lock.json",
|
|
1680
|
+
"npm-shrinkwrap.json",
|
|
1681
|
+
"node_modules/.package-lock.json"
|
|
1682
|
+
])
|
|
1683
|
+
files.push(node_path_1.default.join(root, name));
|
|
1684
|
+
for (const plugin of (0, plugin_npm_1.listNpmPlugins)(side, { skipInvalid: true }))
|
|
1685
|
+
files.push(node_path_1.default.join(plugin.packageRoot, "package.json"), node_path_1.default.join(plugin.directory, "plugin.json"));
|
|
1686
|
+
return JSON.stringify(files.map((filename) => {
|
|
1687
|
+
try {
|
|
1688
|
+
const stat = node_fs_1.default.statSync(filename);
|
|
1689
|
+
return [filename, stat.size, stat.mtimeMs, stat.ctimeMs];
|
|
1690
|
+
}
|
|
1691
|
+
catch (_a) {
|
|
1692
|
+
return [filename, null];
|
|
1693
|
+
}
|
|
1694
|
+
}));
|
|
1695
|
+
};
|
|
1696
|
+
let applied = stamp();
|
|
1697
|
+
let candidate = applied;
|
|
1698
|
+
let busy = false;
|
|
1699
|
+
ctx.setInterval(() => {
|
|
1700
|
+
if (busy || (0, plugin_npm_1.npmPluginOperationPending)())
|
|
1701
|
+
return;
|
|
1702
|
+
try {
|
|
1703
|
+
const next = stamp();
|
|
1704
|
+
if (next === applied) {
|
|
1705
|
+
candidate = next;
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
if (next !== candidate) {
|
|
1709
|
+
candidate = next;
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
busy = true;
|
|
1713
|
+
void reload()
|
|
1714
|
+
.then(() => {
|
|
1715
|
+
applied = next;
|
|
1716
|
+
candidate = next;
|
|
1717
|
+
changed === null || changed === void 0 ? void 0 : changed();
|
|
1718
|
+
})
|
|
1719
|
+
.catch((error) => ctx.logger.warn("Failed to synchronize npm plugins", error))
|
|
1720
|
+
.finally(() => {
|
|
1721
|
+
busy = false;
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
catch (error) {
|
|
1725
|
+
ctx.logger.warn("Failed to inspect npm plugins", error);
|
|
1726
|
+
}
|
|
1727
|
+
}, 2000);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
|
|
1238
1731
|
/***/ },
|
|
1239
1732
|
|
|
1240
1733
|
/***/ "../common/src/process_tools.ts"
|
|
@@ -1547,6 +2040,9 @@ function npmPluginRoots(side) {
|
|
|
1547
2040
|
const { resolveNpmPlugins } = runtimeRequire(resolver);
|
|
1548
2041
|
return resolveNpmPlugins(packagePath(), side, { skipInvalid: true }).map((record) => ({
|
|
1549
2042
|
directory: record.directory,
|
|
2043
|
+
packageRoot: record.packageRoot,
|
|
2044
|
+
packageName: record.name,
|
|
2045
|
+
npmSource: record.source,
|
|
1550
2046
|
...(record.folder ? { folder: record.folder } : {}),
|
|
1551
2047
|
// Built-in packages are part of the runtime's default configuration. A
|
|
1552
2048
|
// project dependency is installed but remains dormant until the user
|
|
@@ -2216,11 +2712,10 @@ async function apply(ctx) {
|
|
|
2216
2712
|
/**
|
|
2217
2713
|
* Asks the daemons the user picked to re-scan their plugin directories.
|
|
2218
2714
|
*
|
|
2219
|
-
*
|
|
2220
|
-
*
|
|
2221
|
-
* that has just appeared stays invisible to the connection the panel holds.
|
|
2715
|
+
* Current daemons dispatch through their live registry. Reconnecting also
|
|
2716
|
+
* keeps this panel compatible with older daemons that bind routes per socket.
|
|
2222
2717
|
*/
|
|
2223
|
-
async function reloadNodes(daemonIds) {
|
|
2718
|
+
async function reloadNodes(daemonIds, pluginId) {
|
|
2224
2719
|
let reloaded = true;
|
|
2225
2720
|
for (const daemonId of daemonIds) {
|
|
2226
2721
|
const node = ctx.remote.services.getInstance(daemonId);
|
|
@@ -2229,7 +2724,10 @@ async function apply(ctx) {
|
|
|
2229
2724
|
continue;
|
|
2230
2725
|
}
|
|
2231
2726
|
try {
|
|
2232
|
-
await new ctx.remote.Request(node).request("plugin/reload");
|
|
2727
|
+
const inventory = await new ctx.remote.Request(node).request("plugin/reload");
|
|
2728
|
+
if (Array.isArray(inventory) &&
|
|
2729
|
+
inventory.some((plugin) => plugin.id === pluginId && plugin.state === "restart-required"))
|
|
2730
|
+
reloaded = false;
|
|
2233
2731
|
node.refreshReconnect();
|
|
2234
2732
|
}
|
|
2235
2733
|
catch (error) {
|
|
@@ -2246,18 +2744,21 @@ async function apply(ctx) {
|
|
|
2246
2744
|
*
|
|
2247
2745
|
* Both halves re-scan after the dependency is installed: the panel scans its
|
|
2248
2746
|
* own npm plugins, and the selected daemons scan theirs before reconnecting.
|
|
2249
|
-
* Loaders
|
|
2250
|
-
* backend revision; replacing that revision still requires restart.
|
|
2747
|
+
* Loaders dispose the previous revision before activating its replacement.
|
|
2251
2748
|
*/
|
|
2252
|
-
async function hotReload(sides, daemonIds = []) {
|
|
2749
|
+
async function hotReload(sides, daemonIds = [], pluginId) {
|
|
2253
2750
|
if (!sides.length)
|
|
2254
2751
|
return true;
|
|
2255
2752
|
try {
|
|
2256
2753
|
if (sides.includes("panel"))
|
|
2257
2754
|
await ctx.plugins.reload();
|
|
2755
|
+
const panelReady = !sides.includes("panel") ||
|
|
2756
|
+
!ctx.plugins
|
|
2757
|
+
.inventory()
|
|
2758
|
+
.some((plugin) => plugin.id === pluginId && plugin.state === "restart-required");
|
|
2258
2759
|
if (sides.includes("daemon"))
|
|
2259
|
-
return await reloadNodes(daemonIds);
|
|
2260
|
-
return
|
|
2760
|
+
return (await reloadNodes(daemonIds, pluginId)) && panelReady;
|
|
2761
|
+
return panelReady;
|
|
2261
2762
|
}
|
|
2262
2763
|
catch (error) {
|
|
2263
2764
|
ctx.logger.warn(`Failed to reload plugins after a market install: ${error}`);
|
|
@@ -2331,8 +2832,7 @@ async function apply(ctx) {
|
|
|
2331
2832
|
});
|
|
2332
2833
|
// Installing adds a dependency to this installation's package.json and sends the
|
|
2333
2834
|
// same npm install to the nodes the user picked. Both sides are asked to re-scan
|
|
2334
|
-
// after the dependency arrives
|
|
2335
|
-
// in-process.
|
|
2835
|
+
// after the dependency arrives and replace the running plugin revision.
|
|
2336
2836
|
router.post("/plugin/install", requireAdmin, ctx.middleware.speedLimit(3), ctx.middleware.validator({ body: { pluginId: String, name: String } }), async (requestCtx) => {
|
|
2337
2837
|
var _a;
|
|
2338
2838
|
const body = ((_a = requestCtx.request.body) !== null && _a !== void 0 ? _a : {});
|
|
@@ -2350,17 +2850,17 @@ async function apply(ctx) {
|
|
|
2350
2850
|
version: detail.selectedVersion.version,
|
|
2351
2851
|
installedAt: Date.now()
|
|
2352
2852
|
};
|
|
2353
|
-
|
|
2354
|
-
// A source or bundled plugin that ships with the runtime already owns the
|
|
2355
|
-
// id; installing the npm package would be shadowed by it.
|
|
2853
|
+
// Core services and source plugins cannot be replaced while running.
|
|
2356
2854
|
if (sides.includes("panel") &&
|
|
2357
|
-
(
|
|
2358
|
-
ctx.plugins.inventory().some((item) => item.id === info.pluginId))
|
|
2855
|
+
ctx.plugins.inventory().some((item) => item.id === info.pluginId && !item.removable))
|
|
2359
2856
|
throw new plugin_market_1.PluginMarketError("BUNDLED");
|
|
2360
|
-
const replacedVersion = Boolean(previous && previous.version !== info.version);
|
|
2361
2857
|
const changedSides = [];
|
|
2362
2858
|
if (sides.includes("panel")) {
|
|
2363
|
-
await
|
|
2859
|
+
await ctx.plugins.install({
|
|
2860
|
+
name: detail.name,
|
|
2861
|
+
pluginId: detail.id,
|
|
2862
|
+
version: detail.selectedVersion.version
|
|
2863
|
+
});
|
|
2364
2864
|
changedSides.push("panel");
|
|
2365
2865
|
}
|
|
2366
2866
|
const { failedNodes, changedNodes } = sides.includes("daemon") && daemonIds.length
|
|
@@ -2368,10 +2868,9 @@ async function apply(ctx) {
|
|
|
2368
2868
|
: { failedNodes: [], changedNodes: [] };
|
|
2369
2869
|
if (changedNodes.length)
|
|
2370
2870
|
changedSides.push("daemon");
|
|
2371
|
-
const reloaded = await hotReload(changedSides, changedNodes);
|
|
2871
|
+
const reloaded = await hotReload(changedSides, changedNodes, info.pluginId);
|
|
2372
2872
|
return {
|
|
2373
|
-
restartRequired:
|
|
2374
|
-
!reloaded,
|
|
2873
|
+
restartRequired: !reloaded,
|
|
2375
2874
|
installedVersion: (_a = (0, plugin_market_1.listInstalled)().find((item) => item.pluginId === info.pluginId)) === null || _a === void 0 ? void 0 : _a.version,
|
|
2376
2875
|
failedNodes
|
|
2377
2876
|
};
|
|
@@ -2388,7 +2887,10 @@ async function apply(ctx) {
|
|
|
2388
2887
|
requestCtx.body = await withPluginLock(pluginId, async () => {
|
|
2389
2888
|
const selectedNodes = new Set(daemonIds);
|
|
2390
2889
|
const installations = (0, plugin_market_1.readRemoteInstalls)(pluginId).filter((item) => selectedNodes.has(item.daemonId));
|
|
2391
|
-
const
|
|
2890
|
+
const local = ctx.plugins.inventory().find((plugin) => plugin.id === pluginId);
|
|
2891
|
+
if (local)
|
|
2892
|
+
await ctx.plugins.remove(pluginId);
|
|
2893
|
+
const removed = Boolean(local);
|
|
2392
2894
|
const { failedNodes, changedNodes } = await removeFromNodes(installations);
|
|
2393
2895
|
const sides = removed ? ["panel"] : [];
|
|
2394
2896
|
if (changedNodes.length)
|
|
@@ -2398,7 +2900,7 @@ async function apply(ctx) {
|
|
|
2398
2900
|
removed: !remaining,
|
|
2399
2901
|
installedVersion: remaining === null || remaining === void 0 ? void 0 : remaining.version,
|
|
2400
2902
|
failedNodes,
|
|
2401
|
-
restartRequired: !(await hotReload(sides, changedNodes))
|
|
2903
|
+
restartRequired: !(await hotReload(sides, changedNodes, pluginId))
|
|
2402
2904
|
};
|
|
2403
2905
|
});
|
|
2404
2906
|
}
|
|
@@ -2408,7 +2910,12 @@ async function apply(ctx) {
|
|
|
2408
2910
|
});
|
|
2409
2911
|
ctx.settingsForm.declare({
|
|
2410
2912
|
fields: () => [
|
|
2411
|
-
{
|
|
2913
|
+
{
|
|
2914
|
+
key: "pluginMarketAddr",
|
|
2915
|
+
type: "string",
|
|
2916
|
+
title: ctx.i18n.$t("TXT_CODE_PLUGIN_MARKET_ADDR"),
|
|
2917
|
+
description: ctx.i18n.$t("TXT_CODE_PLUGIN_MARKET_ADDR_DESC")
|
|
2918
|
+
},
|
|
2412
2919
|
{ type: "link", title: ctx.i18n.$t("TXT_CODE_PLUGIN_MARKET"), route: "/market/plugins" }
|
|
2413
2920
|
],
|
|
2414
2921
|
read: () => ({ ...(0, market_settings_1.marketSettings)() }),
|
|
@@ -2504,8 +3011,6 @@ exports.recordRemoteInstall = recordRemoteInstall;
|
|
|
2504
3011
|
exports.forgetRemoteInstall = forgetRemoteInstall;
|
|
2505
3012
|
exports.listInstalled = listInstalled;
|
|
2506
3013
|
exports.fetchMarketPluginDetail = fetchMarketPluginDetail;
|
|
2507
|
-
exports.installPanelPlugin = installPanelPlugin;
|
|
2508
|
-
exports.uninstallPanelPlugin = uninstallPanelPlugin;
|
|
2509
3014
|
const crypto_1 = __webpack_require__("crypto");
|
|
2510
3015
|
const path_1 = __importDefault(__webpack_require__("path"));
|
|
2511
3016
|
const axios_1 = __importDefault(__webpack_require__("./node_modules/axios/dist/node/axios.cjs"));
|
|
@@ -2606,7 +3111,7 @@ function localRecord(record) {
|
|
|
2606
3111
|
directories: [record.directory],
|
|
2607
3112
|
daemonIds: [],
|
|
2608
3113
|
source: record.source,
|
|
2609
|
-
removable: record.
|
|
3114
|
+
removable: !(0, mcsmanager_common_1.isEssentialNpmPlugin)(record.pluginId)
|
|
2610
3115
|
};
|
|
2611
3116
|
}
|
|
2612
3117
|
/** Local npm dependencies, both bundled and project-installed, plus node records. */
|
|
@@ -2656,7 +3161,9 @@ function listInstalled() {
|
|
|
2656
3161
|
function sidesOf(value) {
|
|
2657
3162
|
if (!Array.isArray(value))
|
|
2658
3163
|
return [];
|
|
2659
|
-
return [
|
|
3164
|
+
return [
|
|
3165
|
+
...new Set(value.filter((item) => item === "panel" || item === "daemon"))
|
|
3166
|
+
];
|
|
2660
3167
|
}
|
|
2661
3168
|
/**
|
|
2662
3169
|
* Reads the release the page is about to install. The market's catalogue is the
|
|
@@ -2687,33 +3194,6 @@ async function fetchMarketPluginDetail(addr, pluginId, version) {
|
|
|
2687
3194
|
}
|
|
2688
3195
|
};
|
|
2689
3196
|
}
|
|
2690
|
-
/** Adds one npm package to this installation's dependencies. */
|
|
2691
|
-
async function installPanelPlugin(detail) {
|
|
2692
|
-
try {
|
|
2693
|
-
return await (0, mcsmanager_common_1.installNpmPlugin)({
|
|
2694
|
-
name: detail.name,
|
|
2695
|
-
pluginId: detail.id,
|
|
2696
|
-
version: detail.selectedVersion.version,
|
|
2697
|
-
side: "panel"
|
|
2698
|
-
});
|
|
2699
|
-
}
|
|
2700
|
-
catch (error) {
|
|
2701
|
-
if (String(error === null || error === void 0 ? void 0 : error.message).startsWith("BUNDLED:"))
|
|
2702
|
-
throw new PluginMarketError("BUNDLED");
|
|
2703
|
-
throw error;
|
|
2704
|
-
}
|
|
2705
|
-
}
|
|
2706
|
-
/** Removes one project-installed npm plugin from this installation. */
|
|
2707
|
-
async function uninstallPanelPlugin(pluginId) {
|
|
2708
|
-
try {
|
|
2709
|
-
return await (0, mcsmanager_common_1.uninstallNpmPlugin)(pluginId);
|
|
2710
|
-
}
|
|
2711
|
-
catch (error) {
|
|
2712
|
-
if (String(error === null || error === void 0 ? void 0 : error.message).startsWith("BUNDLED:"))
|
|
2713
|
-
throw new PluginMarketError("BUNDLED");
|
|
2714
|
-
throw error;
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
3197
|
|
|
2718
3198
|
|
|
2719
3199
|
/***/ },
|
|
@@ -2879,6 +3359,13 @@ module.exports = require("node:fs");
|
|
|
2879
3359
|
|
|
2880
3360
|
/***/ },
|
|
2881
3361
|
|
|
3362
|
+
/***/ "node:module"
|
|
3363
|
+
(module) {
|
|
3364
|
+
|
|
3365
|
+
module.exports = require("node:module");
|
|
3366
|
+
|
|
3367
|
+
/***/ },
|
|
3368
|
+
|
|
2882
3369
|
/***/ "node:os"
|
|
2883
3370
|
(module) {
|
|
2884
3371
|
|
|
@@ -2893,6 +3380,13 @@ module.exports = require("node:path");
|
|
|
2893
3380
|
|
|
2894
3381
|
/***/ },
|
|
2895
3382
|
|
|
3383
|
+
/***/ "node:url"
|
|
3384
|
+
(module) {
|
|
3385
|
+
|
|
3386
|
+
module.exports = require("node:url");
|
|
3387
|
+
|
|
3388
|
+
/***/ },
|
|
3389
|
+
|
|
2896
3390
|
/***/ "os"
|
|
2897
3391
|
(module) {
|
|
2898
3392
|
|