mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
feat: convert serial console into the serial plugin
This commit is contained in:
1 parent
708eedb861
commit
791d8656a1
121 files changed
+2216
-1425
No files matched your search
@@ -1111,6 +1111,21 @@ export interface PluginAudit {
|
||||
}) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic capability check, for a privileged action that has no dedicated
|
||||
* ctx method to wrap it: a plugin bundling its own native dependency to touch
|
||||
* hardware (a serial port, a USB device) is the first caller. Core cannot
|
||||
* mediate that access the way it mediates ctx.ssh or ctx.db, so the
|
||||
* capability here is a declared, reviewable, audited statement of intent
|
||||
* rather than a technical gate, matching credentials:read and process:spawn.
|
||||
*/
|
||||
export interface PluginCapabilities {
|
||||
/** Whether this plugin currently holds `capability`. Not audited: for deciding whether to offer something, not for gating an action. */
|
||||
has: (capability: string) => Promise<boolean>;
|
||||
/** Throws PluginCapabilityError if this plugin does not hold `capability`. Audited like every guarded ctx method. */
|
||||
require: (capability: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface PluginContext {
|
||||
readonly pluginId: string;
|
||||
readonly manifest: PluginManifest;
|
||||
@@ -1127,6 +1142,8 @@ export interface PluginContext {
|
||||
readonly rbac: PluginRbac;
|
||||
readonly settings: PluginSettings;
|
||||
readonly disposables: PluginDisposables;
|
||||
/** A generic capability check, for privileged code no ctx method wraps. */
|
||||
readonly capabilities: PluginCapabilities;
|
||||
/** Hosts the actor can see, and host-sharing operations. Needs hosts:read / hosts:write. */
|
||||
readonly hosts: PluginHosts;
|
||||
/** SSH through core's connect pipeline. Needs ssh:connect and credentials:use. */
|
||||
|
||||
@@ -45,6 +45,7 @@ export const CAPABILITY_CATALOG: readonly CapabilityInfo[] = [
|
||||
entry("users:write", "high"),
|
||||
entry("auth:provide", "high"),
|
||||
entry("system:tls", "high"),
|
||||
entry("device:serial", "high"),
|
||||
|
||||
entry("hosts:write", "medium"),
|
||||
entry("credentials:use", "medium"),
|
||||
|
||||
@@ -232,6 +232,12 @@ export interface TabOptions {
|
||||
singleton?: boolean;
|
||||
/** A live session: asks before closing and can be refreshed. */
|
||||
session?: boolean;
|
||||
/**
|
||||
* Whether a session tab offers "Share session". Default true. A plugin
|
||||
* whose ref does not implement session sharing (a local device, not a
|
||||
* remote connection another user could join) sets this false.
|
||||
*/
|
||||
shareable?: boolean;
|
||||
/** Restorable without a host. */
|
||||
hostless?: boolean;
|
||||
/** Decides whether a saved tab for this host may be restored. */
|
||||
|
||||
@@ -298,6 +298,15 @@ export interface PluginManifest {
|
||||
/** Locales directory, relative to the plugin root. */
|
||||
locales?: string;
|
||||
platforms?: string[];
|
||||
/**
|
||||
* Bare npm package names carrying a native (.node) binding. The CLI build
|
||||
* never bundles them: they stay a real dependency in the plugin's own
|
||||
* package.json and are resolved from node_modules at runtime instead,
|
||||
* because esbuild bundling a native addon breaks the relative path it uses
|
||||
* to locate its compiled binary. See ARCHITECTURE.md under "native
|
||||
* dependencies".
|
||||
*/
|
||||
nativeDependencies?: string[];
|
||||
}
|
||||
|
||||
export const DEFAULT_BACKEND_ENTRY = "dist/backend.js";
|
||||
@@ -331,6 +340,7 @@ const ALLOWED_TOP_LEVEL = new Set([
|
||||
"frontend",
|
||||
"locales",
|
||||
"platforms",
|
||||
"nativeDependencies",
|
||||
]);
|
||||
|
||||
const ALLOWED_CONTRIBUTES = new Set([
|
||||
@@ -441,6 +451,7 @@ export function validateManifest(manifest: unknown): string[] {
|
||||
validateCategory(m.category, errors);
|
||||
validateCapabilities(m.capabilities, errors);
|
||||
validatePlatforms(m.platforms, errors);
|
||||
validateNativeDependencies(m.nativeDependencies, errors);
|
||||
validateDependencyMap(m.dependencies, "dependencies", errors);
|
||||
validateDependencyMap(m.optionalDependencies, "optionalDependencies", errors);
|
||||
validateProvides(m.provides, errors);
|
||||
@@ -535,6 +546,21 @@ function validatePlatforms(platforms: unknown, errors: string[]): void {
|
||||
});
|
||||
}
|
||||
|
||||
function validateNativeDependencies(value: unknown, errors: string[]): void {
|
||||
if (value === undefined) return;
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
errors.push(
|
||||
'Field "nativeDependencies" must be a non-empty array when present',
|
||||
);
|
||||
return;
|
||||
}
|
||||
value.forEach((entry, index) => {
|
||||
if (typeof entry !== "string" || entry.length === 0) {
|
||||
errors.push(`nativeDependencies[${index}] must be a non-empty string`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateDependencyMap(
|
||||
value: unknown,
|
||||
field: string,
|
||||
|
||||
@@ -406,6 +406,13 @@ export function createFakeContext(
|
||||
}) as PluginMiddleware,
|
||||
},
|
||||
|
||||
// Nothing here is capability-checked, per the module doc; createMockCtx
|
||||
// gates this against options.capabilities.
|
||||
capabilities: {
|
||||
has: async () => true,
|
||||
require: async () => {},
|
||||
},
|
||||
|
||||
settings: {
|
||||
get: async (key) =>
|
||||
settings.get(settingsKey("admin", undefined, key)) as never,
|
||||
@@ -936,6 +943,13 @@ export function createMockCtx(
|
||||
return ctx.desktop.openIsolatedWindow(request);
|
||||
},
|
||||
},
|
||||
|
||||
capabilities: {
|
||||
has: async (capability) => granted.has(capability),
|
||||
require: async (capability) => {
|
||||
require(capability);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { ...base, ctx: gatedCtx, checked };
|
||||
|
||||
Reference in new issue
Block a user