File size: 2,611 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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;
}