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
@@ -25,24 +25,21 @@ export const BACKEND_EXTERNALS = [
|
||||
"drizzle-orm/*",
|
||||
];
|
||||
|
||||
/**
|
||||
* Resolved by the page's import map to the shell's own copies, because these
|
||||
* must be one instance: React, i18next, the toast store, and the SDK entries
|
||||
* that share state with core. Anything else a plugin imports is bundled.
|
||||
*/
|
||||
export const FRONTEND_EXTERNALS = [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"react-dom/client",
|
||||
"@termix/plugin-sdk",
|
||||
"@termix/plugin-sdk/*",
|
||||
"i18next",
|
||||
"react-i18next",
|
||||
// Core-provided UI libraries. A7 serves these through the SDK ui entry.
|
||||
"lucide-react",
|
||||
"sonner",
|
||||
"axios",
|
||||
"cytoscape",
|
||||
"react-cytoscapejs",
|
||||
"guacamole-common-js",
|
||||
"react-xtermjs",
|
||||
"@xterm/*",
|
||||
// Legacy: the shell's own modules, still reached by alias. A7 removes.
|
||||
"@/*",
|
||||
"@termix/plugin-sdk/frontend",
|
||||
"@termix/plugin-sdk/ui",
|
||||
// Legacy: the shell's own modules, see legacy-core-imports.mjs. D1 removes.
|
||||
"@termix/legacy-core/*",
|
||||
];
|
||||
@@ -12,6 +12,8 @@
|
||||
* Delete this file in D1, once every plugin reaches core through the SDK.
|
||||
*/
|
||||
|
||||
import { legacyCoreSpecifier } from "./legacy-core-specifier.mjs";
|
||||
|
||||
// "../../../src/backend/" or "../../../../src/backend/", any depth.
|
||||
const CORE_BACKEND = /^(?:\.\.\/)+src\/backend\//;
|
||||
// Shared types. Mostly erased at compile, but automations imports a real
|
||||
@@ -27,10 +29,36 @@ const CROSS_PLUGIN = /^(?:\.\.\/)+([a-z0-9-]+)\/(?:src\/)?backend\//;
|
||||
const COMPILED_CORE = "../../../backend/backend/";
|
||||
const COMPILED_TYPES = "../../../backend/types/";
|
||||
|
||||
export function legacyCoreImports({ pluginId, platform }) {
|
||||
const CORE_ALIAS = /^@\//;
|
||||
|
||||
export function legacyCoreImports({ pluginId, platform, insideTermix = true }) {
|
||||
return {
|
||||
name: "legacy-core-imports",
|
||||
setup(build) {
|
||||
// The shell's own modules, reached as "@/..." or by relative path.
|
||||
// Bundling them would give the plugin a second copy of the shell's
|
||||
// contexts and API client, so each becomes a bare specifier the page's
|
||||
// import map resolves to the shell's own module.
|
||||
if (platform === "browser") {
|
||||
const toLegacy = (args) => {
|
||||
const specifier = legacyCoreSpecifier(args.path);
|
||||
if (!specifier) return null;
|
||||
if (!insideTermix) {
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
text: `${pluginId}: "${args.path}" reaches Termix core. Only plugins built inside a Termix checkout may; use @termix/plugin-sdk instead.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return { path: specifier, external: true };
|
||||
};
|
||||
build.onResolve({ filter: CORE_ALIAS }, toLegacy);
|
||||
build.onResolve({ filter: CORE_UI }, toLegacy);
|
||||
build.onResolve({ filter: CORE_TYPES }, toLegacy);
|
||||
}
|
||||
|
||||
build.onResolve({ filter: CORE_BACKEND }, (args) => ({
|
||||
path: args.path.replace(CORE_BACKEND, COMPILED_CORE),
|
||||
external: true,
|
||||
@@ -41,16 +69,6 @@ export function legacyCoreImports({ pluginId, platform }) {
|
||||
external: true,
|
||||
}));
|
||||
|
||||
// The shell's own modules. Only plugins/docker's frontend entry reaches
|
||||
// core this way; every other frontend uses the "@/" alias, which is
|
||||
// externalized by name. A7 removes both.
|
||||
if (platform === "browser") {
|
||||
build.onResolve({ filter: CORE_UI }, (args) => ({
|
||||
path: args.path,
|
||||
external: true,
|
||||
}));
|
||||
}
|
||||
|
||||
build.onResolve({ filter: CROSS_PLUGIN }, (args) => {
|
||||
const [, other] = CROSS_PLUGIN.exec(args.path);
|
||||
if (other === "src" || other === pluginId) return null;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Legacy core modules a bundled plugin frontend still imports.
|
||||
*
|
||||
* Bundled plugin frontends predate the SDK and import the shell's own modules
|
||||
* as "@/..." (or, in a couple of places, by relative path into src/). Bundling
|
||||
* those would give the plugin a second copy of the shell's contexts and API
|
||||
* client, so the CLI rewrites each one to a bare specifier under
|
||||
* "@termix/legacy-core/" and core's build exposes exactly that module through
|
||||
* the import map. Both sides use this one normalizer so they cannot disagree.
|
||||
*
|
||||
* Only plugins built inside a Termix checkout may do this. D1 deletes it.
|
||||
*/
|
||||
|
||||
export const LEGACY_CORE_PREFIX = "@termix/legacy-core/";
|
||||
|
||||
const EXTENSION = /\.(tsx|ts|jsx|js|mjs)$/;
|
||||
|
||||
function clean(rest) {
|
||||
return rest.replace(EXTENSION, "").replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* "@/components/button.tsx" -> "@termix/legacy-core/ui/components/button"
|
||||
* "@/types" -> "@termix/legacy-core/types"
|
||||
* "@/types/connection-log" -> "@termix/legacy-core/types/connection-log"
|
||||
* "../../../src/ui/x.tsx" -> "@termix/legacy-core/ui/x"
|
||||
* Anything else -> null
|
||||
*/
|
||||
export function legacyCoreSpecifier(specifier) {
|
||||
if (specifier.startsWith("@/")) {
|
||||
const rest = clean(specifier.slice(2));
|
||||
if (rest === "types" || rest.startsWith("types/")) {
|
||||
return LEGACY_CORE_PREFIX + rest;
|
||||
}
|
||||
return `${LEGACY_CORE_PREFIX}ui/${rest}`;
|
||||
}
|
||||
const relative = /^(?:\.\.\/)+src\/(ui|types)(\/.*)?$/.exec(specifier);
|
||||
if (relative) {
|
||||
return LEGACY_CORE_PREFIX + clean(`${relative[1]}${relative[2] ?? ""}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every value import in a source file that names a legacy core module.
|
||||
* Type-only imports are skipped: they are erased and never reach the map.
|
||||
*/
|
||||
export function findLegacyCoreImports(source) {
|
||||
const found = new Set();
|
||||
const patterns = [
|
||||
/^\s*(?:import|export)\s+(?!type\s)[^'";]*?\sfrom\s+["']([^"']+)["']/gm,
|
||||
/^\s*import\s+["']([^"']+)["']/gm,
|
||||
/import\(\s*["']([^"']+)["']\s*\)/g,
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
for (const match of source.matchAll(pattern)) {
|
||||
const normalized = legacyCoreSpecifier(match[1]);
|
||||
if (normalized) found.add(normalized);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
Reference in new issue
Block a user