import { type HygieneCommonOptions } from "../shared.js"; export type SlugConflictsKeepRule = "oldest" | "newest" | "shortest-path" | "interactive"; export type SlugConflictsAction = "delete" | "rename"; export interface CleanupSlugConflictsOptions extends HygieneCommonOptions { root?: string; index?: string; limit?: number; includeSystem?: boolean; language?: string; pageParallelism?: number; exclude?: string[]; since?: string; owner?: string; caseInsensitive?: boolean; /** * Which member of each conflict group survives. Default `oldest` * (the survivor is whichever sibling was created first). `newest` * keeps the most-recently-updated sibling; `shortest-path` keeps the * one whose full path is shortest (rare tie-breaker — useful when * one sibling is at the canonical depth and others are buried). * `interactive` prompts per group; requires a TTY. */ keepRule?: SlugConflictsKeepRule; /** * What to do with the losers. `delete` (default) calls * `deleteItem(permanently: true)`. `rename` keeps the item but * renames it to `${name}${suffix}` where suffix is auto-generated * from the itemId (8-char prefix) so subsequent runs don't re-collide. * Rename preserves inbound refs (the itemId is unchanged) at the cost * of leaving stale-named siblings around the parent — operators * picking between the two should think about whether broken refs or * stale names are worse for their tenant. */ action?: SlugConflictsAction; /** * Override the auto-generated rename suffix. Use `{shortId}` as a * placeholder for the loser's 8-char itemId prefix, `{full}` for the * full 32-char id. Default: `-{shortId}`. Ignored when * `--action delete`. */ renameSuffix?: string; /** Concurrency for delete/rename calls. Default 4. */ concurrency?: number; whatIf?: boolean; allowWrite?: boolean; /** Required when `keepRule === "interactive"` and stdin isn't a TTY. */ nonInteractive?: boolean; /** * Pre-action inbound-reference check. When true, runs * `audit references --to ` against every loser BEFORE the * resolve phase. Behavior depends on mode: * * - **Preview (`whatIf: true`):** counts attach to each resolved * row as `inboundRefs`. Warn-only — never aborts. * - **Apply (`whatIf: false`):** if any loser has positive count, * the whole run aborts with INPUT_INVALID listing the blockers. * The implicit safety net for "you're about to break N inbound * refs." * * Cost: one `audit references` scan per loser. Uses `cache: true` so * the underlying field reads are shared across scans (large blast- * radius runs are cheaper than naive O(N) implies). Off by default * — operators who care opt in. */ checkRefs?: boolean; /** * Content root the inbound-ref check scans. Default `/sitecore` to * cover refs from anywhere on the tenant. Narrow if you know refs * to losing items can only come from a subtree (e.g. `/sitecore/content`). * Ignored when `checkRefs` is unset. */ refCheckRoot?: string; } export interface SlugConflictPurgeAction { parentPath: string; slug: string; kept: { itemId: string; path: string; }; resolved: Array<{ itemId: string; path: string; /** `delete` losers get `permanently: true`. `rename` losers get a new sibling name. */ action: SlugConflictsAction; /** Only set on `rename`. The new item name (not the full path). */ newName?: string; /** * Inbound-reference count from the optional `checkRefs` pre-scan. * Set when the caller passed `checkRefs: true`; omitted otherwise. * Surfaced in preview output so operators can see "I'm about to * break N refs." In apply mode a positive count aborts the run. */ inboundRefs?: number; status: "applied" | "what-if" | "failed"; error?: string; }>; } /** * Resolve sibling-name conflicts surfaced by `audit slug-conflicts`. * * Strategy: * 1. Run `audit slug-conflicts` to find groups of siblings sharing * a name (case-insensitive by default — match the audit). * 2. For each group, pick the survivor per `--keep-rule`. * 3. Resolve the losers via `--action`: * - `delete` — `deleteItem(permanently: true)` (default). * - `rename` — `renameItem({ name: oldName + suffix })`. The * suffix template defaults to `-{shortId}` so renamed items * stay traceable to their itemId. * * Notes: * - **Inbound refs.** Deletes turn refs into broken links — run * `audit broken-links list` after a delete pass. Renames preserve * inbound refs (the itemId doesn't change), but URLs depending on * the old slug break. * - The audit's report has no createdDate/updatedDate, so the * `oldest`/`newest` rules fall back to itemId-stable ordering. This * is deterministic across runs (so baselines stay valid) but isn't * literally creation-date sorted. Use `interactive` when the * decision matters per-item. * - With `--keep-rule interactive`, the command prompts per group. * Honors `s`/`skip` to leave a group untouched. Requires a TTY; * pass any other keep-rule when scripting. */ export declare const runCleanupSlugConflicts: (options: CleanupSlugConflictsOptions) => Promise;