Skip to content

feat(dev): prisma dev url prints where a local service answers; add prisma dev stop and prisma emulator list - #357

Merged
wmadden-electric merged 19 commits into
mainfrom
feat/emulator-commands
Oct 15, 2026
Merged

wmadden-electric merged 19 commits into
mainfrom
feat/emulator-commands

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Linked issue

n/a. Part of the local-dev work; the plan is in .drive/projects/emulator-commands/.

Summary

Scripts, docs and agents can ask where a locally running service is, instead of assuming its port:

$ prisma dev module.ts        # terminal 1: runs the app
$ prisma dev url gateway      # terminal 2
http://localhost:3001
$ prisma dev stop             # stops this directory's app; its data stays
$ prisma emulator list        # every local emulator on this machine, and what it runs

Decision: Composer adds three commands, prisma dev url, prisma dev stop and prisma emulator list. The first two are also programmatic operations, devUrl and devStop, on @prisma/composer/control. ADR-0053 records this.

Why

Local ports aren't fixed. prisma dev gives services the lowest free ports from 3000, in dependency order, and each directory keeps its own (#354). So localhost:3001 is right for one checkout on one machine and wrong for the next. Composer's getting-started page currently hard-codes curl localhost:3001. prisma dev url lets it ask instead.

Background

prisma dev runs an app against emulators: long-lived local stand-ins for Prisma Cloud, shared across the machine, which run its services, Postgres databases and buckets. Each part of the app is a node, and its address is its dotted name in the app, such as gateway or shop.quotes. #354 and #356 made prisma dev record, in .prisma-composer/dev/emulator-instances.json, which emulator instance runs each node of the app it last ran from that directory.

prisma dev url <address>

prisma dev url reads that record for the current directory and prints where the node answers:

  • a service: http://localhost:<port>;
  • a database: its local connection string, password included, since it's only useful with it and only works locally;
  • a bucket: the bucket emulator's S3 endpoint plus /<bucket>. Access keys are never printed.

It prints a URL only while the node is running, so every URL it prints answers. Its JSON result is { address, kind, url, state }. Failures each have their own code:

Situation Code
prisma dev never ran in this directory DEV.NOT_STARTED_HERE
No node has that address (the known ones are listed) DEV.ADDRESS_UNKNOWN
The node's emulator isn't running DEV.EMULATOR_UNREACHABLE
The node exists but isn't running. The advice depends on why: it's stopped, so run prisma dev; it's crashing, so read its log; or its emulator lost it, so prisma dev re-creates it, empty DEV.NOT_RUNNING

prisma dev stop

prisma dev stop stops the current directory's app, its service processes and its Postgres servers, as ending prisma dev does. Data and ports stay. It's for an agent cleaning up before deleting its checkout, or for a prisma dev that was killed before it could stop the app. It reports what it actually stopped.

prisma emulator list

prisma emulator list asks about the machine, not a project, so it needs no prisma.config.ts. For each emulator it shows:

  • whether it is running, and its admin URL;
  • its instances, with their id, kind, URL, state and labels. The labels show the directory and address that started each one.

An emulator that doesn't answer gets an error in its own entry, and the others are still listed.

How it fits Composer's layers

Composer's core doesn't know about any particular deploy target. devUrl and devStop therefore go through the local-target interface that core already has. It gains an optional current({ devDir }), which returns the directory's recorded app and lets core locate or stop its nodes. The Prisma Cloud target implements it from the record. emulator list has no project to start from, so it lives next to the emulator clients, and @prisma/composer-cli adds it to the command family.

Mounting and piping

These commands reach users once the prisma CLI mounts them. That needs @prisma/cli-engine 0.8.0, which lets prisma dev be both a command and a group (prisma/prisma-cli#349, merged).

Today a piped prisma command prints JSON, so the guide shows scripts using jq. prisma/prisma-cli#353 changes a pipe to get the plain URL, after which curl "$(prisma dev url gateway)" works directly.

Testing performed

  • Through a real prisma.config.ts against real emulators (skipped on Windows):
    • devUrl returns a service URL that answers and a database connection string.
    • devStop stops exactly the app's nodes and closes their ports.
    • devUrl then fails with DEV.NOT_RUNNING.
  • At the target level, against real Compute, Postgres and bucket emulators:
    • A service URL answers HTTP, a database URL answers select 1, and a bucket URL takes an S3 write and listing.
    • Stop reports only what it stopped.
    • Two directories are listed with their labels.
  • Every error in the table above, over a fake target.
  • emulator list with nothing running, with an emulator that doesn't answer, and with one that returns an HTTP 500.
  • The commands' grammar, help and output.

Also pnpm build, typecheck, lint, lint:deps, lint:casts, the family import-graph check, and the packaging checks.

Checklist

  • All commits are signed off (git commit -s) per the DCO. The DCO status check will block merge if any commit is missing a Signed-off-by: trailer.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • The PR title is a conventional commit (feat, fix, chore, docs, refactor, test, build) — PR titles drive the auto-generated release notes.
  • Tests are updated (or n/a if the change is doc-only / refactor with no behavioural delta).

Notes for the reviewer

  • Stacked on feat(dev): checkouts of one app get their own local databases and buckets, and prisma dev stops its Postgres servers on exit #356. It retargets to main as the stack merges.
  • Docs updated: ADR-0053; ADR-0044 (three new DEV codes); local-dev.md; deploy-cli.md; the running-locally and deploying guides; the core-concepts skill. They say prisma emulator start and stop are planned.
  • The prisma CLI now carries the emulator client. @prisma/composer-cli bundles it, and the emulators may have been started by a different Composer version. So the emulators' registry files and instance-list routes now have to stay backward compatible. ADR-0053 says so.
  • emulator list's JSON departs from the CLI's usual list shape. It nests emulators and their instances, because an instance means nothing without its emulator.

Alternatives considered

  • prisma emulator url <address>. It would behave the same, but it answers a question about this directory's app, which belongs under dev. The emulator commands are about the machine.
  • A top-level prisma url. It's ambiguous between a local service and a deployed one.
  • Naming the app on the command line, or with --app. Addresses are relative to the directory's app, and app names repeat across checkouts.
  • emulator list through the local-target interface. That would require a project and a configuration for a question about the machine.

Agent: attila-51

🤖 Generated with Claude Code

wmadden-electric and others added 11 commits October 13, 2026 18:23
…ommands

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`@prisma/composer/control` gains two operations that act on the app
`prisma dev` last ran from a directory, read from its dev state alone:

- `devUrl({ config, address, cwd })` returns `{ address, kind, url,
  state }` for a running node: a service's URL, a database's connection
  string, or a bucket's S3 endpoint plus `/<bucket>`.
- `devStop({ config, cwd })` stops the app's service processes and
  database servers and returns what it stopped. Data and ports stay.

They fail with DEV.NOT_STARTED_HERE (no record in the directory),
DEV.ADDRESS_UNKNOWN (listing the known addresses),
DEV.EMULATOR_UNREACHABLE (the node's emulator is not running) and
DEV.NOT_RUNNING (naming the node's state).

The core local-target seam gains an optional `current({ devDir })`,
which returns the recorded app's name, its addresses, `locate(address)`
and `stop()`. Core never reads the record; the Prisma Cloud target
implements it from emulator-instances.json.

`listEmulators()` in the Prisma Cloud domain, exported from
`@prisma/composer-prisma-cloud/local-target`, lists each emulator
(`service`, `database`, `bucket`), whether it runs, its admin URL, and
its instances with id, kind, URL, state and labels. Nothing running is a
valid answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The family ships two more commands, keyed by the path they are meant to
mount at; the host decides where each one mounts.

- `dev url <address>` prints a node's local URL. In human format the
  URL alone goes to stdout, so `$(prisma dev url gateway --format
  human)` is the URL; the JSON result is `{ address, kind, url, state }`.
- `dev stop` stops the app `prisma dev` last ran from the directory and
  returns `{ app, stopped }`.

Both declare the `composer` config section. `createComposerFamily`
takes extra `commands`, so the published family can add `emulator
list`, and the operations double covers `devUrl` and `devStop`. The
`dev` command's description now defines what the commands beneath it
rely on: an emulator, a node's address, and that the app is recorded
in the directory.

The family's tests mount `deploy` and `dev` explicitly, since the
engine they build against (0.7.0) cannot mount commands beneath a
command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`@prisma/composer-cli/family` assembles `emulator list` into the
family, beside the framework's commands; it needs no project and no
config. The listing comes from the Prisma Cloud domain, which this
public package may import and the framework may not, and loads on first
use, so the family's static graph stays light.

Human output is one tree: each emulator, whether it runs and its admin
URL, and under it each instance with its URL, state, and the node and
directory that started it. The JSON result is `{ emulators: [{ kind,
running, url, instances: [{ id, kind, url, state, labels }] }] }`.
`createComposerFamily({ listEmulators })` lets a host substitute the
listing in its tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
ADR-0053 records the three commands, the devUrl and devStop operations,
the core local-target current() seam and the DEV errors it raises. It
amends ADR-0050 (the family was exactly deploy and dev) and ADR-0043
(the control subpath had four operations). The running-locally guide,
the deploying guide, local-dev.md, deploy-cli.md and the core-concepts
skill describe the commands, the jq form for scripts, and that
prisma emulator start and stop are planned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…de is down

The executor gave every error, bugs included, the DEV.EMULATOR_FAILED code.
Now only failures from the target's current, locate and stop calls get it;
other errors are rethrown, and a recorded address the target cannot locate
is an InternalError.

DEV.NOT_RUNNING now chooses its reason and fix from the node's condition,
which the target reports: a stopped node needs `prisma dev`, a
crash-looping service needs its log read, and a node its emulator lost is
created again by `prisma dev`.

dev stop reports only the nodes whose emulator was running. Stopping no
longer swallows errors other than "the emulator is not running", so an
invalid emulators directory fails with DEV.EMULATORS_DIR_INVALID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
An emulator that does not answer, or answers with an error, no longer fails
the whole listing. Its entry carries `error: { code, message }`, with
DEV.EMULATOR_UNREACHABLE or DEV.EMULATOR_FAILED, and the others are still
listed. `url` is null for an emulator that is not running. Only an
invalid emulators directory fails the command.

The listEmulators re-export from @prisma/composer-prisma-cloud/local-target
is removed: nothing used it, and the command uses the client bundled into
@prisma/composer-cli.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…rks after dev ends

The dev card now defines a node as one service, database or bucket in the
app, and says that `prisma dev url` works while `prisma dev` runs and
`prisma dev stop` also works after it was killed. The dev url summary
defines a node in place, the address brief drops "module", and the jq
example uses -e so a failure does not print a URL.

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

The target's own emulators hook records the integration fixture's app in a
temporary directory on real emulators in a temporary emulators directory.
devUrl, called with the package's prisma.config.ts, gives a URL the service
answers at and the database's connection string; devStop stops the
services and the database server, their ports close, and devUrl then fails
with DEV.NOT_RUNNING.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…s, and the safe jq form

ADR-0053 and local-dev.md say emulator list uses the client bundled into
@prisma/composer-cli, record that the emulators' registry files and list
routes are now read across Composer versions, and give the per-emulator
error and null url in its JSON. ADR-0044 says devStop raises only
NOT_STARTED_HERE. The guide, the skill and the ADR use
`url=$(… | jq -er …) && curl "$url"`, because jq -r alone prints null on
failure and curl runs anyway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@prisma-gizmo

prisma-gizmo Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

✅ Gizmo reviewed 9329213 — posted 0 inline comment(s) this pass.

Open findings: none

Change walkthrough

This incremental pass reviewed the delta since the last reviewed commit: a coordinated dependency/version-bump block across the workspace, plus design-doc, guide, skill, and integration-test updates. It does not touch the dev url/dev stop/emulator list implementation itself.

Version and dependency bumps. Every workspace manifest moves from 0.29.1 to 0.30.0 (the minor bump produced by scripts/bump-minior.ts's procedure via set-version.ts), with coordinated dependency upgrades: alchemy 2.0.0-beta.78 → beta.83, effect 4.0.0-rc.115 → 4.0.3, @prisma/orm-postgres/orm-toolchain rc.17 → rc.18, @distilled.cloud/prisma rc.12 → rc.15, and vitest ^4 → ^5. I verified these pins are consistent across all packages, examples, test/integration, and the website — no manifest was left behind, and the lockfile-adjacent checks the repo runs (check-npm-effect-resolution, check-orm-pins, check-publish-deps, check:cli-engine-pin) map onto exactly these files. @prisma/composer-cli deliberately keeps its @prisma/cli-engine 0.7.0 peer/dev pin, matching the PR's stated constraint that the family waits on the CLI's 0.8.0.

Docs. docs/design/10-domains/local-dev.md refines the concurrent-starts rules: a start whose process has already exited no longer accepts waiters, so a session-resume or identical-deployment PUT arriving in that window starts the service again — the prose and the two qualified table rows agree with each other and with the readiness/crash-loop rules earlier in the section. docs/guides/deploying.md documents the node-address-as-logicalId identity semantics (address is the identity; renaming or re-homing a node makes a new entity and deletes the old rows, data included) and updates the effect pin in the hoisting-resolution recipe. The same identity paragraph lands in skills/prisma-composer-core-concepts/SKILL.md alongside the version stamp, and the two docs stay consistent.

Tests. test/integration/test/control.dev-current.test.ts hardens the port-skip placeholder-instance setup: a failed creation now throws with the status and response body before the unchecked { port } JSON parse, turning a silent bad-port path into a clear setup error.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 66f7b7f2-6d09-4a71-870e-c6a336ed2390
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/composer@357
npm i https://pkg.pr.new/@prisma/composer-cli@357
npm i https://pkg.pr.new/@prisma/composer-prisma-cloud@357

commit: 3141896

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: 🟡 1 minor · trace

Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-dev-current.ts Outdated

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No critical or major Gizmo finding is open and the head commit has been reviewed. Approving.

…T_RUNNING

The local-target seam reserves an absent URL for an instance the emulator
lost, which reports the `missing` condition. A target that reports a node
as running with no URL broke that contract, and devUrl printed "is not
running (running)". It now raises an InternalError, as it does for a
recorded address the target cannot locate.

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

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: none · trace

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No critical or major Gizmo finding is open and the head commit has been reviewed. Approving.

… use

Each test file runs its own Compute emulator, and each emulator gives
service ports from 3000. A port is free when the emulator gives it, but
another file's service can bind it first, and then the readiness probe
and the test's fetch reach that file's service: on macOS CI the gateway
answered "first", the body compute-deployment.test.ts serves.

The two files that fetch a service they deployed now fill the ports below
a start of their own with placeholder instances first: 3050 for
local-target-current.test.ts and 3100 for control.dev-current.test.ts. Each
asserts its service's port is at or above that start, and the listing test
ignores the placeholders.

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

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: none · trace

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No critical or major Gizmo finding is open and the head commit has been reviewed. Approving.

wmadden-electric and others added 2 commits October 14, 2026 08:40
Brings in the `listens: false` deployment property for services that never
listen on their port, and the test of its error-message rewrite. No
conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Brings in the bucket-path fix in the local-dev criteria 4 and 5 proof.

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
wmadden-electric and others added 2 commits October 15, 2026 17:49
Brings in #353's start fixes and tests, the readiness tests' own ports,
and main. The target extension's and @prisma/composer-cli's manifests
keep this branch's dev dependencies at main's version, 0.30.0, and take
main's @distilled.cloud/prisma; the lockfile follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…st at its cause

The loop that fills ports below 3100 read .port from any response. It now throws with the emulator's status and body when creating a placeholder fails. local-target-current.test.ts creates its placeholders through the compute client, which already throws on an error response.

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

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: none · trace

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Gizmo finding is open and the head commit has been reviewed. Approving.

Brings in #353's own port range for the readiness tests; the test file keeps the instance-keyed version. No other changes.

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

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Gizmo finding is open and the head commit has been reviewed. Approving.

@wmadden-electric wmadden-electric changed the title feat(dev): prisma dev url prints where a local node answers, prisma dev stop stops the app, prisma emulator list shows the machine's emulators feat(dev): prisma dev url prints where a local service answers; add prisma dev stop and prisma emulator list Oct 15, 2026
Base automatically changed from feat/emulator-data-instances to main October 15, 2026 19:13
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Gizmo finding is open and the head commit has been reviewed. Approving.

@wmadden-electric
wmadden-electric merged commit 7fa7302 into main Oct 15, 2026
34 of 35 checks passed
@wmadden-electric
wmadden-electric deleted the feat/emulator-commands branch October 15, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant