Skip to content

Commit 5daaa40

Browse files
authored
Merge pull request #51 from prisma/feat/explicit-project-context-resolution
Require explicit Project binding for Project-scoped CLI commands
2 parents 6865caa + dd0c66a commit 5daaa40

17 files changed

Lines changed: 708 additions & 322 deletions

docs/product/cli-style-guide.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,13 @@ Recommended symbols:
6464
Human-oriented command output in TTY mode should usually start with a compact header:
6565

6666
```text
67-
project show → Showing the project resolved for this directory.
67+
project show → No Project linked to this directory.
6868
69-
│ project: Acme Dashboard
7069
│ workspace: Acme Inc
71-
│ source: package-name
70+
│ project: unbound
71+
│ suggested: billing-api (package name)
72+
│ match: Billing API
73+
│ next: prisma-cli project link <id-or-name>
7274
7375
│ Read more docs/product/command-spec.md#prisma-cli-project-show
7476
```

docs/product/command-spec.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,8 @@ Commands resolve project context in this order:
8080
2. `PRISMA_PROJECT_ID` when set for headless deploy/domain commands
8181
3. `.prisma/local.json` project pin when present, revalidated against platform data
8282
4. durable platform mapping when available
83-
5. remembered local project context, revalidated against platform data
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
83+
5. explicit setup choice from `project link`, `project create`, an interactive setup picker, `app deploy --project`, or `app deploy --create-project`
84+
6. structured failure when no explicit or durable Project binding exists
8785

8886
`--project` is an explicit Project choice. When used from an unbound directory
8987
with `app deploy`, it writes `.prisma/local.json` after validation and before
@@ -93,10 +91,13 @@ suggest setup defaults, but they never authorize Project creation by themselves.
9391
When `PRISMA_PROJECT_ID` is set, `app deploy` and `app domain` commands skip
9492
`.prisma/local.json` reads and do not write a new pin.
9593

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`.
94+
Project-scoped commands never use package-name matching, directory-name
95+
matching, or remembered local context as selected Project scope. Local metadata
96+
may suggest setup defaults and candidate Projects, but only explicit input or
97+
durable state may select a Project. Without a pin, durable mapping, supported
98+
env var, or explicit Project flag, Project-scoped commands fail with
99+
`PROJECT_SETUP_REQUIRED`; `app deploy` may enter explicit interactive setup
100+
before failing.
100101

101102
### App Selection
102103

@@ -376,17 +377,19 @@ prisma-cli project list --json
376377

377378
Purpose:
378379

379-
- show the Prisma project resolved for this directory
380+
- show this directory's Prisma Project binding
380381

381382
Behavior:
382383

383384
- requires auth
384-
- resolves project context without creating projects
385+
- inspects explicit or durable project context without creating projects
385386
- does not prompt for project selection
386387
- does not mutate local state
387388
- `--project <id-or-name>` resolves only the explicit project
388-
- returns Workspace, Project, and `resolution.projectSource`
389-
- fails with `PROJECT_UNRESOLVED`, `PROJECT_NOT_FOUND`, `PROJECT_AMBIGUOUS`, or `LOCAL_STATE_STALE` when resolution cannot continue safely
389+
- when bound, returns Workspace, Project, and `resolution.projectSource`
390+
- when unbound, exits successfully with `project: null`, `resolution.projectSource: "unbound"`, a suggested Project name, matching Project candidates, and recovery commands
391+
- package names and directory names only power unbound suggestions
392+
- fails with `PROJECT_NOT_FOUND`, `PROJECT_AMBIGUOUS`, or `LOCAL_STATE_STALE` when explicit or durable binding validation cannot continue safely
390393

391394
Examples:
392395

docs/product/error-conventions.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ These codes are the minimum stable set for the MVP:
159159

160160
- `USAGE_ERROR`
161161
- `AUTH_REQUIRED`
162-
- `PROJECT_UNRESOLVED`
163162
- `PROJECT_SETUP_REQUIRED`
164163
- `PROJECT_CREATE_FAILED`
165164
- `PROJECT_NOT_FOUND`
@@ -199,13 +198,12 @@ Recommended meanings:
199198

200199
- `USAGE_ERROR`: invalid arguments or invalid command combination
201200
- `AUTH_REQUIRED`: command needs an authenticated session
202-
- `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
201+
- `PROJECT_SETUP_REQUIRED`: command needs explicit or durable Project context before it can continue
204202
- `PROJECT_CREATE_FAILED`: Project creation failed before deployment or linking could continue
205203
- `PROJECT_NOT_FOUND`: requested project does not exist or is not accessible
206204
- `PROJECT_AMBIGUOUS`: multiple safe project candidates matched
207205
- `APP_AMBIGUOUS`: multiple apps matched the inferred or explicit app target
208-
- `LOCAL_STATE_STALE`: local Project pin or remembered context no longer matches platform data and continuing would be ambiguous
206+
- `LOCAL_STATE_STALE`: local Project pin no longer matches platform data and continuing would be ambiguous
209207
- `BRANCH_NOT_DEPLOYABLE`: command tried to deploy to a non-deployable branch context
210208
- `FRAMEWORK_NOT_DETECTED`: app deploy could not detect a supported Beta framework and no explicit framework/build type was provided
211209
- `DEPLOYMENT_NOT_FOUND`: requested deployment id does not exist

docs/product/output-conventions.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,13 @@ Human output should:
220220
Recommended header shape:
221221

222222
```text
223-
project show → Showing the project resolved for this directory.
223+
project show → No Project linked to this directory.
224224
225-
│ project: Acme Dashboard
226225
│ workspace: Acme Inc
227-
│ source: package-name
226+
│ project: unbound
227+
│ suggested: billing-api (package name)
228+
│ match: Billing API
229+
│ next: prisma-cli project link <id-or-name>
228230
229231
│ Read more docs/product/command-spec.md#prisma-cli-project-show
230232
```

docs/product/resource-model.md

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -193,20 +193,18 @@ Commands resolve project context in this order:
193193
2. `PRISMA_PROJECT_ID` when set for headless deploy/domain commands
194194
3. `.prisma/local.json` project pin when present, revalidated against platform data
195195
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
200-
201-
Remembered local project context is an internal convenience after successful
202-
resolution. It must be revalidated before use and must not be described to users
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.
196+
5. explicit setup choice: `project link`, `project create`, interactive setup picker, `app deploy --project`, or `app deploy --create-project`
197+
6. structured failure when no explicit or durable Project binding exists
198+
199+
Package names and directory names may suggest setup defaults and matching
200+
Project candidates, but they do not select Project scope. Remembered local
201+
context may help with app/deployment convenience after a Project is explicit,
202+
but it is not a Project binding source.
203+
204+
Project-scoped commands require explicit or durable Project context. If the
205+
directory is not pinned and no explicit Project source is provided, they fail
206+
with `PROJECT_SETUP_REQUIRED`; `app deploy` may enter explicit interactive setup
207+
before failing.
210208

211209
### App Selection Resolution
212210

packages/cli/src/controllers/app-env.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import type {
2121
EnvVariableMetadata,
2222
} from "../types/app-env";
2323
import { requireAuthenticatedAuthState } from "./auth";
24-
import { createSelectPromptPort } from "./select-prompt-port";
2524
import { listRealWorkspaceProjects } from "./project";
2625

2726
interface ResolvedScope {
@@ -72,7 +71,7 @@ export async function runEnvAdd(
7271
);
7372
}
7473

75-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
74+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env add");
7675
const resolved = await resolveScopeToApi(client, projectId, scope, {
7776
createBranchIfMissing: true,
7877
});
@@ -152,7 +151,7 @@ export async function runEnvUpdate(
152151
);
153152
}
154153

155-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
154+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env update");
156155
const resolved = await resolveScopeToApi(client, projectId, scope, {
157156
createBranchIfMissing: false,
158157
});
@@ -203,7 +202,7 @@ export async function runEnvList(
203202
const explicit = resolveEnvScope(flags, { requireExplicit: false, command: "list" });
204203
const scope = explicit ?? defaultRoleScope();
205204

206-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
205+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
207206
const resolved = await resolveScopeToApi(client, projectId, scope, {
208207
createBranchIfMissing: false,
209208
});
@@ -249,7 +248,7 @@ export async function runEnvRemove(
249248
);
250249
}
251250

252-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
251+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env remove");
253252
const resolved = await resolveScopeToApi(client, projectId, scope, {
254253
createBranchIfMissing: false,
255254
});
@@ -293,6 +292,7 @@ export async function runEnvRemove(
293292
async function requireClientAndProject(
294293
context: CommandContext,
295294
explicitProject: string | undefined,
295+
commandName: string,
296296
): Promise<{ client: ManagementApiClient; projectId: string }> {
297297
const authState = await requireAuthenticatedAuthState(context);
298298
const client = await requireComputeAuth(context.runtime.env);
@@ -308,8 +308,7 @@ async function requireClientAndProject(
308308
workspace: authState.workspace,
309309
explicitProject,
310310
listProjects: () => listRealWorkspaceProjects(client, authState.workspace!),
311-
prompt: createSelectPromptPort(context),
312-
remember: true,
311+
commandName,
313312
});
314313

315314
return { client, projectId: target.project.id };

0 commit comments

Comments
 (0)