mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 15:15:24 +00:00
feat: redesign user host defaults system
This commit is contained in:
1 parent
3028ab1176
commit
a4a4d4f27b
144 files changed
+24609
-2555
No files matched your search
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Every key a host can inherit: core's own catalog plus each running
|
||||
* plugin's host fields, in one shape the resolver and materializer share.
|
||||
*/
|
||||
|
||||
import type {
|
||||
PluginManifest,
|
||||
PluginSettingsField,
|
||||
} from "@termix/plugin-sdk/manifest";
|
||||
import { coerceSettingValue } from "@termix/plugin-sdk/settings";
|
||||
import {
|
||||
CORE_HOST_DEFAULTS,
|
||||
CORE_NAMESPACE,
|
||||
HOST_DEFAULTS_LEVELS,
|
||||
joinDefaultKey,
|
||||
type HostDefaultsLevel,
|
||||
} from "../../../types/host-defaults.js";
|
||||
import { declaredFields } from "../../plugins/settings.js";
|
||||
|
||||
export interface DefaultKeyInfo {
|
||||
namespace: string;
|
||||
key: string;
|
||||
fullKey: string;
|
||||
levels: readonly HostDefaultsLevel[];
|
||||
/** Undefined when nothing is inherited unless some level sets it. */
|
||||
builtin: unknown;
|
||||
normalize: (value: unknown) => unknown;
|
||||
personal: boolean;
|
||||
/** Plugin keys only. */
|
||||
field?: PluginSettingsField;
|
||||
}
|
||||
|
||||
export type DefaultsCatalog = Map<string, DefaultKeyInfo>;
|
||||
|
||||
/** Whether a plugin's host field can have a default. */
|
||||
export function isDefaultableField(field: PluginSettingsField): boolean {
|
||||
if (field.type === "secret") return false;
|
||||
if (field.type === "json" && field.secretKeys?.length) return false;
|
||||
return field.defaultable !== false;
|
||||
}
|
||||
|
||||
/** A plugin's host fields that can have a default, enable switch included. */
|
||||
export function defaultableHostFields(
|
||||
manifest: PluginManifest,
|
||||
): PluginSettingsField[] {
|
||||
return declaredFields(manifest, "host").filter(isDefaultableField);
|
||||
}
|
||||
|
||||
function isUnset(value: unknown): boolean {
|
||||
return value === undefined || value === null || value === "";
|
||||
}
|
||||
|
||||
export function normalizePluginValue(
|
||||
field: PluginSettingsField,
|
||||
value: unknown,
|
||||
): unknown {
|
||||
// A row with no value reads as the manifest default, so compare that way.
|
||||
if (isUnset(value)) return field.default ?? null;
|
||||
try {
|
||||
const coerced = coerceSettingValue(field, value);
|
||||
return isUnset(coerced) ? (field.default ?? null) : coerced;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export function coreCatalogEntries(): DefaultKeyInfo[] {
|
||||
return CORE_HOST_DEFAULTS.map((entry) => ({
|
||||
namespace: CORE_NAMESPACE,
|
||||
key: entry.key,
|
||||
fullKey: joinDefaultKey(CORE_NAMESPACE, entry.key),
|
||||
levels: entry.levels,
|
||||
builtin: entry.builtin,
|
||||
normalize: entry.normalize,
|
||||
personal: false,
|
||||
}));
|
||||
}
|
||||
|
||||
export function pluginCatalogEntries(
|
||||
manifest: PluginManifest,
|
||||
): DefaultKeyInfo[] {
|
||||
return defaultableHostFields(manifest).map((field) => ({
|
||||
namespace: manifest.id,
|
||||
key: field.key,
|
||||
fullKey: joinDefaultKey(manifest.id, field.key),
|
||||
levels: field.defaultLevels?.length
|
||||
? field.defaultLevels
|
||||
: HOST_DEFAULTS_LEVELS,
|
||||
builtin: normalizePluginValue(field, field.default),
|
||||
normalize: (value: unknown) => normalizePluginValue(field, value),
|
||||
personal: field.personal === true,
|
||||
field,
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildCatalog(manifests: PluginManifest[]): DefaultsCatalog {
|
||||
const catalog: DefaultsCatalog = new Map();
|
||||
for (const entry of coreCatalogEntries()) catalog.set(entry.fullKey, entry);
|
||||
for (const manifest of manifests) {
|
||||
for (const entry of pluginCatalogEntries(manifest)) {
|
||||
catalog.set(entry.fullKey, entry);
|
||||
}
|
||||
}
|
||||
return catalog;
|
||||
}
|
||||
|
||||
/** Namespaces the catalog covers: core plus each plugin with a defaultable field. */
|
||||
export function catalogNamespaces(catalog: DefaultsCatalog): string[] {
|
||||
return [...new Set([...catalog.values()].map((entry) => entry.namespace))];
|
||||
}
|
||||
Reference in new issue
Block a user