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
92 changes: 78 additions & 14 deletions docs/product/command-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,23 @@ Commands resolve project context in this order:
3. `.prisma/local.json` project pin when present, revalidated against platform data
4. durable platform mapping when available
5. remembered local project context, revalidated against platform data
6. `package.json` name matched exactly against accessible project id, name, or slug
7. unambiguous project creation for commands that are allowed to create projects
8. prompt in interactive mode, or structured failure in `--json` / `--no-interactive` mode

`--project` is an escape hatch for ambiguous or unavailable automatic
resolution, not a setup step. Only `app deploy` may create a missing project,
and only when the inferred name is unambiguous.
6. `package.json` name matched exactly against an existing accessible Project for non-mutating resolution
7. explicit setup choice from `project link`, `project create`, an interactive setup picker, `app deploy --project`, or `app deploy --create-project`
8. structured failure in `--json` / `--no-interactive` mode

`--project` is an explicit Project choice. When used from an unbound directory
with `app deploy`, it writes `.prisma/local.json` after validation and before
the deployment starts. `--create-project <name>` is the explicit deploy-time
choice to create and bind a new Project. Package names and directory names may
suggest setup defaults, but they never authorize Project creation by themselves.
When `PRISMA_PROJECT_ID` is set, `app deploy` and `app domain` commands skip
`.prisma/local.json` reads and do not write a new pin.

`app deploy` is stricter than general inspection commands: it does not use
package-name matching or remembered local context as Project scope. Without a
pin, durable mapping, env var, or explicit Project flag, it enters explicit
setup or fails with `PROJECT_SETUP_REQUIRED`.

### App Selection

Preview app commands that need an app resolve it in this order:
Expand All @@ -104,9 +111,6 @@ Preview app commands that need an app resolve it in this order:
7. interactive picker only when multiple matching apps make the target ambiguous
8. `APP_AMBIGUOUS` in non-interactive or `--json` mode when unresolved

When `PRISMA_APP_ID` is set, `app deploy` and `app domain` commands skip
`.prisma/local.json` reads and do not write a new pin.

`.prisma/local.json` pins the directory to a Workspace and Project only. It does
not pin an App ID. App services are branch-scoped; a service ID from `main`
must not be reused automatically when the user deploys from `feat/billing`.
Expand Down Expand Up @@ -392,6 +396,50 @@ prisma-cli project show --json
prisma-cli project show --project proj_123 --json
```

## `prisma-cli project create <name>`

Purpose:

- create a Prisma Project and bind the current directory to it

Behavior:

- requires auth
- creates a Project in the authenticated workspace
- writes `.prisma/local.json` with Workspace and Project IDs
- ensures `.prisma/` is ignored by Git
- does not create a Branch, App, Deployment, database, or Git repository connection
- fails if the platform rejects Project creation

Examples:

```bash
prisma-cli project create my-app
prisma-cli project create my-app --json
```

## `prisma-cli project link <id-or-name>`

Purpose:

- bind the current directory to an existing Prisma Project

Behavior:

- requires auth
- resolves exactly one Project by id or name in the authenticated workspace
- writes `.prisma/local.json` with Workspace and Project IDs
- ensures `.prisma/` is ignored by Git
- does not create remote resources
- fails with `PROJECT_NOT_FOUND` or `PROJECT_AMBIGUOUS` when the Project cannot be selected safely

Examples:

```bash
prisma-cli project link proj_123
prisma-cli project link "Acme Dashboard" --json
```

## `prisma-cli git connect [git-url]`

Purpose:
Expand Down Expand Up @@ -543,7 +591,7 @@ prisma-cli app run --build-type nextjs
prisma-cli app run --build-type bun --entry server.ts --port 3000
```

## `prisma-cli app deploy --project <id-or-name> --app <name> --branch <name> --framework <nextjs|hono|tanstack-start> --entry <path> --http-port <port> --env <name=value>`
## `prisma-cli app deploy --project <id-or-name> --create-project <name> --app <name> --branch <name> --framework <nextjs|hono|tanstack-start> --entry <path> --http-port <port> --env <name=value>`

Purpose:

Expand All @@ -552,14 +600,28 @@ Purpose:
Behavior:

- requires auth
- resolves or creates project context from `--project`, `PRISMA_PROJECT_ID`, `.prisma/local.json`, `package.json#name`, or current directory name
- resolves project context from `--project`, `--create-project`, `PRISMA_PROJECT_ID`, `.prisma/local.json`, durable platform mapping, or an interactive setup choice
- does not infer and create Project context from `package.json#name` or current directory name without explicit setup
- when no Project is resolved in interactive mode, asks which Project the directory should use:

```text
? Which Project should this directory use?
❯ Acme Dashboard
Billing API
Create a new Project
Cancel
```

- when "Create a new Project" is selected, prompts for a Project name with the package/directory name as a suggestion
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- when no Project is resolved in `--json` / `--no-interactive` mode, fails with `PROJECT_SETUP_REQUIRED`
- `--yes` alone does not choose Project scope; use `--project` or `--create-project`
- `--project` and `--create-project` are mutually exclusive with each other and with `PRISMA_PROJECT_ID`
- resolves or creates branch context from `--branch`, local Git branch, or `main`
- resolves or creates app context inside the resolved branch from `--app`, `PRISMA_APP_ID`, `package.json#name`, or current directory name
- does not prompt when there is no real choice; zero matching apps creates the inferred app
- detects supported frameworks and shows the resolved framework/runtime settings only while binding the directory for the first time
- writes `.prisma/local.json` after Project binding succeeds and before build/deploy starts, so retries after a failed deploy do not repeat setup
- asks `Customize settings? (y/N)` only while binding the directory for the first time, and only asks for Framework and HTTP port when the user opts in
- subsequent deploys print a compact target header such as `Deploying ./j1 to j1 / main / j1`
- after setup, deploy prints `Deploying to <Project> / <Branch> / <App>`; later deploys print a compact target header such as `Deploying ./j1 to j1 / main / j1`
- deploy progress uses short stage copy (`Building locally...`, `Built <size>`, `Uploading...`, `Uploaded`, `Deploying...`, `Deployed`) and never prints `Status: running` or `Deployment is running at ...`
- success human output prints `Live in <duration>`, the URL on its own line, and `Logs prisma-cli app logs`
- accepts repeated `--env NAME=VALUE` flags
Expand All @@ -572,6 +634,8 @@ Examples:

```bash
prisma-cli app deploy
prisma-cli app deploy --project proj_123
prisma-cli app deploy --create-project my-app --yes
prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example
prisma-cli app deploy --framework nextjs --http-port 3000
prisma-cli app deploy --branch feat-login --framework hono --http-port 3000
Expand Down
6 changes: 5 additions & 1 deletion docs/product/error-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ These codes are the minimum stable set for the MVP:
- `USAGE_ERROR`
- `AUTH_REQUIRED`
- `PROJECT_UNRESOLVED`
- `PROJECT_SETUP_REQUIRED`
- `PROJECT_CREATE_FAILED`
- `PROJECT_NOT_FOUND`
- `PROJECT_AMBIGUOUS`
- `APP_AMBIGUOUS`
Expand Down Expand Up @@ -198,10 +200,12 @@ Recommended meanings:
- `USAGE_ERROR`: invalid arguments or invalid command combination
- `AUTH_REQUIRED`: command needs an authenticated session
- `PROJECT_UNRESOLVED`: command needs project context and none could be resolved
- `PROJECT_SETUP_REQUIRED`: `app deploy` needs an explicit Project setup choice before it can continue
- `PROJECT_CREATE_FAILED`: Project creation failed before deployment or linking could continue
- `PROJECT_NOT_FOUND`: requested project does not exist or is not accessible
- `PROJECT_AMBIGUOUS`: multiple safe project candidates matched
- `APP_AMBIGUOUS`: multiple apps matched the inferred or explicit app target
- `LOCAL_STATE_STALE`: remembered local project context no longer matches platform data and continuing would be ambiguous
- `LOCAL_STATE_STALE`: local Project pin or remembered context no longer matches platform data and continuing would be ambiguous
- `BRANCH_NOT_DEPLOYABLE`: command tried to deploy to a non-deployable branch context
- `FRAMEWORK_NOT_DETECTED`: app deploy could not detect a supported Beta framework and no explicit framework/build type was provided
- `DEPLOYMENT_NOT_FOUND`: requested deployment id does not exist
Expand Down
36 changes: 22 additions & 14 deletions docs/product/output-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,21 +265,30 @@ When a command acts on a project, branch, app, or deployment, the output should
Examples:

- `app deploy` should state the resolved target that matters in the current slice
- first local `app deploy` binding should show Workspace, Project, Branch, App, Framework, and Runtime with source annotations before work begins
- first local `app deploy` binding should make the Project choice explicit before work begins
- subsequent `app deploy` calls should use a compact target header such as `Deploying ./j1 to j1 / main / j1`
- `app logs` should state the deployment it resolved
- `app list-deploys` should state which app or branch is being listed

The CLI must not make users guess which target a command acted on.

For `app deploy`, the setup block is a one-time local binding surface, not a
per-run summary. Once `.prisma/local.json` has been written, retries and later
deploys should feel like deploys, not setup. Do not repeat source annotations or
ask `Customize settings?` again unless the user deletes the pin or passes a
flag that explicitly changes targeting/configuration. The first setup title
should read `Setting up your local directory <path>`, followed by the resolved
table and then the plain-language note `This directory is now linked to project
<name>.`
For `app deploy`, Project setup is a one-time local binding surface, not a
per-run summary. If no Project is resolved in interactive mode, ask which
Project the directory should use with an arrow-key selection prompt. The picker
lists existing Projects, then `Create a new Project`, then `Cancel`; do not add
a manual id/name entry to the picker. If the user chooses to create a Project,
prompt for a Project name using the package/directory name as an editable
suggestion. Once `.prisma/local.json` has been written, retries and later
deploys should feel like deploys, not setup.

After setup, keep the confirmation compact:

```text
✔ Linked "./my-app" to Project "Acme Dashboard"
Saved .prisma/local.json

Deploying to Acme Dashboard / feat-login / my-app
```

Deploy progress should describe phases without claiming runtime success before
health is known. Do not print `Status: running` or `Deployment is running at ...`.
Expand All @@ -289,11 +298,10 @@ On success, print `Live in <duration>`, the URL on its own line, and
`Logs prisma-cli app logs`.
Human deploy output is stderr; `--json` is the machine-readable stdout path.

Deploy setup and result rows should share one table style: labels start two
spaces from the left margin, values align in one column, and optional origins
align in a dim third column prefixed with `·`. Values should be the strongest
part of the row; origins are secondary reassurance and must be dimmed only when
color is enabled.
Deploy result rows use one compact style: labels start two spaces from the left
margin and values align in one column. Avoid repeating a full Workspace /
Project / Branch / App / Framework / Runtime table in the setup path unless a
future command needs that extra detail.

## Action and Data Commands

Expand Down
24 changes: 16 additions & 8 deletions docs/product/resource-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ Rules:
- `project` is not the same thing as `app`
- Public Beta does not read or write committed config files such as `prisma.config.ts` or `.prisma/settings.json` for project resolution
- `.prisma/local.json` is a gitignored local pin/cache for Workspace and Project IDs; it is not a declarative repo config file
- `app deploy` may create missing project context only when resolution is unambiguous
- other commands must not create project context implicitly
- Project setup is explicit: users choose an existing Project or explicitly create a new one before remote work starts
- `app deploy` may orchestrate Project setup, but it must not silently choose or create Project scope
- everything under a project happens in a branch

### Branch
Expand Down Expand Up @@ -190,15 +190,23 @@ Long-term, branch is where app and database relationships meet.
Commands resolve project context in this order:

1. explicit `--project <id-or-name>` when present
2. durable platform mapping when available
3. remembered local project context, revalidated against platform data
4. `package.json` name matched exactly against accessible project id, name, or slug
5. unambiguous project creation for commands that are allowed to create projects
6. prompt in interactive mode, or structured failure in `--json` / `--no-interactive` mode
2. `PRISMA_PROJECT_ID` when set for headless deploy/domain commands
3. `.prisma/local.json` project pin when present, revalidated against platform data
4. durable platform mapping when available
5. remembered local project context, revalidated against platform data
6. `package.json` name matched exactly against an existing accessible Project for non-mutating resolution
7. explicit setup choice: `project link`, `project create`, interactive setup picker, `app deploy --project`, or `app deploy --create-project`
8. structured failure in `--json` / `--no-interactive` mode

Remembered local project context is an internal convenience after successful
resolution. It must be revalidated before use and must not be described to users
as durable linking. Only `app deploy` may create projects implicitly.
as durable linking. Package names and directory names may be suggested during
setup, but they do not authorize Project creation by themselves.

`app deploy` is stricter than general inspection commands: if the directory is
not pinned and no explicit Project source is provided, it enters explicit setup
or fails with `PROJECT_SETUP_REQUIRED` instead of using package-name or
remembered-local inference.

### App Selection Resolution

Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/commands/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ function createDeployCommand(runtime: CliRuntime): Command {
command
.addOption(new Option("--app <name>", "App name"))
.addOption(new Option("--project <id-or-name>", "Project id or name"))
.addOption(new Option("--create-project <name>", "Create and link a new Project before deploying"))
.addOption(new Option("--branch <name>", "Branch name"))
.addOption(
new Option("--framework <name>", "Framework to deploy")
Expand Down Expand Up @@ -198,13 +199,15 @@ function createDeployCommand(runtime: CliRuntime): Command {
const httpPort = (options as { httpPort?: string }).httpPort;
const envAssignments = (options as { env?: string[] }).env;
const projectRef = (options as { project?: string }).project;
const createProjectName = (options as { createProject?: string }).createProject;

await runCommand<AppDeployResult>(
runtime,
"app.deploy",
options as Record<string, unknown>,
(context) => runAppDeploy(context, appName, {
projectRef,
createProjectName,
branchName,
entrypoint: entry,
buildType,
Expand Down
52 changes: 50 additions & 2 deletions packages/cli/src/commands/project/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Command } from "commander";

import { runProjectList, runProjectShow } from "../../controllers/project";
import { runProjectCreate, runProjectLink, runProjectList, runProjectShow } from "../../controllers/project";
import {
renderProjectSetup,
renderProjectList,
renderProjectShow,
serializeProjectSetup,
serializeProjectList,
serializeProjectShow,
} from "../../presenters/project";
import { attachCommandDescriptor } from "../../shell/command-meta";
import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags";
import { runCommand } from "../../shell/command-runner";
import { configureRuntimeCommand, type CliRuntime } from "../../shell/runtime";
import type { ProjectListResult, ProjectShowResult } from "../../types/project";
import type { ProjectListResult, ProjectSetupResult, ProjectShowResult } from "../../types/project";
import { createEnvCommand } from "../env";

export function createProjectCommand(runtime: CliRuntime): Command {
Expand All @@ -21,11 +23,57 @@ export function createProjectCommand(runtime: CliRuntime): Command {

project.addCommand(createProjectListCommand(runtime));
project.addCommand(createProjectShowCommand(runtime));
project.addCommand(createProjectCreateCommand(runtime));
project.addCommand(createProjectLinkCommand(runtime));
project.addCommand(createEnvCommand(runtime));

return project;
}

function createProjectCreateCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("create"), runtime), "project.create");

command.argument("<name>", "Project name");
addGlobalFlags(command);

command.action(async (name, options) => {
await runCommand<ProjectSetupResult>(
runtime,
"project.create",
options as Record<string, unknown>,
(context) => runProjectCreate(context, String(name)),
{
renderHuman: (context, descriptor, result) => renderProjectSetup(context, descriptor, result),
renderJson: (result) => serializeProjectSetup(result),
},
);
});

return command;
}

function createProjectLinkCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("link"), runtime), "project.link");

command.argument("<id-or-name>", "Project id or name");
addGlobalFlags(command);

command.action(async (projectRef, options) => {
await runCommand<ProjectSetupResult>(
runtime,
"project.link",
options as Record<string, unknown>,
(context) => runProjectLink(context, String(projectRef)),
{
renderHuman: (context, descriptor, result) => renderProjectSetup(context, descriptor, result),
renderJson: (result) => serializeProjectSetup(result),
},
);
});

return command;
}

function createProjectListCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "project.list");

Expand Down
Loading
Loading