import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { DEFAULT_BACKEND_ENTRY, type PluginManifest, } from "@termix/plugin-sdk/manifest"; /** * Where user-installed plugins live. Resolved per call rather than captured at * module scope: tests set DATA_DIR in beforeEach, and a module-scope constant * would freeze whatever the first import saw. */ export function getPluginsDir(): string { return path.join(process.env.DATA_DIR || "./db/data", "plugins"); } /** * A plugin's own files (ctx.files.dataDir(), downloaded binaries). Kept out of * getPluginsDir(), where the loader would read every data folder as a user * plugin that clashes with the bundled one of the same name. */ export function getPluginDataDir(pluginId: string): string { return path.join( process.env.DATA_DIR || "./db/data", "plugin-data", pluginId, ); } /** * Where user .tmxplug files are unpacked. A dot directory inside the user * plugins dir, which the loader's directory scan skips. */ export function getUnpackedPluginsDir(): string { return path.join(getPluginsDir(), ".unpacked"); } /** * Where the plugins that ship with Termix live. * * Separate from getPluginsDir() because that one is user data: a bundled * plugin is part of the install and must not be deletable or shadowable by * whatever happens to be in the data directory. * * Resolved from this module's location rather than cwd, because the build * emits to dist/backend/backend/plugins/ and the bundled plugins sit beside * dist/backend. */ export function getBundledPluginsDir(): string { const override = process.env.TERMIX_BUNDLED_PLUGINS_DIR; if (override) return override; const here = path.dirname(fileURLToPath(import.meta.url)); const candidates = [ // Built server: dist/backend/backend/plugins -> dist/plugins // Source tree: src/backend/plugins -> plugins // Both are three levels up, which is why there is one entry, not two. path.resolve(here, "../../../plugins"), // Built server invoked from a nested layout: .../dist/backend/backend/plugins path.resolve(here, "../../../../plugins"), ]; for (const candidate of candidates) { if (fs.existsSync(candidate)) return candidate; } return candidates[0]; } /** The backend entry, from the manifest's own `backend` field. */ export function getPluginBackendEntry( pluginDir: string, manifest?: Pick, ): string { return path.join(pluginDir, manifest?.backend ?? DEFAULT_BACKEND_ENTRY); } export function getPluginManifestPath(pluginDir: string): string { return path.join(pluginDir, "manifest.json"); }