single snapshot test
This commit is contained in:
145
dist/registry.js
vendored
Normal file
145
dist/registry.js
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import AjvModule from "ajv";
|
||||
const Ajv = AjvModule.default || AjvModule;
|
||||
import { pluginManifestSchema } from "./schema.js";
|
||||
export class PluginRegistry {
|
||||
pluginsDir;
|
||||
stateFile;
|
||||
basePath;
|
||||
ajv = new Ajv({ allErrors: true });
|
||||
validate = this.ajv.compile(pluginManifestSchema);
|
||||
plugins = new Map();
|
||||
state = new Map();
|
||||
constructor(pluginsDir, stateFile, basePath = "") {
|
||||
this.pluginsDir = pluginsDir;
|
||||
this.stateFile = stateFile;
|
||||
this.basePath = basePath;
|
||||
this.loadState();
|
||||
}
|
||||
loadState() {
|
||||
this.state.clear();
|
||||
if (!fs.existsSync(this.stateFile))
|
||||
return;
|
||||
try {
|
||||
const raw = JSON.parse(fs.readFileSync(this.stateFile, "utf8"));
|
||||
for (const [id, enabled] of Object.entries(raw.enabled || {})) {
|
||||
this.state.set(id, !!enabled);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// ignore corrupt state file and fall back to defaults
|
||||
}
|
||||
}
|
||||
saveState() {
|
||||
fs.mkdirSync(path.dirname(this.stateFile), { recursive: true });
|
||||
const enabled = Object.fromEntries([...this.state.entries()]);
|
||||
fs.writeFileSync(this.stateFile, JSON.stringify({ enabled }, null, 2));
|
||||
}
|
||||
load() {
|
||||
this.plugins.clear();
|
||||
this.loadState();
|
||||
if (!fs.existsSync(this.pluginsDir))
|
||||
return [];
|
||||
const dirs = fs
|
||||
.readdirSync(this.pluginsDir, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory())
|
||||
.map((d) => d.name);
|
||||
for (const dir of dirs) {
|
||||
const pluginPath = path.join(this.pluginsDir, dir);
|
||||
const manifestPath = path.join(pluginPath, "plugin.json");
|
||||
if (!fs.existsSync(manifestPath))
|
||||
continue;
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||||
const valid = this.validate(manifest);
|
||||
const enabled = this.state.has(manifest.id) ? !!this.state.get(manifest.id) : !!manifest.enabledByDefault;
|
||||
this.plugins.set(manifest.id, {
|
||||
manifest,
|
||||
path: pluginPath,
|
||||
enabled,
|
||||
valid: !!valid,
|
||||
errors: valid ? [] : (this.validate.errors || []).map((e) => `${e.instancePath || "/"} ${e.message}`),
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
this.plugins.set(dir, {
|
||||
manifest: { id: dir, name: dir, version: "0.0.0" },
|
||||
path: pluginPath,
|
||||
enabled: false,
|
||||
valid: false,
|
||||
errors: [String(err?.message || err)],
|
||||
});
|
||||
}
|
||||
}
|
||||
return [...this.plugins.values()];
|
||||
}
|
||||
list() {
|
||||
return [...this.plugins.values()];
|
||||
}
|
||||
get(id) {
|
||||
return this.plugins.get(id);
|
||||
}
|
||||
enable(id) {
|
||||
const r = this.plugins.get(id);
|
||||
if (r) {
|
||||
r.enabled = true;
|
||||
this.state.set(id, true);
|
||||
this.saveState();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
disable(id) {
|
||||
const r = this.plugins.get(id);
|
||||
if (r) {
|
||||
r.enabled = false;
|
||||
this.state.set(id, false);
|
||||
this.saveState();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
resolveContribution(pluginId, manifest, contribution) {
|
||||
const uiBase = `${this.basePath}/plugins/${pluginId}/ui`;
|
||||
const apiBase = `${this.basePath}/plugins/${pluginId}/api`;
|
||||
return {
|
||||
...contribution,
|
||||
pluginId,
|
||||
pluginName: manifest.name,
|
||||
pluginVersion: manifest.version,
|
||||
uiBase,
|
||||
apiBase,
|
||||
mountUrl: contribution.mount ? `${uiBase}/${contribution.mount.replace(/^\/+/, "")}` : undefined,
|
||||
cssUrl: contribution.css ? `${uiBase}/${contribution.css.replace(/^\/+/, "")}` : undefined,
|
||||
};
|
||||
}
|
||||
hooksIndex() {
|
||||
const m = {};
|
||||
for (const r of this.plugins.values()) {
|
||||
if (!r.enabled || !r.valid)
|
||||
continue;
|
||||
for (const h of r.manifest.hooks || []) {
|
||||
m[h] ||= [];
|
||||
m[h].push(r.manifest.id);
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
resolvedHooks() {
|
||||
const m = {};
|
||||
for (const r of this.plugins.values()) {
|
||||
if (!r.enabled || !r.valid)
|
||||
continue;
|
||||
const contributions = r.manifest.contributions || {};
|
||||
for (const [hook, items] of Object.entries(contributions)) {
|
||||
if (!items?.length)
|
||||
continue;
|
||||
m[hook] ||= [];
|
||||
for (const item of items) {
|
||||
m[hook].push(this.resolveContribution(r.manifest.id, r.manifest, item));
|
||||
}
|
||||
m[hook].sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user