Skip to content
Merged
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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ The canonical command shape is:
prisma <group> <action>
```

The package includes project, environment-variable, service and service-version inspection, promotion, rollback, and deletion commands, plus the Prisma ORM (`contract`, `db`, `migration`, `orm init`) and Composer workflows (root `dev` and `deploy`), and the `postgres` and `bucket` resource groups. The product model intentionally avoids product-specific namespaces.
The package includes project, environment-variable, service and service-version inspection, promotion, rollback, and deletion commands, plus the Prisma ORM (`contract`, `db`, `migration`, `orm init`) and Composer workflows (root `dev` and `deploy`, with `dev url`, `dev stop` and `emulator list`), and the `postgres` and `bucket` resource groups. The product model intentionally avoids product-specific namespaces.

## Documentation

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Architecture and contributor workflow references:
- Group commands by developer workflow, not product ownership.
- No `orm`, `postgres`, or `compute` namespaces in the command surface.
- Canonical command shape is `prisma <group> <action>`.
- The shipped groups are `auth`, `project`, `git`, `branch`, `postgres`, `bucket`, `service`, `skills`, the root `init`, `dev` and `deploy` verbs, and the ORM family (`contract`, `db`, `migration`, `orm init`, `lsp`).
- The shipped groups are `auth`, `project`, `git`, `branch`, `postgres`, `bucket`, `service`, `skills`, the root `init` and `deploy` verbs, `dev` (a command that is also a group: `dev url`, `dev stop`), `emulator`, and the ORM family (`contract`, `db`, `migration`, `orm init`, `lsp`).
- Preserve the long-term resource model: `workspace -> project -> branch -> { service, database, bucket }`.

The mounted tree in `packages/cli/src/cli.ts` is the authoritative command surface.
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ This package exposes `prisma-cli` so it can coexist with the existing
| `postgres` | Create, inspect, back up, restore, and delete Prisma Postgres databases and their connections. |
| `bucket` | Create, list, and delete object-store buckets and their access keys. |
| `service` | Inspect services: versions, logs, domains, promote, roll back, delete. |
| `dev`, `deploy` | Run a Composer app locally; deploy it to the platform. |
| `dev`, `deploy` | Run a Composer app locally; deploy it to the platform. `dev url` prints a running node's local URL and `dev stop` stops the app. |
| `emulator` | List the local emulators `prisma dev` runs services, databases and buckets on. |
| `contract`, `db`, `migration`, `orm init`, `lsp` | The Prisma ORM workflow. |

Common examples:
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"dependencies": {
"@manypkg/tools": "^2.1.2",
"@prisma/cli-engine": "workspace:0.9.0",
"@prisma/composer-cli": "0.30.0",
"@prisma/composer-cli": "0.31.0",
"@prisma/compute-sdk": "0.43.0",
"@prisma/management-api-sdk": "1.80.0",
"@prisma/orm-toolchain": "8.0.0-rc.18",
Expand All @@ -62,7 +62,7 @@
"open": "^11.0.0"
},
"devDependencies": {
"@prisma/composer": "0.30.0",
"@prisma/composer": "0.31.0",
"@prisma/credentials-store": "^7.8.0",
"@repo/cli-conformance": "workspace:8.0.0-rc.23",
"@repo/cli-telemetry": "workspace:8.0.0-rc.23",
Expand Down
16 changes: 15 additions & 1 deletion packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,16 @@ export const cliGroups: Readonly<
"A ref is a named pointer to a contract, letting commands target a contract by a stable name. Set, list, and delete refs here.",
},
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",
},
emulator: {
brief:
"Inspect the local emulators: programs on this machine that stand in for Prisma Cloud during 'prisma dev'",
description:
"An emulator runs one kind of resource locally, so 'prisma dev' needs no account or network: one runs services, one runs Postgres databases, one runs S3-compatible buckets. Each holds instances, one per service, database or bucket that a 'prisma dev' started, from any directory on this machine. Emulators keep running after 'prisma dev' ends. Instances and their data stay until 'prisma dev --fresh' removes the current app's, and a stopped instance keeps its port.",
},
skills: {
brief:
"Manage Prisma skills for AI coding agents. Sync and list the instruction files",
Expand Down Expand Up @@ -411,9 +421,13 @@ export const mountedCommands: Readonly<Record<string, AnyCommand>> = {
"service domain delete": serviceDomainDeleteCommand,
"service domain retry": serviceDomainRetryCommand,
"service domain wait": serviceDomainWaitCommand,
// Composer's two product verbs, mounted at the root.
// Composer's product verbs: deploy and dev at the root, dev's
// children beneath it, and the local emulators under their own group.
deploy: composerCommandFamily.commands.deploy,
dev: composerCommandFamily.commands.dev,
"dev url": composerCommandFamily.commands["dev url"],
"dev stop": composerCommandFamily.commands["dev stop"],
"emulator list": composerCommandFamily.commands["emulator list"],
// The ORM family. Written out per path: the shell owns the tree
// (R12), so this map — not the family's own keying — is the source of
// truth for where each command mounts.
Expand Down
138 changes: 138 additions & 0 deletions packages/cli/tests/composer-mount.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Composer's commands, mounted at the paths this shell gives them, run
* against Composer's operations double: what is proven is the mount and
* what a pipe receives, not Composer's dev logic, which Composer's own
* integration tests run against real emulators.
*/
import { createTestCli } from "@prisma/cli-engine/testing";
import { defineConfig as composer } from "@prisma/composer/config";
import { nodeBuild } from "@prisma/composer/node/control";
import {
createComposerFamily,
type ListEmulators,
} from "@prisma/composer-cli/family";
import { createOperationsDouble } from "@prisma/composer-cli/testing";
import { describe, expect, it } from "vitest";
import { cliGroups, mountedCommands } from "../src/cli";

const build = nodeBuild();

const COMPOSER_SECTION = composer({
extensions: [build],
state: {
extension: build.id,
create: () => {
throw new Error("no command in these tests reaches the state store");
},
},
});

type Listing = Awaited<ReturnType<ListEmulators>>;

function listing(emulators: readonly EmulatorListing[]): Listing {
const value = { emulators };
return {
ok: true as const,
value,
assertOk: () => value,
assertNotOk: (): never => {
throw new Error("the listing succeeded");
},
};
}

type EmulatorListing = Extract<
Listing,
{ ok: true }
>["value"]["emulators"][number];

/** One running service emulator holding one instance that `prisma dev`
* started, and a database emulator that is not running. */
const ONE_INSTANCE = listing([
{
kind: "service",
running: true,
url: "http://localhost:4000",
instances: [
{
id: "svc_gateway",
kind: "service",
url: "http://localhost:4001",
state: "running",
labels: {
"composer.address": "gateway",
"composer.directory": "/projects/shop",
},
},
],
error: null,
},
{ kind: "database", running: false, url: null, instances: [], error: null },
]);

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

function shell(emulators: Listing = listing([])) {
const family = createComposerFamily({
operations: createOperationsDouble().operations,
listEmulators: async () => emulators,
});
const commands = Object.fromEntries(
COMPOSER_PATHS.map((path) => [path, family.commands[path]]),
);
return createTestCli({
commandFamilies: [family],
commands,
groups: { dev: cliGroups.dev, emulator: cliGroups.emulator },
config: { composer: COMPOSER_SECTION },
now: () => new Date(0),
});
}

describe("Composer's commands in the shell", () => {
it("mounts Composer's dev children and emulator list where the shell says", () => {
for (const path of COMPOSER_PATHS) {
expect(mountedCommands[path], path).toBeDefined();
}
expect(cliGroups.dev).toBeDefined();
expect(cliGroups.emulator?.description).toContain("emulator");
});

it("pipes the bare URL from dev url", async () => {
const result = await shell().run(["dev", "url", "gateway"]);

expect(result.exitCode).toBe(0);
expect(result.stdout).toBe("http://localhost:3000\n");
});

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

expect(result.exitCode).toBe(0);
expect(result.stdout).toBe("");
});

it("lists emulators", async () => {
const result = await shell().run(["emulator", "list"]);

expect(result.exitCode).toBe(0);
});

it("pipes one tab-separated line per emulator instance", async () => {
const result = await shell(ONE_INSTANCE).run(["emulator", "list"]);

expect(result.exitCode).toBe(0);
expect(result.stdout).toBe(
"service\tsvc_gateway\thttp://localhost:4001\trunning\tgateway\t/projects/shop\n",
);
});

it("shows dev's own help with its children listed", async () => {
const result = await shell().run(["dev", "--help"]);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("prisma-test dev");
expect(result.stdout).toContain("url <address>");
expect(result.stdout).toContain("stop");
});
});
11 changes: 11 additions & 0 deletions packages/cli/tests/e2e-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ const E2E_DIR = path.resolve(import.meta.dirname, "../e2e");
const ORM_FAMILY_REASON =
"ORM command: no management API involved. Real e2e lives in prisma/prisma (R7); the shell proves composition in orm-mount.test.ts (R8).";

/**
* Composer's local commands talk to emulators on this machine, never to
* the management API this suite exists to cover. Composer's own
* integration tests run them against real emulators.
*/
const LOCAL_EMULATOR_REASON =
"Local emulators only: no management API involved. Composer's own integration tests run it against real emulators.";

const EXCLUSIONS: Readonly<Record<string, string>> = {
"contract emit": ORM_FAMILY_REASON,
"contract infer": ORM_FAMILY_REASON,
Expand Down Expand Up @@ -98,6 +106,9 @@ const EXCLUSIONS: Readonly<Record<string, string>> = {
deploy:
"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 stop": LOCAL_EMULATOR_REASON,
"emulator list": LOCAL_EMULATOR_REASON,
};

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/tests/mount-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ const EXPECTED_MOUNT_PATHS: readonly string[] = [
"db verify",
"deploy",
"dev",
"dev stop",
"dev url",
"emulator list",
"feedback",
"git connect",
"git disconnect",
Expand Down
3 changes: 2 additions & 1 deletion packages/prisma/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ npx prisma project env list --role preview
| `postgres` | Create, inspect, back up, restore, and delete Prisma Postgres databases and their connections. |
| `bucket` | Create, list, and delete object-store buckets and their access keys. |
| `service` | Inspect services: versions, logs, domains, promote, roll back, delete. |
| `dev`, `deploy` | Run a Composer app locally; deploy it to the platform. |
| `dev`, `deploy` | Run a Composer app locally; deploy it to the platform. `dev url` prints a running node's local URL and `dev stop` stops the app. |
| `emulator` | List the local emulators `prisma dev` runs services, databases and buckets on. |
| `contract`, `db`, `migration`, `orm init`, `lsp` | The Prisma ORM workflow. |

Common examples:
Expand Down
2 changes: 1 addition & 1 deletion packages/prisma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"dependencies": {
"@manypkg/tools": "^2.1.2",
"@prisma/cli-engine": "workspace:0.9.0",
"@prisma/composer-cli": "0.30.0",
"@prisma/composer-cli": "0.31.0",
"@prisma/compute-sdk": "0.43.0",
"@prisma/management-api-sdk": "1.80.0",
"@prisma/orm-toolchain": "8.0.0-rc.18",
Expand Down
26 changes: 13 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading