Files
suaveplan/packages/devtools/tooling/installer/src/host/plan.md
2026-05-27 23:29:53 +01:00

2.4 KiB

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

import { runPlan } from "@suaveplan/installer";

const ops = await runPlan(myPlugin, answers, ctx);
console.log(`plan returned ${ops.length} ops`);

Advanced Example

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<TAnswers, THandle>(plugin, answers, ctx): Promise<ReadonlyArray<FileOp>>
  • 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<FileOp> 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.