mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
chore: lock the plugin boundary and remove 2.9.0 leftovers
This commit is contained in:
1 parent
6489b62ece
commit
2961297989
399 files changed
+3964
-5627
No files matched your search
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Core knows no plugin by name.
|
||||
*
|
||||
* Fails when anything under src/ imports from plugins/, or spells a plugin id:
|
||||
* a bare literal ("docker"), a plugin route ("/plugin-api/docker/..."), or an
|
||||
* action, slot or permission id that starts with one ("docker.open"). The
|
||||
* shell (src/ui) also may not spell a view a plugin owns (a tab, panel or
|
||||
* dashboard card id from a manifest). What core needs from a plugin comes
|
||||
* through the registries instead.
|
||||
*
|
||||
* Tests and locales are exempt, and so is src/backend/upgrade/: the one-time
|
||||
* 2.8 to 2.9 data moves have to name the plugin each piece of data moves to,
|
||||
* the same way LEGACY_TABLE_OWNERS in the SDK names the plugin that adopts
|
||||
* each legacy table.
|
||||
*
|
||||
* One plugin id is also an SSH term: "totp" is the one-time-code prompt kind
|
||||
* in keyboard-interactive auth, which the connect pipeline in
|
||||
* src/backend/hosts/connect/ classifies. It is not the login plugin.
|
||||
*/
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
const SRC = path.join(ROOT, "src");
|
||||
const UI = path.join(SRC, "ui");
|
||||
const PLUGINS = path.join(ROOT, "plugins");
|
||||
const EXEMPT_DIRS = new Set(["tests", "locales"]);
|
||||
const EXEMPT_PATHS = [path.join(SRC, "backend", "upgrade")];
|
||||
const SSH_CONNECT = path.join(SRC, "backend", "hosts", "connect");
|
||||
const SSH_TERMS = new Set(["totp"]);
|
||||
|
||||
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 names() {
|
||||
const ids = new Set();
|
||||
const views = new Set();
|
||||
for (const manifest of manifests()) {
|
||||
ids.add(manifest.id);
|
||||
const contributes = manifest.contributes ?? {};
|
||||
for (const list of [
|
||||
contributes.tabs,
|
||||
contributes.panels,
|
||||
contributes.dashboardCards,
|
||||
]) {
|
||||
for (const view of list ?? []) views.add(view.id);
|
||||
}
|
||||
}
|
||||
return { ids, views };
|
||||
}
|
||||
|
||||
function walk(dir, out) {
|
||||
if (EXEMPT_PATHS.includes(dir)) return out;
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (EXEMPT_DIRS.has(entry.name)) continue;
|
||||
walk(full, out);
|
||||
} else if (/\.(tsx?|mjs|cjs|jsx?)$/.test(entry.name)) {
|
||||
out.push(full);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Every string literal in a file, template literals included. */
|
||||
function literals(source) {
|
||||
const out = [];
|
||||
const pattern =
|
||||
/"((?:[^"\\\n]|\\.)*)"|'((?:[^'\\\n]|\\.)*)'|`((?:[^`\\]|\\.)*)`/g;
|
||||
for (const match of source.matchAll(pattern)) {
|
||||
out.push(match[1] ?? match[2] ?? match[3] ?? "");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function scan() {
|
||||
const { ids, views } = names();
|
||||
const found = {};
|
||||
const add = (file, what) => {
|
||||
const key = path.relative(ROOT, file).replaceAll("\\", "/");
|
||||
(found[key] ??= new Set()).add(what);
|
||||
};
|
||||
|
||||
for (const file of walk(SRC, [])) {
|
||||
const source = fs.readFileSync(file, "utf8");
|
||||
const inShell = file.startsWith(UI + path.sep);
|
||||
const inConnect = file.startsWith(SSH_CONNECT + path.sep);
|
||||
for (const match of source.matchAll(
|
||||
/(?:from|import)\s*\(?\s*["']([^"']+)["']/g,
|
||||
)) {
|
||||
const target = match[1].startsWith(".")
|
||||
? path.resolve(path.dirname(file), match[1])
|
||||
: "";
|
||||
if (target.startsWith(PLUGINS + path.sep)) {
|
||||
add(file, `import ${match[1]}`);
|
||||
}
|
||||
}
|
||||
for (const text of literals(source)) {
|
||||
const sshTerm = inConnect && SSH_TERMS.has(text);
|
||||
if ((ids.has(text) && !sshTerm) || (inShell && views.has(text))) {
|
||||
add(file, text);
|
||||
}
|
||||
for (const route of text.matchAll(
|
||||
/\/plugin-(?:api|ws|assets)\/([a-z0-9-]+)/g,
|
||||
)) {
|
||||
if (ids.has(route[1])) add(file, route[0]);
|
||||
}
|
||||
const prefix = /^([a-z][a-z0-9-]*)\.[a-zA-Z]/.exec(text);
|
||||
if (prefix && ids.has(prefix[1])) add(file, text);
|
||||
}
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(found).map(([file, set]) => [file, [...set].sort()]),
|
||||
);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const found = scan();
|
||||
const entries = Object.entries(found);
|
||||
if (process.argv.includes("--list")) {
|
||||
console.log(JSON.stringify(found, null, 2));
|
||||
return;
|
||||
}
|
||||
if (entries.length > 0) {
|
||||
console.error("Core names plugins it should reach through a registry:");
|
||||
for (const [file, list] of entries) {
|
||||
console.error(` ${file}: ${list.join(", ")}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -23,7 +23,7 @@ const SCRIPT_SRC = /script-src ([^;"]*)/;
|
||||
async function main() {
|
||||
const { sharedModules, productionImportMap, importMapCspHash } =
|
||||
await import("./lib/plugin-import-map.mjs");
|
||||
const expected = importMapCspHash(productionImportMap(sharedModules(ROOT)));
|
||||
const expected = importMapCspHash(productionImportMap(sharedModules()));
|
||||
const write = process.argv.includes("--write");
|
||||
const problems = [];
|
||||
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* Counts the import-boundary debt between core and plugins, and fails when it
|
||||
* grows.
|
||||
*
|
||||
* The eslint fence marks these directions as warnings because there is nowhere
|
||||
* for them to go yet (see packages/plugin-sdk/ARCHITECTURE.md). A warning does
|
||||
* not fail a build, so this script holds the line instead: every offender is
|
||||
* listed in plugin-boundary-allowlist.json, a new one fails, and an entry that
|
||||
* stopped being an offender fails too, so the list shrinks as B steps land
|
||||
* rather than rotting. D1 empties it and the eslint rules become errors.
|
||||
*/
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = path.resolve(__dirname, "..");
|
||||
const allowlistPath = path.join(
|
||||
root,
|
||||
"scripts",
|
||||
"plugin-boundary-allowlist.json",
|
||||
);
|
||||
const norm = (p) => p.split(path.sep).join("/");
|
||||
|
||||
const DIRECTIONS = {
|
||||
// The shell importing plugin code. Empty since A7, and kept at zero: any
|
||||
// entry here fails, allowlisted or not.
|
||||
"ui-to-plugin": {
|
||||
roots: ["src/ui", "src/main.tsx"],
|
||||
skip: (rel) => rel.includes("/tests/"),
|
||||
pattern: /["'][^"']*\/plugins\/[a-z0-9-]+\//,
|
||||
mustBeEmpty: true,
|
||||
},
|
||||
// A bundled plugin frontend importing the shell's own modules through the
|
||||
// "@/" alias, which the CLI turns into @termix/legacy-core/* for the import
|
||||
// map. D1 empties this.
|
||||
"plugin-frontend-to-core": {
|
||||
roots: ["plugins"],
|
||||
only: (rel) => /^plugins\/[a-z0-9-]+\/src\/frontend\//.test(rel),
|
||||
pattern: /(?:from|import)\s*\(?\s*["']@\//,
|
||||
},
|
||||
// A plugin backend reaching core by relative path. D1 empties this.
|
||||
"plugin-to-core": {
|
||||
roots: ["plugins"],
|
||||
only: (rel) => /^plugins\/[a-z0-9-]+\/src\//.test(rel),
|
||||
pattern: /["'](?:\.\.\/)+src\/(backend|types)\//,
|
||||
},
|
||||
// One plugin importing another's source. Empty, and stays that way.
|
||||
"plugin-to-plugin": {
|
||||
roots: ["plugins"],
|
||||
only: (rel) => /^plugins\/[a-z0-9-]+\/src\//.test(rel),
|
||||
pattern: /["'](?:\.\.\/)+(?!src\/)[a-z0-9-]+\/src\/(backend|frontend)\//,
|
||||
},
|
||||
};
|
||||
|
||||
function walk(dir, out = []) {
|
||||
if (!fs.existsSync(dir)) return out;
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
||||
walk(full, out);
|
||||
} else if (/\.(ts|tsx|mjs)$/.test(entry.name)) {
|
||||
out.push(full);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function filesUnder(base) {
|
||||
const full = path.join(root, base);
|
||||
if (fs.existsSync(full) && fs.statSync(full).isFile()) return [full];
|
||||
return walk(full);
|
||||
}
|
||||
|
||||
function offendersFor(name, config) {
|
||||
const found = new Set();
|
||||
for (const base of config.roots) {
|
||||
for (const file of filesUnder(base)) {
|
||||
const rel = norm(path.relative(root, file));
|
||||
if (config.only && !config.only(rel)) continue;
|
||||
if (config.skip && config.skip(rel)) continue;
|
||||
const source = fs.readFileSync(file, "utf8");
|
||||
if (config.pattern.test(source)) found.add(rel);
|
||||
}
|
||||
}
|
||||
return [...found].sort();
|
||||
}
|
||||
|
||||
const allowlist = fs.existsSync(allowlistPath)
|
||||
? JSON.parse(fs.readFileSync(allowlistPath, "utf8"))
|
||||
: {};
|
||||
|
||||
if (process.argv.includes("--write")) {
|
||||
const next = {};
|
||||
for (const [name, config] of Object.entries(DIRECTIONS)) {
|
||||
next[name] = offendersFor(name, config);
|
||||
}
|
||||
fs.writeFileSync(allowlistPath, JSON.stringify(next, null, 2) + "\n");
|
||||
console.log("Wrote the allowlist from the current tree.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let failed = false;
|
||||
|
||||
for (const [name, config] of Object.entries(DIRECTIONS)) {
|
||||
const offenders = offendersFor(name, config);
|
||||
const allowed = new Set(allowlist[name] ?? []);
|
||||
|
||||
const added = config.mustBeEmpty
|
||||
? offenders
|
||||
: offenders.filter((file) => !allowed.has(file));
|
||||
const stale = [...allowed].filter((file) => !offenders.includes(file));
|
||||
|
||||
if (added.length > 0) {
|
||||
failed = true;
|
||||
console.error(`\n${name}: ${added.length} new violation(s):`);
|
||||
for (const file of added) console.error(` ${file}`);
|
||||
}
|
||||
|
||||
if (stale.length > 0) {
|
||||
failed = true;
|
||||
console.error(
|
||||
`\n${name}: ${stale.length} allowlist entry(ies) no longer violate. Remove them:`,
|
||||
);
|
||||
for (const file of stale) console.error(` ${file}`);
|
||||
}
|
||||
|
||||
if (added.length === 0 && stale.length === 0) {
|
||||
console.log(`${name}: ${offenders.length} remaining (allowlisted)`);
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
console.error(
|
||||
"\nRun `node scripts/check-plugin-boundaries.cjs --write` if the change is intentional.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/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, such as a fixed
|
||||
* button next to Quick Connect.
|
||||
* 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();
|
||||
@@ -10,12 +10,6 @@
|
||||
*/
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
LEGACY_CORE_PREFIX,
|
||||
findLegacyCoreImports,
|
||||
} from "../../packages/plugin-sdk/cli/lib/legacy-core-specifier.mjs";
|
||||
|
||||
/**
|
||||
* Third-party modules that must be one instance. Keep in step with the CLI's
|
||||
@@ -42,59 +36,8 @@ export const SHARED_SDK_MODULES = [
|
||||
"@termix/plugin-sdk/ui",
|
||||
];
|
||||
|
||||
function walk(dir, out) {
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return out;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name !== "node_modules" && entry.name !== "dist") {
|
||||
walk(full, out);
|
||||
}
|
||||
} else if (/\.(tsx?|mjs|jsx?)$/.test(entry.name)) {
|
||||
out.push(full);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Legacy core modules imported by any bundled plugin's frontend. */
|
||||
export function scanLegacyCoreModules(repoRoot) {
|
||||
const found = new Set();
|
||||
const pluginsDir = path.join(repoRoot, "plugins");
|
||||
let plugins = [];
|
||||
try {
|
||||
plugins = fs.readdirSync(pluginsDir, { withFileTypes: true });
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
for (const plugin of plugins) {
|
||||
if (!plugin.isDirectory()) continue;
|
||||
const files = walk(
|
||||
path.join(pluginsDir, plugin.name, "src", "frontend"),
|
||||
[],
|
||||
);
|
||||
for (const file of files) {
|
||||
for (const specifier of findLegacyCoreImports(
|
||||
fs.readFileSync(file, "utf8"),
|
||||
)) {
|
||||
found.add(specifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...found].sort();
|
||||
}
|
||||
|
||||
export function sharedModules(repoRoot) {
|
||||
return [
|
||||
...SHARED_VENDOR_MODULES,
|
||||
...SHARED_SDK_MODULES,
|
||||
...scanLegacyCoreModules(repoRoot),
|
||||
];
|
||||
export function sharedModules() {
|
||||
return [...SHARED_VENDOR_MODULES, ...SHARED_SDK_MODULES];
|
||||
}
|
||||
|
||||
/** A file-name-safe name for a shared module's shim. */
|
||||
@@ -126,19 +69,3 @@ export function productionImportMap(specifiers) {
|
||||
export function importMapCspHash(text) {
|
||||
return `'sha256-${crypto.createHash("sha256").update(text, "utf8").digest("base64")}'`;
|
||||
}
|
||||
|
||||
/** Absolute source file behind a legacy core specifier, or null. */
|
||||
export function resolveLegacyCoreFile(repoRoot, specifier) {
|
||||
if (!specifier.startsWith(LEGACY_CORE_PREFIX)) return null;
|
||||
const rest = specifier.slice(LEGACY_CORE_PREFIX.length);
|
||||
const base = rest.startsWith("types")
|
||||
? path.join(repoRoot, "src", rest)
|
||||
: path.join(repoRoot, "src", "ui", rest.slice("ui/".length));
|
||||
const candidates = [
|
||||
`${base}.ts`,
|
||||
`${base}.tsx`,
|
||||
path.join(base, "index.ts"),
|
||||
path.join(base, "index.tsx"),
|
||||
];
|
||||
return candidates.find((file) => fs.existsSync(file)) ?? null;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
{
|
||||
"ui-to-plugin": [],
|
||||
"plugin-frontend-to-core": [
|
||||
"plugins/file-manager/src/frontend/FileManager.tsx",
|
||||
"plugins/file-manager/src/frontend/FileManagerContextMenu.tsx",
|
||||
"plugins/file-manager/src/frontend/FileManagerDialogs.tsx",
|
||||
"plugins/file-manager/src/frontend/FileManagerGrid.tsx",
|
||||
"plugins/file-manager/src/frontend/FileManagerSidebar.tsx",
|
||||
"plugins/file-manager/src/frontend/FileManagerToolbar.tsx",
|
||||
"plugins/file-manager/src/frontend/LocalFileContextMenu.tsx",
|
||||
"plugins/file-manager/src/frontend/LocalFilePane.tsx",
|
||||
"plugins/file-manager/src/frontend/SftpTransferTab.tsx",
|
||||
"plugins/file-manager/src/frontend/TransferMonitor.tsx",
|
||||
"plugins/file-manager/src/frontend/api/file-manager-data-api.ts",
|
||||
"plugins/file-manager/src/frontend/api/local-transfer-api.ts",
|
||||
"plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts",
|
||||
"plugins/file-manager/src/frontend/components/AudioPreview.tsx",
|
||||
"plugins/file-manager/src/frontend/components/ColumnResizeHandle.tsx",
|
||||
"plugins/file-manager/src/frontend/components/ColumnVisibilityMenu.tsx",
|
||||
"plugins/file-manager/src/frontend/components/DiffViewer.tsx",
|
||||
"plugins/file-manager/src/frontend/components/DiffWindow.tsx",
|
||||
"plugins/file-manager/src/frontend/components/DownloadProgressToast.tsx",
|
||||
"plugins/file-manager/src/frontend/components/DraggableWindow.tsx",
|
||||
"plugins/file-manager/src/frontend/components/FileViewer.tsx",
|
||||
"plugins/file-manager/src/frontend/components/FileWindow.tsx",
|
||||
"plugins/file-manager/src/frontend/components/PaneResizeHandle.tsx",
|
||||
"plugins/file-manager/src/frontend/components/TerminalWindow.tsx",
|
||||
"plugins/file-manager/src/frontend/components/TransferProgressBar.tsx",
|
||||
"plugins/file-manager/src/frontend/components/TransferProgressToast.tsx",
|
||||
"plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx",
|
||||
"plugins/file-manager/src/frontend/file-manager-types.ts",
|
||||
"plugins/file-manager/src/frontend/file-preview-preload.ts",
|
||||
"plugins/file-manager/src/frontend/homepage/FileManagerWidget.tsx",
|
||||
"plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts",
|
||||
"plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts",
|
||||
"plugins/file-manager/src/frontend/hooks/useLocalTransfers.ts",
|
||||
"plugins/file-manager/src/frontend/lib/file-content-request-cache.ts",
|
||||
"plugins/file-manager/src/frontend/lib/file-list-request-cache.ts",
|
||||
"plugins/file-manager/src/frontend/local-transfer-utils.ts",
|
||||
"plugins/file-manager/src/frontend/optimistic-file-list.ts",
|
||||
"plugins/file-manager/src/frontend/transferMetricsFormat.ts",
|
||||
"plugins/file-manager/src/frontend/transferProgressMonitor.tsx",
|
||||
"plugins/proxmox/src/frontend/HostProxmoxTab.tsx",
|
||||
"plugins/proxmox/src/frontend/ProxmoxDiscoverDialog.tsx",
|
||||
"plugins/proxmox/src/frontend/index.tsx",
|
||||
"plugins/tailscale/src/frontend/TailscaleCheckDialog.tsx",
|
||||
"plugins/tailscale/src/frontend/TailscaleDevicesPanel.tsx",
|
||||
"plugins/tailscale/src/frontend/TailscaleDevicesStatus.tsx",
|
||||
"plugins/tailscale/src/frontend/index.tsx",
|
||||
"plugins/tailscale/src/frontend/tailscale-api.ts",
|
||||
"plugins/web-endpoint/src/frontend/HostEditorWebUiSection.tsx",
|
||||
"plugins/web-endpoint/src/frontend/WebEndpointTab.tsx",
|
||||
"plugins/web-endpoint/src/frontend/index.tsx",
|
||||
"plugins/web-endpoint/src/frontend/web-endpoint-api.ts"
|
||||
],
|
||||
"plugin-to-core": [
|
||||
"plugins/file-manager/src/backend/index.ts",
|
||||
"plugins/file-manager/src/backend/operation-routes.ts",
|
||||
"plugins/tailscale/src/backend/routes.ts"
|
||||
],
|
||||
"plugin-to-plugin": []
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$comment": "Literals src/ui may still spell that name a plugin id or a plugin-owned view. Each is data, not knowledge of a plugin. Checked by scripts/check-shell-plugin-ids.cjs, which also fails when an entry stops being needed.",
|
||||
"files": {
|
||||
"src/ui/sidebar/QuickConnectPanel.tsx": {
|
||||
"literals": ["files"],
|
||||
"reason": "The quick-connect-for-an-unsaved-host flow offers a fixed Files button next to the default connect button, not an extensible list a plugin contributes to."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* Makes the shell a host for plugin bundles.
|
||||
*
|
||||
* - Every shared module (React, i18next, the SDK, and the legacy core modules
|
||||
* bundled plugins still import) becomes an entry chunk, reached through a
|
||||
* stable shim at dist/shared/<name>.js.
|
||||
* - Every shared module (React, i18next and the SDK) becomes an entry chunk,
|
||||
* reached through a stable shim at dist/shared/<name>.js.
|
||||
* - index.html gets an import map pointing those bare specifiers at the shims,
|
||||
* so a plugin bundle's `import "react"` lands on the shell's React.
|
||||
* - In `vite dev`, src/ui/plugin-host/workspace-plugins.ts is replaced with
|
||||
@@ -17,7 +16,6 @@ import { createRequire } from "node:module";
|
||||
import {
|
||||
importMapCspHash,
|
||||
productionImportMap,
|
||||
resolveLegacyCoreFile,
|
||||
sharedModules,
|
||||
shimName,
|
||||
shimPath,
|
||||
@@ -86,13 +84,7 @@ export function termixPluginHost({ repoRoot, sdkUiEntry, sdkFrontendEntry }) {
|
||||
if (specifier === "@termix/plugin-sdk/frontend") {
|
||||
return sourceFacade(sdkFrontendEntry);
|
||||
}
|
||||
const file = resolveLegacyCoreFile(repoRoot, specifier);
|
||||
if (!file) {
|
||||
throw new Error(
|
||||
`termix-plugin-host: a plugin imports ${specifier}, which does not exist in core`,
|
||||
);
|
||||
}
|
||||
return sourceFacade(file);
|
||||
throw new Error(`termix-plugin-host: ${specifier} is not a shared module`);
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -100,7 +92,7 @@ export function termixPluginHost({ repoRoot, sdkUiEntry, sdkFrontendEntry }) {
|
||||
|
||||
config(_config, env) {
|
||||
command = env.command;
|
||||
shared = sharedModules(repoRoot);
|
||||
shared = sharedModules();
|
||||
if (command !== "build") return;
|
||||
const input = { index: path.join(repoRoot, "index.html") };
|
||||
for (const specifier of shared) {
|
||||
|
||||
Reference in new issue
Block a user