@prisma/cli 3.0.0-dev.57.1 → 3.0.0-dev.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,7 +74,7 @@ The beta package exposes `prisma-cli` so it can coexist with the existing
74
74
  | `auth` | Log in, log out, and inspect the active Prisma account. |
75
75
  | `project` | List projects, show the resolved project, and manage project environment variables. |
76
76
  | `git` | Connect or disconnect a project from a GitHub repository. |
77
- | `branch` | Inspect the Prisma branch that maps to the current work context. |
77
+ | `branch` | List Prisma branches for the resolved project. |
78
78
  | `app` | Build, run, deploy, inspect, open, stream logs, promote, roll back, and remove apps. |
79
79
 
80
80
  Common examples:
@@ -83,7 +83,7 @@ Common examples:
83
83
  npx prisma-cli version
84
84
  npx prisma-cli auth whoami
85
85
  npx prisma-cli project show
86
- npx prisma-cli branch show
86
+ npx prisma-cli branch list
87
87
  npx prisma-cli app deploy --branch feat-login --framework nextjs
88
88
  npx prisma-cli app promote <deployment-id>
89
89
  ```
@@ -1,3 +1,4 @@
1
+ import { usageError } from "../../shell/errors.js";
1
2
  import { attachCommandDescriptor } from "../../shell/command-meta.js";
2
3
  import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags.js";
3
4
  import { configureRuntimeCommand } from "../../shell/runtime.js";
@@ -64,7 +65,7 @@ function createDeployCommand(runtime) {
64
65
  "hono",
65
66
  "tanstack-start",
66
67
  "bun"
67
- ])).addOption(new Option("--entry <path>", "Entrypoint path for Bun deploys")).addOption(new Option("--http-port <port>", "HTTP port override for the deployed app")).addOption(new Option("--env <name=value>", "Environment variable").argParser(collectRepeatableValues)).addOption(new Option("--prod", "Confirm intent to deploy to production"));
68
+ ])).addOption(new Option("--entry <path>", "Entrypoint path for Bun deploys")).addOption(new Option("--http-port <port>", "HTTP port override for the deployed app")).addOption(new Option("--env <name=value>", "Environment variable").argParser(collectRepeatableValues)).addOption(new Option("--db", "Create and wire an isolated database for the preview Branch")).addOption(new Option("--no-db", "Skip branch database setup")).addOption(new Option("--prod", "Confirm intent to deploy to production"));
68
69
  addGlobalFlags(command);
69
70
  command.action(async (options) => {
70
71
  const appName = options.app;
@@ -76,22 +77,31 @@ function createDeployCommand(runtime) {
76
77
  const projectRef = options.project;
77
78
  const createProjectName = options.createProject;
78
79
  const prod = options.prod;
79
- await runCommand(runtime, "app.deploy", options, (context) => runAppDeploy(context, appName, {
80
- projectRef,
81
- createProjectName,
82
- branchName,
83
- entrypoint: entry,
84
- framework,
85
- httpPort,
86
- envAssignments,
87
- prod: prod === true
88
- }), {
80
+ const db = options.db;
81
+ const hasDbConflict = hasFlag(runtime.argv, "--db") && hasFlag(runtime.argv, "--no-db");
82
+ await runCommand(runtime, "app.deploy", options, (context) => {
83
+ if (hasDbConflict) throw usageError("app deploy accepts either --db or --no-db", "--db requests branch database setup, while --no-db disables it.", "Pass exactly one database setup flag.", ["prisma-cli app deploy --db", "prisma-cli app deploy --no-db"], "app");
84
+ return runAppDeploy(context, appName, {
85
+ projectRef,
86
+ createProjectName,
87
+ branchName,
88
+ entrypoint: entry,
89
+ framework,
90
+ httpPort,
91
+ envAssignments,
92
+ prod: prod === true,
93
+ db
94
+ });
95
+ }, {
89
96
  renderHuman: (context, descriptor, result) => renderAppDeploy(context, descriptor, result),
90
97
  renderJson: (result) => serializeAppDeploy(result)
91
98
  });
92
99
  });
93
100
  return command;
94
101
  }
102
+ function hasFlag(argv, flag) {
103
+ return argv.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
104
+ }
95
105
  function createShowCommand(runtime) {
96
106
  const command = attachCommandDescriptor(configureRuntimeCommand(new Command("show"), runtime), "app.show");
97
107
  command.addOption(new Option("--app <name>", "App name")).addOption(new Option("--project <id-or-name>", "Project id or name"));
@@ -2,16 +2,14 @@ import { attachCommandDescriptor } from "../../shell/command-meta.js";
2
2
  import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags.js";
3
3
  import { configureRuntimeCommand } from "../../shell/runtime.js";
4
4
  import { runCommand } from "../../shell/command-runner.js";
5
- import { runBranchList, runBranchShow, runBranchUse } from "../../controllers/branch.js";
6
- import { renderBranchList, renderBranchShow, renderBranchUse, serializeBranchList, serializeBranchShow } from "../../presenters/branch.js";
5
+ import { runBranchList } from "../../controllers/branch.js";
6
+ import { renderBranchList, serializeBranchList } from "../../presenters/branch.js";
7
7
  import { Command } from "commander";
8
8
  //#region src/commands/branch/index.ts
9
9
  function createBranchCommand(runtime) {
10
10
  const branch = attachCommandDescriptor(configureRuntimeCommand(new Command("branch"), runtime), "branch");
11
11
  addCompactGlobalFlags(branch);
12
12
  branch.addCommand(createBranchListCommand(runtime));
13
- branch.addCommand(createBranchShowCommand(runtime));
14
- branch.addCommand(createBranchUseCommand(runtime));
15
13
  return branch;
16
14
  }
17
15
  function createBranchListCommand(runtime) {
@@ -25,28 +23,5 @@ function createBranchListCommand(runtime) {
25
23
  });
26
24
  return command;
27
25
  }
28
- function createBranchShowCommand(runtime) {
29
- const command = attachCommandDescriptor(configureRuntimeCommand(new Command("show"), runtime), "branch.show");
30
- addGlobalFlags(command);
31
- command.action(async (options) => {
32
- await runCommand(runtime, "branch.show", options, (context) => runBranchShow(context), {
33
- renderHuman: (context, descriptor, result) => renderBranchShow(context, descriptor, result),
34
- renderJson: (result) => serializeBranchShow(result)
35
- });
36
- });
37
- return command;
38
- }
39
- function createBranchUseCommand(runtime) {
40
- const command = attachCommandDescriptor(configureRuntimeCommand(new Command("use"), runtime), "branch.use");
41
- command.argument("[name]", "Branch name");
42
- addGlobalFlags(command);
43
- command.action(async (branchName, options) => {
44
- await runCommand(runtime, "branch.use", options, (context) => runBranchUse(context, branchName), {
45
- renderHuman: (context, descriptor, result) => renderBranchUse(context, descriptor, result),
46
- renderJson: (result) => serializeBranchShow(result)
47
- });
48
- });
49
- return command;
50
- }
51
26
  //#endregion
52
27
  export { createBranchCommand };
@@ -19,6 +19,7 @@ import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js
19
19
  import { readLocalGitBranch } from "../lib/git/local-branch.js";
20
20
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
21
21
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
22
+ import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
22
23
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
23
24
  import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
24
25
  import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
@@ -170,6 +171,10 @@ async function runAppDeploy(context, appName, options) {
170
171
  assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
171
172
  const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint, context.runtime.signal);
172
173
  const portMapping = parseDeployPortMapping(String(runtime.port));
174
+ const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
175
+ db: options?.db,
176
+ inlineEnvVars: envVars
177
+ });
173
178
  const progressState = createPreviewDeployProgressState();
174
179
  const deployStartedAt = Date.now();
175
180
  const deployResult = await provider.deployApp({
@@ -200,8 +205,9 @@ async function runAppDeploy(context, appName, options) {
200
205
  result: {
201
206
  workspace: target.workspace,
202
207
  project: target.project,
203
- branch: target.branch,
208
+ branch: toResultBranch(target.branch),
204
209
  resolution: target.resolution,
210
+ branchDatabase: branchDatabaseSetup.result,
205
211
  app: {
206
212
  id: deployResult.app.id,
207
213
  name: deployResult.app.name
@@ -210,7 +216,7 @@ async function runAppDeploy(context, appName, options) {
210
216
  durationMs: deployDurationMs,
211
217
  localPin: localPinResult
212
218
  },
213
- warnings: [],
219
+ warnings: branchDatabaseSetup.warnings,
214
220
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
215
221
  };
216
222
  }
@@ -757,7 +763,7 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
757
763
  resultTarget: {
758
764
  workspace: target.workspace,
759
765
  project: target.project,
760
- branch: target.branch,
766
+ branch: toResultBranch(target.branch),
761
767
  app: {
762
768
  id: selectedApp.id,
763
769
  name: selectedApp.name
@@ -1320,6 +1326,7 @@ async function resolveProjectContext(context, client, explicitProject, options)
1320
1326
  return {
1321
1327
  ...resolved,
1322
1328
  branch: {
1329
+ id: null,
1323
1330
  name: branch.name,
1324
1331
  kind: toBranchKind(branch.name)
1325
1332
  }
@@ -1446,6 +1453,7 @@ async function withRemoteDeployBranch(provider, target, branch, signal) {
1446
1453
  return {
1447
1454
  ...target,
1448
1455
  branch: {
1456
+ id: remoteBranch.id,
1449
1457
  name: remoteBranch.name,
1450
1458
  kind: remoteBranch.role
1451
1459
  }
@@ -1454,6 +1462,20 @@ async function withRemoteDeployBranch(provider, target, branch, signal) {
1454
1462
  function toBranchKind(name) {
1455
1463
  return name === "production" || name === "main" ? "production" : "preview";
1456
1464
  }
1465
+ function toResultBranch(branch) {
1466
+ return {
1467
+ name: branch.name,
1468
+ kind: branch.kind
1469
+ };
1470
+ }
1471
+ function toBranchDatabaseDeployBranch(branch) {
1472
+ if (!branch.id) throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);
1473
+ return {
1474
+ id: branch.id,
1475
+ name: branch.name,
1476
+ kind: branch.kind
1477
+ };
1478
+ }
1457
1479
  function assertExclusiveDeployProjectInputs(options) {
1458
1480
  const provided = [
1459
1481
  options.projectRef ? "--project" : null,
@@ -1,73 +1,99 @@
1
- import { featureUnavailableError, usageError } from "../shell/errors.js";
2
- import { canPrompt } from "../shell/runtime.js";
1
+ import { CliError, authRequiredError, workspaceRequiredError } from "../shell/errors.js";
2
+ import { requireComputeAuth } from "../lib/auth/guard.js";
3
+ import { resolveProjectTarget } from "../lib/project/resolution.js";
3
4
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
4
5
  import { createSelectPromptPort } from "./select-prompt-port.js";
6
+ import { requireAuthenticatedAuthState } from "./auth.js";
7
+ import { listRealWorkspaceProjects } from "./project.js";
5
8
  import { createBranchUseCases } from "../use-cases/branch.js";
6
9
  //#region src/controllers/branch.ts
7
- const PREVIEW_BRANCH_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
8
10
  function isRealMode(context) {
9
11
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
10
12
  }
11
13
  async function runBranchList(context) {
12
- if (isRealMode(context)) throw branchCommandsUnavailableError();
13
- return {
14
+ if (isRealMode(context)) return {
14
15
  command: "branch.list",
15
- result: await createBranchUseCases(createCliUseCaseGateways(context)).list(),
16
+ result: await listRealBranches(context),
16
17
  warnings: [],
17
18
  nextSteps: []
18
19
  };
19
- }
20
- async function runBranchShow(context) {
21
- if (isRealMode(context)) throw branchCommandsUnavailableError();
22
- const result = await createBranchUseCases(createCliUseCaseGateways(context)).show();
23
20
  return {
24
- command: "branch.show",
25
- result,
21
+ command: "branch.list",
22
+ result: await createBranchUseCases(createCliUseCaseGateways(context)).list(),
26
23
  warnings: [],
27
- nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma-cli app deploy"] : []
24
+ nextSteps: []
28
25
  };
29
26
  }
30
- async function runBranchUse(context, branchName) {
31
- if (isRealMode(context)) throw branchCommandsUnavailableError();
32
- const useCases = createBranchUseCases(createCliUseCaseGateways(context));
33
- const resolvedBranchName = await resolveBranchNameForUse(context, useCases, branchName);
34
- validateBranchName(resolvedBranchName);
35
- const result = await useCases.use(resolvedBranchName);
27
+ async function listRealBranches(context) {
28
+ const authState = await requireAuthenticatedAuthState(context);
29
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
30
+ if (!client) throw authRequiredError(["prisma-cli auth login"]);
31
+ const workspace = authState.workspace;
32
+ if (!workspace) throw workspaceRequiredError();
33
+ const target = await resolveProjectTarget({
34
+ context,
35
+ workspace,
36
+ listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
37
+ prompt: createSelectPromptPort(context),
38
+ remember: true
39
+ });
40
+ const branches = await listBranches(client, target.project.id, context.runtime.signal);
36
41
  return {
37
- command: "branch.use",
38
- result,
39
- warnings: result.branch.kind === "production" ? ["Production is protected and durable. Use with care."] : [],
40
- nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma-cli branch show", "prisma-cli app deploy"] : ["prisma-cli branch show"]
42
+ projectId: target.project.id,
43
+ projectName: target.project.name,
44
+ branches: sortBranches(branches.map(toBranchSummary))
41
45
  };
42
46
  }
43
- async function resolveBranchNameForUse(context, useCases, branchName) {
44
- if (branchName) return branchName;
45
- if (!canPrompt(context)) throw branchSelectionRequiredError();
46
- const result = await useCases.list();
47
- return createSelectPromptPort(context).select({
48
- message: "Select a branch",
49
- choices: result.branches.map((branch) => ({
50
- label: renderBranchChoiceLabel(branch),
51
- value: branch.name
52
- }))
47
+ function sortBranches(branches) {
48
+ return branches.slice().sort((left, right) => {
49
+ const leftRank = branchOrder(left);
50
+ const rightRank = branchOrder(right);
51
+ if (leftRank !== rightRank) return leftRank - rightRank;
52
+ return left.name.localeCompare(right.name);
53
53
  });
54
54
  }
55
- function renderBranchChoiceLabel(branch) {
56
- const markers = [];
57
- if (branch.active) markers.push("active");
58
- if (!branch.remoteState) markers.push("not created yet");
59
- return markers.length > 0 ? `${branch.name} (${markers.join(", ")})` : branch.name;
55
+ function branchOrder(branch) {
56
+ return branch.role === "production" ? 0 : 1;
60
57
  }
61
- function validateBranchName(branchName) {
62
- if (branchName === "production") return;
63
- if (PREVIEW_BRANCH_PATTERN.test(branchName)) return;
64
- throw usageError("Branch name must use the documented form", "Branch names must be production or a lowercase preview slug such as preview or feat-auth.", "Use production or a lowercase preview branch name with letters, numbers, and hyphens.", ["prisma-cli branch list"], "branch");
58
+ async function listBranches(client, projectId, signal) {
59
+ const collected = [];
60
+ let cursor;
61
+ while (true) {
62
+ const query = {};
63
+ if (cursor !== void 0) query.cursor = cursor;
64
+ const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
65
+ params: {
66
+ path: { projectId },
67
+ query
68
+ },
69
+ signal
70
+ });
71
+ if (error || !data) throw branchApiError("Failed to list branches", response, error);
72
+ collected.push(...data.data);
73
+ if (!data.pagination.hasMore || !data.pagination.nextCursor) break;
74
+ cursor = data.pagination.nextCursor;
75
+ }
76
+ return collected;
65
77
  }
66
- function branchSelectionRequiredError() {
67
- return usageError("Branch use requires a target in non-interactive mode", "This command cannot prompt for branch selection in the current mode.", "Re-run prisma-cli branch use in a TTY, or pass a branch name explicitly.", ["prisma-cli branch list"], "branch");
78
+ function toBranchSummary(branch) {
79
+ return {
80
+ id: branch.id,
81
+ name: branch.gitName,
82
+ role: branch.role,
83
+ envMap: branch.role
84
+ };
68
85
  }
69
- function branchCommandsUnavailableError() {
70
- return featureUnavailableError("Branch commands are not available in this preview", "The current preview cannot resolve or change remote branch context yet.", "Use prisma-cli app deploy for preview app deployment workflows.", ["prisma-cli app deploy --app <name>"], "branch");
86
+ function branchApiError(summary, response, error) {
87
+ const status = response?.status ?? 0;
88
+ return new CliError({
89
+ code: error?.error?.code ?? "BRANCH_API_ERROR",
90
+ domain: "branch",
91
+ summary,
92
+ why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
93
+ fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
94
+ exitCode: 1,
95
+ nextSteps: []
96
+ });
71
97
  }
72
98
  //#endregion
73
- export { runBranchList, runBranchShow, runBranchUse };
99
+ export { runBranchList };
@@ -0,0 +1,287 @@
1
+ import { CliError, usageError } from "../../shell/errors.js";
2
+ import { renderSummaryLine } from "../../shell/ui.js";
3
+ import { canPrompt } from "../../shell/runtime.js";
4
+ import { confirmPrompt } from "../../shell/prompt.js";
5
+ import { formatCommandArgument } from "../../shell/command-arguments.js";
6
+ import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } from "./branch-database.js";
7
+ import path from "node:path";
8
+ //#region src/lib/app/branch-database-deploy.ts
9
+ async function maybeSetupBranchDatabase(context, provider, projectId, branch, options) {
10
+ if (options.db === false) return emptyBranchDatabaseSetupOutcome();
11
+ if (hasInlineDatabaseEnvVars(options.inlineEnvVars)) {
12
+ if (options.db === true) throw usageError("Branch database setup cannot be combined with inline database env vars", "The deploy command received --db and an inline DATABASE_URL or DIRECT_URL value.", "Remove the inline --env database value to let --db create a branch override, or remove --db to deploy with the provided value.", ["prisma-cli app deploy --db", "prisma-cli app deploy --env DATABASE_URL=postgresql://example"], "app");
13
+ return emptyBranchDatabaseSetupOutcome();
14
+ }
15
+ if (branch.kind === "production") {
16
+ if (options.db === true) throw usageError("Branch database setup is only available for preview branches", "Production database wiring is a durable environment decision and is not created implicitly by app deploy.", "Use project env commands to manage production DATABASE_URL, or deploy a preview branch with --db.", ["prisma-cli project env add DATABASE_URL=<value> --role production", "prisma-cli app deploy --branch feature/db --db"], "app");
17
+ return emptyBranchDatabaseSetupOutcome();
18
+ }
19
+ const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
20
+ const envState = await inspectBranchDatabaseEnv(provider, projectId, branch.id, context.runtime.signal);
21
+ const branchEnvVars = [envState.branchDatabaseUrl, envState.branchDirectUrl].filter((variable) => Boolean(variable)).map((variable) => variable.key).sort();
22
+ if (envState.branchDatabaseUrl) {
23
+ const warning = options.db === true ? `Branch "${branch.name}" already has DATABASE_URL. Leaving branch database env vars unchanged.` : null;
24
+ if (warning) emitBranchDatabaseWarning(context, warning);
25
+ return {
26
+ result: options.db === true ? {
27
+ status: "skipped",
28
+ reason: "branch-env-exists",
29
+ envVars: branchEnvVars,
30
+ schema: null
31
+ } : void 0,
32
+ warnings: warning ? [warning] : []
33
+ };
34
+ }
35
+ const hasSignal = hasBranchDatabaseSignal(localSignal) || Boolean(envState.previewDatabaseUrl);
36
+ if (options.db !== true) {
37
+ if (!hasSignal) return emptyBranchDatabaseSetupOutcome();
38
+ if (!canPrompt(context) || context.flags.yes) {
39
+ const warning = "This app appears to use DATABASE_URL. Run prisma-cli app deploy --db to create an isolated database for this preview branch.";
40
+ emitBranchDatabaseWarning(context, warning);
41
+ return {
42
+ result: void 0,
43
+ warnings: [warning]
44
+ };
45
+ }
46
+ maybeRenderBranchDatabaseSignal(context, branch.name, localSignal, envState);
47
+ if (!await confirmPrompt({
48
+ input: context.runtime.stdin,
49
+ output: context.output.stderr,
50
+ message: `Create an isolated database for branch "${branch.name}"?`,
51
+ initialValue: false
52
+ })) return emptyBranchDatabaseSetupOutcome();
53
+ }
54
+ return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
55
+ }
56
+ async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
57
+ emitBranchDatabaseProgress(context, "pending", "Creating branch database");
58
+ const database = await provider.createBranchDatabase({
59
+ projectId,
60
+ branchId: branch.id,
61
+ branchName: branch.name,
62
+ signal: context.runtime.signal
63
+ }).catch((error) => {
64
+ throw branchDatabaseSetupFailedError("Failed to create branch database", error, branch.name);
65
+ });
66
+ emitBranchDatabaseProgress(context, "success", "Created branch database");
67
+ try {
68
+ let schemaSetup = null;
69
+ const warnings = [];
70
+ let skippedSchemaWarning = null;
71
+ if (signal.schema) {
72
+ emitBranchDatabaseProgress(context, "pending", `Applying database schema with ${formatSchemaSetupCommand(signal.schema.command)}`);
73
+ schemaSetup = await runBranchDatabaseSchemaSetup({
74
+ context,
75
+ schema: signal.schema,
76
+ databaseUrl: database.databaseUrl,
77
+ directUrl: database.directUrl
78
+ }).catch((error) => {
79
+ throw schemaSetupFailedError(error, signal.schema, branch.name);
80
+ });
81
+ emitBranchDatabaseProgress(context, "success", "Applied database schema");
82
+ } else skippedSchemaWarning = "No schema.prisma file was found. Branch database env vars were created, but schema setup was skipped.";
83
+ const envVars = await upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState);
84
+ emitBranchDatabaseProgress(context, "success", `Added branch env override${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
85
+ if (skippedSchemaWarning) {
86
+ emitBranchDatabaseWarning(context, skippedSchemaWarning);
87
+ warnings.push(skippedSchemaWarning);
88
+ }
89
+ return {
90
+ result: {
91
+ status: "created",
92
+ database: {
93
+ id: database.id,
94
+ name: database.name
95
+ },
96
+ envVars,
97
+ schema: schemaSetup ? {
98
+ command: schemaSetup.command,
99
+ path: schemaSetup.schemaPath
100
+ } : null
101
+ },
102
+ warnings
103
+ };
104
+ } catch (error) {
105
+ throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch.name, error);
106
+ }
107
+ }
108
+ async function upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState) {
109
+ const written = [];
110
+ await upsertBranchDatabaseEnvVar(context, provider, {
111
+ projectId,
112
+ branchId: branch.id,
113
+ className: "preview",
114
+ key: "DATABASE_URL",
115
+ value: database.databaseUrl,
116
+ existing: envState.branchDatabaseUrl,
117
+ branchName: branch.name
118
+ });
119
+ written.push("DATABASE_URL");
120
+ if (database.directUrl) {
121
+ await upsertBranchDatabaseEnvVar(context, provider, {
122
+ projectId,
123
+ branchId: branch.id,
124
+ className: "preview",
125
+ key: "DIRECT_URL",
126
+ value: database.directUrl,
127
+ existing: envState.branchDirectUrl,
128
+ branchName: branch.name
129
+ });
130
+ written.push("DIRECT_URL");
131
+ } else if (envState.branchDirectUrl) await provider.deleteEnvironmentVariable({
132
+ envVarId: envState.branchDirectUrl.id,
133
+ signal: context.runtime.signal
134
+ }).catch((error) => {
135
+ throw branchDatabaseSetupFailedError("Failed to remove stale DIRECT_URL", error, branch.name);
136
+ });
137
+ return written;
138
+ }
139
+ async function upsertBranchDatabaseEnvVar(context, provider, options) {
140
+ if (options.existing) {
141
+ await provider.updateEnvironmentVariable({
142
+ envVarId: options.existing.id,
143
+ value: options.value,
144
+ signal: context.runtime.signal
145
+ }).catch((error) => {
146
+ throw branchDatabaseSetupFailedError(`Failed to update ${options.key}`, error, options.branchName);
147
+ });
148
+ return;
149
+ }
150
+ await provider.createEnvironmentVariable({
151
+ projectId: options.projectId,
152
+ branchId: options.branchId,
153
+ className: options.className,
154
+ key: options.key,
155
+ value: options.value,
156
+ signal: context.runtime.signal
157
+ }).catch((error) => {
158
+ throw branchDatabaseSetupFailedError(`Failed to write ${options.key}`, error, options.branchName);
159
+ });
160
+ }
161
+ async function inspectBranchDatabaseEnv(provider, projectId, branchId, signal) {
162
+ const [databaseUrlRows, directUrlRows] = await Promise.all([provider.listEnvironmentVariables({
163
+ projectId,
164
+ className: "preview",
165
+ key: "DATABASE_URL",
166
+ signal
167
+ }), provider.listEnvironmentVariables({
168
+ projectId,
169
+ className: "preview",
170
+ key: "DIRECT_URL",
171
+ signal
172
+ })]);
173
+ return {
174
+ branchDatabaseUrl: findEnvVar(databaseUrlRows, { branchId }),
175
+ branchDirectUrl: findEnvVar(directUrlRows, { branchId }),
176
+ previewDatabaseUrl: findEnvVar(databaseUrlRows, { branchId: null })
177
+ };
178
+ }
179
+ function findEnvVar(rows, options) {
180
+ return rows.find((row) => row.branchId === options.branchId) ?? null;
181
+ }
182
+ function hasInlineDatabaseEnvVars(envVars) {
183
+ return Boolean(envVars && ("DATABASE_URL" in envVars || "DIRECT_URL" in envVars));
184
+ }
185
+ function maybeRenderBranchDatabaseSignal(context, branchName, signal, envState) {
186
+ if (context.flags.json || context.flags.quiet) return;
187
+ const rows = [
188
+ signal.schema ? ` Schema ${path.relative(context.runtime.cwd, signal.schema.path) || "schema.prisma"}` : null,
189
+ signal.databaseUrlReferences.length > 0 ? ` Code ${signal.databaseUrlReferences.slice(0, 3).join(", ")}` : null,
190
+ envState.previewDatabaseUrl ? " Env preview DATABASE_URL is inherited by this branch" : null
191
+ ].filter((row) => Boolean(row));
192
+ context.output.stderr.write(`Database signal found for branch "${branchName}"\n${rows.join("\n")}\n\n`);
193
+ }
194
+ function emitBranchDatabaseProgress(context, status, message) {
195
+ if (context.flags.json || context.flags.quiet) return;
196
+ const line = status === "pending" ? `${context.ui.warning("◇")} ${message}...` : renderSummaryLine(context.ui, "success", message);
197
+ context.output.stderr.write(`${line}\n`);
198
+ }
199
+ function emitBranchDatabaseWarning(context, warning) {
200
+ if (context.flags.json || context.flags.quiet) return;
201
+ context.output.stderr.write(`${renderSummaryLine(context.ui, "warning", warning)}\n`);
202
+ }
203
+ function emptyBranchDatabaseSetupOutcome() {
204
+ return {
205
+ result: void 0,
206
+ warnings: []
207
+ };
208
+ }
209
+ function formatSchemaSetupCommand(command) {
210
+ return command === "migrate-deploy" ? "prisma migrate deploy" : "prisma db push";
211
+ }
212
+ function branchDatabaseSetupFailedError(summary, error, branchName) {
213
+ return new CliError({
214
+ code: "BRANCH_DATABASE_SETUP_FAILED",
215
+ domain: "app",
216
+ summary,
217
+ why: error instanceof Error ? error.message : String(error),
218
+ fix: "Retry the command, or create the branch database and env vars manually with project env commands.",
219
+ debug: formatDebugDetails(error),
220
+ meta: { branch: branchName },
221
+ exitCode: 1,
222
+ nextSteps: [`prisma-cli app deploy --branch ${formatCommandArgument(branchName)} --db`, `prisma-cli project env list --branch ${formatCommandArgument(branchName)}`]
223
+ });
224
+ }
225
+ async function cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branchName, error) {
226
+ const setupError = error instanceof CliError ? error : branchDatabaseSetupFailedError("Branch database setup failed", error, branchName);
227
+ emitBranchDatabaseProgress(context, "pending", "Removing branch database after setup failed");
228
+ try {
229
+ await provider.deleteBranchDatabase({
230
+ databaseId: database.id,
231
+ signal: context.runtime.signal
232
+ });
233
+ emitBranchDatabaseProgress(context, "success", "Removed branch database after setup failed");
234
+ } catch (cleanupError) {
235
+ return branchDatabaseCleanupFailedError(setupError, cleanupError, database, branchName);
236
+ }
237
+ return setupError;
238
+ }
239
+ function branchDatabaseCleanupFailedError(setupError, cleanupError, database, branchName) {
240
+ const cleanupWhy = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
241
+ const setupWhy = setupError.why ?? "Branch database setup failed.";
242
+ return new CliError({
243
+ code: setupError.code,
244
+ domain: setupError.domain,
245
+ summary: setupError.summary,
246
+ why: `${setupWhy} Prisma could not delete the created database "${database.name}" (${database.id}): ${cleanupWhy}`,
247
+ fix: "Delete the created branch database from Console or contact Prisma support, then rerun deploy with --db.",
248
+ debug: formatCombinedDebugDetails(setupError, cleanupError),
249
+ meta: {
250
+ ...setupError.meta,
251
+ branch: branchName,
252
+ databaseId: database.id,
253
+ databaseName: database.name,
254
+ cleanupFailed: true
255
+ },
256
+ exitCode: setupError.exitCode,
257
+ nextSteps: []
258
+ });
259
+ }
260
+ function schemaSetupFailedError(error, schema, branchName) {
261
+ return new CliError({
262
+ code: "SCHEMA_SETUP_FAILED",
263
+ domain: "app",
264
+ summary: "Database schema setup failed",
265
+ why: error instanceof Error ? error.message : String(error),
266
+ fix: "Fix the Prisma schema or migrations, then rerun deploy with --db.",
267
+ debug: formatDebugDetails(error),
268
+ meta: {
269
+ branch: branchName,
270
+ schemaPath: schema.path,
271
+ command: schema.command
272
+ },
273
+ exitCode: 1,
274
+ nextSteps: [schema.command === "migrate-deploy" ? "npx --no-install prisma migrate deploy" : "npx --no-install prisma db push --skip-generate", `prisma-cli app deploy --branch ${formatCommandArgument(branchName)} --db`]
275
+ });
276
+ }
277
+ function formatDebugDetails(error) {
278
+ if (error instanceof Error) return error.stack ?? error.message;
279
+ return typeof error === "string" ? error : null;
280
+ }
281
+ function formatCombinedDebugDetails(setupError, cleanupError) {
282
+ const setupDebug = setupError.debug ?? setupError.stack ?? setupError.message;
283
+ const cleanupDebug = formatDebugDetails(cleanupError);
284
+ return [setupDebug ? `Setup error:\n${setupDebug}` : null, cleanupDebug ? `Cleanup error:\n${cleanupDebug}` : null].filter((line) => Boolean(line)).join("\n\n") || null;
285
+ }
286
+ //#endregion
287
+ export { maybeSetupBranchDatabase };