Port migration plan, new and status onto the CLI engine; redirect the four retired status flags - #29982
Conversation
…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>
…rker-and-control-client-double
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>
pnpm links dependencies strictly per package, and the CLI package does not declare @prisma/orm-postgres, @prisma/orm-mongo or dotenv. A test project created in the OS temp directory therefore cannot resolve them, so a scaffolded prisma-next.config.ts written there fails to import its own config entrypoint. That is invisible when the test happens to resolve from the process working directory instead, and it fails in CI. The fixture package declares those dependencies and nothing else; test projects are created underneath it so Node walks up into its node_modules. Its ephemeral test-* subdirectories are gitignored, matching how the integration suite already treats its own fixture apps. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The offline write commands ran against a project in the OS temp directory, outside the repository, where no workspace dependency is linked. It joins the rest of the ORM command tests in the fixture package that declares them. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
init chooses the package manager by walking up from the project it is scaffolding. A project inside the repository walks out of the fixture package and finds the repository's pnpm lockfile, so init treats it as part of a pnpm workspace and reports the repository's catalog as the project's own. Naming a manager on the fixture package ends the walk there and gives a test project the same neutral answer it had in the OS temp directory. A test that wants a different manager writes a lockfile into its own project, which detection finds first. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The fixture package declared a package manager to stop detection walking up into the repository. That was fighting the point of putting test projects in the repository: a project here really is inside a pnpm workspace, and pnpm is the correct answer. Which manager runs an install belongs to the CLI engine, so no ORM test should pin it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> # Conflicts: # .agents/rules/no-family-vocabulary-in-framework.mdc # scripts/lint-framework-vocabulary.config.json # scripts/lint-framework-vocabulary.mjs # scripts/lint-framework-vocabulary.test.mjs
Two ways ANSI reached output that should carry none. The on-path label applied the palette's emphasis whatever colorize said, so a run with colour turned off on a capable terminal still got bold on the directory name — every sibling painter already honoured the flag. And the span reader matched an SGR sequence carrying one parameter, so a sequence carrying several survived into a span and put raw escape bytes in front of the engine. It now reads the whole parameter list and applies each code, which strips the shapes a 256-colour or combined style emits. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The vocabulary checker is now a Biome GritQL plugin, so the old `framework-vocabulary-ignore` comment no longer suppresses anything and was left behind as an inert comment by the merge. Restate it as the suppression the plugin actually reads, keeping the original reasoning. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-labels.ts (1)
310-328: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAlign the on-path branch with the off-path fallback.
The off-path branch at line 318 falls back to
style.dirNamewhenopts.colorizeis false. The on-path branch at line 315 does not;rolePainterreturnsidentityin that case. An on-path migration name therefore skipsstyle.dirNamewhile every other name goes through it. If a caller supplies a styler that adds non-colour decoration, on-path names lose it.♻️ Proposed fix
if (highlight === 'on-path') { // On-path: tint the name with the on-path green (matching the route's green // glyphs in the gutter), not bolded. - dirNameStyler = rolePainter(palette, opts.colorize, 'on-path'); + dirNameStyler = opts.colorize ? rolePainter(palette, true, 'on-path') : style.dirName; hashOverride = undefined;🤖 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-labels.ts` around lines 310 - 328, Update the on-path branch in the highlight selection logic to use style.dirName when opts.colorize is false, matching the off-path fallback; retain the rolePainter-based on-path styler when colorization is enabled and leave hashOverride unchanged.
🧹 Nitpick comments (3)
packages/1-framework/3-tooling/cli/src/orm/cli.ts (1)
60-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the Biome suppression.
Line 67 disables
lint/plugin/no-family-vocabulary. The TypeScript rules prohibit Biome suppressions. Replace the local suppression with an approved adapter or request an explicit exception.As per coding guidelines, “Never suppress Biome lints.”
🤖 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 60 - 69, Remove the biome-ignore suppression in runtimeFromProcess and preserve the stderr.columns behavior without inline Biome suppression. Use the project’s approved adapter for the terminal-width property, or obtain an explicit lint exception through the established configuration process if no adapter exists.Source: Coding guidelines
packages/1-framework/3-tooling/cli/src/orm/migration/show.ts (1)
41-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winOperation and preview block builders are duplicated across two commands.
operationNodes,operationBlocks, andpreviewBlocksexist in both files with the same structure and the same destructive warning text. The differences are limited to the tree root label, the empty-operations result, and the optional-preview guard.
packages/1-framework/3-tooling/cli/src/orm/migration/show.ts#L41-L100: move these three helpers into a shared formatter module and accept the tree root label and the empty-operations block as parameters.packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts#L50-L99: delete the local copies and call the shared helpers, passingresult.dir ?? 'operations'as the label and an empty block list for the empty case.🤖 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/migration/show.ts` around lines 41 - 100, Extract operationNodes, operationBlocks, and previewBlocks into a shared migration formatter module, parameterizing operationBlocks with the tree root label and empty-operations block while preserving the shared destructive warning and preview formatting. In packages/1-framework/3-tooling/cli/src/orm/migration/show.ts#L41-L100, replace the local helpers with calls to the shared implementations using show’s existing values. In packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts#L50-L99, remove the duplicate helpers and call the shared helpers with result.dir ?? 'operations' and an empty block list for the empty case, retaining the optional-preview guard.packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-palette.ts (1)
21-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive the lane count from the lane arrays.
LANE_COUNTis a separate constant.ANSI_LANESandTONE_LANESeach hold six entries today. If a colour is added to one array only,lane % LANE_COUNTsilently ignores it and the??fallbacks hide the mismatch.♻️ Proposed fix
-const LANE_COUNT = 6; - const forced = createColors({ useColor: true }); const ANSI_LANES = [ forced.white, forced.cyan, forced.yellow, forced.blueBright, forced.magenta, forced.green, ] as const;- lane: (lane, text) => (ANSI_LANES[lane % LANE_COUNT] ?? ((value: string) => value))(text), + lane: (lane, text) => (ANSI_LANES[lane % ANSI_LANES.length] ?? ((value: string) => value))(text),- lane: (lane, text) => tonePainter(TONE_LANES[lane % LANE_COUNT] ?? 'color-1')(text), + lane: (lane, text) => tonePainter(TONE_LANES[lane % TONE_LANES.length] ?? 'color-1')(text),Also applies to: 45-45
🤖 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-palette.ts` around lines 21 - 35, Remove the standalone LANE_COUNT constant and derive the lane count from the lane array length, using the same derived value wherever lane modulo or bounds logic is applied. Ensure ANSI_LANES and TONE_LANES remain aligned, and avoid fallback behavior that hides mismatched array lengths.
🤖 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/paths.ts`:
- Around line 25-34: Replace the cwd-derived path from projectConfigPathFor with
the selected configuration’s LoadedConfig.path when invoking migration new and
migration plan handlers. Propagate that effective path through the relevant
handler context so createProjectSpecifierResolver anchors manifest discovery to
the actual config location, and add a regression test covering --config pointing
outside cwd.
In `@packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts`:
- Around line 42-45: Update the migration plan path construction for
emittedExtensionDirs and written paths to use the configured migrations
directory from config.migrations.dir instead of the hardcoded "migrations"
segment. Preserve the existing spaceId and dirName path structure in both
rendering locations.
In `@packages/1-framework/3-tooling/cli/src/orm/migration/status-findings.ts`:
- Around line 72-78: Add severity: 'warn' to the MIGRATION.MISSING_INVARIANTS
JSON schema and both document construction paths, including the object returned
by the missing-invariants finding builder. Update affected fixtures to include
the new required severity field.
In `@packages/1-framework/3-tooling/cli/src/orm/migration/status.ts`:
- Around line 399-402: Update
packages/1-framework/3-tooling/cli/src/orm/migration/status.ts at lines 399-402
so markerNotInHistoryFinding receives entry.space and includes that space in its
message and meta, preserving distinct findings for diverged spaces. At lines
480-495, record the space whose hasMigrationPath check fails and pass that
space’s marker and target hash to buildNoPathSummary instead of the app-space
values.
---
Outside diff comments:
In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-labels.ts`:
- Around line 310-328: Update the on-path branch in the highlight selection
logic to use style.dirName when opts.colorize is false, matching the off-path
fallback; retain the rolePainter-based on-path styler when colorization is
enabled and leave hashOverride unchanged.
---
Nitpick comments:
In `@packages/1-framework/3-tooling/cli/src/orm/cli.ts`:
- Around line 60-69: Remove the biome-ignore suppression in runtimeFromProcess
and preserve the stderr.columns behavior without inline Biome suppression. Use
the project’s approved adapter for the terminal-width property, or obtain an
explicit lint exception through the established configuration process if no
adapter exists.
In `@packages/1-framework/3-tooling/cli/src/orm/migration/show.ts`:
- Around line 41-100: Extract operationNodes, operationBlocks, and previewBlocks
into a shared migration formatter module, parameterizing operationBlocks with
the tree root label and empty-operations block while preserving the shared
destructive warning and preview formatting. In
packages/1-framework/3-tooling/cli/src/orm/migration/show.ts#L41-L100, replace
the local helpers with calls to the shared implementations using show’s existing
values. In packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts#L50-L99,
remove the duplicate helpers and call the shared helpers with result.dir ??
'operations' and an empty block list for the empty case, retaining the
optional-preview guard.
In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-palette.ts`:
- Around line 21-35: Remove the standalone LANE_COUNT constant and derive the
lane count from the lane array length, using the same derived value wherever
lane modulo or bounds logic is applied. Ensure ANSI_LANES and TONE_LANES remain
aligned, and avoid fallback behavior that hides mismatched array lengths.
🪄 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: 8576ea22-7660-4c63-8d58-5baa134ab267
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (58)
packages/1-framework/3-tooling/cli/.gitignorepackages/1-framework/3-tooling/cli/package.jsonpackages/1-framework/3-tooling/cli/src/orm/cli.tspackages/1-framework/3-tooling/cli/src/orm/family.tspackages/1-framework/3-tooling/cli/src/orm/load-config.tspackages/1-framework/3-tooling/cli/src/orm/migration/graph.tspackages/1-framework/3-tooling/cli/src/orm/migration/list.tspackages/1-framework/3-tooling/cli/src/orm/migration/log.tspackages/1-framework/3-tooling/cli/src/orm/migration/new.tspackages/1-framework/3-tooling/cli/src/orm/migration/paths.tspackages/1-framework/3-tooling/cli/src/orm/migration/plan.tspackages/1-framework/3-tooling/cli/src/orm/migration/show.tspackages/1-framework/3-tooling/cli/src/orm/migration/status-findings.tspackages/1-framework/3-tooling/cli/src/orm/migration/status.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-command-render.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-labels.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-occlusion-render.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-palette.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-sections.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-space-render.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-render.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-styler.tspackages/1-framework/3-tooling/cli/src/utils/formatters/tone-markup.tspackages/1-framework/3-tooling/cli/test/control-api/contract-snapshot-resolution.test.tspackages/1-framework/3-tooling/cli/test/control-api/migrate-show-plan.test.tspackages/1-framework/3-tooling/cli/test/control-api/migration-graph-entries.test.tspackages/1-framework/3-tooling/cli/test/control-api/ref-advancement.test.tspackages/1-framework/3-tooling/cli/test/control-api/refs.test.tspackages/1-framework/3-tooling/cli/test/fixture-app/README.mdpackages/1-framework/3-tooling/cli/test/fixture-app/package.jsonpackages/1-framework/3-tooling/cli/test/orm/cli.test.tspackages/1-framework/3-tooling/cli/test/orm/fixtures/offline-project.tspackages/1-framework/3-tooling/cli/test/orm/load-config.test.tspackages/1-framework/3-tooling/cli/test/orm/migration-graph.test.tspackages/1-framework/3-tooling/cli/test/orm/migration-list.test.tspackages/1-framework/3-tooling/cli/test/orm/migration-log.test.tspackages/1-framework/3-tooling/cli/test/orm/migration-new.test.tspackages/1-framework/3-tooling/cli/test/orm/migration-plan.test.tspackages/1-framework/3-tooling/cli/test/orm/migration-show.test.tspackages/1-framework/3-tooling/cli/test/orm/migration-status.test.tspackages/1-framework/3-tooling/cli/test/utils/formatters/tone-markup.test.tspackages/1-framework/3-tooling/cli/test/utils/test-project-dir.tspackages/9-public/@prisma/orm-toolchain/package.jsontest/integration/package.jsontest/integration/test/cli-journeys/data-transform-not-null-backfill.e2e.test.tstest/integration/test/cli-journeys/data-transform-nullable-tightening.e2e.test.tstest/integration/test/cli-journeys/data-transform-type-change.e2e.test.tstest/integration/test/cli-journeys/drift-deleted-root.e2e.test.tstest/integration/test/cli-journeys/invariant-routing.e2e.test.tstest/integration/test/cli-journeys/marker-read-errors-status-empty-migrations.e2e.test.tstest/integration/test/cli-journeys/migration-list.e2e.test.tstest/integration/test/cli-journeys/migration-log.e2e.test.tstest/integration/test/cli-journeys/migration-status-diagnostics.e2e.test.tstest/integration/test/cli-journeys/multi-step-migration.e2e.test.tstest/integration/test/cli-journeys/ref-routing.e2e.test.tstest/integration/test/cli-journeys/schema-evolution-migrations.e2e.test.tstest/integration/test/cli.migration-plan-ref-aware.e2e.test.tstest/integration/test/utils/journey-test-helpers.ts
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/family.ts # packages/1-framework/3-tooling/cli/test/orm/cli.test.ts
… directory `migration plan` hardcoded the `migrations` path segment when rendering `emittedExtensionDirs` rows and the review next-action, so a project with `migrations.dir` configured pointed users at directories that do not exist. The presentation now receives the configured directory (relative to the invocation dir) and joins extension-space paths under it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…s document, no module mock Three changes to `migration status` and its published JSON document: - The MIGRATION.MISSING_INVARIANTS document now carries `severity: "warn"` like its sibling diagnostics, in the arktype schema and both construction paths (the ORM finding builder and the pre-engine command). - Per-space conditions are no longer reported with app-space identity: the marker-not-in-history finding names the space it diverged in (message and diagnostic meta), and the no-path / diverged headline uses the failing space's marker and target hashes instead of the app space's. - The migration-status test drives the real control client over fake family/driver descriptors seeded through createTestCli config, replacing the vi.mock of src/control-api/client and the module-registry reset dance. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Mount the full command tree: main's createBinCommands factory (db init, db schema, format, migrate, ref set/list/delete) plus this branch's migration plan/new/status. The four migration-status flag redirects stay because migration status is now mounted. Deduplicate appRefsDirFor in migration/paths.ts (both sides added it; main's copy kept) and drop the commander-command imports in journey-test-helpers that both sides retired in favour of engine runs. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> # Conflicts: # test/integration/test/utils/journey-test-helpers.ts
At a glance
After this PR, three more migration commands run on the new engine-based CLI, and the old
migration statusflags tell you where their functionality went instead of failing as unknown flags:The decision
We port
migration plan,migration newandmigration statusfrom the old commander.js CLI onto@prisma/cli-engine, and we declare the four retiredmigration statusflags (--graph,--all,--limit,--ref) as flag redirects, so an old invocation gets a typedCLI.COMMAND_MOVEDerror naming the replacement command instead of an "unknown flag" failure.Background for readers new to this series: we are moving the CLI's commands, one batch per PR, from the commander.js implementation onto the engine, which owns argument parsing, output formatting (
--json/--quiet), and a typed result protocol — every run ends in a result envelope carrying an exit code, optional diagnostics, and typed next actions.migration list,show,logandgraphwere ported in earlier PRs; this PR is the remaining three read/write commands, their tests, and the redirect declarations.How the change builds up
1. The three commands. Each is defined through the shared
defineOrmCommandhelper and registered in the ORM command family (src/orm/family.ts).migration newscaffolds a migration file,migration planpreviews the steps and SQL to reach a target contract,migration statusreports pending migrations against the database marker.2.
migration statusis the first ported command with typed diagnostics. It can hit three conditions while still answering the question it was asked:CONTRACT.UNREADABLE,MIGRATION.MARKER_NOT_IN_HISTORY, andMIGRATION.MISSING_INVARIANTS. Each is reported as aseverity: 'warn'diagnostic on a successful (exit 0) result — finding these conditions is the command doing its job, not the command failing. Each finding appears both in the--jsondocument and as an engine diagnostic with next actions (src/orm/migration/status-findings.ts). Tests assert code, severity and exit code together, since that trio is the behavioral contract.3. The flag redirects become possible. The engine requires a flag redirect to name a mounted command, so the four retired
migration statusflags could not be declared untilmigration statusitself existed on the engine. They were recorded as a debt in the port's findings file and are closed out here:--graphpoints atmigration graph,--alland--limitatmigration log --db <url>, and--refatmigration status --to <contract>.Intentional divergences from the commander CLI
--legendno longer errors under--json/--quiet. Engine 0.0.8 gives a handler no view of the active output format, so the commander'sMIGRATION.LEGEND_HUMAN_ONLYerror cannot be implemented; the flag is silently ignored, matching the already-mergedmigration listandgraph.--ascii.config:row and the command title.migration newgains a header where the commander printed an empty one.Next:prose becomes typed next actions; the missing-invariants text block becomes an engine-rendered diagnostic.--json).Reviewer notes
{bin}substitution applies to help examples and redirect replacements, but not toNextAction.command. A redirect renders as<your binary name> migration graph, while a next action namesprisma-nextliterally. Worth knowing before the binary is ever renamed.migration planresolves its origin from thedbref, not the graph tip — test fixtures need adbref plus a contract snapshot, or every plan comes out greenfield. This cost real debugging time and is recorded for the next porter.Verification
pnpm build,pnpm --filter @internal/cli test(134 files, 1628 tests),pnpm typecheck,pnpm lint,pnpm lint:deps,check:error-reference, and the CLI journey suite (49 files, 134 tests). Each command was also run through the built binary against the demo project and diffed against the commander output, through a pty, with colour and withNO_COLOR=1.Two environmental problems surfaced, neither caused by this change and both recorded:
init-journeyfails when two worktrees pack into the same shared tarball cache and the installer reads a half-written archive, andtest:integrationis unreliable under parallel load on this machine — 46, 30 and 5 failures across three runs, all passing when run in isolation.Alternatives considered
migration statusfinds a problem (error-severity diagnostics, nonzero exit). Rejected: the command still delivered its full answer, and the engine refuses an error-severity diagnostic on a run that exits 0 — so the choice is genuinely binary, and warn-at-exit-0 matches what the command means.MIGRATION.LEGEND_HUMAN_ONLYerror for--legendunder--json. Not implementable on engine 0.0.8 (handlers cannot see the active format); silently ignoring the flag keeps the three tree commands consistent rather than special-casing one.migration statusdid not exist on the engine until now. The interim was an explicit recorded debt, not an oversight.--asciiflag, consistent with the already-ported tree commands.