mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: load plugin frontends at runtime and remove plugin ids from the shell
This commit is contained in:
1 parent
49f0286a14
commit
e88d5a52e3
721 files changed
+54265
-44416
No files matched your search
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* The shell knows no plugin by name.
|
||||
*
|
||||
* Fails when anything under src/ui (tests and locales aside) imports from
|
||||
* plugins/, or spells a plugin id or a view a plugin owns (a tab, panel or
|
||||
* dashboard card id from a manifest) as a string literal. What the shell
|
||||
* needs from a plugin comes through the registries instead.
|
||||
*
|
||||
* A few literals are data rather than knowledge of a plugin: host protocol
|
||||
* names that are columns on the host until Phase B moves them, and the
|
||||
* terminal's own websocket, which lives in core until the terminal moves.
|
||||
* Those are listed with a reason in scripts/shell-plugin-id-allowlist.json,
|
||||
* which fails too when an entry stops being needed, so it only shrinks.
|
||||
*/
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
const UI = path.join(ROOT, "src", "ui");
|
||||
const ALLOWLIST = path.join(__dirname, "shell-plugin-id-allowlist.json");
|
||||
|
||||
function manifests() {
|
||||
const dir = path.join(ROOT, "plugins");
|
||||
return fs
|
||||
.readdirSync(dir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => path.join(dir, entry.name, "manifest.json"))
|
||||
.filter((file) => fs.existsSync(file))
|
||||
.map((file) => JSON.parse(fs.readFileSync(file, "utf8")));
|
||||
}
|
||||
|
||||
function pluginNames() {
|
||||
const names = new Set();
|
||||
for (const manifest of manifests()) {
|
||||
names.add(manifest.id);
|
||||
const contributes = manifest.contributes ?? {};
|
||||
for (const list of [
|
||||
contributes.tabs,
|
||||
contributes.panels,
|
||||
contributes.dashboardCards,
|
||||
]) {
|
||||
for (const view of list ?? []) names.add(view.id);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
function walk(dir, out) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "tests" || entry.name === "locales") continue;
|
||||
walk(full, out);
|
||||
} else if (/\.(tsx?|mjs|jsx?)$/.test(entry.name)) {
|
||||
out.push(full);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function scan() {
|
||||
const names = pluginNames();
|
||||
const found = {};
|
||||
const add = (file, what) => {
|
||||
const key = path.relative(ROOT, file).replaceAll("\\", "/");
|
||||
(found[key] ??= new Set()).add(what);
|
||||
};
|
||||
|
||||
for (const file of walk(UI, [])) {
|
||||
const source = fs.readFileSync(file, "utf8");
|
||||
for (const match of source.matchAll(
|
||||
/(?:from|import)\s*\(?\s*["']([^"']+)["']/g,
|
||||
)) {
|
||||
if (/(^|\/)plugins\//.test(match[1])) add(file, `import ${match[1]}`);
|
||||
}
|
||||
for (const match of source.matchAll(/["'`]([a-z][a-z0-9_-]*)["'`]/g)) {
|
||||
if (names.has(match[1])) add(file, match[1]);
|
||||
}
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(found).map(([file, set]) => [file, [...set].sort()]),
|
||||
);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const found = scan();
|
||||
if (process.argv.includes("--list")) {
|
||||
console.log(JSON.stringify(found, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const allow = JSON.parse(fs.readFileSync(ALLOWLIST, "utf8")).files ?? {};
|
||||
const problems = [];
|
||||
|
||||
for (const [file, literals] of Object.entries(found)) {
|
||||
const allowed = new Set(allow[file]?.literals ?? []);
|
||||
for (const literal of literals) {
|
||||
if (!allowed.has(literal)) problems.push(`${file}: ${literal}`);
|
||||
}
|
||||
}
|
||||
for (const [file, entry] of Object.entries(allow)) {
|
||||
for (const literal of entry.literals ?? []) {
|
||||
if (!found[file]?.includes(literal)) {
|
||||
problems.push(
|
||||
`${file}: allowlisted "${literal}" no longer appears; remove it`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (problems.length > 0) {
|
||||
console.error("The shell names plugins it should reach through a registry:");
|
||||
for (const problem of problems) console.error(` ${problem}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user