From 945aa53d1dbbb24c9db1f67468e55c7d289f0477 Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 21 Oct 2026 10:05:43 +0200 Subject: [PATCH 1/3] chore(drive): close the emulator-commands project Move its open follow-ups to .drive/deferred.md and delete .drive/projects/emulator-commands/. The design lives in ADR-0052 and ADR-0053. Co-Authored-By: Claude Opus 5.5 Signed-off-by: willbot Signed-off-by: Will Madden --- .drive/deferred.md | 54 +++++++++++ .drive/projects/emulator-commands/deferred.md | 16 ---- .../emulator-commands/design-notes.md | 68 -------------- .drive/projects/emulator-commands/plan.md | 64 ------------- .../slices/s1a-compute-instances/spec.md | 87 ------------------ .../slices/s1b-data-instances/spec.md | 76 --------------- .../slices/s2-command-group/spec.md | 59 ------------ .../slices/s3-commands/spec.md | 76 --------------- .../slices/s6-dev-detach/spec.md | 46 ---------- .../slices/s7-piped-output/spec.md | 43 --------- .drive/projects/emulator-commands/spec.md | 92 ------------------- 11 files changed, 54 insertions(+), 627 deletions(-) delete mode 100644 .drive/projects/emulator-commands/deferred.md delete mode 100644 .drive/projects/emulator-commands/design-notes.md delete mode 100644 .drive/projects/emulator-commands/plan.md delete mode 100644 .drive/projects/emulator-commands/slices/s1a-compute-instances/spec.md delete mode 100644 .drive/projects/emulator-commands/slices/s1b-data-instances/spec.md delete mode 100644 .drive/projects/emulator-commands/slices/s2-command-group/spec.md delete mode 100644 .drive/projects/emulator-commands/slices/s3-commands/spec.md delete mode 100644 .drive/projects/emulator-commands/slices/s6-dev-detach/spec.md delete mode 100644 .drive/projects/emulator-commands/slices/s7-piped-output/spec.md delete mode 100644 .drive/projects/emulator-commands/spec.md diff --git a/.drive/deferred.md b/.drive/deferred.md index 44776670d..46f7a3190 100644 --- a/.drive/deferred.md +++ b/.drive/deferred.md @@ -187,3 +187,57 @@ registry). What we deliberately didn't do: (branch `claude/prisma-app-port` in prisma/datahub) was proven by a graph test and a local boot, but never refreshed to the current packages, deployed with real credentials, or cut over. Needs operator credentials. + +# Emulator commands — deferred at project close (2026-10-21) + +Shipped: `prisma dev url`, `prisma dev stop`, `prisma dev --detach` and +`prisma emulator list` (Composer 0.32.0, `prisma@8.0.0-rc.24`). The design is +ADR-0052 (Composer maps its nodes to emulator instances per directory) and +ADR-0053 (the commands). What we deliberately didn't do: + +- **A deployment that arrives while its instance is being deleted can start a + process the emulator no longer tracks.** Same race as an app delete in the + Compute emulator. +- **`env.json` and `secrets.json` in `.prisma-composer/dev/` are shared by + every app run from a directory, and `--fresh` wipes them.** One app's + `--fresh` can drop rows another app in the same directory needs on its next + redeploy. Making them per app, like bucket objects + (`buckets///`), would fix it. +- **`[dev]` notices bypass the CLI engine's output.** The emulators hook + writes `[dev] compute emulator ready`, the port-change line and the + app-switch line with `console.log` to stdout, so they land among a piped + `prisma dev`'s data lines and inside `--json` frames. Fixing it needs a + channel for the local target's hooks to report through the engine. +- **`prisma dev url` doesn't retry an emulator that is briefly unreachable.** + `prisma dev` and `prisma log` use `withEmulatorRetry` + (`cli/src/operations/emulator-retry.ts`); `dev url` should too. +- **`prisma dev url` loads the config's extensions, and so Alchemy, to read + one JSON file.** It only matters when a script calls it in a loop. +- **`prisma dev url` and `prisma dev stop` in a directory with no + `prisma.config.ts` fail with two stacked config-section errors instead of + `DEV.NOT_STARTED_HERE`.** The commands declare that they need the `composer` + config section, so the CLI engine refuses them before the operation can say + that `prisma dev` never ran here. Either the commands stop requiring the + section, or the engine's missing-config error names the real requirement. +- **`prisma deploy` and `prisma dev --detach` print no data lines to a + pipe.** The deploy engine runs as a delegated child that inherits stdout, so + its log would share the pipe with the data. Once a delegated child's output + stops sharing stdout, give `deploy` one line per deployed node (address and + public URL) and `--detach` one line per service (address and URL). Until + then, scripts use `prisma dev url
` after `--detach` returns. +- **A run-then-stop mode for CI: `prisma dev --exec ""`,** like Firebase + `emulators:exec`. It would bring the app up as `--detach` does, run the + command, stop the app and exit with the command's code. +- **ORM commands print nothing to a pipe.** The `prisma` CLI now gives a + piped stdout plain data, and each command needs data lines in its owning + repo (`@prisma/orm-toolchain`): `migration list`, `migration ref list`, + `migration status`, `migration log`, `migration show`, `migration plan`, + `migration new`, `migration check`, `migration graph`, `migration ref set`, + `migration ref delete`, `contract infer`, `contract format`, `db init`, + `db schema`, `db sign`, `db update`, `db verify`, `db migrate`, `orm init`. +- **The `prisma` CLI's "agent skills out of date" notice names + `@prisma/orm-postgres` in projects that don't use it** (prisma/prisma-cli). +- **Removing instances and their data, and `prisma emulator start` / + `stop`.** A deleted worktree leaves its stopped instances' records and + Postgres data on disk, holding their ports. Planned for the emulator + start/stop project. diff --git a/.drive/projects/emulator-commands/deferred.md b/.drive/projects/emulator-commands/deferred.md deleted file mode 100644 index cd1d3e34f..000000000 --- a/.drive/projects/emulator-commands/deferred.md +++ /dev/null @@ -1,16 +0,0 @@ -# Deferred - -- **A start can join a crash-backoff restart that has already exited (prisma/composer#353).** When the fifth fast exit marks an instance `held`, the readiness probe only notices the exit after its current connection attempt. A start request arriving in that gap joins the dying start and gets its old failure, so nothing resumes. Seen once as an intermittent failure of "a held service resumes on start" in `compute-deployment.test.ts`; not reproduced in 10 runs under CPU load. Likely fix: a start must not join an in-flight start whose process has already exited. Belongs in #353, not this project. -- **What a previous app's databases and buckets become when another app runs from the same directory.** Compute instances are deleted (S1a). Databases and buckets hold data; decide in S1b. -- **`[dev]` notices bypass the CLI engine's output.** The emulators hook writes `[dev] compute emulator ready`, the port-change line and the app-switch line with `console.log` to stdout, so once a pipe gets plain data (prisma-cli's piped-output change) they land among a piped `prisma dev`'s data lines; with `--json` they still mix into the JSON frames. Fixing it needs a channel for the local target's hooks to report through the engine. -- **A deployment that arrives while its instance is being deleted can start a process the emulator no longer tracks.** Same race as #353's app delete. -- **`env.json` and `secrets.json` in `.prisma-composer/dev/` are shared by every app run from a directory, and `--fresh` wipes them.** One app's `--fresh` can drop rows another app in the same directory needs on its next redeploy. Pre-existing. Making them per app, like bucket objects (S1b moved those to `buckets///`), would fix it. -- **A service that never listens fails `prisma dev` under prisma/composer#353** (the cron scheduler; `examples/store`). Raised with Will; the fix belongs in #353. -- **`prisma dev url` doesn't retry an emulator that is briefly unreachable.** Retry policy is #353's (`withEmulatorRetry`); apply it here once that settles. -- **`prisma dev url` loads the config's extensions, and so Alchemy, to read one JSON file.** It only matters when a script calls it in a loop. -- **A run-then-stop mode for CI: `prisma dev --exec ""`, like Firebase `emulators:exec`.** It would bring the app up as `--detach` does, run the command, stop the app and exit with the command's code. Likely next step after S6. -- **Commands that print nothing to a pipe once prisma-cli's piped output becomes plain data.** Each needs data lines in its owning repo: - - ORM (`@prisma/orm-toolchain`): `migration list` and `migration ref list` (one row per item), `migration status` (`keyvalue`), `migration log`, `migration show`, `migration plan` and `migration new` (the created migration's id or path), `migration check`, `migration graph`, `migration ref set` and `migration ref delete`, `contract infer`, `contract format`, `contract print` (the printed contract is the data), `db init`, `db schema`, `db sign`, `db update`, `db verify`, `db migrate`, `orm init`. Matched by function name in the built bundle, so approximate. -- **`prisma deploy` has no data lines.** A piped `prisma deploy` should write one line per deployed node (its address and, tab-separated, its public URL), but in human mode the deploy engine runs as a delegated child that inherits stdout, so its progress output would share the pipe and a script could not tell the two apart. Add the data lines once a delegated child's output stops sharing stdout, for example when the CLI engine routes a delegated child's stdout to stderr in human mode. Dropped from prisma/composer#365 for this reason. -- **`prisma dev --detach` writes its front door only to stderr.** `dev: ready` and the service URLs are human text on stderr, while the deploy engine's log, from the delegated child that inherits stdout, goes to stdout. A script that pipes `prisma dev --detach` gets the engine's log rather than the URLs. Same cause as `prisma deploy`: fix it together, once a delegated child's output stops sharing stdout, and give `--detach` data lines (one line per service: address and URL). Until then, scripts use `prisma dev url
` after it returns. -- **`prisma dev url` in a directory with no `prisma.config.ts` fails with the config-section errors, two stacked, instead of `DEV.NOT_STARTED_HERE`.** The command declares that it needs the `composer` config section, so the CLI engine refuses to run it before the operation can say that `prisma dev` never ran here. `prisma dev stop` behaves the same. Either the commands stop requiring the section and report `DEV.NOT_STARTED_HERE` when no record exists, or the engine's missing-config error names the command's real requirement. diff --git a/.drive/projects/emulator-commands/design-notes.md b/.drive/projects/emulator-commands/design-notes.md deleted file mode 100644 index 49914e63a..000000000 --- a/.drive/projects/emulator-commands/design-notes.md +++ /dev/null @@ -1,68 +0,0 @@ -# Design notes — emulator commands - -Decisions taken while shaping, with the reasoning. Will's rulings are marked; the rest are orchestrator decisions he can overturn. - -## Rulings (Will, 2026-10-13) - -- The commands are `prisma dev url
` and `prisma emulator list`. A top-level `prisma url` was rejected as ambiguous (local and deployed running objects). -- `emulator` commands act on the machine's emulators, not on a project. -- The emulators know nothing about Composer. Composer maps (directory, app, address) to emulator instances itself, per directory. Instances carry informative labels only. -- `dev url` resolves a Composer node address only, never an emulator instance id. -- Databases and buckets are in scope alongside services. -- Piped output stays JSON; docs use `jq`. No output-format change to the CLI engine. -- The CLI engine changes so a path can be both a command and a group, keeping `prisma dev `. -- The emulators may move out of Composer later (own project, or prisma-cli). Not part of this work. - -## Facts the design rests on (checked against main, 90a30381) - -- The config does not name the entry or the app. ADR-0003 rejected a config that names the app; `deploy-cli.md` lists entry discovery as out of scope. So `dev url` cannot find the app from `prisma.config.ts`; it needs a record `prisma dev` leaves in the directory. -- Today the emulators are app-aware: Compute routes are `/apps/:app/services/:id`, Postgres server names come from `instanceNameFor(app, db)`, buckets are named `--`. The local identity is the root name alone (`target/src/local-target/container.ts`), so two directories running one app share instances. -- `LocalTargetAttachment.endpoints()` returns `{ address, url }` only. The emulator's `ServiceInfo` has `status: running | backoff | held | stopped`. -- The engine rejects a command path under another command (`packages/cli-engine/src/execution/command-tree.ts:157`). -- When stdout is not a terminal the engine prints JSON frames: `{"kind":"result","envelope":{"ok":true,"result":…}}` (`shared-flags.ts:191`, `settlement.ts:83`). -- `9-public` may import both `framework` and `prisma-cloud` (`architecture.config.json` cross-domain rules). `framework` imports nothing outside itself. - -## Where the code lives - -- **Emulators** (`dev-emulators`): instance-keyed routes and state; every create call accepts a `labels: Record` that is stored and returned verbatim. Each emulator gains a route that lists all its instances. -- **The mapping** is owned by the Prisma Cloud local target (`local-target` / `target/src/local-target`), because that code creates the instances and learns their ids. It lives in `/.prisma-composer/dev/` (the dev state directory, already per working directory), and records the app name plus, per node address, the emulator kind and instance id. -- **`prisma dev url`** goes through the core seam, like the `log` operation: config, then `resolveLocalTargets`, then the target's attachment. The attachment gains a method that resolves one address to `{ address, kind, url, state }`. Core stays target-agnostic; the framework domain imports nothing new. It runs from a Composer project, which `dev url` needs anyway. -- **`prisma emulator list`** needs no project and no config, so it cannot go through the seam. Its handler lives in the Prisma Cloud domain next to the emulator client, and `@prisma/composer-cli` (in `9-public`, which may import `prisma-cloud`) adds it to the command family. No dependency-cruiser exception is needed. -- The emulator client stays the only module that speaks the emulators' HTTP API, so a later move out of Composer replaces one dependency. - -## Ports (Will, 2026-10-13: agents in many worktrees start and stop environments) - -An instance keeps its port from creation until it is deleted, running or stopped. A deployment carries its port inside the service's environment (`COMPOSER_
_PORT`) and in its dependents' URLs, and Alchemy's no-op reconcile on a warm start never redeploys, so a port that could change between sessions would leave services built for the old port. Releasing ports on stop was considered and dropped for that reason (implementer finding, S1a dispatch 1). The cost: instances left behind by deleted worktrees keep their ports until the later remove command deletes them. - -Composer remembers each node's port in the directory's mapping and requests it only when it has to create the instance again, for example after the emulator lost its state. A requested port is a preference: if another instance or process holds it, the emulator gives the lowest free port from 3000 and `prisma dev` prints one line saying the port changed. Failing instead would break an agent's `prisma dev` whenever another worktree happened to take the port. On a first start, ports still go in dependency order (prisma/composer#350). - -## Labels Composer writes - -`composer.directory` (absolute path), `composer.app` (app name), `composer.address` (node address). `emulator list` shows them; nothing reads them back as identity. - -## Output shapes - -- `dev url`, human: the URL alone on stdout's data line, so `--format human` also works in `$(…)`. JSON `result`: `{ address, kind, url, state }`, where `kind` is `service | database | bucket` and `state` is the service status, or `running` for a database or bucket whose emulator is up. -- `emulator list`, JSON `result`: `{ emulators: [{ kind, running, url, instances: [{ id, kind, url, state, labels }] }] }`. Human: one block per emulator, instances listed under it with the address and directory from their labels when present. It doesn't fit the list convention's `{ name, id, status }` items, because two levels are nested. That is a deliberate departure, noted in the slice spec. - -## Error codes (namespace `DEV` for `dev url`, new `EMULATOR` for `emulator list`) - -- `DEV.NOT_STARTED_HERE`: no `prisma dev` has run in this directory (no mapping). Fix: run `prisma dev ` here. -- `DEV.ADDRESS_UNKNOWN`: no node with that address in this directory's app; lists the known addresses. -- `DEV.EMULATOR_UNREACHABLE` (from #353): the node's emulator is not running or not answering. -- `DEV.SERVICE_NOT_RUNNING`: the service exists but is stopped, backing off or held; says which, and points to `prisma dev`. -- `EMULATOR.*`: only if `list` needs one beyond `DEV.EMULATORS_DIR_INVALID` and `DEV.EMULATOR_UNREACHABLE`. Final names are fixed in the slice specs and recorded in ADR-0044. - -## Alternatives considered - -- **`prisma emulator url
`.** Same behaviour, but `url` asks a Composer question about this directory's app, which belongs under `dev`. -- **Rename the run command to `prisma dev start`.** Avoids the engine change, but breaks every `prisma dev module.ts` and reverses ADR-0050's root verb. -- **Name the app on the command line (`url my-app gateway`) or with `--app`.** Addresses are meant relative to the local app, and names collide across directories. -- **Make the local identity "app name + directory hash" and keep app-aware emulators.** Fixes collisions but keeps Composer concepts inside the emulators, which is the opposite of the model. -- **`emulator list` through the core seam.** Requires a project and config for a machine-level command. -- **Print a bare URL even when piped.** An engine output change Will deferred. - -## Rulings (Will, 2026-10-15) - -- `prisma dev` stays in the foreground, watching, and Ctrl-C stops the app. `prisma dev --detach` waits until every service answers, prints the URLs, exits 0 and leaves the app running for `prisma dev stop`. Prior art (Docker Compose, .NET Aspire, Sail, and Prisma ORM's own `prisma dev`) defaults to foreground with an opt-in detach. Slice S6. -- A piped stdout gets the command's plain data lines, and JSON requires `--json`. No CLI surveyed (gh, aws, kubectl, gcloud, az, docker, supabase) switches to JSON when piped. This supersedes "Piped output stays JSON; docs use `jq`" above. Slice S7. diff --git a/.drive/projects/emulator-commands/plan.md b/.drive/projects/emulator-commands/plan.md deleted file mode 100644 index 81191b0ae..000000000 --- a/.drive/projects/emulator-commands/plan.md +++ /dev/null @@ -1,64 +0,0 @@ -# Emulator commands — project plan - -**Spec:** `.drive/projects/emulator-commands/spec.md` · **Design notes:** `design-notes.md` - -## Slices - -### S1 — Emulators know nothing about Composer (prisma/composer) - -**Outcome.** The Compute, Postgres and bucket emulators key instances by their own ids, accept and return free-form labels, and each lists all its instances. The Prisma Cloud local target creates instances, labels them, and records the per-directory mapping in `.prisma-composer/dev/`. Every Composer operation on the emulators goes through the mapping: deploy, session start and stop, logs, `--fresh` teardown. `prisma dev` behaves the same from outside, except that pre-upgrade local state is discarded. Two directories running one app get separate instances. Port allocation keeps its current rule on a first start: services reserve ports in dependency order (prisma/composer#350), lowest free from 3000. An instance keeps its port from creation until it is deleted, stopped or not, so a directory's ports never change. The mapping remembers each node's port and requests it when an instance has to be re-created. When `prisma dev` ends it stops all of the directory's instances, Postgres servers included (the Postgres emulator gains a stop that keeps data). - -**Builds on:** prisma/composer#353 (stack on its branch until it merges). -**Hands to S3:** the mapping file and its reader; the emulator client's list-everything calls; ADR amending ADR-0041. - -Likely too large for one review: split at pickup into Compute (with the mapping and remembered ports), then Postgres and buckets (with the Postgres stop). - -### S2 — A command path can also be a group (prisma/prisma-cli) - -**Outcome.** The CLI engine accepts a command mounted at a path that other commands mount under. `prisma dev module.ts` runs the command, `prisma dev url gateway` runs the subcommand, a subcommand name wins over a positional argument with the same spelling, and `prisma dev --help` shows the command's usage and its subcommands. Proven with engine tests using a stand-in command; no Composer change needed. - -**Builds on:** nothing. Runs in parallel with S1. -**Hands to S4:** an engine release that allows the `dev` mount. - -### S3 — `prisma dev url`, `prisma dev stop` and `prisma emulator list` (prisma/composer) - -**Outcome.** The command family exports `dev url`, `dev stop` and `emulator list` with tests first (bun:test; emulator tests set `PRISMA_COMPOSER_EMULATORS_DIR` to a temporary directory). The core local-target attachment gains the resolve-one-address method. Error codes are recorded in ADR-0044. A new ADR amends ADR-0050 (the family is no longer exactly `deploy` and `dev`). `local-dev.md`, the running-locally guide and the core-concepts skill are updated, including that `start` and `stop` are planned. - -**Builds on:** S1. -**Hands to S4:** a Composer release containing both commands. - -### S4 — Mount the commands (prisma/prisma-cli) - -**Outcome.** The `prisma` binary pins the Composer release from S3 and mounts `dev url`, `dev stop` and `emulator list`, with an `emulator` group whose help defines "emulator", `mount-coverage.test.ts` updated, and `EXCLUSIONS` entries in `e2e-coverage.test.ts`. - -**Builds on:** S2, S3 (released). -**Hands to S5:** a `prisma` release with both commands. - -### S5 — Docs stop hard-coding ports (prisma/web) - -**Outcome.** The Composer getting-started and local-development pages use `prisma dev url` instead of `localhost:3000`/`3001`. - -**Builds on:** S4 (released). - -## Sequence - -``` -#353 ─► S1 ─► S3 ─┐ - ├─► S4 ─► S5 -S2 ───────────────┘ -``` - -S1 and S2 run in parallel. S4 and S5 wait on releases, which are currently blocked on an upstream Alchemy issue. - -Five slices is one over the usual ceiling. S4 and S5 are small handoffs into other repos, and each one waits on a release, so they can't fold into a neighbour. - -## Tracking - -No Linear project exists for this work. Slices are tracked in this plan. - -## Close-out - -- [ ] Verify every Project DoD item in `spec.md`. -- [ ] Long-lived content lives in ADRs, `docs/design/10-domains/local-dev.md`, the guide and the skill. -- [ ] Strip repo-wide references to `.drive/projects/emulator-commands/`. -- [ ] Delete `.drive/projects/emulator-commands/`. diff --git a/.drive/projects/emulator-commands/slices/s1a-compute-instances/spec.md b/.drive/projects/emulator-commands/slices/s1a-compute-instances/spec.md deleted file mode 100644 index adc700b86..000000000 --- a/.drive/projects/emulator-commands/slices/s1a-compute-instances/spec.md +++ /dev/null @@ -1,87 +0,0 @@ -# S1a — The Compute emulator knows nothing about Composer - -Repo: prisma/composer. Branch `feat/emulator-instances`, stacked on prisma/composer#353 (`fix/dev-ready-after-listening`). PR base: that branch until #353 merges, then `main`. - -## At a glance - -After this slice, the Compute emulator runs service instances keyed by its own ids. It never sees an app name, a node address or a directory, except as label values it stores and returns without reading. Composer keeps, per working directory, a record of which instance backs each service address and which port it last had. - -```text -.prisma-composer/dev/ (per directory, written by Composer) - app: "my-app" - nodes: - quotes → compute instance "svc_k3f9…", port 3000 - gateway → compute instance "svc_81ad…", port 3001 - -Compute emulator (machine-wide, Composer-unaware) - GET /instances → [{ id: "svc_k3f9…", port: 3000, url, status: "running", - labels: { "composer.directory": "/path", "composer.app": "my-app", - "composer.address": "quotes" } }, …] -``` - -Two directories running the same app get separate instances. `prisma dev` otherwise behaves the same from the outside. - -## Chosen design - -**Emulator (`packages/1-prisma-cloud/0-lowering/dev-emulators`).** -- Instances are created by the emulator and get an id it generates. Create accepts `labels: Record` and an optional requested port. -- Every instance operation is by id: deploy, start, stop, delete, logs, get. There is no app grouping. Bulk start and stop take a list of ids. -- `GET` for all instances returns id, port, url, status and labels. -- Ports: an instance gets its port at creation and keeps it until deleted, running or stopped (a deployment's env and its dependents' URLs contain the port, and a warm start does not redeploy). Create takes an optional requested port: used if free, otherwise the lowest free port from 3000; the response reports the port given. No request → lowest free from 3000. -- The deployment body carries no Composer address. Emulator messages name the instance id; Composer adds the service's address when it rethrows, so what `prisma dev` prints is unchanged. -- State written in the old app-keyed format is discarded on load. Local dev state is never migrated across versions (running-locally guide). -- #353's readiness behaviour (a service is `running` only once its port accepts connections), the start and stop concurrency rules, crash supervision and the version-skew restart all carry over unchanged, keyed by instance instead of `(app, id)`. - -**Composer's mapping (Prisma Cloud local target: `0-lowering/local-target`, `1-extensions/target/src/local-target`).** -- One file in `/.prisma-composer/dev/` records the app name and, per service address, the Compute instance id and its last port. One module owns reading and writing it. S3 reads it for `prisma dev url`. -- The emulators hook keeps prisma/composer#350's order: it ensures one instance per service in `graph.nodes` order (dependencies first, ties in declaration order). It reuses the mapped instance when it still exists on the emulator. Otherwise it creates one, requesting the remembered port, and records the result. -- When a requested port was unavailable and the emulator gave a different one, `prisma dev` prints one line naming the service, the old port and the new one. -- Labels on create: `composer.directory` (absolute cwd), `composer.app`, `composer.address`. No Composer code finds an instance by its labels. -- The `App` and `Deployment` providers, attach (endpoints, logs, `startServices`, `stopServices`) and the `--fresh` teardown all reach Compute instances only through the mapping. -- `--fresh` deletes the mapped Compute instances and clears their mapping entries. - -**Docs.** -- A new ADR records the model: the emulators know nothing about Composer, Composer maps its nodes to instances per directory, and labels are informative only. It amends ADR-0041's local identity ("a stable local identity from the app name"). -- `docs/design/10-domains/local-dev.md` is updated for Compute. -- The running-locally guide and the core-concepts skill describe the user-visible change: two directories running one app no longer share services. Follow `.agents/rules/user-facing-surface-changes.mdc`. - -## Coherence rationale - -One reviewer can hold this as "Compute instances become emulator-owned, and Composer's per-directory mapping replaces the app name as the key". The Postgres and bucket emulators stay app-keyed until S1b. Both keys are valid in the meantime: the app name still identifies Postgres and buckets, and the mapping identifies Compute. - -## Scope - -In: the Compute emulator and its client; the mapping module; the emulators hook, the Compute providers, attach and teardown for Compute; tests; the ADR and docs above. - -Deliberately out: -- Postgres and buckets (S1b). -- Stopping Postgres when `dev` ends (S1b). -- `dev url`, `dev stop` and `emulator list` (S3). This slice does add the emulator's list-all route and client call, because the mapping's "does this instance still exist" check needs it. -- Deleting instances whose directory is gone. - -## Pre-investigated edge cases - -| Case | Expected | -| --- | --- | -| Mapping names an instance the emulator no longer has (emulator state lost, or another directory's `--fresh`) | Create a new instance requesting the remembered port; update the mapping | -| Remembered port taken by another directory's running instance | Lowest free port; one-line notice; mapping updated | -| Two directories, same app, started concurrently | Separate instances, no shared ports, neither redeploys over the other | -| Warm restart after Ctrl-C (the #164 warm-restart case) | Same instances, same ports, services started from their last deployment | -| Emulator restarted by a version change | Old app-keyed state discarded; the next `dev` creates instances, requesting remembered ports | - -## Slice-specific done conditions - -- [ ] Two temporary directories run the same fixture app at once. Each has its own Compute instances, and neither directory's services restart when the other deploys. This is proven by a test against a real Compute emulator in a temporary `PRISMA_COMPOSER_EMULATORS_DIR`. -- [ ] A test fails if the Compute emulator's routes, state keys, file names or generated fields carry an app name, address or directory, or if it alters label values or the deployment payload. - -## References - -- Project: `../../spec.md`, `../../design-notes.md`, `../../plan.md`. -- prisma/composer#350 (port order), prisma/composer#353 (readiness; this branch's base). -- `docs/design/10-domains/local-dev.md`, ADR-0041, ADR-0044, ADR-0051. - -## Dispatch plan - -1. **Emulator and client.** The Compute emulator and `computeClient` move to instance ids, labels, requested ports, list-all and bulk start and stop. The emulator's own tests are migrated and extended. *Hands to 2:* a Compute client keyed by instance id. -2. **Composer's mapping.** The mapping module, the emulators hook, the providers, attach and teardown move to the mapping, including the change-of-port notice and the two-directory test. *Hands to 3:* `prisma dev` working end to end on instance ids. -3. **Docs.** The ADR, local-dev.md, the guide and the skill. *Hands to:* the PR. diff --git a/.drive/projects/emulator-commands/slices/s1b-data-instances/spec.md b/.drive/projects/emulator-commands/slices/s1b-data-instances/spec.md deleted file mode 100644 index 3090470e8..000000000 --- a/.drive/projects/emulator-commands/slices/s1b-data-instances/spec.md +++ /dev/null @@ -1,76 +0,0 @@ -# S1b — The Postgres and bucket emulators know nothing about Composer - -Repo: prisma/composer. Branch `feat/emulator-data-instances`, stacked on S1a (`feat/emulator-instances`, prisma/composer#354). PR base: that branch until it merges. - -## At a glance - -After S1a, only the Compute emulator follows ADR-0052. This slice moves the Postgres and bucket emulators to the same model, makes `prisma dev` stop its Postgres servers when it ends, and lets one directory keep the local data of more than one app. - -```text -~/work/shop/.prisma-composer/dev/emulator-instances.json - { "current": "shop", - "apps": { - "shop": { "nodes": { - "quotes": { "kind": "service", "id": "svc_k3f9…", "port": 3000 }, - "catalog": { "kind": "database", "id": "db_02c4…" }, - "uploads": { "kind": "bucket", "id": "bkt-77e1…" } } }, - "shop-v2": { "nodes": { … } } } } -``` - -Two checkouts of one app no longer share a local database or a bucket. - -## Chosen design - -**Postgres emulator.** -- A database is an instance with an emulator-generated id and labels, hosted on a `@prisma/dev` server named after that id. Every operation takes the id. -- A route lists all instances. -- New: **stop** shuts the instance's server down and keeps its data; **start** brings it back on the same port with the same data. Delete removes the server and its data. A stopped database keeps its port, by the same reasoning as Compute: its connection URL is in its dependents' environments. -- Old app-keyed servers and state are discarded on load. If `@prisma/dev` keeps data for them outside the emulator's own directory, remove it there too, or document exactly what is left behind. - -**Bucket emulator.** -- A bucket is an instance with an emulator-generated id and labels. Its name on the S3 wire is that id, replacing `--`. -- Its data root is the in-project directory Composer registers, `.prisma-composer/dev/buckets///` (the app is in the path so two apps in one directory don't share a bucket's files). The emulator stores that path verbatim and never interprets it. -- Credentials are registered per instance. A route lists all instances. -- A bucket has no process, so stop and start don't apply. - -**The record holds every app run from the directory.** This changes S1a's rule that switching apps deletes the previous app's Compute instances. -- `current` names the app `prisma dev` last ran here. `prisma dev url` (S3) resolves addresses in it. -- Switching apps stops the previous app's instances and keeps them, with their data and ports. Switching back resumes them warm. -- `--fresh` deletes the current app's instances and its entry. -- This follows Will's statement that the mapping is keyed by directory, app and topology. It also avoids deleting a database because a root module was renamed or run with a different `--name`. - -**`prisma dev` ends.** On Ctrl-C, SIGTERM or a failure, `prisma dev` stops the current app's service processes, as today, and also its Postgres servers. The next start brings them back with their data. - -**Everything else reaches databases and buckets through the record.** That includes the providers (`Database`, `Connection`, `Bucket`, `BucketKey`), attach, teardown, and `PgWarm`/`OrmMigration`, which already use the provider's URL. The emulators hook ensures them, as it does for Compute. Every instance gets the three `composer.*` labels. - -**Docs.** -- ADR-0052 is extended in place, since it is unreleased, to cover databases, buckets, stop and the multi-app record. ADR-0041's amendment note is adjusted if needed. -- `local-dev.md`, the running-locally guide and the core-concepts skill are updated, removing every "Postgres and buckets are still keyed by the app" caveat. -- The guide's note that `PRISMA_COMPOSER_EMULATORS_DIR` doesn't isolate Postgres goes, because generated ids make checkouts independent. - -## Scope - -In: the Postgres and bucket emulators and their clients; the record's multi-app shape and the switch rule for all kinds; stopping Postgres servers on exit; the data providers, attach and teardown; tests; docs. - -Out: `dev url`, `dev stop` and `emulator list` (S3); removing instances left by deleted directories. - -## Pre-investigated edge cases - -| Case | Expected | -| --- | --- | -| Warm start after Ctrl-C | Same database, same port, data intact, migrations no-op | -| A recorded database missing from the emulator | Re-created, requesting the remembered port; empty data, so migrations replay as on a fresh start | -| Switch app A → B → A | A's services, databases and buckets resume with their data and ports | -| Two checkouts of one app, each writes a row | Each reads only its own row; each lists only its own bucket objects | - -## Slice-specific done conditions - -- [ ] Two directories running one app with a database and a bucket each see only their own rows and objects (real emulators, temporary emulators directory). -- [ ] After `prisma dev` ends, no Postgres server of that app is running; the next start has the data. - -## Dispatch plan - -1. **Postgres emulator and client:** instances, labels, list, stop and start keeping data, delete. *Hands to 2.* -2. **Bucket emulator and client:** instances, labels, list, per-instance credentials. *Hands to 3.* -3. **Composer side:** the multi-app record, the data providers, attach and stop-on-exit, teardown, the two-directory data test. *Hands to 4.* -4. **Docs.** *Hands to:* the PR. diff --git a/.drive/projects/emulator-commands/slices/s2-command-group/spec.md b/.drive/projects/emulator-commands/slices/s2-command-group/spec.md deleted file mode 100644 index 631533c6e..000000000 --- a/.drive/projects/emulator-commands/slices/s2-command-group/spec.md +++ /dev/null @@ -1,59 +0,0 @@ -# S2 — A command path can also be a group - -Repo: prisma/prisma-cli. Package: `packages/cli-engine` (`@prisma/cli-engine`). Branch `feat/engine-command-group` off `main`. - -## At a glance - -Today the engine refuses to build a CLI where one command is mounted at a path that is also the prefix of other commands (`packages/cli-engine/src/execution/command-tree.ts:157`, "collides with a command mounted at a prefix of it"). After this slice it accepts it: - -```ts -createCli({ - groups: { dev: { brief: "…" } }, - commands: { - dev: devCommand, // prisma dev module.ts - "dev url": devUrlCommand, // prisma dev url gateway - "dev stop": devStopCommand, - }, -}); -``` - -```bash -prisma dev module.ts # runs devCommand with entry "module.ts" -prisma dev url gateway # runs devUrlCommand -prisma dev --help # devCommand's usage, then the subcommands under dev -prisma dev url --help # devUrlCommand's help -``` - -A bare `dev` that also has subcommands is the established convention (`pnpm dev`, `next dev`). The project needs it so `prisma dev ` keeps working while `prisma dev url` and `prisma dev stop` are added. - -## Chosen design - -- A path may carry both a command and child commands. The path still needs a group declaration in `groups`, because it has children; the group's brief is what appears in its parent's listing. -- Routing: when the next argument after the path is the name of a child, the child runs. Otherwise the path's own command runs with the remaining arguments. A child name therefore wins over a positional argument spelled the same. This is documented, and it is harmless for `dev`, whose positional is a file path. -- Help: ` --help` shows the path's own command help and lists the child commands. -- Every existing construction check still applies to both the command and its children. Only the prefix collision check is relaxed. Two commands at the same exact path are still refused. -- Everything else that walks the tree handles a node that is both: shell completion, the `commands` listing, unknown-command suggestions, redirects (a redirect still cannot sit on a mounted path), mount metadata, and anything in `packages/cli` that enumerates mounted paths (`mount-coverage.test.ts`, `e2e-coverage.test.ts`'s key reader). -- If Stricli's route maps support a default command for a route, use that. Otherwise implement the routing in the engine's adapter. The implementer checks which. - -## Coherence rationale - -One engine capability, proven with stand-in commands. No product command changes. Composer's `dev url` and `dev stop` mount later, in S4. - -## Scope - -In: the engine change, its tests, the engine's docs (README and AGENTS.md where they describe the tree), the version bump or changeset that the repo's release process expects for an engine change, and docs in `docs/architecture` or `docs/product` that state the old rule. - -Out: mounting any Composer command; changing `prisma dev`; output formats. - -## Slice-specific done conditions - -- [ ] Engine tests with a stand-in `dev` command and `dev url` child cover: the parent runs with a positional; the child runs; `--help` on each; a child name winning over the same-spelled positional; and two commands at one exact path still being refused. - -## References - -- `docs/architecture/cli-engine-requirements.md` (R12: the shell defines the command tree), `docs/product/command-principles.md`, `docs/product/cli-help-standard.md`. -- Project: `../../spec.md`, `../../design-notes.md`. - -## Dispatch plan - -1. One dispatch: tests first, then the change, then docs. *Hands to:* the PR. diff --git a/.drive/projects/emulator-commands/slices/s3-commands/spec.md b/.drive/projects/emulator-commands/slices/s3-commands/spec.md deleted file mode 100644 index 914e89098..000000000 --- a/.drive/projects/emulator-commands/slices/s3-commands/spec.md +++ /dev/null @@ -1,76 +0,0 @@ -# S3 — `prisma dev url`, `prisma dev stop` and `prisma emulator list` - -Repo: prisma/composer. Branch `feat/emulator-commands`, stacked on S1b (`feat/emulator-data-instances`, prisma/composer#356). - -## At a glance - -```bash -$ prisma dev module.ts # terminal 1 -$ prisma dev url gateway # terminal 2 -http://localhost:3001 -$ prisma dev url catalog --json -{"kind":"result","envelope":{"ok":true,"result":{"address":"catalog","kind":"database","url":"postgres://…:51300/…","state":"running"},…}} -$ prisma dev stop # stops this directory's app: services and Postgres servers; data stays -$ prisma emulator list # every emulator on this machine and every instance on it, with its labels -``` - -## Chosen design - -**Operations first (ADR-0043: commands render operations).** -- `@prisma/composer/control` gains two operations. `devUrl` takes a config, a cwd and an address, and returns `{ address, kind, url, state }`. `devStop` takes a config and a cwd, and stops the current app recorded in that directory. -- They go through the core local-target seam, so the framework stays target-agnostic. -- `LocalTargetDescriptor` gains what they need without an app name. Taking the dev state directory alone, it resolves an address and stops the directory's current app. The Prisma Cloud target implements both from `emulator-instances.json`. -- Core never learns the record's format. -- `emulator list` needs no project and no config, so it can't go through the seam. Its operation lives in the Prisma Cloud domain and is exported from `@prisma/composer-prisma-cloud/local-target`. It lists each emulator (`service`, `database`, `bucket`): whether it is running, its admin URL, and its instances with id, kind, URL, state and labels. -- It honours `PRISMA_COMPOSER_EMULATORS_DIR`, like everything else. - -**Commands.** -- The family exports three new commands. -- The host decides the mount paths (prisma-cli requirement R12). The intended paths are `dev url`, `dev stop` and `emulator list`; prisma-cli's engine now allows `dev` to be both a command and a group (prisma/prisma-cli#349). -- `dev url` and `dev stop` declare the `composer` config section. `emulator list` declares none. -- `emulator list` is defined in the Prisma Cloud domain, or wherever keeps layering legal. `@prisma/composer-cli`, in `9-public`, which may import both domains, assembles it into the family. -- The family's static import graph stays free of alchemy and effect values (`scripts/check-family-static-graph.mjs`). -- `family.test.ts`'s pinned command list is updated. - -**Output.** -- `dev url`, human format: the URL as the command's data line, so `--format human` works inside `$(…)`, plus a minimal human block. The JSON `result` is `{ address, kind, url, state }`, where `kind` is `service | database | bucket`. -- `state` is the service status; for a database `running | stopped`; for a bucket whether its emulator runs. -- URLs: - - **Service:** `http://localhost:`. - - **Database:** its connection string. - - **Bucket:** the bucket emulator's S3 endpoint plus `/` plus the bucket's id. Access keys are never printed. -- `dev stop` returns what it stopped. -- `emulator list` JSON `result`: `{ emulators: [{ kind, running, url, instances: [{ id, kind, url, state, labels }] }] }`. Human output: one block per emulator, and its instances with their address and directory from the labels when present. -- Follow prisma-cli's `docs/product/output-conventions.md` and `cli-style-guide.md` where they apply. The two-level list departs from the list convention; record that in the ADR. - -**Errors (ADR-0044; record every new code in its registry in this change).** -- No record in this directory → `DEV.NOT_STARTED_HERE`. Fix: run `prisma dev ` here. -- Unknown address → `DEV.ADDRESS_UNKNOWN`, listing the known addresses. -- The node's emulator isn't running → `DEV.EMULATOR_UNREACHABLE`. -- The node exists but isn't running → `DEV.NOT_RUNNING`. It names the state and points to `prisma dev`. -- `emulator list` with nothing running → ok, with every emulator `running: false`. No error. - -**Help.** Every summary, description and flag brief meets prisma-cli's `docs/product/cli-help-standard.md`: the reader has never used Prisma, and each term is defined in place, "emulator" included. The `dev` command's description must define what its children rely on (prisma-cli `cli-style-guide.md`, "Groups With Their Own Command"). - -**Docs.** -- A new ADR amends ADR-0050 (the family is no longer exactly `deploy` and `dev`) and ADR-0043 (the new operations), and lists the alternatives from `design-notes.md`. -- Also update `local-dev.md`, `deploy-cli.md`'s scope, the running-locally guide and the core-concepts skill, including that `prisma emulator start` and `stop` are planned. -- The guide shows the `jq` form for scripts: `curl "$(prisma dev url gateway | jq -r 'select(.kind=="result").envelope.result.url')"`. - -## Scope - -In: the operations, the seam additions, the commands, the family assembly, tests (bun:test; real emulators in temporary directories, skipped on Windows), the ADR, ADR-0044 and the docs. - -Out: mounting in prisma-cli (S4); prisma/web (S5); `emulator start`, `emulator stop` and removing instances. - -## Slice-specific done conditions - -- [ ] With an app running from a temporary directory on real emulators, `dev url` returns a URL for a service, a database and a bucket that each answer: an HTTP request, a Postgres `select 1`, and an S3 listing with the app's credentials. -- [ ] `dev stop` leaves no process or Postgres server of that app running. -- [ ] `emulator list` shows the instances of two directories with their labels. - -## Dispatch plan - -1. **Operations and seam:** `devUrl`, `devStop` and the emulator listing, with the errors and tests. *Hands to 2.* -2. **Commands and family assembly:** help text, output, family tests and the static-graph check. *Hands to 3.* -3. **Docs:** the ADR, ADR-0044, `local-dev.md`, `deploy-cli.md`, the guide and the skill. *Hands to:* the PR. diff --git a/.drive/projects/emulator-commands/slices/s6-dev-detach/spec.md b/.drive/projects/emulator-commands/slices/s6-dev-detach/spec.md deleted file mode 100644 index 92bf0a553..000000000 --- a/.drive/projects/emulator-commands/slices/s6-dev-detach/spec.md +++ /dev/null @@ -1,46 +0,0 @@ -# S6 — `prisma dev --detach` - -Repo: prisma/composer. Branch `feat/dev-detach`, stacked on `feat/emulator-commands` (prisma/composer#357). - -## At a glance - -```bash -$ prisma dev module.ts --detach # returns once every service answers -dev: ready -gateway: http://localhost:3001 -quotes: http://localhost:3000 -$ curl "$(prisma dev url gateway)" # same terminal -$ prisma dev stop # the only thing that stops a detached app -``` - -Will ruled on 2026-10-15 that `prisma dev` stays in the foreground, watching, and Ctrl-C stops the app, as today. `--detach` is opt-in. - -This follows prior art: Docker Compose `up -d --wait`, .NET Aspire `run --detach`, and Prisma ORM's own `prisma dev --detach`. Agents and scripts use `--detach` so they don't have to manage a long-running foreground process. - -## Chosen design - -- `--detach` runs the same pipeline as `prisma dev` up to "ready": converge, wait for every service to accept connections (prisma/composer#353), and print the front door. Then it exits 0. -- A detached run starts no watch loop and installs no stop-on-exit. -- The app keeps running on the emulators until `prisma dev stop`, or until the next `prisma dev` in the directory, which reconverges as today. -- A failure before ready stops what this run started, exactly as the foreground path does, and exits non-zero with the same error. -- Ctrl-C before ready stops the app and exits, as today. -- `--detach` combines with `--fresh` and `--name`. -- The `dev` operation in `@prisma/composer/control` gains an equivalent option. The command renders it, per the rule that the CLI's commands are thin renderers over the operations. The session handle's `stop()`, `closed` and events behave sensibly for a detached session: it is already closed and owns nothing. -- Running `prisma dev` in the foreground while the app is already running detached just takes over. It converges, which is a no-op when nothing changed, watches, and Ctrl-C stops the app. No special case. - -## Docs - -- Update the ADR that records `dev url`, `dev stop` and `emulator list` in place, since it is unreleased: `--detach` and why foreground stays the default, with the prior-art comparison as the alternative. -- `local-dev.md`: the session lifetimes section. -- The running-locally guide and the core-concepts skill: a "run it in the background" subsection. Agents and CI use `--detach` then `prisma dev stop`. -- The `dev` command's help: a `--detach` flag brief that meets prisma-cli's help standard. - -## Slice-specific done conditions - -- [ ] Against real emulators in a temporary directory, `prisma dev --detach` exits 0 once every service answers, and the services keep answering after it exits. -- [ ] `prisma dev stop` then stops them. -- [ ] A detached run that fails before ready leaves nothing of the app running. - -## Out of scope - -A run-then-stop mode (`--exec ""`, like Firebase `emulators:exec`). Record it in `deferred.md` as a likely next step for CI. diff --git a/.drive/projects/emulator-commands/slices/s7-piped-output/spec.md b/.drive/projects/emulator-commands/slices/s7-piped-output/spec.md deleted file mode 100644 index a41f8d274..000000000 --- a/.drive/projects/emulator-commands/slices/s7-piped-output/spec.md +++ /dev/null @@ -1,43 +0,0 @@ -# S7 — A piped stdout gets the command's data, not JSON - -Repo: prisma/prisma-cli. Branch `feat/piped-output-is-data` off `main`, which now includes prisma/prisma-cli#349. - -## At a glance - -```bash -$ prisma project list | head -3 # today: one NDJSON result frame -proj_cm3x… shop -proj_k91… blog -$ curl "$(prisma dev url gateway)" # today: curl gets a JSON frame -$ prisma project list --json | jq '…' # JSON is always explicit -``` - -Will ruled on 2026-10-15 that when stdout is not a terminal and no format flag is given, the CLI prints the command's plain data lines, and JSON requires `--json` (or `--format json`). - -This replaces the engine's rule from the PR that created it ("a terminal gets human output and a pipe gets JSON"). None of the CLIs surveyed switch to JSON when piped (gh, aws, kubectl, gcloud, az, docker, supabase). Single-value commands print the value bare: `docker port`, `encore db conn-uri`. - -## Chosen design - -**Engine (`packages/cli-engine`).** -- With no `--json`/`--format` flag, the format is `human` whether or not stdout is a TTY. -- In human format, stdout carries only the command's data lines (`presentation.stdout`), as the product docs already require. Human blocks go to stderr, undecorated when stderr isn't a TTY, as today. -- The existing rule that skips the stdout data lines when stdout and stderr are the same terminal stays. -- The pre-parse format sniff (`sniffFormat`) and `resolveAutoFormat` change accordingly. Errors in a pipe render as human errors on stderr with the exit code, unless `--json` is given. -- `--format markdown` is unchanged. -- This is a breaking change to the engine's output contract, so the engine gets a minor version bump with `pnpm bump-cli-engine-version`, and conformance exceptions as the repo's process requires. - -**Every mounted command defines its data lines.** About 34 of 58 presentations define no stdout today, so in a pipe they would print nothing. For each mounted command, decide what a pipe user expects and define `stdout`, following `docs/product/output-conventions.md`: -- A list: one line per item, the subject a follow-up command takes (normally its id), plus the name, tab-separated. -- A show: the resource's identifying value, or `key\tvalue` lines when there is no single value. -- A command whose result is one value: the bare value. Examples: a URL, a connection string, a created id. -- A command with no data (logout, delete): nothing on stdout. Its success is the exit code. - -Write the rules into the product docs first, as ARCHITECTURE.md requires, then implement. Composer and ORM family commands are owned by those repos. List any whose stdout needs work as follow-ups, with the specific command. - -**Docs.** Update every statement that a pipe gets JSON: `output-conventions.md` (TTY and piped behaviour, the `--format markdown` paragraph), `cli-style-guide.md`, `error-conventions.md`, the engine README and AGENTS.md, and the agent skills in `skills/` that tell agents to rely on piped JSON. Agents use `--json`. The e2e harness and tests that rely on piped JSON pass `--json` explicitly. - -## Slice-specific done conditions - -- [ ] Engine tests: no flag and piped stdout gives the data lines and no JSON; `--json` piped gives one NDJSON result frame as today; an error piped without `--json` gives a human error on stderr and the right exit code. -- [ ] A test that fails when a mounted command defines no stdout data lines and isn't on an explicit, reasoned list of commands with no data. -- [ ] The e2e suite still passes (`pnpm --filter @prisma/cli test:e2e`, if credentials are available; otherwise say so). diff --git a/.drive/projects/emulator-commands/spec.md b/.drive/projects/emulator-commands/spec.md deleted file mode 100644 index 689c299ad..000000000 --- a/.drive/projects/emulator-commands/spec.md +++ /dev/null @@ -1,92 +0,0 @@ -# Purpose - -Let users, docs and agents ask the `prisma` CLI where a locally running Composer node is, instead of assuming which port it got: - -```bash -npx prisma dev module.ts # terminal 1 -curl "$(npx prisma dev url gateway | jq -r 'select(.kind=="result").envelope.result.url')" # terminal 2 -``` - -and see everything the machine's local emulators are running: - -```bash -npx prisma emulator list -``` - -Today the Composer getting-started page on prisma/web hard-codes `curl localhost:3001` and a POST to `localhost:3000`. Those only work because prisma/composer#350 happens to give the getting-started app those ports. - -# At a glance - -The project rests on one model, settled with Will on 2026-10-13: - -- **The emulators are standalone, machine-local services.** The Compute, Postgres and bucket emulators hold instances: a running service process, a database, a bucket. They know nothing about Composer, apps, directories or node addresses. Each instance carries informative labels that the emulator stores and returns but never interprets, such as "started by Composer in /path/to/project for node `gateway`". Users will be able to start and stop emulators freely (a later project). -- **Composer is one client of the emulators.** `prisma dev` asks them for the instances its topology needs. -- **Composer owns the mapping from its names to emulator instances, per directory.** For each node address of the app run from a directory, Composer records which emulator instance backs it, in `.prisma-composer/dev/`. Every Composer operation on the emulators goes through that mapping: deploy, start, stop, logs, `--fresh`. - -On that model, two commands: - -- **`prisma dev url
`** prints the URL of a node of the app that `prisma dev` runs from the current directory. It covers services (`http://localhost:`), databases (the local connection string) and buckets (the bucket emulator's S3 endpoint and the bucket's name on it). It reads the directory's mapping, then asks the emulator for that instance. `--json` gives the address, URL, kind and state. -- **`prisma emulator list`** prints every emulator on the machine, whether it is running, and each running emulator's instances with their kind, id, URL, state and labels. It needs no Composer project. - -- **`prisma dev stop`** stops every instance this directory's app has on the emulators: service processes and Postgres servers. Data stays. An agent runs it before deleting its worktree, or after a `prisma dev` that was killed without a chance to clean up. - -When `prisma dev` ends (Ctrl-C, SIGTERM, or a failure), it does the same stop. Today it stops only the service processes and leaves Postgres servers running. An instance keeps its port from creation until it is deleted, whether it is running or stopped, so a directory's ports never change. When an instance has to be re-created (its emulator lost its state), Composer requests the port remembered in the directory's mapping. - -The shared emulator daemons themselves keep running; other directories may be using them. - -`prisma dev ` keeps working unchanged. The CLI engine learns to let a path be both a command and a group, so `prisma dev module.ts` runs the app and `prisma dev url gateway` runs the subcommand. A bare `dev` is the established convention (`pnpm dev`, `next dev`). - -# Non-goals - -- **Removing instances and their data.** A worktree that is deleted leaves its stopped instances' records and Postgres data on disk. They hold no process, but they do keep their ports reserved. A remove command comes with the later emulator start/stop project. -- **`prisma emulator start` and `stop`.** Planned; they change what runs on the shared emulators and get their own design. -- **Printing a bare value when stdout is a pipe.** The CLI keeps its rule that a piped stdout gets JSON frames. Docs use `jq`. No output-format change to the CLI engine. -- **`url` for things Composer didn't start.** `prisma dev url` takes a Composer node address only, never an emulator instance id. -- **Moving the emulators out of Composer.** Likely later, into their own project or into prisma-cli. This project keeps the emulator client behind one module boundary so that move stays cheap, but does not make it. -- **Migrating existing local dev state.** Local dev state is never migrated across Composer versions (running-locally guide). After the upgrade, a first `prisma dev` starts from empty instances. Instances the old emulators held are discarded. - -# Place in the larger world - -- **prisma/composer** — the emulators (`packages/1-prisma-cloud/0-lowering/dev-emulators`), the local target that drives them (`0-lowering/local-target`, `1-extensions/target/src/local-target`), the `dev` and `log` operations and the command family (`packages/0-framework/3-tooling/cli`), and `@prisma/composer-cli`, which the `prisma` binary mounts. -- **prisma/prisma-cli** — the CLI engine (`packages/cli-engine`), and the host that decides where each Composer command mounts (`packages/cli/src/cli.ts`, requirement R12 "the shell defines the command tree"). -- **prisma/web** — the Composer getting-started and local-development pages. -- **Depends on prisma/composer#353** (open): a service counts as running only once its port accepts connections, and `withEmulatorRetry` retries only `DEV.EMULATOR_UNREACHABLE`. Without it, a URL `dev url` prints can refuse connections for a second or two after `ready`. -- **Releases.** Composer and CLI releases are currently blocked on an upstream Alchemy issue. The prisma-cli mount and the prisma/web change wait for a Composer release with the commands. - -# Cross-cutting requirements - -- **The emulators never learn a Composer concept.** No emulator route, file or field names an app, address or directory as anything other than an opaque label. A test or lint holds this. -- **Composer's mapping is the only source of truth for "which instance is this node".** Labels are for display. No Composer code finds an instance by reading labels. -- **Architecture boundaries hold.** The framework domain (`0-framework`) still imports nothing from `1-prisma-cloud`. The commands reach the emulators through a boundary dependency-cruiser allows, recorded in an ADR. -- **Every printed URL answers.** `dev url` reports a service as running only when the emulator does, which after #353 means its port accepts connections. -- **Errors follow ADR-0044.** Every failure a user can cause has its own code, raised where it is detected, with why and fix: no `prisma dev` has run in this directory; no node has that address (the known addresses listed); the node's emulator is not running; the service is not running. New codes and any new namespace are recorded in ADR-0044 in the same change. -- **User-facing changes ship with their docs** (`.agents/rules/user-facing-surface-changes.mdc`): `docs/guides/running-locally.md` and the core-concepts skill in Composer, and help text that meets prisma-cli's `docs/product/cli-help-standard.md`, which includes defining "emulator" for a reader who has never used Prisma. -- **`prisma dev` behaves the same from the outside** at every intermediate step, apart from the documented loss of pre-upgrade local state. - -# Transitional-shape constraints - -- The emulator refactor lands one emulator at a time if that keeps PRs reviewable. Each step leaves `prisma dev` fully working on main. -- Until the prisma-cli engine change ships, the Composer family may export the new commands without the host mounting them. The host mounts them only after the engine supports `dev` as both a command and a group. - -# Project DoD - -- [ ] On a machine with nothing running, the getting-started app's `prisma dev module.ts` followed by the documented `curl "$(npx prisma dev url gateway | jq …)"` returns a quote, run from the published `prisma` binary. -- [ ] Two directories running the same app at once get separate instances, and `prisma dev url gateway` in each returns that directory's own service. -- [ ] After Ctrl-C of `prisma dev`, or `prisma dev stop` following a `kill -9`, no process of that directory's app is running; the next `prisma dev` gets the same ports. -- [ ] `prisma emulator list` shows the services, databases and buckets of both, each labelled with the directory and address that started it. -- [ ] No emulator route, state key, file name or emulator-generated field carries an app name, address or directory. Composer data reaches an emulator only as label values and inside the deployment payload, stored verbatim and never interpreted. -- [ ] Composer ADRs record the emulator model (amending ADR-0041) and the new commands (amending ADR-0050). ADR-0044 lists the new codes. -- [ ] `docs/design/10-domains/local-dev.md`, the running-locally guide and the core-concepts skill describe both commands and the model. -- [ ] prisma-cli mounts both commands, with `EXCLUSIONS` entries in the e2e coverage test (local only, no management API). -- [ ] The prisma/web getting-started and local-development pages use `prisma dev url` instead of hard-coded ports. - -# Open questions - -None blocking. Decisions taken by the orchestrator, recorded in `design-notes.md`: the mapping file's location and shape, the label keys Composer writes, the `--json` shapes, and the error code names. - -# References - -- Brief (unvalidated, superseded by this spec where they differ): summarized in the conversation of 2026-10-13. -- `docs/design/10-domains/local-dev.md`, ADR-0041, ADR-0043, ADR-0044, ADR-0050, ADR-0051. -- prisma/composer#350 (merged), prisma/composer#353 (open). -- prisma-cli: `docs/architecture/cli-engine-requirements.md` (R12), `docs/product/cli-help-standard.md`, `docs/product/output-conventions.md`, `packages/cli/tests/e2e-coverage.test.ts`, `packages/cli/tests/mount-coverage.test.ts`. From be7555ad4c0d332ca4b85a9ab0c3fba910a5d21b Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 21 Oct 2026 10:07:59 +0200 Subject: [PATCH 2/3] chore(drive): say why contract print is not on the ORM data-lines list Co-Authored-By: Claude Opus 5.5 Signed-off-by: willbot Signed-off-by: Will Madden --- .drive/deferred.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.drive/deferred.md b/.drive/deferred.md index 46f7a3190..71d8202be 100644 --- a/.drive/deferred.md +++ b/.drive/deferred.md @@ -234,7 +234,8 @@ ADR-0053 (the commands). What we deliberately didn't do: `migration status`, `migration log`, `migration show`, `migration plan`, `migration new`, `migration check`, `migration graph`, `migration ref set`, `migration ref delete`, `contract infer`, `contract format`, `db init`, - `db schema`, `db sign`, `db update`, `db verify`, `db migrate`, `orm init`. + `db schema`, `db sign`, `db update`, `db verify`, `db migrate`, `orm init`. (`contract print` and `contract emit` already write their + output to stdout, so a pipe gets data from them.) - **The `prisma` CLI's "agent skills out of date" notice names `@prisma/orm-postgres` in projects that don't use it** (prisma/prisma-cli). - **Removing instances and their data, and `prisma emulator start` / From 8e16538d3ba8ebd3fc732e6927610cd358cd86c5 Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 21 Oct 2026 10:11:58 +0200 Subject: [PATCH 3/3] chore(drive): limit the stdout note to contract print Co-Authored-By: Claude Opus 5.5 Signed-off-by: willbot Signed-off-by: Will Madden --- .drive/deferred.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drive/deferred.md b/.drive/deferred.md index 71d8202be..d65a6d523 100644 --- a/.drive/deferred.md +++ b/.drive/deferred.md @@ -234,8 +234,8 @@ ADR-0053 (the commands). What we deliberately didn't do: `migration status`, `migration log`, `migration show`, `migration plan`, `migration new`, `migration check`, `migration graph`, `migration ref set`, `migration ref delete`, `contract infer`, `contract format`, `db init`, - `db schema`, `db sign`, `db update`, `db verify`, `db migrate`, `orm init`. (`contract print` and `contract emit` already write their - output to stdout, so a pipe gets data from them.) + `db schema`, `db sign`, `db update`, `db verify`, `db migrate`, `orm init`. (`contract print` is not on the list: it already writes + the contract to stdout, so a pipe gets data from it.) - **The `prisma` CLI's "agent skills out of date" notice names `@prisma/orm-postgres` in projects that don't use it** (prisma/prisma-cli). - **Removing instances and their data, and `prisma emulator start` /