# `plan` ## Purpose Invokes `plugin.plan(answers, ctx)` and validates the returned `FileOp[]` against `FileOpsSchema` before letting them flow into apply. This is the quality gate that prevents a buggy plugin from feeding malformed ops into the host's transactional apply path. Both plugin-thrown errors and schema-validation failures surface as `INSTALLER.PLAN_FAILED` so callers have a single code to switch on. ## Features - Schema validation via `FileOpsSchema` — every op kind, path, contents type, and conflict policy is checked at the boundary. - Wrapped in OTel span `installer.plan` carrying `plugin.id`, `plugin.version`, and `dry_run`. - Distinguishes `plan` failures (plugin threw) from invalid-shape failures (plan returned bad ops) only via `messageKey` — both surface as the same code, which keeps consumer error handlers simple. - Original error chained via `cause` so the underlying Zod error or plugin exception is recoverable for logging. ## Basic Example ```ts import { runPlan } from "@suaveplan/installer"; const ops = await runPlan(myPlugin, answers, ctx); console.log(`plan returned ${ops.length} ops`); ``` ## Advanced Example ```ts import { InstallerError, runPlan } from "@suaveplan/installer"; try { const ops = await runPlan(myPlugin, answers, ctx); return ops; } catch (err) { if ( err instanceof InstallerError && err.code === "INSTALLER.PLAN_FAILED" ) { const issues = err.context?.["issues"]; if (issues) { console.error("Plugin returned invalid ops:", issues); } else { console.error("Plugin plan threw:", err.cause); } } throw err; } ``` ## API Reference - `runPlan(plugin, answers, ctx): Promise>` - Errors: `INSTALLER.PLAN_FAILED` with either: - `installer.plan.invalid_ops` and `context.issues` for schema failures - `installer.plan.threw` and `cause` set to the plugin's exception ## Implementation Notes Validation runs *after* the plugin returns, so a plugin can build ops incrementally without seeing intermediate validation errors. The schema also fixes the common mistake of returning a single op instead of an array — `FileOpsSchema` requires `Array` so that gets caught at the boundary instead of crashing later in apply. If the plugin already threw an `InstallerError`, we re-throw it directly rather than wrapping twice — this preserves `cause` chains all the way to the top.