@prisma-next/cli 0.5.0-dev.3 → 0.5.0-dev.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -21
- package/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
- package/dist/cli.mjs +131 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-TG7rbCWT.mjs → client-keSCAgjW.mjs} +43 -19
- package/dist/client-keSCAgjW.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +7 -2
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +8 -2
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +11 -9
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +8 -5
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.mjs +8 -7
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +10 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +10 -9
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/commands/migration-apply.d.mts +2 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +15 -38
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +24 -30
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +45 -47
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +6 -4
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +31 -40
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +28 -29
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +5 -4
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +7 -2
- package/dist/{config-loader-_W4T21X1.mjs → config-loader-ih8ViDb_.mjs} +2 -2
- package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-DS5NzZh2.mjs +6 -0
- package/dist/contract-emit-DWtGQYCD.mjs +150 -0
- package/dist/contract-emit-DWtGQYCD.mjs.map +1 -0
- package/dist/contract-emit-RZBWzkop.mjs +329 -0
- package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
- package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
- package/dist/{contract-infer-BP3DrGgz.mjs → contract-infer-GztVCOCJ.mjs} +11 -19
- package/dist/contract-infer-GztVCOCJ.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +78 -21
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +7 -5
- package/dist/exports/index.mjs +8 -3
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +39 -0
- package/dist/exports/init-output.d.mts.map +1 -0
- package/dist/exports/init-output.mjs +3 -0
- package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
- package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
- package/dist/init-DAbQMxIR.mjs +2062 -0
- package/dist/init-DAbQMxIR.mjs.map +1 -0
- package/dist/{inspect-live-schema-DWzf4Q_m.mjs → inspect-live-schema-BaR9ISwa.mjs} +9 -9
- package/dist/inspect-live-schema-BaR9ISwa.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -11
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +308 -84
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-CLMD302g.mjs → migration-command-scaffold-D1dWuEWQ.mjs} +7 -7
- package/dist/{migration-command-scaffold-CLMD302g.mjs.map → migration-command-scaffold-D1dWuEWQ.mjs.map} +1 -1
- package/dist/{migration-status-B0HLF7So.mjs → migration-status-CP5k8O5i.mjs} +21 -35
- package/dist/migration-status-CP5k8O5i.mjs.map +1 -0
- package/dist/{migrations-B0dOQlk0.mjs → migrations-MEoKMiV5.mjs} +42 -21
- package/dist/migrations-MEoKMiV5.mjs.map +1 -0
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-CIyu0Pdt.mjs → result-handler-BmVh8AeV.mjs} +12 -93
- package/dist/result-handler-BmVh8AeV.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
- package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
- package/dist/{verify-BxiVp50b.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-BxiVp50b.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +21 -15
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +1 -0
- package/src/commands/db-update.ts +1 -1
- package/src/commands/init/detect-pnpm-catalog.ts +141 -0
- package/src/commands/init/errors.ts +254 -0
- package/src/commands/init/exit-codes.ts +62 -0
- package/src/commands/init/hygiene-gitattributes.ts +97 -0
- package/src/commands/init/hygiene-gitignore.ts +48 -0
- package/src/commands/init/hygiene-package-scripts.ts +91 -0
- package/src/commands/init/index.ts +112 -7
- package/src/commands/init/init.ts +766 -144
- package/src/commands/init/inputs.ts +421 -0
- package/src/commands/init/output.ts +147 -0
- package/src/commands/init/probe-db.ts +308 -0
- package/src/commands/init/reinit-cleanup.ts +83 -0
- package/src/commands/init/templates/agent-skill-mongo.md +63 -31
- package/src/commands/init/templates/agent-skill-postgres.md +1 -1
- package/src/commands/init/templates/agent-skill.ts +25 -3
- package/src/commands/init/templates/code-templates.ts +125 -32
- package/src/commands/init/templates/env.ts +80 -0
- package/src/commands/init/templates/quick-reference-mongo.md +34 -13
- package/src/commands/init/templates/quick-reference-postgres.md +11 -9
- package/src/commands/init/templates/quick-reference.ts +42 -3
- package/src/commands/init/templates/tsconfig.ts +167 -5
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +16 -51
- package/src/commands/migration-new.ts +26 -32
- package/src/commands/migration-plan.ts +80 -55
- package/src/commands/migration-ref.ts +40 -54
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +33 -50
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +21 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-init.ts +8 -5
- package/src/control-api/operations/db-update.ts +8 -5
- package/src/control-api/operations/migration-apply.ts +29 -9
- package/src/control-api/types.ts +61 -7
- package/src/exports/control-api.ts +2 -1
- package/src/exports/init-output.ts +10 -0
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/command-helpers.ts +13 -26
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +2 -2
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-DHq6GQGu.mjs +0 -5
- package/dist/client-TG7rbCWT.mjs.map +0 -1
- package/dist/config-loader-_W4T21X1.mjs.map +0 -1
- package/dist/contract-emit-CNYyzJwF.mjs +0 -195
- package/dist/contract-emit-CNYyzJwF.mjs.map +0 -1
- package/dist/contract-emit-CQfj7xJn.mjs +0 -122
- package/dist/contract-emit-CQfj7xJn.mjs.map +0 -1
- package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
- package/dist/contract-infer-BP3DrGgz.mjs.map +0 -1
- package/dist/extract-operation-statements-DZUJNmL3.mjs +0 -13
- package/dist/extract-operation-statements-DZUJNmL3.mjs.map +0 -1
- package/dist/extract-sql-ddl-DDMX-9mz.mjs +0 -26
- package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +0 -1
- package/dist/init-CQfo_4Ro.mjs +0 -430
- package/dist/init-CQfo_4Ro.mjs.map +0 -1
- package/dist/inspect-live-schema-DWzf4Q_m.mjs.map +0 -1
- package/dist/migration-status-B0HLF7So.mjs.map +0 -1
- package/dist/migrations-B0dOQlk0.mjs.map +0 -1
- package/dist/result-handler-CIyu0Pdt.mjs.map +0 -1
- package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
- package/dist/validate-contract-deps-esa-VQ0h.mjs +0 -37
- package/dist/validate-contract-deps-esa-VQ0h.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"migration-status-B0HLF7So.mjs","names":["STATUS_ICON: Record<EdgeStatusKind, string>","nodeList: GraphNode[]","markers: NodeMarker[]","edgeList: GraphEdge[]","relevantPaths: string[][]","spineTargetHash: string","contractMarkers: NodeMarker[]","REF_COLORS: ColorFn[]","BOX_CHAR: Record<number, string>","tags: InlineTag[]","rows: string[]","runColor: ColorFn | undefined","ch: string","color: ColorFn | undefined","deduped: Point[]","bends: BendDirection[]","result: Point[]","final: Point[]","best: (Point & { score: number }) | undefined","segments: Segment[]","candidates: Point[]","bestPoly: Point[] | null","bestLabel: Point | undefined","edgeNames: string[]","edgeEntries: EdgeEntry[]","dagrePoints: Point[]","role: EdgeEntry['role']","drawnEdges: DrawnEdge[]","ax: number | undefined","ay: number | undefined","arrow: string | undefined","path: string[]","graphRenderer: GraphRenderer","statuses: EdgeStatus[]","entries: MigrationStatusEntry[]","status: EdgeStatusKind | 'unknown'","activeRefName: string | undefined","activeRefHash: string | undefined","allRefs: Refs","statusRefs: StatusRef[]","details: Array<{ label: string; value: string }>","diagnostics: StatusDiagnostic[]","contractHash: string","bundles: readonly MigrationBundle[]","graph: MigrationGraph","targetHash: string | undefined","markerHash: string | undefined","mode: 'online' | 'offline'","hints: string[]","summary: string","pathDecision: MigrationStatusResult['pathDecision']","lines: string[]"],"sources":["../src/utils/formatters/graph-types.ts","../src/utils/formatters/graph-migration-mapper.ts","../src/utils/formatters/graph-render.ts","../src/commands/migration-status.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Generic graph renderer types — domain-agnostic\n// ---------------------------------------------------------------------------\n\n/** A typed marker attached to a node. Different marker kinds get different visual treatment. */\nexport type NodeMarker =\n | { readonly kind: 'db' }\n | { readonly kind: 'ref'; readonly name: string; readonly active?: boolean }\n | { readonly kind: 'contract'; readonly planned: boolean }\n | { readonly kind: 'custom'; readonly label: string };\n\n/** A node in the graph. Rendered as ○ with optional typed markers. */\nexport interface GraphNode {\n readonly id: string;\n /** Typed markers rendered inline on the node row */\n readonly markers?: readonly NodeMarker[] | undefined;\n /** Detached nodes use a dashed connector (e.g. unplanned contract) */\n readonly style?: 'normal' | 'detached' | undefined;\n}\n\n/** A directed edge between two nodes. Carries an optional label rendered on the edge line. */\nexport interface GraphEdge {\n readonly from: string;\n readonly to: string;\n /** Edge line text, e.g. \"20260101_init ✓ abc12..\" */\n readonly label?: string;\n /**\n * Visual color hint for the edge. Overrides the default role-based\n * coloring (spine/branch/backward) when set.\n *\n * - `'applied'` — cyan (CVD-safe: completed/done)\n * - `'pending'` — yellow (CVD-safe: waiting/upcoming)\n * - `'unreachable'` — magenta (CVD-safe: DB on a different branch)\n */\n readonly colorHint?: 'applied' | 'pending' | 'unreachable';\n /**\n * Edge rendering style.\n * - `'solid'` (default) — normal edge with `│` connector\n * - `'dashed'` — draft/phantom edge with `┊` connector, excluded from path resolution\n */\n readonly style?: 'solid' | 'dashed';\n}\n\n/**\n * Immutable directed graph with adjacency-list indexing.\n *\n * Built once from flat arrays of nodes and edges, then passed around as\n * the primary graph representation for rendering, traversal, truncation,\n * and subgraph extraction.\n */\nexport class RenderGraph {\n readonly nodes: readonly GraphNode[];\n readonly edges: readonly GraphEdge[];\n\n /** Forward adjacency: node id → outgoing edges. */\n readonly forward: ReadonlyMap<string, readonly GraphEdge[]>;\n /** Set of node ids that have at least one incoming edge. */\n readonly incomingNodes: ReadonlySet<string>;\n /** Node lookup by id. */\n readonly nodeById: ReadonlyMap<string, GraphNode>;\n\n constructor(nodes: readonly GraphNode[], edges: readonly GraphEdge[]) {\n this.nodes = nodes;\n this.edges = edges;\n\n const fwd = new Map<string, GraphEdge[]>();\n const inc = new Set<string>();\n const byId = new Map<string, GraphNode>();\n\n for (const n of nodes) {\n byId.set(n.id, n);\n }\n for (const e of edges) {\n const list = fwd.get(e.from);\n if (list) list.push(e);\n else fwd.set(e.from, [e]);\n inc.add(e.to);\n }\n\n this.forward = fwd;\n this.incomingNodes = inc;\n this.nodeById = byId;\n }\n\n /** Outgoing edges from a node (empty array if none). */\n outgoing(nodeId: string): readonly GraphEdge[] {\n return this.forward.get(nodeId) ?? [];\n }\n}\n\n/** Options controlling graph rendering. */\nexport interface GraphRenderOptions {\n /** Node id for the spine endpoint */\n readonly spineTarget: string;\n /** Root node id. Defaults to the node with no incoming edges. */\n readonly rootId?: string;\n /** Enable ANSI colour output */\n readonly colorize?: boolean;\n /** Terminal width. Default 80. */\n readonly maxWidth?: number;\n /**\n * Truncate to show at most this many spine edges. `undefined` = no truncation.\n * The effective limit expands to include any DB or contract markers on the spine.\n */\n readonly limit?: number | undefined;\n /**\n * Override dagre layout parameters. Merged over defaults:\n * `{ ranksep: 4, nodesep: 6, marginx: 2, marginy: 1 }`.\n *\n * When `ranksep` is ≤ 1, labels are placed inline on the arrowhead row\n * instead of in a separate label-placement pass (there isn't enough\n * vertical space for a dedicated label row).\n */\n readonly dagreOptions?: {\n readonly ranksep?: number;\n readonly nodesep?: number;\n readonly marginx?: number;\n readonly marginy?: number;\n };\n}\n","/**\n * Maps MigrationGraph + status info to the generic graph renderer types.\n */\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { findPath } from '@prisma-next/migration-tools/dag';\nimport type { MigrationGraph } from '@prisma-next/migration-tools/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nimport type { StatusRef } from '../migration-types';\nimport {\n type GraphEdge,\n type GraphNode,\n type GraphRenderOptions,\n type NodeMarker,\n RenderGraph,\n} from './graph-types';\n\nexport type EdgeStatusKind = 'applied' | 'pending' | 'unreachable';\n\nconst STATUS_ICON: Record<EdgeStatusKind, string> = {\n applied: ' ✓',\n pending: ' ⧗',\n unreachable: ' ✗',\n};\n\n/** Shorten a contract hash for display: strip sha256: prefix, take 7 chars. */\nfunction shortHash(hash: string): string {\n const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;\n return stripped.slice(0, 7);\n}\n\nfunction toShortId(hash: string): string {\n return hash === EMPTY_CONTRACT_HASH ? '∅' : shortHash(hash);\n}\n\n/** Minimal per-edge status from the CLI's status result. */\nexport interface EdgeStatus {\n readonly dirName: string;\n readonly status: EdgeStatusKind;\n}\n\nexport interface MigrationGraphInput {\n readonly graph: MigrationGraph;\n readonly mode: 'online' | 'offline';\n readonly markerHash?: string | undefined;\n readonly contractHash: string;\n readonly refs?: readonly StatusRef[] | undefined;\n readonly activeRefHash?: string | undefined;\n readonly activeRefName?: string | undefined;\n /**\n * Per-edge applied/pending status from the ledger. When provided, status\n * icons (✓/⧗) are baked into edge labels. Undefined in offline mode.\n */\n readonly edgeStatuses?: readonly EdgeStatus[] | undefined;\n}\n\nexport interface MigrationRenderInput {\n readonly graph: RenderGraph;\n readonly options: GraphRenderOptions;\n /** All relevant paths (root→contract, root→marker, root→ref). */\n readonly relevantPaths: readonly (readonly string[])[];\n}\n\n/**\n * Convert a MigrationGraph + status info into the generic graph renderer types.\n */\nexport function migrationGraphToRenderInput(input: MigrationGraphInput): MigrationRenderInput {\n const { graph, mode, markerHash, contractHash, refs, activeRefHash, edgeStatuses } = input;\n\n const statusByDirName = new Map(edgeStatuses?.map((e) => [e.dirName, e.status]));\n\n // Build nodes\n const nodeList: GraphNode[] = [];\n for (const nodeId of graph.nodes) {\n const markers: NodeMarker[] = [];\n\n // DB marker\n if (mode === 'online' && markerHash === nodeId) {\n markers.push({ kind: 'db' });\n }\n\n // Ref markers\n if (refs) {\n for (const ref of refs) {\n if (ref.hash === nodeId) {\n markers.push({ kind: 'ref', name: ref.name, active: ref.active });\n }\n }\n }\n\n // Contract marker\n if (contractHash === nodeId && contractHash !== EMPTY_CONTRACT_HASH) {\n markers.push({ kind: 'contract', planned: true });\n }\n\n nodeList.push({\n id: toShortId(nodeId),\n markers: markers.length > 0 ? markers : undefined,\n });\n }\n\n // Build edges\n const edgeList: GraphEdge[] = [];\n\n for (const [, entries] of graph.forwardChain) {\n for (const entry of entries) {\n const status = statusByDirName.get(entry.dirName);\n const icon = status ? STATUS_ICON[status] : '';\n const label = `${entry.dirName}${icon}`;\n\n edgeList.push({\n from: toShortId(entry.from),\n to: toShortId(entry.to),\n label,\n ...ifDefined('colorHint', status),\n });\n }\n }\n\n // Compute paths to all interesting targets so the default view shows the\n // minimal subgraph that covers everything relevant: contract, DB marker, ref.\n const relevantPaths: string[][] = [];\n const rootId = EMPTY_CONTRACT_HASH;\n\n function addPathFromRoot(targetHash: string): void {\n if (!graph.nodes.has(targetHash)) return;\n const raw = findPath(graph, rootId, targetHash);\n if (raw && raw.length > 0) {\n relevantPaths.push([toShortId(rootId), ...raw.map((e) => toShortId(e.to))]);\n }\n }\n\n function addPathBetween(fromHash: string, toHash: string): void {\n /* v8 ignore next -- @preserve */\n if (!graph.nodes.has(fromHash) || !graph.nodes.has(toHash)) return;\n const raw = findPath(graph, fromHash, toHash);\n if (raw && raw.length > 0) {\n relevantPaths.push([toShortId(fromHash), ...raw.map((e) => toShortId(e.to))]);\n }\n }\n\n // 1. Path to the DB marker\n if (mode === 'online' && markerHash) {\n addPathFromRoot(markerHash);\n }\n\n // 2. Path to the ref\n if (activeRefHash && activeRefHash !== markerHash) {\n addPathFromRoot(activeRefHash);\n }\n\n // 3. Path(s) to the contract — prefer continuing from marker and/or ref\n // rather than an independent root→contract (which BFS may route through\n // an unrelated branch). When both marker and ref exist, try both paths\n // so that the diamond (two branches converging at the contract) is visible.\n if (contractHash !== EMPTY_CONTRACT_HASH) {\n let contractReached = false;\n\n if (markerHash && markerHash !== contractHash) {\n const markerReaches = findPath(graph, markerHash, contractHash);\n if (markerReaches) {\n addPathBetween(markerHash, contractHash);\n contractReached = true;\n }\n }\n\n if (activeRefHash && activeRefHash !== markerHash && activeRefHash !== contractHash) {\n const refReaches = findPath(graph, activeRefHash, contractHash);\n if (refReaches) {\n addPathBetween(activeRefHash, contractHash);\n contractReached = true;\n }\n }\n\n if (!contractReached && contractHash !== (markerHash ?? activeRefHash)) {\n addPathFromRoot(contractHash);\n }\n }\n\n // Fall back: if no paths were found, try the tip of the forward chain.\n if (relevantPaths.length === 0) {\n const lastEdge = [...graph.forwardChain.values()].flat().pop();\n const fallbackHash = lastEdge?.to ?? EMPTY_CONTRACT_HASH;\n addPathFromRoot(fallbackHash);\n }\n\n // Spine target for rendering (edge coloring, detached node alignment).\n let spineTargetHash: string;\n\n if (activeRefHash && graph.nodes.has(activeRefHash)) {\n spineTargetHash = activeRefHash;\n } else if (contractHash !== EMPTY_CONTRACT_HASH && graph.nodes.has(contractHash)) {\n spineTargetHash = contractHash;\n } else {\n const lastEdge = [...graph.forwardChain.values()].flat().pop();\n spineTargetHash = lastEdge?.to ?? EMPTY_CONTRACT_HASH;\n }\n\n // Contract not in the migration graph — connect from spine target with a\n // dashed edge so the user can see the gap (contract has changed but no\n // migration has been planned yet).\n if (contractHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(contractHash)) {\n const contractMarkers: NodeMarker[] = [];\n if (mode === 'online' && markerHash === contractHash) {\n contractMarkers.push({ kind: 'db' });\n }\n contractMarkers.push({ kind: 'contract', planned: false });\n nodeList.push({\n id: shortHash(contractHash),\n markers: contractMarkers,\n });\n\n if (graph.nodes.has(spineTargetHash) || spineTargetHash === EMPTY_CONTRACT_HASH) {\n edgeList.push({\n from: toShortId(spineTargetHash),\n to: shortHash(contractHash),\n style: 'dashed',\n });\n }\n }\n\n return {\n graph: new RenderGraph(nodeList, edgeList),\n options: {\n spineTarget: toShortId(spineTargetHash),\n rootId: '∅',\n colorize: true,\n },\n relevantPaths,\n };\n}\n","/**\n * Terminal graph renderer.\n *\n * Renders directed graphs as ASCII/box-drawing art for terminal output. Uses\n * dagre for automatic layout (rank assignment + coordinate placement), then\n * stamps the result onto a {@link CharGrid} — a sparse character canvas that\n * resolves box-drawing junctions, color priority, and label placement.\n *\n * ## Rendering pipeline\n *\n * 1. **Layout** — dagre assigns (x, y) coordinates to nodes and polyline\n * control points to edges. We use `rankdir: 'TB'` (top-to-bottom).\n * 2. **Orthogonalization** — dagre's polylines may contain diagonal segments.\n * {@link selectBestVariant} resolves each diagonal into an L-shaped bend,\n * enumerating all 2^N combinations and picking the variant with fewest\n * corners and shortest total length.\n * 3. **Edge stamping** — orthogonal segments are stamped onto the CharGrid as\n * directional bitmasks. The grid resolves overlapping directions into the\n * correct box-drawing character (│, ─, ┌, ┼, etc.).\n * 4. **Label placement** — edge labels are placed adjacent to their polyline\n * segments, preferring horizontal (branch-specific) segments over shared\n * vertical trunks to avoid ambiguity.\n * 5. **Arrowheads** — ▾ ▴ ◂ ▸ placed one cell before the terminal point.\n * 6. **Node stamping** — `○ nodeId` with inline marker tags (db, contract,\n * ref names).\n * 7. **Elided indicator** — when truncation is active, `┊ (N earlier\n * migrations)` is stamped above the visible root.\n * 8. **Detached nodes** — rendered below the graph with `◇` and a dotted\n * connector.\n *\n * ## Graph filtering\n *\n * The caller controls what graph is rendered: the full graph, or a subgraph\n * extracted via {@link extractRelevantSubgraph} (union of relevant paths).\n * The renderer itself is agnostic — it renders whatever graph it receives.\n *\n * Truncation is supported via `options.limit`.\n *\n * ## Color accessibility\n *\n * Uses a CVD-safe palette — no red/green contrast. Shape and icon always\n * carry meaning; color only reinforces.\n */\nimport dagre from '@dagrejs/dagre';\nimport { bold, cyan, dim, magenta, yellow } from 'colorette';\nimport {\n type GraphEdge,\n type GraphNode,\n type GraphRenderOptions,\n type NodeMarker,\n RenderGraph,\n} from './graph-types';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A 2D point on the character grid (integer coordinates). */\ninterface Point {\n x: number;\n y: number;\n}\n\n/** An orthogonal line segment between two points on the character grid. */\ninterface Segment {\n readonly from: Point;\n readonly to: Point;\n}\n\nfunction segment(from: Point, to: Point): Segment {\n return { from, to };\n}\n\nfunction isVertical(seg: Segment): boolean {\n return seg.from.x === seg.to.x;\n}\n\nfunction manhattanLength(seg: Segment): number {\n return Math.abs(seg.to.x - seg.from.x) + Math.abs(seg.to.y - seg.from.y);\n}\n\n/** A function that wraps a string with an ANSI color escape sequence. */\ntype ColorFn = (s: string) => string;\n\n// ---------------------------------------------------------------------------\n// CVD-safe color palette\n//\n// No red/green contrast. Shape/icon always carries meaning; color reinforces.\n// ---------------------------------------------------------------------------\n\n/** Color functions for each semantic role in the graph. */\ninterface GraphColors {\n spine: ColorFn;\n branch: ColorFn;\n backward: ColorFn;\n applied: ColorFn;\n pending: ColorFn;\n unreachable: ColorFn;\n node: ColorFn;\n label: ColorFn;\n marker: ColorFn;\n /** Rotating color for ref markers — cycles through the palette by index. */\n ref: (index: number) => ColorFn;\n}\n\n/** Rotating palette for ref marker names, cycling through these for each ref. */\nconst REF_COLORS: ColorFn[] = [yellow, magenta, bold, cyan];\n\n/** Build the color palette, respecting the `colorize` flag. When false, all color functions become identity. */\nfunction buildColors(colorize: boolean): GraphColors {\n const c = (fn: ColorFn): ColorFn => (colorize ? fn : (s) => s);\n return {\n spine: c(cyan),\n branch: c(dim),\n backward: c(magenta),\n applied: c(cyan),\n pending: c(yellow),\n unreachable: c(magenta),\n node: c(cyan),\n label: c(dim),\n marker: c(bold),\n ref: (index: number) => c(REF_COLORS[index % REF_COLORS.length]!),\n };\n}\n\n/** Map a `colorHint` value to its color function, or `undefined` for no hint. */\nfunction resolveHintColor(hint: GraphEdge['colorHint'], colors: GraphColors): ColorFn | undefined {\n if (hint === 'applied') return colors.applied;\n if (hint === 'pending') return colors.pending;\n if (hint === 'unreachable') return colors.unreachable;\n return undefined;\n}\n\n/**\n * Edge drawing priorities — higher priority wins when edges overlap on the\n * same grid cell. Backward edges are drawn on top so rollback paths remain\n * visible over spine and branch edges.\n */\nconst PRIORITY = {\n branch: 1,\n spine: 2,\n backward: 3,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Direction bitmask → box-drawing character\n//\n// Each grid cell accumulates a bitmask of connected directions (UP, DOWN,\n// LEFT, RIGHT). The bitmask is then mapped to the appropriate Unicode\n// box-drawing character. For example, UP|RIGHT → └, all four → ┼.\n// ---------------------------------------------------------------------------\n\nconst DIR = {\n up: 1,\n down: 2,\n left: 4,\n right: 8,\n dashed: 16,\n} as const;\n\n/** Arrow characters for edge termination (one cell before the target node). */\nconst ARROW = { up: '▴', down: '▾', left: '◂', right: '▸' };\n\n/** Maps a direction bitmask to its box-drawing character. */\nconst BOX_CHAR: Record<number, string> = {\n 0: ' ',\n [DIR.up]: '│',\n [DIR.down]: '│',\n [DIR.up | DIR.down]: '│',\n [DIR.left]: '─',\n [DIR.right]: '─',\n [DIR.left | DIR.right]: '─',\n [DIR.down | DIR.right]: '┌',\n [DIR.down | DIR.left]: '┐',\n [DIR.up | DIR.right]: '└',\n [DIR.up | DIR.left]: '┘',\n [DIR.up | DIR.down | DIR.right]: '├',\n [DIR.up | DIR.down | DIR.left]: '┤',\n [DIR.left | DIR.right | DIR.down]: '┬',\n [DIR.left | DIR.right | DIR.up]: '┴',\n [DIR.up | DIR.down | DIR.left | DIR.right]: '┼',\n // Dashed variants — straight segments only, corners fall back to solid\n [DIR.up | DIR.dashed]: '┊',\n [DIR.down | DIR.dashed]: '┊',\n [DIR.up | DIR.down | DIR.dashed]: '┊',\n [DIR.left | DIR.dashed]: '┈',\n [DIR.right | DIR.dashed]: '┈',\n [DIR.left | DIR.right | DIR.dashed]: '┈',\n};\n\n// ---------------------------------------------------------------------------\n// Inline marker tags\n//\n// Markers (db, contract, ref, custom) are rendered inline after the node id:\n// ○ abc1234 ◆ db prod\n// ---------------------------------------------------------------------------\n\n/** A single rendered tag: the display text and the color function to apply. */\ninterface InlineTag {\n text: string;\n color: ColorFn;\n}\n\n/**\n * Convert a node's markers into renderable inline tags.\n *\n * - `db` → `◆ db`\n * - `contract` → `◆ contract` (applied) or `◇ contract` (planned)\n * - `ref` → the ref name, colored from the rotating {@link REF_COLORS} palette\n * - `custom` → the custom label\n */\nfunction buildInlineTags(markers: readonly NodeMarker[], colors: GraphColors): InlineTag[] {\n const tags: InlineTag[] = [];\n const refNames = markers\n .filter((m): m is NodeMarker & { kind: 'ref' } => m.kind === 'ref')\n .map((m) => m.name);\n\n for (const m of markers) {\n if (m.kind === 'db') {\n tags.push({ text: '◆ db', color: colors.marker });\n } else if (m.kind === 'contract') {\n tags.push({ text: m.planned ? '◆ contract' : '◇ contract', color: colors.marker });\n } else if (m.kind === 'ref') {\n tags.push({ text: m.name, color: colors.ref(refNames.indexOf(m.name)) });\n } else if (m.kind === 'custom') {\n tags.push({ text: m.label, color: colors.marker });\n }\n }\n return tags;\n}\n\n/** Total character width of inline tags including leading spaces (0 if no tags). */\nfunction inlineTagsWidth(tags: InlineTag[]): number {\n if (tags.length === 0) return 0;\n return tags.reduce((w, t) => w + 1 + t.text.length, 0);\n}\n\n// ---------------------------------------------------------------------------\n// Character grid with color priority\n//\n// The grid is the central rendering canvas. It supports two layers:\n//\n// 1. **Connections** — direction bitmasks at (x, y) cells, resolved to\n// box-drawing characters at render time. When multiple edges cross the\n// same cell, their direction bits are OR'd together (e.g. UP|RIGHT → └).\n// Color follows a priority system so higher-priority edges (backward >\n// spine > branch) visually dominate at intersections.\n//\n// 2. **Text stamps** — literal characters placed at (x, y), such as node\n// ids, labels, arrowheads, and markers. Text stamps override connections\n// at the same position.\n//\n// The grid also tracks **reserved areas** (node label regions) so that\n// label-placement heuristics can avoid overlapping node text.\n// ---------------------------------------------------------------------------\n\n/** Tracks the winning color for a grid cell based on edge priority. */\ninterface CellColor {\n color: ColorFn | undefined;\n priority: number;\n}\n\n/**\n * Sparse character canvas for terminal graph rendering.\n *\n * Coordinates are unbounded integers — the grid auto-expands as content is\n * added and trims to the bounding box on {@link render}.\n */\nclass CharGrid {\n private connections = new Map<string, number>();\n private cellColors = new Map<string, CellColor>();\n private chars = new Map<string, { ch: string; color: ColorFn | undefined }>();\n private reserved = new Set<string>();\n private minX = Number.POSITIVE_INFINITY;\n private maxX = Number.NEGATIVE_INFINITY;\n private minY = Number.POSITIVE_INFINITY;\n private maxY = Number.NEGATIVE_INFINITY;\n\n private key(x: number, y: number): string {\n return `${x},${y}`;\n }\n\n /** Expand the bounding box to include (x, y). */\n private touch(x: number, y: number): void {\n if (x < this.minX) this.minX = x;\n if (x > this.maxX) this.maxX = x;\n if (y < this.minY) this.minY = y;\n if (y > this.maxY) this.maxY = y;\n }\n\n /**\n * Add a directional connection at (x, y). Multiple calls at the same cell\n * are OR'd together — e.g. `addConnection(x, y, UP)` then\n * `addConnection(x, y, RIGHT)` produces a └ corner. Color follows\n * priority: higher-priority edges win at shared cells.\n */\n addConnection(\n x: number,\n y: number,\n dir: number,\n color?: ColorFn,\n priority: number = PRIORITY.branch,\n ): void {\n this.touch(x, y);\n const k = this.key(x, y);\n this.connections.set(k, (this.connections.get(k) ?? 0) | dir);\n const existing = this.cellColors.get(k);\n if (!existing || priority >= existing.priority) {\n this.cellColors.set(k, { color, priority });\n }\n }\n\n /** Stamp a horizontal edge segment from x1 to x2 at row y. */\n markHorizontal(\n y: number,\n x1: number,\n x2: number,\n color?: ColorFn,\n priority?: number,\n extraBits = 0,\n ): void {\n const lo = Math.min(x1, x2);\n const hi = Math.max(x1, x2);\n /* v8 ignore next -- @preserve */\n if (lo === hi) return;\n this.addConnection(lo, y, DIR.right | extraBits, color, priority);\n for (let x = lo + 1; x < hi; x++)\n this.addConnection(x, y, DIR.left | DIR.right | extraBits, color, priority);\n this.addConnection(hi, y, DIR.left | extraBits, color, priority);\n }\n\n /** Stamp a vertical edge segment from y1 to y2 at column x. */\n markVertical(\n x: number,\n y1: number,\n y2: number,\n color?: ColorFn,\n priority?: number,\n extraBits = 0,\n ): void {\n const lo = Math.min(y1, y2);\n const hi = Math.max(y1, y2);\n /* v8 ignore next -- @preserve */\n if (lo === hi) return;\n this.addConnection(x, lo, DIR.down | extraBits, color, priority);\n for (let y = lo + 1; y < hi; y++)\n this.addConnection(x, y, DIR.up | DIR.down | extraBits, color, priority);\n this.addConnection(x, hi, DIR.up | extraBits, color, priority);\n }\n\n /** Place literal text at (x, y). Each character occupies one cell. Text stamps override connections. */\n stampText(x: number, y: number, text: string, color?: ColorFn): void {\n for (let i = 0; i < text.length; i++) {\n const cx = x + i;\n this.touch(cx, y);\n this.chars.set(this.key(cx, y), { ch: text[i]!, color });\n }\n }\n\n /** True if (x, y) has stamped text or is in a reserved area (node labels). */\n hasLabel(x: number, y: number): boolean {\n return this.chars.has(this.key(x, y)) || this.reserved.has(this.key(x, y));\n }\n\n /** True if (x, y) has any directional connection (an edge passes through). */\n hasConnection(x: number, y: number): boolean {\n return (this.connections.get(this.key(x, y)) ?? 0) !== 0;\n }\n\n /** True if (x, y) has stamped text (not just a reserved area). */\n hasText(x: number, y: number): boolean {\n return this.chars.has(this.key(x, y));\n }\n\n /** Reserve a horizontal span so label placement avoids it. Used for node id + marker regions. */\n reserveArea(x: number, y: number, width: number): void {\n for (let i = 0; i < width; i++) this.reserved.add(this.key(x + i, y));\n }\n\n /** The largest y coordinate with content — used for positioning detached nodes below the graph. */\n getMaxY(): number {\n return this.maxY;\n }\n\n /**\n * Render the grid to a multi-line string.\n *\n * Iterates row by row over the bounding box, resolving each cell to either\n * its stamped text character or the box-drawing character for its\n * connection bitmask. Consecutive characters with the same color are\n * batched into a single ANSI-wrapped run for efficiency.\n */\n render(): string {\n /* v8 ignore next -- @preserve */\n if (this.minX === Number.POSITIVE_INFINITY) return '(empty)';\n\n const rows: string[] = [];\n for (let y = this.minY; y <= this.maxY; y++) {\n let row = '';\n let runChars = '';\n let runColor: ColorFn | undefined;\n\n const flush = () => {\n if (runChars.length === 0) return;\n row += runColor ? runColor(runChars) : runChars;\n runChars = '';\n };\n\n for (let x = this.minX; x <= this.maxX; x++) {\n const k = this.key(x, y);\n let ch: string;\n let color: ColorFn | undefined;\n\n const label = this.chars.get(k);\n if (label) {\n ch = label.ch;\n color = label.color;\n } else {\n const conn = this.connections.get(k) ?? 0;\n // Dashed corners don't exist — strip the bit and fall back to solid\n ch = BOX_CHAR[conn] ?? BOX_CHAR[conn & ~DIR.dashed] ?? ' ';\n color = conn === 0 ? undefined : this.cellColors.get(k)?.color;\n }\n\n if (color !== runColor) {\n flush();\n runColor = color;\n }\n runChars += ch;\n }\n flush();\n rows.push(row.trimEnd());\n }\n\n while (rows.length > 0 && rows[rows.length - 1] === '') rows.pop();\n return rows.join('\\n');\n }\n}\n\n// ---------------------------------------------------------------------------\n// Spine detection — BFS shortest path from root to target\n//\n// The renderer operates on generic GraphNode/GraphEdge, not MigrationGraph,\n// so it cannot use domain-specific pathfinding. These two BFS functions\n// re-derive the spine from the generic edge list.\n// ---------------------------------------------------------------------------\n\n/**\n * Find the set of edge keys (`\"from→to\"`) on the shortest path from\n * `rootId` to `targetId`. Used to color spine edges distinctly from\n * branch edges in the rendered output.\n *\n * Returns an empty set if no path exists.\n */\nfunction findSpineEdges(graph: RenderGraph, rootId: string, targetId: string): Set<string> {\n const visited = new Set([rootId]);\n const parent = new Map<string, GraphEdge>();\n const queue = [rootId];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n if (current === targetId) {\n const spineEdges = new Set<string>();\n let node = targetId;\n while (parent.has(node)) {\n const edge = parent.get(node)!;\n spineEdges.add(`${edge.from}→${edge.to}`);\n node = edge.from;\n }\n return spineEdges;\n }\n for (const edge of graph.outgoing(current)) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n parent.set(edge.to, edge);\n queue.push(edge.to);\n }\n }\n }\n return new Set();\n}\n\n// ---------------------------------------------------------------------------\n// Orthogonal polyline builder — variant-based\n//\n// Dagre produces polyline control points that may contain diagonal segments\n// (two consecutive points that differ in both x and y). Terminal rendering\n// requires strictly orthogonal segments (horizontal or vertical only).\n//\n// To resolve diagonals, we insert an L-shaped bend at each one. Each diagonal\n// has two possible resolutions (horizontal-first or vertical-first), so N\n// diagonals produce 2^N candidate polylines. We enumerate all variants and\n// pick the one with the fewest corners and shortest total length.\n// ---------------------------------------------------------------------------\n\n/**\n * Prepend `src` and append `tgt` to dagre's control points, round to\n * integers, and deduplicate consecutive identical points.\n */\nfunction prepareRawPoints(src: Point, dagrePoints: Point[], tgt: Point): Point[] {\n const raw = [src, ...dagrePoints, tgt];\n const rounded = raw.map((p) => ({ x: Math.round(p.x), y: Math.round(p.y) }));\n const deduped: Point[] = [rounded[0]!];\n for (let i = 1; i < rounded.length; i++) {\n const prev = deduped[deduped.length - 1]!;\n const curr = rounded[i]!;\n if (curr.x !== prev.x || curr.y !== prev.y) deduped.push(curr);\n }\n return deduped;\n}\n\nfunction countDiagonals(points: Point[]): number {\n let count = 0;\n for (let i = 1; i < points.length; i++) {\n const prev = points[i - 1]!;\n const curr = points[i]!;\n if (prev.x !== curr.x && prev.y !== curr.y) count++;\n }\n return count;\n}\n\ntype BendDirection = 'horizontal-first' | 'vertical-first';\n\n/**\n * Decode a bitmask into an array of bend directions.\n *\n * Each bit selects how one diagonal is converted to an orthogonal corner:\n * 0 → horizontal-first, 1 → vertical-first. Used by {@link selectBestVariant}\n * to enumerate all 2^N combinations.\n */\nfunction bitsToBends(bits: number, count: number): BendDirection[] {\n const bends: BendDirection[] = [];\n for (let k = 0; k < count; k++) {\n bends.push((bits >> k) & 1 ? 'vertical-first' : 'horizontal-first');\n }\n return bends;\n}\n\n/**\n * Build one polyline variant by resolving each diagonal with the given bend direction.\n *\n * Diagonals are detected inline: when the last emitted point and the next\n * input point differ in both x and y, the segment is diagonal and is\n * resolved using the next value from `bends`. The result is deduplicated\n * to remove zero-length segments created when a bend coincides with an\n * adjacent point.\n */\nfunction buildVariant(points: Point[], bends: BendDirection[]): Point[] {\n /* v8 ignore next -- @preserve */\n if (points.length < 2) return points;\n\n let bendIdx = 0;\n const result: Point[] = [points[0]!];\n for (let i = 1; i < points.length; i++) {\n const prev = result[result.length - 1]!;\n const curr = points[i]!;\n\n if (prev.x === curr.x || prev.y === curr.y) {\n result.push(curr);\n } else {\n const bend = bends[bendIdx++] ?? 'horizontal-first';\n if (bend === 'horizontal-first') {\n result.push({ x: curr.x, y: prev.y });\n } else {\n result.push({ x: prev.x, y: curr.y });\n }\n result.push(curr);\n }\n }\n\n const final: Point[] = [result[0]!];\n for (let i = 1; i < result.length; i++) {\n const prev = final[final.length - 1]!;\n const curr = result[i]!;\n if (curr.x !== prev.x || curr.y !== prev.y) final.push(curr);\n }\n return final;\n}\n\n/** Count the number of direction changes (corners) in an orthogonal polyline. */\nfunction countCorners(poly: Point[]): number {\n let corners = 0;\n for (let i = 1; i < poly.length - 1; i++) {\n const a = poly[i - 1]!;\n const b = poly[i]!;\n const c = poly[i + 1]!;\n const d1Vert = a.x === b.x;\n const d2Vert = b.x === c.x;\n if (d1Vert !== d2Vert) corners++;\n }\n return corners;\n}\n\n/** Manhattan length of a polyline (sum of absolute x and y deltas). */\nfunction polyLength(poly: Point[]): number {\n let len = 0;\n for (let i = 0; i < poly.length - 1; i++) {\n len += Math.abs(poly[i + 1]!.x - poly[i]!.x) + Math.abs(poly[i + 1]!.y - poly[i]!.y);\n }\n return len;\n}\n\n// ---------------------------------------------------------------------------\n// Label placement\n//\n// Edge labels (migration names) are placed adjacent to polyline segments.\n// The algorithm generates candidate positions along each segment, scores\n// them, and picks the best. Key heuristics:\n//\n// - **Horizontal segment preference**: when a polyline has both vertical and\n// horizontal segments, horizontal segments are boosted because they\n// uniquely identify a branch, while vertical segments often share column\n// space with the trunk. This prevents labels from \"jumping\" when node\n// widths change.\n// - **Source adjacency penalty**: positions within ±1 row of the source node\n// are penalized — labels there look like they belong to an incoming edge.\n// - **Whitespace bonus**: positions with clear space above and below score\n// higher for readability.\n// ---------------------------------------------------------------------------\n\n/**\n * Find the best (x, y) position to place an edge label adjacent to its\n * polyline. Returns null if no collision-free position exists.\n *\n * @param poly - The orthogonalized polyline for the edge.\n * @param label - The label text to place.\n * @param grid - The character grid (used for collision checks).\n * @param srcY - Y coordinate of the source node (for adjacency penalty).\n */\nfunction findLabelPlacement(\n poly: Point[],\n label: string,\n grid: CharGrid,\n srcY?: number,\n): Point | undefined {\n const segments = polyToSegments(poly);\n\n let best: (Point & { score: number }) | undefined;\n\n for (const seg of segments) {\n const candidates = segmentLabelCandidates(seg, label.length);\n for (const pos of candidates) {\n if (labelCollides(grid, pos.x, pos.y, label)) continue;\n const score = scoreLabelCandidate(pos, seg, segments, label, grid, srcY);\n if (!best || score > best.score) best = { x: pos.x, y: pos.y, score };\n }\n }\n\n return best;\n}\n\n/** Convert a polyline into non-zero-length segments. */\nfunction polyToSegments(poly: readonly Point[]): Segment[] {\n const segments: Segment[] = [];\n for (let i = 0; i < poly.length - 1; i++) {\n const seg = segment(poly[i]!, poly[i + 1]!);\n if (manhattanLength(seg) > 0) segments.push(seg);\n }\n return segments;\n}\n\n/**\n * Generate all candidate (x, y) positions for placing a label adjacent\n * to a single segment. Positions are perpendicular to the segment:\n *\n * - Vertical segments: left and right, at every y along the segment.\n * - Horizontal segments: above and below, at every x where the label fits.\n */\nfunction segmentLabelCandidates(seg: Segment, labelLen: number): Point[] {\n const candidates: Point[] = [];\n\n if (isVertical(seg)) {\n const minY = Math.min(seg.from.y, seg.to.y);\n const maxY = Math.max(seg.from.y, seg.to.y);\n for (const x of [seg.from.x + 2, seg.from.x - labelLen - 1]) {\n for (let y = minY; y <= maxY; y++) {\n candidates.push({ x, y });\n }\n }\n } else {\n const minX = Math.min(seg.from.x, seg.to.x);\n const maxX = Math.max(seg.from.x, seg.to.x);\n for (const dy of [-1, 1]) {\n const y = seg.from.y + dy;\n for (let x = minX; x <= maxX - labelLen + 1; x++) {\n candidates.push({ x, y });\n }\n }\n }\n\n return candidates;\n}\n\n/**\n * Score a candidate label position. Higher is better.\n *\n * Combines: segment length, surrounding whitespace, distance from\n * segment midpoint, source-node proximity penalty, and segment-position\n * bonus (horizontal/later segments preferred when the edge has bends).\n *\n * @param pos - Candidate position (top-left corner of the label text).\n * @param seg - The segment this candidate is adjacent to.\n * @param allSegments - All segments of the edge polyline (for segment-position bonus).\n * @param label - The label text (used for width and whitespace probing).\n * @param grid - The character grid (used for whitespace checks).\n * @param srcY - Y coordinate of the edge's source node (penalizes labels\n * that would appear to belong to the node rather than the edge).\n */\nfunction scoreLabelCandidate(\n pos: Point,\n seg: Segment,\n allSegments: readonly Segment[],\n label: string,\n grid: CharGrid,\n srcY?: number,\n): number {\n const len = manhattanLength(seg);\n const midX = Math.round((seg.from.x + seg.to.x) / 2);\n const midY = Math.round((seg.from.y + seg.to.y) / 2);\n\n let score = len;\n\n // Whitespace above/below the label improves readability.\n for (let dy = 1; dy <= 2; dy++) {\n if (!rowHasContent(grid, pos.x, pos.y - dy, label.length)) score += 3;\n if (!rowHasContent(grid, pos.x, pos.y + dy, label.length)) score += 3;\n }\n\n // Prefer positions near the segment midpoint.\n const labelCenterX = pos.x + Math.floor(label.length / 2);\n score -= (Math.abs(labelCenterX - midX) + Math.abs(pos.y - midY)) * 2;\n\n const labelCenterY = pos.y + Math.floor(label.length / 2);\n score -= (Math.abs(labelCenterY - midY) + Math.abs(pos.x - midX)) * 2;\n\n // Prefer labels to the right of a vertical segment\n if (isVertical(seg) && pos.x > seg.from.x) {\n score += 10;\n }\n\n // Penalize positions adjacent to the source node — labels there\n // look like they belong to the incoming edge above.\n if (srcY !== undefined && Math.abs(pos.y - srcY) <= 1) score -= 20;\n\n // Horizontal segments uniquely identify a branch, while the initial\n // vertical drop from the source often shares column space with the trunk.\n // Boost horizontal and later segments so labels land on the branch.\n const hasHorizontalSeg = allSegments.some((s) => !isVertical(s));\n if (hasHorizontalSeg) {\n if (!isVertical(seg)) score += 15;\n const segIndex = allSegments.indexOf(seg);\n score += (segIndex / allSegments.length) * 5;\n }\n\n return score;\n}\n\n/** True if any cell in the horizontal span [x, x+width) at row y has content. */\nfunction rowHasContent(grid: CharGrid, x: number, y: number, width: number): boolean {\n for (let i = 0; i < width; i++) {\n if (grid.hasLabel(x + i, y) || grid.hasConnection(x + i, y)) return true;\n }\n return false;\n}\n\n/**\n * Check if placing `text` at (x, y) would collide with existing content.\n * Checks one cell of padding on each side to keep labels visually separated.\n */\nfunction labelCollides(grid: CharGrid, x: number, y: number, text: string): boolean {\n for (let i = -1; i <= text.length; i++) {\n const cx = x + i;\n if (grid.hasLabel(cx, y)) return true;\n if (i >= 0 && i < text.length && grid.hasConnection(cx, y)) return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Joint variant selection\n//\n// Combines diagonal resolution with label placement in a single pass. For\n// each of the 2^N polyline variants, we compute a score based on corner\n// count, total length, and whether the label can be placed. The variant\n// with the lowest score wins.\n// ---------------------------------------------------------------------------\n\n/** A resolved polyline paired with its best label position (if any). */\ninterface PolyWithLabel {\n poly: Point[];\n labelPos: Point | undefined;\n}\n\n/**\n * Resolve dagre's polyline into the best orthogonal variant and find the\n * optimal label position in a single pass.\n *\n * Enumerates all 2^N diagonal resolutions, scores each by:\n * - `corners * 10` — fewer corners preferred\n * - `+ manhattan length` — shorter paths preferred\n * - `+ 100` penalty if the label couldn't be placed\n *\n * For edges with no diagonals, the polyline is used as-is.\n */\nfunction selectBestVariant(\n src: Point,\n dagrePoints: Point[],\n tgt: Point,\n label: string | undefined,\n grid: CharGrid,\n): PolyWithLabel {\n const rawPoints = prepareRawPoints(src, dagrePoints, tgt);\n const diagCount = countDiagonals(rawPoints);\n\n if (diagCount === 0) {\n const poly = buildVariant(rawPoints, []);\n const labelPos = label ? findLabelPlacement(poly, label, grid, src.y) : undefined;\n return { poly, labelPos };\n }\n\n // Each diagonal must be converted to an orthogonal corner — either\n // horizontal-first or vertical-first. With N diagonals that's 2^N\n // combinations, enumerated via bitmask: we count from 0 to 2^N - 1,\n // and every number represents a different combination of bend directions.\n const numVariants = 1 << diagCount;\n let bestPoly: Point[] | null = null;\n let bestLabel: Point | undefined;\n let bestScore = Number.POSITIVE_INFINITY;\n\n for (let bits = 0; bits < numVariants; bits++) {\n const bends = bitsToBends(bits, diagCount);\n const poly = buildVariant(rawPoints, bends);\n\n const corners = countCorners(poly);\n const len = polyLength(poly);\n const labelPos = label ? findLabelPlacement(poly, label, grid, src.y) : undefined;\n\n const labelPenalty = label && !labelPos ? 100 : 0;\n const score = corners * 10 + len + labelPenalty;\n\n if (score < bestScore) {\n bestScore = score;\n bestPoly = poly;\n bestLabel = labelPos;\n }\n }\n\n return {\n poly: bestPoly ?? buildVariant(rawPoints, bitsToBends(0, diagCount)),\n labelPos: bestLabel,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Subgraph extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the subgraph containing only the nodes and forward-moving edges\n * along the given path.\n *\n * Backward (rollback) edges are excluded even if both endpoints are on the\n * path — only edges where `from` precedes `to` in path order are kept.\n */\nexport function extractSubgraph(graph: RenderGraph, path: readonly string[]): RenderGraph {\n const pathIndex = new Map(path.map((id, i) => [id, i]));\n const nodeSet = new Set(path);\n // Always keep dashed edges and their endpoints\n for (const e of graph.edges) {\n if (e.style === 'dashed') {\n nodeSet.add(e.from);\n nodeSet.add(e.to);\n }\n }\n const filteredNodes = graph.nodes.filter((n) => nodeSet.has(n.id));\n const filteredEdges = graph.edges.filter((e) => {\n if (e.style === 'dashed') return true;\n const fromIdx = pathIndex.get(e.from);\n const toIdx = pathIndex.get(e.to);\n return fromIdx !== undefined && toIdx !== undefined && fromIdx < toIdx;\n });\n return new RenderGraph(filteredNodes, filteredEdges);\n}\n\n/**\n * Extract the subgraph covering the union of multiple paths.\n *\n * Each path is an ordered list of node ids (root → target). The result\n * contains every node on any path plus every forward edge between\n * consecutive nodes on any path. Detached nodes are always included.\n *\n * When all paths overlap (the common case), the result is identical to\n * a single-path extract. When paths diverge (e.g. DB marker on a\n * different branch than the contract), the result naturally includes the\n * fork and both branches — exactly the minimal information needed.\n */\nexport function extractRelevantSubgraph(\n graph: RenderGraph,\n paths: readonly (readonly string[])[],\n): RenderGraph {\n const nodeSet = new Set<string>();\n const edgePairs = new Set<string>();\n\n for (const path of paths) {\n for (let i = 0; i < path.length; i++) {\n nodeSet.add(path[i]!);\n if (i > 0) {\n edgePairs.add(`${path[i - 1]!}\\0${path[i]!}`);\n }\n }\n }\n\n // Always keep dashed (draft) edges and their endpoints\n const dashedEdges = graph.edges.filter((e) => e.style === 'dashed');\n for (const e of dashedEdges) {\n nodeSet.add(e.from);\n nodeSet.add(e.to);\n }\n\n const filteredNodes = graph.nodes.filter((n) => nodeSet.has(n.id));\n const filteredEdges = graph.edges.filter(\n (e) => edgePairs.has(`${e.from}\\0${e.to}`) || e.style === 'dashed',\n );\n return new RenderGraph(filteredNodes, filteredEdges);\n}\n\n// ---------------------------------------------------------------------------\n// Truncation — keep last N spine edges, expand for markers\n// ---------------------------------------------------------------------------\n\n/** Result of {@link truncateGraph} — the visible subgraph plus truncation metadata. */\nexport interface TruncationResult {\n readonly graph: RenderGraph;\n /** Number of spine edges hidden by truncation (0 = nothing truncated). */\n readonly elidedCount: number;\n /** The visible portion of the spine (subset of the input spine). */\n readonly spine: readonly string[];\n}\n\n/**\n * Truncate a graph to the last `limit` spine edges from the spine target.\n * The window expands to include any node carrying a db or contract marker\n * so those are never truncated away.\n *\n * For the full graph: keeps all branches that fork from the visible spine window.\n * For the spine view: caller should call extractSubgraph first, then truncate.\n */\nexport function truncateGraph(\n graph: RenderGraph,\n spine: readonly string[],\n limit: number,\n): TruncationResult {\n if (spine.length <= 1 || limit >= spine.length - 1) {\n return { graph, elidedCount: 0, spine };\n }\n\n // Find the earliest spine node that has a db or contract marker\n let earliestMarkerIdx = spine.length;\n for (let i = 0; i < spine.length; i++) {\n const n = graph.nodeById.get(spine[i]!);\n if (n?.markers?.some((m) => m.kind === 'db' || m.kind === 'contract')) {\n earliestMarkerIdx = i;\n break;\n }\n }\n\n // Effective limit: expand to include markers\n // spine has N+1 nodes for N edges; we want the last `effectiveEdges` edges,\n // which means keeping the last `effectiveEdges + 1` nodes\n const markerDistance = spine.length - 1 - earliestMarkerIdx;\n const effectiveEdges = Math.max(limit, markerDistance);\n\n if (effectiveEdges >= spine.length - 1) {\n return { graph, elidedCount: 0, spine };\n }\n\n const keepFromIdx = spine.length - 1 - effectiveEdges;\n const truncatedSpine = spine.slice(keepFromIdx);\n const visibleSpineSet = new Set(truncatedSpine);\n\n // Include any node reachable from visible spine nodes\n // (branches that fork from visible portion)\n const reachable = new Set(visibleSpineSet);\n const queue = [...truncatedSpine];\n while (queue.length > 0) {\n const current = queue.shift()!;\n for (const edge of graph.outgoing(current)) {\n if (!reachable.has(edge.to)) {\n reachable.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n\n const truncatedNodes = graph.nodes.filter((n) => reachable.has(n.id));\n const truncatedEdges = graph.edges.filter((e) => reachable.has(e.from) && reachable.has(e.to));\n const elidedCount = spine.length - 1 - effectiveEdges;\n\n return {\n graph: new RenderGraph(truncatedNodes, truncatedEdges),\n elidedCount,\n spine: truncatedSpine,\n };\n}\n\n/**\n * After truncation the original root may not be in the visible graph.\n * Find the first node with no incoming edges as a fallback root.\n */\nfunction findVisibleRoot(graph: RenderGraph, layoutNodes: readonly GraphNode[]): string {\n return layoutNodes.find((n) => !graph.incomingNodes.has(n.id))?.id ?? layoutNodes[0]?.id ?? '∅';\n}\n\n// ---------------------------------------------------------------------------\n// Core layout + render pipeline\n// ---------------------------------------------------------------------------\n\n/**\n * The main rendering pipeline: dagre layout → edge stamping → label\n * placement → arrowheads → nodes → elided indicator → detached nodes.\n *\n * Called by {@link render} after optional truncation. Receives nodes/edges\n * and produces the final multi-line string.\n *\n * @param graph - The graph to render (may include detached nodes, which are\n * rendered below the main graph rather than laid out by dagre).\n * @param options - Render options (rootId, spineTarget, colorize).\n * @param elidedCount - If > 0, a `┊ (N earlier migrations)` indicator\n * is stamped above the visible root node.\n */\nfunction layoutAndRender(graph: RenderGraph, options: GraphRenderOptions, elidedCount = 0): string {\n const colorize = options.colorize ?? true;\n const colors = buildColors(colorize);\n\n const layoutNodes = graph.nodes;\n const layoutNodeIds = new Set(layoutNodes.map((n) => n.id));\n const requestedRoot = options.rootId ?? layoutNodes[0]?.id ?? '∅';\n const rootId = layoutNodeIds.has(requestedRoot)\n ? requestedRoot\n : findVisibleRoot(graph, layoutNodes);\n\n const spineEdgeKeys = findSpineEdges(graph, rootId, options.spineTarget);\n\n const g = new dagre.graphlib.Graph({ multigraph: true });\n const dagreDefaults = { ranksep: 4, nodesep: 6, marginx: 2, marginy: 1 };\n g.setGraph({ rankdir: 'TB', ...dagreDefaults, ...options.dagreOptions });\n g.setDefaultEdgeLabel(() => ({}));\n\n for (const node of layoutNodes) {\n const tags = buildInlineTags(node.markers ?? [], colors);\n const tagWidth = inlineTagsWidth(tags);\n g.setNode(node.id, { width: node.id.length + 6 + tagWidth, height: 1 });\n }\n\n const edgeNames: string[] = [];\n for (let i = 0; i < graph.edges.length; i++) {\n const edge = graph.edges[i]!;\n const name = `e${i}`;\n edgeNames.push(name);\n g.setEdge(edge.from, edge.to, { label: edge.label ?? '' }, name);\n }\n\n dagre.layout(g);\n\n const nodePos = new Map<string, Point>();\n for (const id of g.nodes()) {\n const n = g.node(id);\n nodePos.set(id, { x: Math.round(n.x), y: Math.round(n.y) });\n }\n\n const grid = new CharGrid();\n\n // Reserve node label areas so edges and labels avoid them\n for (const node of layoutNodes) {\n const pos = nodePos.get(node.id);\n /* v8 ignore next -- @preserve */\n if (!pos) continue;\n const tags = buildInlineTags(node.markers ?? [], colors);\n const tagWidth = inlineTagsWidth(tags);\n grid.reserveArea(pos.x - 1, pos.y, node.id.length + 4 + tagWidth);\n }\n\n // --- Prepare edge metadata ---\n type EdgeEntry = {\n idx: number;\n edge: GraphEdge;\n dagrePoints: Point[];\n src: Point;\n tgt: Point;\n role: 'spine' | 'branch' | 'backward';\n edgeColor: ColorFn;\n priority: number;\n };\n const edgeEntries: EdgeEntry[] = [];\n\n for (let i = 0; i < graph.edges.length; i++) {\n const edge = graph.edges[i]!;\n const name = edgeNames[i]!;\n if (!name || !nodePos.has(edge.from) || !nodePos.has(edge.to)) continue;\n\n const src = nodePos.get(edge.from)!;\n const tgt = nodePos.get(edge.to)!;\n const dagreEdge = g.edge({ v: edge.from, w: edge.to, name });\n const dagrePoints: Point[] = dagreEdge?.points ?? [];\n\n const isBackward = tgt.y < src.y;\n const isSpine = spineEdgeKeys.has(`${edge.from}→${edge.to}`);\n const role: EdgeEntry['role'] = isBackward ? 'backward' : isSpine ? 'spine' : 'branch';\n const hintColor = resolveHintColor(edge.colorHint, colors);\n const edgeColor =\n hintColor ??\n (role === 'backward' ? colors.backward : role === 'spine' ? colors.spine : colors.branch);\n const priority =\n role === 'backward' ? PRIORITY.backward : role === 'spine' ? PRIORITY.spine : PRIORITY.branch;\n\n edgeEntries.push({ idx: i, edge, dagrePoints, src, tgt, role, edgeColor, priority });\n }\n\n // --- Pass 1: Draw all edges ---\n type DrawnEdge = { edge: GraphEdge; poly: Point[]; role: EdgeEntry['role']; srcY: number };\n const drawnEdges: DrawnEdge[] = [];\n\n for (const entry of edgeEntries) {\n const { edge, dagrePoints, src, tgt, edgeColor, priority } = entry;\n\n const { poly } = selectBestVariant(src, dagrePoints, tgt, edge.label, grid);\n\n const dashedBit = edge.style === 'dashed' ? DIR.dashed : 0;\n for (let j = 0; j < poly.length - 1; j++) {\n const a = poly[j]!;\n const b = poly[j + 1]!;\n if (a.y === b.y) {\n grid.markHorizontal(a.y, a.x, b.x, edgeColor, priority, dashedBit);\n } else if (a.x === b.x) {\n grid.markVertical(a.x, a.y, b.y, edgeColor, priority, dashedBit);\n }\n }\n\n drawnEdges.push({ edge, poly, role: entry.role, srcY: src.y });\n }\n\n // --- Pass 2: Place labels (longest first) ---\n const labelOrder = [...drawnEdges]\n .map((de, i) => ({ ...de, i }))\n .filter((de) => de.edge.label)\n .sort((a, b) => (b.edge.label?.length ?? 0) - (a.edge.label?.length ?? 0));\n\n for (const { edge, poly, role, srcY } of labelOrder) {\n /* v8 ignore next -- @preserve */\n if (!edge.label) continue;\n const labelPos = findLabelPlacement(poly, edge.label, grid, srcY);\n if (labelPos) {\n const labelColor =\n resolveHintColor(edge.colorHint, colors) ??\n (role === 'backward' ? colors.backward : role === 'spine' ? colors.spine : colors.label);\n grid.stampText(labelPos.x, labelPos.y, edge.label, labelColor);\n }\n }\n\n // --- Pass 3: Arrowheads ---\n for (const { edge, poly, role } of drawnEdges) {\n /* v8 ignore next -- @preserve */\n if (poly.length < 2) continue;\n const last = poly[poly.length - 1]!;\n const prev = poly[poly.length - 2]!;\n\n const edgeColor =\n resolveHintColor(edge.colorHint, colors) ??\n (role === 'backward' ? colors.backward : role === 'spine' ? colors.spine : colors.branch);\n\n let ax: number | undefined;\n let ay: number | undefined;\n let arrow: string | undefined;\n\n if (prev.x === last.x) {\n if (last.y > prev.y) {\n ax = last.x;\n ay = last.y - 1;\n arrow = ARROW.down;\n } else {\n ax = last.x;\n ay = last.y + 1;\n arrow = ARROW.up;\n }\n } else {\n if (last.x > prev.x) {\n ax = last.x - 1;\n ay = last.y;\n arrow = ARROW.right;\n } else {\n ax = last.x + 1;\n ay = last.y;\n arrow = ARROW.left;\n }\n }\n\n if (ax !== undefined && ay !== undefined && arrow && !grid.hasText(ax, ay)) {\n grid.stampText(ax, ay, arrow, edgeColor);\n }\n }\n\n // --- Draw nodes ---\n const spineNodeIds = new Set<string>();\n for (const key of spineEdgeKeys) {\n const [from, to] = key.split('→');\n if (from) spineNodeIds.add(from);\n if (to) spineNodeIds.add(to);\n }\n\n for (const node of layoutNodes) {\n const pos = nodePos.get(node.id);\n if (!pos) continue;\n\n const isSpineNode = spineNodeIds.has(node.id);\n const nodeColor = isSpineNode ? colors.spine : colors.branch;\n\n grid.stampText(pos.x, pos.y, '○', nodeColor);\n grid.stampText(pos.x + 1, pos.y, ' ');\n const hasMarkers = node.markers && node.markers.length > 0;\n grid.stampText(pos.x + 2, pos.y, node.id, isSpineNode || hasMarkers ? bold : dim);\n\n const tags = buildInlineTags(node.markers ?? [], colors);\n if (tags.length > 0) {\n let bx = pos.x + 2 + node.id.length;\n for (const tag of tags) {\n grid.stampText(bx, pos.y, ' ');\n bx++;\n grid.stampText(bx, pos.y, tag.text, tag.color);\n bx += tag.text.length;\n }\n }\n }\n\n // --- Elided indicator above root ---\n if (elidedCount > 0) {\n const topNodeId =\n layoutNodes.find((n) => !graph.incomingNodes.has(n.id))?.id ?? layoutNodes[0]?.id;\n const rootPos = topNodeId ? nodePos.get(topNodeId) : undefined;\n if (rootPos) {\n const label = elidedCount === 1 ? '1 earlier migration' : `${elidedCount} earlier migrations`;\n const topY = rootPos.y - 3;\n grid.stampText(rootPos.x, topY, '┊', colors.label);\n grid.stampText(rootPos.x, topY + 1, '┊', colors.label);\n grid.stampText(rootPos.x + 2, topY + 1, `(${label})`, colors.label);\n grid.stampText(rootPos.x, topY + 2, '┊', colors.label);\n }\n }\n\n return grid.render();\n}\n\n// ---------------------------------------------------------------------------\n// GraphRenderer implementation\n// ---------------------------------------------------------------------------\n\n/**\n * BFS to find the ordered node path from `rootId` to `targetId`.\n * Used for truncation — the spine path determines which edges to keep.\n *\n * Returns `[rootId]` if no path exists.\n */\nfunction findSpinePath(graph: RenderGraph, rootId: string, targetId: string): string[] {\n const visited = new Set([rootId]);\n const parent = new Map<string, string>();\n const queue = [rootId];\n while (queue.length > 0) {\n const current = queue.shift()!;\n if (current === targetId) {\n const path: string[] = [];\n let node = targetId;\n while (node !== rootId) {\n path.unshift(node);\n node = parent.get(node)!;\n }\n path.unshift(rootId);\n return path;\n }\n for (const edge of graph.outgoing(current)) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n parent.set(edge.to, current);\n queue.push(edge.to);\n }\n }\n }\n return [rootId];\n}\n\n/**\n * Render a graph with optional truncation.\n *\n * The caller decides what to pass in: the full graph for `--graph`, or a\n * subgraph extracted via {@link extractRelevantSubgraph} for the default view.\n */\nfunction render(graph: RenderGraph, options: GraphRenderOptions): string {\n if (options.limit !== undefined) {\n const spine = findSpinePath(\n graph,\n options.rootId ?? graph.nodes[0]?.id ?? '∅',\n options.spineTarget,\n );\n const { graph: truncated, elidedCount } = truncateGraph(graph, spine, options.limit);\n return layoutAndRender(truncated, options, elidedCount);\n }\n return layoutAndRender(graph, options);\n}\n\nexport interface GraphRenderer {\n render(graph: RenderGraph, options: GraphRenderOptions): string;\n}\n\nexport const graphRenderer: GraphRenderer = {\n render,\n};\n\n/** True if the graph is a single linear chain (no branching), ignoring dashed edges. */\nexport function isLinearGraph(graph: RenderGraph): boolean {\n for (const node of graph.nodes) {\n const solidOutgoing = graph.outgoing(node.id).filter((e) => e.style !== 'dashed');\n if (solidOutgoing.length > 1) return false;\n }\n return true;\n}\n","import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport {\n findPath,\n findPathWithDecision,\n findReachableLeaves,\n} from '@prisma-next/migration-tools/dag';\nimport type { Refs } from '@prisma-next/migration-tools/refs';\nimport { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';\nimport type {\n MigrationBundle,\n MigrationChainEntry,\n MigrationGraph,\n} from '@prisma-next/migration-tools/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { cyan, dim, magenta, yellow } from 'colorette';\nimport { Command } from 'commander';\n\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport { type CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n loadAllBundles,\n maskConnectionUrl,\n readContractEnvelope,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n toPathDecisionResult,\n} from '../utils/command-helpers';\nimport {\n type EdgeStatus,\n type EdgeStatusKind,\n migrationGraphToRenderInput,\n} from '../utils/formatters/graph-migration-mapper';\nimport {\n extractRelevantSubgraph,\n graphRenderer,\n isLinearGraph,\n} from '../utils/formatters/graph-render';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport type { StatusDiagnostic, StatusRef } from '../utils/migration-types';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationStatusOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly ref?: string;\n readonly graph?: boolean;\n readonly limit?: string;\n readonly all?: boolean;\n}\n\nexport interface MigrationStatusEntry {\n readonly dirName: string;\n readonly from: string;\n readonly to: string;\n readonly migrationId: string;\n readonly operationCount: number;\n readonly operationSummary: string;\n readonly hasDestructive: boolean;\n readonly status: EdgeStatusKind | 'unknown';\n}\n\nexport type { StatusDiagnostic, StatusRef } from '../utils/migration-types';\n\nexport interface MigrationStatusResult {\n readonly ok: true;\n readonly mode: 'online' | 'offline';\n readonly migrations: readonly MigrationStatusEntry[];\n readonly markerHash?: string;\n readonly targetHash: string;\n readonly contractHash: string;\n readonly refs?: readonly StatusRef[];\n readonly pathDecision?: {\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n readonly selectedPath: readonly {\n readonly dirName: string;\n readonly migrationId: string;\n readonly from: string;\n readonly to: string;\n }[];\n };\n readonly summary: string;\n readonly diagnostics: readonly StatusDiagnostic[];\n readonly graph?: MigrationGraph;\n readonly bundles?: readonly MigrationBundle[];\n readonly edgeStatuses?: readonly EdgeStatus[];\n readonly activeRefHash?: string;\n readonly activeRefName?: string;\n readonly diverged?: boolean;\n}\n\nfunction summarizeOps(ops: readonly MigrationPlanOperation[]): {\n summary: string;\n hasDestructive: boolean;\n} {\n if (ops.length === 0) return { summary: '0 ops', hasDestructive: false };\n\n const classes = new Map<string, number>();\n for (const op of ops) {\n classes.set(op.operationClass, (classes.get(op.operationClass) ?? 0) + 1);\n }\n\n const hasDestructive = classes.has('destructive');\n const count = ops.length;\n const noun = count === 1 ? 'op' : 'ops';\n\n if (classes.size === 1) {\n const cls = [...classes.keys()][0]!;\n return { summary: `${count} ${noun} (all ${cls})`, hasDestructive };\n }\n\n const destructiveCount = classes.get('destructive');\n if (destructiveCount) {\n return { summary: `${count} ${noun} (${destructiveCount} destructive)`, hasDestructive };\n }\n\n const parts = [...classes.entries()].map(([cls, n]) => `${n} ${cls}`);\n return { summary: `${count} ${noun} (${parts.join(', ')})`, hasDestructive };\n}\n\n/**\n * Derive per-edge status across the full graph using path analysis.\n *\n * - **applied**: edge is on the path from root to the DB marker\n * - **pending**: edge is on the path from the DB marker to the target\n * (and the marker is reachable from root, i.e. it's on the same branch)\n * - **unreachable**: edge is on the path from root to the target but the DB\n * marker is on a different branch — `apply` can't reach these edges\n * without the DB first moving to this branch\n *\n * Returns statuses only for edges that have a known status (skips offline\n * and edges not on any relevant path).\n *\n * @internal Exported for testing only.\n */\nexport function deriveEdgeStatuses(\n graph: MigrationGraph,\n targetHash: string,\n contractHash: string,\n markerHash: string | undefined,\n mode: 'online' | 'offline',\n): EdgeStatus[] {\n if (mode === 'offline') return [];\n\n const edgeKey = (e: MigrationChainEntry) => `${e.from}\\0${e.to}`;\n\n // No marker = empty DB — treat root as the marker (nothing applied, everything pending)\n const effectiveMarker = markerHash ?? EMPTY_CONTRACT_HASH;\n\n const appliedPath =\n markerHash !== undefined ? findPath(graph, EMPTY_CONTRACT_HASH, markerHash) : null;\n\n const pendingPath = findPath(graph, effectiveMarker, targetHash);\n const targetPath = findPath(graph, EMPTY_CONTRACT_HASH, targetHash);\n\n const statuses: EdgeStatus[] = [];\n const assignedKeys = new Set<string>();\n\n // Applied edges (root → marker)\n if (appliedPath) {\n for (const e of appliedPath) {\n assignedKeys.add(edgeKey(e));\n statuses.push({ dirName: e.dirName, status: 'applied' });\n }\n }\n\n // Pending edges (marker → target)\n if (pendingPath) {\n for (const e of pendingPath) {\n assignedKeys.add(edgeKey(e));\n statuses.push({ dirName: e.dirName, status: 'pending' });\n }\n }\n\n // Pending edges beyond the target: target → contract (when target is a ref\n // and the contract is reachable from it)\n if (\n contractHash !== EMPTY_CONTRACT_HASH &&\n contractHash !== targetHash &&\n graph.nodes.has(contractHash)\n ) {\n const beyondTarget = findPath(graph, targetHash, contractHash);\n if (beyondTarget) {\n for (const e of beyondTarget) {\n if (!assignedKeys.has(edgeKey(e))) {\n assignedKeys.add(edgeKey(e));\n statuses.push({ dirName: e.dirName, status: 'pending' });\n }\n }\n }\n }\n\n // Unreachable edges: on the path from root to the target but neither applied\n // nor pending. This covers two cases:\n // 1. Marker can't reach target at all (different branch entirely)\n // 2. Marker reaches target via a different route, leaving some root→target\n // edges orphaned (e.g. a fork where one branch was applied and apply\n // will continue through the other)\n if (targetPath) {\n for (const e of targetPath) {\n if (!assignedKeys.has(edgeKey(e))) {\n statuses.push({ dirName: e.dirName, status: 'unreachable' });\n }\n }\n }\n\n return statuses;\n}\n\n/**\n * @param mode — 'online' if we connected to the database, 'offline' otherwise\n * @param markerHash — the marker hash from the database, or undefined if no marker row / offline\n */\nfunction buildMigrationEntries(\n chain: readonly MigrationChainEntry[],\n packages: readonly MigrationBundle[],\n mode: 'online' | 'offline',\n markerHash: string | undefined,\n edgeStatuses?: readonly EdgeStatus[],\n): MigrationStatusEntry[] {\n const pkgByDirName = new Map(packages.map((p) => [p.dirName, p]));\n const statusByDirName = edgeStatuses\n ? new Map(edgeStatuses.map((e) => [e.dirName, e.status]))\n : undefined;\n\n const markerInChain = markerHash === undefined || chain.some((e) => e.to === markerHash);\n\n const entries: MigrationStatusEntry[] = [];\n let reachedMarker = mode === 'online' && markerHash === undefined;\n\n for (const migration of chain) {\n const pkg = pkgByDirName.get(migration.dirName);\n const ops = (pkg?.ops ?? []) as readonly MigrationPlanOperation[];\n const { summary, hasDestructive } = summarizeOps(ops);\n\n let status: EdgeStatusKind | 'unknown';\n const edgeStatus = statusByDirName?.get(migration.dirName);\n if (edgeStatus) {\n status = edgeStatus;\n } else if (mode === 'offline' || !markerInChain) {\n status = 'unknown';\n } else if (reachedMarker) {\n status = 'pending';\n } else {\n status = 'applied';\n }\n\n entries.push({\n dirName: migration.dirName,\n from: migration.from,\n to: migration.to,\n migrationId: migration.migrationId,\n operationCount: ops.length,\n operationSummary: summary,\n hasDestructive,\n status,\n });\n\n if (!reachedMarker && migration.to === markerHash) {\n reachedMarker = true;\n }\n }\n\n return entries;\n}\n\n/**\n * Resolve the migration chain to display in status output.\n *\n * When offline or the marker is at EMPTY, the chain is simply the shortest\n * path from EMPTY to the target — all structural paths are equivalent per\n * the spec, so the deterministic shortest path is the canonical display.\n *\n * When online with a non-empty marker, the chain routes *through* the marker:\n * EMPTY→marker (applied history) + marker→target (pending edges). This ensures\n * the displayed chain includes the marker node so applied/pending status is\n * correct. Without this, BFS from EMPTY to target could pick a shortest path\n * that bypasses the marker entirely (e.g. in a diamond graph), causing the\n * marker to appear \"diverged\" when it isn't.\n */\nfunction resolveDisplayChain(\n graph: MigrationGraph,\n targetHash: string,\n markerHash: string | undefined,\n): readonly MigrationChainEntry[] | null {\n if (markerHash === undefined) {\n return findPath(graph, EMPTY_CONTRACT_HASH, targetHash);\n }\n\n const toMarker = findPath(graph, EMPTY_CONTRACT_HASH, markerHash);\n // Marker unreachable from EMPTY — show the target chain anyway.\n // The caller detects this via markerInChain and emits a divergence diagnostic.\n if (!toMarker) return findPath(graph, EMPTY_CONTRACT_HASH, targetHash);\n\n if (markerHash === targetHash) return toMarker;\n\n const fromMarker = findPath(graph, markerHash, targetHash);\n if (fromMarker) return [...toMarker, ...fromMarker];\n\n // Marker is ahead of target (or on a disconnected branch).\n // Try the inverse: target→marker. If it succeeds, the marker is ahead —\n // show the full chain from EMPTY through the target and on to the marker.\n const toTarget = findPath(graph, EMPTY_CONTRACT_HASH, targetHash);\n if (!toTarget) return null;\n\n const targetToMarker = findPath(graph, targetHash, markerHash);\n if (targetToMarker) return [...toTarget, ...targetToMarker];\n\n // Genuinely disconnected — show EMPTY→target; caller handles divergence diagnostic.\n return toTarget;\n}\n\nconst DEFAULT_LIMIT = 10;\n\nfunction determineLimit(opts: MigrationStatusOptions) {\n if (opts.all) {\n // No limit\n return;\n }\n if (!opts.limit) {\n return DEFAULT_LIMIT;\n }\n const parsed = Number.parseInt(opts.limit, 10);\n if (Number.isNaN(parsed)) {\n return DEFAULT_LIMIT;\n }\n return parsed;\n}\n\nasync function executeMigrationStatusCommand(\n options: MigrationStatusOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrationStatusResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n const hasDriver = !!config.driver;\n\n let activeRefName: string | undefined;\n let activeRefHash: string | undefined;\n let allRefs: Refs = {};\n try {\n allRefs = await readRefs(refsPath);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(\n errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code },\n }),\n );\n }\n throw error;\n }\n\n if (options.ref) {\n activeRefName = options.ref;\n const refHash = allRefs[activeRefName];\n if (refHash) {\n activeRefHash = refHash;\n } else {\n try {\n activeRefHash = resolveRef(allRefs, activeRefName);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(\n errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code },\n }),\n );\n }\n throw error;\n }\n }\n }\n\n const statusRefs: StatusRef[] = Object.entries(allRefs).map(([name, hash]) => ({\n name,\n hash,\n active: name === activeRefName,\n }));\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (dbConnection && hasDriver) {\n details.push({ label: 'database', value: maskConnectionUrl(String(dbConnection)) });\n }\n if (activeRefName) {\n details.push({ label: 'ref', value: activeRefName });\n }\n const header = formatStyledHeader({\n command: 'migration status',\n description: 'Show migration history and applied status',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n const diagnostics: StatusDiagnostic[] = [];\n let contractHash: string = EMPTY_CONTRACT_HASH;\n try {\n const envelope = await readContractEnvelope(config);\n contractHash = envelope.storageHash;\n } catch (error) {\n diagnostics.push({\n code: 'CONTRACT.UNREADABLE',\n severity: 'warn',\n message: `Could not read contract: ${error instanceof Error ? error.message : 'unknown error'}`,\n hints: [\"Run 'prisma-next contract emit' to generate a valid contract\"],\n });\n }\n\n let bundles: readonly MigrationBundle[];\n let graph: MigrationGraph;\n try {\n ({ bundles, graph } = await loadAllBundles(migrationsDir));\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(\n errorRuntime(error.message, { why: error.why, fix: error.fix, meta: { code: error.code } }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n\n if (bundles.length === 0) {\n if (contractHash !== EMPTY_CONTRACT_HASH) {\n diagnostics.push({\n code: 'CONTRACT.AHEAD',\n severity: 'warn',\n message: 'No migration exists for the current contract',\n hints: [\n \"Run 'prisma-next migration plan' to generate a migration for the current contract\",\n ],\n });\n }\n return ok({\n ok: true,\n mode: dbConnection && hasDriver ? 'online' : 'offline',\n migrations: [],\n targetHash: EMPTY_CONTRACT_HASH,\n contractHash,\n summary: 'No migrations found',\n diagnostics,\n });\n }\n\n let targetHash: string | undefined;\n\n if (activeRefHash) {\n targetHash = activeRefHash;\n } else if (graph.nodes.has(contractHash)) {\n targetHash = contractHash;\n } else {\n const leaves = findReachableLeaves(graph, EMPTY_CONTRACT_HASH);\n if (leaves.length === 1) {\n targetHash = leaves[0];\n } else {\n diagnostics.push({\n code: 'MIGRATION.DIVERGED',\n severity: 'warn',\n message: 'There are multiple valid migration paths — you must select a target',\n hints: [\n \"Use '--ref <name>' to select a target\",\n \"Or 'prisma-next migration ref set <name> <hash>' to create one\",\n ],\n });\n }\n }\n\n let markerHash: string | undefined;\n let mode: 'online' | 'offline' = 'offline';\n\n if (dbConnection && hasDriver) {\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n try {\n await client.connect(dbConnection);\n markerHash = (await client.readMarker())?.storageHash;\n mode = 'online';\n } catch {\n if (!flags.json && !flags.quiet) {\n ui.warn('Could not connect to database — showing offline status');\n }\n } finally {\n await client.close();\n }\n }\n\n // Marker exists but is not in the migration graph and doesn't match the\n // contract hash. The DB is at an unknown state relative to the graph.\n // Bail out early with a clear diagnostic instead of rendering a confusing\n // graph with no statuses.\n //\n // When marker === contract (both off-graph), the DB matches the current\n // contract — proceed normally; the detached contract node will carry both\n // the db and contract markers.\n if (\n mode === 'online' &&\n markerHash !== undefined &&\n !graph.nodes.has(markerHash) &&\n markerHash !== contractHash\n ) {\n const hints: string[] = [];\n if (graph.nodes.has(contractHash)) {\n hints.push(\n \"Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract\",\n \"Run 'prisma-next db update' to push the current contract to the database\",\n \"Run 'prisma-next contract infer' to make your contract match the database\",\n \"Run 'prisma-next db verify' to inspect the database state\",\n );\n } else {\n hints.push(\n \"Run 'prisma-next db update' to push the current contract to the database\",\n \"Run 'prisma-next contract infer' to make your contract match the database\",\n \"Run 'prisma-next db verify' to inspect the database state\",\n );\n }\n diagnostics.push({\n code: 'MIGRATION.MARKER_NOT_IN_HISTORY',\n severity: 'warn',\n message:\n 'Database was updated outside the migration system (marker does not match any migration)',\n hints,\n });\n return ok({\n ok: true,\n mode,\n migrations: [],\n targetHash: EMPTY_CONTRACT_HASH,\n contractHash,\n summary: `${bundles.length} migration(s) on disk`,\n diagnostics,\n markerHash,\n ...(statusRefs.length > 0 ? { refs: statusRefs } : {}),\n });\n }\n\n if (mode === 'online' && markerHash === undefined) {\n diagnostics.push({\n code: 'MIGRATION.NO_MARKER',\n severity: 'warn',\n message: 'Database has not been initialized — no migration marker found',\n hints: [\"Run 'prisma-next migration apply' to apply pending migrations\"],\n });\n }\n\n // Contract diagnostic — fires when no migration produces the current contract hash.\n // Suppressed when: (a) graph is diverged (MIGRATION.DIVERGED already guides the user),\n // (b) marker === contract and both off-graph (marker-not-in-graph diagnostic covers it).\n if (\n targetHash &&\n contractHash !== EMPTY_CONTRACT_HASH &&\n !graph.nodes.has(contractHash) &&\n markerHash !== contractHash\n ) {\n diagnostics.push({\n code: 'CONTRACT.AHEAD',\n severity: 'warn',\n message: 'Contract has changed since the last migration was planned',\n hints: [\"Run 'prisma-next migration plan' to generate a migration for the current contract\"],\n });\n }\n\n if (!targetHash) {\n return ok({\n ok: true,\n mode,\n migrations: [],\n targetHash: EMPTY_CONTRACT_HASH,\n contractHash,\n summary: `${bundles.length} migration(s) on disk`,\n diagnostics,\n ...ifDefined('markerHash', markerHash),\n ...(statusRefs.length > 0 ? { refs: statusRefs } : {}),\n graph,\n bundles,\n diverged: true,\n });\n }\n\n const chain = resolveDisplayChain(graph, targetHash, markerHash);\n\n if (!chain) {\n return notOk(\n errorRuntime('Cannot reconstruct migration history', {\n why: `No path from ${EMPTY_CONTRACT_HASH} to target ${targetHash}`,\n fix: 'The migration history may have gaps. Check the migrations directory for missing or corrupted packages.',\n }),\n );\n }\n\n const edgeStatuses = deriveEdgeStatuses(graph, targetHash, contractHash, markerHash, mode);\n const entries = buildMigrationEntries(chain, bundles, mode, markerHash, edgeStatuses);\n\n const pendingCount = edgeStatuses.filter((e) => e.status === 'pending').length;\n const appliedCount = edgeStatuses.filter((e) => e.status === 'applied').length;\n\n let summary: string;\n if (mode === 'online') {\n if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {\n summary = `${bundles.length} migration(s) on disk`;\n } else if (activeRefHash && markerHash !== undefined) {\n summary = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName!);\n } else if (pendingCount === 0) {\n summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? 's' : ''} applied)`;\n } else if (markerHash === undefined) {\n summary = `${pendingCount} pending migration(s) — database has no marker`;\n } else {\n summary = `${pendingCount} pending migration(s) — run 'prisma-next migration apply' to apply`;\n }\n } else {\n summary = `${entries.length} migration(s) on disk`;\n }\n\n if (mode === 'online') {\n if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {\n diagnostics.push({\n code: 'MIGRATION.MARKER_NOT_IN_HISTORY',\n severity: 'warn',\n message:\n 'Database matches the current contract but was updated directly (not via migration apply)',\n hints: [\"Run 'prisma-next migration plan' to plan a migration to your current contract\"],\n });\n } else if (pendingCount > 0) {\n diagnostics.push({\n code: 'MIGRATION.DATABASE_BEHIND',\n severity: 'info',\n message: `${pendingCount} migration(s) pending`,\n hints: [\"Run 'prisma-next migration apply' to apply pending migrations\"],\n });\n } else {\n diagnostics.push({\n code: 'MIGRATION.UP_TO_DATE',\n severity: 'info',\n message: 'Database is up to date',\n hints: [],\n });\n }\n }\n\n let pathDecision: MigrationStatusResult['pathDecision'];\n if (mode === 'online' && markerHash !== undefined) {\n const decision = findPathWithDecision(graph, markerHash, targetHash, activeRefName);\n if (decision) {\n pathDecision = toPathDecisionResult(decision);\n }\n }\n\n const result: MigrationStatusResult = {\n ok: true,\n mode,\n migrations: entries,\n targetHash,\n contractHash,\n summary,\n diagnostics,\n ...ifDefined('markerHash', markerHash),\n ...(statusRefs.length > 0 ? { refs: statusRefs } : {}),\n ...ifDefined('pathDecision', pathDecision),\n graph,\n bundles,\n edgeStatuses,\n ...ifDefined('activeRefHash', activeRefHash),\n ...ifDefined('activeRefName', activeRefName),\n };\n return ok(result);\n}\n\nexport function createMigrationStatusCommand(): Command {\n const command = new Command('status');\n setCommandDescriptions(\n command,\n 'Show migration history and applied status',\n 'Displays the migration history in order. When a database connection\\n' +\n 'is available, shows which migrations are applied and which are pending.\\n' +\n 'Without a database connection, shows the history from disk only.',\n );\n setCommandExamples(command, [\n 'prisma-next migration status',\n 'prisma-next migration status --db $DATABASE_URL',\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--ref <name>', 'Target ref name from migrations/refs/')\n .option('--graph', 'Show the full migration graph with all branches')\n .option('--limit <n>', 'Maximum number of migrations to display (default: 10)')\n .option('--all', 'Show full history (disables truncation)')\n .action(async (options: MigrationStatusOptions) => {\n const flags = parseGlobalFlags(options);\n\n const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });\n\n const result = await executeMigrationStatusCommand(options, flags, ui);\n\n const exitCode = handleResult(result, flags, ui, (statusResult) => {\n if (flags.json) {\n const {\n graph: _g,\n bundles: _b,\n edgeStatuses: _es,\n activeRefHash: _arh,\n activeRefName: _arn,\n diverged: _d,\n ...jsonResult\n } = statusResult;\n ui.output(JSON.stringify(jsonResult, null, 2));\n } else if (!flags.quiet) {\n const colorize = flags.color !== false;\n\n if (statusResult.graph) {\n const limit = determineLimit(options);\n const renderInput = migrationGraphToRenderInput({\n graph: statusResult.graph,\n mode: statusResult.mode,\n markerHash: statusResult.markerHash,\n contractHash: statusResult.contractHash,\n refs: statusResult.refs,\n activeRefHash: statusResult.activeRefHash,\n activeRefName: statusResult.activeRefName,\n edgeStatuses: statusResult.edgeStatuses,\n });\n\n const graphToRender =\n options.graph || statusResult.diverged\n ? renderInput.graph\n : extractRelevantSubgraph(renderInput.graph, renderInput.relevantPaths);\n const dagreOptions =\n !options.graph && isLinearGraph(graphToRender) ? { ranksep: 1 } : undefined;\n const renderOptions = {\n ...renderInput.options,\n colorize,\n ...ifDefined('limit', limit),\n ...ifDefined('dagreOptions', dagreOptions),\n };\n const graphOutput = graphRenderer.render(graphToRender, renderOptions);\n ui.log(graphOutput);\n if (statusResult.mode === 'online') {\n ui.log(formatLegend(colorize));\n }\n }\n ui.log('');\n ui.log(formatStatusSummary(statusResult, colorize));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n\nfunction formatLegend(colorize: boolean): string {\n const c = (fn: (s: string) => string, s: string) => (colorize ? fn(s) : s);\n const parts = [\n `${c(cyan, '✓')} applied`,\n `${c(yellow, '⧗')} pending`,\n `${c(magenta, '✗')} unreachable`,\n ];\n return c(dim, parts.join(' '));\n}\n\nfunction formatStatusSummary(result: MigrationStatusResult, colorize: boolean): string {\n const c = (fn: (s: string) => string, s: string) => (colorize ? fn(s) : s);\n const lines: string[] = [];\n\n const hasUnknown = result.migrations.some((e) => e.status === 'unknown');\n const pendingCount = result.migrations.filter((e) => e.status === 'pending').length;\n\n const hasWarnings = result.diagnostics?.some((d) => d.severity === 'warn') ?? false;\n\n if (result.mode === 'online') {\n if (hasUnknown || hasWarnings) {\n lines.push(`${c(yellow, '⚠')} ${result.summary}`);\n } else if (pendingCount === 0) {\n lines.push(`${c(cyan, '✔')} ${result.summary}`);\n } else {\n lines.push(`${c(yellow, '⧗')} ${result.summary}`);\n }\n } else {\n lines.push(result.summary);\n }\n\n const warnings = result.diagnostics?.filter((d) => d.severity === 'warn') ?? [];\n for (const diag of warnings) {\n lines.push(`${c(yellow, '⚠')} ${diag.message}`);\n for (const hint of diag.hints) {\n lines.push(` ${c(dim, hint)}`);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction summarizeRefDistance(\n graph: MigrationGraph,\n markerHash: string,\n refHash: string,\n refName: string,\n): string {\n if (markerHash === refHash) return `At ref \"${refName}\" target`;\n\n const pathToRef = findPath(graph, markerHash, refHash);\n if (pathToRef) return `${pathToRef.length} migration(s) behind ref \"${refName}\"`;\n\n const pathFromRef = findPath(graph, refHash, markerHash);\n if (pathFromRef) return `${pathFromRef.length} migration(s) ahead of ref \"${refName}\"`;\n\n return `No path between database marker and ref \"${refName}\" target`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"migrations-B0dOQlk0.mjs","names":["lines: string[]"],"sources":["../src/utils/formatters/migrations.ts"],"sourcesContent":["import { green, yellow } from 'colorette';\n\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim, isVerbose } from './helpers';\n\n// ============================================================================\n// Migration Command Output Formatters (shared by db init and db update)\n// ============================================================================\n\n/**\n * Shared CLI output type for migration commands (db init, db update).\n */\nexport interface MigrationCommandResult {\n readonly ok: true;\n readonly mode: 'plan' | 'apply';\n readonly plan: {\n readonly targetId: string;\n readonly destination: {\n readonly storageHash: string;\n readonly profileHash?: string;\n };\n readonly operations: readonly {\n readonly id: string;\n readonly label: string;\n readonly operationClass: string;\n }[];\n readonly sql?: readonly string[];\n };\n readonly execution?: {\n readonly operationsPlanned: number;\n readonly operationsExecuted: number;\n };\n readonly marker?: {\n readonly storageHash: string;\n readonly profileHash?: string;\n };\n readonly summary: string;\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for migration commands (db init, db update) in plan mode.\n */\nexport function formatMigrationPlanOutput(\n result: MigrationCommandResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Plan summary\n const operationCount = result.plan?.operations.length ?? 0;\n lines.push(`${formatGreen('✔')} Planned ${operationCount} operation(s)`);\n\n // Show operations tree\n if (result.plan?.operations && result.plan.operations.length > 0) {\n const formatYellow = createColorFormatter(useColor, yellow);\n lines.push(`${formatDimText('│')}`);\n for (let i = 0; i < result.plan.operations.length; i++) {\n const op = result.plan.operations[i];\n if (!op) continue;\n const isLast = i === result.plan.operations.length - 1;\n const treeChar = isLast ? '└' : '├';\n const opClassLabel =\n op.operationClass === 'destructive'\n ? formatYellow(`[${op.operationClass}]`)\n : formatDimText(`[${op.operationClass}]`);\n lines.push(`${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);\n }\n\n const hasDestructive = result.plan.operations.some((op) => op.operationClass === 'destructive');\n if (hasDestructive) {\n lines.push('');\n lines.push(\n `${formatYellow('⚠')} This migration contains destructive operations that may cause data loss.`,\n );\n }\n }\n\n // Destination hash\n if (result.plan?.destination) {\n lines.push('');\n lines.push(`${formatDimText(`Destination hash: ${result.plan.destination.storageHash}`)}`);\n }\n\n // SQL DDL preview (SQL family only)\n const planSql = result.plan?.sql;\n if (planSql) {\n lines.push('');\n lines.push(`${formatDimText('DDL preview')}`);\n if (planSql.length === 0) {\n lines.push(`${formatDimText('No DDL operations.')}`);\n } else {\n lines.push('');\n for (const statement of planSql) {\n const trimmed = statement.trim();\n if (!trimmed) continue;\n const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;\n lines.push(`${line}`);\n }\n }\n }\n\n // Timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(`Total time: ${result.timings.total}ms`)}`);\n }\n\n // Note about dry run\n lines.push('');\n lines.push(`${formatDimText('This is a dry run. No changes were applied.')}`);\n lines.push(`${formatDimText('Run without --dry-run to apply changes.')}`);\n\n return lines.join('\\n');\n}\n\nexport interface MigrationApplyCommandOutputResult {\n readonly migrationsApplied: number;\n readonly markerHash: string;\n readonly applied: readonly {\n readonly dirName: string;\n readonly operationsExecuted: number;\n }[];\n readonly summary: string;\n readonly timings?: {\n readonly total: number;\n };\n}\n\nexport function formatMigrationApplyCommandOutput(\n result: MigrationApplyCommandOutputResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (result.migrationsApplied === 0) {\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n lines.push(formatDimText(` marker: ${result.markerHash}`));\n return lines.join('\\n');\n }\n\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n lines.push('');\n\n for (let i = 0; i < result.applied.length; i++) {\n const migration = result.applied[i]!;\n const isLast = i === result.applied.length - 1;\n const treeChar = isLast ? '└' : '├';\n lines.push(\n `${formatDimText(treeChar)}─ ${migration.dirName} ${formatDimText(`[${migration.operationsExecuted} op(s)]`)}`,\n );\n }\n\n lines.push('');\n lines.push(formatDimText(`marker: ${result.markerHash}`));\n\n if (isVerbose(flags, 1) && result.timings) {\n lines.push('');\n lines.push(formatDimText(`Total time: ${result.timings.total}ms`));\n }\n\n return lines.join('\\n');\n}\n\ninterface MigrationShowResult {\n readonly dirName: string;\n readonly dirPath: string;\n readonly from: string;\n readonly to: string;\n readonly migrationId: string;\n readonly kind: string;\n readonly createdAt: string;\n readonly operations: readonly {\n readonly id: string;\n readonly label: string;\n readonly operationClass: string;\n }[];\n readonly sql: readonly string[];\n readonly summary: string;\n}\n\nexport function formatMigrationShowOutput(result: MigrationShowResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n lines.push(`${formatGreen('✔')} ${result.dirName}`);\n lines.push(`${formatDimText(` kind: ${result.kind}`)}`);\n lines.push(`${formatDimText(` from: ${result.from}`)}`);\n lines.push(`${formatDimText(` to: ${result.to}`)}`);\n lines.push(`${formatDimText(` migrationId: ${result.migrationId}`)}`);\n lines.push(`${formatDimText(` created: ${result.createdAt}`)}`);\n\n lines.push('');\n lines.push(`${result.operations.length} operation(s)`);\n\n if (result.operations.length > 0) {\n lines.push(`${formatDimText('│')}`);\n for (let i = 0; i < result.operations.length; i++) {\n const op = result.operations[i]!;\n const isLast = i === result.operations.length - 1;\n const treeChar = isLast ? '└' : '├';\n const opClassLabel =\n op.operationClass === 'destructive'\n ? formatYellow(`[${op.operationClass}]`)\n : formatDimText(`[${op.operationClass}]`);\n lines.push(`${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);\n }\n\n const hasDestructive = result.operations.some((op) => op.operationClass === 'destructive');\n if (hasDestructive) {\n lines.push('');\n lines.push(\n `${formatYellow('⚠')} This migration contains destructive operations that may cause data loss.`,\n );\n }\n }\n\n if (result.sql.length > 0) {\n lines.push('');\n lines.push(`${formatDimText('DDL preview')}`);\n lines.push('');\n for (const statement of result.sql) {\n const trimmed = statement.trim();\n if (!trimmed) continue;\n const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;\n lines.push(`${line}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats human-readable output for migration commands (db init, db update) in apply mode.\n */\nexport function formatMigrationApplyOutput(\n result: MigrationCommandResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (result.ok) {\n // Success summary\n const executed = result.execution?.operationsExecuted ?? 0;\n if (executed === 0) {\n lines.push(`${formatGreen('✔')} Database already matches contract`);\n } else {\n lines.push(`${formatGreen('✔')} Applied ${executed} operation(s)`);\n }\n\n // Marker info\n if (result.marker) {\n lines.push(`${formatDimText(` Signature: ${result.marker.storageHash}`)}`);\n if (result.marker.profileHash) {\n lines.push(`${formatDimText(` Profile hash: ${result.marker.profileHash}`)}`);\n }\n }\n\n // Timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for migration commands (db init, db update).\n */\nexport function formatMigrationJson(result: MigrationCommandResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"result-handler-CIyu0Pdt.mjs","names":["LEFT_COLUMN_WIDTH","createPrismaNextBadge","formatHeaderLine","formatReadMoreLine","lines: string[]","lines: string[]","parts: string[]","current: Command | undefined","flags: {\n json?: boolean;\n quiet?: boolean;\n verbose?: number;\n color?: boolean;\n interactive?: boolean;\n yes?: boolean;\n }","lines: string[]"],"sources":["../src/utils/formatters/helpers.ts","../src/utils/formatters/styled.ts","../src/utils/formatters/help.ts","../src/utils/global-flags.ts","../src/utils/command-helpers.ts","../src/utils/formatters/errors.ts","../src/utils/result-handler.ts"],"sourcesContent":["import { dim } from 'colorette';\n\nimport type { GlobalFlags } from '../global-flags';\n\n/**\n * Checks if verbose output is enabled at the specified level.\n */\nexport function isVerbose(flags: GlobalFlags, level: 1 | 2): boolean {\n return (flags.verbose ?? 0) >= level;\n}\n\n/**\n * Creates a color-aware formatter function.\n * Returns a function that applies the color only if colors are enabled.\n */\nexport function createColorFormatter<T extends (text: string) => string>(\n useColor: boolean,\n colorFn: T,\n): (text: string) => string {\n return useColor ? colorFn : (text: string) => text;\n}\n\n/**\n * Formats text with dim styling if colors are enabled.\n */\nexport function formatDim(useColor: boolean, text: string): string {\n return useColor ? dim(text) : text;\n}\n","import { blue, bold, cyan, green } from 'colorette';\nimport type { Command } from 'commander';\nimport stringWidth from 'string-width';\nimport stripAnsi from 'strip-ansi';\n\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim } from './helpers';\n\n// ============================================================================\n// Styled Output Formatters\n// ============================================================================\n\n/**\n * Fixed width for left column in help output.\n */\nconst LEFT_COLUMN_WIDTH = 20;\n\n/**\n * Creates an arrow segment badge with green background and white text.\n * Body: green background with white \"prisma-next\" text\n * Tip: dark grey arrow pointing right (Powerline separator)\n */\nfunction createPrismaNextBadge(useColor: boolean): string {\n if (!useColor) {\n return 'prisma-next';\n }\n return bold('prisma-next');\n}\n\n/**\n * Creates a padding function.\n */\nfunction createPadFunction(): (s: string, w: number) => string {\n return (s: string, w: number) => s + ' '.repeat(Math.max(0, w - s.length));\n}\n\n/**\n * Formats a header line: brand + operation + intent\n */\nfunction formatHeaderLine(options: {\n readonly brand: string;\n readonly operation: string;\n readonly intent: string;\n}): string {\n if (options.operation) {\n return `${options.brand} ${options.operation} → ${options.intent}`;\n }\n return `${options.brand} ${options.intent}`;\n}\n\n/**\n * Formats a \"Read more\" URL line.\n * The \"Read more\" label is in default color (not cyan), and the URL is blue.\n */\nfunction formatReadMoreLine(options: {\n readonly url: string;\n readonly maxLabelWidth: number;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n}): string {\n const pad = createPadFunction();\n const labelPadded = pad('Read more', options.maxLabelWidth);\n // Label is default color (not cyan)\n const valueColored = options.useColor ? blue(options.url) : options.url;\n return `${options.formatDimText('│')} ${labelPadded} ${valueColored}`;\n}\n\n/**\n * Pads text to a fixed width, accounting for ANSI escape codes.\n * Uses string-width to measure the actual display width.\n */\nexport function padToFixedWidth(text: string, width: number): string {\n const actualWidth = stringWidth(text);\n const padding = Math.max(0, width - actualWidth);\n return text + ' '.repeat(padding);\n}\n\n/**\n * Renders a command tree structure.\n * Handles both single-level (subcommands of a command) and multi-level (top-level commands with subcommands) trees.\n */\nexport function renderCommandTree(options: {\n readonly commands: readonly Command[];\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly hasItemsAfter: boolean;\n readonly continuationPrefix?: string;\n}): string[] {\n const { commands, useColor, formatDimText, hasItemsAfter, continuationPrefix } = options;\n const lines: string[] = [];\n\n if (commands.length === 0) {\n return lines;\n }\n\n // Format each command\n for (let i = 0; i < commands.length; i++) {\n const cmd = commands[i];\n if (!cmd) continue;\n\n const subcommands = cmd.commands.filter((subcmd) => !subcmd.name().startsWith('_'));\n const isLastCommand = i === commands.length - 1;\n\n if (subcommands.length > 0) {\n // Command with subcommands - show command name, then tree-structured subcommands\n const treeChar = isLastCommand && !hasItemsAfter ? formatDimText('└') : formatDimText('├');\n // For top-level command, pad name to fixed width (accounting for \"| |-- \" = 5 chars)\n const treePrefix = `${treeChar}─ `;\n const treePrefixWidth = stringWidth(stripAnsi(treePrefix));\n const remainingWidth = LEFT_COLUMN_WIDTH - treePrefixWidth;\n const commandNamePadded = padToFixedWidth(cmd.name(), remainingWidth);\n const commandNameColored = useColor ? cyan(commandNamePadded) : commandNamePadded;\n lines.push(`${formatDimText('│')} ${treePrefix}${commandNameColored}`);\n\n for (let j = 0; j < subcommands.length; j++) {\n const subcmd = subcommands[j];\n if (!subcmd) continue;\n\n const isLastSubcommand = j === subcommands.length - 1;\n const shortDescription = subcmd.description() || '';\n\n // Use tree characters: -- for last subcommand, |-- for others\n const treeChar = isLastSubcommand ? '└' : '├';\n const continuation =\n continuationPrefix ??\n (isLastCommand && isLastSubcommand && !hasItemsAfter ? ' ' : formatDimText('│'));\n // For subcommands, account for \"| | -- \" = 7 chars (or \"| -- \" = 6 chars if continuation is space)\n const continuationStr = continuation === ' ' ? ' ' : continuation;\n const subTreePrefix = `${continuationStr} ${formatDimText(treeChar)}─ `;\n const subTreePrefixWidth = stringWidth(stripAnsi(subTreePrefix));\n const subRemainingWidth = LEFT_COLUMN_WIDTH - subTreePrefixWidth;\n const subcommandNamePadded = padToFixedWidth(subcmd.name(), subRemainingWidth);\n const subcommandNameColored = useColor ? cyan(subcommandNamePadded) : subcommandNamePadded;\n lines.push(\n `${formatDimText('│')} ${subTreePrefix}${subcommandNameColored} ${shortDescription}`,\n );\n }\n } else {\n // Standalone command - show command name and description on same line\n const treeChar = isLastCommand && !hasItemsAfter ? formatDimText('└') : formatDimText('├');\n const treePrefix = `${treeChar}─ `;\n const treePrefixWidth = stringWidth(stripAnsi(treePrefix));\n const remainingWidth = LEFT_COLUMN_WIDTH - treePrefixWidth;\n const commandNamePadded = padToFixedWidth(cmd.name(), remainingWidth);\n const commandNameColored = useColor ? cyan(commandNamePadded) : commandNamePadded;\n const shortDescription = cmd.description() || '';\n lines.push(`${formatDimText('│')} ${treePrefix}${commandNameColored} ${shortDescription}`);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats the header in the new experimental visual style.\n * This header appears at the start of command output, showing the operation,\n * intent, documentation link, and parameters.\n */\nexport function formatStyledHeader(options: {\n readonly command: string;\n readonly description: string;\n readonly url?: string;\n readonly details: ReadonlyArray<{ readonly label: string; readonly value: string }>;\n readonly flags: GlobalFlags;\n}): string {\n const lines: string[] = [];\n const useColor = options.flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Header: arrow + operation badge + intent\n const brand = createPrismaNextBadge(useColor);\n // Use full command path (e.g., \"contract emit\" not just \"emit\")\n const operation = useColor ? bold(options.command) : options.command;\n const intent = formatDimText(options.description);\n lines.push(formatHeaderLine({ brand, operation, intent }));\n lines.push(formatDimText('│')); // Vertical line separator between command and params\n\n // Format details using fixed left column width (same style as help text options)\n for (const detail of options.details) {\n // Add colon to label, then pad to fixed width using padToFixedWidth for ANSI-aware padding\n const labelWithColon = `${detail.label}:`;\n const labelPadded = padToFixedWidth(labelWithColon, LEFT_COLUMN_WIDTH);\n const labelColored = useColor ? cyan(labelPadded) : labelPadded;\n lines.push(`${formatDimText('│')} ${labelColored} ${detail.value}`);\n }\n\n // Add \"Read more\" URL if present (same style as help text)\n if (options.url) {\n lines.push(formatDimText('│')); // Separator line before \"Read more\"\n lines.push(\n formatReadMoreLine({\n url: options.url,\n maxLabelWidth: LEFT_COLUMN_WIDTH,\n useColor,\n formatDimText,\n }),\n );\n }\n\n lines.push(formatDimText('└'));\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/**\n * Formats a success message in the styled output format.\n */\nexport function formatSuccessMessage(flags: GlobalFlags): string {\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n return `${formatGreen('✔')} Success`;\n}\n","import { blue, bold, cyan, dim, green, magenta } from 'colorette';\nimport type { Command } from 'commander';\nimport wrapAnsi from 'wrap-ansi';\n\nimport { getCommandExamples, getLongDescription } from '../command-helpers';\nimport type { GlobalFlags } from '../global-flags';\nimport { formatDim } from './helpers';\nimport { padToFixedWidth, renderCommandTree } from './styled';\n\n// ============================================================================\n// Help Output Formatters\n// ============================================================================\n\n/**\n * Fixed width for left column in help output.\n * Must match the value in styled.ts.\n */\nconst LEFT_COLUMN_WIDTH = 20;\n\n/**\n * Minimum width for right column wrapping in help output.\n */\nconst RIGHT_COLUMN_MIN_WIDTH = 40;\n\n/**\n * Maximum width for right column wrapping in help output (when terminal is wide enough).\n */\nconst RIGHT_COLUMN_MAX_WIDTH = 90;\n\n/**\n * Gets the terminal width, or returns a default if not available.\n */\nfunction getTerminalWidth(): number {\n // Help text goes to stderr, so prefer stderr columns. Fall back to stdout, then CLI_WIDTH env.\n const terminalWidth = process.stderr.columns || process.stdout.columns;\n const envWidth = Number.parseInt(process.env['CLI_WIDTH'] || '', 10);\n return terminalWidth || (Number.isFinite(envWidth) ? envWidth : 80);\n}\n\n/**\n * Calculates the available width for the right column based on terminal width.\n */\nfunction calculateRightColumnWidth(): number {\n const terminalWidth = getTerminalWidth();\n const availableWidth = terminalWidth - 2 - LEFT_COLUMN_WIDTH - 2;\n return Math.max(RIGHT_COLUMN_MIN_WIDTH, Math.min(availableWidth, RIGHT_COLUMN_MAX_WIDTH));\n}\n\n/**\n * Creates the CLI brand badge.\n */\nfunction createPrismaNextBadge(useColor: boolean): string {\n return useColor ? bold('prisma-next') : 'prisma-next';\n}\n\n/**\n * Formats a header line: brand + operation + intent\n */\nfunction formatHeaderLine(options: {\n readonly brand: string;\n readonly operation: string;\n readonly intent: string;\n}): string {\n if (options.operation) {\n return `${options.brand} ${options.operation} → ${options.intent}`;\n }\n return `${options.brand} ${options.intent}`;\n}\n\n/**\n * Wraps text to fit within a specified width using wrap-ansi.\n */\nfunction wrapTextAnsi(text: string, width: number): string[] {\n const wrapped = wrapAnsi(text, width, { hard: false, trim: true });\n return wrapped.split('\\n');\n}\n\n/**\n * Formats a default value as \"default: <value>\" with dimming.\n */\nfunction formatDefaultValue(value: unknown, useColor: boolean): string {\n const valueStr = String(value);\n const defaultText = `default: ${valueStr}`;\n return useColor ? dim(defaultText) : defaultText;\n}\n\n/**\n * Formats a \"Read more\" URL line.\n */\nfunction formatReadMoreLine(options: {\n readonly url: string;\n readonly maxLabelWidth: number;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n}): string {\n const labelPadded = `Read more${' '.repeat(Math.max(0, options.maxLabelWidth - 'Read more'.length))}`;\n const valueColored = options.useColor ? blue(options.url) : options.url;\n return `${options.formatDimText('│')} ${labelPadded} ${valueColored}`;\n}\n\n/**\n * Formats multiline description with \"Prisma Next\" in green.\n */\nfunction formatMultilineDescription(options: {\n readonly descriptionLines: readonly string[];\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n}): string[] {\n const lines: string[] = [];\n const formatGreen = (text: string) => (options.useColor ? green(text) : text);\n\n const rightColumnWidth = calculateRightColumnWidth();\n const totalWidth = 2 + LEFT_COLUMN_WIDTH + 2 + rightColumnWidth;\n const wrapWidth = totalWidth - 2;\n\n for (const descLine of options.descriptionLines) {\n const formattedLine = descLine.replace(/Prisma Next/g, (match) => formatGreen(match));\n const wrappedLines = wrapTextAnsi(formattedLine, wrapWidth);\n for (const wrappedLine of wrappedLines) {\n lines.push(`${options.formatDimText('│')} ${wrappedLine}`);\n }\n }\n return lines;\n}\n\n/**\n * Maps command paths to their documentation URLs.\n */\nfunction getCommandDocsUrl(commandPath: string): string | undefined {\n const docsMap: Record<string, string> = {\n 'contract emit': 'https://pris.ly/contract-emit',\n 'contract infer': 'https://pris.ly/contract-infer',\n 'db schema': 'https://pris.ly/db-schema',\n 'db verify': 'https://pris.ly/db-verify',\n 'db update': 'https://pris.ly/db-update',\n 'migration plan': 'https://pris.ly/migration-plan',\n 'migration apply': 'https://pris.ly/migration-apply',\n 'migration show': 'https://pris.ly/migration-show',\n 'migration status': 'https://pris.ly/migration-status',\n };\n return docsMap[commandPath];\n}\n\n/**\n * Builds the full command path from a command and its parents.\n */\nfunction buildCommandPath(command: Command): string {\n const parts: string[] = [];\n let current: Command | undefined = command;\n while (current && current.name() !== 'prisma-next') {\n parts.unshift(current.name());\n current = current.parent ?? undefined;\n }\n return parts.join(' ');\n}\n\n/**\n * Formats help output for a command using the styled format.\n */\nexport function formatCommandHelp(options: {\n readonly command: Command;\n readonly flags: GlobalFlags;\n}): string {\n const { command, flags } = options;\n const lines: string[] = [];\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Build full command path (e.g., \"db verify\")\n const commandPath = buildCommandPath(command);\n const shortDescription = command.description() || '';\n const longDescription = getLongDescription(command);\n\n // Include positional arguments in the header line\n const argsSuffix = command.registeredArguments\n .map((arg) => (arg.required ? `<${arg.name()}>` : `[${arg.name()}]`))\n .join(' ');\n const brand = createPrismaNextBadge(useColor);\n const commandWithArgs = argsSuffix ? `${commandPath} ${argsSuffix}` : commandPath;\n const operation = useColor ? bold(commandWithArgs) : commandWithArgs;\n const intent = formatDimText(shortDescription);\n lines.push(formatHeaderLine({ brand, operation, intent }));\n lines.push(formatDimText('│'));\n\n // Extract options and format them\n const optionsList = command.options.map((opt) => {\n const description = opt.description || '';\n // Commander.js stores default value in defaultValue property\n const defaultValue = (opt as { defaultValue?: unknown }).defaultValue;\n return { flags: opt.flags, description, defaultValue };\n });\n\n // Extract subcommands if any\n const subcommands = command.commands.filter((cmd) => !cmd.name().startsWith('_'));\n\n // Format subcommands as a tree if present\n if (subcommands.length > 0) {\n const hasItemsAfter = optionsList.length > 0;\n const treeLines = renderCommandTree({\n commands: subcommands,\n useColor,\n formatDimText,\n hasItemsAfter,\n });\n lines.push(...treeLines);\n }\n\n // Add separator between subcommands and options if both exist\n if (subcommands.length > 0 && optionsList.length > 0) {\n lines.push(formatDimText('│'));\n }\n\n // Format options with fixed width, wrapping, and default values\n if (optionsList.length > 0) {\n for (const opt of optionsList) {\n // Format flag with fixed 30-char width\n const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);\n let flagsColored = flagsPadded;\n if (useColor) {\n // Color placeholders in magenta, then wrap in cyan\n flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match: string) => magenta(match));\n flagsColored = cyan(flagsColored);\n }\n\n // Wrap description based on terminal width\n const rightColumnWidth = calculateRightColumnWidth();\n const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);\n\n // First line: flag + first line of description\n lines.push(`${formatDimText('│')} ${flagsColored} ${wrappedDescription[0] || ''}`);\n\n // Continuation lines: empty label (30 spaces) + wrapped lines\n for (let i = 1; i < wrappedDescription.length; i++) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${wrappedDescription[i] || ''}`);\n }\n\n // Default value line (if present)\n if (opt.defaultValue !== undefined) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n const defaultText = formatDefaultValue(opt.defaultValue, useColor);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${defaultText}`);\n }\n }\n }\n\n // Add docs URL if available (with separator line before it)\n const docsUrl = getCommandDocsUrl(commandPath);\n if (docsUrl) {\n lines.push(formatDimText('│')); // Separator line between params and docs\n lines.push(\n formatReadMoreLine({\n url: docsUrl,\n maxLabelWidth: LEFT_COLUMN_WIDTH,\n useColor,\n formatDimText,\n }),\n );\n }\n\n // Examples (copy-pastable)\n const examples = getCommandExamples(command);\n if (examples && examples.length > 0) {\n lines.push(formatDimText('│'));\n lines.push(`${formatDimText('│')} ${formatDimText('Examples:')}`);\n for (const example of examples) {\n lines.push(`${formatDimText('│')} ${useColor ? dim('$') : '$'} ${example}`);\n }\n }\n\n // Multi-line description (if present) - shown after all other content\n if (longDescription) {\n lines.push(formatDimText('│'));\n const descriptionLines = longDescription.split('\\n').filter((line) => line.trim().length > 0);\n lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));\n }\n\n lines.push(formatDimText('└'));\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/**\n * Formats help output for the root program using the styled format.\n */\nexport function formatRootHelp(options: {\n readonly program: Command;\n readonly flags: GlobalFlags;\n}): string {\n const { program, flags } = options;\n const lines: string[] = [];\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Header: \"prisma-next -> Manage your data layer\"\n const brand = createPrismaNextBadge(useColor);\n const shortDescription = 'Manage your data layer';\n const intent = formatDimText(shortDescription);\n lines.push(formatHeaderLine({ brand, operation: '', intent }));\n lines.push(formatDimText('│')); // Vertical line separator after header\n\n // Extract top-level commands (exclude hidden commands starting with '_' and the 'help' command)\n const topLevelCommands = program.commands.filter(\n (cmd) => !cmd.name().startsWith('_') && cmd.name() !== 'help',\n );\n\n // Extract global options (needed to determine if last command)\n const globalOptions = program.options.map((opt) => {\n const description = opt.description || '';\n // Commander.js stores default value in defaultValue property\n const defaultValue = (opt as { defaultValue?: unknown }).defaultValue;\n return { flags: opt.flags, description, defaultValue };\n });\n\n // Build command tree\n if (topLevelCommands.length > 0) {\n const hasItemsAfter = globalOptions.length > 0;\n const treeLines = renderCommandTree({\n commands: topLevelCommands,\n useColor,\n formatDimText,\n hasItemsAfter,\n });\n lines.push(...treeLines);\n }\n\n // Add separator between commands and options if both exist\n if (topLevelCommands.length > 0 && globalOptions.length > 0) {\n lines.push(formatDimText('│'));\n }\n\n // Format global options with fixed width, wrapping, and default values\n if (globalOptions.length > 0) {\n for (const opt of globalOptions) {\n // Format flag with fixed 30-char width\n const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);\n let flagsColored = flagsPadded;\n if (useColor) {\n // Color placeholders in magenta, then wrap in cyan\n flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match: string) => magenta(match));\n flagsColored = cyan(flagsColored);\n }\n\n // Wrap description based on terminal width\n const rightColumnWidth = calculateRightColumnWidth();\n const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);\n\n // First line: flag + first line of description\n lines.push(`${formatDimText('│')} ${flagsColored} ${wrappedDescription[0] || ''}`);\n\n // Continuation lines: empty label (30 spaces) + wrapped lines\n for (let i = 1; i < wrappedDescription.length; i++) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${wrappedDescription[i] || ''}`);\n }\n\n // Default value line (if present)\n if (opt.defaultValue !== undefined) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n const defaultText = formatDefaultValue(opt.defaultValue, useColor);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${defaultText}`);\n }\n }\n }\n\n // Multi-line description (white, not dimmed, with \"Prisma Next\" in green) - shown at bottom\n const formatGreen = (text: string) => (useColor ? green(text) : text);\n const descriptionLines = [\n `Use ${formatGreen('Prisma Next')} to define your data layer as a contract. Sign your database and application with the same contract to guarantee compatibility. Plan and apply migrations to safely evolve your schema.`,\n ];\n if (descriptionLines.length > 0) {\n lines.push(formatDimText('│')); // Separator line before description\n lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));\n }\n\n lines.push(formatDimText('└'));\n\n return `${lines.join('\\n')}\\n`;\n}\n","export interface GlobalFlags {\n readonly json?: boolean;\n readonly quiet?: boolean;\n readonly verbose?: number; // 0, 1, or 2\n readonly color?: boolean;\n readonly interactive?: boolean;\n readonly yes?: boolean;\n}\n\n/**\n * Common options parsed by Commander.js for every command.\n * Extend this for command-specific options instead of duplicating these fields.\n */\nexport interface CommonCommandOptions {\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly trace?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n readonly interactive?: boolean;\n readonly 'no-interactive'?: boolean;\n readonly yes?: boolean;\n readonly y?: boolean;\n}\n\n/**\n * Parses global flags from CLI options.\n * Handles verbosity flags (-v, --trace), JSON output, quiet mode, color,\n * interactivity (--interactive/--no-interactive), and auto-accept (-y/--yes).\n */\nexport function parseGlobalFlags(options: CommonCommandOptions): GlobalFlags {\n const flags: {\n json?: boolean;\n quiet?: boolean;\n verbose?: number;\n color?: boolean;\n interactive?: boolean;\n yes?: boolean;\n } = {};\n\n // JSON output: explicit --json flag or auto-detect piped stdout (Unix convention)\n if (options.json || !process.stdout.isTTY) {\n flags.json = true;\n }\n\n // Quiet mode\n if (options.quiet || options.q) {\n flags.quiet = true;\n }\n\n // Verbosity: -v = 1, --trace = 2\n // Env toggles: PRISMA_NEXT_TRACE=1 ≅ --trace, PRISMA_NEXT_DEBUG=1 ≅ -v\n if (options.trace || process.env['PRISMA_NEXT_TRACE'] === '1') {\n flags.verbose = 2;\n } else if (options.verbose || options.v || process.env['PRISMA_NEXT_DEBUG'] === '1') {\n flags.verbose = 1;\n } else {\n flags.verbose = 0;\n }\n\n // Color: respect NO_COLOR env var, --color/--no-color flags\n // When JSON output is enabled, disable color to ensure clean JSON output\n if (process.env['NO_COLOR'] || flags.json) {\n flags.color = false;\n } else if (options['no-color']) {\n flags.color = false;\n } else if (options.color !== undefined) {\n flags.color = options.color;\n } else {\n // Default: enable color if TTY\n flags.color = process.stdout.isTTY && !process.env['CI'];\n }\n\n // Interactivity: --interactive/--no-interactive\n // Default: interactive when stdout is a TTY\n if (options['no-interactive']) {\n flags.interactive = false;\n } else if (options.interactive !== undefined) {\n flags.interactive = options.interactive;\n } else {\n flags.interactive = !!process.stdout.isTTY;\n }\n\n // Auto-accept prompts: -y/--yes\n if (options.yes || options.y) {\n flags.yes = true;\n }\n\n return flags as GlobalFlags;\n}\n","import { readFile } from 'node:fs/promises';\nimport type { ControlTargetDescriptor } from '@prisma-next/framework-components/control';\nimport { hasMigrations } from '@prisma-next/framework-components/control';\nimport type { PathDecision } from '@prisma-next/migration-tools/dag';\nimport { reconstructGraph } from '@prisma-next/migration-tools/dag';\nimport { readMigrationsDir } from '@prisma-next/migration-tools/io';\nimport type { MigrationBundle, MigrationGraph } from '@prisma-next/migration-tools/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { Command } from 'commander';\nimport { relative, resolve } from 'pathe';\nimport { formatCommandHelp } from './formatters/help';\nimport type { CommonCommandOptions } from './global-flags';\nimport { parseGlobalFlags } from './global-flags';\n\nconst longDescriptions = new WeakMap<Command, string>();\nconst commandExamples = new WeakMap<Command, readonly string[]>();\n\n/**\n * Sets both short and long descriptions for a command.\n * The short description is used in command trees and headers.\n * The long description is shown at the bottom of help output.\n */\nexport function setCommandDescriptions(\n command: Command,\n shortDescription: string,\n longDescription?: string,\n): Command {\n command.description(shortDescription);\n if (longDescription) {\n longDescriptions.set(command, longDescription);\n }\n return command;\n}\n\n/**\n * Sets copy-pastable examples for a command, shown in help text.\n */\nexport function setCommandExamples(command: Command, examples: readonly string[]): Command {\n commandExamples.set(command, examples);\n return command;\n}\n\n/**\n * Gets the long description from a command if it was set via setCommandDescriptions.\n */\nexport function getLongDescription(command: Command): string | undefined {\n return longDescriptions.get(command);\n}\n\n/**\n * Gets examples from a command if set via setCommandExamples.\n */\nexport function getCommandExamples(command: Command): readonly string[] | undefined {\n return commandExamples.get(command);\n}\n\n/**\n * Shared CLI options interface for migration commands (db init, db update).\n * These are the Commander.js parsed options common to both commands.\n */\nexport interface MigrationCommandOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly dryRun?: boolean;\n}\n\n/**\n * Resolves the absolute path to contract.json from the config.\n * Centralises the fallback logic shared by every command that reads the contract.\n */\nexport function resolveContractPath(config: { contract?: { output?: string } }): string {\n return config.contract?.output\n ? resolve(config.contract.output)\n : resolve('src/prisma/contract.json');\n}\n\n/**\n * Resolves the migrations directory and config path from CLI options.\n * Shared by migration-apply, migration-plan, and migration-status.\n */\nexport function resolveMigrationPaths(\n configOption: string | undefined,\n config: { migrations?: { dir?: string } },\n): {\n configPath: string;\n migrationsDir: string;\n migrationsRelative: string;\n refsPath: string;\n} {\n const configPath = configOption\n ? relative(process.cwd(), resolve(configOption))\n : 'prisma-next.config.ts';\n const migrationsDir = resolve(\n configOption ? resolve(configOption, '..') : process.cwd(),\n config.migrations?.dir ?? 'migrations',\n );\n const migrationsRelative = relative(process.cwd(), migrationsDir);\n const refsPath = resolve(migrationsDir, 'refs.json');\n return { configPath, migrationsDir, migrationsRelative, refsPath };\n}\n\n/**\n * Slim representation of a PathDecision for CLI JSON output.\n * Strips internal fields (createdAt, labels) from path entries.\n */\nexport interface PathDecisionResult {\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n readonly selectedPath: readonly {\n readonly dirName: string;\n readonly migrationId: string;\n readonly from: string;\n readonly to: string;\n }[];\n}\n\n/**\n * Maps a PathDecision to the slim CLI output representation.\n */\nexport function toPathDecisionResult(decision: PathDecision): PathDecisionResult {\n return {\n fromHash: decision.fromHash,\n toHash: decision.toHash,\n alternativeCount: decision.alternativeCount,\n tieBreakReasons: decision.tieBreakReasons,\n ...ifDefined('refName', decision.refName),\n selectedPath: decision.selectedPath.map((entry) => ({\n dirName: entry.dirName,\n migrationId: entry.migrationId,\n from: entry.from,\n to: entry.to,\n })),\n };\n}\n\nexport function targetSupportsMigrations(target: ControlTargetDescriptor<string, string>): boolean {\n return hasMigrations(target);\n}\n\nexport function getTargetMigrations(target: ControlTargetDescriptor<string, string>) {\n return hasMigrations(target) ? target.migrations : undefined;\n}\n\n/**\n * Reads the migrations directory and builds the migration graph from all\n * bundles. Throws on I/O or graph errors — callers handle error mapping.\n *\n * Every on-disk bundle is content-addressed (`migrationId` is always a\n * string); there is no draft state to filter out.\n */\nexport async function loadMigrationBundles(migrationsDir: string): Promise<{\n bundles: readonly MigrationBundle[];\n graph: MigrationGraph;\n}> {\n const bundles = await readMigrationsDir(migrationsDir);\n const graph = reconstructGraph(bundles);\n return { bundles, graph };\n}\n\nexport interface MigrationBundleSet {\n readonly bundles: readonly MigrationBundle[];\n readonly graph: MigrationGraph;\n}\n\n/**\n * Alias of `loadMigrationBundles` retained for naming-clarity in commands\n * that previously needed both attested and draft splits. With the\n * collapse of the draft state, both helpers do the same thing.\n */\nexport async function loadAllBundles(migrationsDir: string): Promise<MigrationBundleSet> {\n return loadMigrationBundles(migrationsDir);\n}\n\n/**\n * The subset of the emitted contract.json that the framework layer can\n * safely type. The emitter adds these fields on top of the family-specific\n * storage/models/relations. Other fields exist in the JSON but are opaque\n * at this layer — the index signature preserves them for downstream\n * consumers that operate at the family level (e.g., the control client).\n */\nexport interface ContractEnvelope {\n readonly storageHash: string;\n readonly schemaVersion: string;\n readonly target: string;\n readonly targetFamily: string;\n readonly profileHash?: string;\n readonly [key: string]: unknown;\n}\n\n/**\n * Reads and parses contract.json, validating the framework-level envelope\n * fields (storageHash, schemaVersion, target, targetFamily).\n *\n * Family-specific validation (storage structure, codec mappings, etc.)\n * happens downstream in the control client via the family instance.\n */\nexport async function readContractEnvelope(config: {\n contract?: { output?: string };\n}): Promise<ContractEnvelope> {\n const contractPath = resolveContractPath(config);\n const content = await readFile(contractPath, 'utf-8');\n const json = JSON.parse(content) as Record<string, unknown>;\n\n const { schemaVersion, target, targetFamily, profileHash } = json;\n const storage = json['storage'] as Record<string, unknown> | undefined;\n const storageHash = storage?.['storageHash'];\n\n if (typeof storageHash !== 'string') {\n throw new Error(\n `Contract at ${relative(process.cwd(), contractPath)} is missing a valid storage.storageHash. Run \\`prisma-next contract emit\\` to regenerate.`,\n );\n }\n if (typeof schemaVersion !== 'string') {\n throw new Error(\n `Contract at ${relative(process.cwd(), contractPath)} is missing schemaVersion.`,\n );\n }\n if (typeof target !== 'string') {\n throw new Error(`Contract at ${relative(process.cwd(), contractPath)} is missing target.`);\n }\n if (typeof targetFamily !== 'string') {\n throw new Error(\n `Contract at ${relative(process.cwd(), contractPath)} is missing targetFamily.`,\n );\n }\n\n return {\n ...json,\n storageHash,\n schemaVersion,\n target,\n targetFamily,\n ...(typeof profileHash === 'string' ? { profileHash } : {}),\n };\n}\n\n/**\n * Masks credentials in a database connection URL.\n * Handles standard URLs (username + password + query params) and libpq-style key=value strings.\n */\nexport function maskConnectionUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (parsed.username) {\n parsed.username = '****';\n }\n if (parsed.password) {\n parsed.password = '****';\n }\n // Also mask password in query parameters (e.g., ?password=secret, ?sslpassword=secret)\n for (const key of [...parsed.searchParams.keys()]) {\n if (/password/i.test(key)) {\n parsed.searchParams.set(key, '****');\n }\n }\n return parsed.toString();\n } catch {\n // Fallback for libpq-style key=value connection strings (e.g., \"host=localhost password=secret user=admin\")\n return url\n .replace(/password\\s*=\\s*\\S+/gi, 'password=****')\n .replace(/user\\s*=\\s*\\S+/gi, 'user=****');\n }\n}\n\n/**\n * Strips raw connection URL fragments from an error message to prevent credential leakage.\n * Call this before surfacing driver errors to the user.\n */\nexport function sanitizeErrorMessage(message: string, connectionUrl?: string): string {\n if (!connectionUrl) {\n return message;\n }\n try {\n const parsed = new URL(connectionUrl);\n // Replace the full URL (with and without trailing slash)\n let sanitized = message;\n sanitized = sanitized.replaceAll(connectionUrl, maskConnectionUrl(connectionUrl));\n // Also replace the password and username individually if they appear\n if (parsed.password) {\n sanitized = sanitized.replaceAll(parsed.password, '****');\n }\n if (parsed.username) {\n sanitized = sanitized.replaceAll(parsed.username, '****');\n }\n return sanitized;\n } catch {\n // For libpq-style strings, mask password and user values in the message\n return message\n .replace(/password\\s*=\\s*\\S+/gi, 'password=****')\n .replace(/user\\s*=\\s*\\S+/gi, 'user=****');\n }\n}\n\n/**\n * Registers the global CLI options shared by every command:\n * --json, -q/--quiet, -v/--verbose, --trace, --color, --no-color,\n * --interactive, --no-interactive, -y/--yes.\n *\n * Also sets up the styled help formatter.\n */\nexport function addGlobalOptions(command: Command): Command {\n return command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--json', 'Output as JSON')\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('--trace', 'Trace output: deep internals, stack traces')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .option('--interactive', 'Force interactive mode')\n .option('--no-interactive', 'Disable interactive prompts')\n .option('-y, --yes', 'Auto-accept prompts');\n}\n","import { red } from 'colorette';\n\nimport type { CliErrorConflict, CliErrorEnvelope } from '../cli-errors';\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim, isVerbose } from './helpers';\n\n/**\n * Formats error output for human-readable display.\n */\nexport function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags): string {\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatRed = createColorFormatter(useColor, red);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n lines.push(`${formatRed('✖')} ${error.summary} (${error.code})`);\n\n if (error.why) {\n lines.push(`${formatDimText(` Why: ${error.why}`)}`);\n }\n if (error.fix) {\n lines.push(`${formatDimText(` Fix: ${error.fix}`)}`);\n }\n if (error.where?.path) {\n const whereLine = error.where.line\n ? `${error.where.path}:${error.where.line}`\n : error.where.path;\n lines.push(`${formatDimText(` Where: ${whereLine}`)}`);\n }\n // Show conflicts list if present (always show a short list; show full list when verbose)\n if (error.meta?.['conflicts']) {\n const conflicts = error.meta['conflicts'] as readonly CliErrorConflict[];\n if (conflicts.length > 0) {\n const maxToShow = isVerbose(flags, 1) ? conflicts.length : Math.min(3, conflicts.length);\n const header = isVerbose(flags, 1)\n ? ' Conflicts:'\n : ` Conflicts (showing ${maxToShow} of ${conflicts.length}):`;\n lines.push(`${formatDimText(header)}`);\n for (const conflict of conflicts.slice(0, maxToShow)) {\n lines.push(`${formatDimText(` - [${conflict.kind}] ${conflict.summary}`)}`);\n }\n if (!isVerbose(flags, 1) && conflicts.length > maxToShow) {\n lines.push(`${formatDimText(' Re-run with -v/--verbose to see all conflicts')}`);\n }\n }\n }\n // Show issues list if present (always show a short list; show full list when verbose)\n if (error.meta?.['issues']) {\n const issues = error.meta['issues'] as readonly { kind?: string; message?: string }[];\n if (issues.length > 0) {\n const maxToShow = isVerbose(flags, 1) ? issues.length : Math.min(3, issues.length);\n const header = isVerbose(flags, 1)\n ? ' Issues:'\n : ` Issues (showing ${maxToShow} of ${issues.length}):`;\n lines.push(`${formatDimText(header)}`);\n for (const issue of issues.slice(0, maxToShow)) {\n const kind = issue.kind ?? 'issue';\n const message = issue.message ?? '';\n lines.push(`${formatDimText(` - [${kind}] ${message}`)}`);\n }\n if (!isVerbose(flags, 1) && issues.length > maxToShow) {\n lines.push(`${formatDimText(' Re-run with -v/--verbose to see all issues')}`);\n }\n }\n }\n if (error.docsUrl && isVerbose(flags, 1)) {\n lines.push(formatDimText(error.docsUrl));\n }\n if (isVerbose(flags, 2) && error.meta) {\n lines.push(`${formatDimText(` Meta: ${JSON.stringify(error.meta, null, 2)}`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats error output as JSON.\n */\nexport function formatErrorJson(error: CliErrorEnvelope): string {\n return JSON.stringify(error, null, 2);\n}\n","import type { Result } from '@prisma-next/utils/result';\nimport type { CliStructuredError } from './cli-errors';\nimport { formatErrorJson, formatErrorOutput } from './formatters/errors';\nimport type { GlobalFlags } from './global-flags';\nimport type { TerminalUI } from './terminal-ui';\n\n/**\n * Processes a CLI command result, handling both success and error cases.\n * Formats output appropriately and returns the exit code.\n * Never throws - returns exit code for commands to use with process.exit().\n *\n * Error output:\n * - JSON mode: JSON error to stdout (piped) via ui.output(), human sees nothing on stderr.\n * - Interactive: human-readable error to stderr.\n */\nexport function handleResult<T>(\n result: Result<T, CliStructuredError>,\n flags: GlobalFlags,\n ui: TerminalUI,\n onSuccess?: (value: T) => void,\n): number {\n if (result.ok) {\n if (onSuccess) {\n onSuccess(result.value);\n }\n return 0;\n }\n\n // Convert to CLI envelope\n const envelope = result.failure.toEnvelope();\n\n if (flags.json) {\n // JSON error → stdout only\n ui.output(formatErrorJson(envelope));\n } else {\n // Human-readable error → stderr\n ui.error(formatErrorOutput(envelope, flags));\n }\n\n // Infer exit code from error domain: CLI errors = 2, RUN errors = 1\n const exitCode = result.failure.domain === 'CLI' ? 2 : 1;\n return exitCode;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-ui-C5k88MmW.mjs","names":["graceTimer: ReturnType<typeof setTimeout> | undefined","shutdownSignal: AbortSignal","noop: clack.SpinnerResult","inner: clack.SpinnerResult | undefined","timer: ReturnType<typeof setTimeout> | undefined","pendingMsg: string | undefined"],"sources":["../src/utils/shutdown.ts","../src/utils/terminal-ui.ts"],"sourcesContent":["/**\n * Global shutdown controller for graceful SIGINT/SIGTERM handling.\n *\n * The CLI installs signal handlers once at startup. When a signal fires:\n * 1. The AbortController is aborted — in-flight async work (DB queries, emit) can check `signal.aborted`.\n * 2. A 3-second grace timer starts — gives `finally` blocks time to close connections.\n * 3. If the process hasn't exited by then, force-exit with code 130 (128 + SIGINT).\n * 4. A second signal during the grace period force-exits immediately.\n */\n\n/**\n * Creates a shutdown handler with its own AbortController.\n * Exposed for testing — production code uses the singleton below.\n */\nexport interface ShutdownHandler {\n readonly signal: AbortSignal;\n readonly isShuttingDown: () => boolean;\n readonly onSignal: () => void;\n readonly clearGraceTimer: () => void;\n}\n\nexport function createShutdownHandler(options?: {\n readonly exit?: (code: number) => void;\n readonly gracePeriodMs?: number;\n}): ShutdownHandler {\n const exit = options?.exit ?? ((code: number) => process.exit(code));\n const gracePeriodMs = options?.gracePeriodMs ?? 3000;\n\n const controller = new AbortController();\n let shuttingDown = false;\n let graceTimer: ReturnType<typeof setTimeout> | undefined;\n\n const onSignal = () => {\n if (shuttingDown) {\n // Second signal — force exit\n exit(130);\n return;\n }\n shuttingDown = true;\n controller.abort();\n\n // Give finally blocks time to clean up, then force-exit\n graceTimer = setTimeout(() => exit(130), gracePeriodMs);\n graceTimer.unref();\n };\n\n return {\n signal: controller.signal,\n isShuttingDown: () => shuttingDown,\n onSignal,\n clearGraceTimer: () => {\n if (graceTimer !== undefined) {\n clearTimeout(graceTimer);\n graceTimer = undefined;\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Singleton for production use\n// ---------------------------------------------------------------------------\n\nconst globalHandler = createShutdownHandler();\n\n/**\n * The global AbortSignal. Pass this to any async operation that should\n * be cancellable on Ctrl+C (e.g., DB queries, long-running emit).\n */\nexport const shutdownSignal: AbortSignal = globalHandler.signal;\n\n/**\n * Whether a shutdown has been initiated.\n */\nexport function isShuttingDown(): boolean {\n return globalHandler.isShuttingDown();\n}\n\n/**\n * Installs SIGINT and SIGTERM handlers. Call once at CLI startup.\n *\n * - First signal: aborts the controller, starts a 3s grace timer.\n * - Second signal: force-exits immediately.\n */\nlet installed = false;\n\nexport function installShutdownHandlers(): void {\n if (installed) return;\n installed = true;\n process.on('SIGINT', globalHandler.onSignal);\n process.on('SIGTERM', globalHandler.onSignal);\n}\n","import * as clack from '@clack/prompts';\nimport { bold, cyan, dim, green, red, yellow } from 'colorette';\nimport { shutdownSignal } from './shutdown';\n\n/**\n * Composable CLI output abstraction.\n *\n * Follows the Unix convention of separating data from decoration:\n * - **stdout** — data output only (`ui.output()`). This is what scripts and pipes capture.\n * - **stderr** — all decoration (spinners, logs, notes, intro/outro). Visible in terminal, invisible in pipes.\n *\n * Rules:\n * 1. All methods except `output()` and `error()` write to stderr only in interactive mode.\n * 2. `output(data)` always writes to stdout — if a command calls it, there is data to emit.\n * 3. `error()` always writes to stderr — errors matter even when piped.\n * 4. All other decoration is suppressed when piped — `isInteractive` gates every other decoration method.\n * 5. Never write data to stderr — decoration methods are for human context only.\n * 6. Never write decoration to stdout — it breaks pipes, `$(...)` captures, and `> file` redirects.\n */\nexport class TerminalUI {\n /**\n * True when stdout is a TTY (interactive terminal).\n * False when piped (e.g., `prisma-next db verify | jq`).\n */\n readonly isInteractive: boolean;\n\n /**\n * Whether color output is enabled.\n */\n readonly useColor: boolean;\n\n private static readonly stderrOpts = { output: process.stderr } as const;\n\n constructor(options?: {\n readonly color?: boolean | undefined;\n readonly interactive?: boolean | undefined;\n }) {\n // --interactive/--no-interactive override TTY detection\n this.isInteractive = options?.interactive ?? !!process.stdout.isTTY;\n this.useColor = options?.color ?? this.isInteractive;\n }\n\n // ---------------------------------------------------------------------------\n // Decoration → stderr (only in interactive mode)\n // ---------------------------------------------------------------------------\n\n /**\n * Log a message line to stderr. No-op when piped.\n */\n log(message: string): void {\n if (!this.isInteractive) return;\n clack.log.message(message, TerminalUI.stderrOpts);\n }\n\n /**\n * Log a success message to stderr. No-op when piped.\n */\n success(message: string): void {\n if (!this.isInteractive) return;\n clack.log.success(message, TerminalUI.stderrOpts);\n }\n\n /**\n * Log a warning message to stderr. No-op when piped.\n */\n warn(message: string): void {\n if (!this.isInteractive) return;\n clack.log.warn(message, TerminalUI.stderrOpts);\n }\n\n /**\n * Log an error message to stderr. Always writes (errors matter even in pipes).\n */\n error(message: string): void {\n clack.log.error(message, TerminalUI.stderrOpts);\n }\n\n /**\n * Log an info message to stderr. No-op when piped.\n */\n info(message: string): void {\n if (!this.isInteractive) return;\n clack.log.info(message, TerminalUI.stderrOpts);\n }\n\n /**\n * Log a step message to stderr. No-op when piped.\n */\n step(message: string): void {\n if (!this.isInteractive) return;\n clack.log.step(message, TerminalUI.stderrOpts);\n }\n\n /**\n * Display a note box on stderr. No-op when piped.\n */\n note(message: string, title?: string): void {\n if (!this.isInteractive) return;\n clack.note(message, title, TerminalUI.stderrOpts);\n }\n\n /**\n * Display intro banner on stderr. No-op when piped.\n */\n intro(title?: string): void {\n if (!this.isInteractive) return;\n clack.intro(title, TerminalUI.stderrOpts);\n }\n\n /**\n * Display outro banner on stderr. No-op when piped.\n */\n outro(message?: string): void {\n if (!this.isInteractive) return;\n clack.outro(message, TerminalUI.stderrOpts);\n }\n\n /**\n * Create a Clack spinner on stderr with a 100ms delay threshold.\n * The spinner only appears if the operation takes longer than the threshold,\n * avoiding flicker for fast operations. Returns a no-op spinner when not interactive.\n */\n spinner(delayMs = 100): clack.SpinnerResult {\n const noop: clack.SpinnerResult = {\n start: () => {},\n stop: () => {},\n cancel: () => {},\n error: () => {},\n message: () => {},\n clear: () => {},\n get isCancelled() {\n return false;\n },\n };\n\n if (!this.isInteractive) {\n return noop;\n }\n\n // Wrap the real spinner with a delay: only show it after `delayMs`\n let inner: clack.SpinnerResult | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pendingMsg: string | undefined;\n let settled = false;\n\n const ensureCleared = () => {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n // Cancel the spinner if a shutdown signal fires\n const onAbort = () => {\n if (!settled) {\n settled = true;\n ensureCleared();\n if (inner) {\n inner.cancel('Interrupted');\n }\n }\n };\n if (!shutdownSignal.aborted) {\n shutdownSignal.addEventListener('abort', onAbort, { once: true });\n }\n\n return {\n start(msg?: string) {\n pendingMsg = msg;\n timer = setTimeout(() => {\n if (!settled) {\n inner = clack.spinner(TerminalUI.stderrOpts);\n inner.start(pendingMsg);\n }\n }, delayMs);\n },\n stop(msg?: string) {\n settled = true;\n ensureCleared();\n if (inner) {\n inner.stop(msg);\n }\n },\n cancel(msg?: string) {\n settled = true;\n ensureCleared();\n if (inner) {\n inner.cancel(msg);\n }\n },\n error(msg?: string) {\n settled = true;\n ensureCleared();\n if (inner) {\n inner.error(msg);\n }\n },\n message(msg?: string) {\n pendingMsg = msg;\n if (inner) {\n inner.message(msg);\n }\n },\n clear() {\n settled = true;\n ensureCleared();\n if (inner) {\n inner.clear();\n }\n },\n get isCancelled() {\n return inner?.isCancelled ?? false;\n },\n };\n }\n\n /**\n * Prompt for yes/no confirmation on stderr. Returns true if confirmed.\n * In non-interactive mode or when cancelled (Ctrl-C), returns false.\n */\n async confirm(message: string): Promise<boolean> {\n if (!this.isInteractive) return false;\n const result = await clack.confirm({\n message,\n ...TerminalUI.stderrOpts,\n });\n if (clack.isCancel(result)) return false;\n return result;\n }\n\n /**\n * Write a raw line to stderr. No-op when piped.\n * Use for decoration that doesn't fit Clack's log format (e.g. styled headers).\n */\n stderr(message: string): void {\n if (!this.isInteractive) return;\n process.stderr.write(`${message}\\n`);\n }\n\n // ---------------------------------------------------------------------------\n // Data → stdout (only when piped)\n // ---------------------------------------------------------------------------\n\n /**\n * Write machine-readable data to stdout.\n * Always writes — if a command calls output(), there is data to emit.\n *\n * This is what scripts and pipes capture: `prisma-next db verify --json | jq .ok`\n */\n output(data: string): void {\n process.stdout.write(`${data}\\n`);\n }\n\n // ---------------------------------------------------------------------------\n // Color helpers\n // ---------------------------------------------------------------------------\n\n green(text: string): string {\n return this.useColor ? green(text) : text;\n }\n red(text: string): string {\n return this.useColor ? red(text) : text;\n }\n cyan(text: string): string {\n return this.useColor ? cyan(text) : text;\n }\n dim(text: string): string {\n return this.useColor ? dim(text) : text;\n }\n bold(text: string): string {\n return this.useColor ? bold(text) : text;\n }\n yellow(text: string): string {\n return this.useColor ? yellow(text) : text;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
|
|
3
|
-
//#region src/utils/validate-contract-deps.ts
|
|
4
|
-
const IMPORT_PATTERN = /import\s+type\s+.*?\s+from\s+['"](@[^/]+\/[^/'"]+)/g;
|
|
5
|
-
function extractPackageSpecifiers(dtsContent) {
|
|
6
|
-
const packages = /* @__PURE__ */ new Set();
|
|
7
|
-
for (const match of dtsContent.matchAll(IMPORT_PATTERN)) {
|
|
8
|
-
const pkg = match[1];
|
|
9
|
-
if (pkg) packages.add(pkg);
|
|
10
|
-
}
|
|
11
|
-
return [...packages];
|
|
12
|
-
}
|
|
13
|
-
function validateContractDeps(dtsContent, projectRoot) {
|
|
14
|
-
const packages = extractPackageSpecifiers(dtsContent);
|
|
15
|
-
const resolve = createRequire(`${projectRoot}/package.json`);
|
|
16
|
-
const missing = [];
|
|
17
|
-
for (const pkg of packages) try {
|
|
18
|
-
resolve.resolve(`${pkg}/package.json`);
|
|
19
|
-
} catch {
|
|
20
|
-
missing.push(pkg);
|
|
21
|
-
}
|
|
22
|
-
if (missing.length === 0) return { missing };
|
|
23
|
-
return {
|
|
24
|
-
missing,
|
|
25
|
-
warning: [
|
|
26
|
-
"contract.d.ts imports types from packages that are not installed:",
|
|
27
|
-
missing.map((p) => ` - ${p}`).join("\n"),
|
|
28
|
-
"",
|
|
29
|
-
"Install them with your package manager:",
|
|
30
|
-
...missing.map((p) => ` ${p}`)
|
|
31
|
-
].join("\n")
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
//#endregion
|
|
36
|
-
export { validateContractDeps };
|
|
37
|
-
//# sourceMappingURL=validate-contract-deps-esa-VQ0h.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validate-contract-deps-esa-VQ0h.mjs","names":["missing: string[]"],"sources":["../src/utils/validate-contract-deps.ts"],"sourcesContent":["import { createRequire } from 'node:module';\n\nconst IMPORT_PATTERN = /import\\s+type\\s+.*?\\s+from\\s+['\"](@[^/]+\\/[^/'\"]+)/g;\n\nexport function extractPackageSpecifiers(dtsContent: string): string[] {\n const packages = new Set<string>();\n for (const match of dtsContent.matchAll(IMPORT_PATTERN)) {\n const pkg = match[1];\n if (pkg) packages.add(pkg);\n }\n return [...packages];\n}\n\nexport interface ContractDepsValidation {\n readonly missing: readonly string[];\n readonly warning?: string;\n}\n\nexport function validateContractDeps(\n dtsContent: string,\n projectRoot: string,\n): ContractDepsValidation {\n const packages = extractPackageSpecifiers(dtsContent);\n const resolve = createRequire(`${projectRoot}/package.json`);\n\n const missing: string[] = [];\n for (const pkg of packages) {\n try {\n resolve.resolve(`${pkg}/package.json`);\n } catch {\n missing.push(pkg);\n }\n }\n\n if (missing.length === 0) {\n return { missing };\n }\n\n const list = missing.map((p) => ` - ${p}`).join('\\n');\n const warning = [\n 'contract.d.ts imports types from packages that are not installed:',\n list,\n '',\n 'Install them with your package manager:',\n ...missing.map((p) => ` ${p}`),\n ].join('\\n');\n\n return { missing, warning };\n}\n"],"mappings":";;;AAEA,MAAM,iBAAiB;AAEvB,SAAgB,yBAAyB,YAA8B;CACrE,MAAM,2BAAW,IAAI,KAAa;AAClC,MAAK,MAAM,SAAS,WAAW,SAAS,eAAe,EAAE;EACvD,MAAM,MAAM,MAAM;AAClB,MAAI,IAAK,UAAS,IAAI,IAAI;;AAE5B,QAAO,CAAC,GAAG,SAAS;;AAQtB,SAAgB,qBACd,YACA,aACwB;CACxB,MAAM,WAAW,yBAAyB,WAAW;CACrD,MAAM,UAAU,cAAc,GAAG,YAAY,eAAe;CAE5D,MAAMA,UAAoB,EAAE;AAC5B,MAAK,MAAM,OAAO,SAChB,KAAI;AACF,UAAQ,QAAQ,GAAG,IAAI,eAAe;SAChC;AACN,UAAQ,KAAK,IAAI;;AAIrB,KAAI,QAAQ,WAAW,EACrB,QAAO,EAAE,SAAS;AAYpB,QAAO;EAAE;EAAS,SARF;GACd;GAFW,QAAQ,KAAK,MAAM,OAAO,IAAI,CAAC,KAAK,KAAK;GAIpD;GACA;GACA,GAAG,QAAQ,KAAK,MAAM,KAAK,IAAI;GAChC,CAAC,KAAK,KAAK;EAEe"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
|
|
2
|
-
import { extractSqlDdl } from './extract-sql-ddl';
|
|
3
|
-
|
|
4
|
-
export function extractOperationStatements(
|
|
5
|
-
familyId: string,
|
|
6
|
-
operations: readonly MigrationPlanOperation[],
|
|
7
|
-
): string[] | undefined {
|
|
8
|
-
switch (familyId) {
|
|
9
|
-
case 'sql':
|
|
10
|
-
return extractSqlDdl(operations);
|
|
11
|
-
default:
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
}
|