Files
Termix/src/backend/plugins/bundled-index.ts
T
a89ccc6b07 v26.10.3 (#1692)
* feat: shell controller, TabStage, layout and theme plugins with repair view

* feat: move the classic layout into its own bundled plugin

* feat: move color themes into a bundled themes plugin

* fix: plugin tests and type checks work with a linked sdk

* fix: home tab, layout switch and missing layout fallbacks for looks

* chore: add classic layout and themes to the readme

* chore: drop example plugin links from the plugin guide

* feat: plugin filters by type, paged browse, hide plugins needing newer termix

* feat: move desktop, status, import, audit and branding features into plugins

* feat: host lock api so a plugin can load hosts from a config file (#1581)

* chore: add hosts config to the plugin table

* feat: login policy hook for plugins and optional https redirect (#1584)

* chore: add login security to the plugin table

* feat: api key socket tickets so a master can open remote sessions (#1654)

* feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654)

* chore: add sync to the plugin table

* feat: move the sync screens and wizard out of core into the plugin (#1580)

* fix: add a neutral linked desktop hook for plugins (#1580)

* fix: show the linked account's name and admin flag in the app (#1580)

* feat: ssh transport providers, core ssh relay and plugin folders api

* feat: support FIDO2 security keys through ssh agent

* feat: add companion panels beside host tabs

* feat: mTLS client certificates for desktop server links

* feat: native web view for plugins in the desktop app

* fix: detect the desktop link window by Electron's default user agent

Fixes #1675

* fix: changelog for desktop link fix

* fix: keep text primary keys in the plugin data export

* fix: ship sharp binaries for Windows arm64 builds

* feat: client cert on the sync link types (sdk 1.0.18)

* chore: update translations

* feat: themed title bar for the desktop app (#1690)

* chore: changelog for the themed title bar

* fix: draw the title bar in the sidebar color

* feat: per host layout for the terminal panel bar

* chore: sdk 1.0.19 for the panel bar layout

* chore: bundle all 43 plugins for 26.10.3

* chore: update translations

---------

Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com>
Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
2026-10-20 16:03:29 -05:00

122 lines
3.5 KiB
TypeScript

/**
* The onboarding defaults and the default look of the bundled plugins, from
* dist/plugins/bundled-index.json (written by scripts/build-plugins.cjs from
* docker/bundled-plugins.json). Core never names plugins, so which ones a
* fresh install keeps lives in that file and not here.
*/
import fs from "node:fs";
import path from "node:path";
import { getBundledPluginsDir } from "./paths.js";
export const BUNDLED_INDEX_FILE = "bundled-index.json";
export interface OnboardingDefaults {
recommended: boolean;
consent: boolean;
}
type Recommended = boolean | "desktop";
interface BundledIndex {
plugins: Record<
string,
{
onboarding?: {
recommended?: Recommended;
consent?: boolean;
enabledByEnv?: string[];
};
defaultLook?: boolean;
}
>;
}
let cached: BundledIndex | null | undefined;
export function readBundledIndex(): BundledIndex | null {
if (cached !== undefined) return cached;
try {
const raw = JSON.parse(
fs.readFileSync(
path.join(getBundledPluginsDir(), BUNDLED_INDEX_FILE),
"utf8",
),
);
cached =
raw && typeof raw.plugins === "object" && raw.plugins
? { plugins: raw.plugins }
: null;
} catch {
cached = null;
}
return cached;
}
/**
* Bundled plugins whose layout and theme a fresh install shows. Core reads
* the default look from here so it never has to name a plugin.
*/
export function defaultLookPluginIds(): string[] {
const index = readBundledIndex();
if (!index) return [];
return Object.entries(index.plugins)
.filter(([, entry]) => entry?.defaultLook === true)
.map(([id]) => id)
.sort();
}
export function isDefaultLookPlugin(pluginId: string): boolean {
return defaultLookPluginIds().includes(pluginId);
}
export function resetBundledIndexCache(): void {
cached = undefined;
}
function isDesktop(): boolean {
return process.env.ELECTRON_EMBEDDED === "true";
}
/**
* Without an index (a build that skipped build-plugins) every plugin counts
* as recommended, which is how installs behaved before the picker. A plugin
* whose enabledByEnv variables are set counts as recommended too, so a server
* set up for it through the environment works from the first boot.
*/
export function onboardingDefaults(pluginId: string): OnboardingDefaults {
const index = readBundledIndex();
if (!index) return { recommended: true, consent: false };
const entry = index.plugins[pluginId]?.onboarding;
const recommended = entry?.recommended;
const configured = (entry?.enabledByEnv ?? []).some(
(name) => typeof name === "string" && !!process.env[name]?.trim(),
);
return {
recommended:
recommended === true ||
(recommended === "desktop" && isDesktop()) ||
configured,
consent: entry?.consent === true,
};
}
/**
* The state a newly seen plugin's row starts in. Bundled plugins start
* enabled, except on a fresh install, where only recommended ones do and
* consent plugins wait for the admin. A plugin dropped into the data
* directory always starts disabled.
*/
export function initialPluginState(
pluginId: string,
source: "bundled" | "user",
freshInstall: boolean,
): "enabled" | "disabled" {
if (source !== "bundled") return "disabled";
if (!freshInstall) return "enabled";
// The default look is what the app is drawn with.
if (isDefaultLookPlugin(pluginId)) return "enabled";
const { recommended, consent } = onboardingDefaults(pluginId);
return recommended && !consent ? "enabled" : "disabled";
}