first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
# `host`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - conflict — Resolves the per-op `conflict` policy on…
- [apply](./apply/apply.md) — <!-- begin:auto-docs --> _(no submodules)_- conflict — Resolves the per-op `conflict` policy on…
- [conflict](./apply/conflict.md) — Resolves the per-op `conflict` policy on a target that already exists. Five policies are supported: `error` (raise `APP…
- [json-merge](./apply/json-merge.md) — Implements the deep-merge semantics used by `patch-json` ops with `conflict: "merge"`. Match the conventions of `@suave…
- [text-patch](./apply/text-patch.md) — Applies an ordered sequence of `TextEdit`s to a string. Used by `patch-text` ops to make targeted insertions/deletions/…
- [detect](./detect.md) — Runs a plugin's optional `detect` stage inside an OpenTelemetry span. The plugin reports what is already present in the…
- [host](./host.md) — <!-- begin:auto-docs --> _(no submodules)_- conflict — Resolves the per-op `conflict` policy on…
- [plan](./plan.md) — Invokes `plugin.plan(answers, ctx)` and validates the returned `FileOp[]` against `FileOpsSchema` before letting them f…
- [post-install](./post-install.md) — Runs the plugin's optional `postInstall` stage with abort propagation. This is where plugins do follow-up work that doe…
- [prompts](./prompts.md) — Runs the plugin's optional `prompts` stage and validates the answers against the schema the plugin returns. This is whe…
- conflict — Resolves the per-op `conflict` policy on…
- [apply](./apply/apply.md) — <!-- begin:auto-docs --> _(no submodules)_- conflict — Resolves the per-op `conflict` policy on…
- [conflict](./apply/conflict.md) — Resolves the per-op `conflict` policy on a target that already exists. Five policies are supported: `error` (raise `APP…
- [json-merge](./apply/json-merge.md) — Implements the deep-merge semantics used by `patch-json` ops with `conflict: "merge"`. Match the conventions of `@suave…
- [text-patch](./apply/text-patch.md) — Applies an ordered sequence of `TextEdit`s to a string. Used by `patch-text` ops to make targeted insertions/deletions/…
- [detect](./detect.md) — Runs a plugin's optional `detect` stage inside an OpenTelemetry span. The plugin reports what is already present in the…
- [host](./host.md) — <!-- begin:auto-docs --> _(no submodules)_- conflict — Resolves the per-op `conflict` policy on…
- [plan](./plan.md) — Invokes `plugin.plan(answers, ctx)` and validates the returned `FileOp[]` against `FileOpsSchema` before letting them f…
- [post-install](./post-install.md) — Runs the plugin's optional `postInstall` stage with abort propagation. This is where plugins do follow-up work that doe…
- [prompts](./prompts.md) — Runs the plugin's optional `prompts` stage and validates the answers against the schema the plugin returns. This is whe…
- conflict — Resolves the per-op `conflict` policy on…
- [apply](./apply/apply.md) — <!-- begin:auto-docs --> _(no submodules)_- conflict — Resolves the per-op `conflict` policy on…
- [conflict](./apply/conflict.md) — Resolves the per-op `conflict` policy on a target that already exists. Five policies are supported: `error` (raise `APP…
- [json-merge](./apply/json-merge.md) — Implements the deep-merge semantics used by `patch-json` ops with `conflict: "merge"`. Match the conventions of `@suave…
- [text-patch](./apply/text-patch.md) — Applies an ordered sequence of `TextEdit`s to a string. Used by `patch-text` ops to make targeted insertions/deletions/…
- [detect](./detect.md) — Runs a plugin's optional `detect` stage inside an OpenTelemetry span. The plugin reports what is already present in the…
- [host](./host.md) — <!-- begin:auto-docs --> _(no submodules)_- conflict — Resolves the per-op `conflict` policy on…
- [plan](./plan.md) — Invokes `plugin.plan(answers, ctx)` and validates the returned `FileOp[]` against `FileOpsSchema` before letting them f…
- [post-install](./post-install.md) — Runs the plugin's optional `postInstall` stage with abort propagation. This is where plugins do follow-up work that doe…
- [prompts](./prompts.md) — Runs the plugin's optional `prompts` stage and validates the answers against the schema the plugin returns. This is whe…
- conflict — Resolves the per-op `conflict` policy on…
- [apply](./apply/apply.md) — <!-- begin:auto-docs --> _(no submodules)_- conflict — Resolves the per-op `conflict` policy on…
- [conflict](./apply/conflict.md) — Resolves the per-op `conflict` policy on a target that already exists. Five policies are supported: `error` (raise `APP…
- [json-merge](./apply/json-merge.md) — Implements the deep-merge semantics used by `patch-json` ops with `conflict: "merge"`. Match the conventions of `@suave…
- [text-patch](./apply/text-patch.md) — Applies an ordered sequence of `TextEdit`s to a string. Used by `patch-text` ops to make targeted insertions/deletions/…
- [detect](./detect.md) — Runs a plugin's optional `detect` stage inside an OpenTelemetry span. The plugin reports what is already present in the…
- [host](./host.md) — <!-- begin:auto-docs --> _(no submodules)_- apply — <!-- begin:auto-docs --> _(no submodules)_…
- [plan](./plan.md) — Invokes `plugin.plan(answers, ctx)` and validates the returned `FileOp[]` against `FileOpsSchema` before letting them f…
- [post-install](./post-install.md) — Runs the plugin's optional `postInstall` stage with abort propagation. This is where plugins do follow-up work that doe…
- [prompts](./prompts.md) — Runs the plugin's optional `prompts` stage and validates the answers against the schema the plugin returns. This is whe…
- [apply](./apply/apply.md) — <!-- begin:auto-docs --> _(no submodules)_- [conflict](./apply/conflict.md) — Resolves the per-op `conflict` policy on a target that already exists. Five policies are supported: `error` (raise `APP…
- [json-merge](./apply/json-merge.md) — Implements the deep-merge semantics used by `patch-json` ops with `conflict: "merge"`. Match the conventions of `@suave…
- [text-patch](./apply/text-patch.md) — Applies an ordered sequence of `TextEdit`s to a string. Used by `patch-text` ops to make targeted insertions/deletions/…
- [detect](./detect.md) — Runs a plugin's optional `detect` stage inside an OpenTelemetry span. The plugin reports what is already present in the…
- [host](./host.md) — <!-- begin:auto-docs --> _(no submodules)_- [plan](./plan.md) — Invokes `plugin.plan(answers, ctx)` and validates the returned `FileOp[]` against `FileOpsSchema` before letting them f…
- [post-install](./post-install.md) — Runs the plugin's optional `postInstall` stage with abort propagation. This is where plugins do follow-up work that doe…
- [prompts](./prompts.md) — Runs the plugin's optional `prompts` stage and validates the answers against the schema the plugin returns. This is whe…
## Purpose
The plugin host. `Host.run(plugin, options)` executes the strict pipeline
`detect → prompts → plan → apply → postInstall`, with abort propagation,
`--dry-run` short-circuiting, and `only`/`exclude` filtering. Each stage is
wrapped in an OpenTelemetry span (`installer.<stage>`) carrying
`plugin.id`, `plugin.version`, and `dry_run` attributes — that
instrumentation is what lets operators correlate apply failures with the
specific plugin and version that produced them.
## Features
- Strict ordering — optional stages (`detect`, `prompts`, `postInstall`)
are skipped without affecting the order of mandatory stages (`plan`,
`apply`).
- Abort checks at every stage boundary — if the signal aborts mid-run, the
host raises `INSTALLER.ABORTED` and apply rolls back any partial work.
- `--dry-run` — when set, the host returns the plan ops without invoking
apply or postInstall.
- `only`/`exclude` — substring filters applied to the op target path
(`op.to` for renames, `op.path` otherwise) before apply runs.
- `answersFromFile` channel — used by the bin's `--answers <file>` flag to
feed pre-recorded answers into prompt validation without going
interactive.
## Basic Example
```ts
import { Host, MemoryFs } from "@suaveplan/installer";
const fs = new MemoryFs();
const host = new Host(
{
cwd: "/",
fs,
cli: { confirm: async () => false, prompt: async () => "", write: () => undefined },
signal: new AbortController().signal,
logger: {
debug: () => undefined,
info: () => undefined,
warn: () => undefined,
error: () => undefined,
},
templates: { render: (s) => s },
dryRun: false,
force: false,
noInteractive: true,
},
fs
);
const result = await host.run(myPlugin);
console.log(result.summary);
```
## Advanced Example
```ts
import { Host } from "@suaveplan/installer";
const ac = new AbortController();
const host = new Host({ ...ctx, signal: ac.signal, dryRun: true }, fs);
setTimeout(() => ac.abort(), 5_000);
const dryResult = await host.run(myPlugin, {
only: ["crypto", "events"],
exclude: ["legacy"],
});
console.log(`would apply ${dryResult.ops.length} ops`);
```
## API Reference
- `class Host` — constructed with `(ctx: InstallerContext, fs: MutableInstallerFs)`.
- `Host.run<TAnswers, THandle>(plugin, options): Promise<HostRunResult<THandle>>`
- `interface HostRunOptions<TAnswers> extends RunOptions<TAnswers>` —
adds `answersFromFile?: unknown` for the bin.
- `interface HostRunResult<THandle>` — `{ detect, ops, summary, handle, dryRun }`.
## Implementation Notes
`filterOps` runs _after_ plan but _before_ apply, so the conformance
guarantee that "every emitted op respects its conflict policy on re-run"
applies to the post-filter set. `--dry-run` returns a synthetic summary
(`skipped = ops.length`) so dashboards see a coherent counter even on
hypothetical runs. Abort checks are placed at every awaitable boundary
(after detect, after prompts, before apply) so a long-running prompt
schema can be cancelled cleanly. `applyOps` itself owns the rollback
journal — the host's only responsibility post-apply is to wire up
`postInstall` with the resulting handle.