Skip to content

TML-2768: migration list shows on-disk migrations as a graph tree#706

Merged
wmadden-electric merged 5 commits into
mainfrom
tml-2768-migration-list-renders-tree
Jun 3, 2026
Merged

TML-2768: migration list shows on-disk migrations as a graph tree#706
wmadden-electric merged 5 commits into
mainfrom
tml-2768-migration-list-renders-tree

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

At a glance

Interactive migration list (TTY) now draws the same condensed graph tree as migration graph and migration status, with per-package facts on migration rows (N ops, {invariants}) and refs on contract nodes. Machine output is unchanged.

Single contract space (no app: heading when there is only one space):

$ prisma-next migration list
○   55bada2
│↑  20260422T0742_migration  4cb4256 → 55bada2  1 ops
○   4cb4256
│↑  20260422T0720_initial    ∅       → 4cb4256  1 ops
∅

2 migration(s) on disk

Multiple contract spaces (spaceId: section headings, two-space tree indent):

$ prisma-next migration list
app:
  ○   804e018                           (db)
  │↑  20260518T1701_namespaces_bookend  55bada2 → 804e018  1 ops
  ○   55bada2                           (production)
  │↑  20260422T0742_migration           4cb4256 → 55bada2  1 ops
  ○   4cb4256
  │↑  20260422T0720_initial             ∅       → 4cb4256  1 ops
  ∅

postgis:
  ○   9aabbcc                                  (db)
  │↑  20260601T0000_install_postgis_extension  ∅       → 9aabbcc  1 ops
  ∅

4 migration(s) across 2 contract space(s)

Piping or --json still emits the flat package array (MigrationListResult); the tree never appears in machine formats.

The decision

migration list now renders the on-disk migration graph as a tree in human (pretty / TTY) output — contracts as nodes, packages as annotated edges — using the shared renderer the read-command family already uses for graph and (soon) status. Package facts (operationCount, providedInvariants) ride a new shared edge-annotation overlay on that renderer; refs stay on destination contract nodes. migration list --json, non-TTY stdout, and the flat sort order stay byte-identical for scripts and tooling.

Why this shape

One renderer for on-disk topology

list, graph, and status all answer questions about on-disk contract-space history. They now share one condensed tree engine for human output and differ only in overlays: list adds package facts on edges, graph emphasizes contract topology and (contract) / (refs) node labels, and status (follow-up in #705) will add applied/pending markers. One code path to maintain; one mental model for operators.

Flat rows were the wrong default for humans

The old human output was a flat table sorted by migration directory name. That order is lexicographic, not chronological, and it hides branching — for anything beyond a linear chain you had to run list and graph together to see relationships. The tree is the readable default now that the family shares a renderer; machine consumers still get the faithful flat package inventory in JSON.

Human vs machine stays split

The tree is rendered only on the pretty/TTY path. Piping a read command already selects JSON via resolveOutputFormat, so scripts never accidentally parse box-drawing. This PR also drops the misleading “latest first” wording in the command description; flat JSON order is unchanged (still lexicographic by dirName).

What this PR does not change

  • migration list --jsonMigrationListResult shape, field names, and flat sort order (golden tests pin byte-identical JSON).
  • migration graph — separate command, separate { nodes, edges } JSON contract; not modified here.
  • migration list --graph — already removed earlier in the migration-graph-rendering project; not touched.
  • migration status DB overlay — applied/pending glyphs and (db) marker land in TML-2748: migration status overlays applied and pending on the shared migration tree #705 (tml-2748-migration-status-db-overlay); this PR only introduces the shared edgeAnnotationsByHash field that status extends with a status key.
  • Ledger / migration log — out of scope.

Alternatives considered

  • Keep flat rows as the default, add --tree on list. Rejected: the tree is the right human default now that graph and status share one renderer; an opt-in flag would preserve the unreadable path most people would still hit.
  • Have migration status drive list rendering (status wraps list). Rejected: both commands consume the same tree library; neither owns the other. list populates package edge annotations; status adds DB-state keys on the same overlay type.

Linked issue

Refs TML-2768. Related: TML-2697 (original list readability ask). DB overlay follow-up: #705.

Testing performed

  • pnpm --filter @prisma-next/cli... build
  • pnpm --filter @prisma-next/cli typecheck
  • CLI package vitest (tree render golden, --json byte-identical, --space, --ascii, multi-space headings)
  • pnpm lint:deps

Skill update

n/a — CLI behaviour change is user-visible but covered by existing migration-command skills; no new flags.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The PR title is in TML-NNNN: <sentence-case title> form.
  • Skill update section filled in (n/a — internal only above).

Summary by CodeRabbit

  • New Features

    • Migration edges now display operation counts and invariants for improved visibility into migration details.
    • Added colorization support for enhanced terminal output readability.
  • Improvements

    • Enhanced migration list visualization with improved formatting.
    • Updated migration list command description.

Review Change Stack

wmadden added 2 commits June 2, 2026 18:39
Record cross-command decisions (D11 edge annotations) and the TML-2768
slice spec that routes migration list human output through the shared tree.

Signed-off-by: Will Madden <madden@prisma.io>
Introduce edgeAnnotationsByHash on the tree renderer for package facts
(operation count, invariants) and wire migration list TTY output through
buildMigrationGraphRows → buildMigrationGraphLayout → renderMigrationGraphTree
per space. JSON output and flat sort order are unchanged; command copy no
longer claims "latest first".

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric requested a review from a team as a code owner June 2, 2026 16:40
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR refactors the migration-list CLI rendering from a row-by-row approach to a graph-tree model. Edge annotations (operation counts and invariants) are added to the tree renderer, and the rendering API changes from passing pre-computed topology to passing a colorize option through an options object.

Changes

Migration list graph-tree rendering with edge annotations

Layer / File(s) Summary
Edge annotation support in tree renderer
src/utils/formatters/migration-graph-tree-render.ts
Introduces MigrationEdgeAnnotation interface with optional operation count and invariants, extends RenderMigrationGraphTreeOptions with edgeAnnotationsByHash map, and implements formatEdgeAnnotationSuffix to append annotation details to edge rows.
Graph-based rendering refactor
src/utils/formatters/migration-list-render.ts
Refactors rendering from per-entry row formatting to a graph-tree model. Introduces migrationGraphFromListEntries, buildEdgeAnnotationsByHashFromListEntries, and buildRefsByHashFromListEntries helpers. Changes renderMigrationListWithStyle signature to accept RenderMigrationListWithStyleOptions with colorize field instead of topologyBySpaceId parameter.
CLI command integration
src/commands/migration-list.ts
Wires the new renderMigrationListWithStyle options by passing { colorize: options.useColor }. Updates command description text to reflect tree/glyph-based rendering behavior.
Remove obsolete data-column exports
src/utils/formatters/migration-list-data-column.ts
Removes exported functions and constants no longer used after the graph-tree refactor: kind glyph mappings, decoration prefix, data column formatting utilities, and directory-name width computation.
Documentation updates for topology classifier
src/utils/formatters/migration-list-graph-topology.ts
Clarifies comments and JSDoc that both tier-2 and tier-3 topology classifiers use shared edge-classification logic on normalized edge shapes.
Test coverage for edge annotations
test/utils/formatters/migration-graph-tree-render.test.ts
Adds three test cases validating edge annotation rendering: operation count and invariants appended to edges, invariants omitted when empty, and no suffix when annotations absent.
Test updates for graph-based rendering
test/commands/migration-list.test.ts, test/utils/formatters/migration-list-render.test.ts
Updates rendering tests from inline snapshots to targeted substring assertions for glyphs, refs, invariants, and operation counts. Adds coverage for migrationGraphFromListEntries and validates multi-space output with per-space headings and updated tree glyphs.
Test updates for styling with new API
test/utils/formatters/migration-list-styler.test.ts
Updates styling tests to use new renderMigrationListWithStyle signature with 'unicode' and { colorize: true } options. Changes assertions from exact string matching to targeted checks for styled tokens.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • prisma/prisma-next#674: Modifies migration-graph-tree-render.ts to add lane-aware colorization and --legend support alongside this PR's per-edge annotation suffixes in the same tree renderer.
  • prisma/prisma-next#636: Updates migration-list CLI rendering by changing how topology options are passed to renderers; this PR replaces topologyBySpaceId with an options object while the other standardizes topology passing.
  • prisma/prisma-next#628: Refactors migration-list human rendering to use the shared migration-graph tree pipeline; this PR extends that with per-edge annotation support.

Suggested reviewers

  • aqrln

Poem

🐰 From rows to trees the migrations now dance,
With annotations that sparkle and glance,
Edge counts and invariants trim each display,
The graph takes the stage in a whole new way! 🌳✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: routing migration list output through the shared graph tree renderer to display migrations as a tree structure instead of a flat list.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2768-migration-list-renders-tree

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 137.3 KB (0%)
postgres / emit 108.81 KB (0%)
mongo / no-emit 75.78 KB (0%)
mongo / emit 70.78 KB (0%)
cf-worker / no-emit 166.31 KB (0%)
cf-worker / emit 134.52 KB (0%)

Remove unwired flat-list helpers and the Tier-2 topology classifier,
drop the unused topology map parameter from the list renderer, and pin
linear and multi-space tree layout with golden assertions.

Signed-off-by: Will Madden <madden@prisma.io>
@pkg-pr-new

pkg-pr-new Bot commented Jun 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@706

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@706

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@706

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@706

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@706

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@706

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@706

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@706

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@706

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@706

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@706

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@706

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@706

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@706

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@706

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@706

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@706

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@706

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@706

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@706

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@706

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@706

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@706

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@706

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@706

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@706

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@706

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@706

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@706

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@706

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@706

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@706

prisma-next

npm i https://pkg.pr.new/prisma-next@706

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@706

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@706

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@706

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@706

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@706

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@706

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@706

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@706

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@706

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@706

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@706

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@706

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@706

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@706

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@706

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@706

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@706

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@706

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@706

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@706

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@706

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@706

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@706

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@706

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@706

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@706

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@706

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@706

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@706

commit: 404f9a1

@wmadden-electric wmadden-electric changed the title TML-2768: migration list renders the shared graph tree TML-2768: migration list shows on-disk migrations as a graph tree Jun 3, 2026
wmadden added 2 commits June 3, 2026 13:12
Keep topology classifier tests and Tier-2 entry surface from main
after #704/#705 landed; restore classifyMigrationListGraphTopology
for shared rollback-peel coverage.

Signed-off-by: Will Madden <madden@prisma.io>
Re-export abbreviateContractHash after main merge brought in log
formatting that shares list hash display constants.

Signed-off-by: Will Madden <madden@prisma.io>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-render.ts`:
- Around line 61-63: The canonicalFrom function currently only maps null to
EMPTY_CONTRACT_HASH but leaves empty-string ('') as a real hash; update
canonicalFrom to treat both null and empty-string as baseline by returning
EMPTY_CONTRACT_HASH when from is null or from === '' (otherwise return the
original from) so rendering uses the baseline symbol; reference function
canonicalFrom and constant EMPTY_CONTRACT_HASH when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ee947c89-f08b-4f06-bc33-5c3d8e6cee3f

📥 Commits

Reviewing files that changed from the base of the PR and between bcb1a0a and 404f9a1.

⛔ Files ignored due to path filters (3)
  • projects/migration-graph-rendering/README.md is excluded by !projects/**
  • projects/migration-graph-rendering/decisions.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/list-renders-tree/spec.md is excluded by !projects/**
📒 Files selected for processing (9)
  • packages/1-framework/3-tooling/cli/src/commands/migration-list.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-data-column.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-graph-topology.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-render.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-list.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-tree-render.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-list-render.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-list-styler.test.ts
💤 Files with no reviewable changes (1)
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-data-column.ts

Comment on lines +61 to 63
function canonicalFrom(from: string | null): string {
return from ?? EMPTY_CONTRACT_HASH;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat empty-string from as baseline hash.

Line 62 only canonicalizes null; '' will be treated as a real hash node and render incorrectly instead of mapping to .

💡 Proposed fix
 function canonicalFrom(from: string | null): string {
-  return from ?? EMPTY_CONTRACT_HASH;
+  return from === null || from.length === 0 ? EMPTY_CONTRACT_HASH : from;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function canonicalFrom(from: string | null): string {
return from ?? EMPTY_CONTRACT_HASH;
}
function canonicalFrom(from: string | null): string {
return from === null || from.length === 0 ? EMPTY_CONTRACT_HASH : from;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-render.ts`
around lines 61 - 63, The canonicalFrom function currently only maps null to
EMPTY_CONTRACT_HASH but leaves empty-string ('') as a real hash; update
canonicalFrom to treat both null and empty-string as baseline by returning
EMPTY_CONTRACT_HASH when from is null or from === '' (otherwise return the
original from) so rendering uses the baseline symbol; reference function
canonicalFrom and constant EMPTY_CONTRACT_HASH when making the change.

@wmadden-electric wmadden-electric merged commit a088b25 into main Jun 3, 2026
21 checks passed
@wmadden-electric wmadden-electric deleted the tml-2768-migration-list-renders-tree branch June 3, 2026 11:28
medz pushed a commit to medz/prisma-next that referenced this pull request Jun 5, 2026
…ble (prisma#704)

## At a glance

_Representative output (multi-space ledger; column alignment from
unit-test golden on this branch). At the terminal the divider row is
dim; timestamps below use local time with offset._

```
$ prisma-next migration log

 Applied at                      Space  Migration              Change                   Ops
─────────────────────────────── ────── ────────────────────── ──────────────────────── ───────
 2026-06-01 10:00:00 +02:00     app    20260301_init          ∅ → ef9de27              5 ops
 2026-06-01 10:00:02 +02:00     audit  20260301_init          ∅ → 9a1c2f3              3 ops
 2026-06-02 10:30:00 +02:00     app    20260303_add_phone     ef9de27 → 73e3abe        2 ops
 2026-06-03 11:00:00 +02:00     app    20260305_rollback      73e3abe → ef9de27        2 ops
```

## The decision

`migration log` now reads the per-migration ledger journal directly from
the connected database and presents it as a single flat chronological
table — every applied migration across every space, ordered by
`appliedAt` (oldest first). Human output on a TTY renders local time
with a numeric timezone offset; `--utc` switches human output to UTC;
`--json`, non-TTY pipes, and other machine paths emit ISO-8601 UTC
(`…Z`) for stable tooling. This replaces the previous `migration log`
behaviour wholesale: no on-disk graph, no `findPath` reconstruction, no
per-space sections.

## Narrative

### Ledger journal (TML-2769, merged)

The on-apply ledger was restructured in
[prisma#665](prisma#665) into a
per-migration journal: one row per applied edge with `space`,
`migrationName`, `migrationHash`, `from` / `to`, `operationCount`, and
`appliedAt`. That shape is what `status` matches against and what `log`
is meant to surface.

### `migration log` as the human-facing journal view

`log` answers “what actually ran against this database, and when?” It
calls `readLedger()` with no space filter so the adapter returns the
whole table, sorts globally by `appliedAt`, and prints aligned rows
(`Applied at` · optional `Space` · `Migration` · `Change` · `Ops`). The
ledger is conceptually flat, so the command stays flat too — not
space-scoped like `list` / `graph` / `status`. The `Space` column
appears only when more than one space contributes rows. Rollbacks and
re-applies are repeated uniform rows; `from → to` (with `∅` for a null
origin) carries the story without classifying event kinds.

### Timestamp formatting

TTY humans get local time plus offset (`2026-06-01 10:00:00 +02:00`).
`--utc` is human-only and prints UTC with a `Z` suffix. Machine output
(`--json`, or any non-TTY pipe) is always ISO-8601 UTC regardless of
`--utc`, so scripts never inherit the operator’s timezone.

### Styling

`from → to` uses the shared migration-list colour palette so hash
transitions read the same way as in `migration graph` and `migration
status`. Column layout is a dedicated flat table renderer (not the
shared tree).

## What this PR does not change

- **Ledger schema or `readLedger` contract** — already landed in
TML-2769 ([prisma#665](prisma#665)); this
PR only widens the read to be space-optional and wires `log` to it.
- **`migration list`** — separate slice
([prisma#706](prisma#706)).
- **`migration status`** — separate slice
([prisma#705](prisma#705)).
- **Ledger writes** — the migrate runner that appends rows is untouched.

## Alternatives considered

1. **Per-space sections with headings (like `graph` / `list` /
`status`)** — rejected. The ledger is a flat table; sectioning would
re-group flat data and break global chronological ordering across
spaces.
2. **Render via the shared tree renderer with a chronological pivot** —
rejected. The tree’s value is structural topology; for apply history
ordered by time, a table is the right shape.
3. **Default to UTC everywhere** — rejected. Humans benefit from local
time at the terminal; tooling gets stable `Z` ISO timestamps on `--json`
/ pipes.

## Linked issue

Refs [TML-2770](https://linear.app/prisma-company/issue/TML-2770).
Builds on ledger journal
[TML-2769](https://linear.app/prisma-company/issue/TML-2769)
([prisma#665](prisma#665)).

## Testing performed

- `pnpm --filter @prisma-next/cli... build`
- `pnpm --filter @prisma-next/cli typecheck`
- CLI: `migration-log-table`, `migration-log`, `readLedger` client, JSON
golden tests
- Adapter unscoped reads: SQLite `runner.ledger`, Mongo `marker-ledger`
- `pnpm lint:deps`
- Full CI (pending on PR)

## Skill update

n/a — internal CLI behaviour change; no published skill documents
`migration log` table layout yet.

## Checklist

- [x] All commits are signed off (`git commit -s`)
- [x] I read CONTRIBUTING.md and the change is scoped to one logical
concern
- [x] Tests are updated
- [x] PR title uses `TML-2770: …` form
- [x] Skill update section filled in


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Migration log command now reads data directly from the database ledger
  * Added UTC timestamp formatting option for migration log display
* Enhanced migration log output with improved table rendering and
formatting

* **Changes**
* Ledger read operations now return entries for all spaces when the
space parameter is omitted

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Will Madden <madden@prisma.io>
Co-authored-by: Will Madden <madden@prisma.io>
medz pushed a commit to medz/prisma-next that referenced this pull request Jun 5, 2026
… migration tree

## At a-glance

Connected database with one migration still to run:

```
$ prisma-next migration status --no-color
app:
○   3b2d98d                      (contract) (main)
│↑  20260305_add_avatar    73e3abe → 3b2d98d   ⧗ pending
○   73e3abe                      (db)
│✓  20260303_add_phone     ef9de27 → 73e3abe   ✓ applied
○   ef9de27
│✓  20260301_init          ∅ → ef9de27         ✓ applied
○   ∅

1 pending — run `prisma-next migrate --to 3b2d98d`
```

Hypothetical origin (`--from` drops applied glyphs and the `(db)`
marker; pending is still the shortest path from that origin to the
target):

```
$ prisma-next migration status --no-color --from ef9de27 --to 3b2d98d
app:
○   3b2d98d                      (contract) (main)
│↑  20260305_add_avatar    73e3abe → 3b2d98d   ⧗ pending
○   73e3abe
│↑  20260303_add_phone     ef9de27 → 73e3abe   ⧗ pending
○   ef9de27
│↑  20260301_init          ∅ → ef9de27
○   ∅

2 pending — run `prisma-next migrate --to 3b2d98d`
```

*(Representative output — same tree renderer and overlay rules exercised
in CLI unit tests and integration journeys.)*

## The decision

`migration status` now overlays **applied** and **pending** state on the
**shared migration graph tree**, giving you one picture of every on-disk
migration's relationship to the database. **Applied** means a ledger row
exists for that migration hash (green `✓`). **Pending** means the edge
sits on the shortest path from the DB marker (or `--from` origin) to the
app contract and is not yet applied (yellow `⧗`). Everything else stays
plain — the full on-disk graph, not a pruned subgraph. The old
summary-only view and the dagre renderer are deleted; `migration graph`
defaults to this tree (the experimental `--tree` flag is gone).

## How we got here

### One tree renderer for the read family

`migration graph` already renders contract topology as a condensed
annotated tree (`buildMigrationGraphRows` → `buildMigrationGraphLayout`
→ `renderMigrationGraphTree`). `list` and `status` are meant to draw
that same map with different overlays — package facts vs DB state — not
maintain parallel layout code.

### `status` used to look different

Previously, `migration status` fed a dagre layout and a compact summary
that did not match `graph` or `list`. Applied edges were inferred from a
graph walk from ∅ to the marker instead of the ledger, which could
disagree with what actually ran.

### Overlay on the shared renderer

Status computation builds `edgeAnnotationsByHash` (`applied` /
`pending`) and passes them into the same tree renderer `graph` uses. The
`(db)` marker reuses the existing `dbHash` node overlay. Multi-space
projects get one tree section per space (with a `spaceId:` heading when
there is more than one), matching `list` / `graph`.

### `--from` for hypotheticals

`--from <hash>` overrides the path origin so you can ask offline: "what
if my DB were at this contract?" Applied overlays are suppressed (they
only make sense against the real DB); pending still follows the shortest
path from the hypothetical origin to the target (`--to` or the app
contract).

### Dagre removed; tree is the default

With `status` on the shared tree, dagre had no remaining consumer.
`migration graph` always renders the tree; `--ascii`, `--legend`,
`--dot`, and `--json` remain. `@dagrejs/dagre` and its
mapper/types/tests are removed.

## What this PR does not change

- **`--json` wire shape** — still mirrors `migration list` plus
per-migration `status`, with `markerHash` / `targetHash` per space
(documented in the slice spec).
- **`migration list`** — separate slice
([prisma#706](prisma#706)); this PR only
shares the renderer hook `list` will populate later.
- **`migration log`** — separate slice
([prisma#704](prisma#704)); flat ledger
history, not tree-shaped.

## Linked issue

Refs [TML-2748](https://linear.app/prisma-company/issue/TML-2748)
(resolves applied-from-ledger
[TML-2130](https://linear.app/prisma-company/issue/TML-2130)).

## Testing performed

- `pnpm --filter @prisma-next/cli build`
- `pnpm --filter @prisma-next/cli typecheck`
- `pnpm --filter @prisma-next/cli test` (status overlay, graph
default-tree, dagre removal)
- `rg 'dagre|graphRenderer|migrationGraphToRenderInput'
packages/1-framework/3-tooling/cli/` → empty
- `pnpm lint:deps`

## Checklist

- [x] All commits are signed off (`git commit -s`)
- [x] I read CONTRIBUTING.md and the change is scoped to one logical
concern
- [x] Tests are updated
- [x] PR title is `TML-2748: …`
- [x] n/a — internal CLI surface; skill update not required for this
slice

## Alternatives considered

- **Keep dagre as a `--graph` alternative renderer** — rejected. Two
renderers split the family visually; dagre never matched the tree's
information density for branching histories.
- **Three states (applied / pending / unreachable)** — rejected. Two
states cover every real workflow; unreachable edges stay plain on the
full list.
- **Reimplement layout inside `status`** — rejected. Share `graph` /
`list` layout via the existing engine and differ only in overlays.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added applied/pending status indicators to migration visualizations.

* **Bug Fixes**
* Improved migration status output with per-space reporting and clearer
status summaries.

* **Chores**
  * Removed unused dependency.
  * Simplified migration graph rendering pipeline.
* Removed `--tree` CLI option; graph now renders in optimized format by
default.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Will Madden <madden@prisma.io>
Co-authored-by: Will Madden <madden@prisma.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants