@prisma/cli 3.0.0-beta.3 → 3.0.0-beta.5

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.
@@ -1,38 +1,38 @@
1
1
  import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
2
  import { requireComputeAuth } from "../lib/auth/guard.js";
3
3
  import { resolveProjectTarget } from "../lib/project/resolution.js";
4
+ import { readLocalGitBranch } from "../lib/git/local-branch.js";
4
5
  import { requireAuthenticatedAuthState } from "./auth.js";
5
6
  import { listRealWorkspaceProjects } from "./project.js";
6
7
  import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
8
+ import { readEnvFileAssignments } from "../lib/app/env-file.js";
9
+ import { apiCallError, findVariableByNaturalKey, toMetadata } from "./app-env-api.js";
10
+ import { runEnvAddFile, runEnvUpdateFile } from "./app-env-file.js";
7
11
  //#region src/controllers/app-env.ts
8
- function defaultRoleScope() {
9
- return {
10
- kind: "role",
11
- role: "production"
12
- };
13
- }
14
12
  async function runEnvAdd(context, rawAssignment, flags) {
15
- const { key, value } = parseKeyValuePositional(rawAssignment, "add", context.runtime.env);
13
+ const source = resolveEnvWriteSource(rawAssignment, flags.filePath, "add");
16
14
  const scope = resolveEnvScope(flags, {
17
15
  requireExplicit: true,
18
16
  command: "add"
19
17
  });
20
- if (!scope) throw usageError(`prisma-cli project env add requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env add ${key}=${value} --role production`], "app");
18
+ if (!scope) throw usageError(`prisma-cli project env add requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", ["prisma-cli project env add KEY=value --role production"], "app");
19
+ const input = await resolveEnvWriteInput(context, source, "add");
21
20
  const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env add");
22
21
  const resolved = await resolveScopeToApi(client, projectId, scope, {
23
22
  createBranchIfMissing: true,
24
23
  signal: context.runtime.signal
25
24
  });
26
- if (await findVariableByNaturalKey(client, projectId, key, resolved, context.runtime.signal)) throw new CliError({
25
+ if (input.kind === "file") return runEnvAddFile(context, client, projectId, resolved, input.filePath, input.assignments);
26
+ if (await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal)) throw new CliError({
27
27
  code: "ENV_VARIABLE_ALREADY_EXISTS",
28
28
  domain: "app",
29
- summary: `Variable "${key}" already exists in ${formatScopeLabel(scope)}`,
29
+ summary: `Variable "${input.key}" already exists in ${formatScopeLabel(scope)}`,
30
30
  why: "A variable with this key already exists in the targeted scope.",
31
31
  fix: "Use `prisma-cli project env update` to change an existing variable's value.",
32
32
  exitCode: 1,
33
- nextSteps: [`prisma-cli project env update ${key}=<new-value> ${formatScopeFlag(scope)}`]
33
+ nextSteps: [`prisma-cli project env update ${input.key}=<new-value> ${formatScopeFlag(scope)}`]
34
34
  });
35
- const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(client, projectId, key, {
35
+ const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(client, projectId, input.key, {
36
36
  scope: {
37
37
  kind: "role",
38
38
  role: "preview"
@@ -45,18 +45,18 @@ async function runEnvAdd(context, rawAssignment, flags) {
45
45
  class: "preview",
46
46
  branchId: null
47
47
  }
48
- }, context.runtime.signal) ? [`Variable "${key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
48
+ }, context.runtime.signal) ? [`Variable "${input.key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
49
49
  const { data, error, response } = await client.POST("/v1/environment-variables", {
50
50
  body: {
51
51
  projectId,
52
52
  class: resolved.apiTarget.class,
53
53
  ...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
54
- key,
55
- value
54
+ key: input.key,
55
+ value: input.value
56
56
  },
57
57
  signal: context.runtime.signal
58
58
  });
59
- if (error || !data) throw apiCallError(`Failed to add ${key}`, response, error);
59
+ if (error || !data) throw apiCallError(`Failed to add ${input.key}`, response, error);
60
60
  return {
61
61
  command: "project.env.add",
62
62
  result: {
@@ -69,33 +69,35 @@ async function runEnvAdd(context, rawAssignment, flags) {
69
69
  };
70
70
  }
71
71
  async function runEnvUpdate(context, rawAssignment, flags) {
72
- const { key, value } = parseKeyValuePositional(rawAssignment, "update", context.runtime.env);
72
+ const source = resolveEnvWriteSource(rawAssignment, flags.filePath, "update");
73
73
  const scope = resolveEnvScope(flags, {
74
74
  requireExplicit: true,
75
75
  command: "update"
76
76
  });
77
- if (!scope) throw usageError(`prisma-cli project env update requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env update ${key}=${value} --role production`], "app");
77
+ if (!scope) throw usageError(`prisma-cli project env update requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", ["prisma-cli project env update KEY=value --role production"], "app");
78
+ const input = await resolveEnvWriteInput(context, source, "update");
78
79
  const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env update");
79
80
  const resolved = await resolveScopeToApi(client, projectId, scope, {
80
81
  createBranchIfMissing: false,
81
82
  signal: context.runtime.signal
82
83
  });
83
- const existing = await findVariableByNaturalKey(client, projectId, key, resolved, context.runtime.signal);
84
+ if (input.kind === "file") return runEnvUpdateFile(context, client, projectId, resolved, input.filePath, input.assignments);
85
+ const existing = await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal);
84
86
  if (!existing) throw new CliError({
85
87
  code: "ENV_VARIABLE_NOT_FOUND",
86
88
  domain: "app",
87
- summary: `Variable "${key}" not found in ${formatScopeLabel(scope)}`,
89
+ summary: `Variable "${input.key}" not found in ${formatScopeLabel(scope)}`,
88
90
  why: "No variable with this key exists in the targeted scope.",
89
91
  fix: "Use `prisma-cli project env add` to create a new variable.",
90
92
  exitCode: 1,
91
- nextSteps: [`prisma-cli project env add ${key}=<value> ${formatScopeFlag(scope)}`]
93
+ nextSteps: [`prisma-cli project env add ${input.key}=<value> ${formatScopeFlag(scope)}`]
92
94
  });
93
95
  const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
94
96
  params: { path: { envVarId: existing.id } },
95
- body: { value },
97
+ body: { value: input.value },
96
98
  signal: context.runtime.signal
97
99
  });
98
- if (error || !data) throw apiCallError(`Failed to update value for ${key}`, response, error);
100
+ if (error || !data) throw apiCallError(`Failed to update value for ${input.key}`, response, error);
99
101
  return {
100
102
  command: "project.env.update",
101
103
  result: {
@@ -107,26 +109,57 @@ async function runEnvUpdate(context, rawAssignment, flags) {
107
109
  nextSteps: []
108
110
  };
109
111
  }
112
+ function resolveEnvWriteSource(rawAssignment, filePath, command) {
113
+ if (filePath !== void 0 && rawAssignment !== void 0) throw usageError(`prisma-cli project env ${command} accepts either KEY=VALUE or --file`, "The command received both a positional assignment and a dotenv file path.", "Pass one input source.", [`prisma-cli project env ${command} KEY=value --role preview`, `prisma-cli project env ${command} --file .env --role preview`], "app");
114
+ if (filePath !== void 0) {
115
+ if (filePath.length === 0) throw usageError(`prisma-cli project env ${command} --file requires a path`, "The --file flag was passed without a file path.", "Pass a readable dotenv file path.", [`prisma-cli project env ${command} --file .env --role preview`], "app");
116
+ return {
117
+ kind: "file",
118
+ filePath
119
+ };
120
+ }
121
+ if (rawAssignment === void 0) throw usageError(`prisma-cli project env ${command} requires KEY=VALUE or --file`, "No environment variable input was supplied.", "Pass a single KEY=VALUE assignment or a dotenv file path.", [`prisma-cli project env ${command} KEY=value --role preview`, `prisma-cli project env ${command} --file .env --role preview`], "app");
122
+ return {
123
+ kind: "single",
124
+ rawAssignment
125
+ };
126
+ }
127
+ async function resolveEnvWriteInput(context, source, command) {
128
+ if (source.kind === "file") return {
129
+ kind: "file",
130
+ filePath: source.filePath,
131
+ assignments: await readEnvFileAssignments(context.runtime.cwd, source.filePath, command)
132
+ };
133
+ return {
134
+ kind: "single",
135
+ ...parseKeyValuePositional(source.rawAssignment, command, context.runtime.env)
136
+ };
137
+ }
110
138
  async function runEnvList(context, flags) {
111
- const scope = resolveEnvScope(flags, {
139
+ const explicit = resolveEnvScope(flags, {
112
140
  requireExplicit: false,
113
141
  command: "list"
114
- }) ?? defaultRoleScope();
142
+ });
115
143
  const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
116
- const resolved = await resolveScopeToApi(client, projectId, scope, {
117
- createBranchIfMissing: false,
144
+ const resolved = await resolveListScopeToApi(client, projectId, explicit, {
145
+ cwd: context.runtime.cwd,
118
146
  signal: context.runtime.signal
119
147
  });
120
- const variables = await listVariables(client, projectId, resolved, context.runtime.signal);
148
+ const variables = resolved.kind === "scoped" ? await listVariables(client, projectId, {
149
+ scope: resolved.addScope,
150
+ descriptor: resolved.descriptor,
151
+ apiTarget: resolved.apiTarget
152
+ }, context.runtime.signal) : await listOverviewVariables(client, projectId, context.runtime.signal);
121
153
  return {
122
154
  command: "project.env.list",
123
155
  result: {
124
156
  projectId,
125
157
  scope: resolved.descriptor,
158
+ target: resolved.target,
126
159
  variables: variables.map((row) => toMetadata(row, resolved.descriptor))
127
160
  },
128
161
  warnings: [],
129
- nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(scope)}`] : []
162
+ nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(resolved.addScope)}`] : []
130
163
  };
131
164
  }
132
165
  async function runEnvRemove(context, key, flags) {
@@ -196,12 +229,12 @@ async function resolveScopeToApi(client, projectId, scope, options) {
196
229
  }
197
230
  };
198
231
  const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName, options.signal) : await resolveExistingBranch(client, projectId, scope.branchName, options.signal);
199
- if (branch.isDefault) throw new CliError({
232
+ if (branch.role === "production") throw new CliError({
200
233
  code: "ENV_BRANCH_SCOPE_IS_PRODUCTION",
201
234
  domain: "app",
202
- summary: `Branch "${scope.branchName}" is the default branch`,
235
+ summary: `Branch "${scope.branchName}" is the production branch`,
203
236
  why: "Production variables are project-level only; branch overrides apply to preview branches.",
204
- fix: "Use --role production for the default branch.",
237
+ fix: "Use --role production for the production branch.",
205
238
  exitCode: 1,
206
239
  nextSteps: ["prisma-cli project env list --role production"]
207
240
  });
@@ -218,6 +251,123 @@ async function resolveScopeToApi(client, projectId, scope, options) {
218
251
  }
219
252
  };
220
253
  }
254
+ async function resolveListScopeToApi(client, projectId, explicit, options) {
255
+ if (explicit) {
256
+ const resolved = await resolveScopeToApi(client, projectId, explicit, {
257
+ createBranchIfMissing: false,
258
+ signal: options.signal
259
+ });
260
+ return {
261
+ kind: "scoped",
262
+ descriptor: resolved.descriptor,
263
+ target: targetFromExplicitScope(resolved.descriptor),
264
+ apiTarget: resolved.apiTarget,
265
+ addScope: resolved.scope
266
+ };
267
+ }
268
+ const gitBranch = await readLocalGitBranch(options.cwd, options.signal);
269
+ if (gitBranch) {
270
+ const branch = (await listBranchesByName(client, projectId, gitBranch, options.signal))[0];
271
+ if (!branch) return {
272
+ kind: "scoped",
273
+ descriptor: {
274
+ kind: "role",
275
+ role: "preview"
276
+ },
277
+ target: {
278
+ source: "local-git",
279
+ branchName: gitBranch,
280
+ branchExists: false,
281
+ envMap: "preview"
282
+ },
283
+ apiTarget: {
284
+ class: "preview",
285
+ branchId: null
286
+ },
287
+ addScope: {
288
+ kind: "branch",
289
+ branchName: gitBranch
290
+ }
291
+ };
292
+ if (branch.role === "production") return {
293
+ kind: "scoped",
294
+ descriptor: {
295
+ kind: "role",
296
+ role: "production"
297
+ },
298
+ target: {
299
+ source: "local-git",
300
+ branchName: branch.gitName,
301
+ branchId: branch.id,
302
+ branchRole: branch.role,
303
+ branchExists: true,
304
+ envMap: "production"
305
+ },
306
+ apiTarget: {
307
+ class: "production",
308
+ branchId: null
309
+ },
310
+ addScope: {
311
+ kind: "role",
312
+ role: "production"
313
+ }
314
+ };
315
+ return {
316
+ kind: "scoped",
317
+ descriptor: {
318
+ kind: "branch",
319
+ branchName: branch.gitName,
320
+ branchId: branch.id
321
+ },
322
+ target: {
323
+ source: "local-git",
324
+ branchName: branch.gitName,
325
+ branchId: branch.id,
326
+ branchRole: branch.role,
327
+ branchExists: true,
328
+ envMap: "preview"
329
+ },
330
+ apiTarget: {
331
+ class: "preview",
332
+ branchId: branch.id
333
+ },
334
+ addScope: {
335
+ kind: "branch",
336
+ branchName: branch.gitName
337
+ }
338
+ };
339
+ }
340
+ return {
341
+ kind: "overview",
342
+ descriptor: { kind: "overview" },
343
+ target: {
344
+ source: "overview",
345
+ envMap: "overview"
346
+ },
347
+ addScope: {
348
+ kind: "role",
349
+ role: "preview"
350
+ }
351
+ };
352
+ }
353
+ function targetFromExplicitScope(scope) {
354
+ if (scope.kind === "branch") return {
355
+ source: "explicit",
356
+ branchName: scope.branchName,
357
+ branchId: scope.branchId,
358
+ branchRole: "preview",
359
+ branchExists: true,
360
+ envMap: "preview"
361
+ };
362
+ if (scope.kind === "role") return {
363
+ source: "explicit",
364
+ envMap: scope.role
365
+ };
366
+ return {
367
+ source: "overview",
368
+ envMap: "overview"
369
+ };
370
+ }
221
371
  function formatScopeFlag(scope) {
222
372
  if (scope.kind === "role") return `--role ${scope.role}`;
223
373
  return `--branch ${scope.branchName}`;
@@ -293,47 +443,45 @@ async function projectHasDefaultBranch(client, projectId, signal) {
293
443
  cursor = result.data.pagination.nextCursor;
294
444
  }
295
445
  }
296
- async function findVariableByNaturalKey(client, projectId, key, resolved, signal) {
297
- const { data, error, response } = await client.GET("/v1/environment-variables", {
298
- params: { query: {
299
- projectId,
300
- class: resolved.apiTarget.class,
301
- key
302
- } },
303
- signal
446
+ async function listVariables(client, projectId, resolved, signal) {
447
+ return materializeEffectiveRows(await collectEnvironmentVariables(client, projectId, signal, {
448
+ className: resolved.apiTarget.class,
449
+ filter: (row) => rowMatchesScope(row, resolved)
450
+ }), resolved);
451
+ }
452
+ async function listOverviewVariables(client, projectId, signal) {
453
+ return (await collectEnvironmentVariables(client, projectId, signal, { filter: (row) => row.branchId === null && (row.class === "production" || row.class === "preview") })).sort((left, right) => {
454
+ const roleOrder = roleSortOrder(left.class) - roleSortOrder(right.class);
455
+ return roleOrder !== 0 ? roleOrder : left.key.localeCompare(right.key);
304
456
  });
305
- if (error || !data) throw apiCallError(`Failed to look up ${key}`, response, error);
306
- return data.data.filter((row) => rowMatchesExactScope(row, resolved))[0] ?? null;
307
457
  }
308
- async function listVariables(client, projectId, resolved, signal) {
458
+ async function collectEnvironmentVariables(client, projectId, signal, options) {
309
459
  const collected = [];
310
460
  let cursor;
311
461
  while (true) {
312
- const query = {
313
- projectId,
314
- class: resolved.apiTarget.class
315
- };
462
+ const query = { projectId };
463
+ if (options.className !== void 0) query.class = options.className;
316
464
  if (cursor !== void 0) query.cursor = cursor;
317
465
  const result = await client.GET("/v1/environment-variables", {
318
466
  params: { query },
319
467
  signal
320
468
  });
321
469
  if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
322
- const page = result.data.data.filter((row) => rowMatchesScope(row, resolved));
470
+ const page = result.data.data.filter(options.filter);
323
471
  collected.push(...page);
324
472
  if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
325
473
  cursor = result.data.pagination.nextCursor;
326
474
  }
327
- return materializeEffectiveRows(collected, resolved);
475
+ return collected;
476
+ }
477
+ function roleSortOrder(role) {
478
+ return role === "production" ? 0 : 1;
328
479
  }
329
480
  function rowMatchesScope(row, resolved) {
330
481
  if (row.class !== resolved.apiTarget.class) return false;
331
482
  if (resolved.apiTarget.branchId === null) return row.branchId === null;
332
483
  return row.branchId === null || row.branchId === resolved.apiTarget.branchId;
333
484
  }
334
- function rowMatchesExactScope(row, resolved) {
335
- return row.class === resolved.apiTarget.class && row.branchId === resolved.apiTarget.branchId;
336
- }
337
485
  function materializeEffectiveRows(rows, resolved) {
338
486
  if (resolved.apiTarget.branchId === null) return rows;
339
487
  const byKey = /* @__PURE__ */ new Map();
@@ -341,39 +489,5 @@ function materializeEffectiveRows(rows, resolved) {
341
489
  for (const row of rows) if (row.branchId === resolved.apiTarget.branchId) byKey.set(row.key, row);
342
490
  return [...byKey.values()].sort((left, right) => left.key.localeCompare(right.key));
343
491
  }
344
- function toMetadata(row, requestedScope) {
345
- const rowScope = row.branchId === null ? {
346
- kind: "role",
347
- role: row.class
348
- } : requestedScope;
349
- return {
350
- id: row.id,
351
- key: row.key,
352
- scope: rowScope,
353
- source: formatDescriptorLabel(rowScope),
354
- isManagedBySystem: row.isManagedBySystem,
355
- updatedAt: row.updatedAt
356
- };
357
- }
358
- function formatDescriptorLabel(scope) {
359
- if (scope.kind === "role") return scope.role ?? "unknown";
360
- return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
361
- }
362
- function apiCallError(summary, response, error) {
363
- const status = response?.status ?? 0;
364
- const apiCode = error?.error?.code;
365
- const apiMessage = error?.error?.message;
366
- const apiHint = error?.error?.hint;
367
- if (status === 401 || status === 403) return authRequiredError(["prisma auth login"]);
368
- return new CliError({
369
- code: apiCode ?? "ENV_API_ERROR",
370
- domain: "app",
371
- summary,
372
- why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
373
- fix: apiHint ?? "Re-run with --trace for the underlying API response details.",
374
- exitCode: 1,
375
- nextSteps: []
376
- });
377
- }
378
492
  //#endregion
379
493
  export { runEnvAdd, runEnvList, runEnvRemove, runEnvUpdate };
@@ -16,10 +16,13 @@ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../l
16
16
  import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
17
17
  import { bindProjectToDirectory, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
18
18
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
19
+ import { readLocalGitBranch } from "../lib/git/local-branch.js";
19
20
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
20
21
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
22
+ import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
21
23
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
22
24
  import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
25
+ import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
23
26
  import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
24
27
  import { createSelectPromptPort } from "./select-prompt-port.js";
25
28
  import { requireAuthenticatedAuthState } from "./auth.js";
@@ -158,10 +161,20 @@ async function runAppDeploy(context, appName, options) {
158
161
  });
159
162
  framework = customized.framework;
160
163
  runtime = customized.runtime;
164
+ await enforceProductionDeployGate(context, provider, {
165
+ appId: selectedApp.appId,
166
+ appName: selectedApp.displayName,
167
+ branchKind: target.branch.kind,
168
+ prod: options?.prod === true
169
+ });
161
170
  const buildType = framework.buildType;
162
171
  assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
163
172
  const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint, context.runtime.signal);
164
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
+ });
165
178
  const progressState = createPreviewDeployProgressState();
166
179
  const deployStartedAt = Date.now();
167
180
  const deployResult = await provider.deployApp({
@@ -192,8 +205,9 @@ async function runAppDeploy(context, appName, options) {
192
205
  result: {
193
206
  workspace: target.workspace,
194
207
  project: target.project,
195
- branch: target.branch,
208
+ branch: toResultBranch(target.branch),
196
209
  resolution: target.resolution,
210
+ branchDatabase: branchDatabaseSetup.result,
197
211
  app: {
198
212
  id: deployResult.app.id,
199
213
  name: deployResult.app.name
@@ -202,7 +216,7 @@ async function runAppDeploy(context, appName, options) {
202
216
  durationMs: deployDurationMs,
203
217
  localPin: localPinResult
204
218
  },
205
- warnings: [],
219
+ warnings: branchDatabaseSetup.warnings,
206
220
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
207
221
  };
208
222
  }
@@ -749,7 +763,7 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
749
763
  resultTarget: {
750
764
  workspace: target.workspace,
751
765
  project: target.project,
752
- branch: target.branch,
766
+ branch: toResultBranch(target.branch),
753
767
  app: {
754
768
  id: selectedApp.id,
755
769
  name: selectedApp.name
@@ -1312,6 +1326,7 @@ async function resolveProjectContext(context, client, explicitProject, options)
1312
1326
  return {
1313
1327
  ...resolved,
1314
1328
  branch: {
1329
+ id: null,
1315
1330
  name: branch.name,
1316
1331
  kind: toBranchKind(branch.name)
1317
1332
  }
@@ -1322,7 +1337,7 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1322
1337
  if (!workspace) throw workspaceRequiredError();
1323
1338
  const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1324
1339
  const projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
1325
- if (explicitProject) return withDeployBranch({
1340
+ if (explicitProject) return withRemoteDeployBranch(provider, {
1326
1341
  workspace,
1327
1342
  project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
1328
1343
  resolution: {
@@ -1331,11 +1346,11 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1331
1346
  targetNameSource: "explicit"
1332
1347
  },
1333
1348
  localPinAction: "linked"
1334
- }, branch);
1349
+ }, branch, context.runtime.signal);
1335
1350
  if (options.createProjectName) {
1336
1351
  const projectName = options.createProjectName.trim();
1337
1352
  if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1338
- return withDeployBranch({
1353
+ return withRemoteDeployBranch(provider, {
1339
1354
  workspace,
1340
1355
  project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
1341
1356
  resolution: {
@@ -1344,12 +1359,12 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1344
1359
  targetNameSource: "explicit"
1345
1360
  },
1346
1361
  localPinAction: "created"
1347
- }, branch);
1362
+ }, branch, context.runtime.signal);
1348
1363
  }
1349
1364
  if (options.envProjectId) {
1350
1365
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
1351
1366
  if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1352
- return withDeployBranch({
1367
+ return withRemoteDeployBranch(provider, {
1353
1368
  workspace,
1354
1369
  project: toProjectSummary(project),
1355
1370
  resolution: {
@@ -1357,14 +1372,14 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1357
1372
  targetName: options.envProjectId,
1358
1373
  targetNameSource: "env"
1359
1374
  }
1360
- }, branch);
1375
+ }, branch, context.runtime.signal);
1361
1376
  }
1362
1377
  const localPin = options.localPin;
1363
1378
  if (localPin.kind === "present") {
1364
1379
  if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1365
1380
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1366
1381
  if (!project) throw localResolutionPinStaleError();
1367
- return withDeployBranch({
1382
+ return withRemoteDeployBranch(provider, {
1368
1383
  workspace,
1369
1384
  project: toProjectSummary(project),
1370
1385
  resolution: {
@@ -1372,10 +1387,10 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1372
1387
  targetName: project.name,
1373
1388
  targetNameSource: "local-pin"
1374
1389
  }
1375
- }, branch);
1390
+ }, branch, context.runtime.signal);
1376
1391
  }
1377
1392
  const platformMapping = await resolveDurablePlatformMapping();
1378
- if (platformMapping && platformMapping.workspace.id === workspace.id) return withDeployBranch({
1393
+ if (platformMapping && platformMapping.workspace.id === workspace.id) return withRemoteDeployBranch(provider, {
1379
1394
  workspace,
1380
1395
  project: toProjectSummary(platformMapping),
1381
1396
  resolution: {
@@ -1383,8 +1398,8 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1383
1398
  targetName: platformMapping.name,
1384
1399
  targetNameSource: "platform-mapping"
1385
1400
  }
1386
- }, branch);
1387
- if (canPrompt(context) && !context.flags.yes) return withDeployBranch(await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch);
1401
+ }, branch, context.runtime.signal);
1402
+ if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch, context.runtime.signal);
1388
1403
  throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1389
1404
  }
1390
1405
  async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
@@ -1430,18 +1445,37 @@ async function createProjectForDeploySetup(provider, projectName, workspace, sig
1430
1445
  workspace
1431
1446
  };
1432
1447
  }
1433
- function withDeployBranch(target, branch) {
1448
+ async function withRemoteDeployBranch(provider, target, branch, signal) {
1449
+ const remoteBranch = await provider.resolveBranch(target.project.id, {
1450
+ branchName: branch.name,
1451
+ signal
1452
+ });
1434
1453
  return {
1435
1454
  ...target,
1436
1455
  branch: {
1437
- name: branch.name,
1438
- kind: toBranchKind(branch.name)
1456
+ id: remoteBranch.id,
1457
+ name: remoteBranch.name,
1458
+ kind: remoteBranch.role
1439
1459
  }
1440
1460
  };
1441
1461
  }
1442
1462
  function toBranchKind(name) {
1443
1463
  return name === "production" || name === "main" ? "production" : "preview";
1444
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
+ }
1445
1479
  function assertExclusiveDeployProjectInputs(options) {
1446
1480
  const provided = [
1447
1481
  options.projectRef ? "--project" : null,
@@ -1470,42 +1504,6 @@ async function resolveDeployBranch(context, explicitBranchName) {
1470
1504
  annotation: "default"
1471
1505
  };
1472
1506
  }
1473
- async function readLocalGitBranch(cwd, signal) {
1474
- const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
1475
- if (!headPath) return null;
1476
- try {
1477
- const head = (await readFile(headPath, {
1478
- encoding: "utf8",
1479
- signal
1480
- })).trim();
1481
- if (head.startsWith("ref: refs/heads/")) return head.slice(16);
1482
- } catch (error) {
1483
- if (signal.aborted) throw error;
1484
- return null;
1485
- }
1486
- return null;
1487
- }
1488
- async function resolveGitHeadPath(gitPath, signal) {
1489
- signal.throwIfAborted();
1490
- try {
1491
- const raw = await readFile(gitPath, {
1492
- encoding: "utf8",
1493
- signal
1494
- });
1495
- if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
1496
- } catch (error) {
1497
- if (signal.aborted) throw error;
1498
- }
1499
- signal.throwIfAborted();
1500
- try {
1501
- await access(path.join(gitPath, "HEAD"));
1502
- signal.throwIfAborted();
1503
- return path.join(gitPath, "HEAD");
1504
- } catch (error) {
1505
- if (signal.aborted) throw error;
1506
- return null;
1507
- }
1508
- }
1509
1507
  async function resolveDeployFramework(context, options) {
1510
1508
  if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1511
1509
  if (options.entrypoint) return {