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
112 changes: 109 additions & 3 deletions docs/product/command-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,32 @@ The beta package includes these command groups:
- `app`
- `build` (includes `build logs`)

The beta package also includes one top-level utility command:
The beta package also includes two top-level commands:

- `version`
- `init`

`version` is intentionally outside the workflow groups: it reports CLI build and environment state, requires no auth, no project context, and no network, and is the canonical answer to "is this CLI installed and on the build I expect?"

`init` is a top-level workflow verb: it acts on the local project directory
(writing the committed compute config) rather than managing a remote resource,
so it sits beside the ORM's verb register (`generate`, `migrate`, `validate`)
that the unified CLI will absorb. `init` never scaffolds application code;
creating new apps is `create-prisma`'s job.

The Git repository connection slice uses the `git` group. It does not add a
provider-specific `GitHub` group.

Out of scope for the current beta:

- `init`
- `schema`
- `migrate`
- product-specific namespaces such as `compute`

## Global Rules

- Canonical shape is `prisma <group> <action>`.
- `version` is the one top-level command outside that shape (see Scope above).
- `version` and `init` are the top-level commands outside that shape (see Scope above).
- Every command supports `--json`.
- Shared global flags are:
- `--json`
Expand Down Expand Up @@ -378,6 +384,49 @@ prisma-cli --version --json

`prisma-cli version` is the richer environment report; `prisma-cli --version` is the terse one-liner. Both report the same `cli.version`. Use the flag for quick checks, the subcommand for support tickets and bug reports.

## `prisma-cli init --framework <nextjs|nuxt|astro|hono|nestjs|tanstack-start|bun|custom> --entry <path> --http-port <port> --region <region> --name <app-name> --link --no-link --project <id-or-name> --install --no-install`

Purpose:

- write a committed `prisma.compute.ts` for the app in this directory

Behavior:

- init is the config formalizer: `app deploy` works with zero config, and init writes down what deploy would infer so the setup is committed, reviewable, and stable for teammates and CI
- writing the config requires no auth and no network; linking is the only remote step
- fails with `INIT_CONFIG_EXISTS` when a compute config already exists in the invocation directory or any ancestor up to the repository or workspace root; the error names the existing file, and init never overwrites or merges — editing a committed config is the user's editor's job
- detects the framework from the same registry and signals `app deploy` uses; explicit `--framework` wins over detection
- `--entry` sets the source entrypoint for entrypoint frameworks (Bun, Hono); `--http-port` overrides the framework default port; `--name` overrides the app name inferred from `package.json#name` or the directory name
- previews the resolved values with per-value source annotations (`detected`, `framework default`, `package.json`, `flag`) before writing; in interactive mode the preview is followed by an optional adjust step for framework and HTTP port, and `--yes` accepts the preview as shown
- the generated config pins the app's identity: `name`, `framework`, and `httpPort` always; `entry` when the framework consumes a source entrypoint; `region` only when `--region` is passed, because pinning a region the user did not choose would silently place new apps
- the generated config does not include a `build` block: build settings stay inferred (and shown with their sources by deploy) until the user adds one, which keeps package-manager and build-script inference live
- init never scaffolds application code, never creates schema or database resources, and never deploys
- with `--framework custom`, the config includes a commented `build` stub, since custom artifacts require `build.outputDirectory` and `build.entrypoint` before deploy can use them
- when detection fails and no `--framework` is passed: interactive mode prompts for the framework from the supported list; non-interactive and `--json` mode fail with `INIT_DETECTION_FAILED`, with `nextActions` enumerating the `--framework` choices
- types step, after the config is written: the generated config's typed import (`@prisma/compute-sdk/config`) is resolved by the CLI at deploy time without a local install, so a local `@prisma/compute-sdk` devDependency exists purely for editor types
- when the package is already a dependency or devDependency, the step is a no-op
- interactive mode asks `Install @prisma/compute-sdk for config types?` (default yes) and runs the detected package manager's add command (`pnpm add -D`, `bun add -d`, `yarn add -D`, `npm install -D`)
- `--install` runs the install without prompting; `--no-install` skips the step; non-interactive and `--json` mode skip by default
- a skipped, declined, or failed install downgrades to a hint or warning with the exact add command in `nextSteps`; the config write stands and init exits 0
- a directory without a `package.json` skips the step with the hint
- link step, after the config is written:
- interactive mode asks `Link this directory to a Prisma Project now? (Y/n)` when the directory has no project binding; accepting enters the same picker `project link` uses
- `--no-link` suppresses the question; `--link` requires the step; `--project <id-or-name>` links to that project without prompting
- link failures and cancellations after the config is written downgrade to warnings and `nextSteps`; the config write stands and init exits 0
- `nextSteps` includes the deploy command, plus the project link command when the directory is still unlinked
- user-facing command hints in init output (next steps, link hints, error recovery commands) use the package runner detected from the project, such as `pnpm dlx @prisma/cli@latest project link` or `npx -y @prisma/cli@latest app deploy`, matching the `agent` group's convention
- in `--json`, `result` includes `configPath`, the written `app` values, per-value `settings` sources, and `link` state; `--json` never prompts

Examples:

```bash
prisma-cli init
prisma-cli init --framework hono --entry src/index.ts
prisma-cli init --name api --http-port 8080 --no-link
prisma-cli init --project proj_123
prisma-cli init --json
```

## `<runner> @prisma/cli@latest agent install --agent <agent> --all-agents --skill <skill> --global --copy`

Purpose:
Expand Down Expand Up @@ -1332,6 +1381,7 @@ Behavior:
- 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`
- when the deploy resolved its settings without a compute config, success human output adds a `Config` hint line with the runner-formatted init command (such as `pnpm dlx @prisma/cli@latest init`), pointing at the command that pins the inferred settings; the hint is omitted once a config file is discovered
- with `--no-promote`, success human output instead prints `Built <deployment-id> in <duration> (not promoted)`, the candidate URL on its own line, a note that the live deployment is unchanged, and a `Promote prisma-cli app promote <deployment-id>` next step
- accepts repeated `--env NAME=VALUE` flags and dotenv file paths such as `--env .env`
- supports `--db` to create a new empty Prisma Postgres database and write `DATABASE_URL` and `DIRECT_URL` through the existing `project env` storage; the CLI never runs schema or migration commands — applying the schema stays with the user's own tooling
Expand Down Expand Up @@ -1727,6 +1777,62 @@ prisma-cli app logs
prisma-cli app logs --deployment dep_123
```

## `prisma-cli build list [app] --app <name> --project <id-or-name> --branch <name> --limit <n>`

Status: blocked on Management API rollout. The `GET /v1/apps/{appId}/builds`
endpoint exists in the control plane but is not yet deployed or published in
`@prisma/management-api-sdk`. This section is normative for the implementation
that lands once the SDK exposes the endpoint.

Purpose:

- list the git build jobs for an app, so build ids are discoverable without the Console

Behavior:

- requires auth and project context
- resolves the selected app exactly like the other app management commands: `[app]` target argument, `--app`, compute config target, locally selected app, inferred name; never creates apps or branches
- resolves the branch it reads like management commands: explicit `--branch`, active Git branch when it exists in the project, then the project's default branch
- lists builds newest first: build id, state (`pending`, `running`, `succeeded`, `failed`, `cancelled`), source (`webhook`, `setup`, `manual`), Git branch, short commit sha, created and finished timestamps, and the produced deployment id when the build reached that stage
- build ids are the ids `build logs <build-id>` accepts
- `--limit <n>` caps the number of returned builds; JSON output includes `pagination.nextCursor` and `pagination.hasMore` so agents can page
- read-only; never prints secret values
- `nextSteps` includes `prisma-cli build logs <build-id>` for the newest build
- fails with the standard app selection errors (`APP_AMBIGUOUS`, app not found) when the target cannot be resolved safely

Examples:

```bash
prisma-cli build list
prisma-cli build list --app my-app --limit 50
prisma-cli build list --json
```

## `prisma-cli build show <build-id>`

Status: blocked on Management API rollout, same as `build list`; the backing
endpoint is `GET /v1/builds/{buildId}`.

Purpose:

- show one build in detail

Behavior:

- requires auth
- takes a build id, as shown by `build list`, the Console build view, and git-push output
- authorization matches `build logs`: access stays with the workspace that owned the build when it ran, and an unknown or foreign build id fails with an indistinguishable `BUILD_NOT_FOUND`
- shows state, source, Git branch, commit sha, created/started/finished timestamps, the error message when the build failed, and the produced deployment id and deployed URL when present
- read-only; never prints secret values
- `nextSteps` includes `prisma-cli build logs <build-id>`

Examples:

```bash
prisma-cli build show cmcz3v6ft0a1b2c3d
prisma-cli build show cmcz3v6ft0a1b2c3d --json
```

## `prisma-cli build logs <build-id> --follow --cursor <cursor>`

Purpose:
Expand Down
4 changes: 4 additions & 0 deletions docs/product/error-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ These codes are the minimum stable set for the MVP:
- `BUILD_SETTINGS_MIGRATION_REQUIRED`
- `BUILD_SETTINGS_UNSUPPORTED`
- `FRAMEWORK_NOT_DETECTED`
- `INIT_CONFIG_EXISTS`
- `INIT_DETECTION_FAILED`
- `DEPLOYMENT_NOT_FOUND`
- `NO_DEPLOYMENTS`
- `NO_PREVIOUS_DEPLOYMENT`
Expand Down Expand Up @@ -258,6 +260,8 @@ Recommended meanings:
- `BUILD_SETTINGS_MIGRATION_REQUIRED`: a legacy `prisma.app.json` contains custom build settings that must move into the `build` block of `prisma.compute.ts`
- `BUILD_SETTINGS_UNSUPPORTED`: a compute config `build` block targets a framework whose SDK strategy does not consume committed build settings
- `FRAMEWORK_NOT_DETECTED`: app deploy could not detect a supported Beta framework and no explicit framework/build type was provided
- `INIT_CONFIG_EXISTS`: a compute config already exists in this directory or an ancestor; init never overwrites or merges
- `INIT_DETECTION_FAILED`: no supported framework detected and no --framework passed; `meta.frameworks` lists the valid values
- `DEPLOYMENT_NOT_FOUND`: requested deployment id does not exist
- `NO_DEPLOYMENTS`: command resolved a branch or app but found no deployments
- `NO_PREVIOUS_DEPLOYMENT`: rollback could not find an earlier deployment for the selected app
Expand Down
1 change: 1 addition & 0 deletions docs/product/resource-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Rules:
- 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. When a `prisma.compute.ts` is discovered (nearest config from the invocation directory up to the repository or workspace root), the pin and the CLI state cache (`.prisma/cli/state.json`) are read and written in the config file's directory; without a config they stay in the invocation directory
- `prisma.compute.ts` is a committed deploy-defaults file; it must not contain Workspace, Project, Branch, env-secret, or credential resolution state
- `init` is the only command that writes `prisma.compute.ts`, and it never overwrites an existing one; deploy reads the config but never writes it
- 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
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createBranchCommand } from "./commands/branch";
import { createBuildCommand } from "./commands/build";
import { createDatabaseCommand } from "./commands/database";
import { createGitCommand } from "./commands/git";
import { createInitCommand } from "./commands/init";
import { createProjectCommand } from "./commands/project";
import { createVersionCommand } from "./commands/version";
import { runVersion } from "./controllers/version";
Expand Down Expand Up @@ -83,6 +84,7 @@ export function createProgram(runtime: CliRuntime): Command {
program.name("prisma").showSuggestionAfterError();

program.addCommand(createVersionCommand(runtime));
program.addCommand(createInitCommand(runtime));
program.addCommand(createAgentCommand(runtime));
program.addCommand(createAuthCommand(runtime));
program.addCommand(createProjectCommand(runtime));
Expand Down
85 changes: 85 additions & 0 deletions packages/cli/src/commands/init/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Command, Option } from "commander";

import { runInit } from "../../controllers/init";
import { renderInit, serializeInit } from "../../presenters/init";
import { attachCommandDescriptor } from "../../shell/command-meta";
import { runCommand } from "../../shell/command-runner";
import { addGlobalFlags } from "../../shell/global-flags";
import { type CliRuntime, configureRuntimeCommand } from "../../shell/runtime";
import type { InitResult } from "../../types/init";

export function createInitCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(
configureRuntimeCommand(new Command("init"), runtime),
"init",
);

command
.addOption(
new Option(
"--framework <framework>",
"Framework override; detected when omitted",
),
)
.addOption(
new Option(
"--entry <path>",
"Source entrypoint for entrypoint frameworks (Bun, Hono)",
),
)
.addOption(new Option("--http-port <port>", "HTTP port the app listens on"))
.addOption(
new Option(
"--region <region>",
"Region used when deploy creates the app",
),
)
.addOption(new Option("--name <app-name>", "App name"))
.addOption(new Option("--link", "Link this directory to a Project"))
.addOption(new Option("--no-link", "Skip the Project link step"))
.addOption(
new Option("--project <id-or-name>", "Project to link this directory to"),
)
.addOption(
new Option("--install", "Install @prisma/compute-sdk for config types"),
)
.addOption(new Option("--no-install", "Skip the types install step"));
addGlobalFlags(command);

command.action(async (options) => {
const flags = options as {
framework?: string;
entry?: string;
httpPort?: string;
region?: string;
name?: string;
link?: boolean;
project?: string;
install?: boolean;
};

await runCommand<InitResult>(
runtime,
"init",
options as Record<string, unknown>,
(context) =>
runInit(context, {
framework: flags.framework,
entry: flags.entry,
httpPort: flags.httpPort,
region: flags.region,
name: flags.name,
link: flags.link,
project: flags.project,
install: flags.install,
}),
{
renderHuman: (context, descriptor, result) =>
renderInit(context, descriptor, result),
renderJson: (result) => serializeInit(result),
},
);
});

return command;
}
4 changes: 2 additions & 2 deletions packages/cli/src/controllers/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@
});
}

async function runSingleAppDeploy(

Check notice on line 586 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 17 detected (max: 15).
context: CommandContext,
appName: string | undefined,
options: AppDeployOptions | undefined,
Expand Down Expand Up @@ -1174,9 +1174,9 @@
...deployment.deployment,
live: providerLiveDeploymentId
? deployment.deployment.id === providerLiveDeploymentId
: knownLiveDeploymentId
? deployment.deployment.id === knownLiveDeploymentId
: deployment.deployment.live,

Check notice on line 1179 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.
},
},
warnings: [],
Expand Down Expand Up @@ -1555,10 +1555,10 @@
});
}

await sleep(
Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)),
context.runtime.signal,
);

Check notice on line 1561 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
current = await target.provider
.showDomain(current.id, { signal: context.runtime.signal })
.catch((error) => {
Expand Down Expand Up @@ -2120,7 +2120,7 @@
const compute = await resolveComputeManagementContext(
context,
options?.configTarget,
commandName.replace(/^app /, ""),

Check warning on line 2123 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
const branch = resolveDomainBranch(options?.branchName);
if (toBranchKind(branch.name) !== "production") {
Expand Down Expand Up @@ -2253,7 +2253,7 @@
}

function normalizeDomainHostname(hostname: string): string {
const normalized = hostname.trim().replace(/\.$/, "").toLowerCase();

Check warning on line 2256 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
if (!isValidDomainHostname(normalized)) {
throw new CliError({
code: "DOMAIN_HOSTNAME_INVALID",
Expand Down Expand Up @@ -2288,14 +2288,14 @@
}

return labels.every((label) =>
/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label),

Check warning on line 2291 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
}

function sameDomainHostname(left: string, right: string): boolean {
return (
left.trim().replace(/\.$/, "").toLowerCase() ===

Check warning on line 2297 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
right.trim().replace(/\.$/, "").toLowerCase()

Check warning on line 2298 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
}

Expand Down Expand Up @@ -2383,7 +2383,7 @@
}
}

function domainCommandError(

Check notice on line 2386 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 26 detected (max: 15).
command: AppDomainCommand,
error: unknown,
hostname: string,
Expand Down Expand Up @@ -2520,7 +2520,7 @@
text.includes("no cname") ||
text.includes("cname record") ||
text.includes("no a/aaaa") ||
/\bcname(?:s)?\s+to\b/.test(text)

Check warning on line 2523 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
}

Expand Down Expand Up @@ -2585,7 +2585,7 @@
return 0;
}

const match = /^(\d+)(ms|s|m|h)$/.exec(trimmed);

Check warning on line 2588 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
if (!match) {
throw usageError(
`Invalid timeout "${value}"`,
Expand All @@ -2601,11 +2601,11 @@
const multiplier =
unit === "h"
? 60 * 60 * 1000
: unit === "m"
? 60 * 1000
: unit === "s"
? 1000
: 1;

Check notice on line 2608 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.

Check notice on line 2608 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.
return amount * multiplier;
}

Expand Down Expand Up @@ -3379,7 +3379,7 @@
listProjects: () =>
listRealWorkspaceProjects(
client,
authState.workspace!,

Check warning on line 3382 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
context.runtime.signal,
),
commandName: options?.commandName,
Expand Down Expand Up @@ -3413,7 +3413,7 @@
};
}

async function resolveDeployProjectContext(

Check notice on line 3416 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 18 detected (max: 15).
context: CommandContext,
client: ManagementApiClient,
provider: ReturnType<typeof createAppProvider>,
Expand Down Expand Up @@ -3777,7 +3777,7 @@
};
}

interface ResolvedDeployFramework {
export interface ResolvedDeployFramework {
key: string;
buildType: FrameworkBuildType;
displayName: string;
Expand Down Expand Up @@ -4074,7 +4074,7 @@
}
}

async function detectDeployFramework(
export async function detectDeployFramework(
cwd: string,
signal: AbortSignal,
): Promise<ResolvedDeployFramework | null> {
Expand All @@ -4088,7 +4088,7 @@
continue;
}

const configFile = await detectFrameworkConfigFile(cwd, framework, signal);

Check notice on line 4091 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
if (
!configFile.exists &&
!hasAnyPackageDependency(packageJson, framework.detectPackages)
Expand All @@ -4101,8 +4101,8 @@
const annotation =
framework.key === "nextjs" && configFile.standalone
? "standalone output detected"
: configFile.exists
? `detected from ${path.basename(configFile.path!)}`

Check warning on line 4105 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
: "detected from package.json";

Check notice on line 4106 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.

return {
Expand All @@ -4125,10 +4125,10 @@
const filePath = path.join(cwd, candidate);
signal.throwIfAborted();
try {
const content = await readFile(filePath, { encoding: "utf8", signal });

Check notice on line 4128 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
return {
exists: true,
standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),

Check warning on line 4131 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
path: filePath,
};
} catch (error) {
Expand Down
Loading
Loading