@elementspanel/epanel-plugin-instance 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/daemon/backend/index.cjs +576 -66
- package/daemon/plugin.json +1 -4
- package/package.json +13 -1
- package/panel/backend/index.cjs +582 -72
- package/panel/plugin.json +2 -11
package/daemon/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; } }));
|
|
@@ -654,7 +658,45 @@ function createFrontendPluginMetadata(manifest, extras = {}) {
|
|
|
654
658
|
};
|
|
655
659
|
}
|
|
656
660
|
const MANIFEST_FILES = ["plugin.json", "manifest.json", "package.json"];
|
|
657
|
-
|
|
661
|
+
const ENTRY_FIELDS = ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"];
|
|
662
|
+
function objectValue(value) {
|
|
663
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
664
|
+
? value
|
|
665
|
+
: {};
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Source plugins keep package.json beside plugin.json. Published npm plugins
|
|
669
|
+
* keep it at the package root, with runtime metadata under elementspanel.<side>.
|
|
670
|
+
* Stop at the first package boundary so a host's metadata cannot reach a plugin.
|
|
671
|
+
*/
|
|
672
|
+
function readPackageMetadata(directory, id) {
|
|
673
|
+
const pluginDirectory = node_path_1.default.resolve(directory);
|
|
674
|
+
let current = pluginDirectory;
|
|
675
|
+
while (true) {
|
|
676
|
+
const filename = node_path_1.default.join(current, "package.json");
|
|
677
|
+
if (node_fs_1.default.existsSync(filename)) {
|
|
678
|
+
const { elementspanel, ...metadata } = objectValue(JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")));
|
|
679
|
+
const { id: packageId, panel, daemon, ...shared } = objectValue(elementspanel);
|
|
680
|
+
const definition = [panel, daemon]
|
|
681
|
+
.map(objectValue)
|
|
682
|
+
.find((side) => typeof side.directory === "string" &&
|
|
683
|
+
node_path_1.default.resolve(current, side.directory) === pluginDirectory);
|
|
684
|
+
if (current !== pluginDirectory && (packageId !== id || !definition))
|
|
685
|
+
return {};
|
|
686
|
+
const { directory: _directory, folder: _folder, ...sideMetadata } = definition ?? {};
|
|
687
|
+
const result = { ...metadata, ...shared, ...sideMetadata };
|
|
688
|
+
// npm's main/entry fields do not define a side's execution entry.
|
|
689
|
+
for (const field of ["dual", ...ENTRY_FIELDS])
|
|
690
|
+
delete result[field];
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
const parent = node_path_1.default.dirname(current);
|
|
694
|
+
if (parent === current)
|
|
695
|
+
return {};
|
|
696
|
+
current = parent;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
/** Merges package metadata with a plugin's identity and entries. Legacy manifests still load. */
|
|
658
700
|
function readPluginManifest(directory, onWarning, fallbackId = node_path_1.default.basename(directory)) {
|
|
659
701
|
for (const file of MANIFEST_FILES) {
|
|
660
702
|
const filePath = node_path_1.default.join(directory, file);
|
|
@@ -667,7 +709,18 @@ function readPluginManifest(directory, onWarning, fallbackId = node_path_1.defau
|
|
|
667
709
|
const id = typeof value.id === "string" ? value.id : fallbackId;
|
|
668
710
|
if (!id.trim())
|
|
669
711
|
return null;
|
|
670
|
-
|
|
712
|
+
const manifest = {
|
|
713
|
+
...value,
|
|
714
|
+
...readPackageMetadata(directory, id.trim()),
|
|
715
|
+
id: id.trim()
|
|
716
|
+
};
|
|
717
|
+
// Identity and execution paths always belong to plugin.json. Package
|
|
718
|
+
// metadata wins for descriptions, priority, configuration and dependencies.
|
|
719
|
+
for (const field of ["version", "dual", ...ENTRY_FIELDS]) {
|
|
720
|
+
if (value[field] !== undefined)
|
|
721
|
+
manifest[field] = value[field];
|
|
722
|
+
}
|
|
723
|
+
return manifest;
|
|
671
724
|
}
|
|
672
725
|
catch (error) {
|
|
673
726
|
onWarning?.(`Failed to read plugin manifest: ${filePath}`, error);
|
|
@@ -765,6 +818,13 @@ function discoverPluginsFromRoots(roots, options) {
|
|
|
765
818
|
discovered.push({
|
|
766
819
|
...plugin,
|
|
767
820
|
manifest,
|
|
821
|
+
...(root.packageRoot
|
|
822
|
+
? {
|
|
823
|
+
packageRoot: root.packageRoot,
|
|
824
|
+
packageName: root.packageName,
|
|
825
|
+
npmSource: root.npmSource
|
|
826
|
+
}
|
|
827
|
+
: {}),
|
|
768
828
|
...(root.folder ? { folder: root.folder } : {})
|
|
769
829
|
});
|
|
770
830
|
}
|
|
@@ -778,6 +838,177 @@ function sortPlugins(plugins) {
|
|
|
778
838
|
}
|
|
779
839
|
|
|
780
840
|
|
|
841
|
+
/***/ },
|
|
842
|
+
|
|
843
|
+
/***/ "../common/src/plugin_modules.ts"
|
|
844
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
848
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
849
|
+
};
|
|
850
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
851
|
+
exports.pluginBackendRevision = pluginBackendRevision;
|
|
852
|
+
exports.forgetPluginModules = forgetPluginModules;
|
|
853
|
+
exports.loadPluginModule = loadPluginModule;
|
|
854
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
855
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
856
|
+
const node_url_1 = __webpack_require__("node:url");
|
|
857
|
+
const node_crypto_1 = __webpack_require__("node:crypto");
|
|
858
|
+
const node_module_1 = __webpack_require__("node:module");
|
|
859
|
+
const plugin_revision_1 = __webpack_require__("../common/src/plugin_revision.ts");
|
|
860
|
+
const REVISION = "epanelPluginRevision";
|
|
861
|
+
const graphs = new Map();
|
|
862
|
+
const previousRoots = new Map();
|
|
863
|
+
let hooksInstalled = false;
|
|
864
|
+
const sharedModules = new Set([
|
|
865
|
+
"cordis",
|
|
866
|
+
"@cordisjs/core",
|
|
867
|
+
"@cordisjs/logger",
|
|
868
|
+
"@cordisjs/timer",
|
|
869
|
+
"@cordisjs/schema",
|
|
870
|
+
"cosmokit",
|
|
871
|
+
"schemastery",
|
|
872
|
+
"reggol"
|
|
873
|
+
]);
|
|
874
|
+
function dependencyRoots(directory) {
|
|
875
|
+
const visited = new Set([directory]);
|
|
876
|
+
const walk = (root) => {
|
|
877
|
+
const filename = node_path_1.default.join(root, "package.json");
|
|
878
|
+
if (!node_fs_1.default.existsSync(filename))
|
|
879
|
+
return;
|
|
880
|
+
const metadata = JSON.parse(node_fs_1.default.readFileSync(filename, "utf8"));
|
|
881
|
+
const localRequire = (0, node_module_1.createRequire)(filename);
|
|
882
|
+
for (const name of Object.keys({
|
|
883
|
+
...metadata.dependencies,
|
|
884
|
+
...metadata.optionalDependencies
|
|
885
|
+
})) {
|
|
886
|
+
if (sharedModules.has(name))
|
|
887
|
+
continue;
|
|
888
|
+
const target = (localRequire.resolve.paths(name) || [])
|
|
889
|
+
.map((directory) => node_path_1.default.join(directory, name, "package.json"))
|
|
890
|
+
.find((candidate) => node_fs_1.default.existsSync(candidate));
|
|
891
|
+
if (!target)
|
|
892
|
+
continue;
|
|
893
|
+
const dependency = node_fs_1.default.realpathSync(node_path_1.default.dirname(target));
|
|
894
|
+
if (visited.has(dependency))
|
|
895
|
+
continue;
|
|
896
|
+
visited.add(dependency);
|
|
897
|
+
walk(dependency);
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
walk(directory);
|
|
901
|
+
visited.delete(directory);
|
|
902
|
+
return [...visited];
|
|
903
|
+
}
|
|
904
|
+
/** Include transitive dependency versions so npm updates replace the affected graph. */
|
|
905
|
+
function pluginBackendRevision(entry, directory) {
|
|
906
|
+
const hash = (0, node_crypto_1.createHash)("sha256").update((0, plugin_revision_1.pluginDirectoryRevision)(node_path_1.default.dirname(entry)));
|
|
907
|
+
for (const root of [directory, ...dependencyRoots(directory)]) {
|
|
908
|
+
const filename = node_path_1.default.join(root, "package.json");
|
|
909
|
+
if (node_fs_1.default.existsSync(filename))
|
|
910
|
+
hash.update(root).update(node_fs_1.default.readFileSync(filename));
|
|
911
|
+
}
|
|
912
|
+
return hash.digest("hex").slice(0, 24);
|
|
913
|
+
}
|
|
914
|
+
function within(root, filename) {
|
|
915
|
+
const relative = node_path_1.default.relative(root, filename);
|
|
916
|
+
return (relative === "" ||
|
|
917
|
+
(!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== ".." && !node_path_1.default.isAbsolute(relative)));
|
|
918
|
+
}
|
|
919
|
+
/** Clear the plugin's complete CJS graph, keeping shared host modules intact. */
|
|
920
|
+
function forgetPluginModules(directory) {
|
|
921
|
+
const runtimeRequire = eval("require");
|
|
922
|
+
const owned = [directory, ...(previousRoots.get(directory) || []), ...dependencyRoots(directory)];
|
|
923
|
+
previousRoots.delete(directory);
|
|
924
|
+
const belongs = (filename) => {
|
|
925
|
+
const parts = filename.split(`${node_path_1.default.sep}node_modules${node_path_1.default.sep}`);
|
|
926
|
+
if (parts.slice(1).some((part) => {
|
|
927
|
+
const name = part.startsWith("@")
|
|
928
|
+
? part.split(node_path_1.default.sep).slice(0, 2).join("/")
|
|
929
|
+
: part.split(node_path_1.default.sep)[0];
|
|
930
|
+
return sharedModules.has(name);
|
|
931
|
+
}))
|
|
932
|
+
return false;
|
|
933
|
+
return owned.some((root) => within(root, filename));
|
|
934
|
+
};
|
|
935
|
+
for (const filename of Object.keys(runtimeRequire.cache)) {
|
|
936
|
+
if (belongs(filename))
|
|
937
|
+
delete runtimeRequire.cache[filename];
|
|
938
|
+
}
|
|
939
|
+
for (const mod of Object.values(runtimeRequire.cache)) {
|
|
940
|
+
if (mod)
|
|
941
|
+
mod.children = mod.children.filter((child) => !belongs(child.filename));
|
|
942
|
+
}
|
|
943
|
+
// npm can move a dependency or change its main entry. Node's CJS resolver
|
|
944
|
+
// retains those paths separately from require.cache.
|
|
945
|
+
const pathCache = runtimeRequire("node:module")._pathCache;
|
|
946
|
+
for (const [key, filename] of Object.entries(pathCache)) {
|
|
947
|
+
if (belongs(filename) || owned.some((root) => key.includes(root)))
|
|
948
|
+
delete pathCache[key];
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
function isEsm(entry) {
|
|
952
|
+
if (entry.endsWith(".mjs"))
|
|
953
|
+
return true;
|
|
954
|
+
if (entry.endsWith(".cjs"))
|
|
955
|
+
return false;
|
|
956
|
+
let directory = node_path_1.default.dirname(entry);
|
|
957
|
+
while (true) {
|
|
958
|
+
const filename = node_path_1.default.join(directory, "package.json");
|
|
959
|
+
if (node_fs_1.default.existsSync(filename))
|
|
960
|
+
return JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")).type === "module";
|
|
961
|
+
const parent = node_path_1.default.dirname(directory);
|
|
962
|
+
if (parent === directory)
|
|
963
|
+
return false;
|
|
964
|
+
directory = parent;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/** Give an ESM entry and its local imports the same immutable revision URL. */
|
|
968
|
+
async function loadPluginModule(entry, directory, revision) {
|
|
969
|
+
const runtimeRequire = eval("require");
|
|
970
|
+
previousRoots.set(directory, dependencyRoots(directory));
|
|
971
|
+
if (!isEsm(entry)) {
|
|
972
|
+
try {
|
|
973
|
+
return runtimeRequire(entry);
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
976
|
+
if (error?.code !== "ERR_REQUIRE_ESM")
|
|
977
|
+
throw error;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
graphs.set(revision, [directory, ...dependencyRoots(directory)]);
|
|
981
|
+
if (!hooksInstalled) {
|
|
982
|
+
const { registerHooks } = runtimeRequire("node:module");
|
|
983
|
+
if (typeof registerHooks !== "function")
|
|
984
|
+
throw new Error("ESM plugin updates require Node.js 22.15 or newer.");
|
|
985
|
+
registerHooks({
|
|
986
|
+
resolve(specifier, context, nextResolve) {
|
|
987
|
+
const resolved = nextResolve(specifier, context);
|
|
988
|
+
if (!context.parentURL?.startsWith("file:") || !resolved.url.startsWith("file:"))
|
|
989
|
+
return resolved;
|
|
990
|
+
const parent = new URL(context.parentURL);
|
|
991
|
+
const parentRevision = parent.searchParams.get(REVISION);
|
|
992
|
+
if (!parentRevision)
|
|
993
|
+
return resolved;
|
|
994
|
+
const roots = graphs.get(parentRevision);
|
|
995
|
+
if (!roots?.some((root) => within(root, (0, node_url_1.fileURLToPath)(parent))) ||
|
|
996
|
+
!roots.some((root) => within(root, (0, node_url_1.fileURLToPath)(resolved.url))))
|
|
997
|
+
return resolved;
|
|
998
|
+
const url = new URL(resolved.url);
|
|
999
|
+
url.searchParams.set(REVISION, parentRevision);
|
|
1000
|
+
return { ...resolved, url: url.href };
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
hooksInstalled = true;
|
|
1004
|
+
}
|
|
1005
|
+
const url = (0, node_url_1.pathToFileURL)(entry);
|
|
1006
|
+
url.searchParams.set(REVISION, revision);
|
|
1007
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
1008
|
+
return dynamicImport(url.href);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
|
|
781
1012
|
/***/ },
|
|
782
1013
|
|
|
783
1014
|
/***/ "../common/src/plugin_npm.ts"
|
|
@@ -792,15 +1023,19 @@ exports.currentPackageRoot = currentPackageRoot;
|
|
|
792
1023
|
exports.currentProjectRoot = currentProjectRoot;
|
|
793
1024
|
exports.npmPluginRoots = npmPluginRoots;
|
|
794
1025
|
exports.listNpmPlugins = listNpmPlugins;
|
|
1026
|
+
exports.npmPluginOperationPending = npmPluginOperationPending;
|
|
1027
|
+
exports.isEssentialNpmPlugin = isEssentialNpmPlugin;
|
|
795
1028
|
exports.installNpmPlugin = installNpmPlugin;
|
|
796
1029
|
exports.uninstallNpmPlugin = uninstallNpmPlugin;
|
|
797
1030
|
const child_process_1 = __webpack_require__("child_process");
|
|
798
1031
|
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
799
1032
|
const path_1 = __importDefault(__webpack_require__("path"));
|
|
800
1033
|
const module_1 = __webpack_require__("module");
|
|
1034
|
+
const plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
|
|
1035
|
+
const plugin_contract_1 = __webpack_require__("../common/src/plugin_contract.ts");
|
|
801
1036
|
const PLUGIN_ID = /^[a-z][a-z0-9_-]{1,63}$/;
|
|
802
1037
|
const PACKAGE_NAME = /^(?:@[a-z0-9_.-]+\/)?epanel-plugin-[a-z0-9_.-]+$/;
|
|
803
|
-
const VERSION = /^[
|
|
1038
|
+
const VERSION = /^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,63}$/;
|
|
804
1039
|
function readJson(filename) {
|
|
805
1040
|
try {
|
|
806
1041
|
const parsed = JSON.parse(fs_extra_1.default.readFileSync(filename, "utf8"));
|
|
@@ -866,8 +1101,20 @@ function pluginRecord(pluginId, name, source, packageRoot, directory, folder, ve
|
|
|
866
1101
|
return { pluginId, name, version, sides: [], directory, folder, packageRoot, source };
|
|
867
1102
|
}
|
|
868
1103
|
function readPackageEntry(packageRoot, name) {
|
|
869
|
-
|
|
870
|
-
|
|
1104
|
+
// Read package boundaries directly: Node caches require.resolve() paths even
|
|
1105
|
+
// after npm moves an installation between nested and hoisted node_modules.
|
|
1106
|
+
let manifestPath;
|
|
1107
|
+
for (const root of [packageRoot, currentProjectRoot(packageRoot)]) {
|
|
1108
|
+
const localRequire = (0, module_1.createRequire)(path_1.default.join(root, "package.json"));
|
|
1109
|
+
manifestPath = (localRequire.resolve.paths(name) || [])
|
|
1110
|
+
.map((directory) => path_1.default.join(directory, name, "package.json"))
|
|
1111
|
+
.find((filename) => fs_extra_1.default.existsSync(filename));
|
|
1112
|
+
if (manifestPath)
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
if (!manifestPath)
|
|
1116
|
+
throw new Error(`Missing npm plugin package: ${name}.`);
|
|
1117
|
+
manifestPath = fs_extra_1.default.realpathSync(manifestPath);
|
|
871
1118
|
const metadata = readJson(manifestPath);
|
|
872
1119
|
if (!metadata)
|
|
873
1120
|
throw new Error(`Missing package metadata for ${name}.`);
|
|
@@ -881,7 +1128,10 @@ function sideDirectory(packageRoot, metadata, side) {
|
|
|
881
1128
|
return undefined;
|
|
882
1129
|
const target = path_1.default.resolve(packageRoot, directory);
|
|
883
1130
|
const relative = path_1.default.relative(packageRoot, target);
|
|
884
|
-
if (relative === "" ||
|
|
1131
|
+
if (relative === "" ||
|
|
1132
|
+
relative === ".." ||
|
|
1133
|
+
relative.startsWith(`..${path_1.default.sep}`) ||
|
|
1134
|
+
path_1.default.isAbsolute(relative))
|
|
885
1135
|
return undefined;
|
|
886
1136
|
return target;
|
|
887
1137
|
}
|
|
@@ -898,7 +1148,7 @@ function recordFromPackage(pluginId, name, source, packageRoot, metadata, side)
|
|
|
898
1148
|
return undefined;
|
|
899
1149
|
const directory = sideDirectory(packageRoot, metadata, target);
|
|
900
1150
|
const folder = stringValue(objectValue(elements[target])?.folder);
|
|
901
|
-
if (!directory || !folder)
|
|
1151
|
+
if (!directory || !PLUGIN_ID.test(folder))
|
|
902
1152
|
return undefined;
|
|
903
1153
|
return {
|
|
904
1154
|
...pluginRecord(pluginId, name, source, packageRoot, directory, folder, version),
|
|
@@ -918,9 +1168,24 @@ function listNpmPlugins(side, options = {}) {
|
|
|
918
1168
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
919
1169
|
if (!host)
|
|
920
1170
|
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
921
|
-
const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
|
|
1171
|
+
const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
|
|
1172
|
+
? null
|
|
1173
|
+
: readProjectManifest(projectRoot);
|
|
922
1174
|
const hostMappings = mappingsOf(host);
|
|
923
|
-
const projectMappings = mappingsOf(project);
|
|
1175
|
+
const projectMappings = { ...mappingsOf(project) };
|
|
1176
|
+
for (const name of Object.keys(dependenciesOf(project))) {
|
|
1177
|
+
if (!PACKAGE_NAME.test(name) || Object.values(projectMappings).includes(name))
|
|
1178
|
+
continue;
|
|
1179
|
+
try {
|
|
1180
|
+
const entry = readPackageEntry(projectRoot, name);
|
|
1181
|
+
const id = stringValue(objectValue(entry.metadata.elementspanel)?.id);
|
|
1182
|
+
if (PLUGIN_ID.test(id) && projectMappings[id] === undefined)
|
|
1183
|
+
projectMappings[id] = name;
|
|
1184
|
+
}
|
|
1185
|
+
catch {
|
|
1186
|
+
/* npm may still be writing the package. */
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
924
1189
|
const mappings = new Map();
|
|
925
1190
|
for (const [pluginId, name] of Object.entries(projectMappings)) {
|
|
926
1191
|
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
@@ -930,6 +1195,8 @@ function listNpmPlugins(side, options = {}) {
|
|
|
930
1195
|
for (const [pluginId, name] of Object.entries(hostMappings)) {
|
|
931
1196
|
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
932
1197
|
continue;
|
|
1198
|
+
if (dependenciesOf(host)[name] === undefined)
|
|
1199
|
+
continue;
|
|
933
1200
|
mappings.set(pluginId, { name, source: "bundled" });
|
|
934
1201
|
}
|
|
935
1202
|
const hostDependencies = dependenciesOf(host);
|
|
@@ -945,7 +1212,7 @@ function listNpmPlugins(side, options = {}) {
|
|
|
945
1212
|
}
|
|
946
1213
|
let entry;
|
|
947
1214
|
try {
|
|
948
|
-
entry = readPackageEntry(packageRoot, mapping.name);
|
|
1215
|
+
entry = readPackageEntry(mapping.source === "project" ? projectRoot : packageRoot, mapping.name);
|
|
949
1216
|
}
|
|
950
1217
|
catch (error) {
|
|
951
1218
|
if (options.skipInvalid)
|
|
@@ -977,7 +1244,12 @@ function runNpm(args, cwd) {
|
|
|
977
1244
|
: process.platform === "win32"
|
|
978
1245
|
? {
|
|
979
1246
|
command: process.env.ComSpec || "cmd.exe",
|
|
980
|
-
args: [
|
|
1247
|
+
args: [
|
|
1248
|
+
"/d",
|
|
1249
|
+
"/s",
|
|
1250
|
+
"/c",
|
|
1251
|
+
`npm ${args.map((item) => `"${item.replace(/"/g, '""')}"`).join(" ")}`
|
|
1252
|
+
],
|
|
981
1253
|
shell: false
|
|
982
1254
|
}
|
|
983
1255
|
: { command: "npm", args, shell: false };
|
|
@@ -996,9 +1268,10 @@ function runNpm(args, cwd) {
|
|
|
996
1268
|
};
|
|
997
1269
|
child.stdout?.on("data", append);
|
|
998
1270
|
child.stderr?.on("data", append);
|
|
1271
|
+
const action = args.find((argument) => argument === "install" || argument === "uninstall") || "operation";
|
|
999
1272
|
const timer = setTimeout(() => {
|
|
1000
1273
|
child.kill("SIGKILL");
|
|
1001
|
-
reject(new Error(`npm ${
|
|
1274
|
+
reject(new Error(`npm ${action} timed out.`));
|
|
1002
1275
|
}, 10 * 60 * 1000);
|
|
1003
1276
|
timer.unref?.();
|
|
1004
1277
|
child.once("error", (error) => {
|
|
@@ -1009,15 +1282,69 @@ function runNpm(args, cwd) {
|
|
|
1009
1282
|
clearTimeout(timer);
|
|
1010
1283
|
if (code === 0)
|
|
1011
1284
|
return resolve(output);
|
|
1012
|
-
reject(new Error(`npm ${
|
|
1285
|
+
reject(new Error(`npm ${action} failed (${code}): ${output.trim()}`));
|
|
1013
1286
|
});
|
|
1014
1287
|
});
|
|
1015
1288
|
}
|
|
1016
|
-
const
|
|
1289
|
+
const operationsKey = Symbol.for("elementspanel.npm.operations");
|
|
1290
|
+
const shared = globalThis;
|
|
1291
|
+
const operations = (shared[operationsKey] ??= new Map());
|
|
1292
|
+
const LOCK_NAME = ".elementspanel-npm.lock";
|
|
1293
|
+
async function acquireProjectLock(projectRoot) {
|
|
1294
|
+
await fs_extra_1.default.ensureDir(projectRoot);
|
|
1295
|
+
const directory = path_1.default.join(projectRoot, LOCK_NAME);
|
|
1296
|
+
const deadline = Date.now() + 11 * 60 * 1000;
|
|
1297
|
+
while (true) {
|
|
1298
|
+
try {
|
|
1299
|
+
await fs_extra_1.default.mkdir(directory);
|
|
1300
|
+
await fs_extra_1.default.writeJson(path_1.default.join(directory, "owner.json"), { pid: process.pid });
|
|
1301
|
+
return () => fs_extra_1.default.remove(directory);
|
|
1302
|
+
}
|
|
1303
|
+
catch (error) {
|
|
1304
|
+
if (error.code !== "EEXIST")
|
|
1305
|
+
throw error;
|
|
1306
|
+
try {
|
|
1307
|
+
const owner = await fs_extra_1.default.readJson(path_1.default.join(directory, "owner.json"));
|
|
1308
|
+
try {
|
|
1309
|
+
process.kill(owner.pid, 0);
|
|
1310
|
+
}
|
|
1311
|
+
catch (failure) {
|
|
1312
|
+
if (failure.code === "ESRCH") {
|
|
1313
|
+
await fs_extra_1.default.remove(directory);
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
catch {
|
|
1319
|
+
if (Date.now() - ((await fs_extra_1.default.stat(directory).catch(() => null))?.mtimeMs ?? Date.now()) >
|
|
1320
|
+
60_000) {
|
|
1321
|
+
await fs_extra_1.default.remove(directory);
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
if (Date.now() > deadline)
|
|
1326
|
+
throw new Error("Another npm plugin operation is still running.");
|
|
1327
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function npmPluginOperationPending() {
|
|
1332
|
+
const { packageRoot, projectRoot } = npmPluginRoots();
|
|
1333
|
+
return (operations.size > 0 ||
|
|
1334
|
+
[packageRoot, projectRoot].some((root) => fs_extra_1.default.existsSync(path_1.default.join(root, LOCK_NAME))));
|
|
1335
|
+
}
|
|
1017
1336
|
function withProjectLock(projectRoot, operation) {
|
|
1018
1337
|
const key = process.platform === "win32" ? projectRoot.toLowerCase() : projectRoot;
|
|
1019
1338
|
const previous = operations.get(key) ?? Promise.resolve();
|
|
1020
|
-
const result = previous.then(
|
|
1339
|
+
const result = previous.then(async () => {
|
|
1340
|
+
const release = await acquireProjectLock(projectRoot);
|
|
1341
|
+
try {
|
|
1342
|
+
return await operation();
|
|
1343
|
+
}
|
|
1344
|
+
finally {
|
|
1345
|
+
await release();
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1021
1348
|
const settled = result.then(() => { }, () => { });
|
|
1022
1349
|
operations.set(key, settled);
|
|
1023
1350
|
return result.finally(() => {
|
|
@@ -1025,18 +1352,18 @@ function withProjectLock(projectRoot, operation) {
|
|
|
1025
1352
|
operations.delete(key);
|
|
1026
1353
|
});
|
|
1027
1354
|
}
|
|
1028
|
-
/**
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
*/
|
|
1355
|
+
/** Core services keep the connection and management interface available. */
|
|
1356
|
+
function isEssentialNpmPlugin(pluginId) {
|
|
1357
|
+
return ["i18n", "storage", "runtime", "server", "console", "config"].includes(pluginId);
|
|
1358
|
+
}
|
|
1359
|
+
/** npm changes dependencies and lockfiles; only append our plugin mapping afterwards. */
|
|
1034
1360
|
async function installNpmPlugin(options) {
|
|
1035
|
-
const { name,
|
|
1036
|
-
|
|
1037
|
-
throw new Error(`Invalid plugin id: ${pluginId}`);
|
|
1361
|
+
const { name, side } = options;
|
|
1362
|
+
const version = options.version || "latest";
|
|
1038
1363
|
if (!PACKAGE_NAME.test(name))
|
|
1039
1364
|
throw new Error(`Invalid npm plugin package: ${name}`);
|
|
1365
|
+
if (options.pluginId && !PLUGIN_ID.test(options.pluginId))
|
|
1366
|
+
throw new Error(`Invalid plugin id: ${options.pluginId}`);
|
|
1040
1367
|
if (!VERSION.test(version))
|
|
1041
1368
|
throw new Error(`Invalid plugin version: ${version}`);
|
|
1042
1369
|
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
@@ -1044,64 +1371,154 @@ async function installNpmPlugin(options) {
|
|
|
1044
1371
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
1045
1372
|
if (!host)
|
|
1046
1373
|
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1374
|
+
const hostId = Object.entries(mappingsOf(host)).find(([, value]) => value === name && dependenciesOf(host)[name] !== undefined)?.[0];
|
|
1375
|
+
const targetRoot = hostId ? packageRoot : projectRoot;
|
|
1376
|
+
if (hostId && isEssentialNpmPlugin(hostId))
|
|
1377
|
+
throw new Error(`The essential plugin "${hostId}" cannot be replaced while running.`);
|
|
1378
|
+
if (options.pluginId &&
|
|
1379
|
+
mappingsOf(host)[options.pluginId] &&
|
|
1380
|
+
dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
|
|
1381
|
+
mappingsOf(host)[options.pluginId] !== name)
|
|
1382
|
+
throw new Error(`BUNDLED:${options.pluginId}`);
|
|
1383
|
+
const original = readProjectManifest(targetRoot);
|
|
1384
|
+
const previous = dependenciesOf(original)[name];
|
|
1385
|
+
let previousVersion = previous;
|
|
1386
|
+
if (previous) {
|
|
1387
|
+
try {
|
|
1388
|
+
previousVersion = readPackageEntry(targetRoot, name).metadata.version || previous;
|
|
1389
|
+
}
|
|
1390
|
+
catch {
|
|
1391
|
+
/* Keep the declared spec if the previous package is incomplete. */
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const backups = new Map();
|
|
1395
|
+
for (const filename of ["package.json", "package-lock.json", "npm-shrinkwrap.json"]) {
|
|
1396
|
+
const target = path_1.default.join(targetRoot, filename);
|
|
1397
|
+
backups.set(target, fs_extra_1.default.existsSync(target) ? fs_extra_1.default.readFileSync(target) : null);
|
|
1398
|
+
}
|
|
1055
1399
|
try {
|
|
1056
|
-
|
|
1400
|
+
await runNpm([
|
|
1401
|
+
"--prefix",
|
|
1402
|
+
targetRoot,
|
|
1403
|
+
"install",
|
|
1404
|
+
`${name}@${version}`,
|
|
1405
|
+
"--save",
|
|
1406
|
+
"--save-exact",
|
|
1407
|
+
"--no-fund",
|
|
1408
|
+
"--no-audit",
|
|
1409
|
+
"--ignore-scripts"
|
|
1410
|
+
], targetRoot);
|
|
1411
|
+
const entry = readPackageEntry(targetRoot, name);
|
|
1412
|
+
const pluginId = options.pluginId || stringValue(objectValue(entry.metadata.elementspanel)?.id);
|
|
1413
|
+
if (!PLUGIN_ID.test(pluginId))
|
|
1414
|
+
throw new Error(`Invalid plugin id in ${name}.`);
|
|
1415
|
+
if (mappingsOf(host)[pluginId] &&
|
|
1416
|
+
dependenciesOf(host)[String(mappingsOf(host)[pluginId])] !== undefined &&
|
|
1417
|
+
mappingsOf(host)[pluginId] !== name)
|
|
1418
|
+
throw new Error(`Plugin id ${pluginId} belongs to a bundled dependency.`);
|
|
1419
|
+
if (hostId && pluginId !== hostId)
|
|
1420
|
+
throw new Error(`The package ${name} changed its plugin id from ${hostId} to ${pluginId}.`);
|
|
1421
|
+
const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
|
|
1422
|
+
if (mapped !== undefined && mapped !== name)
|
|
1423
|
+
throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
|
|
1424
|
+
const record = recordFromPackage(pluginId, name, hostId ? "bundled" : "project", entry.root, entry.metadata, side);
|
|
1425
|
+
if (!record)
|
|
1426
|
+
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
1427
|
+
if (/^[0-9]/.test(version) && record.version !== version)
|
|
1428
|
+
throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
|
|
1429
|
+
if (!process.env.EPANEL_PACKAGE_ROOT &&
|
|
1430
|
+
(0, plugin_manifest_1.discoverPlugins)(path_1.default.join(packageRoot, "plugins"), {
|
|
1431
|
+
entryFields: [],
|
|
1432
|
+
includeDisabled: true
|
|
1433
|
+
}).some((plugin) => plugin.manifest.id === pluginId))
|
|
1434
|
+
throw new Error(`Source plugin ${pluginId} cannot be replaced with an npm dependency.`);
|
|
1435
|
+
const manifest = (0, plugin_manifest_1.readPluginManifest)(record.directory);
|
|
1436
|
+
if (!manifest || manifest.id !== pluginId || manifest.version !== record.version)
|
|
1437
|
+
throw new Error(`The plugin manifest in ${name} does not match its package.`);
|
|
1438
|
+
(0, plugin_contract_1.validatePluginCompatibility)(manifest.elements);
|
|
1439
|
+
for (const field of ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"]) {
|
|
1440
|
+
if (typeof manifest[field] === "string" &&
|
|
1441
|
+
!(0, plugin_manifest_1.resolvePluginEntry)(record.directory, manifest, { entryFields: [field] }))
|
|
1442
|
+
throw new Error(`Invalid ${field} entry in ${name}.`);
|
|
1443
|
+
}
|
|
1444
|
+
const others = listNpmPlugins(side, { packageRoot, projectRoot, skipInvalid: true });
|
|
1445
|
+
if (others.some((plugin) => plugin.pluginId === pluginId && plugin.name !== name))
|
|
1446
|
+
throw new Error(`Plugin id ${pluginId} is already provided by another dependency.`);
|
|
1447
|
+
const collision = others.find((plugin) => plugin.pluginId !== pluginId && plugin.folder === record.folder);
|
|
1448
|
+
if (collision)
|
|
1449
|
+
throw new Error(`Plugin asset directory ${record.folder} is already used by ${collision.name}.`);
|
|
1450
|
+
const updated = readProjectManifest(targetRoot);
|
|
1451
|
+
updated.dependencies = { ...dependenciesOf(updated), [name]: record.version };
|
|
1452
|
+
updated.elementspanel = {
|
|
1453
|
+
...objectValue(updated.elementspanel),
|
|
1454
|
+
plugins: { ...mappingsOf(updated), [pluginId]: name }
|
|
1455
|
+
};
|
|
1456
|
+
writeProjectManifest(targetRoot, updated);
|
|
1457
|
+
return record;
|
|
1057
1458
|
}
|
|
1058
1459
|
catch (error) {
|
|
1059
|
-
|
|
1460
|
+
try {
|
|
1461
|
+
await runNpm(previous
|
|
1462
|
+
? [
|
|
1463
|
+
"--prefix",
|
|
1464
|
+
targetRoot,
|
|
1465
|
+
"install",
|
|
1466
|
+
`${name}@${String(previousVersion)}`,
|
|
1467
|
+
"--save",
|
|
1468
|
+
"--no-fund",
|
|
1469
|
+
"--no-audit",
|
|
1470
|
+
"--ignore-scripts"
|
|
1471
|
+
]
|
|
1472
|
+
: [
|
|
1473
|
+
"--prefix",
|
|
1474
|
+
targetRoot,
|
|
1475
|
+
"uninstall",
|
|
1476
|
+
name,
|
|
1477
|
+
"--no-fund",
|
|
1478
|
+
"--no-audit",
|
|
1479
|
+
"--ignore-scripts"
|
|
1480
|
+
], targetRoot);
|
|
1481
|
+
}
|
|
1482
|
+
catch (rollback) {
|
|
1483
|
+
throw new Error(`${String(error)}; npm rollback failed: ${String(rollback)}`);
|
|
1484
|
+
}
|
|
1485
|
+
finally {
|
|
1486
|
+
for (const [filename, content] of backups) {
|
|
1487
|
+
if (content === null)
|
|
1488
|
+
await fs_extra_1.default.remove(filename);
|
|
1489
|
+
else
|
|
1490
|
+
await fs_extra_1.default.writeFile(filename, content);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1060
1493
|
throw error;
|
|
1061
1494
|
}
|
|
1062
|
-
const record = recordFromPackage(pluginId, name, "project", entry.root, entry.metadata, side);
|
|
1063
|
-
if (!record) {
|
|
1064
|
-
await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
|
|
1065
|
-
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
1066
|
-
}
|
|
1067
|
-
if (record.version !== version) {
|
|
1068
|
-
await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], projectRoot).catch(() => { });
|
|
1069
|
-
throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
|
|
1070
|
-
}
|
|
1071
|
-
const manifest = readProjectManifest(projectRoot);
|
|
1072
|
-
const dependencies = { ...dependenciesOf(manifest), [name]: version };
|
|
1073
|
-
const elementspanel = { ...(objectValue(manifest.elementspanel) ?? {}) };
|
|
1074
|
-
const plugins = { ...mappingsOf(manifest), [pluginId]: name };
|
|
1075
|
-
manifest.dependencies = dependencies;
|
|
1076
|
-
manifest.elementspanel = { ...elementspanel, plugins };
|
|
1077
|
-
writeProjectManifest(projectRoot, manifest);
|
|
1078
|
-
return record;
|
|
1079
1495
|
});
|
|
1080
1496
|
}
|
|
1081
|
-
/**
|
|
1497
|
+
/** Remove the dependency from the manifest that owns it, preserving user configuration. */
|
|
1082
1498
|
async function uninstallNpmPlugin(pluginId, options = {}) {
|
|
1083
1499
|
if (!PLUGIN_ID.test(pluginId))
|
|
1084
1500
|
throw new Error(`Invalid plugin id: ${pluginId}`);
|
|
1501
|
+
if (isEssentialNpmPlugin(pluginId))
|
|
1502
|
+
throw new Error(`The essential plugin "${pluginId}" cannot be uninstalled.`);
|
|
1085
1503
|
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
1086
1504
|
return withProjectLock(projectRoot, async () => {
|
|
1087
1505
|
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1506
|
+
const bundledName = mappingsOf(host)[pluginId];
|
|
1507
|
+
const targetRoot = typeof bundledName === "string" && dependenciesOf(host)[bundledName] !== undefined
|
|
1508
|
+
? packageRoot
|
|
1509
|
+
: projectRoot;
|
|
1510
|
+
const manifest = readProjectManifest(targetRoot);
|
|
1511
|
+
const name = mappingsOf(manifest)[pluginId] ??
|
|
1512
|
+
listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId)?.name;
|
|
1092
1513
|
if (typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
1093
1514
|
return null;
|
|
1094
1515
|
const record = listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId);
|
|
1095
|
-
await runNpm(["uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"],
|
|
1096
|
-
const
|
|
1516
|
+
await runNpm(["--prefix", targetRoot, "uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], targetRoot);
|
|
1517
|
+
const updated = readProjectManifest(targetRoot);
|
|
1518
|
+
const plugins = { ...mappingsOf(updated) };
|
|
1097
1519
|
delete plugins[pluginId];
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
elementspanel.plugins = plugins;
|
|
1101
|
-
else
|
|
1102
|
-
delete elementspanel.plugins;
|
|
1103
|
-
manifest.elementspanel = elementspanel;
|
|
1104
|
-
writeProjectManifest(projectRoot, manifest);
|
|
1520
|
+
updated.elementspanel = { ...objectValue(updated.elementspanel), plugins };
|
|
1521
|
+
writeProjectManifest(targetRoot, updated);
|
|
1105
1522
|
return record ?? null;
|
|
1106
1523
|
});
|
|
1107
1524
|
}
|
|
@@ -1250,6 +1667,82 @@ function pluginDirectoryRevision(directory) {
|
|
|
1250
1667
|
}
|
|
1251
1668
|
|
|
1252
1669
|
|
|
1670
|
+
/***/ },
|
|
1671
|
+
|
|
1672
|
+
/***/ "../common/src/plugin_watch.ts"
|
|
1673
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1677
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1678
|
+
};
|
|
1679
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1680
|
+
exports.watchNpmPlugins = watchNpmPlugins;
|
|
1681
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
1682
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
1683
|
+
const plugin_npm_1 = __webpack_require__("../common/src/plugin_npm.ts");
|
|
1684
|
+
/** Poll package boundaries, allowing npm to finish replacing files before reconciliation. */
|
|
1685
|
+
function watchNpmPlugins(ctx, side, reload, changed) {
|
|
1686
|
+
const stamp = () => {
|
|
1687
|
+
const { packageRoot, projectRoot } = (0, plugin_npm_1.npmPluginRoots)();
|
|
1688
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(packageRoot, "package.json")))
|
|
1689
|
+
return "";
|
|
1690
|
+
const files = [];
|
|
1691
|
+
for (const root of [packageRoot, projectRoot])
|
|
1692
|
+
for (const name of [
|
|
1693
|
+
"package.json",
|
|
1694
|
+
"package-lock.json",
|
|
1695
|
+
"npm-shrinkwrap.json",
|
|
1696
|
+
"node_modules/.package-lock.json"
|
|
1697
|
+
])
|
|
1698
|
+
files.push(node_path_1.default.join(root, name));
|
|
1699
|
+
for (const plugin of (0, plugin_npm_1.listNpmPlugins)(side, { skipInvalid: true }))
|
|
1700
|
+
files.push(node_path_1.default.join(plugin.packageRoot, "package.json"), node_path_1.default.join(plugin.directory, "plugin.json"));
|
|
1701
|
+
return JSON.stringify(files.map((filename) => {
|
|
1702
|
+
try {
|
|
1703
|
+
const stat = node_fs_1.default.statSync(filename);
|
|
1704
|
+
return [filename, stat.size, stat.mtimeMs, stat.ctimeMs];
|
|
1705
|
+
}
|
|
1706
|
+
catch {
|
|
1707
|
+
return [filename, null];
|
|
1708
|
+
}
|
|
1709
|
+
}));
|
|
1710
|
+
};
|
|
1711
|
+
let applied = stamp();
|
|
1712
|
+
let candidate = applied;
|
|
1713
|
+
let busy = false;
|
|
1714
|
+
ctx.setInterval(() => {
|
|
1715
|
+
if (busy || (0, plugin_npm_1.npmPluginOperationPending)())
|
|
1716
|
+
return;
|
|
1717
|
+
try {
|
|
1718
|
+
const next = stamp();
|
|
1719
|
+
if (next === applied) {
|
|
1720
|
+
candidate = next;
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
if (next !== candidate) {
|
|
1724
|
+
candidate = next;
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
busy = true;
|
|
1728
|
+
void reload()
|
|
1729
|
+
.then(() => {
|
|
1730
|
+
applied = next;
|
|
1731
|
+
candidate = next;
|
|
1732
|
+
changed?.();
|
|
1733
|
+
})
|
|
1734
|
+
.catch((error) => ctx.logger.warn("Failed to synchronize npm plugins", error))
|
|
1735
|
+
.finally(() => {
|
|
1736
|
+
busy = false;
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
catch (error) {
|
|
1740
|
+
ctx.logger.warn("Failed to inspect npm plugins", error);
|
|
1741
|
+
}
|
|
1742
|
+
}, 2000);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
|
|
1253
1746
|
/***/ },
|
|
1254
1747
|
|
|
1255
1748
|
/***/ "../common/src/process_tools.ts"
|
|
@@ -1571,6 +2064,9 @@ function npmPluginRoots(side) {
|
|
|
1571
2064
|
const { resolveNpmPlugins } = runtimeRequire(resolver);
|
|
1572
2065
|
return resolveNpmPlugins(packagePath(), side, { skipInvalid: true }).map((record) => ({
|
|
1573
2066
|
directory: record.directory,
|
|
2067
|
+
packageRoot: record.packageRoot,
|
|
2068
|
+
packageName: record.name,
|
|
2069
|
+
npmSource: record.source,
|
|
1574
2070
|
...(record.folder ? { folder: record.folder } : {}),
|
|
1575
2071
|
// Built-in packages are part of the runtime's default configuration. A
|
|
1576
2072
|
// project dependency is installed but remains dormant until the user
|
|
@@ -10320,6 +10816,13 @@ module.exports = require("node:fs");
|
|
|
10320
10816
|
|
|
10321
10817
|
/***/ },
|
|
10322
10818
|
|
|
10819
|
+
/***/ "node:module"
|
|
10820
|
+
(module) {
|
|
10821
|
+
|
|
10822
|
+
module.exports = require("node:module");
|
|
10823
|
+
|
|
10824
|
+
/***/ },
|
|
10825
|
+
|
|
10323
10826
|
/***/ "node:os"
|
|
10324
10827
|
(module) {
|
|
10325
10828
|
|
|
@@ -10341,6 +10844,13 @@ module.exports = require("node:stream");
|
|
|
10341
10844
|
|
|
10342
10845
|
/***/ },
|
|
10343
10846
|
|
|
10847
|
+
/***/ "node:url"
|
|
10848
|
+
(module) {
|
|
10849
|
+
|
|
10850
|
+
module.exports = require("node:url");
|
|
10851
|
+
|
|
10852
|
+
/***/ },
|
|
10853
|
+
|
|
10344
10854
|
/***/ "os"
|
|
10345
10855
|
(module) {
|
|
10346
10856
|
|