2.7 KiB
detect
Purpose
Runs a plugin's optional detect stage inside an OpenTelemetry span. The
plugin reports what is already present in the target — existing skill
directories, conflicting files, configuration to migrate — so the host
can show that information to the user before prompts run, and so plugins
can tailor their plans accordingly. Detect is read-only by contract: it
inspects ctx.fs but never mutates state.
Features
- Wraps the plugin call in
installer.detectspan carryingplugin.id,plugin.version, anddry_runso traces correlate with apply. - No-op when the plugin omits
detect— returns{}without opening a span, so plugins that don't need detection pay zero observability overhead. - Surfaces plugin-thrown errors unchanged — the host's outer
try/catchis responsible for wrapping these inINSTALLER.PLAN_FAILEDif they occur post-detect, but detect-stage exceptions propagate raw so plugins can model their own detection-time error codes.
Basic Example
import { runDetect } from "@suaveplan/installer";
const result = await runDetect(myPlugin, ctx);
if (result.alreadyInstalled) {
ctx.cli.write("Plugin is already installed");
}
Advanced Example
import type { InstallerPlugin } from "@suaveplan/installer";
const plugin: InstallerPlugin<undefined, void> = {
id: "auditor",
version: "0.1.0",
detect: async (ctx) => {
const skillsDir = ctx.fs.pathOf(ctx.cwd, ".suaveplan/skills");
const exists = await ctx.fs.exists(skillsDir);
if (!exists) {
return {
alreadyInstalled: false,
notes: ["fresh target"],
};
}
const entries = await ctx.fs.listDir(skillsDir);
return {
alreadyInstalled: true,
conflicts: entries.filter((e) => e !== "index.json"),
notes: [`${entries.length} entries already present`],
};
},
plan: async () => [],
apply: async () => undefined,
};
API Reference
runDetect<TAnswers, THandle>(plugin, ctx): Promise<DetectResult>DetectResult—{ alreadyInstalled?: boolean, conflicts?: string[], notes?: string[] }. All fields optional.
Implementation Notes
The span is opened only when plugin.detect exists; this is a small
optimisation but it matters when the host is called in a tight loop
(e.g. list discovers many plugins). The default empty DetectResult
({}) ensures downstream stages never see undefined and lets the
alreadyInstalled boolean be used in CLI rendering without an extra
guard. Detect doesn't receive answers — those don't exist yet — so any
configuration-dependent detection must happen lazily during plan
instead.