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

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 (66) 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 +64 -23
  6. package/dist/cli.js +18 -3
  7. package/dist/cli2.js +9 -5
  8. package/dist/commands/app/index.js +84 -59
  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 +286 -131
  15. package/dist/controllers/app.js +699 -244
  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 +156 -85
  20. package/dist/lib/app/branch-database-deploy.js +325 -0
  21. package/dist/lib/app/branch-database.js +215 -0
  22. package/dist/lib/app/bun-project.js +12 -5
  23. package/dist/lib/app/compute-config.js +147 -0
  24. package/dist/lib/app/deploy-output.js +10 -1
  25. package/dist/lib/app/deploy-plan.js +58 -0
  26. package/dist/lib/app/env-config.js +1 -1
  27. package/dist/lib/app/env-file.js +82 -0
  28. package/dist/lib/app/env-vars.js +28 -2
  29. package/dist/lib/app/local-dev.js +8 -49
  30. package/dist/lib/app/preview-branch-database.js +102 -0
  31. package/dist/lib/app/preview-build-settings.js +376 -0
  32. package/dist/lib/app/preview-build.js +314 -97
  33. package/dist/lib/app/preview-provider.js +178 -65
  34. package/dist/lib/app/production-deploy-gate.js +161 -0
  35. package/dist/lib/auth/auth-ops.js +30 -21
  36. package/dist/lib/auth/guard.js +3 -2
  37. package/dist/lib/auth/login.js +116 -14
  38. package/dist/lib/database/provider.js +167 -0
  39. package/dist/lib/diagnostics.js +15 -0
  40. package/dist/lib/fs/home-path.js +24 -0
  41. package/dist/lib/git/local-branch.js +53 -0
  42. package/dist/lib/git/local-status.js +57 -0
  43. package/dist/lib/project/interactive-setup.js +1 -1
  44. package/dist/lib/project/local-pin.js +182 -33
  45. package/dist/lib/project/resolution.js +204 -50
  46. package/dist/lib/project/setup.js +66 -19
  47. package/dist/output/patterns.js +1 -1
  48. package/dist/presenters/app-env.js +149 -14
  49. package/dist/presenters/app.js +178 -23
  50. package/dist/presenters/branch.js +37 -102
  51. package/dist/presenters/database.js +274 -0
  52. package/dist/presenters/project.js +57 -39
  53. package/dist/presenters/verbose-context.js +64 -0
  54. package/dist/shell/command-meta.js +103 -13
  55. package/dist/shell/command-runner.js +57 -19
  56. package/dist/shell/diagnostics-output.js +57 -0
  57. package/dist/shell/errors.js +12 -1
  58. package/dist/shell/help.js +30 -20
  59. package/dist/shell/output.js +10 -1
  60. package/dist/shell/runtime.js +11 -7
  61. package/dist/shell/ui.js +42 -3
  62. package/dist/shell/update-check.js +247 -0
  63. package/dist/use-cases/branch.js +20 -68
  64. package/dist/use-cases/create-cli-gateways.js +2 -17
  65. package/dist/use-cases/project.js +2 -1
  66. package/package.json +26 -10
@@ -1,54 +1,79 @@
1
1
  import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
2
  import { FileTokenStorage } from "../adapters/token-storage.js";
3
3
  import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
4
+ import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
4
5
  import { renderCommandHeader } from "../shell/ui.js";
5
- import { writeJsonEvent } from "../shell/output.js";
6
6
  import { canPrompt } from "../shell/runtime.js";
7
- import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
8
- import { requireComputeAuth } from "../lib/auth/guard.js";
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";
12
- import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
13
- import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
14
- import { formatCommandArgument } from "../shell/command-arguments.js";
15
7
  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";
18
- import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
8
+ import { formatCommandArgument } from "../shell/command-arguments.js";
9
+ import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
10
+ import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
11
+ import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
12
+ import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
13
+ import { COMPUTE_CONFIG_FILENAME as COMPUTE_CONFIG_FILENAME$1, ComputeConfigTargetRequiredError, computeConfigErrorToCliError, computeFrameworkToBuildType, computeTargetAppDir, inferComputeTargetFromCwd, loadComputeConfig as loadComputeConfig$1, mergeComputeDeployInputs, mergeComputeLocalInputs, selectComputeDeployTarget } from "../lib/app/compute-config.js";
14
+ import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
15
+ import { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy } from "../lib/app/deploy-plan.js";
16
+ import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
17
+ import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
18
+ import { DEFAULT_LOCAL_DEV_PORT, runLocalApp } from "../lib/app/local-dev.js";
19
+ import { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredPreviewBuildSettings, resolveInferredPreviewBuildSettings } from "../lib/app/preview-build-settings.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";
21
22
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
22
23
  import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
23
- import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
24
+ import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
25
+ import { requireComputeAuth } from "../lib/auth/guard.js";
26
+ import { readAuthState } from "../lib/auth/auth-ops.js";
27
+ import { readLocalGitBranch } from "../lib/git/local-branch.js";
28
+ import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
29
+ import { writeJsonEvent } from "../shell/output.js";
24
30
  import { createSelectPromptPort } from "./select-prompt-port.js";
25
31
  import { requireAuthenticatedAuthState } from "./auth.js";
26
32
  import { listRealWorkspaceProjects } from "./project.js";
33
+ import { ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, frameworkByKey, frameworkFromAlias, isConfigBackedBuildType } from "@prisma/compute-sdk/config";
27
34
  import { access, readFile } from "node:fs/promises";
28
35
  import path from "node:path";
36
+ import { Result, matchError } from "better-result";
29
37
  import open from "open";
30
38
  //#region src/controllers/app.ts
31
- const DEPLOY_FRAMEWORKS = [
32
- "nextjs",
33
- "hono",
34
- "tanstack-start",
35
- "bun"
36
- ];
37
- const TANSTACK_START_PACKAGES = ["@tanstack/react-start", "@tanstack/solid-start"];
38
39
  const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
39
40
  const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
40
41
  const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
41
42
  function isRealMode(context) {
42
43
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
43
44
  }
44
- async function runAppBuild(context, entrypoint, requestedBuildType) {
45
- const buildType = normalizeBuildType(requestedBuildType);
46
- assertSupportedEntrypoint(buildType, entrypoint, "build");
45
+ async function runAppBuild(context, options) {
46
+ const compute = await resolveComputeTargetOrThrow(context, options?.configTarget, "build");
47
+ const merged = mergeComputeLocalInputs({
48
+ cli: {
49
+ entrypoint: options?.entrypoint,
50
+ buildType: options?.buildType
51
+ },
52
+ target: compute.target
53
+ });
54
+ const appDir = await resolveComputeAppDir(context, compute);
55
+ let buildType = normalizeBuildType(merged.buildType);
56
+ if (compute.target?.build && buildType === "auto") {
57
+ const detected = await detectDeployFramework(appDir, context.runtime.signal);
58
+ if (!detected) throw frameworkNotDetectedError(appDir);
59
+ buildType = detected.buildType;
60
+ }
61
+ assertSupportedEntrypoint(buildType, merged.entrypoint, "build");
62
+ if (compute.target?.build && buildType !== "auto") assertConfigBackedBuildSettings(buildType);
63
+ const buildSettings = compute.config && compute.target?.build && isConfigBackedBuildType(buildType) ? (await resolveConfiguredPreviewBuildSettings({
64
+ appPath: appDir,
65
+ buildType,
66
+ configured: compute.target.build,
67
+ configPath: compute.config.configPath,
68
+ signal: context.runtime.signal
69
+ })).settings : void 0;
47
70
  try {
48
71
  const { artifact, buildType: actualBuildType } = await executePreviewBuild({
49
- appPath: context.runtime.cwd,
50
- entrypoint,
51
- buildType
72
+ appPath: appDir,
73
+ entrypoint: merged.entrypoint,
74
+ buildType,
75
+ buildSettings,
76
+ signal: context.runtime.signal
52
77
  });
53
78
  return {
54
79
  command: "app.build",
@@ -65,25 +90,42 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
65
90
  throw buildFailedError("Local app build failed", error);
66
91
  }
67
92
  }
68
- async function runAppRun(context, entrypoint, requestedBuildType, requestedPort) {
93
+ async function runAppRun(context, options) {
69
94
  if (context.flags.json) throw usageError("App run does not support --json", "This command streams the framework dev server directly and cannot return structured JSON.", "Rerun without --json to pass framework logs through directly.", ["prisma-cli app run"], "app");
70
- const buildType = normalizeBuildType(requestedBuildType);
71
- assertSupportedEntrypoint(buildType, entrypoint, "run");
72
- const port = parseLocalPort(requestedPort);
73
- const resolvedBuildType = await requireLocalBuildType(context, buildType, "run");
95
+ const compute = await resolveComputeTargetOrThrow(context, options?.configTarget, "run");
96
+ const merged = mergeComputeLocalInputs({
97
+ cli: {
98
+ entrypoint: options?.entrypoint,
99
+ buildType: options?.buildType,
100
+ port: options?.port
101
+ },
102
+ target: compute.target
103
+ });
104
+ if (merged.buildTypeFromConfig && compute.target?.framework && !frameworkByKey(compute.target.framework).hasLocalDevServer) throw usageError(`App run does not support the ${compute.target?.framework} framework yet`, `${compute.config?.relativeConfigPath ?? COMPUTE_CONFIG_FILENAME$1} sets a framework that has no local dev server in the current preview.`, "Run the framework dev server directly, or pass --build-type nextjs or --build-type bun to override.", ["prisma-cli app run --build-type nextjs", "prisma-cli app run --build-type bun --entry server.ts"], "app");
105
+ const appDir = await resolveComputeAppDir(context, compute);
106
+ const buildType = normalizeBuildType(merged.buildType);
107
+ assertSupportedEntrypoint(buildType, merged.entrypoint, "run");
108
+ const port = parseLocalPort(merged.port);
109
+ const framework = await resolveLocalRunFramework(context, {
110
+ requestedBuildType: buildType,
111
+ configFramework: compute.target?.framework ?? null,
112
+ appDir
113
+ });
114
+ const entrypoint = framework.buildType === "bun" ? await resolveDeployEntrypoint(appDir, framework, merged.entrypoint, context.runtime.signal) : merged.entrypoint;
74
115
  let runResult;
75
116
  try {
76
117
  runResult = await runLocalApp({
77
- appPath: context.runtime.cwd,
78
- buildType: resolvedBuildType,
118
+ appPath: appDir,
119
+ buildType: framework.buildType,
79
120
  entrypoint,
80
121
  port,
81
- env: context.runtime.env
122
+ env: context.runtime.env,
123
+ signal: context.runtime.signal
82
124
  });
83
125
  } catch (error) {
84
126
  throw runFailedError("Local app run failed", error);
85
127
  }
86
- if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") process.exitCode = runResult.signal === "SIGINT" ? 130 : 143;
128
+ if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") throw new DOMException("Command canceled", "AbortError");
87
129
  else if (runResult.exitCode !== 0) throw runFailedError("Local app run failed", `The ${formatFrameworkName(runResult.framework)} process exited with code ${runResult.exitCode}.`, runResult.exitCode);
88
130
  return {
89
131
  command: "app.run",
@@ -99,6 +141,106 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
99
141
  }
100
142
  async function runAppDeploy(context, appName, options) {
101
143
  ensurePreviewAppMode(context);
144
+ const loaded = await loadComputeConfig$1(context.runtime.cwd, context.runtime.signal);
145
+ if (loaded.isErr()) throw computeConfigErrorToCliError(loaded.error, "deploy");
146
+ const config = loaded.value;
147
+ const plan = planAppDeploy({
148
+ config,
149
+ requestedTarget: options?.configTarget ?? (config ? inferComputeTargetFromCwd(config, context.runtime.cwd) : void 0),
150
+ hasCreateProject: options?.createProjectName !== void 0
151
+ });
152
+ if (plan.mode === "all") return runAppDeployAll(context, config, plan.targets, appName, options);
153
+ return runSingleAppDeploy(context, appName, options, config);
154
+ }
155
+ async function runAppDeployAll(context, config, plannedTargets, appName, options) {
156
+ assertNoPerAppInputsForDeployAll(context, config, appName, options);
157
+ const deployments = [];
158
+ const warnings = [];
159
+ for (const planned of plannedTargets) {
160
+ maybeRenderDeployAllTargetHeader(context, planned);
161
+ const targetOptions = {
162
+ ...options,
163
+ configTarget: planned.targetKey,
164
+ createProjectName: planned.bindsCreateProject ? options?.createProjectName : void 0
165
+ };
166
+ try {
167
+ const single = await runSingleAppDeploy(context, void 0, targetOptions, config);
168
+ deployments.push({
169
+ target: planned.targetKey,
170
+ result: single.result
171
+ });
172
+ warnings.push(...single.warnings);
173
+ } catch (error) {
174
+ throw deployAllFailedError(error, config, planned.index, deployments);
175
+ }
176
+ }
177
+ return {
178
+ command: "app.deploy",
179
+ result: { deployments },
180
+ warnings,
181
+ nextSteps: ["prisma-cli app list-deploys <app>"]
182
+ };
183
+ }
184
+ function assertNoPerAppInputsForDeployAll(context, config, appName, options) {
185
+ const used = perAppInputsForDeployAll({
186
+ appName,
187
+ framework: options?.framework,
188
+ entrypoint: options?.entrypoint,
189
+ httpPort: options?.httpPort,
190
+ envAssignments: options?.envAssignments,
191
+ appIdEnvVar: {
192
+ name: PRISMA_APP_ID_ENV_VAR,
193
+ value: readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR)
194
+ }
195
+ });
196
+ if (used.length === 0) return;
197
+ const targets = config.targets.map((target) => target.key).join(", ");
198
+ throw usageError(`Deploying all apps does not accept ${used.join(", ")}`, `Without a target, app deploy deploys every configured app (${targets}), so per-app inputs are ambiguous.`, "Pass the app target to apply per-app inputs to one app, or remove them to deploy all apps.", config.targets.map((target) => `prisma-cli app deploy ${target.key}`), "app");
199
+ }
200
+ function maybeRenderDeployAllTargetHeader(context, planned) {
201
+ if (context.flags.json || context.flags.quiet) return;
202
+ context.output.stderr.write(`${planned.index > 0 ? "\n" : ""}── ${planned.targetKey} (${planned.index + 1}/${planned.total}) ──\n\n`);
203
+ }
204
+ function deployAllFailedError(error, config, failedIndex, deployments) {
205
+ if (!(error instanceof CliError)) return error;
206
+ const failure = describeDeployAllFailure({
207
+ targetKeys: config.targets.map((target) => target.key),
208
+ failedIndex,
209
+ completed: deployments.map(({ target, result }) => ({
210
+ target,
211
+ deploymentId: result.deployment.id,
212
+ url: result.deployment.url
213
+ }))
214
+ });
215
+ const contextSentence = failure.contextLines.join(" ");
216
+ return new CliError({
217
+ code: error.code,
218
+ domain: error.domain,
219
+ summary: error.summary,
220
+ why: error.humanLines ? error.why : [error.why, contextSentence].filter(Boolean).join(" "),
221
+ fix: error.fix,
222
+ debug: error.debug,
223
+ where: error.where,
224
+ meta: {
225
+ ...error.meta,
226
+ deployAll: {
227
+ failedTarget: failure.failedTarget,
228
+ completed: failure.completed,
229
+ notAttempted: failure.notAttempted
230
+ }
231
+ },
232
+ docsUrl: error.docsUrl,
233
+ exitCode: error.exitCode,
234
+ nextSteps: error.nextSteps,
235
+ nextActions: error.nextActions,
236
+ humanLines: error.humanLines ? [
237
+ ...error.humanLines,
238
+ "",
239
+ ...failure.contextLines
240
+ ] : void 0
241
+ });
242
+ }
243
+ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
102
244
  const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
103
245
  const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
104
246
  assertExclusiveDeployProjectInputs({
@@ -106,14 +248,27 @@ async function runAppDeploy(context, appName, options) {
106
248
  createProjectName: options?.createProjectName,
107
249
  envProjectId
108
250
  });
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();
251
+ const computeConfig = await resolveComputeTargetOrThrow(context, options?.configTarget, "deploy", { preloaded: preloadedConfig });
252
+ const merged = mergeComputeDeployInputs({
253
+ cli: {
254
+ framework: options?.framework,
255
+ entrypoint: options?.entrypoint,
256
+ httpPort: options?.httpPort,
257
+ envInputs: options?.envAssignments
258
+ },
259
+ target: computeConfig.target,
260
+ configFilename: computeConfig.config?.relativeConfigPath ?? COMPUTE_CONFIG_FILENAME$1
261
+ });
262
+ const appDir = await resolveComputeAppDir(context, computeConfig);
263
+ const projectDir = computeConfig.config?.configDir ?? context.runtime.cwd;
264
+ const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(projectDir, context.runtime.signal);
265
+ if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
266
+ const localPin = localPinReadResult.value;
112
267
  const branch = await resolveDeployBranch(context, options?.branchName);
113
- if (options?.httpPort) parseDeployHttpPort(options.httpPort);
268
+ if (merged.httpPort) parseDeployHttpPort(merged.httpPort.value);
114
269
  assertSupportedEntrypointForRequestedDeployShape({
115
- requestedFramework: options?.framework,
116
- entrypoint: options?.entrypoint
270
+ requestedFramework: merged.framework?.value,
271
+ entrypoint: merged.entrypoint?.value
117
272
  });
118
273
  const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
119
274
  branch,
@@ -123,25 +278,32 @@ async function runAppDeploy(context, appName, options) {
123
278
  });
124
279
  let localPinResult;
125
280
  if (target.localPinAction) {
126
- 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);
281
+ const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction, projectDir);
282
+ if (setupResult.isErr()) throw projectDirectoryBindingErrorToCliError(setupResult.error);
283
+ const projectSetup = setupResult.value;
284
+ localPinResult = projectSetup.localPin;
285
+ maybeRenderProjectLinked(context, projectSetup.directory, projectSetup.project.name, projectSetup.localPin.path);
129
286
  }
130
287
  let framework = await resolveDeployFramework(context, {
131
- requestedFramework: options?.framework,
132
- entrypoint: options?.entrypoint
133
- });
134
- let runtime = resolveDeployRuntime(options?.httpPort, framework);
135
- assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
136
- const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
288
+ requestedFramework: merged.framework?.value,
289
+ requestedFrameworkAnnotation: merged.framework?.annotation,
290
+ entrypoint: merged.entrypoint?.value,
291
+ entrypointAnnotation: merged.entrypoint?.annotation,
292
+ appDir
293
+ });
294
+ let runtime = resolveDeployRuntime(merged.httpPort?.value, merged.httpPort?.annotation, framework);
295
+ assertSupportedEntrypoint(framework.buildType, merged.entrypoint?.value, "deploy");
296
+ const envVars = toOptionalEnvVars(await parseEnvInputs(merged.envInputsFromConfig ? projectDir : context.runtime.cwd, merged.envInputs, { commandName: "deploy" }));
137
297
  const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
138
298
  explicitAppName: appName,
139
299
  explicitAppId: envAppId,
300
+ configAppName: merged.configAppName,
140
301
  firstDeploy: Boolean(target.localPinAction),
141
- inferName: () => inferTargetName(context.runtime.cwd)
302
+ inferName: () => inferTargetName(appDir, context.runtime.signal)
142
303
  });
143
304
  await maybeRenderDeploySetupBlock(context, {
144
305
  includeDirectory: !target.localPinAction,
306
+ appDir,
145
307
  projectName: target.project.name,
146
308
  branchName: target.branch.name,
147
309
  appName: selectedApp.displayName
@@ -150,20 +312,46 @@ async function runAppDeploy(context, appName, options) {
150
312
  framework,
151
313
  runtime,
152
314
  firstDeploy: selectedApp.firstDeploy,
153
- explicitFramework: Boolean(options?.framework),
154
- explicitEntrypoint: Boolean(options?.entrypoint),
155
- explicitHttpPort: Boolean(options?.httpPort)
315
+ explicitFramework: Boolean(merged.framework),
316
+ explicitEntrypoint: Boolean(merged.entrypoint),
317
+ explicitHttpPort: Boolean(merged.httpPort)
156
318
  });
157
319
  framework = customized.framework;
158
320
  runtime = customized.runtime;
321
+ const productionDeployGate = await enforceProductionDeployGate(context, provider, {
322
+ appId: selectedApp.appId,
323
+ appName: selectedApp.displayName,
324
+ branchKind: target.branch.kind,
325
+ prod: options?.prod === true
326
+ });
159
327
  const buildType = framework.buildType;
160
- assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
161
- const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint);
328
+ assertSupportedEntrypoint(buildType, merged.entrypoint?.value, "deploy");
329
+ const entrypoint = await resolveDeployEntrypoint(appDir, framework, merged.entrypoint?.value, context.runtime.signal);
330
+ if (computeConfig.target?.build) assertConfigBackedBuildSettings(buildType);
331
+ const buildSettingsResolution = computeConfig.config && computeConfig.target?.build && isConfigBackedBuildType(buildType) ? await resolveConfiguredPreviewBuildSettings({
332
+ appPath: appDir,
333
+ buildType,
334
+ configured: computeConfig.target.build,
335
+ configPath: computeConfig.config.configPath,
336
+ signal: context.runtime.signal
337
+ }) : await resolveInferredPreviewBuildSettings({
338
+ appPath: appDir,
339
+ buildType,
340
+ signal: context.runtime.signal
341
+ });
342
+ const legacyWarnings = await handleLegacyBuildSettings(context, appDir, buildSettingsResolution.settings);
343
+ maybeRenderDeployBuildSettings(context, buildSettingsResolution);
162
344
  const portMapping = parseDeployPortMapping(String(runtime.port));
345
+ const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
346
+ db: options?.db,
347
+ providedEnvVars: envVars,
348
+ firstProductionDeploy: productionDeployGate.firstProductionDeploy,
349
+ projectDir
350
+ });
163
351
  const progressState = createPreviewDeployProgressState();
164
352
  const deployStartedAt = Date.now();
165
353
  const deployResult = await provider.deployApp({
166
- cwd: context.runtime.cwd,
354
+ cwd: appDir,
167
355
  projectId,
168
356
  branchName: target.branch.name,
169
357
  appId: selectedApp.appId,
@@ -171,9 +359,11 @@ async function runAppDeploy(context, appName, options) {
171
359
  region: selectedApp.region,
172
360
  entrypoint,
173
361
  buildType,
362
+ buildSettings: buildSettingsResolution.settings,
174
363
  portMapping,
175
364
  envVars,
176
365
  interaction: void 0,
366
+ signal: context.runtime.signal,
177
367
  progress: createPreviewDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
178
368
  }).catch((error) => {
179
369
  throw appDeployFailedError(error, progressState);
@@ -189,35 +379,65 @@ async function runAppDeploy(context, appName, options) {
189
379
  result: {
190
380
  workspace: target.workspace,
191
381
  project: target.project,
192
- branch: target.branch,
382
+ branch: toResultBranch(target.branch),
193
383
  resolution: target.resolution,
384
+ branchDatabase: branchDatabaseSetup.result,
194
385
  app: {
195
386
  id: deployResult.app.id,
196
387
  name: deployResult.app.name
197
388
  },
198
389
  deployment: deployResult.deployment,
390
+ deploySettings: {
391
+ config: {
392
+ path: buildSettingsResolution.relativeConfigPath,
393
+ status: buildSettingsResolution.status
394
+ },
395
+ buildCommand: {
396
+ value: buildSettingsResolution.settings.buildCommand,
397
+ source: buildSettingsResolution.settings.buildCommandSource
398
+ },
399
+ outputDirectory: {
400
+ value: buildSettingsResolution.settings.outputDirectory,
401
+ source: buildSettingsResolution.settings.outputDirectorySource
402
+ },
403
+ framework: {
404
+ key: framework.key,
405
+ buildType,
406
+ name: framework.displayName,
407
+ source: framework.annotation
408
+ },
409
+ entrypoint: entrypoint ?? null,
410
+ httpPort: runtime.port,
411
+ region: selectedApp.region ?? null,
412
+ envVars: envVarNames(envVars)
413
+ },
199
414
  durationMs: deployDurationMs,
200
415
  localPin: localPinResult
201
416
  },
202
- warnings: [],
417
+ warnings: [...legacyWarnings, ...branchDatabaseSetup.warnings],
203
418
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
204
419
  };
205
420
  }
206
- async function runAppListDeploys(context, appName, projectRef) {
421
+ async function runAppListDeploys(context, appName, projectRef, configTarget) {
207
422
  ensurePreviewAppMode(context);
208
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app list-deploys" });
209
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
423
+ const compute = await resolveComputeManagementContext(context, configTarget, "list-deploys");
424
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
425
+ commandName: "app list-deploys",
426
+ projectDir: compute.projectDir
427
+ });
428
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName ?? compute.configAppName);
210
429
  if (!selectedApp) return {
211
430
  command: "app.list-deploys",
212
431
  result: {
213
432
  projectId,
433
+ verboseContext: toAppVerboseContext(target),
214
434
  app: null,
215
435
  deployments: []
216
436
  },
217
437
  warnings: [],
218
438
  nextSteps: ["prisma-cli app deploy"]
219
439
  };
220
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
440
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
221
441
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app deploy"]);
222
442
  });
223
443
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -230,6 +450,7 @@ async function runAppListDeploys(context, appName, projectRef) {
230
450
  command: "app.list-deploys",
231
451
  result: {
232
452
  projectId,
453
+ verboseContext: toAppVerboseContext(target),
233
454
  app: {
234
455
  id: deploymentsResult.app.id,
235
456
  name: deploymentsResult.app.name
@@ -240,14 +461,19 @@ async function runAppListDeploys(context, appName, projectRef) {
240
461
  nextSteps: deployments.length > 0 ? [`prisma-cli app show-deploy ${deployments[0]?.id}`] : ["prisma-cli app deploy"]
241
462
  };
242
463
  }
243
- async function runAppShow(context, appName, projectRef) {
464
+ async function runAppShow(context, appName, projectRef, configTarget) {
244
465
  ensurePreviewAppMode(context);
245
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app show" });
246
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
466
+ const compute = await resolveComputeManagementContext(context, configTarget, "show");
467
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
468
+ commandName: "app show",
469
+ projectDir: compute.projectDir
470
+ });
471
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName ?? compute.configAppName);
247
472
  if (!selectedApp) return {
248
473
  command: "app.show",
249
474
  result: {
250
475
  projectId,
476
+ verboseContext: toAppVerboseContext(target),
251
477
  app: null,
252
478
  liveDeployment: null,
253
479
  liveUrl: null,
@@ -256,7 +482,7 @@ async function runAppShow(context, appName, projectRef) {
256
482
  warnings: [],
257
483
  nextSteps: ["prisma-cli app deploy"]
258
484
  };
259
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
485
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
260
486
  throw deployFailedError("Failed to inspect app", error, ["prisma-cli app list-deploys"]);
261
487
  });
262
488
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -270,6 +496,7 @@ async function runAppShow(context, appName, projectRef) {
270
496
  command: "app.show",
271
497
  result: {
272
498
  projectId,
499
+ verboseContext: toAppVerboseContext(target),
273
500
  app: {
274
501
  id: deploymentsResult.app.id,
275
502
  name: deploymentsResult.app.name
@@ -284,7 +511,7 @@ async function runAppShow(context, appName, projectRef) {
284
511
  }
285
512
  async function runAppShowDeploy(context, deploymentId) {
286
513
  ensurePreviewAppMode(context);
287
- const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId).catch((error) => {
514
+ const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
288
515
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
289
516
  });
290
517
  if (!deployment) throw new CliError({
@@ -316,12 +543,17 @@ async function runAppShowDeploy(context, deploymentId) {
316
543
  nextSteps: []
317
544
  };
318
545
  }
319
- async function runAppOpen(context, appName, projectRef) {
546
+ async function runAppOpen(context, appName, projectRef, configTarget) {
320
547
  ensurePreviewAppMode(context);
321
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
548
+ const compute = await resolveComputeManagementContext(context, configTarget, "open");
549
+ appName = appName ?? compute.configAppName;
550
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
551
+ commandName: "app open",
552
+ projectDir: compute.projectDir
553
+ });
322
554
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
323
555
  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) => {
556
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
325
557
  throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
326
558
  });
327
559
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -334,11 +566,16 @@ async function runAppOpen(context, appName, projectRef) {
334
566
  if (!liveDeployment) throw noDeploymentsError("No deployments available to open", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
335
567
  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
568
  const shouldOpen = canPrompt(context);
337
- if (shouldOpen) await open(deploymentsResult.app.liveUrl);
569
+ if (shouldOpen) {
570
+ context.runtime.signal.throwIfAborted();
571
+ await open(deploymentsResult.app.liveUrl);
572
+ context.runtime.signal.throwIfAborted();
573
+ }
338
574
  return {
339
575
  command: "app.open",
340
576
  result: {
341
577
  projectId,
578
+ verboseContext: toAppVerboseContext(target),
342
579
  app: {
343
580
  id: deploymentsResult.app.id,
344
581
  name: deploymentsResult.app.name
@@ -355,7 +592,8 @@ async function runAppDomainAdd(context, hostname, options) {
355
592
  const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
356
593
  const added = await target.provider.addDomain({
357
594
  appId: target.app.id,
358
- hostname: normalizedHostname
595
+ hostname: normalizedHostname,
596
+ signal: context.runtime.signal
359
597
  }).catch((error) => {
360
598
  throw domainCommandError("add", error, normalizedHostname);
361
599
  });
@@ -373,8 +611,8 @@ async function runAppDomainAdd(context, hostname, options) {
373
611
  async function runAppDomainShow(context, hostname, options) {
374
612
  const normalizedHostname = normalizeDomainHostname(hostname);
375
613
  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) => {
614
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show", context.runtime.signal);
615
+ const detail = await target.provider.showDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
378
616
  throw domainCommandError("show", error, normalizedHostname);
379
617
  });
380
618
  return {
@@ -390,9 +628,9 @@ async function runAppDomainShow(context, hostname, options) {
390
628
  async function runAppDomainRemove(context, hostname, options) {
391
629
  const normalizedHostname = normalizeDomainHostname(hostname);
392
630
  const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
393
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
631
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove", context.runtime.signal);
394
632
  await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
395
- await target.provider.removeDomain(domain.id).catch((error) => {
633
+ await target.provider.removeDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
396
634
  throw domainCommandError("remove", error, normalizedHostname);
397
635
  });
398
636
  return {
@@ -409,8 +647,8 @@ async function runAppDomainRemove(context, hostname, options) {
409
647
  async function runAppDomainRetry(context, hostname, options) {
410
648
  const normalizedHostname = normalizeDomainHostname(hostname);
411
649
  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) => {
650
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry", context.runtime.signal);
651
+ const retried = await target.provider.retryDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
414
652
  throw domainCommandError("retry", error, normalizedHostname);
415
653
  });
416
654
  return {
@@ -427,7 +665,7 @@ async function runAppDomainWait(context, hostname, options) {
427
665
  const normalizedHostname = normalizeDomainHostname(hostname);
428
666
  const timeoutMs = parseDomainWaitTimeout(options?.timeout);
429
667
  const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
430
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
668
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait", context.runtime.signal);
431
669
  if (!context.flags.json && !context.flags.quiet) context.output.stderr.write([
432
670
  `app domain wait -> Waiting for ${normalizedHostname} to become active.`,
433
671
  "",
@@ -470,21 +708,25 @@ async function runAppDomainWait(context, hostname, options) {
470
708
  exitCode: 1,
471
709
  nextSteps: [`prisma-cli app domain show ${normalizedHostname}`]
472
710
  });
473
- await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)));
474
- current = await target.provider.showDomain(current.id).catch((error) => {
711
+ await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)), context.runtime.signal);
712
+ current = await target.provider.showDomain(current.id, { signal: context.runtime.signal }).catch((error) => {
475
713
  throw domainCommandError("wait", error, normalizedHostname);
476
714
  });
477
715
  }
478
716
  }
479
- async function runAppLogs(context, appName, deploymentId, projectRef) {
717
+ async function runAppLogs(context, appName, deploymentId, projectRef, configTarget) {
480
718
  ensurePreviewAppMode(context);
481
- const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app logs" });
719
+ const compute = await resolveComputeManagementContext(context, configTarget, "logs");
720
+ appName = appName ?? compute.configAppName;
721
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, {
722
+ commandName: "app logs",
723
+ projectDir: compute.projectDir
724
+ });
482
725
  const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
483
726
  if (!context.flags.json && !context.flags.quiet) {
484
727
  const lines = renderCommandHeader(context.ui, {
485
728
  commandLabel: "app logs",
486
729
  description: "Streaming logs for the selected deployment.",
487
- docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
488
730
  rows: [
489
731
  {
490
732
  key: "project",
@@ -504,6 +746,7 @@ async function runAppLogs(context, appName, deploymentId, projectRef) {
504
746
  }
505
747
  await provider.streamDeploymentLogs({
506
748
  deploymentId: target.deployment.id,
749
+ signal: context.runtime.signal,
507
750
  onRecord: (record) => writeLogRecord(context, record)
508
751
  }).catch((error) => {
509
752
  throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
@@ -513,7 +756,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
513
756
  if (appName) {
514
757
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
515
758
  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) => {
759
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
517
760
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
518
761
  });
519
762
  const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
@@ -526,7 +769,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
526
769
  deployment
527
770
  };
528
771
  }
529
- const shown = await provider.showDeployment(deploymentId).catch((error) => {
772
+ const shown = await provider.showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
530
773
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
531
774
  });
532
775
  if (!shown) throw new CliError({
@@ -569,7 +812,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
569
812
  async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
570
813
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
571
814
  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) => {
815
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
573
816
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
574
817
  });
575
818
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -600,11 +843,16 @@ function writeLogRecord(context, record) {
600
843
  if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
601
844
  }
602
845
  }
603
- async function runAppPromote(context, deploymentId, appName, projectRef) {
846
+ async function runAppPromote(context, deploymentId, appName, projectRef, configTarget) {
604
847
  ensurePreviewAppMode(context);
605
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
848
+ const compute = await resolveComputeManagementContext(context, configTarget, "promote");
849
+ appName = appName ?? compute.configAppName;
850
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
851
+ commandName: "app promote",
852
+ projectDir: compute.projectDir
853
+ });
606
854
  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) => {
855
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
608
856
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
609
857
  });
610
858
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -617,6 +865,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
617
865
  if (!targetAlreadyLive) await provider.promoteDeployment({
618
866
  appId: selectedApp.id,
619
867
  deploymentId: targetDeployment.id,
868
+ signal: context.runtime.signal,
620
869
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
621
870
  }).catch((error) => {
622
871
  throw deployFailedError("Failed to promote deployment", error, ["prisma-cli app list-deploys"]);
@@ -626,6 +875,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
626
875
  command: "app.promote",
627
876
  result: {
628
877
  projectId,
878
+ verboseContext: toAppVerboseContext(target),
629
879
  app: {
630
880
  id: deploymentsResult.app.id,
631
881
  name: deploymentsResult.app.name
@@ -640,11 +890,16 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
640
890
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
641
891
  };
642
892
  }
643
- async function runAppRollback(context, appName, deploymentId, projectRef) {
893
+ async function runAppRollback(context, appName, deploymentId, projectRef, configTarget) {
644
894
  ensurePreviewAppMode(context);
645
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
895
+ const compute = await resolveComputeManagementContext(context, configTarget, "rollback");
896
+ appName = appName ?? compute.configAppName;
897
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
898
+ commandName: "app rollback",
899
+ projectDir: compute.projectDir
900
+ });
646
901
  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) => {
902
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
648
903
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
649
904
  });
650
905
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -658,6 +913,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
658
913
  if (!targetAlreadyLive) await provider.promoteDeployment({
659
914
  appId: selectedApp.id,
660
915
  deploymentId: targetDeployment.id,
916
+ signal: context.runtime.signal,
661
917
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
662
918
  }).catch((error) => {
663
919
  throw deployFailedError("Failed to roll back deployment", error, ["prisma-cli app list-deploys"]);
@@ -667,6 +923,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
667
923
  command: "app.rollback",
668
924
  result: {
669
925
  projectId,
926
+ verboseContext: toAppVerboseContext(target),
670
927
  app: {
671
928
  id: deploymentsResult.app.id,
672
929
  name: deploymentsResult.app.name
@@ -682,12 +939,17 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
682
939
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
683
940
  };
684
941
  }
685
- async function runAppRemove(context, appName, projectRef) {
942
+ async function runAppRemove(context, appName, projectRef, configTarget) {
686
943
  ensurePreviewAppMode(context);
687
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
944
+ const compute = await resolveComputeManagementContext(context, configTarget, "remove");
945
+ appName = appName ?? compute.configAppName;
946
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
947
+ commandName: "app remove",
948
+ projectDir: compute.projectDir
949
+ });
688
950
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
689
951
  await confirmAppRemoval(context, selectedApp);
690
- const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
952
+ const removedApp = await provider.removeApp(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
691
953
  throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
692
954
  });
693
955
  const warnings = await cleanupRemovedAppState(context, projectId, removedApp.id);
@@ -695,6 +957,7 @@ async function runAppRemove(context, appName, projectRef) {
695
957
  command: "app.remove",
696
958
  result: {
697
959
  projectId,
960
+ verboseContext: toAppVerboseContext(target),
698
961
  app: {
699
962
  id: removedApp.id,
700
963
  name: removedApp.name
@@ -707,6 +970,7 @@ async function runAppRemove(context, appName, projectRef) {
707
970
  }
708
971
  async function resolveAppDomainTarget(context, options, commandName = "app domain") {
709
972
  ensurePreviewAppMode(context);
973
+ const compute = await resolveComputeManagementContext(context, options?.configTarget, commandName.replace(/^app /, ""));
710
974
  const branch = resolveDomainBranch(options?.branchName);
711
975
  if (toBranchKind(branch.name) !== "production") throw new CliError({
712
976
  code: "BRANCH_NOT_DEPLOYABLE",
@@ -722,10 +986,11 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
722
986
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
723
987
  branch,
724
988
  commandName,
725
- envProjectId
989
+ envProjectId,
990
+ projectDir: compute.projectDir
726
991
  });
727
992
  const selectedApp = await resolveDomainAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
728
- explicitAppName: options?.appName,
993
+ explicitAppName: options?.appName ?? compute.configAppName,
729
994
  explicitAppId: envAppId
730
995
  });
731
996
  await context.stateStore.setSelectedApp(projectId, {
@@ -738,7 +1003,7 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
738
1003
  resultTarget: {
739
1004
  workspace: target.workspace,
740
1005
  project: target.project,
741
- branch: target.branch,
1006
+ branch: toResultBranch(target.branch),
742
1007
  app: {
743
1008
  id: selectedApp.id,
744
1009
  name: selectedApp.name
@@ -762,8 +1027,8 @@ async function resolveDomainAppSelection(context, projectId, apps, options) {
762
1027
  if (selectedApp) return selectedApp;
763
1028
  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
1029
  }
765
- async function resolveDomainByHostname(provider, appId, hostname, command) {
766
- const matched = (await provider.listDomains(appId).catch((error) => {
1030
+ async function resolveDomainByHostname(provider, appId, hostname, command, signal) {
1031
+ const matched = (await provider.listDomains(appId, { signal }).catch((error) => {
767
1032
  throw domainCommandError(command, error, hostname);
768
1033
  })).find((domain) => sameDomainHostname(domain.hostname, hostname));
769
1034
  if (matched) return matched;
@@ -994,9 +1259,20 @@ function formatElapsed(milliseconds) {
994
1259
  const remainingSeconds = seconds % 60;
995
1260
  return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
996
1261
  }
997
- async function sleep(milliseconds) {
1262
+ async function sleep(milliseconds, signal) {
998
1263
  if (milliseconds <= 0) return;
999
- await new Promise((resolve) => setTimeout(resolve, milliseconds));
1264
+ signal.throwIfAborted();
1265
+ await new Promise((resolve, reject) => {
1266
+ const onAbort = () => {
1267
+ clearTimeout(timeout);
1268
+ reject(signal.reason);
1269
+ };
1270
+ const timeout = setTimeout(() => {
1271
+ signal.removeEventListener("abort", onAbort);
1272
+ resolve();
1273
+ }, milliseconds);
1274
+ signal.addEventListener("abort", onAbort, { once: true });
1275
+ });
1000
1276
  }
1001
1277
  async function resolveDeployAppSelection(context, projectId, apps, options) {
1002
1278
  if (options.explicitAppName) {
@@ -1027,6 +1303,25 @@ async function resolveDeployAppSelection(context, projectId, apps, options) {
1027
1303
  firstDeploy: options.firstDeploy
1028
1304
  };
1029
1305
  }
1306
+ if (options.configAppName) {
1307
+ const configName = options.configAppName;
1308
+ const matches = findAppsByName(apps, configName.value);
1309
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, configName.value, options.firstDeploy);
1310
+ const matched = matches[0];
1311
+ if (matched) return {
1312
+ appId: matched.id,
1313
+ displayName: matched.name,
1314
+ annotation: configName.annotation,
1315
+ firstDeploy: options.firstDeploy
1316
+ };
1317
+ return {
1318
+ appName: configName.value,
1319
+ region: PREVIEW_DEFAULT_REGION,
1320
+ displayName: configName.value,
1321
+ annotation: configName.annotation,
1322
+ firstDeploy: options.firstDeploy
1323
+ };
1324
+ }
1030
1325
  const inferredName = await options.inferName();
1031
1326
  const matches = findAppsByName(apps, inferredName.name);
1032
1327
  if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
@@ -1211,7 +1506,10 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
1211
1506
  });
1212
1507
  }
1213
1508
  async function listApps(context, provider, projectId, branchName) {
1214
- return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
1509
+ return provider.listApps(projectId, {
1510
+ branchName,
1511
+ signal: context.runtime.signal
1512
+ }).then(sortApps).catch((error) => {
1215
1513
  if (isMissingProjectError(error)) throw new CliError({
1216
1514
  code: "PROJECT_NOT_FOUND",
1217
1515
  domain: "project",
@@ -1229,20 +1527,20 @@ async function requirePreviewAppProvider(context) {
1229
1527
  return provider;
1230
1528
  }
1231
1529
  async function requirePreviewAppProviderWithClient(context) {
1232
- const client = await requireComputeAuth(context.runtime.env);
1530
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
1233
1531
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
1234
1532
  return {
1235
1533
  client,
1236
- provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
1534
+ provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env, context.runtime.signal))
1237
1535
  };
1238
1536
  }
1239
- function createPreviewLogAuthOptions(env) {
1537
+ function createPreviewLogAuthOptions(env, signal) {
1240
1538
  const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
1241
1539
  if (rawToken) return {
1242
1540
  baseUrl: getApiBaseUrl(env),
1243
1541
  getToken: async () => rawToken
1244
1542
  };
1245
- const tokenStorage = new FileTokenStorage(env);
1543
+ const tokenStorage = new FileTokenStorage(env, signal);
1246
1544
  return {
1247
1545
  baseUrl: getApiBaseUrl(env),
1248
1546
  getToken: async () => {
@@ -1275,18 +1573,22 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
1275
1573
  async function resolveProjectContext(context, client, explicitProject, options) {
1276
1574
  const authState = await requireAuthenticatedAuthState(context);
1277
1575
  if (!authState.workspace) throw workspaceRequiredError();
1278
- const resolved = await resolveProjectTarget({
1576
+ const resolvedResult = await resolveProjectTarget({
1279
1577
  context,
1280
1578
  workspace: authState.workspace,
1281
1579
  explicitProject,
1282
1580
  envProjectId: options?.envProjectId,
1283
- listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
1581
+ projectDir: options?.projectDir,
1582
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
1284
1583
  commandName: options?.commandName
1285
1584
  });
1585
+ if (resolvedResult.isErr()) throw projectResolutionErrorToCliError(resolvedResult.error);
1586
+ const resolved = resolvedResult.value;
1286
1587
  const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
1287
1588
  return {
1288
1589
  ...resolved,
1289
1590
  branch: {
1591
+ id: null,
1290
1592
  name: branch.name,
1291
1593
  kind: toBranchKind(branch.name)
1292
1594
  }
@@ -1296,8 +1598,8 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1296
1598
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1297
1599
  if (!workspace) throw workspaceRequiredError();
1298
1600
  const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1299
- const projects = await listRealWorkspaceProjects(client, workspace);
1300
- if (explicitProject) return withDeployBranch({
1601
+ const projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
1602
+ if (explicitProject) return withRemoteDeployBranch(provider, {
1301
1603
  workspace,
1302
1604
  project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
1303
1605
  resolution: {
@@ -1306,25 +1608,25 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1306
1608
  targetNameSource: "explicit"
1307
1609
  },
1308
1610
  localPinAction: "linked"
1309
- }, branch);
1611
+ }, branch, context.runtime.signal);
1310
1612
  if (options.createProjectName) {
1311
1613
  const projectName = options.createProjectName.trim();
1312
1614
  if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1313
- return withDeployBranch({
1615
+ return withRemoteDeployBranch(provider, {
1314
1616
  workspace,
1315
- project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace)),
1617
+ project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
1316
1618
  resolution: {
1317
1619
  projectSource: "created",
1318
1620
  targetName: projectName,
1319
1621
  targetNameSource: "explicit"
1320
1622
  },
1321
1623
  localPinAction: "created"
1322
- }, branch);
1624
+ }, branch, context.runtime.signal);
1323
1625
  }
1324
1626
  if (options.envProjectId) {
1325
1627
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
1326
1628
  if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1327
- return withDeployBranch({
1629
+ return withRemoteDeployBranch(provider, {
1328
1630
  workspace,
1329
1631
  project: toProjectSummary(project),
1330
1632
  resolution: {
@@ -1332,14 +1634,14 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1332
1634
  targetName: options.envProjectId,
1333
1635
  targetNameSource: "env"
1334
1636
  }
1335
- }, branch);
1637
+ }, branch, context.runtime.signal);
1336
1638
  }
1337
1639
  const localPin = options.localPin;
1338
1640
  if (localPin.kind === "present") {
1339
1641
  if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1340
1642
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1341
1643
  if (!project) throw localResolutionPinStaleError();
1342
- return withDeployBranch({
1644
+ return withRemoteDeployBranch(provider, {
1343
1645
  workspace,
1344
1646
  project: toProjectSummary(project),
1345
1647
  resolution: {
@@ -1347,10 +1649,10 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1347
1649
  targetName: project.name,
1348
1650
  targetNameSource: "local-pin"
1349
1651
  }
1350
- }, branch);
1652
+ }, branch, context.runtime.signal);
1351
1653
  }
1352
1654
  const platformMapping = await resolveDurablePlatformMapping();
1353
- if (platformMapping && platformMapping.workspace.id === workspace.id) return withDeployBranch({
1655
+ if (platformMapping && platformMapping.workspace.id === workspace.id) return withRemoteDeployBranch(provider, {
1354
1656
  workspace,
1355
1657
  project: toProjectSummary(platformMapping),
1356
1658
  resolution: {
@@ -1358,15 +1660,15 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1358
1660
  targetName: platformMapping.name,
1359
1661
  targetNameSource: "platform-mapping"
1360
1662
  }
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));
1663
+ }, branch, context.runtime.signal);
1664
+ if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch, context.runtime.signal);
1665
+ throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1364
1666
  }
1365
1667
  async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
1366
1668
  const setup = await promptForProjectSetupChoice({
1367
1669
  context,
1368
1670
  projects,
1369
- createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace),
1671
+ createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal),
1370
1672
  cancel: {
1371
1673
  why: "Deploy needs a Project before it can continue.",
1372
1674
  fix: "Choose an existing Project or create a new one, then rerun deploy.",
@@ -1384,8 +1686,11 @@ async function resolveInteractiveDeployProjectSetup(context, provider, workspace
1384
1686
  localPinAction: setup.action
1385
1687
  };
1386
1688
  }
1387
- async function createProjectForDeploySetup(provider, projectName, workspace) {
1388
- const created = await provider.createProject({ name: projectName }).catch((error) => {
1689
+ async function createProjectForDeploySetup(provider, projectName, workspace, signal) {
1690
+ const created = await provider.createProject({
1691
+ name: projectName,
1692
+ signal
1693
+ }).catch((error) => {
1389
1694
  throw projectCreateFailedError(error, projectName, workspace, {
1390
1695
  nextSteps: [
1391
1696
  "prisma-cli project list",
@@ -1402,18 +1707,46 @@ async function createProjectForDeploySetup(provider, projectName, workspace) {
1402
1707
  workspace
1403
1708
  };
1404
1709
  }
1405
- function withDeployBranch(target, branch) {
1710
+ async function withRemoteDeployBranch(provider, target, branch, signal) {
1711
+ const remoteBranch = await provider.resolveBranch(target.project.id, {
1712
+ branchName: branch.name,
1713
+ signal
1714
+ });
1406
1715
  return {
1407
1716
  ...target,
1408
1717
  branch: {
1409
- name: branch.name,
1410
- kind: toBranchKind(branch.name)
1718
+ id: remoteBranch.id,
1719
+ name: remoteBranch.name,
1720
+ kind: remoteBranch.role
1411
1721
  }
1412
1722
  };
1413
1723
  }
1414
1724
  function toBranchKind(name) {
1415
1725
  return name === "production" || name === "main" ? "production" : "preview";
1416
1726
  }
1727
+ function toResultBranch(branch) {
1728
+ return {
1729
+ id: branch.id,
1730
+ name: branch.name,
1731
+ kind: branch.kind
1732
+ };
1733
+ }
1734
+ function toAppVerboseContext(target) {
1735
+ return {
1736
+ workspace: target.workspace,
1737
+ project: target.project,
1738
+ branch: target.branch,
1739
+ resolution: target.resolution
1740
+ };
1741
+ }
1742
+ function toBranchDatabaseDeployBranch(branch) {
1743
+ if (!branch.id) throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);
1744
+ return {
1745
+ id: branch.id,
1746
+ name: branch.name,
1747
+ kind: branch.kind
1748
+ };
1749
+ }
1417
1750
  function assertExclusiveDeployProjectInputs(options) {
1418
1751
  const provided = [
1419
1752
  options.projectRef ? "--project" : null,
@@ -1432,7 +1765,7 @@ async function resolveDeployBranch(context, explicitBranchName) {
1432
1765
  name: explicitBranchName,
1433
1766
  annotation: "set by --branch"
1434
1767
  };
1435
- const gitBranch = await readLocalGitBranch(context.runtime.cwd);
1768
+ const gitBranch = await readLocalGitBranch(context.runtime.cwd, context.runtime.signal);
1436
1769
  if (gitBranch) return {
1437
1770
  name: gitBranch,
1438
1771
  annotation: "from local Git branch"
@@ -1442,45 +1775,130 @@ async function resolveDeployBranch(context, explicitBranchName) {
1442
1775
  annotation: "default"
1443
1776
  };
1444
1777
  }
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;
1778
+ async function resolveComputeTargetOrThrow(context, configTarget, commandName, options) {
1779
+ let config;
1780
+ if (options?.preloaded !== void 0) config = options.preloaded;
1781
+ else {
1782
+ const loaded = await loadComputeConfig$1(context.runtime.cwd, context.runtime.signal);
1783
+ if (loaded.isErr()) throw computeConfigErrorToCliError(loaded.error, commandName);
1784
+ config = loaded.value;
1453
1785
  }
1454
- return null;
1786
+ if (!config) {
1787
+ if (configTarget) throw usageError(`App target "${configTarget}" requires a compute config file`, `No ${COMPUTE_CONFIG_FILENAME$1} exists in the current directory, so there are no named app targets.`, `Create ${COMPUTE_CONFIG_FILENAME$1} with an apps entry named "${configTarget}", or rerun without the target argument.`, [`prisma-cli app ${commandName}`], "app");
1788
+ return {
1789
+ config: null,
1790
+ target: null
1791
+ };
1792
+ }
1793
+ const requestedTarget = configTarget ?? inferComputeTargetFromCwd(config, context.runtime.cwd);
1794
+ const selected = selectComputeDeployTarget(config, requestedTarget);
1795
+ if (selected.isErr()) {
1796
+ if (options?.targetOptional && selected.error instanceof ComputeConfigTargetRequiredError) return {
1797
+ config,
1798
+ target: null
1799
+ };
1800
+ throw computeConfigErrorToCliError(selected.error, commandName);
1801
+ }
1802
+ return {
1803
+ config,
1804
+ target: selected.value
1805
+ };
1455
1806
  }
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 {}
1807
+ /**
1808
+ * Compute-config context for app management commands: the project directory
1809
+ * (where `.prisma/local.json` lives) and the config-selected app name, which
1810
+ * ranks below `--app` but above the remembered app selection.
1811
+ */
1812
+ async function resolveComputeManagementContext(context, configTarget, commandName) {
1813
+ const compute = await resolveComputeTargetOrThrow(context, configTarget, commandName, { targetOptional: true });
1814
+ return {
1815
+ projectDir: compute.config?.configDir ?? context.runtime.cwd,
1816
+ configAppName: compute.target?.name ?? compute.target?.key ?? void 0
1817
+ };
1818
+ }
1819
+ async function resolveComputeAppDir(context, compute) {
1820
+ if (!compute.config || !compute.target) return context.runtime.cwd;
1821
+ const appDir = computeTargetAppDir(compute.config, compute.target);
1822
+ if (!compute.target.root) return appDir;
1823
+ context.runtime.signal.throwIfAborted();
1461
1824
  try {
1462
- await access(path.join(gitPath, "HEAD"));
1463
- return path.join(gitPath, "HEAD");
1464
- } catch {
1465
- return null;
1825
+ await access(appDir);
1826
+ context.runtime.signal.throwIfAborted();
1827
+ } catch (error) {
1828
+ if (context.runtime.signal.aborted) throw error;
1829
+ throw new CliError({
1830
+ code: "COMPUTE_CONFIG_INVALID",
1831
+ domain: "app",
1832
+ summary: `App root "${compute.target.root}" does not exist`,
1833
+ why: `${compute.config.relativeConfigPath} points the selected app at "${compute.target.root}", but that directory does not exist.`,
1834
+ fix: `Fix the root path in ${compute.config.relativeConfigPath} or create the directory.`,
1835
+ where: appDir,
1836
+ meta: {
1837
+ appRoot: compute.target.root,
1838
+ appDir
1839
+ },
1840
+ exitCode: 2,
1841
+ nextSteps: ["prisma-cli app deploy"]
1842
+ });
1843
+ }
1844
+ return appDir;
1845
+ }
1846
+ /**
1847
+ * `prisma.app.json` is no longer read or written. A leftover file that
1848
+ * matches the effective settings only warns; one with custom values fails
1849
+ * with migration guidance so builds never silently change.
1850
+ */
1851
+ async function handleLegacyBuildSettings(context, appDir, effective) {
1852
+ const legacy = await detectLegacyBuildSettings({
1853
+ appPath: appDir,
1854
+ effective,
1855
+ signal: context.runtime.signal
1856
+ });
1857
+ switch (legacy.kind) {
1858
+ case "absent": return [];
1859
+ case "matching": return [`${PRISMA_APP_CONFIG_FILENAME} is no longer used and matches the resolved build settings. Delete it.`];
1860
+ case "invalid": return [`${PRISMA_APP_CONFIG_FILENAME} is no longer used and could not be parsed. Delete it.`];
1861
+ case "custom": {
1862
+ const buildBlock = [
1863
+ "build: {",
1864
+ ` command: ${legacy.buildCommand === null ? "null" : JSON.stringify(legacy.buildCommand)},`,
1865
+ ` outputDirectory: ${JSON.stringify(legacy.outputDirectory)},`,
1866
+ "}"
1867
+ ].join(" ");
1868
+ throw new CliError({
1869
+ code: "BUILD_SETTINGS_MIGRATION_REQUIRED",
1870
+ domain: "app",
1871
+ summary: `${PRISMA_APP_CONFIG_FILENAME} is no longer supported`,
1872
+ why: `${PRISMA_APP_CONFIG_FILENAME} contains custom build settings that differ from the resolved defaults, and the file is no longer read.`,
1873
+ fix: `Move the settings into prisma.compute.ts as \`${buildBlock}\` on this app, then delete ${PRISMA_APP_CONFIG_FILENAME}.`,
1874
+ where: legacy.configPath,
1875
+ meta: {
1876
+ configPath: legacy.configPath,
1877
+ buildCommand: legacy.buildCommand,
1878
+ outputDirectory: legacy.outputDirectory
1879
+ },
1880
+ exitCode: 2,
1881
+ nextSteps: ["prisma-cli app deploy"]
1882
+ });
1883
+ }
1466
1884
  }
1467
1885
  }
1468
1886
  async function resolveDeployFramework(context, options) {
1469
- if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1887
+ if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, options.requestedFrameworkAnnotation ?? "set by --framework");
1470
1888
  if (options.entrypoint) return {
1471
1889
  key: "bun",
1472
1890
  buildType: "bun",
1473
1891
  displayName: "Bun",
1474
- annotation: "set by --entry"
1892
+ annotation: options.entrypointAnnotation ?? "set by --entry"
1475
1893
  };
1476
- const detected = await detectDeployFramework(context.runtime.cwd);
1894
+ const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
1477
1895
  if (detected) return detected;
1478
- throw frameworkNotDetectedError(context.runtime.cwd);
1896
+ throw frameworkNotDetectedError(options.appDir);
1479
1897
  }
1480
- function resolveDeployRuntime(requestedHttpPort, framework) {
1898
+ function resolveDeployRuntime(requestedHttpPort, requestedHttpPortAnnotation, framework) {
1481
1899
  if (requestedHttpPort) return {
1482
1900
  port: parseDeployHttpPort(requestedHttpPort),
1483
- annotation: "set by --http-port"
1901
+ annotation: requestedHttpPortAnnotation ?? "set by --http-port"
1484
1902
  };
1485
1903
  return {
1486
1904
  port: FRAMEWORK_DEFAULT_HTTP_PORT,
@@ -1491,65 +1909,62 @@ function assertSupportedEntrypointForRequestedDeployShape(options) {
1491
1909
  if (!options.requestedFramework) return;
1492
1910
  assertSupportedEntrypoint(frameworkFromUserFacingValue(options.requestedFramework, "set by --framework").buildType, options.entrypoint, "deploy");
1493
1911
  }
1494
- async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint) {
1912
+ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signal) {
1495
1913
  if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
1496
- const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd));
1914
+ const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd, signal));
1497
1915
  if (packageEntrypoint) return packageEntrypoint;
1498
- if (framework.key !== "hono") return;
1499
- const defaultEntrypoint = "src/index.ts";
1916
+ const defaultEntrypoint = frameworkFromAlias(framework.key)?.defaultEntrypoint;
1917
+ if (!defaultEntrypoint) return;
1918
+ signal.throwIfAborted();
1500
1919
  try {
1501
1920
  await access(path.join(cwd, defaultEntrypoint));
1921
+ signal.throwIfAborted();
1502
1922
  return defaultEntrypoint;
1503
1923
  } catch (error) {
1924
+ if (signal.aborted) throw error;
1504
1925
  if (error.code !== "ENOENT") throw error;
1505
1926
  return;
1506
1927
  }
1507
1928
  }
1508
- async function detectDeployFramework(cwd) {
1509
- const packageJson = await readBunPackageJson(cwd);
1510
- const nextConfig = await detectNextConfig(cwd);
1511
- if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
1512
- key: "nextjs",
1513
- buildType: "nextjs",
1514
- displayName: "Next.js",
1515
- annotation: nextConfig.standalone ? "standalone output detected" : nextConfig.exists ? "detected from next.config" : "detected from package.json"
1516
- };
1517
- if (hasPackageDependency(packageJson, "hono")) return {
1518
- key: "hono",
1519
- buildType: "bun",
1520
- displayName: "Hono",
1521
- annotation: "detected from package.json"
1522
- };
1523
- if (hasAnyPackageDependency(packageJson, TANSTACK_START_PACKAGES)) return {
1524
- key: "tanstack-start",
1525
- buildType: "tanstack-start",
1526
- displayName: "TanStack Start",
1527
- annotation: "detected from package.json"
1528
- };
1929
+ async function detectDeployFramework(cwd, signal) {
1930
+ const packageJson = await readBunPackageJson(cwd, signal);
1931
+ for (const framework of FRAMEWORKS) {
1932
+ if (framework.detectConfigFiles.length === 0 && framework.detectPackages.length === 0) continue;
1933
+ const configFile = await detectFrameworkConfigFile(cwd, framework, signal);
1934
+ if (!configFile.exists && !hasAnyPackageDependency(packageJson, framework.detectPackages)) continue;
1935
+ const annotation = framework.key === "nextjs" && configFile.standalone ? "standalone output detected" : configFile.exists ? `detected from ${path.basename(configFile.path)}` : "detected from package.json";
1936
+ return {
1937
+ key: framework.key,
1938
+ buildType: framework.buildType,
1939
+ displayName: framework.displayName,
1940
+ annotation
1941
+ };
1942
+ }
1529
1943
  return null;
1530
1944
  }
1531
- async function detectNextConfig(cwd) {
1532
- for (const candidate of [
1533
- "next.config.js",
1534
- "next.config.mjs",
1535
- "next.config.cjs",
1536
- "next.config.ts",
1537
- "next.config.mts"
1538
- ]) {
1945
+ async function detectFrameworkConfigFile(cwd, framework, signal) {
1946
+ for (const candidate of framework.detectConfigFiles) {
1539
1947
  const filePath = path.join(cwd, candidate);
1948
+ signal.throwIfAborted();
1540
1949
  try {
1541
- const content = await readFile(filePath, "utf8");
1950
+ const content = await readFile(filePath, {
1951
+ encoding: "utf8",
1952
+ signal
1953
+ });
1542
1954
  return {
1543
1955
  exists: true,
1544
- standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
1956
+ standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),
1957
+ path: filePath
1545
1958
  };
1546
1959
  } catch (error) {
1960
+ if (signal.aborted) throw error;
1547
1961
  if (error.code !== "ENOENT") throw error;
1548
1962
  }
1549
1963
  }
1550
1964
  return {
1551
1965
  exists: false,
1552
- standalone: false
1966
+ standalone: false,
1967
+ path: null
1553
1968
  };
1554
1969
  }
1555
1970
  function hasPackageDependency(packageJson, dependencyName) {
@@ -1562,48 +1977,41 @@ function hasDependency(dependencies, dependencyName) {
1562
1977
  return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1563
1978
  }
1564
1979
  function frameworkFromUserFacingValue(value, annotation) {
1565
- switch (value.trim().toLowerCase()) {
1566
- case "next":
1567
- case "next.js":
1568
- case "nextjs": return {
1569
- key: "nextjs",
1570
- buildType: "nextjs",
1571
- displayName: "Next.js",
1572
- annotation
1573
- };
1574
- case "hono": return {
1575
- key: "hono",
1576
- buildType: "bun",
1577
- displayName: "Hono",
1578
- annotation
1579
- };
1580
- case "bun": return {
1581
- key: "bun",
1582
- buildType: "bun",
1583
- displayName: "Bun",
1584
- annotation
1585
- };
1586
- case "tanstack":
1587
- case "tanstack-start":
1588
- case "@tanstack/react-start":
1589
- case "@tanstack/solid-start": return {
1590
- key: "tanstack-start",
1591
- buildType: "tanstack-start",
1592
- displayName: "TanStack Start",
1593
- annotation
1594
- };
1595
- default: throw frameworkNotDetectedError(void 0, value);
1596
- }
1980
+ const framework = frameworkFromAlias(value);
1981
+ if (!framework) throw frameworkNotDetectedError(void 0, value);
1982
+ return {
1983
+ key: framework.key,
1984
+ buildType: framework.buildType,
1985
+ displayName: framework.displayName,
1986
+ annotation
1987
+ };
1988
+ }
1989
+ /**
1990
+ * The nuxt and astro strategies build with their framework CLI and stage
1991
+ * fixed output, so a compute config `build` block has nothing to apply to.
1992
+ * Erroring beats silently ignoring committed settings.
1993
+ */
1994
+ function assertConfigBackedBuildSettings(buildType) {
1995
+ if (isConfigBackedBuildType(buildType)) return;
1996
+ const displayName = FRAMEWORKS.find((framework) => framework.buildType === buildType)?.displayName ?? buildType;
1997
+ throw new CliError({
1998
+ code: "BUILD_SETTINGS_UNSUPPORTED",
1999
+ domain: "app",
2000
+ summary: `build settings are not supported for ${displayName} apps`,
2001
+ why: `${displayName} deploys run \`${buildType} build\` and package its output automatically.`,
2002
+ fix: "Remove the `build` block from prisma.compute.ts for this app.",
2003
+ exitCode: 2
2004
+ });
1597
2005
  }
1598
2006
  function frameworkNotDetectedError(cwd, requestedFramework) {
1599
- const supported = "Next.js, Hono, TanStack Start, Bun";
2007
+ const supported = FRAMEWORKS.map((framework) => framework.displayName).join(", ");
1600
2008
  const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
1601
2009
  return new CliError({
1602
2010
  code: "FRAMEWORK_NOT_DETECTED",
1603
2011
  domain: "app",
1604
2012
  summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
1605
2013
  why: `Supported Beta frameworks: ${supported}.`,
1606
- fix: "Add one of these frameworks as a dependency, pass --framework <nextjs|hono|tanstack-start|bun>, or pass --entry <path> for a Bun app.",
2014
+ fix: `Add one of these frameworks as a dependency, pass --framework <${FRAMEWORKS.map((framework) => framework.key).join("|")}>, or pass --entry <path> for a Bun app.`,
1607
2015
  exitCode: 2,
1608
2016
  nextSteps: [
1609
2017
  "prisma-cli app deploy --framework nextjs",
@@ -1616,10 +2024,24 @@ function frameworkNotDetectedError(cwd, requestedFramework) {
1616
2024
  }
1617
2025
  async function maybeRenderDeploySetupBlock(context, details) {
1618
2026
  if (context.flags.json || context.flags.quiet) return;
1619
- const directory = formatDeployDirectory(context.runtime.cwd);
2027
+ const directory = formatAppDirectoryLabel(context.runtime.cwd, details.appDir);
1620
2028
  const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
1621
2029
  context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1622
2030
  }
2031
+ function maybeRenderDeployBuildSettings(context, resolution) {
2032
+ if (context.flags.json || context.flags.quiet) return;
2033
+ const settings = resolution.settings;
2034
+ const title = resolution.status === "config" ? `Using ${resolution.relativeConfigPath}` : "Build settings";
2035
+ context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [{
2036
+ label: "Build Command",
2037
+ value: settings.buildCommand ?? "none",
2038
+ origin: settings.buildCommandSource ?? void 0
2039
+ }, {
2040
+ label: "Output Directory",
2041
+ value: settings.outputDirectory,
2042
+ origin: settings.outputDirectorySource ?? void 0
2043
+ }]).join("\n")}\n\n`);
2044
+ }
1623
2045
  function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
1624
2046
  if (context.flags.json || context.flags.quiet) return;
1625
2047
  context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
@@ -1629,10 +2051,14 @@ async function maybeCustomizeDeploySettings(context, options) {
1629
2051
  framework: options.framework,
1630
2052
  runtime: options.runtime
1631
2053
  };
2054
+ maybeRenderDeploySettingsPreview(context, {
2055
+ framework: options.framework,
2056
+ runtime: options.runtime
2057
+ });
1632
2058
  if (!await confirmPrompt({
1633
2059
  input: context.runtime.stdin,
1634
2060
  output: context.runtime.stderr,
1635
- message: "Customize settings?",
2061
+ message: "Customize build settings?",
1636
2062
  initialValue: false
1637
2063
  })) return {
1638
2064
  framework: options.framework,
@@ -1642,9 +2068,9 @@ async function maybeCustomizeDeploySettings(context, options) {
1642
2068
  input: context.runtime.stdin,
1643
2069
  output: context.runtime.stderr,
1644
2070
  message: `Framework (${options.framework.displayName})`,
1645
- choices: DEPLOY_FRAMEWORKS.map((framework) => ({
1646
- label: frameworkDisplayName(framework),
1647
- value: framework
2071
+ choices: FRAMEWORKS.map((framework) => ({
2072
+ label: framework.displayName,
2073
+ value: framework.key
1648
2074
  }))
1649
2075
  }), "set by you");
1650
2076
  const requestedPort = await textPrompt({
@@ -1677,13 +2103,15 @@ async function maybeCustomizeDeploySettings(context, options) {
1677
2103
  runtime
1678
2104
  };
1679
2105
  }
1680
- function frameworkDisplayName(framework) {
1681
- switch (framework) {
1682
- case "nextjs": return "Next.js";
1683
- case "hono": return "Hono";
1684
- case "tanstack-start": return "TanStack Start";
1685
- case "bun": return "Bun";
1686
- }
2106
+ function maybeRenderDeploySettingsPreview(context, options) {
2107
+ if (context.flags.quiet || context.flags.json) return;
2108
+ context.output.stderr.write(`Detected ${options.framework.displayName}\n${renderDeploySettingsPreview(context.ui, [{
2109
+ key: "framework",
2110
+ value: options.framework.displayName
2111
+ }, {
2112
+ key: "runtime",
2113
+ value: `HTTP ${options.runtime.port}`
2114
+ }]).join("\n")}\n\n`);
1687
2115
  }
1688
2116
  function validateDeployHttpPortText(value) {
1689
2117
  if (!value?.trim()) return;
@@ -1698,10 +2126,15 @@ function formatDeployDirectory(cwd) {
1698
2126
  const basename = path.basename(cwd);
1699
2127
  return basename ? `./${basename}` : ".";
1700
2128
  }
2129
+ function formatAppDirectoryLabel(cwd, appDir) {
2130
+ if (appDir === cwd) return formatDeployDirectory(cwd);
2131
+ const relative = path.relative(cwd, appDir).split(path.sep).join("/");
2132
+ return relative.startsWith("..") ? relative : `./${relative}`;
2133
+ }
1701
2134
  async function readCurrentWorkspaceId(context) {
1702
2135
  const state = await context.stateStore.read();
1703
2136
  if (state.auth?.workspaceId) return state.auth.workspaceId;
1704
- return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
2137
+ return (await readAuthState(context.runtime.env, context.runtime.signal)).workspace?.id ?? null;
1705
2138
  }
1706
2139
  function normalizeBuildType(requestedBuildType) {
1707
2140
  if (!requestedBuildType) return "auto";
@@ -1717,15 +2150,25 @@ function getBuildTypeExamples(commandName) {
1717
2150
  });
1718
2151
  }
1719
2152
  function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
1720
- if (buildType !== "auto" && buildType !== "bun" && entrypoint) {
2153
+ if (buildType !== "auto" && !ENTRYPOINT_BUILD_TYPES.includes(buildType) && entrypoint) {
1721
2154
  if (commandName === "deploy") throw usageError(`App deploy does not accept --entry with ${formatBuildTypeName(buildType)}`, `${formatBuildTypeName(buildType)} apps derive their runtime entrypoint from build output.`, "Remove --entry, or use --framework bun when you want to target a Bun entrypoint directly.", [`prisma-cli app deploy --framework ${buildType}`, "prisma-cli app deploy --framework bun --entry server.ts"], "app");
1722
2155
  throw usageError(`App ${commandName} does not accept --entry with --build-type ${buildType}`, `${formatBuildTypeName(buildType)} apps do not use an entrypoint flag in the current preview.`, `Remove --entry, or rerun prisma-cli app ${commandName} with --build-type bun when you want to target a Bun entrypoint directly.`, [`prisma-cli app ${commandName} --build-type ${buildType}`, `prisma-cli app ${commandName} --build-type bun --entry server.ts`], "app");
1723
2156
  }
1724
2157
  }
1725
- async function requireLocalBuildType(context, buildType, commandName) {
1726
- const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
1727
- if (resolvedBuildType) return resolvedBuildType;
1728
- 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");
2158
+ /**
2159
+ * Resolves the framework for `app run` with the same detection as deploy, so
2160
+ * a repo that deploys without flags also runs without flags. Local dev server
2161
+ * support is intentionally narrower than deploy build support: only Next.js
2162
+ * and Bun/Hono have dev servers in the current preview.
2163
+ */
2164
+ async function resolveLocalRunFramework(context, options) {
2165
+ if (LOCAL_DEV_BUILD_TYPES.includes(options.requestedBuildType)) {
2166
+ if (options.configFramework && computeFrameworkToBuildType(options.configFramework) === options.requestedBuildType) return frameworkFromUserFacingValue(options.configFramework, `set by ${COMPUTE_CONFIG_FILENAME$1}`);
2167
+ return frameworkFromUserFacingValue(options.requestedBuildType, "set by --build-type");
2168
+ }
2169
+ const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
2170
+ if (detected && LOCAL_DEV_BUILD_TYPES.includes(detected.buildType)) return detected;
2171
+ throw usageError("App run 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 run --build-type nextjs", "prisma-cli app run --build-type bun --entry server.ts"], "app");
1729
2172
  }
1730
2173
  function parseLocalPort(requestedPort) {
1731
2174
  if (!requestedPort) return DEFAULT_LOCAL_DEV_PORT;
@@ -1852,6 +2295,18 @@ function localResolutionPinStaleError() {
1852
2295
  ]
1853
2296
  });
1854
2297
  }
2298
+ function localPinReadErrorToDeployError(error) {
2299
+ return matchError(error, {
2300
+ LocalResolutionPinInvalidJsonError: () => localResolutionPinStaleError(),
2301
+ LocalResolutionPinInvalidShapeError: () => localResolutionPinStaleError(),
2302
+ LocalResolutionPinReadAbortedError: (error) => {
2303
+ throw error;
2304
+ },
2305
+ UnhandledException: (error) => {
2306
+ throw error;
2307
+ }
2308
+ });
2309
+ }
1855
2310
  function readDeployEnvOverride(context, name) {
1856
2311
  const value = context.runtime.env[name]?.trim();
1857
2312
  return value ? value : void 0;