Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5f1d233
drive(prisma-cli-v8): brief for the init port and shell retirement
wmadden-electric Aug 11, 2026
5ca5f39
drive(prisma-cli-v8): rule config loading — copy the reference reposi…
wmadden-electric Aug 11, 2026
b080ff9
drive(prisma-cli-v8): the config question is ruled, so the brief stop…
wmadden-electric Aug 11, 2026
9655742
fix(cli-engine): read prisma.config.ts through c12, so the shipped bi…
wmadden-electric Aug 11, 2026
3f20100
refactor(cli-engine): make the c12 call the reference implementation,…
wmadden-electric Aug 11, 2026
1cdf8f4
Port the version command onto the engine
wmadden-electric Aug 11, 2026
0566591
Merge remote-tracking branch 'bot/main' into s2d-init-and-retirement
wmadden-electric Aug 11, 2026
cacff20
Let a command declare a boolean the user can leave unsaid
wmadden-electric Aug 11, 2026
7367236
Port the init wizard onto the engine
wmadden-electric Aug 11, 2026
cc4d2d9
drive(prisma-cli-v8): record what the version and init ports changed …
wmadden-electric Aug 11, 2026
95859e5
Drop the version command: the engine already answers this
wmadden-electric Aug 11, 2026
c0723a2
Give commands the host facts, so they stop reaching into process
wmadden-electric Aug 11, 2026
b88f20f
drive(prisma-cli-v8): record the host surface and the two call sites …
wmadden-electric Aug 11, 2026
f7349eb
Merge main: the platform is fully ported, so init and ctx.host rebase…
wmadden-electric Aug 12, 2026
612cc5e
drive(prisma-cli-v8): one divergence document for S2 sign-off
wmadden-electric Aug 12, 2026
f7c354d
Delete the commander shell: the engine is the only way commands run now
wmadden-electric Aug 12, 2026
f96b2ae
feedback and the agent commands read ctx.host, not process
wmadden-electric Aug 12, 2026
90efd34
The package's one bin is the engine shell, and prisma-v8 retires
wmadden-electric Aug 12, 2026
20e920e
drive(prisma-cli-v8): the deletion's divergences join the sign-off do…
wmadden-electric Aug 12, 2026
c2df8a5
Merge remote-tracking branch 'bot/init-shell-retirement-brief' into s…
wmadden-electric Aug 12, 2026
c5e658b
Merge main: S7's release pipeline, and the init collision it surfaced
wmadden-electric Aug 12, 2026
99ef1e6
drive(prisma-cli-v8): record the init/orm-init ruling for sign-off
wmadden-electric Aug 12, 2026
1db54c1
drive(prisma-cli-v8): record the operator's S2 sign-off
wmadden-electric Aug 12, 2026
88873ac
The working name v8 retires: one tree, one dist, no prefix
wmadden-electric Aug 12, 2026
3f87dd5
The agent tests stop asking Windows what to expect
wmadden-electric Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Brief: port `init`, then delete the old CLI

Written 2026-08-11 for an agent with no prior context. The operator is Will Madden. Everything you need is here or in the documents named; where this brief summarises a document, the document wins.

## What this slice is

Repo `prisma/prisma-cli`. This is the last slice of the platform port. Two things happen in it, in this order:

1. **Port the two commands that are left** — the `init` wizard and the `version` command — onto the new engine.
2. **Delete the old CLI**: the commander-based shell, the fixture/mock machinery, and everything that only existed to serve them. Then make the engine-based binary the one we ship.

When this lands, `prisma-cli` runs entirely on `@prisma/cli-engine` and the old shell is gone.

The slice contract is `.drive/projects/prisma-cli-v8/specs/s2d-init-and-retirement.md`. Read it in full before you touch anything — it is normative. One note: an earlier version of that contract contradicted itself mid-sentence on the shipped-binary cutover. That paragraph has been replaced with the ruling described below; if you are reading a stale copy, the question ledger in `.drive/projects/prisma-cli-v8/specs/s2-overview.md` is authoritative.

## Do not start yet

Two pull requests must land before you begin, because both add commands to the tree you are about to complete and delete code you are about to remove:

- **#133** — the resources port, and the `database` → `postgres` rename. Awaiting first review.
- **#132** — the services port (`app` becomes `service`). Currently at changes-requested.

Starting before these merge means rebasing a very large deletion across two moving branches. Wait.

## Reading the config file from the shipped binary — already decided

The CLI reads `prisma.config.ts` from the user's project, and the loader (`packages/cli-engine/src/config-loader.ts`) does a plain dynamic `import()` of that path. That works today only because everything runs under `tsx`. The binary we ship runs on ordinary Node, which cannot execute TypeScript, so as things stand the released CLI cannot read the config file it is built around.

**Do not design a solution. Copy the two reference repositories, which solved this already and identically** (operator ruling, 2026-08-11): take `c12` as a dependency, import it dynamically at the call site, call `loadConfig({ name, cwd, configFile? })`, and declare `typescript` as a peer dependency. The shapes to copy are `packages/1-framework/3-tooling/config-loader/src/load.ts` in prisma/prisma, and the `cli` and `composer` packages in prisma/composer.

Copy the dependency declarations, not just the call. `c12` evaluates TypeScript through `jiti`, which it declares as a peer, and that is the part that makes it work on plain Node.

Two behaviours worth carrying over with it: `c12` discovers the config by `name`, walking up from `cwd`, with an explicit path passed as `configFile`; and prisma/prisma then verifies that the file `c12` actually loaded is the one that was asked for, treating a missing or empty config as a structured not-found error rather than an empty object.

Nothing in this slice is blocked any more.

## The work, in the order I would do it

**1. Port `init`.** It is the hardest command in the product and the reason this slice is last. Today it is `packages/cli/src/controllers/init.ts`, about 1,100 lines. It is a wizard: project name, template and framework selection, a linking question, writing environment files, and an offer to install the agent skill. The contract pins the step list and the current defaults.

Three things to get right. The prompts run on the engine's prompt surface, so the wizard must work interactively, under `--yes`, non-interactively, and when cancelled — that matrix is the acceptance bar. The files it writes are data, not rendering: the templates stay byte-asserted against what the current CLI produces. And the step that checks whether you are signed in must read auth state without forcing a login, the way `auth whoami` does, offering a sign-in next action instead — if that differs from the current behaviour, record the difference.

**2. Port `version`.** The `--version` flag already exists on the engine. This is the `version` *command*, presenting version, node and platform, with a JSON serializer. Small, but user-visible, so it gets the same test matrix as anything else.

**3. Delete the old CLI.** Do this only once every port is green, and as its own commit series so the diff is reviewable. The scale, as of today:

| Directory | Files | Lines |
| --- | --- | --- |
| `src/shell` | 13 | 2,462 |
| `src/controllers` | 16 | 13,032 |
| `src/presenters` | 12 | 3,317 |
| `src/adapters` | 3 | 1,073 |
| `src/use-cases` | 5 | 713 |

Not all of that dies. Controllers and presenters survive **only** where the ported commands still call them as an operation layer — the new command asks the old function to do the API work. Enumerate the survivors explicitly in the pull request; a survivor nobody listed is how this kind of deletion goes wrong.

Also delete: the fixture machinery (`src/adapters/mock-api.ts`, `src/use-cases/**`, the fixture providers, and every `isRealMode` branch — seven files mention it or the `PRISMA_CLI_MOCK_FIXTURE_PATH` variable), all remaining fixture-mode tests, that environment variable itself, and `--trace`.

**One knot worth knowing about before you pull on it.** `src/auth/errors.ts` still constructs `CliError`, the old shell's error class, and `src/v8/auth/errors.ts` maps those into structured errors. So the auth module depends on the shell it is meant to outlive. When the shell dies, either `CliError` moves somewhere durable or the auth operations throw structured errors directly and both mapping layers go. The second is cleaner. Decide deliberately rather than discovering it halfway through the deletion.

**4. Cut the binary over.** `packages/cli/package.json`'s `bin` points at the engine entry, the build bundles the new tree, and the `prisma-v8` working name and its root script are deleted. Prove it by running the packed tarball on plain Node — not through `tsx`.

**5. Add the grammar completeness check.** A build-time test asserting the mounted command tree is exactly the target grammar: every command in the inventory, minus the ruled removals, plus the ruled renames. The removals so far are `service build`, `service deploy` and `service run` — all superseded by Composer — and the mock-only login flags. `.drive/projects/prisma-cli-v8/assets/s2/command-inventory.md` is the inventory.

**6. Consolidate the divergence record.** Every slice has been appending user-visible differences from the old CLI to `.drive/projects/prisma-cli-v8/assets/s2/parity-divergences.md`. Fold yours in and hand the whole document to the operator for sign-off. This is the last chance to catch a behaviour change nobody meant to ship.

## What the engine gives you

The engine surface changed substantially in the slice that just merged, so anything you read in an older document may be stale. As of now: a command reads `ctx.activeCredential()` for what the process is authenticated as; there is no `getCredentials` and no raw token available to a command; the credential manager has seven members; and the test harness seeds are `sessions`, `selectedWorkspaceId`, `credential` and `environmentCredential`. `packages/cli-engine/src/credential-manager.ts` and `context.ts` are the truth.

For prompts specifically — the surface you will lean on hardest — read `packages/cli-engine/src/context.ts` (`PromptSurface`) and `packages/cli-engine/tests/interaction-affordances.test.ts`. Consent is deliberately not defaultable: `--yes` cannot satisfy it, and a destructive prompt with a token needs `--confirm <token>` non-interactively.

## Verification

Every one of these must exit 0 before you report anything as done:

```
pnpm --filter @prisma/cli-engine test
pnpm --filter @prisma/cli test
pnpm --filter @repo/cli-telemetry test
pnpm typecheck
pnpm lint
```

Engine tests that use `createTestCli` execute the **built** `dist`, so run the package's own `test` script, which builds first. Invoking vitest directly gives you stale results. This has caused wasted work twice.

A passing test is not the same as a test that holds the behaviour down. For anything you claim is fixed, break the code deliberately and confirm the test fails. Two real defects in the last slice were found exactly this way, and one flaky-looking test turned out to be a genuine deadlock.

## Process rules, non-negotiable

- **Git identity.** You act as the `wmadden-electric` bot. Stage explicitly by path — never `git add -A`, never anything under `wip/` or `.drive/projects/prisma-cli-v8/specs/reviews/`. Commit with `git commit -s --trailer "Signed-off-by: Will Madden <madden@prisma.io>"`. Push only to the `bot` remote (`git@github-wmadden-electric:prisma/prisma-cli.git`).
- **Finish the job.** The deliverable is a pull request. Commit, push, and open it — draft if the work is partial, saying what is unresolved. Do not end with work sitting uncommitted.
- **Pull request text.** The operator's structure: a grounding example first (a real command run, before and after), then the decision, then the narrative building up, alternatives last. No internal process codes, no dispatch or round labels, no reviewer numbering. Assume the reader has none of your context — spell out any project shorthand rather than making them look it up.
- **Reports.** Plain English, full sentences, no invented jargon, no session-internal labels. Banned words: "load-bearing", "smoking gun", "belt and suspenders", "gate". Bring questions to decide, not decisions to ratify. Stop on any contradiction between the design and the code that the design does not anticipate — never improvise.
- **Subagents** on Opus, implementers and reviewers alike.

## Wider state

The engine publishes as `@prisma/cli-engine`. Nothing publishes automatically any more: a push to `main` publishes only when it changes the committed version, and `8.0.0-rc.1` is committed but deliberately **not** released. Do not bump the version and do not publish.

Other agents work the two open pull requests independently. Do not touch their branches.

The remaining unanswered questions are in `.drive/projects/prisma-cli-v8/specs/s2-overview.md`. The one most likely to reach you is whether an unauthenticated command should still launch a browser login automatically the way the old CLI did — the port fails with a sign-in error instead. It is built to that default and unratified.
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,10 @@ export interface CommandContext<TConfig = undefined, TCode extends number = neve
* via ctx.env, never process.env (R4). */
readonly env: Readonly<Record<string, string | undefined>>

/** What this process runs on, from Runtime.host. Handlers read the
* runtime version, platform and arch here — never from `process`. */
readonly host: Host

/**
* R13, the conditional form (evidence: composer needs @prisma/dev only
* when the config declares postgres resources — unconditional needs
Expand Down Expand Up @@ -881,6 +885,15 @@ export declare const flag: {
default?: number
}): FlagSpec<number | undefined>
boolean<A extends string = never>(spec: { brief: string; alias?: A & Char<A> }): FlagSpec<boolean>
/**
* A boolean the user can leave unsaid: `--flag`, `--no-flag`, or
* neither, which arrives as undefined. Use it when absence means
* something of its own — "ask me" rather than "no".
*/
optionalBoolean<A extends string = never>(spec: {
brief: string
alias?: A & Char<A>
}): FlagSpec<boolean | undefined>
enum<const T extends readonly string[], A extends string = never>(spec: {
brief: string
values: T
Expand Down Expand Up @@ -1559,6 +1572,8 @@ export interface Runtime {
/** Used by the ENGINE to phrase install commands (handlers never
* do — see needs.dependencies and ctx.requireDependency). */
readonly packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun' | 'unknown'
/** What this process runs on; commands read it via ctx.host. */
readonly host: Host
}

/** The minimal process surface a bin adapts a Runtime from — Node's
Expand Down Expand Up @@ -1633,6 +1648,7 @@ export declare function createTestCli(spec: {
readonly client?: ManagementApiClient
}
readonly packageManager?: 'npm' | 'pnpm' | 'yarn' | 'bun' | 'unknown'
readonly host?: Host
/** Fixed clock for deterministic stream timestamps; a clock that
* advances also drives prompt.browserWait's timeout, whose waiting
* is instant under the harness. */
Expand Down Expand Up @@ -1732,3 +1748,14 @@ export interface TestCli {
readonly presented: PresentedResult<unknown> | undefined
}>
}

/**
* What this process runs on. Deliberately not node-shaped: products are
* runtime-agnostic (R4), so the runtime names itself rather than the
* field naming it.
*/
export interface Host {
readonly runtime: { readonly name: string; readonly version: string }
readonly platform: string
readonly arch: string
}
Loading
Loading