@prisma/cli 3.0.0-beta.1 → 3.0.0-beta.10

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.
Files changed (61) hide show
  1. package/README.md +5 -3
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +12 -4
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +63 -22
  6. package/dist/cli.js +17 -2
  7. package/dist/cli2.js +7 -3
  8. package/dist/commands/app/index.js +22 -10
  9. package/dist/commands/branch/index.js +2 -27
  10. package/dist/commands/database/index.js +159 -0
  11. package/dist/commands/env.js +8 -4
  12. package/dist/controllers/app-env-api.js +54 -0
  13. package/dist/controllers/app-env-file.js +181 -0
  14. package/dist/controllers/app-env.js +283 -129
  15. package/dist/controllers/app.js +247 -106
  16. package/dist/controllers/auth.js +8 -8
  17. package/dist/controllers/branch.js +78 -48
  18. package/dist/controllers/database.js +318 -0
  19. package/dist/controllers/project.js +153 -82
  20. package/dist/lib/app/branch-database-deploy.js +373 -0
  21. package/dist/lib/app/branch-database.js +316 -0
  22. package/dist/lib/app/bun-project.js +12 -5
  23. package/dist/lib/app/deploy-output.js +10 -1
  24. package/dist/lib/app/env-config.js +1 -1
  25. package/dist/lib/app/env-file.js +82 -0
  26. package/dist/lib/app/env-vars.js +28 -2
  27. package/dist/lib/app/local-dev.js +34 -18
  28. package/dist/lib/app/preview-branch-database.js +102 -0
  29. package/dist/lib/app/preview-build-settings.js +385 -0
  30. package/dist/lib/app/preview-build.js +272 -81
  31. package/dist/lib/app/preview-provider.js +163 -54
  32. package/dist/lib/app/production-deploy-gate.js +161 -0
  33. package/dist/lib/auth/auth-ops.js +30 -21
  34. package/dist/lib/auth/guard.js +3 -2
  35. package/dist/lib/auth/login.js +109 -14
  36. package/dist/lib/database/provider.js +167 -0
  37. package/dist/lib/diagnostics.js +15 -0
  38. package/dist/lib/git/local-branch.js +41 -0
  39. package/dist/lib/git/local-status.js +57 -0
  40. package/dist/lib/project/interactive-setup.js +1 -1
  41. package/dist/lib/project/local-pin.js +182 -33
  42. package/dist/lib/project/resolution.js +203 -49
  43. package/dist/lib/project/setup.js +59 -15
  44. package/dist/presenters/app-env.js +149 -14
  45. package/dist/presenters/app.js +170 -20
  46. package/dist/presenters/branch.js +37 -102
  47. package/dist/presenters/database.js +274 -0
  48. package/dist/presenters/project.js +63 -39
  49. package/dist/presenters/verbose-context.js +64 -0
  50. package/dist/shell/command-meta.js +103 -13
  51. package/dist/shell/command-runner.js +34 -6
  52. package/dist/shell/diagnostics-output.js +63 -0
  53. package/dist/shell/errors.js +12 -1
  54. package/dist/shell/output.js +10 -1
  55. package/dist/shell/runtime.js +3 -3
  56. package/dist/shell/ui.js +23 -1
  57. package/dist/shell/update-check.js +247 -0
  58. package/dist/use-cases/branch.js +20 -68
  59. package/dist/use-cases/create-cli-gateways.js +2 -17
  60. package/dist/use-cases/project.js +2 -1
  61. package/package.json +12 -10
@@ -7,19 +7,23 @@ import { canPrompt } from "../shell/runtime.js";
7
7
  import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
8
8
  import { requireComputeAuth } from "../lib/auth/guard.js";
9
9
  import { readAuthState } from "../lib/auth/auth-ops.js";
10
- import { parseEnvAssignments } from "../lib/app/env-vars.js";
11
- import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
10
+ import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
11
+ import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
12
12
  import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
13
13
  import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
14
14
  import { formatCommandArgument } from "../shell/command-arguments.js";
15
15
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
16
- import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
17
- import { bindProjectToDirectory, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
16
+ import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
17
+ import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, 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";
20
+ import { resolveOrCreatePreviewBuildSettings } from "../lib/app/preview-build-settings.js";
19
21
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
20
22
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
23
+ import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
21
24
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
22
25
  import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
26
+ import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
23
27
  import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
24
28
  import { createSelectPromptPort } from "./select-prompt-port.js";
25
29
  import { requireAuthenticatedAuthState } from "./auth.js";
@@ -27,6 +31,7 @@ import { listRealWorkspaceProjects } from "./project.js";
27
31
  import { access, readFile } from "node:fs/promises";
28
32
  import path from "node:path";
29
33
  import open from "open";
34
+ import { Result, matchError } from "better-result";
30
35
  //#region src/controllers/app.ts
31
36
  const DEPLOY_FRAMEWORKS = [
32
37
  "nextjs",
@@ -48,7 +53,8 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
48
53
  const { artifact, buildType: actualBuildType } = await executePreviewBuild({
49
54
  appPath: context.runtime.cwd,
50
55
  entrypoint,
51
- buildType
56
+ buildType,
57
+ signal: context.runtime.signal
52
58
  });
53
59
  return {
54
60
  command: "app.build",
@@ -78,12 +84,13 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
78
84
  buildType: resolvedBuildType,
79
85
  entrypoint,
80
86
  port,
81
- env: context.runtime.env
87
+ env: context.runtime.env,
88
+ signal: context.runtime.signal
82
89
  });
83
90
  } catch (error) {
84
91
  throw runFailedError("Local app run failed", error);
85
92
  }
86
- if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") process.exitCode = runResult.signal === "SIGINT" ? 130 : 143;
93
+ if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") throw new DOMException("Command canceled", "AbortError");
87
94
  else if (runResult.exitCode !== 0) throw runFailedError("Local app run failed", `The ${formatFrameworkName(runResult.framework)} process exited with code ${runResult.exitCode}.`, runResult.exitCode);
88
95
  return {
89
96
  command: "app.run",
@@ -106,9 +113,9 @@ async function runAppDeploy(context, appName, options) {
106
113
  createProjectName: options?.createProjectName,
107
114
  envProjectId
108
115
  });
109
- const skipLocalPin = Boolean(envProjectId || options?.projectRef || options?.createProjectName);
110
- const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
111
- if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
116
+ const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
117
+ if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
118
+ const localPin = localPinReadResult.value;
112
119
  const branch = await resolveDeployBranch(context, options?.branchName);
113
120
  if (options?.httpPort) parseDeployHttpPort(options.httpPort);
114
121
  assertSupportedEntrypointForRequestedDeployShape({
@@ -124,8 +131,10 @@ async function runAppDeploy(context, appName, options) {
124
131
  let localPinResult;
125
132
  if (target.localPinAction) {
126
133
  const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction);
127
- localPinResult = setupResult.localPin;
128
- maybeRenderProjectLinked(context, setupResult.directory, setupResult.project.name, setupResult.localPin.path);
134
+ if (setupResult.isErr()) throw projectDirectoryBindingErrorToCliError(setupResult.error);
135
+ const projectSetup = setupResult.value;
136
+ localPinResult = projectSetup.localPin;
137
+ maybeRenderProjectLinked(context, projectSetup.directory, projectSetup.project.name, projectSetup.localPin.path);
129
138
  }
130
139
  let framework = await resolveDeployFramework(context, {
131
140
  requestedFramework: options?.framework,
@@ -133,12 +142,12 @@ async function runAppDeploy(context, appName, options) {
133
142
  });
134
143
  let runtime = resolveDeployRuntime(options?.httpPort, framework);
135
144
  assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
136
- const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
145
+ const envVars = toOptionalEnvVars(await parseEnvInputs(context.runtime.cwd, options?.envAssignments, { commandName: "deploy" }));
137
146
  const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
138
147
  explicitAppName: appName,
139
148
  explicitAppId: envAppId,
140
149
  firstDeploy: Boolean(target.localPinAction),
141
- inferName: () => inferTargetName(context.runtime.cwd)
150
+ inferName: () => inferTargetName(context.runtime.cwd, context.runtime.signal)
142
151
  });
143
152
  await maybeRenderDeploySetupBlock(context, {
144
153
  includeDirectory: !target.localPinAction,
@@ -156,10 +165,27 @@ async function runAppDeploy(context, appName, options) {
156
165
  });
157
166
  framework = customized.framework;
158
167
  runtime = customized.runtime;
168
+ const productionDeployGate = await enforceProductionDeployGate(context, provider, {
169
+ appId: selectedApp.appId,
170
+ appName: selectedApp.displayName,
171
+ branchKind: target.branch.kind,
172
+ prod: options?.prod === true
173
+ });
159
174
  const buildType = framework.buildType;
160
175
  assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
161
- const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint);
176
+ const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint, context.runtime.signal);
177
+ const buildSettingsResolution = await resolveOrCreatePreviewBuildSettings({
178
+ appPath: context.runtime.cwd,
179
+ buildType,
180
+ signal: context.runtime.signal
181
+ });
182
+ maybeRenderDeployBuildSettings(context, buildSettingsResolution);
162
183
  const portMapping = parseDeployPortMapping(String(runtime.port));
184
+ const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
185
+ db: options?.db,
186
+ providedEnvVars: envVars,
187
+ firstProductionDeploy: productionDeployGate.firstProductionDeploy
188
+ });
163
189
  const progressState = createPreviewDeployProgressState();
164
190
  const deployStartedAt = Date.now();
165
191
  const deployResult = await provider.deployApp({
@@ -171,9 +197,11 @@ async function runAppDeploy(context, appName, options) {
171
197
  region: selectedApp.region,
172
198
  entrypoint,
173
199
  buildType,
200
+ buildSettings: buildSettingsResolution.settings,
174
201
  portMapping,
175
202
  envVars,
176
203
  interaction: void 0,
204
+ signal: context.runtime.signal,
177
205
  progress: createPreviewDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
178
206
  }).catch((error) => {
179
207
  throw appDeployFailedError(error, progressState);
@@ -189,17 +217,42 @@ async function runAppDeploy(context, appName, options) {
189
217
  result: {
190
218
  workspace: target.workspace,
191
219
  project: target.project,
192
- branch: target.branch,
220
+ branch: toResultBranch(target.branch),
193
221
  resolution: target.resolution,
222
+ branchDatabase: branchDatabaseSetup.result,
194
223
  app: {
195
224
  id: deployResult.app.id,
196
225
  name: deployResult.app.name
197
226
  },
198
227
  deployment: deployResult.deployment,
228
+ deploySettings: {
229
+ config: {
230
+ path: buildSettingsResolution.relativeConfigPath,
231
+ status: buildSettingsResolution.status
232
+ },
233
+ buildCommand: {
234
+ value: buildSettingsResolution.settings.buildCommand,
235
+ source: buildSettingsResolution.settings.buildCommandSource
236
+ },
237
+ outputDirectory: {
238
+ value: buildSettingsResolution.settings.outputDirectory,
239
+ source: buildSettingsResolution.settings.outputDirectorySource
240
+ },
241
+ framework: {
242
+ key: framework.key,
243
+ buildType,
244
+ name: framework.displayName,
245
+ source: framework.annotation
246
+ },
247
+ entrypoint: entrypoint ?? null,
248
+ httpPort: runtime.port,
249
+ region: selectedApp.region ?? null,
250
+ envVars: envVarNames(envVars)
251
+ },
199
252
  durationMs: deployDurationMs,
200
253
  localPin: localPinResult
201
254
  },
202
- warnings: [],
255
+ warnings: branchDatabaseSetup.warnings,
203
256
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
204
257
  };
205
258
  }
@@ -211,13 +264,14 @@ async function runAppListDeploys(context, appName, projectRef) {
211
264
  command: "app.list-deploys",
212
265
  result: {
213
266
  projectId,
267
+ verboseContext: toAppVerboseContext(target),
214
268
  app: null,
215
269
  deployments: []
216
270
  },
217
271
  warnings: [],
218
272
  nextSteps: ["prisma-cli app deploy"]
219
273
  };
220
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
274
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
221
275
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app deploy"]);
222
276
  });
223
277
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -230,6 +284,7 @@ async function runAppListDeploys(context, appName, projectRef) {
230
284
  command: "app.list-deploys",
231
285
  result: {
232
286
  projectId,
287
+ verboseContext: toAppVerboseContext(target),
233
288
  app: {
234
289
  id: deploymentsResult.app.id,
235
290
  name: deploymentsResult.app.name
@@ -248,6 +303,7 @@ async function runAppShow(context, appName, projectRef) {
248
303
  command: "app.show",
249
304
  result: {
250
305
  projectId,
306
+ verboseContext: toAppVerboseContext(target),
251
307
  app: null,
252
308
  liveDeployment: null,
253
309
  liveUrl: null,
@@ -256,7 +312,7 @@ async function runAppShow(context, appName, projectRef) {
256
312
  warnings: [],
257
313
  nextSteps: ["prisma-cli app deploy"]
258
314
  };
259
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
315
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
260
316
  throw deployFailedError("Failed to inspect app", error, ["prisma-cli app list-deploys"]);
261
317
  });
262
318
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -270,6 +326,7 @@ async function runAppShow(context, appName, projectRef) {
270
326
  command: "app.show",
271
327
  result: {
272
328
  projectId,
329
+ verboseContext: toAppVerboseContext(target),
273
330
  app: {
274
331
  id: deploymentsResult.app.id,
275
332
  name: deploymentsResult.app.name
@@ -284,7 +341,7 @@ async function runAppShow(context, appName, projectRef) {
284
341
  }
285
342
  async function runAppShowDeploy(context, deploymentId) {
286
343
  ensurePreviewAppMode(context);
287
- const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId).catch((error) => {
344
+ const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
288
345
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
289
346
  });
290
347
  if (!deployment) throw new CliError({
@@ -321,7 +378,7 @@ async function runAppOpen(context, appName, projectRef) {
321
378
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
322
379
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
323
380
  if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
324
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
381
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
325
382
  throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
326
383
  });
327
384
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -334,11 +391,16 @@ async function runAppOpen(context, appName, projectRef) {
334
391
  if (!liveDeployment) throw noDeploymentsError("No deployments available to open", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
335
392
  if (!deploymentsResult.app.liveUrl) throw featureUnavailableError("Live URL is not available for the selected app", "Deployments exist, but the provider does not expose a stable live service URL for this app yet.", "Run prisma-cli app show to inspect the current deployment state and try again after the app reports a live URL.", ["prisma-cli app show"], "app");
336
393
  const shouldOpen = canPrompt(context);
337
- if (shouldOpen) await open(deploymentsResult.app.liveUrl);
394
+ if (shouldOpen) {
395
+ context.runtime.signal.throwIfAborted();
396
+ await open(deploymentsResult.app.liveUrl);
397
+ context.runtime.signal.throwIfAborted();
398
+ }
338
399
  return {
339
400
  command: "app.open",
340
401
  result: {
341
402
  projectId,
403
+ verboseContext: toAppVerboseContext(target),
342
404
  app: {
343
405
  id: deploymentsResult.app.id,
344
406
  name: deploymentsResult.app.name
@@ -355,7 +417,8 @@ async function runAppDomainAdd(context, hostname, options) {
355
417
  const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
356
418
  const added = await target.provider.addDomain({
357
419
  appId: target.app.id,
358
- hostname: normalizedHostname
420
+ hostname: normalizedHostname,
421
+ signal: context.runtime.signal
359
422
  }).catch((error) => {
360
423
  throw domainCommandError("add", error, normalizedHostname);
361
424
  });
@@ -373,8 +436,8 @@ async function runAppDomainAdd(context, hostname, options) {
373
436
  async function runAppDomainShow(context, hostname, options) {
374
437
  const normalizedHostname = normalizeDomainHostname(hostname);
375
438
  const target = await resolveAppDomainTarget(context, options, `app domain show ${normalizedHostname}`);
376
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show");
377
- const detail = await target.provider.showDomain(domain.id).catch((error) => {
439
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show", context.runtime.signal);
440
+ const detail = await target.provider.showDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
378
441
  throw domainCommandError("show", error, normalizedHostname);
379
442
  });
380
443
  return {
@@ -390,9 +453,9 @@ async function runAppDomainShow(context, hostname, options) {
390
453
  async function runAppDomainRemove(context, hostname, options) {
391
454
  const normalizedHostname = normalizeDomainHostname(hostname);
392
455
  const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
393
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
456
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove", context.runtime.signal);
394
457
  await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
395
- await target.provider.removeDomain(domain.id).catch((error) => {
458
+ await target.provider.removeDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
396
459
  throw domainCommandError("remove", error, normalizedHostname);
397
460
  });
398
461
  return {
@@ -409,8 +472,8 @@ async function runAppDomainRemove(context, hostname, options) {
409
472
  async function runAppDomainRetry(context, hostname, options) {
410
473
  const normalizedHostname = normalizeDomainHostname(hostname);
411
474
  const target = await resolveAppDomainTarget(context, options, `app domain retry ${normalizedHostname}`);
412
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry");
413
- const retried = await target.provider.retryDomain(domain.id).catch((error) => {
475
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry", context.runtime.signal);
476
+ const retried = await target.provider.retryDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
414
477
  throw domainCommandError("retry", error, normalizedHostname);
415
478
  });
416
479
  return {
@@ -427,7 +490,7 @@ async function runAppDomainWait(context, hostname, options) {
427
490
  const normalizedHostname = normalizeDomainHostname(hostname);
428
491
  const timeoutMs = parseDomainWaitTimeout(options?.timeout);
429
492
  const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
430
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
493
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait", context.runtime.signal);
431
494
  if (!context.flags.json && !context.flags.quiet) context.output.stderr.write([
432
495
  `app domain wait -> Waiting for ${normalizedHostname} to become active.`,
433
496
  "",
@@ -470,8 +533,8 @@ async function runAppDomainWait(context, hostname, options) {
470
533
  exitCode: 1,
471
534
  nextSteps: [`prisma-cli app domain show ${normalizedHostname}`]
472
535
  });
473
- await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)));
474
- current = await target.provider.showDomain(current.id).catch((error) => {
536
+ await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)), context.runtime.signal);
537
+ current = await target.provider.showDomain(current.id, { signal: context.runtime.signal }).catch((error) => {
475
538
  throw domainCommandError("wait", error, normalizedHostname);
476
539
  });
477
540
  }
@@ -504,6 +567,7 @@ async function runAppLogs(context, appName, deploymentId, projectRef) {
504
567
  }
505
568
  await provider.streamDeploymentLogs({
506
569
  deploymentId: target.deployment.id,
570
+ signal: context.runtime.signal,
507
571
  onRecord: (record) => writeLogRecord(context, record)
508
572
  }).catch((error) => {
509
573
  throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
@@ -513,7 +577,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
513
577
  if (appName) {
514
578
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
515
579
  if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
516
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
580
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
517
581
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
518
582
  });
519
583
  const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
@@ -526,7 +590,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
526
590
  deployment
527
591
  };
528
592
  }
529
- const shown = await provider.showDeployment(deploymentId).catch((error) => {
593
+ const shown = await provider.showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
530
594
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
531
595
  });
532
596
  if (!shown) throw new CliError({
@@ -569,7 +633,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
569
633
  async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
570
634
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
571
635
  if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
572
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
636
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
573
637
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
574
638
  });
575
639
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -604,7 +668,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
604
668
  ensurePreviewAppMode(context);
605
669
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
606
670
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
607
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
671
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
608
672
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
609
673
  });
610
674
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -617,6 +681,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
617
681
  if (!targetAlreadyLive) await provider.promoteDeployment({
618
682
  appId: selectedApp.id,
619
683
  deploymentId: targetDeployment.id,
684
+ signal: context.runtime.signal,
620
685
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
621
686
  }).catch((error) => {
622
687
  throw deployFailedError("Failed to promote deployment", error, ["prisma-cli app list-deploys"]);
@@ -626,6 +691,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
626
691
  command: "app.promote",
627
692
  result: {
628
693
  projectId,
694
+ verboseContext: toAppVerboseContext(target),
629
695
  app: {
630
696
  id: deploymentsResult.app.id,
631
697
  name: deploymentsResult.app.name
@@ -644,7 +710,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
644
710
  ensurePreviewAppMode(context);
645
711
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
646
712
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
647
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
713
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
648
714
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
649
715
  });
650
716
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -658,6 +724,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
658
724
  if (!targetAlreadyLive) await provider.promoteDeployment({
659
725
  appId: selectedApp.id,
660
726
  deploymentId: targetDeployment.id,
727
+ signal: context.runtime.signal,
661
728
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
662
729
  }).catch((error) => {
663
730
  throw deployFailedError("Failed to roll back deployment", error, ["prisma-cli app list-deploys"]);
@@ -667,6 +734,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
667
734
  command: "app.rollback",
668
735
  result: {
669
736
  projectId,
737
+ verboseContext: toAppVerboseContext(target),
670
738
  app: {
671
739
  id: deploymentsResult.app.id,
672
740
  name: deploymentsResult.app.name
@@ -687,7 +755,7 @@ async function runAppRemove(context, appName, projectRef) {
687
755
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
688
756
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
689
757
  await confirmAppRemoval(context, selectedApp);
690
- const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
758
+ const removedApp = await provider.removeApp(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
691
759
  throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
692
760
  });
693
761
  const warnings = await cleanupRemovedAppState(context, projectId, removedApp.id);
@@ -695,6 +763,7 @@ async function runAppRemove(context, appName, projectRef) {
695
763
  command: "app.remove",
696
764
  result: {
697
765
  projectId,
766
+ verboseContext: toAppVerboseContext(target),
698
767
  app: {
699
768
  id: removedApp.id,
700
769
  name: removedApp.name
@@ -738,7 +807,7 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
738
807
  resultTarget: {
739
808
  workspace: target.workspace,
740
809
  project: target.project,
741
- branch: target.branch,
810
+ branch: toResultBranch(target.branch),
742
811
  app: {
743
812
  id: selectedApp.id,
744
813
  name: selectedApp.name
@@ -762,8 +831,8 @@ async function resolveDomainAppSelection(context, projectId, apps, options) {
762
831
  if (selectedApp) return selectedApp;
763
832
  throw usageError("Custom domain requires an existing app on the production branch", "The resolved production branch does not have an app that can receive a custom domain.", "Deploy or promote an app to production first, then rerun the domain command.", ["prisma-cli app deploy --branch production", "prisma-cli app show"], "app");
764
833
  }
765
- async function resolveDomainByHostname(provider, appId, hostname, command) {
766
- const matched = (await provider.listDomains(appId).catch((error) => {
834
+ async function resolveDomainByHostname(provider, appId, hostname, command, signal) {
835
+ const matched = (await provider.listDomains(appId, { signal }).catch((error) => {
767
836
  throw domainCommandError(command, error, hostname);
768
837
  })).find((domain) => sameDomainHostname(domain.hostname, hostname));
769
838
  if (matched) return matched;
@@ -994,9 +1063,20 @@ function formatElapsed(milliseconds) {
994
1063
  const remainingSeconds = seconds % 60;
995
1064
  return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
996
1065
  }
997
- async function sleep(milliseconds) {
1066
+ async function sleep(milliseconds, signal) {
998
1067
  if (milliseconds <= 0) return;
999
- await new Promise((resolve) => setTimeout(resolve, milliseconds));
1068
+ signal.throwIfAborted();
1069
+ await new Promise((resolve, reject) => {
1070
+ const onAbort = () => {
1071
+ clearTimeout(timeout);
1072
+ reject(signal.reason);
1073
+ };
1074
+ const timeout = setTimeout(() => {
1075
+ signal.removeEventListener("abort", onAbort);
1076
+ resolve();
1077
+ }, milliseconds);
1078
+ signal.addEventListener("abort", onAbort, { once: true });
1079
+ });
1000
1080
  }
1001
1081
  async function resolveDeployAppSelection(context, projectId, apps, options) {
1002
1082
  if (options.explicitAppName) {
@@ -1211,7 +1291,10 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
1211
1291
  });
1212
1292
  }
1213
1293
  async function listApps(context, provider, projectId, branchName) {
1214
- return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
1294
+ return provider.listApps(projectId, {
1295
+ branchName,
1296
+ signal: context.runtime.signal
1297
+ }).then(sortApps).catch((error) => {
1215
1298
  if (isMissingProjectError(error)) throw new CliError({
1216
1299
  code: "PROJECT_NOT_FOUND",
1217
1300
  domain: "project",
@@ -1229,20 +1312,20 @@ async function requirePreviewAppProvider(context) {
1229
1312
  return provider;
1230
1313
  }
1231
1314
  async function requirePreviewAppProviderWithClient(context) {
1232
- const client = await requireComputeAuth(context.runtime.env);
1315
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
1233
1316
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
1234
1317
  return {
1235
1318
  client,
1236
- provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
1319
+ provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env, context.runtime.signal))
1237
1320
  };
1238
1321
  }
1239
- function createPreviewLogAuthOptions(env) {
1322
+ function createPreviewLogAuthOptions(env, signal) {
1240
1323
  const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
1241
1324
  if (rawToken) return {
1242
1325
  baseUrl: getApiBaseUrl(env),
1243
1326
  getToken: async () => rawToken
1244
1327
  };
1245
- const tokenStorage = new FileTokenStorage(env);
1328
+ const tokenStorage = new FileTokenStorage(env, signal);
1246
1329
  return {
1247
1330
  baseUrl: getApiBaseUrl(env),
1248
1331
  getToken: async () => {
@@ -1275,18 +1358,21 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
1275
1358
  async function resolveProjectContext(context, client, explicitProject, options) {
1276
1359
  const authState = await requireAuthenticatedAuthState(context);
1277
1360
  if (!authState.workspace) throw workspaceRequiredError();
1278
- const resolved = await resolveProjectTarget({
1361
+ const resolvedResult = await resolveProjectTarget({
1279
1362
  context,
1280
1363
  workspace: authState.workspace,
1281
1364
  explicitProject,
1282
1365
  envProjectId: options?.envProjectId,
1283
- listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
1366
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
1284
1367
  commandName: options?.commandName
1285
1368
  });
1369
+ if (resolvedResult.isErr()) throw projectResolutionErrorToCliError(resolvedResult.error);
1370
+ const resolved = resolvedResult.value;
1286
1371
  const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
1287
1372
  return {
1288
1373
  ...resolved,
1289
1374
  branch: {
1375
+ id: null,
1290
1376
  name: branch.name,
1291
1377
  kind: toBranchKind(branch.name)
1292
1378
  }
@@ -1296,8 +1382,8 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1296
1382
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1297
1383
  if (!workspace) throw workspaceRequiredError();
1298
1384
  const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1299
- const projects = await listRealWorkspaceProjects(client, workspace);
1300
- if (explicitProject) return withDeployBranch({
1385
+ const projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
1386
+ if (explicitProject) return withRemoteDeployBranch(provider, {
1301
1387
  workspace,
1302
1388
  project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
1303
1389
  resolution: {
@@ -1306,25 +1392,25 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1306
1392
  targetNameSource: "explicit"
1307
1393
  },
1308
1394
  localPinAction: "linked"
1309
- }, branch);
1395
+ }, branch, context.runtime.signal);
1310
1396
  if (options.createProjectName) {
1311
1397
  const projectName = options.createProjectName.trim();
1312
1398
  if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1313
- return withDeployBranch({
1399
+ return withRemoteDeployBranch(provider, {
1314
1400
  workspace,
1315
- project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace)),
1401
+ project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
1316
1402
  resolution: {
1317
1403
  projectSource: "created",
1318
1404
  targetName: projectName,
1319
1405
  targetNameSource: "explicit"
1320
1406
  },
1321
1407
  localPinAction: "created"
1322
- }, branch);
1408
+ }, branch, context.runtime.signal);
1323
1409
  }
1324
1410
  if (options.envProjectId) {
1325
1411
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
1326
1412
  if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1327
- return withDeployBranch({
1413
+ return withRemoteDeployBranch(provider, {
1328
1414
  workspace,
1329
1415
  project: toProjectSummary(project),
1330
1416
  resolution: {
@@ -1332,14 +1418,14 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1332
1418
  targetName: options.envProjectId,
1333
1419
  targetNameSource: "env"
1334
1420
  }
1335
- }, branch);
1421
+ }, branch, context.runtime.signal);
1336
1422
  }
1337
1423
  const localPin = options.localPin;
1338
1424
  if (localPin.kind === "present") {
1339
1425
  if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1340
1426
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1341
1427
  if (!project) throw localResolutionPinStaleError();
1342
- return withDeployBranch({
1428
+ return withRemoteDeployBranch(provider, {
1343
1429
  workspace,
1344
1430
  project: toProjectSummary(project),
1345
1431
  resolution: {
@@ -1347,10 +1433,10 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1347
1433
  targetName: project.name,
1348
1434
  targetNameSource: "local-pin"
1349
1435
  }
1350
- }, branch);
1436
+ }, branch, context.runtime.signal);
1351
1437
  }
1352
1438
  const platformMapping = await resolveDurablePlatformMapping();
1353
- if (platformMapping && platformMapping.workspace.id === workspace.id) return withDeployBranch({
1439
+ if (platformMapping && platformMapping.workspace.id === workspace.id) return withRemoteDeployBranch(provider, {
1354
1440
  workspace,
1355
1441
  project: toProjectSummary(platformMapping),
1356
1442
  resolution: {
@@ -1358,15 +1444,15 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1358
1444
  targetName: platformMapping.name,
1359
1445
  targetNameSource: "platform-mapping"
1360
1446
  }
1361
- }, branch);
1362
- if (canPrompt(context) && !context.flags.yes) return withDeployBranch(await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch);
1363
- throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd));
1447
+ }, branch, context.runtime.signal);
1448
+ if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch, context.runtime.signal);
1449
+ throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1364
1450
  }
1365
1451
  async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
1366
1452
  const setup = await promptForProjectSetupChoice({
1367
1453
  context,
1368
1454
  projects,
1369
- createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace),
1455
+ createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal),
1370
1456
  cancel: {
1371
1457
  why: "Deploy needs a Project before it can continue.",
1372
1458
  fix: "Choose an existing Project or create a new one, then rerun deploy.",
@@ -1384,8 +1470,11 @@ async function resolveInteractiveDeployProjectSetup(context, provider, workspace
1384
1470
  localPinAction: setup.action
1385
1471
  };
1386
1472
  }
1387
- async function createProjectForDeploySetup(provider, projectName, workspace) {
1388
- const created = await provider.createProject({ name: projectName }).catch((error) => {
1473
+ async function createProjectForDeploySetup(provider, projectName, workspace, signal) {
1474
+ const created = await provider.createProject({
1475
+ name: projectName,
1476
+ signal
1477
+ }).catch((error) => {
1389
1478
  throw projectCreateFailedError(error, projectName, workspace, {
1390
1479
  nextSteps: [
1391
1480
  "prisma-cli project list",
@@ -1402,18 +1491,46 @@ async function createProjectForDeploySetup(provider, projectName, workspace) {
1402
1491
  workspace
1403
1492
  };
1404
1493
  }
1405
- function withDeployBranch(target, branch) {
1494
+ async function withRemoteDeployBranch(provider, target, branch, signal) {
1495
+ const remoteBranch = await provider.resolveBranch(target.project.id, {
1496
+ branchName: branch.name,
1497
+ signal
1498
+ });
1406
1499
  return {
1407
1500
  ...target,
1408
1501
  branch: {
1409
- name: branch.name,
1410
- kind: toBranchKind(branch.name)
1502
+ id: remoteBranch.id,
1503
+ name: remoteBranch.name,
1504
+ kind: remoteBranch.role
1411
1505
  }
1412
1506
  };
1413
1507
  }
1414
1508
  function toBranchKind(name) {
1415
1509
  return name === "production" || name === "main" ? "production" : "preview";
1416
1510
  }
1511
+ function toResultBranch(branch) {
1512
+ return {
1513
+ id: branch.id,
1514
+ name: branch.name,
1515
+ kind: branch.kind
1516
+ };
1517
+ }
1518
+ function toAppVerboseContext(target) {
1519
+ return {
1520
+ workspace: target.workspace,
1521
+ project: target.project,
1522
+ branch: target.branch,
1523
+ resolution: target.resolution
1524
+ };
1525
+ }
1526
+ function toBranchDatabaseDeployBranch(branch) {
1527
+ if (!branch.id) throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);
1528
+ return {
1529
+ id: branch.id,
1530
+ name: branch.name,
1531
+ kind: branch.kind
1532
+ };
1533
+ }
1417
1534
  function assertExclusiveDeployProjectInputs(options) {
1418
1535
  const provided = [
1419
1536
  options.projectRef ? "--project" : null,
@@ -1432,7 +1549,7 @@ async function resolveDeployBranch(context, explicitBranchName) {
1432
1549
  name: explicitBranchName,
1433
1550
  annotation: "set by --branch"
1434
1551
  };
1435
- const gitBranch = await readLocalGitBranch(context.runtime.cwd);
1552
+ const gitBranch = await readLocalGitBranch(context.runtime.cwd, context.runtime.signal);
1436
1553
  if (gitBranch) return {
1437
1554
  name: gitBranch,
1438
1555
  annotation: "from local Git branch"
@@ -1442,29 +1559,6 @@ async function resolveDeployBranch(context, explicitBranchName) {
1442
1559
  annotation: "default"
1443
1560
  };
1444
1561
  }
1445
- async function readLocalGitBranch(cwd) {
1446
- const headPath = await resolveGitHeadPath(path.join(cwd, ".git"));
1447
- if (!headPath) return null;
1448
- try {
1449
- const head = (await readFile(headPath, "utf8")).trim();
1450
- if (head.startsWith("ref: refs/heads/")) return head.slice(16);
1451
- } catch {
1452
- return null;
1453
- }
1454
- return null;
1455
- }
1456
- async function resolveGitHeadPath(gitPath) {
1457
- try {
1458
- const raw = await readFile(gitPath, "utf8");
1459
- if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
1460
- } catch {}
1461
- try {
1462
- await access(path.join(gitPath, "HEAD"));
1463
- return path.join(gitPath, "HEAD");
1464
- } catch {
1465
- return null;
1466
- }
1467
- }
1468
1562
  async function resolveDeployFramework(context, options) {
1469
1563
  if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1470
1564
  if (options.entrypoint) return {
@@ -1473,7 +1567,7 @@ async function resolveDeployFramework(context, options) {
1473
1567
  displayName: "Bun",
1474
1568
  annotation: "set by --entry"
1475
1569
  };
1476
- const detected = await detectDeployFramework(context.runtime.cwd);
1570
+ const detected = await detectDeployFramework(context.runtime.cwd, context.runtime.signal);
1477
1571
  if (detected) return detected;
1478
1572
  throw frameworkNotDetectedError(context.runtime.cwd);
1479
1573
  }
@@ -1491,23 +1585,26 @@ function assertSupportedEntrypointForRequestedDeployShape(options) {
1491
1585
  if (!options.requestedFramework) return;
1492
1586
  assertSupportedEntrypoint(frameworkFromUserFacingValue(options.requestedFramework, "set by --framework").buildType, options.entrypoint, "deploy");
1493
1587
  }
1494
- async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint) {
1588
+ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signal) {
1495
1589
  if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
1496
- const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd));
1590
+ const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd, signal));
1497
1591
  if (packageEntrypoint) return packageEntrypoint;
1498
1592
  if (framework.key !== "hono") return;
1499
1593
  const defaultEntrypoint = "src/index.ts";
1594
+ signal.throwIfAborted();
1500
1595
  try {
1501
1596
  await access(path.join(cwd, defaultEntrypoint));
1597
+ signal.throwIfAborted();
1502
1598
  return defaultEntrypoint;
1503
1599
  } catch (error) {
1600
+ if (signal.aborted) throw error;
1504
1601
  if (error.code !== "ENOENT") throw error;
1505
1602
  return;
1506
1603
  }
1507
1604
  }
1508
- async function detectDeployFramework(cwd) {
1509
- const packageJson = await readBunPackageJson(cwd);
1510
- const nextConfig = await detectNextConfig(cwd);
1605
+ async function detectDeployFramework(cwd, signal) {
1606
+ const packageJson = await readBunPackageJson(cwd, signal);
1607
+ const nextConfig = await detectNextConfig(cwd, signal);
1511
1608
  if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
1512
1609
  key: "nextjs",
1513
1610
  buildType: "nextjs",
@@ -1528,22 +1625,26 @@ async function detectDeployFramework(cwd) {
1528
1625
  };
1529
1626
  return null;
1530
1627
  }
1531
- async function detectNextConfig(cwd) {
1628
+ async function detectNextConfig(cwd, signal) {
1532
1629
  for (const candidate of [
1533
1630
  "next.config.js",
1534
1631
  "next.config.mjs",
1535
- "next.config.cjs",
1536
1632
  "next.config.ts",
1537
1633
  "next.config.mts"
1538
1634
  ]) {
1539
1635
  const filePath = path.join(cwd, candidate);
1636
+ signal.throwIfAborted();
1540
1637
  try {
1541
- const content = await readFile(filePath, "utf8");
1638
+ const content = await readFile(filePath, {
1639
+ encoding: "utf8",
1640
+ signal
1641
+ });
1542
1642
  return {
1543
1643
  exists: true,
1544
1644
  standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
1545
1645
  };
1546
1646
  } catch (error) {
1647
+ if (signal.aborted) throw error;
1547
1648
  if (error.code !== "ENOENT") throw error;
1548
1649
  }
1549
1650
  }
@@ -1620,6 +1721,20 @@ async function maybeRenderDeploySetupBlock(context, details) {
1620
1721
  const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
1621
1722
  context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1622
1723
  }
1724
+ function maybeRenderDeployBuildSettings(context, resolution) {
1725
+ if (context.flags.json || context.flags.quiet) return;
1726
+ const settings = resolution.settings;
1727
+ const title = resolution.status === "created" ? `Created ${resolution.relativeConfigPath}` : `Using ${resolution.relativeConfigPath}`;
1728
+ context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [{
1729
+ label: "Build Command",
1730
+ value: settings.buildCommand ?? "none",
1731
+ origin: settings.buildCommandSource ?? void 0
1732
+ }, {
1733
+ label: "Output Directory",
1734
+ value: settings.outputDirectory,
1735
+ origin: settings.outputDirectorySource ?? void 0
1736
+ }]).join("\n")}\n\n`);
1737
+ }
1623
1738
  function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
1624
1739
  if (context.flags.json || context.flags.quiet) return;
1625
1740
  context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
@@ -1629,10 +1744,14 @@ async function maybeCustomizeDeploySettings(context, options) {
1629
1744
  framework: options.framework,
1630
1745
  runtime: options.runtime
1631
1746
  };
1747
+ maybeRenderDeploySettingsPreview(context, {
1748
+ framework: options.framework,
1749
+ runtime: options.runtime
1750
+ });
1632
1751
  if (!await confirmPrompt({
1633
1752
  input: context.runtime.stdin,
1634
1753
  output: context.runtime.stderr,
1635
- message: "Customize settings?",
1754
+ message: "Customize build settings?",
1636
1755
  initialValue: false
1637
1756
  })) return {
1638
1757
  framework: options.framework,
@@ -1677,6 +1796,16 @@ async function maybeCustomizeDeploySettings(context, options) {
1677
1796
  runtime
1678
1797
  };
1679
1798
  }
1799
+ function maybeRenderDeploySettingsPreview(context, options) {
1800
+ if (context.flags.quiet || context.flags.json) return;
1801
+ context.output.stderr.write(`Detected ${options.framework.displayName}\n${renderDeploySettingsPreview(context.ui, [{
1802
+ key: "framework",
1803
+ value: options.framework.displayName
1804
+ }, {
1805
+ key: "runtime",
1806
+ value: `HTTP ${options.runtime.port}`
1807
+ }]).join("\n")}\n\n`);
1808
+ }
1680
1809
  function frameworkDisplayName(framework) {
1681
1810
  switch (framework) {
1682
1811
  case "nextjs": return "Next.js";
@@ -1701,7 +1830,7 @@ function formatDeployDirectory(cwd) {
1701
1830
  async function readCurrentWorkspaceId(context) {
1702
1831
  const state = await context.stateStore.read();
1703
1832
  if (state.auth?.workspaceId) return state.auth.workspaceId;
1704
- return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
1833
+ return (await readAuthState(context.runtime.env, context.runtime.signal)).workspace?.id ?? null;
1705
1834
  }
1706
1835
  function normalizeBuildType(requestedBuildType) {
1707
1836
  if (!requestedBuildType) return "auto";
@@ -1723,7 +1852,7 @@ function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
1723
1852
  }
1724
1853
  }
1725
1854
  async function requireLocalBuildType(context, buildType, commandName) {
1726
- const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
1855
+ const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType, context.runtime.signal);
1727
1856
  if (resolvedBuildType) return resolvedBuildType;
1728
1857
  throw usageError(`App ${commandName} requires an explicit framework when detection is ambiguous`, "This preview only starts local dev servers for clear Next.js or Bun project shapes.", "Pass --build-type nextjs for a Next.js app, or pass --build-type bun with --entry <path> for a Bun app.", [`prisma-cli app ${commandName} --build-type nextjs`, `prisma-cli app ${commandName} --build-type bun --entry server.ts`], "app");
1729
1858
  }
@@ -1852,6 +1981,18 @@ function localResolutionPinStaleError() {
1852
1981
  ]
1853
1982
  });
1854
1983
  }
1984
+ function localPinReadErrorToDeployError(error) {
1985
+ return matchError(error, {
1986
+ LocalResolutionPinInvalidJsonError: () => localResolutionPinStaleError(),
1987
+ LocalResolutionPinInvalidShapeError: () => localResolutionPinStaleError(),
1988
+ LocalResolutionPinReadAbortedError: (error) => {
1989
+ throw error;
1990
+ },
1991
+ UnhandledException: (error) => {
1992
+ throw error;
1993
+ }
1994
+ });
1995
+ }
1855
1996
  function readDeployEnvOverride(context, name) {
1856
1997
  const value = context.runtime.env[name]?.trim();
1857
1998
  return value ? value : void 0;