chore: move plugins to their own repos and drop the plugins folder

This commit is contained in:
LukeGus committed 2026-10-04 22:40:06 -05:00
1 parent 29a946d02e
commit e02b5fe6e4
3167 files changed
+286 -440749

No files matched your search

-129
View File
@@ -5,64 +5,6 @@ import reactRefresh from "eslint-plugin-react-refresh";
import unusedImports from "eslint-plugin-unused-imports";
import tseslint from "typescript-eslint";
import { globalIgnores } from "eslint/config";
import path from "node:path";
// A plugin may import @termix/plugin-sdk, npm packages and its own files.
// A relative path that climbs out of plugins/<id>/ reaches core or another
// plugin, and "@/..." is core's alias.
const pluginBoundary = {
rules: {
"stay-inside": {
meta: {
type: "problem",
messages: {
core: "A plugin reaches core only through @termix/plugin-sdk. Use an SDK API, or add one.",
outside:
"A plugin cannot import files outside its own folder. Use the SDK, ctx.services, events, actions or slots.",
},
},
create(context) {
const file = context.filename.replace(/\\/g, "/");
const root = /^(.*\/plugins\/[^/]+)\//.exec(file)?.[1];
const check = (node, source) => {
if (typeof source !== "string") return;
if (source === "@" || source.startsWith("@/")) {
context.report({ node, messageId: "core" });
return;
}
if (!root || !source.startsWith(".")) return;
const target = path.posix.normalize(
path.posix.join(path.posix.dirname(file), source),
);
if (target !== root && !target.startsWith(`${root}/`)) {
context.report({ node, messageId: "outside" });
}
};
return {
ImportDeclaration: (node) => check(node, node.source.value),
ExportNamedDeclaration: (node) => check(node, node.source?.value),
ExportAllDeclaration: (node) => check(node, node.source.value),
ImportExpression: (node) => check(node, node.source.value),
// vi.mock("@/x") and require("../../x") name a module too.
CallExpression: (node) => {
const callee = node.callee;
const named =
(callee.type === "Identifier" && callee.name === "require") ||
(callee.type === "MemberExpression" &&
callee.object.type === "Identifier" &&
callee.object.name === "vi" &&
callee.property.type === "Identifier" &&
["mock", "doMock", "importActual", "unmock"].includes(
callee.property.name,
));
const first = node.arguments[0];
if (named && first?.type === "Literal") check(node, first.value);
},
};
},
},
},
};
export default tseslint.config([
globalIgnores([
@@ -162,75 +104,4 @@ export default tseslint.config([
],
},
},
{
// Core must not reach into a plugin. A plugin can be disabled, upgraded
// or removed, so an import from core turns "disabled" into a broken
// build rather than a missing feature. Core talks to plugins through the
// runtime in src/backend/plugins/, which handles absence. Core tests only
// use fixture plugins.
files: ["src/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["**/plugins/*/src/backend/**"],
message:
"Core must not import a plugin backend. Go through the plugin runtime, so disabling the plugin degrades cleanly.",
},
],
},
],
},
},
{
// The shell never imports a plugin. It reaches plugins only through the
// registries their frontends fill via the app object (A7). This block
// also carries the backend rule for src/ui files, because flat config
// replaces a rule's options rather than merging them.
files: ["src/ui/**/*.{ts,tsx}", "src/main.tsx"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["**/plugins/*/src/**", "**/plugins/*/dist/**"],
message:
"The shell does not import plugins. Register the surface through the app object and read it from a registry.",
},
],
},
],
},
},
{
// The other direction. A plugin reaches core only through
// @termix/plugin-sdk, and never reaches into another plugin's source.
files: ["plugins/*/src/**/*.{ts,tsx,mjs}", "plugins/*/tests/**/*.{ts,tsx}"],
plugins: { "termix-plugins": pluginBoundary },
rules: {
"termix-plugins/stay-inside": "error",
},
},
{
// react-i18next's hook reads core's namespace, so plugin strings show as raw keys.
files: ["plugins/*/src/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "react-i18next",
importNames: ["useTranslation", "Trans", "withTranslation"],
message:
"Use useTranslation from @termix/plugin-sdk/frontend so keys resolve in this plugin's namespace.",
},
],
},
],
},
},
]);