mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 23:55:23 +00:00
chore: move plugins to their own repos and drop the plugins folder
This commit is contained in:
1 parent
29a946d02e
commit
e02b5fe6e4
3167 files changed
+286
-440749
No files matched your search
@@ -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.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
Reference in new issue
Block a user