mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-05-23 21:25:39 +00:00
Adds plugin discovery, loading, and management to the omniroute CLI. - bin/cli/plugins.mjs: discoverPlugins / loadPlugins / buildPluginContext - bin/cli/commands/plugin.mjs: list / install / remove / info / search / update / scaffold - examples/omniroute-cmd-hello/: minimal working plugin example - docs/dev/plugins.md: plugin API contract and authoring guide - .env.example + ENVIRONMENT.md: document OMNIROUTE_PLUGIN_PATH
90 lines
2.5 KiB
JavaScript
90 lines
2.5 KiB
JavaScript
import { readdirSync, existsSync, readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { homedir } from "node:os";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const PLUGIN_PREFIX_RE = /^(@[^/]+\/)?omniroute-cmd-/;
|
|
|
|
function getPluginDirs() {
|
|
return [join(homedir(), ".omniroute", "plugins"), process.env.OMNIROUTE_PLUGIN_PATH].filter(
|
|
Boolean
|
|
);
|
|
}
|
|
|
|
export async function discoverPlugins() {
|
|
const found = [];
|
|
for (const dir of getPluginDirs()) {
|
|
if (!existsSync(dir)) continue;
|
|
let entries;
|
|
try {
|
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
} catch {
|
|
continue;
|
|
}
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
const pkgPath = join(dir, entry.name, "package.json");
|
|
if (!existsSync(pkgPath)) continue;
|
|
let pkg;
|
|
try {
|
|
pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (!pkg.name || !PLUGIN_PREFIX_RE.test(pkg.name)) continue;
|
|
found.push({
|
|
name: pkg.name,
|
|
version: pkg.version || "0.0.0",
|
|
description: pkg.description || "",
|
|
dir: join(dir, entry.name),
|
|
pkg,
|
|
});
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
export function buildPluginContext(opts = {}) {
|
|
return {
|
|
apiFetch: async (...args) => {
|
|
const { apiFetch } = await import("./api.mjs");
|
|
return apiFetch(...args);
|
|
},
|
|
emit: async (...args) => {
|
|
const { emit } = await import("./output.mjs");
|
|
return emit(...args);
|
|
},
|
|
t: async (...args) => {
|
|
const { t } = await import("./i18n.mjs");
|
|
return t(...args);
|
|
},
|
|
withSpinner: async (...args) => {
|
|
const { withSpinner } = await import("./spinner.mjs");
|
|
return withSpinner(...args);
|
|
},
|
|
baseUrl: opts.baseUrl,
|
|
apiKey: opts.apiKey,
|
|
};
|
|
}
|
|
|
|
export async function loadPlugins(program, ctx = {}) {
|
|
const plugins = await discoverPlugins();
|
|
for (const p of plugins) {
|
|
try {
|
|
const entryPath = join(p.dir, p.pkg.main || "index.mjs");
|
|
if (!existsSync(entryPath)) {
|
|
process.stderr.write(`[plugin] ${p.name}: entry file not found (${entryPath})\n`);
|
|
continue;
|
|
}
|
|
const mod = await import(pathToFileURL(entryPath).href);
|
|
if (typeof mod.register === "function") {
|
|
mod.register(program, buildPluginContext(ctx));
|
|
} else {
|
|
process.stderr.write(`[plugin] ${p.name}: no register() export — skipping\n`);
|
|
}
|
|
} catch (err) {
|
|
process.stderr.write(`[plugin] Failed to load ${p.name}: ${err.message}\n`);
|
|
}
|
|
}
|
|
return plugins.length;
|
|
}
|