Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3dffc41
docs(drive): slice spec for the dev url, dev stop and emulator list c…
wmadden-electric Oct 13, 2026
50d7368
feat: devUrl and devStop operations, and the emulator listing
wmadden-electric Oct 13, 2026
169c381
feat(cli): `dev url` and `dev stop` commands
wmadden-electric Oct 13, 2026
01e5c97
feat(composer-cli): `emulator list` command in the published family
wmadden-electric Oct 13, 2026
4df07a9
docs: prisma dev url, prisma dev stop and prisma emulator list
wmadden-electric Oct 13, 2026
c91b6e0
fix: dev url and dev stop wrap only target failures, and say why a no…
wmadden-electric Oct 13, 2026
0997f29
fix: emulator list reports each emulator on its own
wmadden-electric Oct 13, 2026
8d5cc13
fix(cli): define a node in the dev help, and stop claiming dev url wo…
wmadden-electric Oct 13, 2026
f2ce61a
test: devUrl and devStop against the real Prisma Cloud target through…
wmadden-electric Oct 13, 2026
d639a4a
docs: emulator list's errors and bundled client, NOT_RUNNING's reason…
wmadden-electric Oct 13, 2026
63cca11
docs(drive): record deferred items from the commands slice
wmadden-electric Oct 13, 2026
7c217f6
fix(cli): a running node with no URL is an internal error, not DEV.NO…
wmadden-electric Oct 13, 2026
c3af20d
test: keep the dev url tests' services off the ports other test files…
wmadden-electric Oct 13, 2026
8e86250
Merge feat/emulator-data-instances into feat/emulator-commands
wmadden-electric Oct 14, 2026
4139f41
Merge feat/emulator-data-instances into feat/emulator-commands
wmadden-electric Oct 14, 2026
4def66c
Merge feat/emulator-data-instances into feat/emulator-commands
wmadden-electric Oct 15, 2026
9329213
test(integration): a failed placeholder instance fails the dev url te…
wmadden-electric Oct 15, 2026
abc2169
Merge feat/emulator-data-instances into feat/emulator-commands
wmadden-electric Oct 15, 2026
3141896
Merge main into feat/emulator-commands
wmadden-electric Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .drive/projects/emulator-commands/deferred.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
- **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/<app>/<bucket>/`), 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.
76 changes: 76 additions & 0 deletions .drive/projects/emulator-commands/slices/s3-commands/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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:<port>`.
- **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 <entry>` 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.
6 changes: 6 additions & 0 deletions architecture.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,12 @@
"layer": "public",
"plane": "control"
},
{
"glob": "packages/9-public/composer-cli/src/family/**",
"domain": "public",
"layer": "public",
"plane": "control"
},
{
"glob": "packages/9-public/composer-cli/src/exports/testing.ts",
"domain": "public",
Expand Down
5 changes: 4 additions & 1 deletion docs/design/10-domains/deploy-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ and
## Scope

Two commands, mounted by the `prisma` CLI from `@prisma/composer-cli`'s
command family, plus one operation with no command:
command family, plus one operation with no command. The family also carries
`dev url`, `dev stop` and `emulator list`, which act on the local app and the
local emulators; [local-dev.md](local-dev.md) covers them
([ADR-0053](../90-decisions/ADR-0053-dev-url-dev-stop-and-emulator-list.md)).

- **`prisma deploy <entry>`** — deploy the application whose root node is
`entry`'s default export, to a stage (default: production).
Expand Down
39 changes: 38 additions & 1 deletion docs/design/10-domains/local-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ this doc is the mechanics.

## Scope

One command and one operation:
Three commands about the app, one about the machine, and one operation with no
command:

- **`prisma dev <entry>`** — bring up the application whose root node
is `entry`'s default export, entirely on the local machine, and keep it up:
Expand All @@ -27,6 +28,37 @@ One command and one operation:
return first (default 0, which returns live output only). It only reads the running
app's logs — it neither builds, provisions, starts, nor stops anything.

- **`prisma dev url <address>`** — where a node of the app `prisma dev` last
ran from this directory answers: a service's `http://localhost:<port>`, a
database's connection string (local credentials included), or a bucket's S3
endpoint plus `/<bucket>`. JSON result `{ address, kind, url, state }`; in
human format the URL alone goes to stdout. It is the `devUrl` operation
([ADR-0053](../90-decisions/ADR-0053-dev-url-dev-stop-and-emulator-list.md)).
- **`prisma dev stop`** — stop that app's service processes and database
servers, as ending `prisma dev` does, for a `dev` that was killed without the
chance. Data and ports stay. It is the `devStop` operation, returning
`{ app, stopped }`; `stopped` lists only the nodes whose emulator was
running.
- **`prisma emulator list`** — every local emulator on this machine, whether it
runs, its admin URL (`null` when it is not running), and its instances with
id, kind, URL, state and labels. An emulator that runs but cannot be listed
is reported in its own entry with an `error` of `{ code, message }`, and the
others are still listed. It needs no project or configuration, so it uses
the emulator client bundled into `@prisma/composer-cli` rather than the
project's `@prisma/composer-prisma-cloud`. The emulators' registry files and
instance-list routes are therefore read across Composer versions and must
stay backward compatible. `prisma emulator start` and `prisma emulator stop`
are planned.

`dev url` and `dev stop` take no entry and build nothing. The configuration
does not name the app, so they act on the app the directory's record names as
current. Core reaches it through the local-target seam: a
`LocalTargetDescriptor` may implement `current({ devDir })`, returning the
recorded app's name, its addresses, `locate(address)` and `stop()`. The Prisma
Cloud target implements it from `emulator-instances.json`; core never reads the
record. `dev url` reports a node only while it is running, so every URL it
prints answers.

Flags: `dev` takes `--name` (override the root's name, as on deploy) and
`--fresh` (destroy the dev stack and wipe the dev state directory before
starting). Stages do not apply — a working directory has
Expand Down Expand Up @@ -441,6 +473,11 @@ Deploy's rule holds: every failure names its fix.
| a service exits before it listens, or does not listen within the readiness timeout | the address, the port, the exit code if it exited, and the log path; `dev` stops the app's services before it returns the failure |
| an emulator does not accept connections (retried briefly; an error response from the emulator is never retried) | `DEV.EMULATOR_UNREACHABLE`, with the emulator's URL, from `dev` and `log` alike |
| `PRISMA_COMPOSER_SERVICE_READY_TIMEOUT_MS` is not a positive whole number | `DEV.SERVICE_READY_TIMEOUT_INVALID`, with the value |
| `dev url` or `dev stop` in a directory where no extension recorded an app | `DEV.NOT_STARTED_HERE`: run `prisma dev` here first |
| `dev url` with an address the app does not have | `DEV.ADDRESS_UNKNOWN`, listing the addresses it has |
| `dev url` for a node whose emulator is not running | `DEV.EMULATOR_UNREACHABLE` |
| `dev url` for a node that exists but is not running | `DEV.NOT_RUNNING`, naming its state; for a stopped node the fix is `prisma dev`, for a crash-looping service it is the service's log, for a node its emulator lost it is `prisma dev`, which creates it again |
| the local target fails without a code (for example an emulator's error response) in `dev url` or `dev stop` | `DEV.EMULATOR_FAILED` |
| service crash-loops | the address, exit code, and the last stderr lines, as a standing message |

## Out of scope (designed around)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Amended by [ADR-0049](ADR-0049-composers-configuration-is-the-composer-section-of-prisma-config.md): each operation takes a required `config: { value, file }`, and the effect-conflict diagnosis is retired. Amended by [ADR-0050](ADR-0050-composer-runs-as-prisma-deploy-and-prisma-dev.md): the `prisma-composer` binary is retired; the `prisma` CLI renders two of the four operations, as `prisma deploy` and `prisma dev`, and `destroy` and `log` are called from scripts. The surface below is otherwise unchanged.

Amended by [ADR-0053](ADR-0053-dev-url-dev-stop-and-emulator-list.md): two more operations, `devUrl` and `devStop`, act on the app `prisma dev` last ran from a directory.

## Decision

Composer's deploy pipeline is drivable in-process through one published subpath, **`@prisma/composer/control`**. It exposes four typed operations — `deploy`, `destroy`, `dev`, `log` — that take structured inputs and return structured results. They never parse argv and never call `process.exit`; the operations print nothing — the spawned alchemy child streams its own output to the terminal. The `prisma-composer` CLI is a thin renderer over these same operations, so the command-line surface and the programmatic surface cannot drift apart. *(Amended by [ADR-0050](ADR-0050-composer-runs-as-prisma-deploy-and-prisma-dev.md): the `prisma-composer` binary is retired. The `prisma` CLI renders two of the operations, as `prisma deploy` and `prisma dev`; `destroy` and `log` have no command and are called from scripts.)*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ vocabulary, not import edges.
| `COMPOSE` | topology loading: entry, root node, graph shape, naming | pipeline.ts (load-entry.ts, core's graph modules) |
| `ASSEMBLE` | assembling each service's deploy artifact | `@internal/assemble`'s assemble-services.ts |
| `DEPLOY` | target selection, stage, containers, preflight, engine, teardown, stack write | operations/execute-deploy-destroy.ts (main.ts, validate-stage.ts, run-alchemy.ts) |
| `DEV` | local dev pipeline and session | operations/execute-dev.ts (core/control/local-target.ts) |
| `DEV` | local dev pipeline and session, and the commands on the app it recorded | operations/execute-dev.ts (core/control/local-target.ts, operations/execute-dev-current.ts, `@internal/dev-emulators`) |
| `LOG` | log attach and tail | operations/execute-log.ts |
| `DEPS` | the consumer's installed dependency tree | operations/shared.ts |

Expand All @@ -93,7 +93,7 @@ Closed subcode registry:
BUILD_REQUIRED, ALCHEMY_BIN_MISSING, NODE_MISSING, STACK_WRITE_FAILED.
`prisma dev` starts the Alchemy child the same way `deploy` does, so it
raises ALCHEMY_BIN_MISSING and NODE_MISSING under `DEPLOY` too.
- `DEV` — PLATFORM_UNSUPPORTED, TARGET_UNSUPPORTED, CONTAINER_FAILED, TEARDOWN_FAILED, PREFLIGHT_FAILED, EMULATOR_FAILED, SERVICE_START_FAILED, CONVERGE_FAILED, ATTACH_FAILED, STACK_WRITE_FAILED, EMULATORS_DIR_INVALID, EMULATOR_UNREACHABLE, SERVICE_READY_TIMEOUT_INVALID. EMULATOR_UNREACHABLE is raised by the emulator client when it cannot connect, so `log` raises it under `DEV` too; for a connection failure it takes the place of SERVICE_START_FAILED, ATTACH_FAILED and `LOG.ATTACH_FAILED`.
- `DEV` — PLATFORM_UNSUPPORTED, TARGET_UNSUPPORTED, CONTAINER_FAILED, TEARDOWN_FAILED, PREFLIGHT_FAILED, EMULATOR_FAILED, SERVICE_START_FAILED, CONVERGE_FAILED, ATTACH_FAILED, STACK_WRITE_FAILED, EMULATORS_DIR_INVALID, EMULATOR_UNREACHABLE, SERVICE_READY_TIMEOUT_INVALID, NOT_STARTED_HERE, ADDRESS_UNKNOWN, NOT_RUNNING. EMULATOR_UNREACHABLE is raised by the emulator client when it cannot connect, so `log` raises it under `DEV` too; for a connection failure it takes the place of SERVICE_START_FAILED, ATTACH_FAILED and `LOG.ATTACH_FAILED`. `devUrl` also raises it when the emulator a node runs on is not running. NOT_STARTED_HERE (no `prisma dev` has recorded an app in the directory) is raised by `devUrl` and `devStop`; ADDRESS_UNKNOWN (the recorded app has no node at that address; the known addresses are listed) and NOT_RUNNING (the node exists but is not running; its state is named, and the reason and fix depend on it) by `devUrl` alone ([ADR-0053](ADR-0053-dev-url-dev-stop-and-emulator-list.md)). Both operations raise EMULATOR_FAILED when the local target fails without a code. `emulator list` fails only with EMULATORS_DIR_INVALID; an emulator it cannot list is reported in that emulator's entry with EMULATOR_UNREACHABLE or EMULATOR_FAILED.
- `LOG` — PLATFORM_UNSUPPORTED, ATTACH_FAILED, ADDRESS_UNKNOWN
- `DEPS` — EXECUTOR_UNLOADABLE *(EFFECT_VERSION_CONFLICT retired by [ADR-0049](ADR-0049-composers-configuration-is-the-composer-section-of-prisma-config.md))*

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ADR-0050: Composer runs as `prisma deploy` and `prisma dev`; it has no binary of its own

Amended by [ADR-0053](ADR-0053-dev-url-dev-stop-and-emulator-list.md): the family also ships `dev url`, `dev stop` and `emulator list`, so `prisma dev` is both a command and a group.

## Decision

Composer has no command-line binary of its own. The `prisma` CLI is its only command line, and it mounts exactly two Composer commands:
Expand Down
Loading
Loading