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
39 changes: 39 additions & 0 deletions .github/workflows/notify-error-reference-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Tells prisma/web to regenerate the hosted error reference
# (https://docs.prisma.io/docs/cli/error-reference) as soon as the
# registry changes, instead of waiting for that repo's daily cron.
# prisma/web's sync-error-reference-docs.yml listens for the
# `error-reference-updated` repository_dispatch and re-runs the
# generator against this repo's main.
#
# WEB_DISPATCH_TOKEN is a token allowed to send repository_dispatch
# events to prisma/web (fine-grained PAT on that repo with
# "Contents: read and write", or classic `repo` scope).
name: Notify Docs of Error Reference Changes

on:
push:
branches: [main]
paths:
- docs/reference/error-reference.md

permissions: {}

jobs:
dispatch:
name: Dispatch error-reference-updated to prisma/web
runs-on: ubuntu-latest
steps:
- name: Send repository_dispatch
env:
GH_TOKEN: ${{ secrets.WEB_DISPATCH_TOKEN }}
run: |
gh api repos/prisma/web/dispatches \
--input - <<'EOF'
{
"event_type": "error-reference-updated",
"client_payload": {
"repository": "${{ github.repository }}",
"sha": "${{ github.sha }}"
}
}
EOF
27 changes: 14 additions & 13 deletions docs/product/cli-style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,20 @@ Shared flag rules:
- short aliases exist only for high-frequency flags
- flags should mean the same thing across commands whenever possible

Shared global flags for the MVP:

- `--json`
- `-q`, `--quiet`
- `-v`, `--verbose`
- `--trace`
- `--interactive`
- `--no-interactive`
- `-y`, `--yes`
- `--color`
- `--no-color`

`--quiet`, `--verbose`, and `--trace` affect human output detail, not the JSON schema.
Shared global flags, defined by the engine in `SHARED_FLAG_PARAMETERS` (`packages/cli-engine/src/execution/shared-flags.ts`, the source of truth for this list):

- `--format <human|json>`
- `--json` (shorthand for `--format json`)
- `--log-level <error|warn|info|verbose>`
- `-v`, `--verbose` (shorthand for `--log-level verbose`)
- `-q`, `--quiet` (shorthand for `--log-level error`)
- `-y`, `--yes` (accept prompt defaults)
- `--confirm <value>` (grant a consent prompt non-interactively; repeatable)
- `--interactive`, `--no-interactive`
- `--color`, `--no-color`
- `--config <path>`

`--log-level` and its `--verbose`/`--quiet` shorthands affect human commentary detail, not the JSON schema.

## Interactivity

Expand Down
6 changes: 1 addition & 5 deletions docs/reference/error-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ The local project binding in `.prisma/local.json` is unusable: the pinned projec

### PROJECT.NOT_FOUND

An explicit project reference matched no project in the active workspace, either because it does not exist or because the credential cannot see it — raised during project resolution for any command that accepts one, including `branch list` and the `project link`/`transfer`/`delete` target lookup. The fix is to pass an id or name from `prisma project list`. Meta: none.
An explicit project reference matched no project in the active workspace, either because it does not exist or because the credential cannot see it — raised during project resolution for any command that accepts one, including `branch list` and the `project link`/`transfer`/`delete` target lookup. The fix is to pass an id or name from `prisma project list`. Service commands raise the same code one step later, when the services API answers "Resource Not Found" for a project that did resolve — the directory binding points at a project that no longer exists or is no longer accessible — and their next actions point at `project show` to inspect the binding and `project link` to fix it. Meta: none.

### PROJECT.RENAME_FAILED

Expand Down Expand Up @@ -496,10 +496,6 @@ A `service logs` response body ended without the terminal record that closes a p

The resolved service has no usable version for the command — raised by `service open` when the service has no versions, by `service logs` when it has no live version, and by `service domain add` when the API answers 422 because the production service has no promoted version that can receive a custom domain. The fix on the domain path is to promote a version on the production branch first, then add the domain again. Meta: `status`, `apiCode`, `hint` (domain-add path only; otherwise none).

### SERVICE.PROJECT_NOT_FOUND

The project a service command resolved to does not exist in the authenticated workspace or is no longer accessible — raised when listing services answers "Resource Not Found" for the resolved project id. A service command that cannot match an explicit `--project` reference fails with the project group's own `PROJECT.NOT_FOUND` instead, because the condition is the same one whichever command met it. Next actions point at `project show` to inspect the directory binding and `project link` to fix it. Meta: none.

### SERVICE.SELECTION_INVALID

The named service could not be found among the resolved project branch's services — the match tries the stable platform id first, then the name. The fix is to pass the id or name of an existing service; the suggested command is `service list`, deliberately not `service version list`, which itself has to resolve a service and would fail the same way. Meta: none.
Expand Down
29 changes: 16 additions & 13 deletions packages/cli/src/commands/project/context.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* Glue between the engine command context and the legacy project
* operations. The legacy resolution and env-file operations take a
* shell `CommandContext` but read only `runtime.cwd`, `runtime.env`
* Glue between the engine command context and the project controllers.
* The resolution and env-file operations take a controller
* `CommandContext` but read only `runtime.cwd`, `runtime.env`
* and `runtime.signal`, so the CLI hands them exactly that.
*/
import path from "node:path";
import type { CommandContext } from "@prisma/cli-engine";
import type { CommandContext as ControllerCommandContext } from "../../controllers/context";
import { listRealWorkspaceProjects } from "../../controllers/project";
import type { CommandContext as LegacyCommandContext } from "../../legacy/runtime";
import {
ensureLocalResolutionPinGitignore,
LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
Expand All @@ -26,10 +26,10 @@ import type { ProjectSetupResult, ProjectSummary } from "../../types/project";
export type ProjectCommandContext = CommandContext<undefined, never>;

/**
* The legacy shell's `CommandContext` has many more fields than the
* three the CLI supplies, and the cast that makes the adapter compile also
* hides the day a legacy edit starts reading a fourth. Left alone that
* surfaces as `Cannot read properties of undefined`, worst case inside
* The controllers' `CommandContext` is typed to the three fields the
* CLI supplies today, but a controller edit could start reading a
* fourth. Left alone that surfaces as
* `Cannot read properties of undefined`, worst case inside
* `project transfer` after the project has already moved. Refusing the
* read here names the missing field at the moment it is read instead.
* Probes pass through rather than throwing: symbols are how the language
Expand All @@ -46,21 +46,24 @@ function refuseUnknownReads<T extends object>(fields: T, prefix: string): T {
return Reflect.get(target, key);
}
throw new Error(
"the legacy-context adapter provides only runtime.cwd, " +
"the operation-context adapter provides only runtime.cwd, " +
`runtime.env and runtime.signal; ${prefix}${key} was read`,
);
},
});
}

export function legacyOperationContext(
export function operationContext(
ctx: ProjectCommandContext,
): LegacyCommandContext {
): ControllerCommandContext {
const runtime = refuseUnknownReads(
{ cwd: ctx.cwd, env: ctx.env, signal: ctx.signal },
"runtime.",
);
return refuseUnknownReads({ runtime }, "") as unknown as LegacyCommandContext;
return refuseUnknownReads(
{ runtime },
"",
) as unknown as ControllerCommandContext;
}

export function listWorkspaceProjects(
Expand All @@ -80,7 +83,7 @@ export async function resolvePinnedProject(
commandName: string | undefined,
): Promise<ResolvedProjectTarget> {
const target = await resolveProjectTarget({
context: legacyOperationContext(ctx),
context: operationContext(ctx),
workspace,
explicitProject,
listProjects: () => listWorkspaceProjects(ctx),
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/project/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from "../../lib/project/setup";
import type { ProjectDeleteResult } from "../../types/project";
import { resolveActiveWorkspace } from "../resources-shared/workspace";
import { legacyOperationContext, listWorkspaceProjects } from "./context";
import { listWorkspaceProjects, operationContext } from "./context";
import { localPinDiagnostics } from "./presentation";

const CONSENT_QUESTION =
Expand Down Expand Up @@ -83,7 +83,7 @@ export const projectDeleteCommand = defineCommand({

const warnings: string[] = [];
const cleared = await cleanupLocalPinForProject(
legacyOperationContext(ctx),
operationContext(ctx),
project.id,
{ onError: (message) => warnings.push(message) },
);
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/project/env-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { runEnvAddFile } from "../../controllers/app-env-file";
import { formatScopeLabel } from "../../lib/app/env-config";
import { runCommand, userChoice } from "../../lib/app/env-errors";
import type { EnvAddResult } from "../../types/app-env";
import { legacyOperationContext } from "./context";
import { operationContext } from "./context";
import {
branchFlag,
fileFlag,
Expand Down Expand Up @@ -92,7 +92,7 @@ export const projectEnvAddCommand = defineCommand({
);
const scope = requireEnvScope(args.flags, "add");
const input = await resolveEnvWriteInput(
legacyOperationContext(ctx),
operationContext(ctx),
source,
"add",
);
Expand All @@ -106,7 +106,7 @@ export const projectEnvAddCommand = defineCommand({

if (input.kind === "file") {
const written = await runEnvAddFile(
legacyOperationContext(ctx),
operationContext(ctx),
ctx.api,
projectId,
resolved,
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/project/env-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { runEnvUpdateFile } from "../../controllers/app-env-file";
import { formatScopeLabel } from "../../lib/app/env-config";
import { runCommand, userChoice } from "../../lib/app/env-errors";
import type { EnvUpdateResult } from "../../types/app-env";
import { legacyOperationContext } from "./context";
import { operationContext } from "./context";
import {
branchFlag,
fileFlag,
Expand Down Expand Up @@ -89,7 +89,7 @@ export const projectEnvUpdateCommand = defineCommand({
);
const scope = requireEnvScope(args.flags, "update");
const input = await resolveEnvWriteInput(
legacyOperationContext(ctx),
operationContext(ctx),
source,
"update",
);
Expand All @@ -103,7 +103,7 @@ export const projectEnvUpdateCommand = defineCommand({

if (input.kind === "file") {
const written = await runEnvUpdateFile(
legacyOperationContext(ctx),
operationContext(ctx),
ctx.api,
projectId,
resolved,
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/project/show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "../../lib/project/resolution";
import type { ProjectShowResult } from "../../types/project";
import { resolveActiveWorkspace } from "../resources-shared/workspace";
import { legacyOperationContext, listWorkspaceProjects } from "./context";
import { listWorkspaceProjects, operationContext } from "./context";

interface FieldRow {
readonly label: string;
Expand Down Expand Up @@ -124,7 +124,7 @@ export const projectShowCommand = defineCommand({
handler: async (args, ctx) => {
const workspace = await resolveActiveWorkspace(ctx);
const inspected = await inspectProjectBinding({
context: legacyOperationContext(ctx),
context: operationContext(ctx),
workspace,
explicitProject: args.positionals.project,
listProjects: () => listWorkspaceProjects(ctx),
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/project/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ import {
import type { ProjectTransferResult } from "../../types/project";
import { resolveActiveWorkspace } from "../resources-shared/workspace";
import {
legacyOperationContext,
listWorkspaceProjects,
operationContext,
type ProjectCommandContext,
} from "./context";
import { localPinDiagnostics } from "./presentation";
Expand Down Expand Up @@ -255,7 +255,7 @@ export const projectTransferCommand = defineCommand({

const warnings: string[] = [];
const action = await rewriteOrClearLocalPinForProject(
legacyOperationContext(ctx),
operationContext(ctx),
project.id,
recipient.workspaceId,
{ onError: (message) => warnings.push(message) },
Expand Down
18 changes: 7 additions & 11 deletions packages/cli/src/commands/service/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,13 @@ export function serviceNameRequiredError(): CliStructuredError {
}

export function projectNotFoundError(projectId: string): CliStructuredError {
return new CliStructuredError(
"SERVICE.PROJECT_NOT_FOUND",
"Project not found",
{
why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
nextActions: [
runCommandAction("Inspect the directory binding", "project show"),
runCommandAction("Link a project", "project link <id-or-name>"),
],
},
);
return new CliStructuredError("PROJECT.NOT_FOUND", "Project not found", {
why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
nextActions: [
runCommandAction("Inspect the directory binding", "project show"),
runCommandAction("Link a project", "project link <id-or-name>"),
],
});
}

export function deployFailedError(
Expand Down
11 changes: 9 additions & 2 deletions packages/cli/src/controllers/app-env-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import {
type NextAction,
} from "@prisma/cli-engine/protocol";
import type { ManagementApiClient } from "@prisma/management-api-sdk";
import type { CommandSuccess } from "../legacy/output";
import type { CommandContext } from "../legacy/runtime";
import { type EnvScope, formatScopeLabel } from "../lib/app/env-config";
import { runCommand, userChoice } from "../lib/app/env-errors";
import type { EnvFileAssignment } from "../lib/app/env-file";
Expand All @@ -23,11 +21,20 @@ import {
type ResolvedEnvApiScope,
toMetadata,
} from "./app-env-api";
import type { CommandContext } from "./context";

export interface ResolvedEnvFileScope extends ResolvedEnvApiScope {
scope: EnvScope;
}

/** What an env-file controller returns: the result plus the findings it
* collected along the way, which the command turns into diagnostics. */
export interface CommandSuccess<T> {
command: string;
result: T;
warnings: string[];
}

export async function runEnvAddFile(
context: CommandContext,
client: ManagementApiClient,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/controllers/app-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { CliStructuredError } from "@prisma/cli-engine/protocol";
import type { ManagementApiClient } from "@prisma/management-api-sdk";
import type { CommandContext } from "../legacy/runtime";
import {
type EnvScope,
type EnvVarRole,
Expand All @@ -19,6 +18,7 @@ import {
type RawEnvironmentVariable,
type ResolvedEnvApiScope,
} from "./app-env-api";
import type { CommandContext } from "./context";

interface ResolvedScope extends ResolvedEnvApiScope {
scope: EnvScope;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* The context the surviving legacy operation layer takes. The commander
* shell that built it is gone, and the operations read only these three
* runtime fields; the command handlers supply them
* The context the project and env controllers take. The commander
* shell that once built it is gone; the controllers read only these
* three runtime fields, and the command handlers supply them
* (`src/commands/project/context.ts`).
*/
export interface CliRuntime {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/controllers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type { ManagementApiClient } from "@prisma/management-api-sdk";
import { matchError } from "better-result";

import type { GitHubRepositoryReference } from "../adapters/git";
import type { CommandContext } from "../legacy/runtime";
import type { PrismaCliPackageCommandFormatter } from "../lib/agent/cli-command";
import {
LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
Expand All @@ -24,6 +23,7 @@ import type {
GitRepositoryConnection,
ProjectListResult,
} from "../types/project";
import type { CommandContext } from "./context";

export const GITHUB_INSTALL_POLL_INTERVAL_MS = 2_000;
export const GITHUB_INSTALL_POLL_TIMEOUT_MS = 120_000;
Expand Down
14 changes: 0 additions & 14 deletions packages/cli/src/legacy/output.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/cli/tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mkdtemp, readFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { CliRuntime, CommandContext } from "../src/legacy/runtime";
import type { CliRuntime, CommandContext } from "../src/controllers/context";

export async function createTempCwd(): Promise<string> {
return mkdtemp(path.join(os.tmpdir(), "prisma-cli-"));
Expand Down
Loading
Loading