@prisma/cli 3.0.0-beta.2 → 3.0.0-beta.20

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 (87) hide show
  1. package/README.md +5 -16
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +26 -7
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +344 -25
  6. package/dist/cli.js +18 -3
  7. package/dist/cli2.js +12 -4
  8. package/dist/commands/agent/index.js +60 -0
  9. package/dist/commands/app/index.js +90 -61
  10. package/dist/commands/auth/index.js +55 -2
  11. package/dist/commands/branch/index.js +2 -27
  12. package/dist/commands/build/index.js +29 -0
  13. package/dist/commands/database/index.js +159 -0
  14. package/dist/commands/env.js +8 -4
  15. package/dist/commands/git/index.js +1 -1
  16. package/dist/commands/project/index.js +1 -1
  17. package/dist/controllers/agent.js +228 -0
  18. package/dist/controllers/app-env-api.js +54 -0
  19. package/dist/controllers/app-env-file.js +181 -0
  20. package/dist/controllers/app-env.js +286 -131
  21. package/dist/controllers/app.js +849 -309
  22. package/dist/controllers/auth.js +255 -11
  23. package/dist/controllers/branch.js +78 -48
  24. package/dist/controllers/build.js +88 -0
  25. package/dist/controllers/database.js +318 -0
  26. package/dist/controllers/project.js +156 -87
  27. package/dist/controllers/select-prompt-port.js +1 -0
  28. package/dist/lib/agent/cli-command.js +17 -0
  29. package/dist/lib/agent/constants.js +12 -0
  30. package/dist/lib/agent/package-manager.js +99 -0
  31. package/dist/lib/agent/setup-status.js +83 -0
  32. package/dist/lib/app/app-interaction.js +5 -0
  33. package/dist/lib/app/{preview-provider.js → app-provider.js} +220 -104
  34. package/dist/lib/app/branch-database-api.js +102 -0
  35. package/dist/lib/app/branch-database-deploy.js +326 -0
  36. package/dist/lib/app/branch-database.js +216 -0
  37. package/dist/lib/app/build-settings.js +93 -0
  38. package/dist/lib/app/build.js +82 -0
  39. package/dist/lib/app/bun-project.js +15 -9
  40. package/dist/lib/app/compute-config.js +145 -0
  41. package/dist/lib/app/deploy-plan.js +59 -0
  42. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
  43. package/dist/lib/app/env-config.js +1 -1
  44. package/dist/lib/app/env-file.js +82 -0
  45. package/dist/lib/app/env-vars.js +28 -2
  46. package/dist/lib/app/local-dev.js +8 -49
  47. package/dist/lib/app/production-deploy-gate.js +162 -0
  48. package/dist/lib/app/read-branch.js +30 -0
  49. package/dist/lib/auth/auth-ops.js +38 -23
  50. package/dist/lib/auth/guard.js +6 -2
  51. package/dist/lib/auth/login.js +117 -15
  52. package/dist/lib/database/provider.js +167 -0
  53. package/dist/lib/diagnostics.js +15 -0
  54. package/dist/lib/fs/home-path.js +24 -0
  55. package/dist/lib/git/local-branch.js +53 -0
  56. package/dist/lib/git/local-status.js +57 -0
  57. package/dist/lib/project/interactive-setup.js +2 -1
  58. package/dist/lib/project/local-pin.js +183 -34
  59. package/dist/lib/project/resolution.js +206 -50
  60. package/dist/lib/project/setup.js +64 -18
  61. package/dist/output/patterns.js +1 -1
  62. package/dist/presenters/agent.js +74 -0
  63. package/dist/presenters/app-env.js +149 -14
  64. package/dist/presenters/app.js +187 -26
  65. package/dist/presenters/auth.js +99 -2
  66. package/dist/presenters/branch.js +37 -102
  67. package/dist/presenters/database.js +274 -0
  68. package/dist/presenters/project.js +44 -26
  69. package/dist/presenters/verbose-context.js +64 -0
  70. package/dist/shell/cli-command.js +12 -0
  71. package/dist/shell/command-arguments.js +7 -1
  72. package/dist/shell/command-meta.js +243 -15
  73. package/dist/shell/command-runner.js +60 -21
  74. package/dist/shell/diagnostics-output.js +57 -0
  75. package/dist/shell/errors.js +61 -1
  76. package/dist/shell/help.js +31 -20
  77. package/dist/shell/output.js +10 -1
  78. package/dist/shell/prompt.js +7 -3
  79. package/dist/shell/runtime.js +10 -6
  80. package/dist/shell/ui.js +42 -3
  81. package/dist/shell/update-check.js +247 -0
  82. package/dist/use-cases/auth.js +68 -1
  83. package/dist/use-cases/branch.js +20 -68
  84. package/dist/use-cases/create-cli-gateways.js +2 -17
  85. package/package.json +27 -10
  86. package/dist/lib/app/preview-build.js +0 -284
  87. package/dist/lib/app/preview-interaction.js +0 -5
@@ -1,54 +1,85 @@
1
- import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
- import { FileTokenStorage } from "../adapters/token-storage.js";
1
+ import { resolvePrismaCliPackageCommand } from "../lib/agent/cli-command.js";
2
+ import { PRISMA_AGENT_INSTALL_ARGS, PRISMA_COMPUTE_AGENT_SKILL } from "../lib/agent/constants.js";
3
+ import { readPrismaAgentSetupStatus, shouldOfferPrismaAgentSetup } from "../lib/agent/setup-status.js";
4
+ import { formatCommandArgument } from "../shell/command-arguments.js";
3
5
  import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
4
- import { renderCommandHeader } from "../shell/ui.js";
5
- import { writeJsonEvent } from "../shell/output.js";
6
+ import { runAgentInstall } from "./agent.js";
7
+ import { renderCommandHeader, renderSummaryLine } from "../shell/ui.js";
6
8
  import { canPrompt } from "../shell/runtime.js";
9
+ import { writeJsonEvent } from "../shell/output.js";
10
+ import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
11
+ import { FileTokenStorage } from "../adapters/token-storage.js";
7
12
  import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
13
+ import "../lib/app/app-interaction.js";
14
+ import { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredAppBuildSettings, resolveInferredAppBuildSettings } from "../lib/app/build-settings.js";
15
+ import { APP_BUILD_TYPES, APP_BUILD_TYPE_LABELS, RESOLVED_APP_BUILD_TYPES, executeAppBuild } from "../lib/app/build.js";
16
+ import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
17
+ import { DomainApiError, createAppProvider } from "../lib/app/app-provider.js";
18
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
19
+ import { buildProjectSetupNextActions, inferTargetName, localProjectWorkspaceMismatchError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
20
+ import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
21
+ import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
22
+ import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
23
+ 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";
24
+ import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
25
+ import { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy } from "../lib/app/deploy-plan.js";
26
+ import { createDeployProgress, createDeployProgressState, createPromoteProgress } from "../lib/app/deploy-progress.js";
27
+ import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
28
+ import { DEFAULT_LOCAL_DEV_PORT, runLocalApp } from "../lib/app/local-dev.js";
29
+ import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
30
+ import { resolveReadBranch } from "../lib/app/read-branch.js";
8
31
  import { requireComputeAuth } from "../lib/auth/guard.js";
9
32
  import { readAuthState } from "../lib/auth/auth-ops.js";
10
- import { parseEnvAssignments } from "../lib/app/env-vars.js";
11
- import { renderDeployOutputRows, renderDeploySettingsPreview } 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
- 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";
33
+ import { readLocalGitBranch } from "../lib/git/local-branch.js";
18
34
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
19
- import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
20
- import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
21
- import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
22
- import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
23
- import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
24
35
  import { createSelectPromptPort } from "./select-prompt-port.js";
25
36
  import { requireAuthenticatedAuthState } from "./auth.js";
26
37
  import { listRealWorkspaceProjects } from "./project.js";
27
- import { access, readFile } from "node:fs/promises";
28
38
  import path from "node:path";
39
+ import { access, readFile } from "node:fs/promises";
40
+ import { COMPUTE_REGIONS, ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, frameworkByKey, frameworkFromAlias, isConfigBackedBuildType } from "@prisma/compute-sdk/config";
41
+ import { Result, matchError } from "better-result";
29
42
  import open from "open";
30
43
  //#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
44
  const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
39
45
  const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
40
46
  const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
47
+ const COMPUTE_REGION_IDS = new Set(COMPUTE_REGIONS);
41
48
  function isRealMode(context) {
42
49
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
43
50
  }
44
- async function runAppBuild(context, entrypoint, requestedBuildType) {
45
- const buildType = normalizeBuildType(requestedBuildType);
46
- assertSupportedEntrypoint(buildType, entrypoint, "build");
51
+ async function runAppBuild(context, options) {
52
+ const compute = await resolveComputeTargetOrThrow(context, options?.configTarget, "build");
53
+ const merged = mergeComputeLocalInputs({
54
+ cli: {
55
+ entrypoint: options?.entrypoint,
56
+ buildType: options?.buildType
57
+ },
58
+ target: compute.target
59
+ });
60
+ const appDir = await resolveComputeAppDir(context, compute);
61
+ let buildType = normalizeBuildType(merged.buildType);
62
+ if (compute.target?.build && buildType === "auto") {
63
+ const detected = await detectDeployFramework(appDir, context.runtime.signal);
64
+ if (!detected) throw frameworkNotDetectedError(appDir);
65
+ buildType = detected.buildType;
66
+ }
67
+ assertSupportedEntrypoint(buildType, merged.entrypoint, "build");
68
+ if (compute.target?.build && buildType !== "auto") assertConfigBackedBuildSettings(buildType);
69
+ const buildSettings = compute.config && compute.target?.build && isConfigBackedBuildType(buildType) ? (await resolveConfiguredAppBuildSettings({
70
+ appPath: appDir,
71
+ buildType,
72
+ configured: compute.target.build,
73
+ configPath: compute.config.configPath,
74
+ signal: context.runtime.signal
75
+ })).settings : void 0;
47
76
  try {
48
- const { artifact, buildType: actualBuildType } = await executePreviewBuild({
49
- appPath: context.runtime.cwd,
50
- entrypoint,
51
- buildType
77
+ const { artifact, buildType: actualBuildType } = await executeAppBuild({
78
+ appPath: appDir,
79
+ entrypoint: merged.entrypoint,
80
+ buildType,
81
+ buildSettings,
82
+ signal: context.runtime.signal
52
83
  });
53
84
  return {
54
85
  command: "app.build",
@@ -61,29 +92,46 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
61
92
  nextSteps: ["prisma-cli app deploy"]
62
93
  };
63
94
  } catch (error) {
64
- if (buildType === "auto" && isAutoBuildDetectionError(error)) throw usageError("App build requires an explicit framework when detection is ambiguous", `This preview auto-detects clear project shapes for ${RESOLVED_PREVIEW_BUILD_TYPES.map(formatBuildTypeName).join(", ")}.`, "Pass a supported --build-type value, or pass --entry <path> for a Bun app.", getBuildTypeExamples("build"), "app");
95
+ if (buildType === "auto" && isAutoBuildDetectionError(error)) throw usageError("App build requires an explicit framework when detection is ambiguous", `This preview auto-detects clear project shapes for ${RESOLVED_APP_BUILD_TYPES.map(formatBuildTypeName).join(", ")}.`, "Pass a supported --build-type value, or pass --entry <path> for a Bun app.", getBuildTypeExamples("build"), "app");
65
96
  throw buildFailedError("Local app build failed", error);
66
97
  }
67
98
  }
68
- async function runAppRun(context, entrypoint, requestedBuildType, requestedPort) {
99
+ async function runAppRun(context, options) {
69
100
  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");
101
+ const compute = await resolveComputeTargetOrThrow(context, options?.configTarget, "run");
102
+ const merged = mergeComputeLocalInputs({
103
+ cli: {
104
+ entrypoint: options?.entrypoint,
105
+ buildType: options?.buildType,
106
+ port: options?.port
107
+ },
108
+ target: compute.target
109
+ });
110
+ 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");
111
+ const appDir = await resolveComputeAppDir(context, compute);
112
+ const buildType = normalizeBuildType(merged.buildType);
113
+ assertSupportedEntrypoint(buildType, merged.entrypoint, "run");
114
+ const port = parseLocalPort(merged.port);
115
+ const framework = await resolveLocalRunFramework(context, {
116
+ requestedBuildType: buildType,
117
+ configFramework: compute.target?.framework ?? null,
118
+ appDir
119
+ });
120
+ const entrypoint = framework.buildType === "bun" ? await resolveDeployEntrypoint(appDir, framework, merged.entrypoint, context.runtime.signal) : merged.entrypoint;
74
121
  let runResult;
75
122
  try {
76
123
  runResult = await runLocalApp({
77
- appPath: context.runtime.cwd,
78
- buildType: resolvedBuildType,
124
+ appPath: appDir,
125
+ buildType: framework.buildType,
79
126
  entrypoint,
80
127
  port,
81
- env: context.runtime.env
128
+ env: context.runtime.env,
129
+ signal: context.runtime.signal
82
130
  });
83
131
  } catch (error) {
84
132
  throw runFailedError("Local app run failed", error);
85
133
  }
86
- if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") process.exitCode = runResult.signal === "SIGINT" ? 130 : 143;
134
+ if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") throw new DOMException("Command canceled", "AbortError");
87
135
  else if (runResult.exitCode !== 0) throw runFailedError("Local app run failed", `The ${formatFrameworkName(runResult.framework)} process exited with code ${runResult.exitCode}.`, runResult.exitCode);
88
136
  return {
89
137
  command: "app.run",
@@ -99,6 +147,107 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
99
147
  }
100
148
  async function runAppDeploy(context, appName, options) {
101
149
  ensurePreviewAppMode(context);
150
+ const loaded = await loadComputeConfig$1(context.runtime.cwd, context.runtime.signal);
151
+ if (loaded.isErr()) throw computeConfigErrorToCliError(loaded.error, "deploy");
152
+ const config = loaded.value;
153
+ const plan = planAppDeploy({
154
+ config,
155
+ requestedTarget: options?.configTarget ?? (config ? inferComputeTargetFromCwd(config, context.runtime.cwd) : void 0),
156
+ hasCreateProject: options?.createProjectName !== void 0
157
+ });
158
+ if (plan.mode === "all") return runAppDeployAll(context, config, plan.targets, appName, options);
159
+ return runSingleAppDeploy(context, appName, options, config);
160
+ }
161
+ async function runAppDeployAll(context, config, plannedTargets, appName, options) {
162
+ assertNoPerAppInputsForDeployAll(context, plannedTargets, appName, options);
163
+ const deployments = [];
164
+ const warnings = [];
165
+ for (const planned of plannedTargets) {
166
+ maybeRenderDeployAllTargetHeader(context, planned);
167
+ const targetOptions = {
168
+ ...options,
169
+ configTarget: planned.targetKey,
170
+ createProjectName: planned.bindsCreateProject ? options?.createProjectName : void 0
171
+ };
172
+ try {
173
+ const single = await runSingleAppDeploy(context, void 0, targetOptions, config);
174
+ deployments.push({
175
+ target: planned.targetKey,
176
+ result: single.result
177
+ });
178
+ warnings.push(...single.warnings);
179
+ } catch (error) {
180
+ throw deployAllFailedError(error, plannedTargets, planned.index, deployments);
181
+ }
182
+ }
183
+ return {
184
+ command: "app.deploy",
185
+ result: { deployments },
186
+ warnings,
187
+ nextSteps: ["prisma-cli app list-deploys <app>"]
188
+ };
189
+ }
190
+ function assertNoPerAppInputsForDeployAll(context, plannedTargets, appName, options) {
191
+ const used = perAppInputsForDeployAll({
192
+ appName,
193
+ framework: options?.framework,
194
+ entrypoint: options?.entrypoint,
195
+ httpPort: options?.httpPort,
196
+ region: options?.region,
197
+ envAssignments: options?.envAssignments,
198
+ appIdEnvVar: {
199
+ name: PRISMA_APP_ID_ENV_VAR,
200
+ value: readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR)
201
+ }
202
+ });
203
+ if (used.length === 0) return;
204
+ const targetKeys = plannedTargets.map((target) => target.targetKey);
205
+ throw usageError(`Deploying all apps does not accept ${used.join(", ")}`, `Without a target, app deploy deploys every configured app (${targetKeys.join(", ")}), 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.", targetKeys.map((target) => `prisma-cli app deploy ${target}`), "app");
206
+ }
207
+ function maybeRenderDeployAllTargetHeader(context, planned) {
208
+ if (context.flags.json || context.flags.quiet) return;
209
+ context.output.stderr.write(`${planned.index > 0 ? "\n" : ""}── ${planned.targetKey} (${planned.index + 1}/${planned.total}) ──\n\n`);
210
+ }
211
+ function deployAllFailedError(error, plannedTargets, failedIndex, deployments) {
212
+ if (!(error instanceof CliError)) return error;
213
+ const failure = describeDeployAllFailure({
214
+ targetKeys: plannedTargets.map((target) => target.targetKey),
215
+ failedIndex,
216
+ completed: deployments.map(({ target, result }) => ({
217
+ target,
218
+ deploymentId: result.deployment.id,
219
+ url: result.deployment.url
220
+ }))
221
+ });
222
+ const contextSentence = failure.contextLines.join(" ");
223
+ return new CliError({
224
+ code: error.code,
225
+ domain: error.domain,
226
+ summary: error.summary,
227
+ why: error.humanLines ? error.why : [error.why, contextSentence].filter(Boolean).join(" "),
228
+ fix: error.fix,
229
+ debug: error.debug,
230
+ where: error.where,
231
+ meta: {
232
+ ...error.meta,
233
+ deployAll: {
234
+ failedTarget: failure.failedTarget,
235
+ completed: failure.completed,
236
+ notAttempted: failure.notAttempted
237
+ }
238
+ },
239
+ docsUrl: error.docsUrl,
240
+ exitCode: error.exitCode,
241
+ nextSteps: error.nextSteps,
242
+ nextActions: error.nextActions,
243
+ humanLines: error.humanLines ? [
244
+ ...error.humanLines,
245
+ "",
246
+ ...failure.contextLines
247
+ ] : void 0
248
+ });
249
+ }
250
+ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
102
251
  const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
103
252
  const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
104
253
  assertExclusiveDeployProjectInputs({
@@ -106,14 +255,30 @@ async function runAppDeploy(context, appName, options) {
106
255
  createProjectName: options?.createProjectName,
107
256
  envProjectId
108
257
  });
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();
258
+ const computeConfig = await resolveComputeTargetOrThrow(context, options?.configTarget, "deploy", { preloaded: preloadedConfig });
259
+ const merged = mergeComputeDeployInputs({
260
+ cli: {
261
+ framework: options?.framework,
262
+ entrypoint: options?.entrypoint,
263
+ httpPort: options?.httpPort,
264
+ region: options?.region,
265
+ envInputs: options?.envAssignments
266
+ },
267
+ target: computeConfig.target,
268
+ configFilename: computeConfig.config?.relativeConfigPath ?? COMPUTE_CONFIG_FILENAME$1
269
+ });
270
+ const appDir = await resolveComputeAppDir(context, computeConfig);
271
+ const projectDir = computeConfig.config?.configDir ?? context.runtime.cwd;
272
+ const agentSetupWarnings = await maybePromptForAgentSetup(context, projectDir);
273
+ const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(projectDir, context.runtime.signal);
274
+ if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
275
+ const localPin = localPinReadResult.value;
112
276
  const branch = await resolveDeployBranch(context, options?.branchName);
113
- if (options?.httpPort) parseDeployHttpPort(options.httpPort);
277
+ if (merged.httpPort) parseDeployHttpPort(merged.httpPort.value);
278
+ const deployRegion = normalizeDeployRegionInput(merged.region);
114
279
  assertSupportedEntrypointForRequestedDeployShape({
115
- requestedFramework: options?.framework,
116
- entrypoint: options?.entrypoint
280
+ requestedFramework: merged.framework?.value,
281
+ entrypoint: merged.entrypoint?.value
117
282
  });
118
283
  const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
119
284
  branch,
@@ -123,25 +288,33 @@ async function runAppDeploy(context, appName, options) {
123
288
  });
124
289
  let localPinResult;
125
290
  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);
291
+ const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction, projectDir);
292
+ if (setupResult.isErr()) throw projectDirectoryBindingErrorToCliError(setupResult.error);
293
+ const projectSetup = setupResult.value;
294
+ localPinResult = projectSetup.localPin;
295
+ maybeRenderProjectLinked(context, projectSetup.directory, projectSetup.project.name, projectSetup.localPin.path);
129
296
  }
130
297
  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" }));
298
+ requestedFramework: merged.framework?.value,
299
+ requestedFrameworkAnnotation: merged.framework?.annotation,
300
+ entrypoint: merged.entrypoint?.value,
301
+ entrypointAnnotation: merged.entrypoint?.annotation,
302
+ appDir
303
+ });
304
+ let runtime = resolveDeployRuntime(merged.httpPort?.value, merged.httpPort?.annotation, framework);
305
+ assertSupportedEntrypoint(framework.buildType, merged.entrypoint?.value, "deploy");
306
+ const envVars = toOptionalEnvVars(await parseEnvInputs(merged.envInputsFromConfig ? projectDir : context.runtime.cwd, merged.envInputs, { commandName: "deploy" }));
137
307
  const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
138
308
  explicitAppName: appName,
139
309
  explicitAppId: envAppId,
310
+ configAppName: merged.configAppName,
311
+ configRegion: deployRegion,
140
312
  firstDeploy: Boolean(target.localPinAction),
141
- inferName: () => inferTargetName(context.runtime.cwd)
313
+ inferName: () => inferTargetName(appDir, context.runtime.signal)
142
314
  });
143
315
  await maybeRenderDeploySetupBlock(context, {
144
316
  includeDirectory: !target.localPinAction,
317
+ appDir,
145
318
  projectName: target.project.name,
146
319
  branchName: target.branch.name,
147
320
  appName: selectedApp.displayName
@@ -150,20 +323,41 @@ async function runAppDeploy(context, appName, options) {
150
323
  framework,
151
324
  runtime,
152
325
  firstDeploy: selectedApp.firstDeploy,
153
- explicitFramework: Boolean(options?.framework),
154
- explicitEntrypoint: Boolean(options?.entrypoint),
155
- explicitHttpPort: Boolean(options?.httpPort)
326
+ explicitFramework: Boolean(merged.framework),
327
+ explicitEntrypoint: Boolean(merged.entrypoint),
328
+ explicitHttpPort: Boolean(merged.httpPort)
156
329
  });
157
330
  framework = customized.framework;
158
331
  runtime = customized.runtime;
332
+ const noPromote = options?.noPromote === true;
333
+ const productionDeployGate = noPromote ? { firstProductionDeploy: false } : await enforceProductionDeployGate(context, provider, {
334
+ appId: selectedApp.appId,
335
+ appName: selectedApp.displayName,
336
+ branchKind: target.branch.kind,
337
+ prod: options?.prod === true
338
+ });
159
339
  const buildType = framework.buildType;
160
- assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
161
- const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint);
340
+ assertSupportedEntrypoint(buildType, merged.entrypoint?.value, "deploy");
341
+ const entrypoint = await resolveDeployEntrypoint(appDir, framework, merged.entrypoint?.value, context.runtime.signal);
342
+ const buildSettingsResolution = await resolveDeployBuildSettings({
343
+ computeConfig,
344
+ appDir,
345
+ buildType,
346
+ signal: context.runtime.signal
347
+ });
348
+ const legacyWarnings = await handleLegacyBuildSettings(context, appDir, buildSettingsResolution.settings);
349
+ maybeRenderDeployBuildSettings(context, buildSettingsResolution);
162
350
  const portMapping = parseDeployPortMapping(String(runtime.port));
163
- const progressState = createPreviewDeployProgressState();
351
+ const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
352
+ db: options?.db,
353
+ providedEnvVars: envVars,
354
+ firstProductionDeploy: productionDeployGate.firstProductionDeploy,
355
+ projectDir
356
+ });
357
+ const progressState = createDeployProgressState();
164
358
  const deployStartedAt = Date.now();
165
359
  const deployResult = await provider.deployApp({
166
- cwd: context.runtime.cwd,
360
+ cwd: appDir,
167
361
  projectId,
168
362
  branchName: target.branch.name,
169
363
  appId: selectedApp.appId,
@@ -171,10 +365,13 @@ async function runAppDeploy(context, appName, options) {
171
365
  region: selectedApp.region,
172
366
  entrypoint,
173
367
  buildType,
368
+ buildSettings: buildSettingsResolution.settings,
174
369
  portMapping,
175
370
  envVars,
371
+ skipPromote: noPromote,
176
372
  interaction: void 0,
177
- progress: createPreviewDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
373
+ signal: context.runtime.signal,
374
+ progress: createDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
178
375
  }).catch((error) => {
179
376
  throw appDeployFailedError(error, progressState);
180
377
  });
@@ -183,41 +380,93 @@ async function runAppDeploy(context, appName, options) {
183
380
  id: deployResult.app.id,
184
381
  name: deployResult.app.name
185
382
  });
186
- await context.stateStore.setKnownLiveDeployment(projectId, deployResult.app.id, deployResult.deployment.id);
383
+ const knownLiveDeploymentId = deployResult.promoted ? deployResult.deployment.id : deployResult.app.liveDeploymentId;
384
+ if (knownLiveDeploymentId) await context.stateStore.setKnownLiveDeployment(projectId, deployResult.app.id, knownLiveDeploymentId);
187
385
  return {
188
386
  command: "app.deploy",
189
387
  result: {
190
388
  workspace: target.workspace,
191
389
  project: target.project,
192
- branch: target.branch,
390
+ branch: toResultBranch(target.branch),
193
391
  resolution: target.resolution,
392
+ branchDatabase: branchDatabaseSetup.result,
194
393
  app: {
195
394
  id: deployResult.app.id,
196
395
  name: deployResult.app.name
197
396
  },
198
397
  deployment: deployResult.deployment,
398
+ promoted: deployResult.promoted,
399
+ deploySettings: {
400
+ config: {
401
+ path: buildSettingsResolution.relativeConfigPath,
402
+ status: buildSettingsResolution.status
403
+ },
404
+ buildCommand: {
405
+ value: buildSettingsResolution.settings.buildCommand,
406
+ source: buildSettingsResolution.settings.buildCommandSource
407
+ },
408
+ outputDirectory: {
409
+ value: buildSettingsResolution.settings.outputDirectory,
410
+ source: buildSettingsResolution.settings.outputDirectorySource
411
+ },
412
+ framework: {
413
+ key: framework.key,
414
+ buildType,
415
+ name: framework.displayName,
416
+ source: framework.annotation
417
+ },
418
+ entrypoint: entrypoint ?? buildSettingsResolution.settings.entrypoint ?? null,
419
+ httpPort: runtime.port,
420
+ region: deployResult.app.region ?? selectedApp.region ?? null,
421
+ envVars: envVarNames(envVars)
422
+ },
199
423
  durationMs: deployDurationMs,
200
424
  localPin: localPinResult
201
425
  },
202
- warnings: [],
203
- nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
426
+ warnings: [
427
+ ...agentSetupWarnings,
428
+ ...legacyWarnings,
429
+ ...branchDatabaseSetup.warnings
430
+ ],
431
+ nextSteps: deployResult.promoted ? ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`] : [`prisma-cli app promote ${deployResult.deployment.id}`, `prisma-cli app show-deploy ${deployResult.deployment.id}`]
204
432
  };
205
433
  }
206
- async function runAppListDeploys(context, appName, projectRef) {
434
+ async function resolveDeployBuildSettings(options) {
435
+ const { computeConfig, appDir, buildType, signal } = options;
436
+ if (computeConfig.target?.build) assertConfigBackedBuildSettings(buildType);
437
+ if (computeConfig.config && computeConfig.target?.build && isConfigBackedBuildType(buildType)) return resolveConfiguredAppBuildSettings({
438
+ appPath: appDir,
439
+ buildType,
440
+ configured: computeConfig.target.build,
441
+ configPath: computeConfig.config.configPath,
442
+ signal
443
+ });
444
+ return resolveInferredAppBuildSettings({
445
+ appPath: appDir,
446
+ buildType,
447
+ signal
448
+ });
449
+ }
450
+ async function runAppListDeploys(context, appName, projectRef, configTarget) {
207
451
  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);
452
+ const compute = await resolveComputeManagementContext(context, configTarget, "list-deploys");
453
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
454
+ commandName: "app list-deploys",
455
+ projectDir: compute.projectDir
456
+ });
457
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName ?? compute.configAppName);
210
458
  if (!selectedApp) return {
211
459
  command: "app.list-deploys",
212
460
  result: {
213
461
  projectId,
462
+ verboseContext: toAppVerboseContext(target),
214
463
  app: null,
215
464
  deployments: []
216
465
  },
217
466
  warnings: [],
218
467
  nextSteps: ["prisma-cli app deploy"]
219
468
  };
220
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
469
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
221
470
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app deploy"]);
222
471
  });
223
472
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -230,6 +479,7 @@ async function runAppListDeploys(context, appName, projectRef) {
230
479
  command: "app.list-deploys",
231
480
  result: {
232
481
  projectId,
482
+ verboseContext: toAppVerboseContext(target),
233
483
  app: {
234
484
  id: deploymentsResult.app.id,
235
485
  name: deploymentsResult.app.name
@@ -240,14 +490,19 @@ async function runAppListDeploys(context, appName, projectRef) {
240
490
  nextSteps: deployments.length > 0 ? [`prisma-cli app show-deploy ${deployments[0]?.id}`] : ["prisma-cli app deploy"]
241
491
  };
242
492
  }
243
- async function runAppShow(context, appName, projectRef) {
493
+ async function runAppShow(context, appName, projectRef, configTarget) {
244
494
  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);
495
+ const compute = await resolveComputeManagementContext(context, configTarget, "show");
496
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
497
+ commandName: "app show",
498
+ projectDir: compute.projectDir
499
+ });
500
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName ?? compute.configAppName);
247
501
  if (!selectedApp) return {
248
502
  command: "app.show",
249
503
  result: {
250
504
  projectId,
505
+ verboseContext: toAppVerboseContext(target),
251
506
  app: null,
252
507
  liveDeployment: null,
253
508
  liveUrl: null,
@@ -256,7 +511,7 @@ async function runAppShow(context, appName, projectRef) {
256
511
  warnings: [],
257
512
  nextSteps: ["prisma-cli app deploy"]
258
513
  };
259
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
514
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
260
515
  throw deployFailedError("Failed to inspect app", error, ["prisma-cli app list-deploys"]);
261
516
  });
262
517
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -270,6 +525,7 @@ async function runAppShow(context, appName, projectRef) {
270
525
  command: "app.show",
271
526
  result: {
272
527
  projectId,
528
+ verboseContext: toAppVerboseContext(target),
273
529
  app: {
274
530
  id: deploymentsResult.app.id,
275
531
  name: deploymentsResult.app.name
@@ -284,7 +540,7 @@ async function runAppShow(context, appName, projectRef) {
284
540
  }
285
541
  async function runAppShowDeploy(context, deploymentId) {
286
542
  ensurePreviewAppMode(context);
287
- const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId).catch((error) => {
543
+ const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
288
544
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
289
545
  });
290
546
  if (!deployment) throw new CliError({
@@ -316,12 +572,17 @@ async function runAppShowDeploy(context, deploymentId) {
316
572
  nextSteps: []
317
573
  };
318
574
  }
319
- async function runAppOpen(context, appName, projectRef) {
575
+ async function runAppOpen(context, appName, projectRef, configTarget) {
320
576
  ensurePreviewAppMode(context);
321
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
577
+ const compute = await resolveComputeManagementContext(context, configTarget, "open");
578
+ appName = appName ?? compute.configAppName;
579
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
580
+ commandName: "app open",
581
+ projectDir: compute.projectDir
582
+ });
322
583
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
323
584
  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) => {
585
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
325
586
  throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
326
587
  });
327
588
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -334,11 +595,16 @@ async function runAppOpen(context, appName, projectRef) {
334
595
  if (!liveDeployment) throw noDeploymentsError("No deployments available to open", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
335
596
  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
597
  const shouldOpen = canPrompt(context);
337
- if (shouldOpen) await open(deploymentsResult.app.liveUrl);
598
+ if (shouldOpen) {
599
+ context.runtime.signal.throwIfAborted();
600
+ await open(deploymentsResult.app.liveUrl);
601
+ context.runtime.signal.throwIfAborted();
602
+ }
338
603
  return {
339
604
  command: "app.open",
340
605
  result: {
341
606
  projectId,
607
+ verboseContext: toAppVerboseContext(target),
342
608
  app: {
343
609
  id: deploymentsResult.app.id,
344
610
  name: deploymentsResult.app.name
@@ -355,7 +621,8 @@ async function runAppDomainAdd(context, hostname, options) {
355
621
  const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
356
622
  const added = await target.provider.addDomain({
357
623
  appId: target.app.id,
358
- hostname: normalizedHostname
624
+ hostname: normalizedHostname,
625
+ signal: context.runtime.signal
359
626
  }).catch((error) => {
360
627
  throw domainCommandError("add", error, normalizedHostname);
361
628
  });
@@ -373,8 +640,8 @@ async function runAppDomainAdd(context, hostname, options) {
373
640
  async function runAppDomainShow(context, hostname, options) {
374
641
  const normalizedHostname = normalizeDomainHostname(hostname);
375
642
  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) => {
643
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show", context.runtime.signal);
644
+ const detail = await target.provider.showDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
378
645
  throw domainCommandError("show", error, normalizedHostname);
379
646
  });
380
647
  return {
@@ -390,9 +657,9 @@ async function runAppDomainShow(context, hostname, options) {
390
657
  async function runAppDomainRemove(context, hostname, options) {
391
658
  const normalizedHostname = normalizeDomainHostname(hostname);
392
659
  const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
393
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
660
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove", context.runtime.signal);
394
661
  await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
395
- await target.provider.removeDomain(domain.id).catch((error) => {
662
+ await target.provider.removeDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
396
663
  throw domainCommandError("remove", error, normalizedHostname);
397
664
  });
398
665
  return {
@@ -409,8 +676,8 @@ async function runAppDomainRemove(context, hostname, options) {
409
676
  async function runAppDomainRetry(context, hostname, options) {
410
677
  const normalizedHostname = normalizeDomainHostname(hostname);
411
678
  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) => {
679
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry", context.runtime.signal);
680
+ const retried = await target.provider.retryDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
414
681
  throw domainCommandError("retry", error, normalizedHostname);
415
682
  });
416
683
  return {
@@ -427,7 +694,7 @@ async function runAppDomainWait(context, hostname, options) {
427
694
  const normalizedHostname = normalizeDomainHostname(hostname);
428
695
  const timeoutMs = parseDomainWaitTimeout(options?.timeout);
429
696
  const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
430
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
697
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait", context.runtime.signal);
431
698
  if (!context.flags.json && !context.flags.quiet) context.output.stderr.write([
432
699
  `app domain wait -> Waiting for ${normalizedHostname} to become active.`,
433
700
  "",
@@ -470,21 +737,25 @@ async function runAppDomainWait(context, hostname, options) {
470
737
  exitCode: 1,
471
738
  nextSteps: [`prisma-cli app domain show ${normalizedHostname}`]
472
739
  });
473
- await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)));
474
- current = await target.provider.showDomain(current.id).catch((error) => {
740
+ await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)), context.runtime.signal);
741
+ current = await target.provider.showDomain(current.id, { signal: context.runtime.signal }).catch((error) => {
475
742
  throw domainCommandError("wait", error, normalizedHostname);
476
743
  });
477
744
  }
478
745
  }
479
- async function runAppLogs(context, appName, deploymentId, projectRef) {
746
+ async function runAppLogs(context, appName, deploymentId, projectRef, configTarget) {
480
747
  ensurePreviewAppMode(context);
481
- const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app logs" });
748
+ const compute = await resolveComputeManagementContext(context, configTarget, "logs");
749
+ appName = appName ?? compute.configAppName;
750
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, {
751
+ commandName: "app logs",
752
+ projectDir: compute.projectDir
753
+ });
482
754
  const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
483
755
  if (!context.flags.json && !context.flags.quiet) {
484
756
  const lines = renderCommandHeader(context.ui, {
485
757
  commandLabel: "app logs",
486
758
  description: "Streaming logs for the selected deployment.",
487
- docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
488
759
  rows: [
489
760
  {
490
761
  key: "project",
@@ -504,6 +775,7 @@ async function runAppLogs(context, appName, deploymentId, projectRef) {
504
775
  }
505
776
  await provider.streamDeploymentLogs({
506
777
  deploymentId: target.deployment.id,
778
+ signal: context.runtime.signal,
507
779
  onRecord: (record) => writeLogRecord(context, record)
508
780
  }).catch((error) => {
509
781
  throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
@@ -513,7 +785,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
513
785
  if (appName) {
514
786
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
515
787
  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) => {
788
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
517
789
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
518
790
  });
519
791
  const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
@@ -526,7 +798,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
526
798
  deployment
527
799
  };
528
800
  }
529
- const shown = await provider.showDeployment(deploymentId).catch((error) => {
801
+ const shown = await provider.showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
530
802
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
531
803
  });
532
804
  if (!shown) throw new CliError({
@@ -569,7 +841,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
569
841
  async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
570
842
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
571
843
  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) => {
844
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
573
845
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
574
846
  });
575
847
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -600,11 +872,16 @@ function writeLogRecord(context, record) {
600
872
  if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
601
873
  }
602
874
  }
603
- async function runAppPromote(context, deploymentId, appName, projectRef) {
875
+ async function runAppPromote(context, deploymentId, appName, projectRef, configTarget) {
604
876
  ensurePreviewAppMode(context);
605
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
877
+ const compute = await resolveComputeManagementContext(context, configTarget, "promote");
878
+ appName = appName ?? compute.configAppName;
879
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
880
+ commandName: "app promote",
881
+ projectDir: compute.projectDir
882
+ });
606
883
  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) => {
884
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
608
885
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
609
886
  });
610
887
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -617,7 +894,8 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
617
894
  if (!targetAlreadyLive) await provider.promoteDeployment({
618
895
  appId: selectedApp.id,
619
896
  deploymentId: targetDeployment.id,
620
- progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
897
+ signal: context.runtime.signal,
898
+ progress: createPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
621
899
  }).catch((error) => {
622
900
  throw deployFailedError("Failed to promote deployment", error, ["prisma-cli app list-deploys"]);
623
901
  });
@@ -626,6 +904,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
626
904
  command: "app.promote",
627
905
  result: {
628
906
  projectId,
907
+ verboseContext: toAppVerboseContext(target),
629
908
  app: {
630
909
  id: deploymentsResult.app.id,
631
910
  name: deploymentsResult.app.name
@@ -640,11 +919,16 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
640
919
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
641
920
  };
642
921
  }
643
- async function runAppRollback(context, appName, deploymentId, projectRef) {
922
+ async function runAppRollback(context, appName, deploymentId, projectRef, configTarget) {
644
923
  ensurePreviewAppMode(context);
645
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
924
+ const compute = await resolveComputeManagementContext(context, configTarget, "rollback");
925
+ appName = appName ?? compute.configAppName;
926
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
927
+ commandName: "app rollback",
928
+ projectDir: compute.projectDir
929
+ });
646
930
  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) => {
931
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
648
932
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
649
933
  });
650
934
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -658,7 +942,8 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
658
942
  if (!targetAlreadyLive) await provider.promoteDeployment({
659
943
  appId: selectedApp.id,
660
944
  deploymentId: targetDeployment.id,
661
- progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
945
+ signal: context.runtime.signal,
946
+ progress: createPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
662
947
  }).catch((error) => {
663
948
  throw deployFailedError("Failed to roll back deployment", error, ["prisma-cli app list-deploys"]);
664
949
  });
@@ -667,6 +952,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
667
952
  command: "app.rollback",
668
953
  result: {
669
954
  projectId,
955
+ verboseContext: toAppVerboseContext(target),
670
956
  app: {
671
957
  id: deploymentsResult.app.id,
672
958
  name: deploymentsResult.app.name
@@ -682,12 +968,17 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
682
968
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
683
969
  };
684
970
  }
685
- async function runAppRemove(context, appName, projectRef) {
971
+ async function runAppRemove(context, appName, projectRef, configTarget) {
686
972
  ensurePreviewAppMode(context);
687
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
973
+ const compute = await resolveComputeManagementContext(context, configTarget, "remove");
974
+ appName = appName ?? compute.configAppName;
975
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
976
+ commandName: "app remove",
977
+ projectDir: compute.projectDir
978
+ });
688
979
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
689
980
  await confirmAppRemoval(context, selectedApp);
690
- const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
981
+ const removedApp = await provider.removeApp(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
691
982
  throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
692
983
  });
693
984
  const warnings = await cleanupRemovedAppState(context, projectId, removedApp.id);
@@ -695,6 +986,7 @@ async function runAppRemove(context, appName, projectRef) {
695
986
  command: "app.remove",
696
987
  result: {
697
988
  projectId,
989
+ verboseContext: toAppVerboseContext(target),
698
990
  app: {
699
991
  id: removedApp.id,
700
992
  name: removedApp.name
@@ -707,6 +999,7 @@ async function runAppRemove(context, appName, projectRef) {
707
999
  }
708
1000
  async function resolveAppDomainTarget(context, options, commandName = "app domain") {
709
1001
  ensurePreviewAppMode(context);
1002
+ const compute = await resolveComputeManagementContext(context, options?.configTarget, commandName.replace(/^app /, ""));
710
1003
  const branch = resolveDomainBranch(options?.branchName);
711
1004
  if (toBranchKind(branch.name) !== "production") throw new CliError({
712
1005
  code: "BRANCH_NOT_DEPLOYABLE",
@@ -722,10 +1015,11 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
722
1015
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
723
1016
  branch,
724
1017
  commandName,
725
- envProjectId
1018
+ envProjectId,
1019
+ projectDir: compute.projectDir
726
1020
  });
727
1021
  const selectedApp = await resolveDomainAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
728
- explicitAppName: options?.appName,
1022
+ explicitAppName: options?.appName ?? compute.configAppName,
729
1023
  explicitAppId: envAppId
730
1024
  });
731
1025
  await context.stateStore.setSelectedApp(projectId, {
@@ -738,7 +1032,7 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
738
1032
  resultTarget: {
739
1033
  workspace: target.workspace,
740
1034
  project: target.project,
741
- branch: target.branch,
1035
+ branch: toResultBranch(target.branch),
742
1036
  app: {
743
1037
  id: selectedApp.id,
744
1038
  name: selectedApp.name
@@ -762,8 +1056,8 @@ async function resolveDomainAppSelection(context, projectId, apps, options) {
762
1056
  if (selectedApp) return selectedApp;
763
1057
  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
1058
  }
765
- async function resolveDomainByHostname(provider, appId, hostname, command) {
766
- const matched = (await provider.listDomains(appId).catch((error) => {
1059
+ async function resolveDomainByHostname(provider, appId, hostname, command, signal) {
1060
+ const matched = (await provider.listDomains(appId, { signal }).catch((error) => {
767
1061
  throw domainCommandError(command, error, hostname);
768
1062
  })).find((domain) => sameDomainHostname(domain.hostname, hostname));
769
1063
  if (matched) return matched;
@@ -798,7 +1092,7 @@ function toAppDomainSummary(domain) {
798
1092
  type: domain.type,
799
1093
  url: domain.url,
800
1094
  hostname: domain.hostname,
801
- computeServiceId: domain.computeServiceId,
1095
+ appId: domain.appId,
802
1096
  status: domain.status,
803
1097
  foundryStatus: domain.foundryStatus,
804
1098
  failureReason: domain.failureReason,
@@ -836,12 +1130,13 @@ async function confirmDomainRemoval(context, target, hostname) {
836
1130
  if (!await confirmPrompt({
837
1131
  input: context.runtime.stdin,
838
1132
  output: context.output.stderr,
1133
+ signal: context.runtime.signal,
839
1134
  message: `Detach ${hostname} from App "${target.app.name}"?`,
840
1135
  initialValue: false
841
1136
  })) throw usageError("Custom domain removal canceled", "The command was canceled before the domain was detached.", "Rerun the command and confirm removal, or pass --yes.", [`prisma-cli app domain remove ${hostname} --app ${target.app.name} --yes`], "app");
842
1137
  }
843
1138
  function domainCommandError(command, error, hostname) {
844
- if (error instanceof PreviewDomainApiError) {
1139
+ if (error instanceof DomainApiError) {
845
1140
  if (command === "add" && (error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
846
1141
  if (command === "add" && error.status === 400) return new CliError({
847
1142
  code: "DOMAIN_HOSTNAME_INVALID",
@@ -994,32 +1289,35 @@ function formatElapsed(milliseconds) {
994
1289
  const remainingSeconds = seconds % 60;
995
1290
  return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
996
1291
  }
997
- async function sleep(milliseconds) {
1292
+ async function sleep(milliseconds, signal) {
998
1293
  if (milliseconds <= 0) return;
999
- await new Promise((resolve) => setTimeout(resolve, milliseconds));
1294
+ signal.throwIfAborted();
1295
+ await new Promise((resolve, reject) => {
1296
+ const onAbort = () => {
1297
+ clearTimeout(timeout);
1298
+ reject(signal.reason);
1299
+ };
1300
+ const timeout = setTimeout(() => {
1301
+ signal.removeEventListener("abort", onAbort);
1302
+ resolve();
1303
+ }, milliseconds);
1304
+ signal.addEventListener("abort", onAbort, { once: true });
1305
+ });
1000
1306
  }
1001
1307
  async function resolveDeployAppSelection(context, projectId, apps, options) {
1002
- if (options.explicitAppName) {
1003
- const matches = findAppsByName(apps, options.explicitAppName);
1004
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.explicitAppName, options.firstDeploy);
1005
- const matched = matches[0];
1006
- if (matched) return {
1007
- appId: matched.id,
1008
- displayName: matched.name,
1009
- annotation: "set by --app",
1010
- firstDeploy: options.firstDeploy
1011
- };
1012
- return {
1013
- appName: options.explicitAppName,
1014
- region: PREVIEW_DEFAULT_REGION,
1015
- displayName: options.explicitAppName,
1016
- annotation: "set by --app",
1017
- firstDeploy: options.firstDeploy
1018
- };
1019
- }
1308
+ const newAppRegion = deployNewAppRegion(options.configRegion);
1309
+ if (options.explicitAppName) return resolveDeployAppByName(context, apps, {
1310
+ name: options.explicitAppName,
1311
+ matchedAnnotation: "set by --app",
1312
+ newAnnotation: "set by --app",
1313
+ requestedRegion: options.configRegion,
1314
+ newAppRegion,
1315
+ firstDeploy: options.firstDeploy
1316
+ });
1020
1317
  if (options.explicitAppId) {
1021
1318
  const matched = apps.find((app) => app.id === options.explicitAppId);
1022
1319
  if (!matched) throw usageError("Selected app does not exist in the resolved project", `The app "${options.explicitAppId}" from ${PRISMA_APP_ID_ENV_VAR} could not be found in resolved project "${projectId}".`, `Unset ${PRISMA_APP_ID_ENV_VAR}, pass --app <name>, or choose an app from prisma-cli app list-deploys.`, ["prisma-cli app list-deploys"], "app");
1320
+ assertDeployRegionMatchesExistingApp(matched, options.configRegion);
1023
1321
  return {
1024
1322
  appId: matched.id,
1025
1323
  displayName: matched.name,
@@ -1027,25 +1325,54 @@ async function resolveDeployAppSelection(context, projectId, apps, options) {
1027
1325
  firstDeploy: options.firstDeploy
1028
1326
  };
1029
1327
  }
1328
+ if (options.configAppName) {
1329
+ const configName = options.configAppName;
1330
+ return resolveDeployAppByName(context, apps, {
1331
+ name: configName.value,
1332
+ matchedAnnotation: configName.annotation,
1333
+ newAnnotation: configName.annotation,
1334
+ requestedRegion: options.configRegion,
1335
+ newAppRegion,
1336
+ firstDeploy: options.firstDeploy
1337
+ });
1338
+ }
1030
1339
  const inferredName = await options.inferName();
1031
- const matches = findAppsByName(apps, inferredName.name);
1032
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
1033
- const matched = matches[0];
1034
- if (matched) return {
1035
- appId: matched.id,
1036
- displayName: matched.name,
1037
- annotation: "existing app on this branch",
1340
+ const newAnnotation = inferredName.source === "package-name" ? "created from package.json" : "created from directory name";
1341
+ return resolveDeployAppByName(context, apps, {
1342
+ name: inferredName.name,
1343
+ matchedAnnotation: "existing app on this branch",
1344
+ newAnnotation,
1345
+ requestedRegion: options.configRegion,
1346
+ newAppRegion,
1038
1347
  firstDeploy: options.firstDeploy
1039
- };
1348
+ });
1349
+ }
1350
+ async function resolveDeployAppByName(context, apps, options) {
1351
+ const matches = findAppsByName(apps, options.name);
1352
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.name, options.requestedRegion, options.newAppRegion, options.firstDeploy);
1353
+ const matched = matches[0];
1354
+ if (matched) {
1355
+ assertDeployRegionMatchesExistingApp(matched, options.requestedRegion);
1356
+ return {
1357
+ appId: matched.id,
1358
+ displayName: matched.name,
1359
+ annotation: options.matchedAnnotation,
1360
+ firstDeploy: options.firstDeploy
1361
+ };
1362
+ }
1040
1363
  return {
1041
- appName: inferredName.name,
1042
- region: PREVIEW_DEFAULT_REGION,
1043
- displayName: inferredName.name,
1044
- annotation: inferredName.source === "package-name" ? "created from package.json" : "created from directory name",
1364
+ appName: options.name,
1365
+ region: options.newAppRegion,
1366
+ displayName: options.name,
1367
+ annotation: options.newAnnotation,
1045
1368
  firstDeploy: options.firstDeploy
1046
1369
  };
1047
1370
  }
1048
- async function resolveAmbiguousDeployApp(context, matches, targetName, firstDeploy) {
1371
+ function assertDeployRegionMatchesExistingApp(app, requestedRegion) {
1372
+ if (requestedRegion?.annotation !== "set by --region" || !app.region || app.region === requestedRegion.value) return;
1373
+ throw usageError("App already exists in another region", `The selected app "${app.name}" is in region "${app.region}", but --region requested "${requestedRegion.value}".`, "Remove --region to deploy the existing app, or pass --app <new-name> to create a new app in that region.", [`prisma-cli app deploy --app ${formatCommandArgument(app.name)}`, `prisma-cli app deploy --app <new-name> --region ${formatCommandArgument(requestedRegion.value)}`], "app");
1374
+ }
1375
+ async function resolveAmbiguousDeployApp(context, matches, targetName, requestedRegion, newAppRegion, firstDeploy) {
1049
1376
  if (canPrompt(context)) {
1050
1377
  const createNew = "__create_new_app__";
1051
1378
  const cancel = "__cancel__";
@@ -1071,11 +1398,12 @@ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDepl
1071
1398
  if (selected === cancel) throw usageError("App selection canceled", "The command was canceled before an app was selected.", "Re-run the command and choose an app, or pass --app <name>.", ["prisma-cli app deploy --app <name>"], "app");
1072
1399
  if (selected === createNew) return {
1073
1400
  appName: targetName,
1074
- region: PREVIEW_DEFAULT_REGION,
1401
+ region: newAppRegion,
1075
1402
  displayName: targetName,
1076
1403
  annotation: "created from package.json",
1077
1404
  firstDeploy
1078
1405
  };
1406
+ assertDeployRegionMatchesExistingApp(selected, requestedRegion);
1079
1407
  return {
1080
1408
  appId: selected.id,
1081
1409
  displayName: selected.name,
@@ -1097,6 +1425,9 @@ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDepl
1097
1425
  nextSteps: ["prisma-cli app deploy --app <name>"]
1098
1426
  });
1099
1427
  }
1428
+ function deployNewAppRegion(configRegion) {
1429
+ return configRegion?.value ?? "eu-central-1";
1430
+ }
1100
1431
  async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
1101
1432
  if (explicitAppName) {
1102
1433
  const matched = findAppByName(apps, explicitAppName);
@@ -1139,6 +1470,7 @@ async function confirmAppRemoval(context, app) {
1139
1470
  await textPrompt({
1140
1471
  input: context.runtime.stdin,
1141
1472
  output: context.output.stderr,
1473
+ signal: context.runtime.signal,
1142
1474
  message: `Type ${app.name} to confirm app removal`,
1143
1475
  placeholder: app.name,
1144
1476
  validate: (value) => value === app.name ? void 0 : `Type "${app.name}" to confirm removal.`
@@ -1211,7 +1543,10 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
1211
1543
  });
1212
1544
  }
1213
1545
  async function listApps(context, provider, projectId, branchName) {
1214
- return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
1546
+ return provider.listApps(projectId, {
1547
+ branchName,
1548
+ signal: context.runtime.signal
1549
+ }).then(sortApps).catch((error) => {
1215
1550
  if (isMissingProjectError(error)) throw new CliError({
1216
1551
  code: "PROJECT_NOT_FOUND",
1217
1552
  domain: "project",
@@ -1229,20 +1564,20 @@ async function requirePreviewAppProvider(context) {
1229
1564
  return provider;
1230
1565
  }
1231
1566
  async function requirePreviewAppProviderWithClient(context) {
1232
- const client = await requireComputeAuth(context.runtime.env);
1567
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
1233
1568
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
1234
1569
  return {
1235
1570
  client,
1236
- provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
1571
+ provider: createAppProvider(client, createPreviewLogAuthOptions(context.runtime.env, context.runtime.signal))
1237
1572
  };
1238
1573
  }
1239
- function createPreviewLogAuthOptions(env) {
1574
+ function createPreviewLogAuthOptions(env, signal) {
1240
1575
  const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
1241
1576
  if (rawToken) return {
1242
1577
  baseUrl: getApiBaseUrl(env),
1243
1578
  getToken: async () => rawToken
1244
1579
  };
1245
- const tokenStorage = new FileTokenStorage(env);
1580
+ const tokenStorage = new FileTokenStorage(env, signal);
1246
1581
  return {
1247
1582
  baseUrl: getApiBaseUrl(env),
1248
1583
  getToken: async () => {
@@ -1275,20 +1610,29 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
1275
1610
  async function resolveProjectContext(context, client, explicitProject, options) {
1276
1611
  const authState = await requireAuthenticatedAuthState(context);
1277
1612
  if (!authState.workspace) throw workspaceRequiredError();
1278
- const resolved = await resolveProjectTarget({
1613
+ const resolvedResult = await resolveProjectTarget({
1279
1614
  context,
1280
1615
  workspace: authState.workspace,
1281
1616
  explicitProject,
1282
1617
  envProjectId: options?.envProjectId,
1283
- listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
1618
+ projectDir: options?.projectDir,
1619
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
1284
1620
  commandName: options?.commandName
1285
1621
  });
1286
- const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
1622
+ if (resolvedResult.isErr()) throw projectResolutionErrorToCliError(resolvedResult.error);
1623
+ const resolved = resolvedResult.value;
1624
+ const requested = options?.branch ?? await resolveDeployBranch(context, void 0);
1625
+ const remoteBranch = options?.branch ? null : await resolveReadBranch(client, {
1626
+ projectId: resolved.project.id,
1627
+ branchName: requested.name,
1628
+ signal: context.runtime.signal
1629
+ });
1287
1630
  return {
1288
1631
  ...resolved,
1289
- branch: {
1290
- name: branch.name,
1291
- kind: toBranchKind(branch.name)
1632
+ branch: remoteBranch ?? {
1633
+ id: null,
1634
+ name: requested.name,
1635
+ kind: toBranchKind(requested.name)
1292
1636
  }
1293
1637
  };
1294
1638
  }
@@ -1296,8 +1640,8 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1296
1640
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1297
1641
  if (!workspace) throw workspaceRequiredError();
1298
1642
  const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1299
- const projects = await listRealWorkspaceProjects(client, workspace);
1300
- if (explicitProject) return withDeployBranch({
1643
+ const projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
1644
+ if (explicitProject) return withRemoteDeployBranch(provider, {
1301
1645
  workspace,
1302
1646
  project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
1303
1647
  resolution: {
@@ -1306,25 +1650,25 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1306
1650
  targetNameSource: "explicit"
1307
1651
  },
1308
1652
  localPinAction: "linked"
1309
- }, branch);
1653
+ }, branch, context.runtime.signal);
1310
1654
  if (options.createProjectName) {
1311
1655
  const projectName = options.createProjectName.trim();
1312
1656
  if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1313
- return withDeployBranch({
1657
+ return withRemoteDeployBranch(provider, {
1314
1658
  workspace,
1315
- project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace)),
1659
+ project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
1316
1660
  resolution: {
1317
1661
  projectSource: "created",
1318
1662
  targetName: projectName,
1319
1663
  targetNameSource: "explicit"
1320
1664
  },
1321
1665
  localPinAction: "created"
1322
- }, branch);
1666
+ }, branch, context.runtime.signal);
1323
1667
  }
1324
1668
  if (options.envProjectId) {
1325
1669
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
1326
1670
  if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1327
- return withDeployBranch({
1671
+ return withRemoteDeployBranch(provider, {
1328
1672
  workspace,
1329
1673
  project: toProjectSummary(project),
1330
1674
  resolution: {
@@ -1332,14 +1676,18 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1332
1676
  targetName: options.envProjectId,
1333
1677
  targetNameSource: "env"
1334
1678
  }
1335
- }, branch);
1679
+ }, branch, context.runtime.signal);
1336
1680
  }
1337
1681
  const localPin = options.localPin;
1338
1682
  if (localPin.kind === "present") {
1339
- if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1683
+ if (localPin.pin.workspaceId !== workspace.id) throw localProjectWorkspaceMismatchError({
1684
+ pinnedWorkspaceId: localPin.pin.workspaceId,
1685
+ pinnedProjectId: localPin.pin.projectId,
1686
+ activeWorkspace: workspace
1687
+ });
1340
1688
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1341
1689
  if (!project) throw localResolutionPinStaleError();
1342
- return withDeployBranch({
1690
+ return withRemoteDeployBranch(provider, {
1343
1691
  workspace,
1344
1692
  project: toProjectSummary(project),
1345
1693
  resolution: {
@@ -1347,10 +1695,10 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1347
1695
  targetName: project.name,
1348
1696
  targetNameSource: "local-pin"
1349
1697
  }
1350
- }, branch);
1698
+ }, branch, context.runtime.signal);
1351
1699
  }
1352
1700
  const platformMapping = await resolveDurablePlatformMapping();
1353
- if (platformMapping && platformMapping.workspace.id === workspace.id) return withDeployBranch({
1701
+ if (platformMapping && platformMapping.workspace.id === workspace.id) return withRemoteDeployBranch(provider, {
1354
1702
  workspace,
1355
1703
  project: toProjectSummary(platformMapping),
1356
1704
  resolution: {
@@ -1358,15 +1706,15 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1358
1706
  targetName: platformMapping.name,
1359
1707
  targetNameSource: "platform-mapping"
1360
1708
  }
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));
1709
+ }, branch, context.runtime.signal);
1710
+ if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch, context.runtime.signal);
1711
+ throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1364
1712
  }
1365
1713
  async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
1366
1714
  const setup = await promptForProjectSetupChoice({
1367
1715
  context,
1368
1716
  projects,
1369
- createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace),
1717
+ createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal),
1370
1718
  cancel: {
1371
1719
  why: "Deploy needs a Project before it can continue.",
1372
1720
  fix: "Choose an existing Project or create a new one, then rerun deploy.",
@@ -1384,8 +1732,11 @@ async function resolveInteractiveDeployProjectSetup(context, provider, workspace
1384
1732
  localPinAction: setup.action
1385
1733
  };
1386
1734
  }
1387
- async function createProjectForDeploySetup(provider, projectName, workspace) {
1388
- const created = await provider.createProject({ name: projectName }).catch((error) => {
1735
+ async function createProjectForDeploySetup(provider, projectName, workspace, signal) {
1736
+ const created = await provider.createProject({
1737
+ name: projectName,
1738
+ signal
1739
+ }).catch((error) => {
1389
1740
  throw projectCreateFailedError(error, projectName, workspace, {
1390
1741
  nextSteps: [
1391
1742
  "prisma-cli project list",
@@ -1402,18 +1753,46 @@ async function createProjectForDeploySetup(provider, projectName, workspace) {
1402
1753
  workspace
1403
1754
  };
1404
1755
  }
1405
- function withDeployBranch(target, branch) {
1756
+ async function withRemoteDeployBranch(provider, target, branch, signal) {
1757
+ const remoteBranch = await provider.resolveBranch(target.project.id, {
1758
+ branchName: branch.name,
1759
+ signal
1760
+ });
1406
1761
  return {
1407
1762
  ...target,
1408
1763
  branch: {
1409
- name: branch.name,
1410
- kind: toBranchKind(branch.name)
1764
+ id: remoteBranch.id,
1765
+ name: remoteBranch.name,
1766
+ kind: remoteBranch.role
1411
1767
  }
1412
1768
  };
1413
1769
  }
1414
1770
  function toBranchKind(name) {
1415
1771
  return name === "production" || name === "main" ? "production" : "preview";
1416
1772
  }
1773
+ function toResultBranch(branch) {
1774
+ return {
1775
+ id: branch.id,
1776
+ name: branch.name,
1777
+ kind: branch.kind
1778
+ };
1779
+ }
1780
+ function toAppVerboseContext(target) {
1781
+ return {
1782
+ workspace: target.workspace,
1783
+ project: target.project,
1784
+ branch: target.branch,
1785
+ resolution: target.resolution
1786
+ };
1787
+ }
1788
+ function toBranchDatabaseDeployBranch(branch) {
1789
+ if (!branch.id) throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);
1790
+ return {
1791
+ id: branch.id,
1792
+ name: branch.name,
1793
+ kind: branch.kind
1794
+ };
1795
+ }
1417
1796
  function assertExclusiveDeployProjectInputs(options) {
1418
1797
  const provided = [
1419
1798
  options.projectRef ? "--project" : null,
@@ -1432,7 +1811,7 @@ async function resolveDeployBranch(context, explicitBranchName) {
1432
1811
  name: explicitBranchName,
1433
1812
  annotation: "set by --branch"
1434
1813
  };
1435
- const gitBranch = await readLocalGitBranch(context.runtime.cwd);
1814
+ const gitBranch = await readLocalGitBranch(context.runtime.cwd, context.runtime.signal);
1436
1815
  if (gitBranch) return {
1437
1816
  name: gitBranch,
1438
1817
  annotation: "from local Git branch"
@@ -1442,45 +1821,130 @@ async function resolveDeployBranch(context, explicitBranchName) {
1442
1821
  annotation: "default"
1443
1822
  };
1444
1823
  }
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;
1824
+ async function resolveComputeTargetOrThrow(context, configTarget, commandName, options) {
1825
+ let config;
1826
+ if (options?.preloaded !== void 0) config = options.preloaded;
1827
+ else {
1828
+ const loaded = await loadComputeConfig$1(context.runtime.cwd, context.runtime.signal);
1829
+ if (loaded.isErr()) throw computeConfigErrorToCliError(loaded.error, commandName);
1830
+ config = loaded.value;
1453
1831
  }
1454
- return null;
1832
+ if (!config) {
1833
+ 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");
1834
+ return {
1835
+ config: null,
1836
+ target: null
1837
+ };
1838
+ }
1839
+ const requestedTarget = configTarget ?? inferComputeTargetFromCwd(config, context.runtime.cwd);
1840
+ const selected = selectComputeDeployTarget(config, requestedTarget);
1841
+ if (selected.isErr()) {
1842
+ if (options?.targetOptional && selected.error instanceof ComputeConfigTargetRequiredError) return {
1843
+ config,
1844
+ target: null
1845
+ };
1846
+ throw computeConfigErrorToCliError(selected.error, commandName);
1847
+ }
1848
+ return {
1849
+ config,
1850
+ target: selected.value
1851
+ };
1455
1852
  }
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 {}
1853
+ /**
1854
+ * Compute-config context for app management commands: the project directory
1855
+ * (where `.prisma/local.json` lives) and the config-selected app name, which
1856
+ * ranks below `--app` but above the remembered app selection.
1857
+ */
1858
+ async function resolveComputeManagementContext(context, configTarget, commandName) {
1859
+ const compute = await resolveComputeTargetOrThrow(context, configTarget, commandName, { targetOptional: true });
1860
+ return {
1861
+ projectDir: compute.config?.configDir ?? context.runtime.cwd,
1862
+ configAppName: compute.target?.name ?? compute.target?.key ?? void 0
1863
+ };
1864
+ }
1865
+ async function resolveComputeAppDir(context, compute) {
1866
+ if (!compute.config || !compute.target) return context.runtime.cwd;
1867
+ const appDir = computeTargetAppDir(compute.config, compute.target);
1868
+ if (!compute.target.root) return appDir;
1869
+ context.runtime.signal.throwIfAborted();
1461
1870
  try {
1462
- await access(path.join(gitPath, "HEAD"));
1463
- return path.join(gitPath, "HEAD");
1464
- } catch {
1465
- return null;
1871
+ await access(appDir);
1872
+ context.runtime.signal.throwIfAborted();
1873
+ } catch (error) {
1874
+ if (context.runtime.signal.aborted) throw error;
1875
+ throw new CliError({
1876
+ code: "COMPUTE_CONFIG_INVALID",
1877
+ domain: "app",
1878
+ summary: `App root "${compute.target.root}" does not exist`,
1879
+ why: `${compute.config.relativeConfigPath} points the selected app at "${compute.target.root}", but that directory does not exist.`,
1880
+ fix: `Fix the root path in ${compute.config.relativeConfigPath} or create the directory.`,
1881
+ where: appDir,
1882
+ meta: {
1883
+ appRoot: compute.target.root,
1884
+ appDir
1885
+ },
1886
+ exitCode: 2,
1887
+ nextSteps: ["prisma-cli app deploy"]
1888
+ });
1889
+ }
1890
+ return appDir;
1891
+ }
1892
+ /**
1893
+ * `prisma.app.json` is no longer read or written. A leftover file that
1894
+ * matches the effective settings only warns; one with custom values fails
1895
+ * with migration guidance so builds never silently change.
1896
+ */
1897
+ async function handleLegacyBuildSettings(context, appDir, effective) {
1898
+ const legacy = await detectLegacyBuildSettings({
1899
+ appPath: appDir,
1900
+ effective,
1901
+ signal: context.runtime.signal
1902
+ });
1903
+ switch (legacy.kind) {
1904
+ case "absent": return [];
1905
+ case "matching": return [`${PRISMA_APP_CONFIG_FILENAME} is no longer used and matches the resolved build settings. Delete it.`];
1906
+ case "invalid": return [`${PRISMA_APP_CONFIG_FILENAME} is no longer used and could not be parsed. Delete it.`];
1907
+ case "custom": {
1908
+ const buildBlock = [
1909
+ "build: {",
1910
+ ` command: ${legacy.buildCommand === null ? "null" : JSON.stringify(legacy.buildCommand)},`,
1911
+ ` outputDirectory: ${JSON.stringify(legacy.outputDirectory)},`,
1912
+ "}"
1913
+ ].join(" ");
1914
+ throw new CliError({
1915
+ code: "BUILD_SETTINGS_MIGRATION_REQUIRED",
1916
+ domain: "app",
1917
+ summary: `${PRISMA_APP_CONFIG_FILENAME} is no longer supported`,
1918
+ why: `${PRISMA_APP_CONFIG_FILENAME} contains custom build settings that differ from the resolved defaults, and the file is no longer read.`,
1919
+ fix: `Move the settings into prisma.compute.ts as \`${buildBlock}\` on this app, then delete ${PRISMA_APP_CONFIG_FILENAME}.`,
1920
+ where: legacy.configPath,
1921
+ meta: {
1922
+ configPath: legacy.configPath,
1923
+ buildCommand: legacy.buildCommand,
1924
+ outputDirectory: legacy.outputDirectory
1925
+ },
1926
+ exitCode: 2,
1927
+ nextSteps: ["prisma-cli app deploy"]
1928
+ });
1929
+ }
1466
1930
  }
1467
1931
  }
1468
1932
  async function resolveDeployFramework(context, options) {
1469
- if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1933
+ if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, options.requestedFrameworkAnnotation ?? "set by --framework");
1470
1934
  if (options.entrypoint) return {
1471
1935
  key: "bun",
1472
1936
  buildType: "bun",
1473
1937
  displayName: "Bun",
1474
- annotation: "set by --entry"
1938
+ annotation: options.entrypointAnnotation ?? "set by --entry"
1475
1939
  };
1476
- const detected = await detectDeployFramework(context.runtime.cwd);
1940
+ const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
1477
1941
  if (detected) return detected;
1478
- throw frameworkNotDetectedError(context.runtime.cwd);
1942
+ throw frameworkNotDetectedError(options.appDir);
1479
1943
  }
1480
- function resolveDeployRuntime(requestedHttpPort, framework) {
1944
+ function resolveDeployRuntime(requestedHttpPort, requestedHttpPortAnnotation, framework) {
1481
1945
  if (requestedHttpPort) return {
1482
1946
  port: parseDeployHttpPort(requestedHttpPort),
1483
- annotation: "set by --http-port"
1947
+ annotation: requestedHttpPortAnnotation ?? "set by --http-port"
1484
1948
  };
1485
1949
  return {
1486
1950
  port: FRAMEWORK_DEFAULT_HTTP_PORT,
@@ -1491,65 +1955,62 @@ function assertSupportedEntrypointForRequestedDeployShape(options) {
1491
1955
  if (!options.requestedFramework) return;
1492
1956
  assertSupportedEntrypoint(frameworkFromUserFacingValue(options.requestedFramework, "set by --framework").buildType, options.entrypoint, "deploy");
1493
1957
  }
1494
- async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint) {
1958
+ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signal) {
1495
1959
  if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
1496
- const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd));
1960
+ const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd, signal));
1497
1961
  if (packageEntrypoint) return packageEntrypoint;
1498
- if (framework.key !== "hono") return;
1499
- const defaultEntrypoint = "src/index.ts";
1962
+ const defaultEntrypoint = frameworkFromAlias(framework.key)?.defaultEntrypoint;
1963
+ if (!defaultEntrypoint) return;
1964
+ signal.throwIfAborted();
1500
1965
  try {
1501
1966
  await access(path.join(cwd, defaultEntrypoint));
1967
+ signal.throwIfAborted();
1502
1968
  return defaultEntrypoint;
1503
1969
  } catch (error) {
1970
+ if (signal.aborted) throw error;
1504
1971
  if (error.code !== "ENOENT") throw error;
1505
1972
  return;
1506
1973
  }
1507
1974
  }
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
- };
1975
+ async function detectDeployFramework(cwd, signal) {
1976
+ const packageJson = await readBunPackageJson(cwd, signal);
1977
+ for (const framework of FRAMEWORKS) {
1978
+ if (framework.detectConfigFiles.length === 0 && framework.detectPackages.length === 0) continue;
1979
+ const configFile = await detectFrameworkConfigFile(cwd, framework, signal);
1980
+ if (!configFile.exists && !hasAnyPackageDependency(packageJson, framework.detectPackages)) continue;
1981
+ const annotation = framework.key === "nextjs" && configFile.standalone ? "standalone output detected" : configFile.exists ? `detected from ${path.basename(configFile.path)}` : "detected from package.json";
1982
+ return {
1983
+ key: framework.key,
1984
+ buildType: framework.buildType,
1985
+ displayName: framework.displayName,
1986
+ annotation
1987
+ };
1988
+ }
1529
1989
  return null;
1530
1990
  }
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
- ]) {
1991
+ async function detectFrameworkConfigFile(cwd, framework, signal) {
1992
+ for (const candidate of framework.detectConfigFiles) {
1539
1993
  const filePath = path.join(cwd, candidate);
1994
+ signal.throwIfAborted();
1540
1995
  try {
1541
- const content = await readFile(filePath, "utf8");
1996
+ const content = await readFile(filePath, {
1997
+ encoding: "utf8",
1998
+ signal
1999
+ });
1542
2000
  return {
1543
2001
  exists: true,
1544
- standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
2002
+ standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),
2003
+ path: filePath
1545
2004
  };
1546
2005
  } catch (error) {
2006
+ if (signal.aborted) throw error;
1547
2007
  if (error.code !== "ENOENT") throw error;
1548
2008
  }
1549
2009
  }
1550
2010
  return {
1551
2011
  exists: false,
1552
- standalone: false
2012
+ standalone: false,
2013
+ path: null
1553
2014
  };
1554
2015
  }
1555
2016
  function hasPackageDependency(packageJson, dependencyName) {
@@ -1562,48 +2023,36 @@ function hasDependency(dependencies, dependencyName) {
1562
2023
  return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1563
2024
  }
1564
2025
  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
- }
2026
+ const framework = frameworkFromAlias(value);
2027
+ if (!framework) throw frameworkNotDetectedError(void 0, value);
2028
+ return {
2029
+ key: framework.key,
2030
+ buildType: framework.buildType,
2031
+ displayName: framework.displayName,
2032
+ annotation
2033
+ };
2034
+ }
2035
+ function assertConfigBackedBuildSettings(buildType) {
2036
+ if (isConfigBackedBuildType(buildType)) return;
2037
+ const displayName = FRAMEWORKS.find((framework) => framework.buildType === buildType)?.displayName ?? buildType;
2038
+ throw new CliError({
2039
+ code: "BUILD_SETTINGS_UNSUPPORTED",
2040
+ domain: "app",
2041
+ summary: `build settings are not supported for ${displayName} apps`,
2042
+ why: `${displayName} deploys run \`${buildType} build\` and package its output automatically.`,
2043
+ fix: "Remove the `build` block from prisma.compute.ts for this app.",
2044
+ exitCode: 2
2045
+ });
1597
2046
  }
1598
2047
  function frameworkNotDetectedError(cwd, requestedFramework) {
1599
- const supported = "Next.js, Hono, TanStack Start, Bun";
2048
+ const supported = FRAMEWORKS.map((framework) => framework.displayName).join(", ");
1600
2049
  const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
1601
2050
  return new CliError({
1602
2051
  code: "FRAMEWORK_NOT_DETECTED",
1603
2052
  domain: "app",
1604
2053
  summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
1605
2054
  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.",
2055
+ 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
2056
  exitCode: 2,
1608
2057
  nextSteps: [
1609
2058
  "prisma-cli app deploy --framework nextjs",
@@ -1616,14 +2065,72 @@ function frameworkNotDetectedError(cwd, requestedFramework) {
1616
2065
  }
1617
2066
  async function maybeRenderDeploySetupBlock(context, details) {
1618
2067
  if (context.flags.json || context.flags.quiet) return;
1619
- const directory = formatDeployDirectory(context.runtime.cwd);
2068
+ const directory = formatAppDirectoryLabel(context.runtime.cwd, details.appDir);
1620
2069
  const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
1621
2070
  context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1622
2071
  }
2072
+ function maybeRenderDeployBuildSettings(context, resolution) {
2073
+ if (context.flags.json || context.flags.quiet) return;
2074
+ const settings = resolution.settings;
2075
+ const title = resolution.status === "config" ? `Using ${resolution.relativeConfigPath}` : "Build settings";
2076
+ context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [
2077
+ {
2078
+ label: "Build Command",
2079
+ value: settings.buildCommand ?? "none",
2080
+ origin: settings.buildCommandSource ?? void 0
2081
+ },
2082
+ {
2083
+ label: "Output Directory",
2084
+ value: settings.outputDirectory,
2085
+ origin: settings.outputDirectorySource ?? void 0
2086
+ },
2087
+ ...settings.entrypoint ? [{
2088
+ label: "Entrypoint",
2089
+ value: settings.entrypoint,
2090
+ origin: settings.entrypointSource ?? void 0
2091
+ }] : []
2092
+ ]).join("\n")}\n\n`);
2093
+ }
1623
2094
  function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
1624
2095
  if (context.flags.json || context.flags.quiet) return;
1625
2096
  context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
1626
2097
  }
2098
+ async function maybePromptForAgentSetup(context, projectDir) {
2099
+ if (!canPrompt(context) || context.flags.yes) return [];
2100
+ if (!shouldOfferPrismaAgentSetup(await readPrismaAgentSetupStatus({
2101
+ cwd: projectDir,
2102
+ stateStore: context.stateStore,
2103
+ signal: context.runtime.signal,
2104
+ requiredSkill: "prisma-compute"
2105
+ }))) return [];
2106
+ if (!await confirmPrompt({
2107
+ input: context.runtime.stdin,
2108
+ output: context.runtime.stderr,
2109
+ signal: context.runtime.signal,
2110
+ message: "Install the Prisma Compute skill for this project?",
2111
+ initialValue: true
2112
+ })) {
2113
+ await context.stateStore.setAgentSetupPromptDismissedAt((/* @__PURE__ */ new Date()).toISOString());
2114
+ return [];
2115
+ }
2116
+ try {
2117
+ await runAgentInstall(context, { skill: [PRISMA_COMPUTE_AGENT_SKILL] }, "install", { cwd: projectDir });
2118
+ if (!context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderSummaryLine(context.ui, "success", "Installed the Prisma Compute skill for this project.")}\n\n`);
2119
+ return [];
2120
+ } catch (error) {
2121
+ const message = error instanceof Error ? error.message : "Prisma skill install failed.";
2122
+ if (!context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderSummaryLine(context.ui, "warning", `Skipped Prisma skill install: ${message}`)}\n\n`);
2123
+ return [`The Prisma Compute skill was not installed. Run ${await resolvePrismaCliPackageCommand({
2124
+ cwd: projectDir,
2125
+ signal: context.runtime.signal,
2126
+ args: [
2127
+ ...PRISMA_AGENT_INSTALL_ARGS,
2128
+ "--skill",
2129
+ PRISMA_COMPUTE_AGENT_SKILL
2130
+ ]
2131
+ })} to try again.`];
2132
+ }
2133
+ }
1627
2134
  async function maybeCustomizeDeploySettings(context, options) {
1628
2135
  if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitEntrypoint || options.explicitHttpPort || !canPrompt(context)) return {
1629
2136
  framework: options.framework,
@@ -1636,6 +2143,7 @@ async function maybeCustomizeDeploySettings(context, options) {
1636
2143
  if (!await confirmPrompt({
1637
2144
  input: context.runtime.stdin,
1638
2145
  output: context.runtime.stderr,
2146
+ signal: context.runtime.signal,
1639
2147
  message: "Customize build settings?",
1640
2148
  initialValue: false
1641
2149
  })) return {
@@ -1645,15 +2153,17 @@ async function maybeCustomizeDeploySettings(context, options) {
1645
2153
  const framework = frameworkFromUserFacingValue(await selectPrompt({
1646
2154
  input: context.runtime.stdin,
1647
2155
  output: context.runtime.stderr,
2156
+ signal: context.runtime.signal,
1648
2157
  message: `Framework (${options.framework.displayName})`,
1649
- choices: DEPLOY_FRAMEWORKS.map((framework) => ({
1650
- label: frameworkDisplayName(framework),
1651
- value: framework
2158
+ choices: FRAMEWORKS.map((framework) => ({
2159
+ label: framework.displayName,
2160
+ value: framework.key
1652
2161
  }))
1653
2162
  }), "set by you");
1654
2163
  const requestedPort = await textPrompt({
1655
2164
  input: context.runtime.stdin,
1656
2165
  output: context.runtime.stderr,
2166
+ signal: context.runtime.signal,
1657
2167
  message: `HTTP port (${options.runtime.port})`,
1658
2168
  placeholder: String(options.runtime.port),
1659
2169
  validate: validateDeployHttpPortText
@@ -1691,14 +2201,6 @@ function maybeRenderDeploySettingsPreview(context, options) {
1691
2201
  value: `HTTP ${options.runtime.port}`
1692
2202
  }]).join("\n")}\n\n`);
1693
2203
  }
1694
- function frameworkDisplayName(framework) {
1695
- switch (framework) {
1696
- case "nextjs": return "Next.js";
1697
- case "hono": return "Hono";
1698
- case "tanstack-start": return "TanStack Start";
1699
- case "bun": return "Bun";
1700
- }
1701
- }
1702
2204
  function validateDeployHttpPortText(value) {
1703
2205
  if (!value?.trim()) return;
1704
2206
  try {
@@ -1712,34 +2214,49 @@ function formatDeployDirectory(cwd) {
1712
2214
  const basename = path.basename(cwd);
1713
2215
  return basename ? `./${basename}` : ".";
1714
2216
  }
2217
+ function formatAppDirectoryLabel(cwd, appDir) {
2218
+ if (appDir === cwd) return formatDeployDirectory(cwd);
2219
+ const relative = path.relative(cwd, appDir).split(path.sep).join("/");
2220
+ return relative.startsWith("..") ? relative : `./${relative}`;
2221
+ }
1715
2222
  async function readCurrentWorkspaceId(context) {
1716
2223
  const state = await context.stateStore.read();
1717
2224
  if (state.auth?.workspaceId) return state.auth.workspaceId;
1718
- return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
2225
+ return (await readAuthState(context.runtime.env, context.runtime.signal)).workspace?.id ?? null;
1719
2226
  }
1720
2227
  function normalizeBuildType(requestedBuildType) {
1721
2228
  if (!requestedBuildType) return "auto";
1722
2229
  if (isPreviewBuildType(requestedBuildType)) return requestedBuildType;
1723
- throw usageError(`Unsupported build type "${requestedBuildType}"`, `Only ${PREVIEW_BUILD_TYPES.join(", ")} are supported in the current preview.`, "Pass a supported --build-type value.", getBuildTypeExamples("build"), "app");
2230
+ throw usageError(`Unsupported build type "${requestedBuildType}"`, `Only ${APP_BUILD_TYPE_LABELS} are supported in the current preview.`, "Pass a supported --build-type value.", getBuildTypeExamples("build"), "app");
1724
2231
  }
1725
2232
  function isPreviewBuildType(value) {
1726
- return PREVIEW_BUILD_TYPES.includes(value);
2233
+ return APP_BUILD_TYPES.includes(value);
1727
2234
  }
1728
2235
  function getBuildTypeExamples(commandName) {
1729
- return RESOLVED_PREVIEW_BUILD_TYPES.map((buildType) => {
2236
+ return RESOLVED_APP_BUILD_TYPES.map((buildType) => {
1730
2237
  return `prisma-cli app ${commandName} --build-type ${buildType}${buildType === "bun" ? " --entry server.ts" : ""}`;
1731
2238
  });
1732
2239
  }
1733
2240
  function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
1734
- if (buildType !== "auto" && buildType !== "bun" && entrypoint) {
2241
+ if (buildType !== "auto" && !ENTRYPOINT_BUILD_TYPES.includes(buildType) && entrypoint) {
1735
2242
  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");
1736
2243
  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");
1737
2244
  }
1738
2245
  }
1739
- async function requireLocalBuildType(context, buildType, commandName) {
1740
- const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
1741
- if (resolvedBuildType) return resolvedBuildType;
1742
- 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");
2246
+ /**
2247
+ * Resolves the framework for `app run` with the same detection as deploy, so
2248
+ * a repo that deploys without flags also runs without flags. Local dev server
2249
+ * support is intentionally narrower than deploy build support: only Next.js
2250
+ * and Bun/Hono have dev servers in the current preview.
2251
+ */
2252
+ async function resolveLocalRunFramework(context, options) {
2253
+ if (LOCAL_DEV_BUILD_TYPES.includes(options.requestedBuildType)) {
2254
+ if (options.configFramework && computeFrameworkToBuildType(options.configFramework) === options.requestedBuildType) return frameworkFromUserFacingValue(options.configFramework, `set by ${COMPUTE_CONFIG_FILENAME$1}`);
2255
+ return frameworkFromUserFacingValue(options.requestedBuildType, "set by --build-type");
2256
+ }
2257
+ const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
2258
+ if (detected && LOCAL_DEV_BUILD_TYPES.includes(detected.buildType)) return detected;
2259
+ 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");
1743
2260
  }
1744
2261
  function parseLocalPort(requestedPort) {
1745
2262
  if (!requestedPort) return DEFAULT_LOCAL_DEV_PORT;
@@ -1756,6 +2273,19 @@ function parseDeployHttpPort(requestedPort) {
1756
2273
  if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid HTTP port "${requestedPort}"`, "HTTP port must be an integer between 1 and 65535.", "Pass --http-port <number> with a valid port value.", ["prisma-cli app deploy --http-port 3000"], "app");
1757
2274
  return port;
1758
2275
  }
2276
+ function parseDeployRegion(requestedRegion, source) {
2277
+ const region = requestedRegion.trim();
2278
+ if (region.length === 0) throw usageError("Invalid app region", `The app region ${source} must be a non-empty region id.`, "Pass a Prisma Compute region id.", ["prisma-cli app deploy --region eu-central-1"], "app");
2279
+ if (!COMPUTE_REGION_IDS.has(region)) throw usageError("Invalid app region", `The app region ${source} must be one of: ${COMPUTE_REGIONS.join(", ")}.`, "Pass a supported Prisma Compute region id.", ["prisma-cli app deploy --region eu-central-1"], "app");
2280
+ return region;
2281
+ }
2282
+ function normalizeDeployRegionInput(region) {
2283
+ if (!region) return;
2284
+ return {
2285
+ ...region,
2286
+ value: parseDeployRegion(region.value, region.annotation)
2287
+ };
2288
+ }
1759
2289
  function ensurePreviewAppMode(context) {
1760
2290
  if (isRealMode(context)) return;
1761
2291
  throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project show"], "app");
@@ -1812,7 +2342,7 @@ function appDeployFailedError(error, progress) {
1812
2342
  }
1813
2343
  if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
1814
2344
  const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
1815
- const recoveryLines = progress.versionId ? ["See what happened", `prisma-cli app logs --deployment ${progress.versionId}`] : ["Fix", "Retry the command, or rerun with --trace for more detailed diagnostics."];
2345
+ const recoveryLines = progress.deploymentId ? ["See what happened", `prisma-cli app logs --deployment ${progress.deploymentId}`] : ["Fix", "Retry the command, or rerun with --trace for more detailed diagnostics."];
1816
2346
  const urlLines = progress.deploymentUrl ? [
1817
2347
  "",
1818
2348
  "URL",
@@ -1838,11 +2368,11 @@ function appDeployFailedError(error, progress) {
1838
2368
  domain: "app",
1839
2369
  summary: phaseHeadline,
1840
2370
  why,
1841
- fix: progress.versionId ? `Inspect logs with prisma-cli app logs --deployment ${progress.versionId}.` : "Retry the command, or rerun with --trace for more detailed diagnostics.",
2371
+ fix: progress.deploymentId ? `Inspect logs with prisma-cli app logs --deployment ${progress.deploymentId}.` : "Retry the command, or rerun with --trace for more detailed diagnostics.",
1842
2372
  debug,
1843
2373
  meta: {
1844
2374
  phase: progress.containerLive ? "runtime_ready" : "deploy",
1845
- deploymentId: progress.versionId,
2375
+ deploymentId: progress.deploymentId,
1846
2376
  deploymentUrl: progress.deploymentUrl
1847
2377
  },
1848
2378
  humanLines,
@@ -1866,6 +2396,18 @@ function localResolutionPinStaleError() {
1866
2396
  ]
1867
2397
  });
1868
2398
  }
2399
+ function localPinReadErrorToDeployError(error) {
2400
+ return matchError(error, {
2401
+ LocalResolutionPinInvalidJsonError: () => localResolutionPinStaleError(),
2402
+ LocalResolutionPinInvalidShapeError: () => localResolutionPinStaleError(),
2403
+ LocalResolutionPinReadAbortedError: (error) => {
2404
+ throw error;
2405
+ },
2406
+ UnhandledException: (error) => {
2407
+ throw error;
2408
+ }
2409
+ });
2410
+ }
1869
2411
  function readDeployEnvOverride(context, name) {
1870
2412
  const value = context.runtime.env[name]?.trim();
1871
2413
  return value ? value : void 0;
@@ -1944,14 +2486,12 @@ function isAutoBuildDetectionError(error) {
1944
2486
  return error instanceof Error && error.message.startsWith("Entrypoint is required.");
1945
2487
  }
1946
2488
  function formatBuildTypeName(buildType) {
1947
- switch (buildType) {
1948
- case "nextjs": return "Next.js";
1949
- case "nuxt": return "Nuxt";
1950
- case "astro": return "Astro";
1951
- case "tanstack-start": return "TanStack Start";
1952
- case "bun": return "Bun";
1953
- case "auto": return "Auto";
2489
+ if (buildType === "auto") return "Auto";
2490
+ for (let index = FRAMEWORKS.length - 1; index >= 0; index -= 1) {
2491
+ const framework = FRAMEWORKS[index];
2492
+ if (framework?.buildType === buildType) return framework.displayName;
1954
2493
  }
2494
+ return buildType;
1955
2495
  }
1956
2496
  function removeFailedError(summary, error, nextSteps) {
1957
2497
  return new CliError({