Skip to content

Port migration show, log and graph; produce typed next actions in the command layer - #29973

Merged
wmadden-electric merged 42 commits into
mainfrom
s5-orm-pr2
Aug 11, 2026
Merged

Port migration show, log and graph; produce typed next actions in the command layer#29973
wmadden-electric merged 42 commits into
mainfrom
s5-orm-pr2

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Stacked on #29970 — review that one first; this PR's diff is only the three commands and the error conversion.

What this looks like in practice

migration graph, through the engine-built binary against examples/prisma-8-demo. The drawing is byte-identical to the commander CLI's:

$ node packages/1-framework/3-tooling/cli/dist/bin.mjs migration graph --ascii
app:
  *   b18b261  @contract
  |^  20260810T1108_add_post_engagement_counters  0b4bec6 -> b18b261  3 ops
  *   0b4bec6

An error now carries a typed action rather than a sentence:

// before
{ "code": "MIGRATION.REF_NOT_FOUND", "fix": "Create the ref with `prisma-next ref set <name> <hash>`, or pass a hash." }

// after
{ "code": "MIGRATION.REF_NOT_FOUND",
  "nextActions": [{ "kind": "user-choice", "label": "Create the ref with `prisma-next ref set <name> <hash>`, or pass a hash." }] }

The decision

Three more commands ported — migration show, migration log, migration graph — following the template migration list set in #29970. Still additive: the commander CLI keeps working and keeps owning the prisma-next binary.

The other half is where typed remediation gets produced. Ruled: no error type outside the CLI package learns about nextActions. A run-command action names an executable CLI invocation, which is knowledge only the CLI has — a foundation error raised by the language server or the Vite plugin has no business spelling one. So @internal/errors and every framework package keep raising code/why/fix prose unchanged, and the conversion happens at the command layer: ActionableCliError (a CLI-package subclass) carries typed actions from the fourteen factories that know the invocation, and normalizeError derives an action from fix prose for everything else, so an unconverted raise site still settles as a conforming envelope.

Reviewer notes

  • migration check --json changes shape: checkFailureSchema swaps fix for nextActions. It describes the CLI's own published output, so it converts with the rest. Recorded as a divergence.
  • The factories keep their fix prose alongside the typed actions, deliberately — the commander shell still renders it, and every pre-cutover PR has to leave that CLI working.
  • migration graph --dot no longer beats auto-JSON. Piping selects json and the DOT rides result.dot, so migration graph --dot | dot -Tsvg now needs --format human. That is the ruled behavior, not an accident.
  • Header paths render relative to the invocation directory, which also corrects Port the ORM CLI onto the engine: foundations and the first command #29970's migration list printing an absolute path.
  • Nine divergences total; all are listed in the branch's divergence notes and land in the divergence file at the cutover.

Verification

pnpm build, pnpm --filter @internal/cli test (129 files, 1553 tests), pnpm typecheck, pnpm lint, pnpm lint:deps, check:error-reference, test:journeys (50 files, 166 tests), test:packages (83/83), fixtures:check (no drift). Each ported command was also run through the built binary against a real example project and diffed against the commander CLI's output.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added migration graph, migration log, and migration show CLI commands.
    • Migration graphs support filtering, ASCII/Unicode output, legends, and Graphviz DOT export.
    • Migration logs provide human-readable and JSON views with timestamps and masked database details.
    • Migration details can be previewed by path, directory, hash, or reference.
  • Improvements
    • CLI failures now provide structured recommended actions and commands.
    • Relative migration paths resolve consistently from the command’s working directory.
    • Error messages and output formatting are more consistent across migration commands.
    • Command cleanup is more reliable when closing database connections.

wmadden-electric and others added 30 commits August 9, 2026 13:30
…ion marker

Config loading no longer fails wholesale on the first structural problem.

- loadConfig now returns Result<{ config, diagnostics }, CliStructuredError>.
  Structural problems in an evaluated config become CONFIG.VALIDATION_FAILED
  diagnostics tagged with the config section they concern (meta.section,
  meta.field). Commands fail (exit 2, rendering the diagnostic) only when a
  diagnostic concerns a section they read, via requireConfigSections /
  loadConfigForSections; commands not touching that section proceed.
- A config module that cannot be evaluated at all fails the load with the
  new CONFIG.EVALUATION_FAILED (previously surfaced as CLI.UNEXPECTED).
- defineConfig stops validating and throwing: a throw there happens at
  module-evaluation time and would turn every structural problem into an
  all-commands-fail evaluation error. Validation moved to the loader via
  collectConfigIssues, which reports every problem instead of the first.
- defineConfig now normalizes and stamps a non-enumerable config-format
  version marker; the loader rejects configs that were not created by the
  current defineConfig with the new CONFIG.VERSION_MARKER_MISSING (fail
  early, no best-effort reading of unmarked configs). The marker is read
  from c12's raw layer because the c12 merge drops non-enumerable
  properties.
- All production call sites (CLI commands and operations, language server,
  Vite plugin, cli-telemetry) consume the new API. Ten commands that
  previously leaked config errors as unhandled throws (stack trace,
  exit 1) — including db init and db update — now render the structured
  envelope and exit 2.
- Error registry: CONFIG.EVALUATION_FAILED and CONFIG.VERSION_MARKER_MISSING
  added; CONFIG.VALIDATION_FAILED updated with the new meta shape and
  producing sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…sting

Adds createFixtureControlClient, a ControlClient double for host and
product tests: every operation resolves realistic postgres-flavored
fixture payloads without touching a database or driver, each fixture is
overridable per test, and every call is recorded for assertions.

The implementing class `implements ControlClient` and a type test asserts
the double stays in sync with the real client, so an interface change
fails compilation here. Published via the @internal/cli
./control-api/testing subpath, which the shell build maps to
@prisma/orm-toolchain/cli/control-api/testing automatically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The framework domain is family-blind, and `lint:framework-vocabulary`
counted seven new SQL/target-vocabulary lines: six in the fixture
ControlClient defaults and one in the `CONFIG.VERSION_MARKER_MISSING`
fix text.

The fixture defaults now report neutral `FIXTURE_TARGET_ID` /
`FIXTURE_FAMILY_ID` ids (both exported so tests can assert them), an
operation label that names the model rather than the DDL, and an empty
introspection payload. The marker error points at the target package
config entrypoint without naming a target; the error reference keeps the
concrete example.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The example-migration regen writes a temporary config that spreads the
example's real config. The version marker is non-enumerable, so the
spread dropped it and every emit failed with
CONFIG.VERSION_MARKER_MISSING. Passing the spread result through
defineConfig re-stamps it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…tion

src/load.ts sat below the package coverage thresholds: loadConfigForSections
had no test at all, and none of the toConfigLoadFailure arms were exercised
beyond the plain-Error case.

Adds tests for loadConfigForSections (clean, blocking diagnostic, and load
failure), for a config module that throws a CliStructuredError, a plain
structured error, a non-Error value, or fails to resolve an import, and for
a contract that declares no inputs.

finalizeConfig grew a finalizeContractConfig half that takes a contract
section directly, so the loader no longer re-checks a contract it has
already narrowed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`lint:legacy-name` rejects `prisma-next` outside the allowed uses, and the
unresolvable-import test used it in a made-up package name. Any
unresolvable specifier works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The `orm` command family owns one config section, and the whole Prisma
Next configuration nests under it: `contract`, `db`, `migrations` and the
rest become subsections of `orm`. The engine models one section per
family, so per-subsection blocking does not survive the nesting — any
structural problem anywhere in the section blocks every `orm` command.

The validator is synchronous and total: it takes the raw section value,
returns engine diagnostics, and never throws. Structural checking reuses
`collectConfigIssues`; the emitted-artifact-as-contract-input check moves
here too, since it needs neither the filesystem nor the config directory.
Everything the loader does asynchronously — evaluating the file and
finalizing paths against its directory — stays in the bin adapter.

`@prisma/cli-engine` is pinned at an exact version so the shell and the
family share one module instance. It is a first-party Prisma package, so
it joins the release-age cooldown exemption list for the same reason
`@prisma/dev` is on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The engine and prisma/prisma each define a class called
`CliStructuredError`, and the engine's duck-typed guard accepts both. The
two are not the same shape: the engine settles on `error.nextActions`,
which prisma/prisma's class never sets, and prisma/prisma emits `fix`
prose, which the protocol has no field for. Left unconverted, an error
settles as an envelope with `nextActions: undefined` and a stray `fix`.

`normalizeError` is that conversion, and it is the only one: handlers
pass what they return through `notOk` and what a top-of-handler catch
sees through the same helper. Everything below keeps raising exactly what
it raises today. `nextActions` is always present, derived from the `fix`
prose while the transition lasts — one action per line of it, since a
multi-line fix is several pieces of advice.

`toEngineDiagnostic` is the same projection without the throw, for
callers that need a diagnostic rather than an error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The engine takes its config from the bin, and its own convenience loader
reads prisma.config.ts with the $prismaConfig marker — not the file this
bin reads. So the bin owns the load: the ORM's c12 loader evaluates the
module and finalizes contract paths against the config file's directory,
and the adapter nests the whole result as the single `orm` section.

Only failures that prevent evaluation entirely — no file, a module that
does not evaluate, a missing version marker — become diagnostics here,
tagged `section: null` so they fail exactly the commands that read
config. A config that evaluates but is structurally wrong passes through
untouched; that verdict is the section validator's.

Config discovery now takes the directory as a parameter instead of
reading process state, so the adapter honours the cwd the engine was
given. That is what lets a test harness run several projects in one
process, and it defaults to process.cwd() for every existing caller.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The `orm` command family, the second prisma-next binary, and the three
telemetry commands, all landing beside the commander program rather than
replacing it — the old bin still owns `bin.prisma-next` until the
retirement round.

The family carries the config section, an empty command map for now, and
the docs base the engine appends each diagnostic code to. `telemetry
status|enable|disable` are deliberately NOT family members: they are
commands of this binary only, the unified shell has its own, and they
retire with the binary at cutover.

Telemetry now reports from `onSettled`, so the event carries the exit
code. Two consequences, both intended: a run killed before settlement
emits nothing, and so does a run that never reaches a mounted command.
The old `telemetry`-group exemption is gone with the pre-run fire — under
`onSettled`, `telemetry disable` has already disabled by the time the
hook would send. The wire shape is unchanged: the engine's value-free
snapshot is projected into the shape the existing sanitiser reads, and
the detached sender still derives the two config-derived fields itself.

The pinned engine has no shell-level `--config`, so the bin reads the
flag off argv and strips it before the engine parses. That interim is
deleted when a version carrying the engine's own `--config` publishes.

`--db` gets one shared spec so its brief and placeholder cannot drift
across the commands that will declare it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The first ported command, and the template the rest copy: one file per
command under src/orm/<group>/, definition and handler together, the
handler calling the existing control-API operation with config and cwd
from the context rather than loading either itself.

The tree renderer keeps its line-producing code and its lines ship as the
`stdout` presentation — the engine writes them to stdout in human mode
and drops them in json mode, which keeps the json document the only
machine surface. They are deliberately not `list` blocks: block rendering
prefixes every item with "- ", which would glue a bullet onto box
drawing. Colour is off until the engine exposes its resolved colour mode.
The header details become a fields block on stderr.

The old commander `migration list` is untouched and still owns the
shipped binary; this one runs on the second bin.

Two behaviour changes fall out of the engine surface, both divergences.
`--legend` is now human-only decoration rather than an error when
combined with `--json` or `--quiet`: a handler cannot see the resolved
format, and in json mode the human presentation is never materialized, so
the legend simply does not appear. And migrations resolve against the
invocation directory rather than the config file's directory — the same
place for every invocation that does not pass the interim `--config`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Journeys have always run in-process against commander factories, calling
`process.chdir` and passing `--config` on every step — which is why their
vitest config needs `pool: forks`. `runOnEngine` is the replacement: it
builds a fresh `TestCli` per step and passes the step directory as `cwd`,
so nothing about a run is process-global.

The harness takes config as an already-evaluated record and `run()` has
no config option, so the journey's real `prisma-next.config.ts` is
evaluated here, through the same adapter the binary uses. Evaluating it
per step is what lets a step that writes or rewrites the config be picked
up by the next one, which is what `init` journeys will need.

Only `migration list` moves onto it this round — it is the only ported
command. Every other wrapper still runs the commander factory, and both
paths coexist until the shell is retired. The new journey is the proof
the harness works against a real project on disk: two planned migrations,
the tree on stdout in human mode, a clean frame stream in json mode, and
an unknown space settling as an errored envelope with typed next actions
and no `fix`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Six operation modules loaded config themselves and fell back to the
process cwd for path resolution. Under a harness that supplies a
directory per run, that is silently wrong — the operation would read a
different project than the one the command was invoked against. They now
take the already-loaded config and the invocation directory as explicit
parameters, and no file under src/control-api/ calls the config loader or
reads process.cwd. `resolveMigrationPaths` takes the directory too.

`contract emit` loses its double load in the process: the command already
loaded config to compute header display paths, and that result is now
what the operation receives.

Two ordering changes fall out of hoisting the load, both only observable
when the config is broken. `contract emit`'s header load now asks for all
five sections rather than just `contract`, so a config broken elsewhere
errors before the header prints. And `ref set` loads config before
checking the ref name, so an invalid name plus an unloadable config
reports the config error.

`migration check` was not on the list but is under the same directory and
had two process-cwd reads, so it takes a cwd too. Its
`enumerateCheckSpaces` is re-exported, making that a published signature
change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…rting

`telemetry status|enable|disable` already exist on the prisma-cli side
and will be served by the unified binary, so the prisma-next bin does not
carry its own copies — they would only die again with the commander shell
at cutover. The commander CLI still ships its versions until then; this
removes the engine ports written earlier in the round.

Reporting is unaffected: the bin still wires the engine's onSettled hook
to this repo's telemetry sender.

`@prisma/orm-toolchain` declares the same exact engine pin as
`@internal/cli`, which the shell build check requires — the two must
resolve one module instance rather than two copies with distinct brands
and classes. The init journey installs into a scratch project outside the
workspace, so it needs its own release-age exemption for that pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The typed remediation is produced in the command layer and nowhere else: a
run-command action names an executable CLI invocation, which is knowledge
only the CLI has. `src/utils/cli-errors.ts` is CLI-package code, so its
fourteen factories attach the actions directly; `@internal/errors` and
every other library keeps raising `code`/`why`/`fix` prose, and no
foundation package learns the NextAction type.

The factories now build an `ActionableCliError`, a CLI-package subclass
that carries both fields. `fix` stays because the commander shell still
renders it and every pre-cutover change is additive; the handler boundary
is what drops it. `normalizeError` therefore prefers a raised error's typed
actions over deriving them from prose, and only short-circuits for an
error the engine itself built — identity, not a duck test, since the
engine is an exact-pinned unbundled dependency with one module instance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The `--json` document `migration check` publishes is the CLI's own output, so
its failure rows carry the typed remediation rather than `fix` prose:
`checkFailureSchema` swaps the `fix: string` field for `nextActions`, and
the two producers — the integrity-violation catalogue and the explicit
per-space graph checks — build actions instead of sentences.

This is a breaking change to the published `--json` shape, and the one
user-visible change in this PR that reaches the commander shell: its human
output now prints a `next:` line per action where it printed one `fix:`
line. Recorded for the divergence file.

The two action constructors move to their own module so the check
producers and the error factories share them without the check path
importing the error module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The second ported command, and the first with a positional. The detail
block — metadata, the operation tree, the DDL preview — is a rich
renderer, so its lines keep their line-producing code and ship as the
`stdout` presentation exactly as `migration list`'s table does; the header
details become a fields block on stderr, minus the `config` row, since
config discovery is the shell's and no ported command declares `--config`.

A path-looking target used to resolve against the process working
directory. It now resolves against `ctx.cwd`, which is what makes the
command correct under a harness that supplies a cwd per run;
`resolveAppTargetPath` and `resolveTargetPathAcrossSpaces` take it
explicitly and `migration check` threads its own through unchanged.

The path-resolution helpers move to a sibling module the migration
commands share, and the two errors `migration show` raises on its own
become named factories carrying typed next actions, used by both the
commander command and the ported handler.

The old commander `migration show` is untouched and still owns the
shipped binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The ledger table keeps its line-producing code and ships as the `stdout`
presentation; the masked connection URL becomes the one header field on
stderr, the `config` row going away with per-command `--config`.

Carries the dotted-code fix the contract calls for: an unsupported target
raised `CLI.UNEXPECTED` here where every sibling raises
`MIGRATION.TARGET_UNSUPPORTED`. The ported command raises the migration
code, which is already documented in the error reference.

`--db` comes from the shared flag constant, so its brief and placeholder
cannot drift from the other database-touching commands.

The old commander `migration log` is untouched and still owns the shipped
binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The engine reserves --format, so DOT cannot become a format value. Instead
the precedence quirk goes away: with --dot the DOT text is the command's
stdout payload in human mode, and in json mode the result carries it as a
`dot` field alongside the graph document, so a caller asking for json
never gets DOT where json was promised. --dot with --legend stays an
error and --dot still ignores --space.

Tree-section building, the human rendering and the DOT rendering move to
a formatter module the commander command and the ported handler share, so
the two cannot drift while they coexist.

Header paths render relative to the invocation directory, as the commander
shell rendered them. `migration list` moves to the same helper — it was
printing the absolute migrations directory, which was a needless
divergence from the shell it replaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`migration show`, `migration log` and `migration graph` join `migration list`
on `runOnEngine`, so their journeys drive the ported commands rather than
the commander factories. Assertions move from captured stdout to the
envelope, the presented document and the exit code.

The DOT journey changes shape because its premise did: it used to pin that
an explicit `--dot` beat the auto-JSON default when stdout was piped.
Under the engine that precedence problem cannot arise — piping selects
json and the DOT rides the result as a `dot` field alongside the graph
document, so a caller who asked for json is never handed DOT. The journey
now pins both halves plus the `--legend` refusal.

`migration log` gets the end-to-end coverage it has never had: two
migrations applied, both edges reported from the live ledger, chained
so each edge starts where the last ended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The seeded operation used an operationClass the op schema rejects, so
the loader dropped the package and the assertions ran against an empty
project — including one that expected the empty-project line right
after seeding. Seed a valid operation, assert the rendered table, and
cover the empty project as its own case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The four `as Record<string, unknown>` casts in `config-validation.ts` broke
the repo rule against bare `as` in production code, and they also hid a
reporting wart: a section holding a non-object (`family: 'sql'`) walked the
descriptor fields anyway and produced one issue per missing field instead
of one clear "must be an object" issue.

`isObject` already narrows to `Record<string, unknown>` without a cast, so
`validateFamily`, `validateTarget`, and the `adapter`/`driver` walks now
use it and report a single object-type issue per malformed section.

Also rewrites the rulecard's "Default Path Resolution" example, which told
authors to use `pathe` but then showed only a bare string fallback. It now
shows what the loader actually does: resolve the config-relative default
against the config file's directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Two defects in `loadConfig`, both reported against the wrong file.

First, load failures were classified by searching the error message for
'not found', 'Cannot find' or 'ENOENT'. A config that imports a missing
package throws `Cannot find module 'x'`, so the user was told their config
file did not exist when it did. Verified against c12: a missing config
file never throws — c12 resolves nothing and returns an empty config,
which `loadConfig` already maps to CONFIG.FILE_NOT_FOUND on the
non-throwing path. So everything reaching this catch came from evaluating
a file that exists, and all of it is now CONFIG.EVALUATION_FAILED.

Second, the version-marker check accepted the marker from any c12 layer,
including `extends` bases and rc files. A config that does not itself go
through `defineConfig` passed as long as some base did. c12 puts the
requested config file first in `layers` (we pass no `overrides`), so the
check now reads only that layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
A command that reads a section it did not declare bypasses section-scoped
diagnostics: the malformed value flows into execution and fails later as
something else. `migration status` turned a malformed `contract` section
into a CONTRACT.UNREADABLE warning; `migrate` failed while resolving the
contract path.

Swept all 21 `loadConfigForSections` call sites against the sections each
one reads, directly or through a helper (`resolveContractPath`,
`readContractEnvelope`, `loadContractRawSafely`, `buildReadAggregate`,
`loadAggregateIntegrityViolations` read `contract`; `resolveMigrationPaths`
reads `migrations`). Eleven were short:

- `contract` added to migrate, migration status, migration list,
  migration graph, migration check, migration ref set, migrate --show
- `migrations` added to migration log, db sign, db verify, and the shared
  migration-command scaffold (db init, db update)

`driver` is deliberately not added to the read-only commands that build a
control stack. `createControlStack` stores the descriptor but only
database operations use it, and every command that connects already
declares `driver`; requiring it everywhere would defeat section scoping.

The regression test lives in the integration suite rather than the CLI
package: the CLI suite runs with `isolate: false`, so a new file importing
command modules collides with the per-file `@internal/config-loader` mocks
its neighbours already register. The integration test drives the real
commands against a real malformed config and covers the command handlers
that are module-private.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ycle

The double served every operation from fixtures whether or not `connect()`
had been awaited, so a caller that forgot to connect passed its tests and
then failed against the real client.

It now tracks initialization the way `ControlClientImpl` does: `init()` is
idempotent, `connect()` calls it, and the eleven operations the real
client routes through `ensureConnected` (verify, schemaVerify, sign,
dbInit, dbUpdate, dbVerify, readMarker, readAllMarkers, readLedger,
migrate, introspect) reject with DRIVER.NOT_CONNECTED while disconnected.
The five that only need `init()` in the real client (toSchemaView,
inferPslContract, getPslBlockDescriptors, toOperationPreview, emit) keep
working without a connection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…s from

`resolveConfigInputs` required family, target, adapter, driver and
extensions before it knew whether the project was PSL or TypeScript. A
TypeScript contract project derives its inputs from `contract.source`
alone and never builds a control stack, so a diagnostic on an unrelated
control section stopped formatting and analysis for it.

It now loads once and narrows twice: `contract` and `formatter` for every
project, and the control sections only on the PSL branch that actually
assembles the stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The real client connects from options.connection before it checks for
a driver, so an operation that supplies one needs no prior connect().
The double rejected first, failing calls production accepts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Reworking the load-failure classification and the marker check changed
load.ts branch count and dropped it under the 95% threshold. The three
uncovered arms are defensive fallbacks for conditions their callees
exclude: a non-Error throw, and two c12 fields that are always set on
the paths that reach them. Each is annotated with its reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…to s5-orm-cli-port

Brings in the seven review-fix commits that landed on the base branch after
this port forked from it, and reconciles them with the port's refactor.

The two conflicts are the same shape: the review fix added `contract` to a
`loadConfigForSections` call inside a control-API operation, while the port
had removed that call entirely — operations now take `config` and `cwd` as
parameters. Kept the port's parameterized form and moved the added section
to the caller that now loads config:

- `migrate --show` (`commands/migrate.ts`) gains `contract`
- `ref set` (`commands/ref.ts`) gains `contract`

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric and others added 7 commits August 11, 2026 18:21
Every conflict is the same shape: main carries the squash-merged #29936 work,
this branch carries the same work plus the port's refactor on top, so ours is
strictly newer on every conflicted file. Verified that main and the #29936
branch tip are byte-identical across the CLI, config, config-loader and
integration paths before resolving in favour of ours.

Three things the merge could not settle on its own:

- `config-loader/src/load.ts` keeps the three `/* v8 ignore */` annotations.
  Main does not have them, and without them `src/load.ts` drops to 94% branch
  coverage. Also keeps the port's `cwd` option on `loadConfig`.
- `executeRefDeleteCommand` had main's `loadConfigForSections(options.config,
  ...)` left behind by a clean auto-merge, where `options.config` is now a
  loaded config object rather than a path. Dropped it; the caller loads.
- Three test files kept an `ok` import that only main's config-loader mock
  used.

The lockfile takes main's wholesale, then `pnpm install` re-adds the
`@prisma/cli-engine` pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Picks up main and the seven #29936 review-fix commits through the rebased
first PR. No conflicts: this branch descends from the pre-merge tip of
s5-orm-cli-port, so every incoming change lands on files it did not touch
or touched compatibly.

Audited the section lists afterwards — every `loadConfigForSections` call
site on this branch matches the reviewed list from #29936, and the three
newly ported read commands (`migration show`, `migration log`,
`migration graph`) take the whole ORM config as one engine section, so
per-section scoping does not apply to them.

The lockfile is regenerated and matches s5-orm-cli-port exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
None of these came from the merge — every one was already failing on this
branch before it.

- The tarball smoke tests install packed shells into a scratch project. The
  repo's release-age cooldown reaches that project through the outer
  workspace, but its exemption list does not, so the `@prisma/cli-engine`
  pin this port adds to `@prisma/orm-toolchain` failed the install for the
  first 24 hours after the engine published. The scratch workspace now turns
  the cooldown off; it only ever resolves dependencies the repo already vets.
- The `migration list` journey ran without per-test timeouts. It passes
  under `test:journeys` (which sets a long timeout) but `test:integration`
  picks the same files up under the 200 ms default, where every step that
  emits a contract or plans a migration runs out of time. Every sibling
  journey already passes `timeouts.typeScriptCompilation`; this one now
  does too.
- The framework-vocabulary ratchet counted the telemetry doc comment's
  "collection" as a Mongo collection. Reworded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Two framework-vocabulary sites the ratchet was right to flag, and one it was
not.

`errorPathUnreachable` lives in the family-blind CLI layer but told the user
about a "NOT NULL column". Every other family says that differently, so the
prose now says "a required field". The SQL wording stays where it belongs, in
packages/3-targets.

`MigrationLogTable` is the rendered `migration log` output, not a database
table — the same case as `SymbolTable`, which the allow list already carries.
Allowing the compound drops the recorded count to 794 and locks that in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…text

The nextActions conversion copied two `errorPathUnreachable` sentences
verbatim, so the same wording now lives twice in one call and only the `fix`
copy picked up the family-blind rewording. Both read from a constant, which
also puts the branch back on the framework-vocabulary count.

The `migration graph` section builder's doc comment said "column widths" for
what are tree layout widths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric
wmadden-electric requested a review from a team as a code owner August 11, 2026 17:17
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: e7cb2565-5567-4c85-a9c0-268130f2fa47

📥 Commits

Reviewing files that changed from the base of the PR and between e4cddfd and ec753a5.

📒 Files selected for processing (18)
  • packages/1-framework/3-tooling/cli/src/commands/db-init.ts
  • packages/1-framework/3-tooling/cli/src/commands/db-sign.ts
  • packages/1-framework/3-tooling/cli/src/commands/db-update.ts
  • packages/1-framework/3-tooling/cli/src/commands/db-verify.ts
  • packages/1-framework/3-tooling/cli/src/commands/inspect-live-schema.ts
  • packages/1-framework/3-tooling/cli/src/commands/migrate.ts
  • packages/1-framework/3-tooling/cli/src/commands/migration-log.ts
  • packages/1-framework/3-tooling/cli/src/commands/migration-status.ts
  • packages/1-framework/3-tooling/cli/src/control-api/operations/migrate-show.ts
  • packages/1-framework/3-tooling/cli/src/orm/cli.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/graph.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/list.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/log.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/show.ts
  • packages/1-framework/3-tooling/cli/src/orm/normalize-error.ts
  • packages/1-framework/3-tooling/cli/src/utils/command-helpers.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-log.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/normalize-error.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/1-framework/3-tooling/cli/src/orm/cli.ts
  • packages/1-framework/3-tooling/cli/src/orm/normalize-error.ts
  • packages/1-framework/3-tooling/cli/test/orm/normalize-error.test.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/show.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/list.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/log.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/graph.ts

📝 Walkthrough

Walkthrough

The CLI adds migration graph, log, and show commands. It centralizes migration paths and graph formatting. Migration errors and check failures now expose typed remediation actions. Client cleanup now suppresses close failures.

Changes

Migration remediation and error contracts

Layer / File(s) Summary
Structured remediation actions
packages/1-framework/3-tooling/cli/src/commands/json/schemas.ts, packages/1-framework/3-tooling/cli/src/control-api/operations/migration-check.ts, packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts, packages/1-framework/3-tooling/cli/src/utils/next-actions.ts
Check failures and CLI errors now provide typed nextActions arrays.
Diagnostic normalization
packages/1-framework/3-tooling/cli/src/orm/normalize-error.ts, packages/1-framework/3-tooling/cli/src/utils/integrity-violation-to-check-failure.ts
Normalization preserves typed actions and derives actions from legacy fixes when needed.

Migration commands and shared infrastructure

Layer / File(s) Summary
Shared paths and graph formatting
packages/1-framework/3-tooling/cli/src/orm/migration/paths.ts, packages/1-framework/3-tooling/cli/src/utils/migration-path-target.ts, packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-sections.ts, packages/1-framework/3-tooling/cli/src/commands/migration-graph.ts
Migration paths resolve from the invocation directory. Graph tree, section, and DOT rendering use shared formatters.
Migration graph command
packages/1-framework/3-tooling/cli/src/orm/migration/graph.ts, packages/1-framework/3-tooling/cli/src/orm/cli.ts, packages/1-framework/3-tooling/cli/src/orm/family.ts
Adds the migration graph command with space filtering, tree or DOT output, legends, and multiple presentations.
Migration log and show commands
packages/1-framework/3-tooling/cli/src/orm/migration/log.ts, packages/1-framework/3-tooling/cli/src/orm/migration/show.ts, packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts
Adds ledger reporting and migration preview commands with validation, structured errors, and multiple presentations.

Cleanup and validation

Layer / File(s) Summary
Quiet client cleanup
packages/1-framework/3-tooling/cli/src/utils/command-helpers.ts, packages/1-framework/3-tooling/cli/src/commands/*, packages/1-framework/3-tooling/cli/src/control-api/operations/migrate-show.ts
Control-client cleanup uses closeQuietly across database and migration commands.
Command and integration coverage
packages/1-framework/3-tooling/cli/test/*, test/integration/test/cli-journeys/*, test/integration/test/utils/journey-test-helpers.ts
Tests cover graph rendering, ledger handling, migration resolution, typed actions, relative paths, cleanup behavior, result envelopes, and engine-based execution.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as ORM CLI
  participant Command as migration graph/log/show
  participant Loader as migration loader or control client
  participant Formatter as migration formatter
  CLI->>Command: invoke migration command
  Command->>Loader: load graph, ledger, or migration preview
  Loader-->>Command: return migration data
  Command->>Formatter: render selected presentation
  Formatter-->>CLI: return human, stdout, or JSON result
Loading

Possibly related PRs

Suggested labels: lgtm

Suggested reviewers: wmadden

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% 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 clearly summarizes the main changes: porting migration commands and adding typed next actions in the command layer.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch s5-orm-pr2

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.

@pkg-pr-new

pkg-pr-new Bot commented Aug 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

prisma-next

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

@prisma/orm-extension-arktype-json

npm i https://pkg.pr.new/@prisma/orm-extension-arktype-json@29973

@prisma/orm-extension-middleware-cache

npm i https://pkg.pr.new/@prisma/orm-extension-middleware-cache@29973

@prisma/orm-extension-paradedb

npm i https://pkg.pr.new/@prisma/orm-extension-paradedb@29973

@prisma/orm-extension-pgvector

npm i https://pkg.pr.new/@prisma/orm-extension-pgvector@29973

@prisma/orm-extension-postgis

npm i https://pkg.pr.new/@prisma/orm-extension-postgis@29973

@prisma/orm-extension-supabase

npm i https://pkg.pr.new/@prisma/orm-extension-supabase@29973

@prisma/orm-family-mongo

npm i https://pkg.pr.new/@prisma/orm-family-mongo@29973

@prisma/orm-family-sql

npm i https://pkg.pr.new/@prisma/orm-family-sql@29973

@prisma/orm-framework

npm i https://pkg.pr.new/@prisma/orm-framework@29973

@prisma/orm-mongo

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

@prisma/orm-postgres

npm i https://pkg.pr.new/@prisma/orm-postgres@29973

@prisma/orm-sqlite

npm i https://pkg.pr.new/@prisma/orm-sqlite@29973

@prisma/orm-target-mongo

npm i https://pkg.pr.new/@prisma/orm-target-mongo@29973

@prisma/orm-target-postgres

npm i https://pkg.pr.new/@prisma/orm-target-postgres@29973

@prisma/orm-target-sqlite

npm i https://pkg.pr.new/@prisma/orm-target-sqlite@29973

@prisma/orm-toolchain

npm i https://pkg.pr.new/@prisma/orm-toolchain@29973

commit: ec753a5

@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
postgres / no-emit 170.11 KB (0%)
postgres / emit 147.84 KB (0%)
mongo / no-emit 100.41 KB (0%)
mongo / emit 90.25 KB (0%)
cf-worker / no-emit 194.31 KB (0%)
cf-worker / emit 169.66 KB (0%)

@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

🧹 Nitpick comments (3)
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-sections.ts (1)

98-107: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider escaping dirName in the DOT label.

edge.dirName is interpolated into a quoted DOT attribute without escaping. A directory name that contains " or \ produces malformed DOT. Directory names come from disk, so a renamed directory can break the output. The truncation hazard is documented, but escaping is not addressed.

♻️ Proposed hardening
+function escapeDotText(text: string): string {
+  return text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
+}
+
 export function renderMigrationGraphDot(graph: MigrationGraph): string {
   const lines = ['digraph migrations {'];
   for (const edge of graph.migrationByHash.values()) {
     lines.push(
-      `  "${edge.from.slice(0, 12)}" -> "${edge.to.slice(0, 12)}" [label="${edge.dirName}"];`,
+      `  "${edge.from.slice(0, 12)}" -> "${edge.to.slice(0, 12)}" [label="${escapeDotText(edge.dirName)}"];`,
     );
   }
   lines.push('}');
   return lines.join('\n');
 }
🤖 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-graph-sections.ts`
around lines 98 - 107, Update renderMigrationGraphDot to escape edge.dirName
before interpolating it into the quoted DOT label, handling at least backslashes
and double quotes so directory names cannot produce malformed DOT. Keep the
existing graph iteration, hash truncation, and output structure unchanged.
packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts (1)

355-364: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the space parameter in the new public function.

The parameter holds a migration, not a contract space. The name comes from the private formatSpaceShowBlock. Since renderMigrationShowLines is a new public API, use migration to match the type name.

♻️ Proposed rename
 export function renderMigrationShowLines(
-  space: MigrationShowPresent,
+  migration: MigrationShowPresent,
   options: { readonly colorize: boolean },
 ): readonly string[] {
-  return formatSpaceShowBlock(space, options.colorize);
+  return formatSpaceShowBlock(migration, options.colorize);
 }
🤖 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/migrations.ts` around
lines 355 - 364, Rename the parameter of the public renderMigrationShowLines
function from space to migration, and update its use in the formatSpaceShowBlock
call while preserving the existing behavior.
packages/1-framework/3-tooling/cli/src/orm/cli.ts (1)

71-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The engine command registry is declared twice. Both files list the same four migration * keys mapped to the same command objects. Each new command must be added in two places, and a missed entry makes the command reachable through one entry point only.

  • packages/1-framework/3-tooling/cli/src/orm/cli.ts#L71-L74: consume a single shared command map instead of redeclaring the four entries in BIN_COMMANDS.
  • packages/1-framework/3-tooling/cli/src/orm/family.ts#L17-L20: export the shared command map from one module and pass it to defineCommandFamily.
🤖 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/orm/cli.ts` around lines 71 - 74,
Eliminate the duplicated migration command registry by exporting one shared
command map from defineCommandFamily in
packages/1-framework/3-tooling/cli/src/orm/family.ts#L17-L20, then have
BIN_COMMANDS consume that map in
packages/1-framework/3-tooling/cli/src/orm/cli.ts#L71-L74 instead of redeclaring
the four migration entries; keep all command mappings synchronized through the
shared export.
🤖 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/orm/migration/log.ts`:
- Around line 107-122: Guard the cleanup in the finally block around
client.close() so any rejection is handled without replacing the structured
notOk result from the catch block. Update the try/catch/finally flow using the
existing client symbol, preserving the original migration or connection error as
the returned outcome when cleanup fails.

---

Nitpick comments:
In `@packages/1-framework/3-tooling/cli/src/orm/cli.ts`:
- Around line 71-74: Eliminate the duplicated migration command registry by
exporting one shared command map from defineCommandFamily in
packages/1-framework/3-tooling/cli/src/orm/family.ts#L17-L20, then have
BIN_COMMANDS consume that map in
packages/1-framework/3-tooling/cli/src/orm/cli.ts#L71-L74 instead of redeclaring
the four migration entries; keep all command mappings synchronized through the
shared export.

In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-sections.ts`:
- Around line 98-107: Update renderMigrationGraphDot to escape edge.dirName
before interpolating it into the quoted DOT label, handling at least backslashes
and double quotes so directory names cannot produce malformed DOT. Keep the
existing graph iteration, hash truncation, and output structure unchanged.

In `@packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts`:
- Around line 355-364: Rename the parameter of the public
renderMigrationShowLines function from space to migration, and update its use in
the formatSpaceShowBlock call while preserving the existing behavior.
🪄 Autofix

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 Plus

Run ID: 81019d34-d75f-42ee-a5af-fce37acac8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 7018399 and e4cddfd.

📒 Files selected for processing (31)
  • packages/1-framework/3-tooling/cli/src/commands/json/schemas.ts
  • packages/1-framework/3-tooling/cli/src/commands/migration-check.ts
  • packages/1-framework/3-tooling/cli/src/commands/migration-graph.ts
  • packages/1-framework/3-tooling/cli/src/commands/migration-show.ts
  • packages/1-framework/3-tooling/cli/src/control-api/operations/migration-check.ts
  • packages/1-framework/3-tooling/cli/src/orm/cli.ts
  • packages/1-framework/3-tooling/cli/src/orm/family.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/graph.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/list.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/log.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/paths.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/show.ts
  • packages/1-framework/3-tooling/cli/src/orm/normalize-error.ts
  • packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-sections.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts
  • packages/1-framework/3-tooling/cli/src/utils/integrity-violation-to-check-failure.ts
  • packages/1-framework/3-tooling/cli/src/utils/migration-path-target.ts
  • packages/1-framework/3-tooling/cli/src/utils/next-actions.ts
  • packages/1-framework/3-tooling/cli/test/cli-errors.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-graph-coloured-output.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-show.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-graph.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-list.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-log.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-show.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/normalize-error.test.ts
  • test/integration/test/cli-journeys/migration-graph-dot.e2e.test.ts
  • test/integration/test/cli-journeys/migration-log.e2e.test.ts
  • test/integration/test/cli-journeys/migration-show-reachability.e2e.test.ts
  • test/integration/test/utils/journey-test-helpers.ts

Comment thread packages/1-framework/3-tooling/cli/src/orm/migration/log.ts
wmadden-electric and others added 3 commits August 11, 2026 23:58
Ten review threads, plus one the same review pass surfaced in
normalizeError. The structural ones first:

- `defineOrmCommand` wraps `defineCommand` with a top-of-handler catch
  that runs `normalizeError`. Without it a thrown error settles as a
  malformed envelope: both repos export a class named
  `CliStructuredError`, so the engine accepts prisma/prisma's, calls its
  `toEnvelope()`, and emits a non-protocol `fix` with no `nextActions`.
  The remaining 21 commands inherit the boundary by being defined
  through it.
- `normalizeError` now recognizes both of the repo's error shapes.
  `isRaisedError` required a `toEnvelope` method, which values from
  `structuredError()` do not have, so every one of them flattened to
  `CLI.UNEXPECTED` and lost its code, `why`, `fix`, `where` and `meta`.
  It also promised a dotted code while checking only for a string; it
  now uses `isStructuredError`, which holds the dotted-code pattern. A
  code that does not conform maps to `CLI.UNEXPECTED` and survives in
  `meta` rather than producing a docs link to a page that does not
  exist.
- `runOrmCli` gained the startup failure boundary. Everything before the
  engine owns a run has no invocation to attach a diagnostic to, so a
  throw reached the user as a raw stack trace.
- `migrations.dir` is finalized against the config file's directory, as
  `contract` already was, and `resolveMigrationPaths` resolves a
  relative `--config` from its `cwd` argument instead of
  `process.cwd()`. Together these stop `cd /tmp && prisma-next migration
  list --config /app/prisma-next.config.ts` reading `/tmp/migrations`.

And the local ones:

- `migrate --show --from <ref>` no longer requires a `driver` section.
  The offline plan never reads one, but the section list demanded it, so
  a broken driver config failed a command that does not use it.
- `stripConfigFlag` rejects an empty `--config=` and a `--config` whose
  next token is a flag; both were consumed silently, the second
  swallowing the following flag as the path.
- The artifact-collision check compares normalized paths, so
  `./out/contract.json` against a derived `out/contract.json` is caught.
- Scratch projects install under the repo's release-age cooldown with
  its narrow first-party exemption list, rather than disabling the
  cooldown for every dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Carries the review fixes down the stack. Two conflicts, both additive on
both sides: the bin's import block, and the normalizeError test file.

`migration graph|log|show` are defined through `defineOrmCommand` now,
like `migration list` — the wrapper is the whole point of the boundary,
so a command that skips it keeps the defect the wrapper exists to
remove.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
A rejection thrown out of a `finally` supersedes the value the
`try`/`catch` already returned. Every `finally { await client.close() }`
in the CLI therefore had a hole: if the close rejected, the mapped
error the catch had just built was discarded and the close's own error
escaped instead.

It bites hardest in the case users actually hit. A `connect()` that
fails leaves nothing to close, so the close is the most likely to
reject exactly when there is a real connection error to report — and
the user is shown an unmapped failure in place of it.

`closeQuietly` is the single hang-up, and all eleven sites use it: the
command has already decided its result, and failing to hang up cannot
change it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Base automatically changed from s5-orm-cli-port to main August 11, 2026 22:28
wmadden-electric and others added 2 commits August 12, 2026 00:31
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

# Conflicts:
#	packages/1-framework/3-tooling/cli/src/orm/cli.ts
#	packages/1-framework/3-tooling/cli/src/orm/family.ts
#	packages/1-framework/3-tooling/cli/src/orm/migration/list.ts
#	packages/1-framework/3-tooling/cli/src/orm/normalize-error.ts
#	packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts
#	packages/1-framework/3-tooling/cli/test/orm/migration-list.test.ts
#	packages/1-framework/3-tooling/cli/test/orm/normalize-error.test.ts
#	test/integration/test/utils/journey-test-helpers.ts
Repo-wide vitest runs with isolate: false, so whichever ORM test file
loads src/orm/cli first fixes the control client the module tree is
bound to. This file imported it at module scope, so a sibling loading
first left it driving the real client — green locally, red in CI on the
other ordering. Import after the reset, inside beforeAll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric
wmadden-electric added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 2ce37ba Aug 11, 2026
18 checks passed
@wmadden-electric
wmadden-electric deleted the s5-orm-pr2 branch August 11, 2026 23:41
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.

3 participants