import { type RecipeTenantOptions } from "./shared.js"; import { type FieldMergeStatus, type RecipeMergeStatus } from "./pull-merge.js"; export { MergePlanFieldSchema, MergePlanRecipeSchema, MergePlanSchema } from "./pull-merge-plan.js"; export type { MergePlan, MergePlanField, MergePlanRecipe } from "./pull-merge-plan.js"; export { loadMergePlan, composeMergePlan } from "./pull-merge-plan.js"; export { classifyMergeStatus, perFieldStatuses, mergeContentValueRecipe, mergeTemplateRecipe, } from "./pull-merge.js"; export type { RecipeMergeStatus, FieldMergeStatus, PerFieldStatuses } from "./pull-merge.js"; export { detectStalePlanDrift, assertWithinDir } from "./pull-drift.js"; /** * `scai provision recipe pull` — read tenant state and serialise every * reverse-projectable recipe to disk as `.recipe.json`. * * Two modes: * * ## Snapshot mode (default) * * //.recipe.json * * Read-only against authored sources. Pull overwrites whatever's in * `` but never touches the authored recipes directory. Useful * for backup / inspection / generating starter content. * * ## Merge mode (`--against `) * * Reads the authored recipes from ``, compiles both sides * (disk + tenant projection) through `compileRecipeSet`, hashes per * field via `collectBaselineEntries`, and classifies each recipe: * * in-sync disk + tenant agree (and match baseline if loaded) * disk-ahead disk has changes the tenant hasn't seen yet → push * tenant-edited author edited tenant in the CMS since last push * conflict both sides moved since baseline → operator picks * disk-only recipe on disk, absent on tenant (deleted or never pushed) * tenant-only recipe on tenant, absent on disk (authored in CMS only) * * `--policy` governs the CLI exit code + per-recipe write: * error (default) — exit non-zero if any `tenant-edited` or `conflict` * disk-wins — skip writes for recipes where the disk has changes; * pull only `in-sync` / `tenant-only` / `tenant-edited` * tenant-wins — always write tenant projection (operator accepts * adoption — still to outDir, never overwrites the * authored .recipe.ts source) * * The 10 reverse-projectable kinds (component-section, component-template, * content-template, page-template, enumeration, partial-design, * page-design, page, placeholder, content-item) round-trip; see * `read-current.ts` for the per-kind fidelity notes. */ export interface RecipePullOptions extends RecipeTenantOptions { /** Output directory for serialised tenant recipes. Defaults to `./pulled-recipes`. */ output?: string; /** * Merge mode: path to the authored recipes directory (typically * `./recipes` or the glob resolved by `sitecoreai.cli.json`). When * unset (default), pull runs in snapshot mode — overwrites `` * with no merge detection. When set, pull compares the tenant * projection against the disk recipes + baseline and classifies each * recipe per `RecipeMergeStatus`. * * Use `--against .` to pick up the default `recipes` glob from * `sitecoreai.cli.json` (resolveRecipeInputs reads the config). */ against?: string; /** * Merge-mode conflict policy (only used when `--against` is set): * - `"error"` (default) — exit non-zero on tenant-edited / conflict * - `"disk-wins"` — skip writes for recipes with disk changes * - `"tenant-wins"` — write every tenant projection regardless * * Mirrors push's `--conflict-policy` semantics, direction-inverted. */ conflictPolicy?: "error" | "disk-wins" | "tenant-wins"; /** * Skip three-way merge baseline loading. Without a baseline the * planner falls back to two-way diff (any divergence reads as * `conflict` since we can't tell who moved). Mirrors push's * `--no-baseline`. */ noBaseline?: boolean; /** * Path to write a merge-plan JSON file. Plan is a hand-editable * snapshot of every per-recipe per-field classification + the * pre-filled winner pick per the current `--conflict-policy`. * Operator edits the `winner` entries then re-runs pull with * `--apply-plan ` to commit their picks. Implies merge * mode (`--against` must be set). Doesn't write the synthesised * recipes — that's `--apply-plan`'s job. */ writePlan?: string; /** * Path to read a merge-plan JSON file from. Pull rebuilds * classifications fresh, verifies each plan entry's status still * matches the current tenant + disk state, then synthesises merged * recipes using the plan's `winner` picks per field. Refuses to apply * if the env doesn't match, any plan entry's classification has * drifted, or the plan references recipes the current set doesn't * have — the operator must regenerate. */ applyPlan?: string; /** * Test-only injection for the plan's `generatedAt` timestamp. When * unset, the writer stamps `new Date().toISOString()`. Tests pin a * value so the plan output is byte-stable. */ now?: string; /** * Dry-run: classify + report what WOULD be written, but skip every * file write (recipe JSON files, merge plan, baseline reads still * happen). Useful in CI for verifying tenant + disk are in sync * without leaving any artifacts on the runner FS. Result still * carries `files` entries with `path: null` for entries that would * have been written. */ dryRun?: boolean; /** Override `templatesRoot` from the env profile. */ templatesRoot?: string; /** Override `renderingsRoot` from the env profile. */ renderingsRoot?: string; /** Override `componentsRoot` from the env profile. */ componentsRoot?: string; /** Override `contentModelsRoot` from the env profile. */ contentModelsRoot?: string; /** Override `pageTemplatesRoot` from the env profile. */ pageTemplatesRoot?: string; /** Override `partialDesignsRoot` from the env profile. */ partialDesignsRoot?: string; /** Override `pageDesignsRoot` from the env profile. */ pageDesignsRoot?: string; /** Override `pagesRoot` from the env profile. */ pagesRoot?: string; /** Override `enumerationsRoot` from the env profile. */ enumerationsRoot?: string; /** Override `placeholderSettingsRoot` from the env profile. */ placeholderSettingsRoot?: string; /** Override `contentItemsRoot` from the env profile. */ contentItemsRoot?: string; } /** Per-recipe outcome — file written (if any) + merge status. */ export interface RecipePullEntry { handle: string; kind: string; /** Filesystem path of the written `.recipe.json`. `null` when skipped. */ path: string | null; /** Merge classification. `"in-sync"` is also used in snapshot mode for every recipe. */ status: RecipeMergeStatus; /** * Number of fields whose hash changed on each side relative to the * baseline. Only populated in merge mode; both `0` outside it. */ diskChangedFields?: number; tenantChangedFields?: number; /** * Per-field classifications keyed by humanised label * (`fieldName (lang, v#)` or `fieldName` for shared fields). Surfaced * so JSON consumers + verbose human output can inspect WHICH fields * moved on which side. Only populated in merge mode. */ fieldStatuses?: Array<{ key: string; status: FieldMergeStatus; }>; } export interface RecipePullResult { outputDir: string; totalRecipes: number; byKind: Record; byStatus: Record; files: RecipePullEntry[]; /** * True when the operator passed `--against` AND `--policy=error` AND * any recipe classified as `tenant-edited` or `conflict`. The CLI * layer surfaces this as exit non-zero. */ blocked: boolean; } export declare const runRecipePull: (options: RecipePullOptions) => Promise;