| 84 | // The stages here intentionally separate install/target resolution, entrypoint detection, |
| 85 | // and compatibility checks so callers can report the exact reason a plugin was skipped. |
| 86 | export async function resolve( |
| 87 | plan: Plan, |
| 88 | kind: PluginKind, |
| 89 | ): Promise< |
| 90 | | { ok: true; value: Resolved } |
| 91 | | { ok: false; stage: "missing"; value: Missing } |
| 92 | | { ok: false; stage: "install" | "entry" | "compatibility"; error: unknown } |
| 93 | > { |
| 94 | // First make sure the plugin exists locally, installing npm plugins on demand. |
| 95 | let target = "" |
| 96 | try { |
| 97 | target = await resolvePluginTarget(plan.spec) |
| 98 | } catch (error) { |
| 99 | return { ok: false, stage: "install", error } |
| 100 | } |
| 101 | if (!target) return { ok: false, stage: "install", error: new Error(`Plugin ${plan.spec} target is empty`) } |
| 102 | |
| 103 | // Then inspect the target for the requested server/tui entrypoint. |
| 104 | let base |
| 105 | try { |
| 106 | base = await createPluginEntry(plan.spec, target, kind) |
| 107 | } catch (error) { |
| 108 | return { ok: false, stage: "entry", error } |
| 109 | } |
| 110 | if (!base.entry) |
| 111 | return { |
| 112 | ok: false, |
| 113 | stage: "missing", |
| 114 | value: { |
| 115 | ...plan, |
| 116 | source: base.source, |
| 117 | target: base.target, |
| 118 | pkg: base.pkg, |
| 119 | message: `Plugin ${plan.spec} does not expose a ${kind} entrypoint`, |
| 120 | }, |
| 121 | } |
| 122 | |
| 123 | // npm plugins can declare which opencode versions they support; file plugins are treated |
| 124 | // as local development code and skip this compatibility gate. |
| 125 | if (base.source === "npm") { |
| 126 | try { |
| 127 | await checkPluginCompatibility(base.target, InstallationVersion, base.pkg) |
| 128 | } catch (error) { |
| 129 | return { ok: false, stage: "compatibility", error } |
| 130 | } |
| 131 | } |
| 132 | return { ok: true, value: { ...plan, source: base.source, target: base.target, entry: base.entry, pkg: base.pkg } } |
| 133 | } |
| 134 | |
| 135 | // Import the resolved module only after all earlier validation has succeeded. |
| 136 | export async function load(row: Resolved): Promise<{ ok: true; value: Loaded } | { ok: false; error: unknown }> { |