Skip to content

Commit fefad53

Browse files
authored
Merge pull request #50 from prisma/feat/explicit-project-setup-deploy
2 parents 4b1cb55 + d063b83 commit fefad53

17 files changed

Lines changed: 1277 additions & 322 deletions

File tree

docs/product/command-spec.md

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,23 @@ Commands resolve project context in this order:
8181
3. `.prisma/local.json` project pin when present, revalidated against platform data
8282
4. durable platform mapping when available
8383
5. remembered local project context, revalidated against platform data
84-
6. `package.json` name matched exactly against accessible project id, name, or slug
85-
7. unambiguous project creation for commands that are allowed to create projects
86-
8. prompt in interactive mode, or structured failure in `--json` / `--no-interactive` mode
87-
88-
`--project` is an escape hatch for ambiguous or unavailable automatic
89-
resolution, not a setup step. Only `app deploy` may create a missing project,
90-
and only when the inferred name is unambiguous.
84+
6. `package.json` name matched exactly against an existing accessible Project for non-mutating resolution
85+
7. explicit setup choice from `project link`, `project create`, an interactive setup picker, `app deploy --project`, or `app deploy --create-project`
86+
8. structured failure in `--json` / `--no-interactive` mode
87+
88+
`--project` is an explicit Project choice. When used from an unbound directory
89+
with `app deploy`, it writes `.prisma/local.json` after validation and before
90+
the deployment starts. `--create-project <name>` is the explicit deploy-time
91+
choice to create and bind a new Project. Package names and directory names may
92+
suggest setup defaults, but they never authorize Project creation by themselves.
9193
When `PRISMA_PROJECT_ID` is set, `app deploy` and `app domain` commands skip
9294
`.prisma/local.json` reads and do not write a new pin.
9395

96+
`app deploy` is stricter than general inspection commands: it does not use
97+
package-name matching or remembered local context as Project scope. Without a
98+
pin, durable mapping, env var, or explicit Project flag, it enters explicit
99+
setup or fails with `PROJECT_SETUP_REQUIRED`.
100+
94101
### App Selection
95102

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

107-
When `PRISMA_APP_ID` is set, `app deploy` and `app domain` commands skip
108-
`.prisma/local.json` reads and do not write a new pin.
109-
110114
`.prisma/local.json` pins the directory to a Workspace and Project only. It does
111115
not pin an App ID. App services are branch-scoped; a service ID from `main`
112116
must not be reused automatically when the user deploys from `feat/billing`.
@@ -392,6 +396,50 @@ prisma-cli project show --json
392396
prisma-cli project show --project proj_123 --json
393397
```
394398

399+
## `prisma-cli project create <name>`
400+
401+
Purpose:
402+
403+
- create a Prisma Project and bind the current directory to it
404+
405+
Behavior:
406+
407+
- requires auth
408+
- creates a Project in the authenticated workspace
409+
- writes `.prisma/local.json` with Workspace and Project IDs
410+
- ensures `.prisma/` is ignored by Git
411+
- does not create a Branch, App, Deployment, database, or Git repository connection
412+
- fails if the platform rejects Project creation
413+
414+
Examples:
415+
416+
```bash
417+
prisma-cli project create my-app
418+
prisma-cli project create my-app --json
419+
```
420+
421+
## `prisma-cli project link <id-or-name>`
422+
423+
Purpose:
424+
425+
- bind the current directory to an existing Prisma Project
426+
427+
Behavior:
428+
429+
- requires auth
430+
- resolves exactly one Project by id or name in the authenticated workspace
431+
- writes `.prisma/local.json` with Workspace and Project IDs
432+
- ensures `.prisma/` is ignored by Git
433+
- does not create remote resources
434+
- fails with `PROJECT_NOT_FOUND` or `PROJECT_AMBIGUOUS` when the Project cannot be selected safely
435+
436+
Examples:
437+
438+
```bash
439+
prisma-cli project link proj_123
440+
prisma-cli project link "Acme Dashboard" --json
441+
```
442+
395443
## `prisma-cli git connect [git-url]`
396444

397445
Purpose:
@@ -543,7 +591,7 @@ prisma-cli app run --build-type nextjs
543591
prisma-cli app run --build-type bun --entry server.ts --port 3000
544592
```
545593

546-
## `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>`
594+
## `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>`
547595

548596
Purpose:
549597

@@ -552,14 +600,28 @@ Purpose:
552600
Behavior:
553601

554602
- requires auth
555-
- resolves or creates project context from `--project`, `PRISMA_PROJECT_ID`, `.prisma/local.json`, `package.json#name`, or current directory name
603+
- resolves project context from `--project`, `--create-project`, `PRISMA_PROJECT_ID`, `.prisma/local.json`, durable platform mapping, or an interactive setup choice
604+
- does not infer and create Project context from `package.json#name` or current directory name without explicit setup
605+
- when no Project is resolved in interactive mode, asks which Project the directory should use:
606+
607+
```text
608+
? Which Project should this directory use?
609+
❯ Acme Dashboard
610+
Billing API
611+
Create a new Project
612+
Cancel
613+
```
614+
615+
- when "Create a new Project" is selected, prompts for a Project name with the package/directory name as a suggestion
616+
- when no Project is resolved in `--json` / `--no-interactive` mode, fails with `PROJECT_SETUP_REQUIRED`
617+
- `--yes` alone does not choose Project scope; use `--project` or `--create-project`
618+
- `--project` and `--create-project` are mutually exclusive with each other and with `PRISMA_PROJECT_ID`
556619
- resolves or creates branch context from `--branch`, local Git branch, or `main`
557620
- resolves or creates app context inside the resolved branch from `--app`, `PRISMA_APP_ID`, `package.json#name`, or current directory name
558621
- does not prompt when there is no real choice; zero matching apps creates the inferred app
559-
- detects supported frameworks and shows the resolved framework/runtime settings only while binding the directory for the first time
560622
- writes `.prisma/local.json` after Project binding succeeds and before build/deploy starts, so retries after a failed deploy do not repeat setup
561623
- 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
562-
- subsequent deploys print a compact target header such as `Deploying ./j1 to j1 / main / j1`
624+
- after setup, deploy prints `Deploying to <Project> / <Branch> / <App>`; later deploys print a compact target header such as `Deploying ./j1 to j1 / main / j1`
563625
- deploy progress uses short stage copy (`Building locally...`, `Built <size>`, `Uploading...`, `Uploaded`, `Deploying...`, `Deployed`) and never prints `Status: running` or `Deployment is running at ...`
564626
- success human output prints `Live in <duration>`, the URL on its own line, and `Logs prisma-cli app logs`
565627
- accepts repeated `--env NAME=VALUE` flags
@@ -572,6 +634,8 @@ Examples:
572634

573635
```bash
574636
prisma-cli app deploy
637+
prisma-cli app deploy --project proj_123
638+
prisma-cli app deploy --create-project my-app --yes
575639
prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example
576640
prisma-cli app deploy --framework nextjs --http-port 3000
577641
prisma-cli app deploy --branch feat-login --framework hono --http-port 3000

docs/product/error-conventions.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ These codes are the minimum stable set for the MVP:
160160
- `USAGE_ERROR`
161161
- `AUTH_REQUIRED`
162162
- `PROJECT_UNRESOLVED`
163+
- `PROJECT_SETUP_REQUIRED`
164+
- `PROJECT_CREATE_FAILED`
163165
- `PROJECT_NOT_FOUND`
164166
- `PROJECT_AMBIGUOUS`
165167
- `APP_AMBIGUOUS`
@@ -198,10 +200,12 @@ Recommended meanings:
198200
- `USAGE_ERROR`: invalid arguments or invalid command combination
199201
- `AUTH_REQUIRED`: command needs an authenticated session
200202
- `PROJECT_UNRESOLVED`: command needs project context and none could be resolved
203+
- `PROJECT_SETUP_REQUIRED`: `app deploy` needs an explicit Project setup choice before it can continue
204+
- `PROJECT_CREATE_FAILED`: Project creation failed before deployment or linking could continue
201205
- `PROJECT_NOT_FOUND`: requested project does not exist or is not accessible
202206
- `PROJECT_AMBIGUOUS`: multiple safe project candidates matched
203207
- `APP_AMBIGUOUS`: multiple apps matched the inferred or explicit app target
204-
- `LOCAL_STATE_STALE`: remembered local project context no longer matches platform data and continuing would be ambiguous
208+
- `LOCAL_STATE_STALE`: local Project pin or remembered context no longer matches platform data and continuing would be ambiguous
205209
- `BRANCH_NOT_DEPLOYABLE`: command tried to deploy to a non-deployable branch context
206210
- `FRAMEWORK_NOT_DETECTED`: app deploy could not detect a supported Beta framework and no explicit framework/build type was provided
207211
- `DEPLOYMENT_NOT_FOUND`: requested deployment id does not exist

docs/product/output-conventions.md

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -265,21 +265,30 @@ When a command acts on a project, branch, app, or deployment, the output should
265265
Examples:
266266

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

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

275-
For `app deploy`, the setup block is a one-time local binding surface, not a
276-
per-run summary. Once `.prisma/local.json` has been written, retries and later
277-
deploys should feel like deploys, not setup. Do not repeat source annotations or
278-
ask `Customize settings?` again unless the user deletes the pin or passes a
279-
flag that explicitly changes targeting/configuration. The first setup title
280-
should read `Setting up your local directory <path>`, followed by the resolved
281-
table and then the plain-language note `This directory is now linked to project
282-
<name>.`
275+
For `app deploy`, Project setup is a one-time local binding surface, not a
276+
per-run summary. If no Project is resolved in interactive mode, ask which
277+
Project the directory should use with an arrow-key selection prompt. The picker
278+
lists existing Projects, then `Create a new Project`, then `Cancel`; do not add
279+
a manual id/name entry to the picker. If the user chooses to create a Project,
280+
prompt for a Project name using the package/directory name as an editable
281+
suggestion. Once `.prisma/local.json` has been written, retries and later
282+
deploys should feel like deploys, not setup.
283+
284+
After setup, keep the confirmation compact:
285+
286+
```text
287+
✔ Linked "./my-app" to Project "Acme Dashboard"
288+
Saved .prisma/local.json
289+
290+
Deploying to Acme Dashboard / feat-login / my-app
291+
```
283292

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

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

298306
## Action and Data Commands
299307

docs/product/resource-model.md

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

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

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

199201
Remembered local project context is an internal convenience after successful
200202
resolution. It must be revalidated before use and must not be described to users
201-
as durable linking. Only `app deploy` may create projects implicitly.
203+
as durable linking. Package names and directory names may be suggested during
204+
setup, but they do not authorize Project creation by themselves.
205+
206+
`app deploy` is stricter than general inspection commands: if the directory is
207+
not pinned and no explicit Project source is provided, it enters explicit setup
208+
or fails with `PROJECT_SETUP_REQUIRED` instead of using package-name or
209+
remembered-local inference.
202210

203211
### App Selection Resolution
204212

packages/cli/src/commands/app/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ function createDeployCommand(runtime: CliRuntime): Command {
170170
command
171171
.addOption(new Option("--app <name>", "App name"))
172172
.addOption(new Option("--project <id-or-name>", "Project id or name"))
173+
.addOption(new Option("--create-project <name>", "Create and link a new Project before deploying"))
173174
.addOption(new Option("--branch <name>", "Branch name"))
174175
.addOption(
175176
new Option("--framework <name>", "Framework to deploy")
@@ -198,13 +199,15 @@ function createDeployCommand(runtime: CliRuntime): Command {
198199
const httpPort = (options as { httpPort?: string }).httpPort;
199200
const envAssignments = (options as { env?: string[] }).env;
200201
const projectRef = (options as { project?: string }).project;
202+
const createProjectName = (options as { createProject?: string }).createProject;
201203

202204
await runCommand<AppDeployResult>(
203205
runtime,
204206
"app.deploy",
205207
options as Record<string, unknown>,
206208
(context) => runAppDeploy(context, appName, {
207209
projectRef,
210+
createProjectName,
208211
branchName,
209212
entrypoint: entry,
210213
buildType,

packages/cli/src/commands/project/index.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { Command } from "commander";
22

3-
import { runProjectList, runProjectShow } from "../../controllers/project";
3+
import { runProjectCreate, runProjectLink, runProjectList, runProjectShow } from "../../controllers/project";
44
import {
5+
renderProjectSetup,
56
renderProjectList,
67
renderProjectShow,
8+
serializeProjectSetup,
79
serializeProjectList,
810
serializeProjectShow,
911
} from "../../presenters/project";
1012
import { attachCommandDescriptor } from "../../shell/command-meta";
1113
import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags";
1214
import { runCommand } from "../../shell/command-runner";
1315
import { configureRuntimeCommand, type CliRuntime } from "../../shell/runtime";
14-
import type { ProjectListResult, ProjectShowResult } from "../../types/project";
16+
import type { ProjectListResult, ProjectSetupResult, ProjectShowResult } from "../../types/project";
1517
import { createEnvCommand } from "../env";
1618

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

2224
project.addCommand(createProjectListCommand(runtime));
2325
project.addCommand(createProjectShowCommand(runtime));
26+
project.addCommand(createProjectCreateCommand(runtime));
27+
project.addCommand(createProjectLinkCommand(runtime));
2428
project.addCommand(createEnvCommand(runtime));
2529

2630
return project;
2731
}
2832

33+
function createProjectCreateCommand(runtime: CliRuntime): Command {
34+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("create"), runtime), "project.create");
35+
36+
command.argument("<name>", "Project name");
37+
addGlobalFlags(command);
38+
39+
command.action(async (name, options) => {
40+
await runCommand<ProjectSetupResult>(
41+
runtime,
42+
"project.create",
43+
options as Record<string, unknown>,
44+
(context) => runProjectCreate(context, String(name)),
45+
{
46+
renderHuman: (context, descriptor, result) => renderProjectSetup(context, descriptor, result),
47+
renderJson: (result) => serializeProjectSetup(result),
48+
},
49+
);
50+
});
51+
52+
return command;
53+
}
54+
55+
function createProjectLinkCommand(runtime: CliRuntime): Command {
56+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("link"), runtime), "project.link");
57+
58+
command.argument("<id-or-name>", "Project id or name");
59+
addGlobalFlags(command);
60+
61+
command.action(async (projectRef, options) => {
62+
await runCommand<ProjectSetupResult>(
63+
runtime,
64+
"project.link",
65+
options as Record<string, unknown>,
66+
(context) => runProjectLink(context, String(projectRef)),
67+
{
68+
renderHuman: (context, descriptor, result) => renderProjectSetup(context, descriptor, result),
69+
renderJson: (result) => serializeProjectSetup(result),
70+
},
71+
);
72+
});
73+
74+
return command;
75+
}
76+
2977
function createProjectListCommand(runtime: CliRuntime): Command {
3078
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "project.list");
3179

0 commit comments

Comments
 (0)