79 lines
2.7 KiB
Markdown
79 lines
2.7 KiB
Markdown
# `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.detect` span carrying `plugin.id`,
|
|
`plugin.version`, and `dry_run` so 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/catch`
|
|
is responsible for wrapping these in `INSTALLER.PLAN_FAILED` if they
|
|
occur post-detect, but detect-stage exceptions propagate raw so plugins
|
|
can model their own detection-time error codes.
|
|
|
|
## Basic Example
|
|
|
|
```ts
|
|
import { runDetect } from "@suaveplan/installer";
|
|
|
|
const result = await runDetect(myPlugin, ctx);
|
|
if (result.alreadyInstalled) {
|
|
ctx.cli.write("Plugin is already installed");
|
|
}
|
|
```
|
|
|
|
## Advanced Example
|
|
|
|
```ts
|
|
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.
|