Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 docs/product/command-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ The scaffolded config imports `definePrismaConfig` from `prisma/config` and spel

Resolve a service version and show or stream its logs.

`dev logs` is the local counterpart: it reads the services of the app `prisma dev` runs from the current directory, so it sits under `dev` beside `dev url` and `dev stop` and `service logs` keeps meaning a deployed service. It takes the same `--tail` (default 100) and `--follow` (off by default) as `service logs`, so the two read alike.

### `delete` and `remove`

`delete` destroys a resource; `remove` detaches one thing from another without destroying it. A command that permanently destroys what it targets is spelled `delete` (`project delete`, `service delete`, `postgres delete`); `remove` is reserved for detachment.
Expand Down
5 changes: 5 additions & 0 deletions docs/product/output-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,11 @@ In human mode, `app logs` is a special case: raw app log lines are the primary
streamed payload and go to stdout so they can be piped or redirected. CLI
context, status, decoration, and errors stay on stderr.

`dev logs` follows the same rule for a local app. Without an address it
prefixes each line with `[address] ` so lines from several services stay
attributable in a pipe; with one, lines are bare. In `--json` mode each line is
an `output` frame whose `data` is `{ service, line }`.

## Data Conventions

- ids are opaque and stable
Expand Down
22 changes: 15 additions & 7 deletions docs/reference/error-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Namespaces:
| `BRANCH` | Branch listing (`prisma branch`) |
| `BUCKET` | Bucket and bucket-key management (`prisma bucket`) |
| `CLI` | Engine-level invocation: arguments, config loading, prompts, consent, credentials, child processes |
| `DEV` | Running an app on this machine with Composer (`prisma dev`, `prisma dev url`, `prisma dev stop`, `prisma emulator list`) |
| `DEV` | Running an app on this machine with Composer (`prisma dev`, `prisma dev url`, `prisma dev logs`, `prisma dev stop`, `prisma emulator list`) |
| `FEEDBACK` | Sending product feedback (`prisma feedback`) |
| `GIT` | Git repository connections (`prisma git`) |
| `INIT` | Project initialization diagnostics (`prisma init`) |
Expand Down Expand Up @@ -223,31 +223,39 @@ The typed path routed to no command (stricli's route failure, mapped at the adap

## DEV

These codes come from Composer, the command family behind `prisma dev`, `prisma dev url`, `prisma dev stop` and `prisma emulator list`. A node is one service, database or bucket in your app; its address is its name in the app, with dots for a nested node (`catalog.database`). Each node runs on a local emulator: a program on this machine that stands in for Prisma Cloud.
These codes come from Composer, the command family behind `prisma dev`, `prisma dev url`, `prisma dev logs`, `prisma dev stop` and `prisma emulator list`. A node is one service, database or bucket in your app; its address is its name in the app, with dots for a nested node (`catalog.database`). Each node runs on a local emulator: a program on this machine that stands in for Prisma Cloud.

### DEV.ADDRESS_UNKNOWN

`prisma dev url <address>` named an address that the app `prisma dev` last ran from this directory does not have. The `why` lists the addresses that are recorded for it. The fix is to pass one of those; a nested node has a dotted address, such as `catalog.database`. Meta: `address`, `known` (the recorded addresses).
`prisma dev url <address>` or `prisma dev logs <address>` named an address that the app `prisma dev` last ran from this directory does not have. The `why` lists the addresses that are recorded for it. The fix is to pass one of those; a nested node has a dotted address, such as `catalog.database`. Meta: `address`, `known` (the recorded addresses).

### DEV.EMULATOR_FAILED

A local emulator answered a request with an error. `prisma dev url`, `prisma dev stop` and `prisma dev` raise it when the emulator that runs a node fails a request. In `prisma emulator list` it is not a failure of the command: the emulator is listed with this code and the emulator's message instead of its instances, and the command still exits `0`. Meta: none.
A local emulator answered a request with an error. `prisma dev url`, `prisma dev logs`, `prisma dev stop` and `prisma dev` raise it when the emulator that runs a node fails a request. In `prisma emulator list` it is not a failure of the command: the emulator is listed with this code and the emulator's message instead of its instances, and the command still exits `0`. Meta: none.

### DEV.EMULATOR_UNREACHABLE

A local emulator could not be reached. `prisma dev url` raises it when the emulator that runs the node is not running; the fix is to run `prisma dev <entry>` in this directory, which starts the emulators the app needs. It is also raised when an emulator is registered and its process is alive but it did not accept the connection; the fix then is to run the command again, and if it keeps failing, to end the emulator's process so the next `prisma dev` starts a new one. Meta: `address` and `kind` (the node's address and kind) when the emulator is not running; `url` (the emulator's address) when it did not accept the connection.
A local emulator could not be reached. `prisma dev url` raises it when the emulator that runs the node is not running, and `prisma dev logs` when the emulator that runs the services is not running; the fix is to run `prisma dev <entry>` in this directory, which starts the emulators the app needs. It is also raised when an emulator is registered and its process is alive but it did not accept the connection; the fix then is to run the command again, and if it keeps failing, to end the emulator's process so the next `prisma dev` starts a new one. Meta: `address` and `kind` (the node's address and kind) when the emulator is not running; `url` (the emulator's address) when it did not accept the connection.

### DEV.EMULATORS_DIR_INVALID

Any command that reaches the local emulators (`prisma dev`, `prisma dev url`, `prisma dev stop`, `prisma emulator list`) found the `PRISMA_COMPOSER_EMULATORS_DIR` environment variable set to a relative path. Every process that starts or reaches the local emulators resolves this directory, and a relative path would name a different directory in each working directory. The fix is to set it to an absolute path (`~` is not expanded) or to unset it, which uses `~/.prisma-composer/emulators`. Meta: `variable`, `value`.
Any command that reaches the local emulators (`prisma dev`, `prisma dev url`, `prisma dev logs`, `prisma dev stop`, `prisma emulator list`) found the `PRISMA_COMPOSER_EMULATORS_DIR` environment variable set to a relative path. Every process that starts or reaches the local emulators resolves this directory, and a relative path would name a different directory in each working directory. The fix is to set it to an absolute path (`~` is not expanded) or to unset it, which uses `~/.prisma-composer/emulators`. Meta: `variable`, `value`.

### DEV.LOGS_UNSUPPORTED

`prisma dev logs` without `--follow` ran against an app whose Composer packages are older than the Composer in this `prisma` CLI, and they cannot read a service's log without following it. The fix is to upgrade the app's Composer packages (`@prisma/composer`, `@prisma/composer-prisma-cloud`) to the version this CLI uses, or to pass `--follow`. Meta: none.

### DEV.NO_LOGS

`prisma dev logs <address>` named a database or a bucket. Only services have logs: what a service's process writes to stdout and stderr. The fix is to pass a service's address, or no address for every service. Meta: `address`, `kind`.

### DEV.NOT_RUNNING

`prisma dev url` named a node that is not running, so it has no URL that answers. The `why` says which of three cases applies: the node is stopped (`prisma dev stop`, `prisma dev` ending, and running another app in this directory all stop an app's services and database servers; the fix is to run `prisma dev <entry>` in this directory); the node exited after it started and is being restarted or was held after repeated crashes (the fix is to read its log, whose path is in the `fix` and `meta.logPath` when known, fix the cause and rebuild); or the emulator no longer has it, for example because a Composer upgrade discarded the emulator's state (running `prisma dev <entry>` creates it again, empty). Meta: `address`, `kind`, `state`, and `logPath` when known.

### DEV.NOT_STARTED_HERE

`prisma dev url` or `prisma dev stop` ran in a directory where `prisma dev` has not run. An app's local services, databases and buckets are recorded in the directory `prisma dev` runs from, and there is no such record here. The fix is to run `prisma dev <entry>` in this directory, or to run the command from the directory where `prisma dev` ran. Meta: `cwd`.
`prisma dev url`, `prisma dev logs` or `prisma dev stop` ran in a directory where `prisma dev` has not run. An app's local services, databases and buckets are recorded in the directory `prisma dev` runs from, and there is no such record here. The fix is to run `prisma dev <entry>` in this directory, or to run the command from the directory where `prisma dev` ran. Meta: `cwd`.

## FEEDBACK

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export const cliGroups: Readonly<
orm: { brief: "Initialize a Prisma ORM project" },
dev: {
brief:
"Run your app on this machine, with no Prisma account. Start it, print a node's local URL, stop it",
"Run your app on this machine, with no Prisma account. Start it, print a node's local URL, read its logs, stop it",
},
emulator: {
brief:
Expand Down Expand Up @@ -426,6 +426,7 @@ export const mountedCommands: Readonly<Record<string, AnyCommand>> = {
deploy: composerCommandFamily.commands.deploy,
dev: composerCommandFamily.commands.dev,
"dev url": composerCommandFamily.commands["dev url"],
"dev logs": composerCommandFamily.commands["dev logs"],
"dev stop": composerCommandFamily.commands["dev stop"],
"emulator list": composerCommandFamily.commands["emulator list"],
// The ORM family. Written out per path: the shell owns the tree
Expand Down
26 changes: 24 additions & 2 deletions packages/cli/tests/composer-mount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,22 @@ const ONE_INSTANCE = listing([
{ kind: "database", running: false, url: null, instances: [], error: null },
]);

const COMPOSER_PATHS = ["dev", "dev url", "dev stop", "emulator list"];
const COMPOSER_PATHS = [
"dev",
"dev url",
"dev logs",
"dev stop",
"emulator list",
];

function shell(emulators: Listing = listing([])) {
const family = createComposerFamily({
operations: createOperationsDouble().operations,
operations: createOperationsDouble({
devLogLines: [
{ service: "catalog", line: "catalog ready" },
{ service: "gateway", line: "GET / 200" },
],
}).operations,
listEmulators: async () => emulators,
});
const commands = Object.fromEntries(
Expand Down Expand Up @@ -105,6 +116,16 @@ describe("Composer's commands in the shell", () => {
expect(result.stdout).toBe("http://localhost:3000\n");
});

it("pipes the log lines from dev logs, labelled by service", async () => {
const all = await shell().run(["dev", "logs"]);
const one = await shell().run(["dev", "logs", "gateway", "--tail", "20"]);

expect(all.exitCode).toBe(0);
expect(all.stdout).toBe("[catalog] catalog ready\n[gateway] GET / 200\n");
expect(one.exitCode).toBe(0);
expect(one.stdout).toBe("GET / 200\n");
});

it("pipes nothing from dev stop", async () => {
const result = await shell().run(["dev", "stop"]);

Expand Down Expand Up @@ -133,6 +154,7 @@ describe("Composer's commands in the shell", () => {
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("prisma-test dev");
expect(result.stdout).toContain("url <address>");
expect(result.stdout).toContain("logs");
expect(result.stdout).toContain("stop");
});
});
1 change: 1 addition & 0 deletions packages/cli/tests/e2e-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const EXCLUSIONS: Readonly<Record<string, string>> = {
"Provisions real cloud infrastructure for an app entry point, through a child `alchemy deploy`. Standing that up per CI run is neither cheap nor unattended-safe.",
dev: "A session command: it runs until SIGINT or SIGTERM, redeploying on file change, so it has no happy path that terminates on its own.",
"dev url": LOCAL_EMULATOR_REASON,
"dev logs": LOCAL_EMULATOR_REASON,
"dev stop": LOCAL_EMULATOR_REASON,
"emulator list": LOCAL_EMULATOR_REASON,
};
Expand Down
1 change: 1 addition & 0 deletions packages/cli/tests/mount-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"db verify",
"deploy",
"dev",
"dev logs",
"dev stop",
"dev url",
"emulator list",
Expand Down Expand Up @@ -192,7 +193,7 @@
Object.values(MOUNTED_FAMILIES),
FAMILYLESS,
),
).toEqual([]);

Check failure on line 196 in packages/cli/tests/mount-coverage.test.ts

View workflow job for this annotation

GitHub Actions / Grammar Completeness

tests/mount-coverage.test.ts > prisma mount coverage > gives every mounted command a family, except the deliberately familyless ones

AssertionError: expected [ 'dev logs' ] to deeply equal [] - Expected + Received - [] + [ + "dev logs", + ] ❯ tests/mount-coverage.test.ts:196:7
});

it("declares a group for every mount path prefix", () => {
Expand Down
Loading