@prisma/cli 3.0.0-dev.97.1 → 8.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +3 -14
  2. package/dist/adapters/local-state.js +14 -3
  3. package/dist/{lib/auth → auth}/client.js +11 -3
  4. package/dist/auth/errors.js +50 -0
  5. package/dist/{lib/auth → auth}/guard.js +7 -6
  6. package/dist/{lib/auth → auth}/login.js +147 -6
  7. package/dist/{lib/auth/auth-ops.js → auth/operations.js} +63 -13
  8. package/dist/auth/recipient.js +42 -0
  9. package/dist/{adapters → auth}/token-storage.js +28 -7
  10. package/dist/auth/workspaces.js +171 -0
  11. package/dist/cli-command.js +14 -0
  12. package/dist/cli-name.js +12 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/cli2.js +25 -8
  15. package/dist/command-arguments.js +12 -0
  16. package/dist/commands/agent/index.js +60 -0
  17. package/dist/commands/app/index.js +11 -6
  18. package/dist/commands/auth/index.js +2 -3
  19. package/dist/commands/bucket/index.js +123 -0
  20. package/dist/commands/build/index.js +29 -0
  21. package/dist/commands/database/index.js +92 -2
  22. package/dist/commands/feedback/index.js +20 -0
  23. package/dist/commands/git/index.js +1 -1
  24. package/dist/commands/init/index.js +33 -0
  25. package/dist/commands/project/index.js +54 -5
  26. package/dist/controllers/agent-setup.js +52 -0
  27. package/dist/controllers/agent.js +228 -0
  28. package/dist/controllers/app-env-api.js +3 -2
  29. package/dist/controllers/app-env-file.js +1 -1
  30. package/dist/controllers/app-env.js +5 -5
  31. package/dist/controllers/app.js +423 -358
  32. package/dist/controllers/auth.js +62 -254
  33. package/dist/controllers/branch.js +5 -16
  34. package/dist/controllers/bucket.js +184 -0
  35. package/dist/controllers/build.js +88 -0
  36. package/dist/controllers/database.js +230 -85
  37. package/dist/controllers/feedback.js +90 -0
  38. package/dist/controllers/init.js +814 -0
  39. package/dist/controllers/project.js +345 -184
  40. package/dist/controllers/select-prompt-port.js +1 -0
  41. package/dist/{shell/errors.js → errors.js} +8 -40
  42. package/dist/lib/agent/cli-command.js +20 -0
  43. package/dist/lib/agent/constants.js +12 -0
  44. package/dist/lib/agent/package-manager.js +99 -0
  45. package/dist/lib/agent/setup-status.js +83 -0
  46. package/dist/lib/app/app-provider.js +168 -95
  47. package/dist/lib/app/branch-database-deploy.js +70 -42
  48. package/dist/lib/app/branch-database.js +39 -20
  49. package/dist/lib/app/build-settings.js +8 -3
  50. package/dist/lib/app/build.js +16 -14
  51. package/dist/lib/app/bun-project.js +1 -1
  52. package/dist/lib/app/compute-config.js +29 -31
  53. package/dist/lib/app/deploy-plan.js +1 -0
  54. package/dist/lib/app/deploy-progress.js +7 -7
  55. package/dist/lib/app/env-config.js +1 -1
  56. package/dist/lib/app/env-file.js +2 -2
  57. package/dist/lib/app/env-vars.js +1 -1
  58. package/dist/lib/app/local-dev.js +1 -1
  59. package/dist/lib/app/production-deploy-gate.js +3 -2
  60. package/dist/lib/bucket/provider.js +139 -0
  61. package/dist/lib/database/provider.js +235 -17
  62. package/dist/lib/diagnostics.js +2 -1
  63. package/dist/lib/feedback.js +15 -0
  64. package/dist/lib/git/local-branch.js +1 -1
  65. package/dist/lib/project/interactive-setup.js +6 -5
  66. package/dist/lib/project/local-pin.js +1 -1
  67. package/dist/lib/project/provider.js +93 -0
  68. package/dist/lib/project/resolution.js +11 -8
  69. package/dist/lib/project/setup.js +9 -6
  70. package/dist/lib/version.js +3 -2
  71. package/dist/lib/workspace-id.js +18 -0
  72. package/dist/payload-B88Qvzxk-CrtTXSOe.js +34 -0
  73. package/dist/presenters/agent.js +74 -0
  74. package/dist/presenters/app.js +33 -7
  75. package/dist/presenters/auth.js +3 -2
  76. package/dist/presenters/bucket.js +174 -0
  77. package/dist/presenters/database.js +193 -5
  78. package/dist/presenters/feedback.js +26 -0
  79. package/dist/presenters/init.js +30 -0
  80. package/dist/presenters/project.js +98 -4
  81. package/dist/shell/cli-command.js +2 -0
  82. package/dist/shell/command-meta.js +303 -4
  83. package/dist/shell/command-runner.js +13 -6
  84. package/dist/shell/help.js +6 -5
  85. package/dist/shell/output.js +65 -3
  86. package/dist/shell/prompt.js +12 -5
  87. package/dist/shell/runtime.js +4 -21
  88. package/dist/state-dir.js +12 -0
  89. package/dist/{shell/update-check.js → update-check.js} +6 -5
  90. package/dist/v8/cli.js +14699 -0
  91. package/dist/v8/sender.js +192 -0
  92. package/package.json +21 -13
  93. package/dist/adapters/mock-api.js +0 -136
  94. package/dist/lib/app/app-interaction.js +0 -5
  95. package/dist/shell/command-arguments.js +0 -6
  96. package/dist/use-cases/auth.js +0 -145
  97. package/dist/use-cases/branch.js +0 -47
  98. package/dist/use-cases/create-cli-gateways.js +0 -68
  99. package/dist/use-cases/project.js +0 -30
@@ -1,21 +1,21 @@
1
- import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
- import { FileTokenStorage } from "../adapters/token-storage.js";
3
- import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
4
- import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
5
- import { DEFAULT_REGION } from "../lib/app/app-interaction.js";
6
- import { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredAppBuildSettings, resolveInferredAppBuildSettings } from "../lib/app/build-settings.js";
7
- import { APP_BUILD_TYPES, RESOLVED_APP_BUILD_TYPES, executeAppBuild } from "../lib/app/build.js";
8
- import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
9
- import { DomainApiError, createAppProvider } from "../lib/app/app-provider.js";
1
+ import { formatCommandArgument } from "../command-arguments.js";
2
+ import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../errors.js";
10
3
  import { renderCommandHeader } from "../shell/ui.js";
4
+ import { getApiBaseUrl } from "../auth/client.js";
5
+ import { sameWorkspaceId } from "../lib/workspace-id.js";
6
+ import { FileTokenStorage } from "../auth/token-storage.js";
7
+ import { authenticatedManagementApiClient } from "../auth/guard.js";
8
+ import { readAuthState } from "../auth/operations.js";
11
9
  import { canPrompt } from "../shell/runtime.js";
12
- import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
13
- import { formatCommandArgument } from "../shell/command-arguments.js";
14
- import { buildProjectSetupNextActions, inferTargetName, localProjectWorkspaceMismatchError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
15
- import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
10
+ import { writeJsonEvent } from "../shell/output.js";
11
+ import { APP_BUILD_TYPES, APP_BUILD_TYPE_LABELS, RESOLVED_APP_BUILD_TYPES, executeAppBuild } from "../lib/app/build.js";
12
+ import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
13
+ import { DomainApiError, createAppProvider } from "../lib/app/app-provider.js";
14
+ import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
16
15
  import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
16
+ import { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredAppBuildSettings, resolveInferredAppBuildSettings } from "../lib/app/build-settings.js";
17
17
  import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
18
- 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";
18
+ import { computeConfigErrorToCliError, computeFrameworkToBuildType, loadComputeConfig as loadComputeConfig$1, mergeComputeDeployInputs, mergeComputeLocalInputs } from "../lib/app/compute-config.js";
19
19
  import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
20
20
  import { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy } from "../lib/app/deploy-plan.js";
21
21
  import { createDeployProgress, createDeployProgressState, createPromoteProgress } from "../lib/app/deploy-progress.js";
@@ -23,26 +23,41 @@ import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
23
23
  import { DEFAULT_LOCAL_DEV_PORT, runLocalApp } from "../lib/app/local-dev.js";
24
24
  import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
25
25
  import { resolveReadBranch } from "../lib/app/read-branch.js";
26
- import { requireComputeAuth } from "../lib/auth/guard.js";
27
- import { readAuthState } from "../lib/auth/auth-ops.js";
28
26
  import { readLocalGitBranch } from "../lib/git/local-branch.js";
27
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
28
+ import { buildProjectSetupNextActions, inferTargetName, localProjectWorkspaceMismatchError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
29
+ import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
29
30
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
30
- import { writeJsonEvent } from "../shell/output.js";
31
+ import { maybePromptForAgentSetup } from "./agent-setup.js";
31
32
  import { createSelectPromptPort } from "./select-prompt-port.js";
32
33
  import { requireAuthenticatedAuthState } from "./auth.js";
33
34
  import { listRealWorkspaceProjects } from "./project.js";
34
- import { ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, frameworkByKey, frameworkFromAlias, isConfigBackedBuildType } from "@prisma/compute-sdk/config";
35
- import { access, readFile } from "node:fs/promises";
36
35
  import path from "node:path";
37
- import { Result, matchError } from "better-result";
36
+ import { access } from "node:fs/promises";
37
+ import { COMPUTE_CONFIG_FILENAME, COMPUTE_REGIONS, ComputeConfigTargetRequiredError, ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, computeTargetAppDir, frameworkByKey, frameworkFromAlias, inferComputeTargetFromCwd, isConfigBackedBuildType, selectComputeDeployTarget } from "@prisma/compute-sdk/config";
38
+ import { SERVICE_TOKEN_ENV_VAR } from "@prisma/cli-engine";
38
39
  import open from "open";
40
+ import { detectComputeAppFromDirectory } from "@prisma/compute-sdk/config/directory";
41
+ import { matchError } from "better-result";
39
42
  //#region src/controllers/app.ts
40
43
  const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
41
44
  const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
42
45
  const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
43
- function isRealMode(context) {
44
- return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
45
- }
46
+ const COMPUTE_REGION_IDS = new Set(COMPUTE_REGIONS);
47
+ const APP_COMMAND_PREFIX = /^app /;
48
+ const TRAILING_DOT = /\.$/;
49
+ const DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
50
+ const CNAME_TO_PHRASE = /\bcname(?:s)?\s+to\b/;
51
+ const PRISMA_BUILD_HOSTNAME = /\b((?:[a-z0-9-]+\.)+prisma\.build)\b/i;
52
+ const WAIT_TIMEOUT_DURATION = /^(\d+)(ms|s|m|h)$/;
53
+ const WAIT_TIMEOUT_UNIT_MULTIPLIER_MS = {
54
+ ms: 1,
55
+ s: 1e3,
56
+ m: 60 * 1e3,
57
+ h: 3600 * 1e3
58
+ };
59
+ const NEXTJS_MENTION = /next\.?js/i;
60
+ const STANDALONE_OUTPUT_MENTION = /standalone output/i;
46
61
  async function runAppBuild(context, options) {
47
62
  const compute = await resolveComputeTargetOrThrow(context, options?.configTarget, "build");
48
63
  const merged = mergeComputeLocalInputs({
@@ -102,7 +117,7 @@ async function runAppRun(context, options) {
102
117
  },
103
118
  target: compute.target
104
119
  });
105
- 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");
120
+ 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} 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");
106
121
  const appDir = await resolveComputeAppDir(context, compute);
107
122
  const buildType = normalizeBuildType(merged.buildType);
108
123
  assertSupportedEntrypoint(buildType, merged.entrypoint, "run");
@@ -110,7 +125,8 @@ async function runAppRun(context, options) {
110
125
  const framework = await resolveLocalRunFramework(context, {
111
126
  requestedBuildType: buildType,
112
127
  configFramework: compute.target?.framework ?? null,
113
- appDir
128
+ appDir,
129
+ entrypoint: merged.entrypoint
114
130
  });
115
131
  const entrypoint = framework.buildType === "bun" ? await resolveDeployEntrypoint(appDir, framework, merged.entrypoint, context.runtime.signal) : merged.entrypoint;
116
132
  let runResult;
@@ -141,7 +157,6 @@ async function runAppRun(context, options) {
141
157
  };
142
158
  }
143
159
  async function runAppDeploy(context, appName, options) {
144
- ensurePreviewAppMode(context);
145
160
  const loaded = await loadComputeConfig$1(context.runtime.cwd, context.runtime.signal);
146
161
  if (loaded.isErr()) throw computeConfigErrorToCliError(loaded.error, "deploy");
147
162
  const config = loaded.value;
@@ -154,7 +169,7 @@ async function runAppDeploy(context, appName, options) {
154
169
  return runSingleAppDeploy(context, appName, options, config);
155
170
  }
156
171
  async function runAppDeployAll(context, config, plannedTargets, appName, options) {
157
- assertNoPerAppInputsForDeployAll(context, config, appName, options);
172
+ assertNoPerAppInputsForDeployAll(context, plannedTargets, appName, options);
158
173
  const deployments = [];
159
174
  const warnings = [];
160
175
  for (const planned of plannedTargets) {
@@ -172,7 +187,7 @@ async function runAppDeployAll(context, config, plannedTargets, appName, options
172
187
  });
173
188
  warnings.push(...single.warnings);
174
189
  } catch (error) {
175
- throw deployAllFailedError(error, config, planned.index, deployments);
190
+ throw deployAllFailedError(error, plannedTargets, planned.index, deployments);
176
191
  }
177
192
  }
178
193
  return {
@@ -182,12 +197,13 @@ async function runAppDeployAll(context, config, plannedTargets, appName, options
182
197
  nextSteps: ["prisma-cli app list-deploys <app>"]
183
198
  };
184
199
  }
185
- function assertNoPerAppInputsForDeployAll(context, config, appName, options) {
200
+ function assertNoPerAppInputsForDeployAll(context, plannedTargets, appName, options) {
186
201
  const used = perAppInputsForDeployAll({
187
202
  appName,
188
203
  framework: options?.framework,
189
204
  entrypoint: options?.entrypoint,
190
205
  httpPort: options?.httpPort,
206
+ region: options?.region,
191
207
  envAssignments: options?.envAssignments,
192
208
  appIdEnvVar: {
193
209
  name: PRISMA_APP_ID_ENV_VAR,
@@ -195,17 +211,17 @@ function assertNoPerAppInputsForDeployAll(context, config, appName, options) {
195
211
  }
196
212
  });
197
213
  if (used.length === 0) return;
198
- const targets = config.targets.map((target) => target.key).join(", ");
199
- throw usageError(`Deploying all apps does not accept ${used.join(", ")}`, `Without a target, app deploy deploys every configured app (${targets}), so per-app inputs are ambiguous.`, "Pass the app target to apply per-app inputs to one app, or remove them to deploy all apps.", config.targets.map((target) => `prisma-cli app deploy ${target.key}`), "app");
214
+ const targetKeys = plannedTargets.map((target) => target.targetKey);
215
+ 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");
200
216
  }
201
217
  function maybeRenderDeployAllTargetHeader(context, planned) {
202
218
  if (context.flags.json || context.flags.quiet) return;
203
219
  context.output.stderr.write(`${planned.index > 0 ? "\n" : ""}── ${planned.targetKey} (${planned.index + 1}/${planned.total}) ──\n\n`);
204
220
  }
205
- function deployAllFailedError(error, config, failedIndex, deployments) {
221
+ function deployAllFailedError(error, plannedTargets, failedIndex, deployments) {
206
222
  if (!(error instanceof CliError)) return error;
207
223
  const failure = describeDeployAllFailure({
208
- targetKeys: config.targets.map((target) => target.key),
224
+ targetKeys: plannedTargets.map((target) => target.targetKey),
209
225
  failedIndex,
210
226
  completed: deployments.map(({ target, result }) => ({
211
227
  target,
@@ -255,18 +271,25 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
255
271
  framework: options?.framework,
256
272
  entrypoint: options?.entrypoint,
257
273
  httpPort: options?.httpPort,
274
+ region: options?.region,
258
275
  envInputs: options?.envAssignments
259
276
  },
260
277
  target: computeConfig.target,
261
- configFilename: computeConfig.config?.relativeConfigPath ?? COMPUTE_CONFIG_FILENAME$1
278
+ configFilename: computeConfig.config?.relativeConfigPath ?? COMPUTE_CONFIG_FILENAME
262
279
  });
263
280
  const appDir = await resolveComputeAppDir(context, computeConfig);
264
281
  const projectDir = computeConfig.config?.configDir ?? context.runtime.cwd;
265
- const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(projectDir, context.runtime.signal);
266
- if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
267
- const localPin = localPinReadResult.value;
282
+ const agentSetupWarnings = await maybePromptForAgentSetup(context, projectDir);
283
+ const localPin = await readDeployLocalPin({
284
+ projectDir,
285
+ signal: context.runtime.signal,
286
+ envProjectId,
287
+ projectRef: options?.projectRef,
288
+ createProjectName: options?.createProjectName
289
+ });
268
290
  const branch = await resolveDeployBranch(context, options?.branchName);
269
291
  if (merged.httpPort) parseDeployHttpPort(merged.httpPort.value);
292
+ const deployRegion = normalizeDeployRegionInput(merged.region);
270
293
  assertSupportedEntrypointForRequestedDeployShape({
271
294
  requestedFramework: merged.framework?.value,
272
295
  entrypoint: merged.entrypoint?.value
@@ -274,17 +297,11 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
274
297
  const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
275
298
  branch,
276
299
  createProjectName: options?.createProjectName,
300
+ createProjectRegion: deployRegion?.value,
277
301
  envProjectId,
278
302
  localPin
279
303
  });
280
- let localPinResult;
281
- if (target.localPinAction) {
282
- const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction, projectDir);
283
- if (setupResult.isErr()) throw projectDirectoryBindingErrorToCliError(setupResult.error);
284
- const projectSetup = setupResult.value;
285
- localPinResult = projectSetup.localPin;
286
- maybeRenderProjectLinked(context, projectSetup.directory, projectSetup.project.name, projectSetup.localPin.path);
287
- }
304
+ const localPinResult = await bindDeployProjectDirectory(context, target, projectDir);
288
305
  let framework = await resolveDeployFramework(context, {
289
306
  requestedFramework: merged.framework?.value,
290
307
  requestedFrameworkAnnotation: merged.framework?.annotation,
@@ -299,6 +316,7 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
299
316
  explicitAppName: appName,
300
317
  explicitAppId: envAppId,
301
318
  configAppName: merged.configAppName,
319
+ configRegion: deployRegion,
302
320
  firstDeploy: Boolean(target.localPinAction),
303
321
  inferName: () => inferTargetName(appDir, context.runtime.signal)
304
322
  });
@@ -319,7 +337,8 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
319
337
  });
320
338
  framework = customized.framework;
321
339
  runtime = customized.runtime;
322
- const productionDeployGate = await enforceProductionDeployGate(context, provider, {
340
+ const noPromote = options?.noPromote === true;
341
+ const productionDeployGate = noPromote ? { firstProductionDeploy: false } : await enforceProductionDeployGate(context, provider, {
323
342
  appId: selectedApp.appId,
324
343
  appName: selectedApp.displayName,
325
344
  branchKind: target.branch.kind,
@@ -328,15 +347,9 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
328
347
  const buildType = framework.buildType;
329
348
  assertSupportedEntrypoint(buildType, merged.entrypoint?.value, "deploy");
330
349
  const entrypoint = await resolveDeployEntrypoint(appDir, framework, merged.entrypoint?.value, context.runtime.signal);
331
- if (computeConfig.target?.build) assertConfigBackedBuildSettings(buildType);
332
- const buildSettingsResolution = computeConfig.config && computeConfig.target?.build && isConfigBackedBuildType(buildType) ? await resolveConfiguredAppBuildSettings({
333
- appPath: appDir,
334
- buildType,
335
- configured: computeConfig.target.build,
336
- configPath: computeConfig.config.configPath,
337
- signal: context.runtime.signal
338
- }) : await resolveInferredAppBuildSettings({
339
- appPath: appDir,
350
+ const buildSettingsResolution = await resolveDeployBuildSettings({
351
+ computeConfig,
352
+ appDir,
340
353
  buildType,
341
354
  signal: context.runtime.signal
342
355
  });
@@ -363,6 +376,7 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
363
376
  buildSettings: buildSettingsResolution.settings,
364
377
  portMapping,
365
378
  envVars,
379
+ skipPromote: noPromote,
366
380
  interaction: void 0,
367
381
  signal: context.runtime.signal,
368
382
  progress: createDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
@@ -370,11 +384,7 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
370
384
  throw appDeployFailedError(error, progressState);
371
385
  });
372
386
  const deployDurationMs = Date.now() - deployStartedAt;
373
- await context.stateStore.setSelectedApp(projectId, {
374
- id: deployResult.app.id,
375
- name: deployResult.app.name
376
- });
377
- await context.stateStore.setKnownLiveDeployment(projectId, deployResult.app.id, deployResult.deployment.id);
387
+ await cacheDeployedApp(context, projectId, deployResult);
378
388
  return {
379
389
  command: "app.deploy",
380
390
  result: {
@@ -388,9 +398,10 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
388
398
  name: deployResult.app.name
389
399
  },
390
400
  deployment: deployResult.deployment,
401
+ promoted: deployResult.promoted,
391
402
  deploySettings: {
392
403
  config: {
393
- path: buildSettingsResolution.relativeConfigPath,
404
+ path: computeConfig.config?.relativeConfigPath ?? null,
394
405
  status: buildSettingsResolution.status
395
406
  },
396
407
  buildCommand: {
@@ -407,20 +418,62 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
407
418
  name: framework.displayName,
408
419
  source: framework.annotation
409
420
  },
410
- entrypoint: entrypoint ?? null,
421
+ entrypoint: entrypoint ?? buildSettingsResolution.settings.entrypoint ?? null,
411
422
  httpPort: runtime.port,
412
- region: selectedApp.region ?? null,
423
+ region: deployResult.app.region ?? selectedApp.region ?? null,
424
+ regionSource: deployRegion?.annotation ?? null,
413
425
  envVars: envVarNames(envVars)
414
426
  },
415
427
  durationMs: deployDurationMs,
416
428
  localPin: localPinResult
417
429
  },
418
- warnings: [...legacyWarnings, ...branchDatabaseSetup.warnings],
419
- nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
430
+ warnings: [
431
+ ...agentSetupWarnings,
432
+ ...legacyWarnings,
433
+ ...branchDatabaseSetup.warnings
434
+ ],
435
+ 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}`]
420
436
  };
421
437
  }
438
+ async function readDeployLocalPin(options) {
439
+ if (options.envProjectId || options.projectRef || options.createProjectName) return { kind: "missing" };
440
+ const readResult = await readLocalResolutionPin(options.projectDir, options.signal);
441
+ if (readResult.isErr()) throw localPinReadErrorToDeployError(readResult.error);
442
+ return readResult.value;
443
+ }
444
+ async function bindDeployProjectDirectory(context, target, projectDir) {
445
+ if (!target.localPinAction) return;
446
+ const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction, projectDir);
447
+ if (setupResult.isErr()) throw projectDirectoryBindingErrorToCliError(setupResult.error);
448
+ const projectSetup = setupResult.value;
449
+ maybeRenderProjectLinked(context, projectSetup.directory, projectSetup.project.name, projectSetup.localPin.path);
450
+ return projectSetup.localPin;
451
+ }
452
+ async function cacheDeployedApp(context, projectId, deployResult) {
453
+ await context.stateStore.setSelectedApp(projectId, {
454
+ id: deployResult.app.id,
455
+ name: deployResult.app.name
456
+ });
457
+ const knownLiveDeploymentId = deployResult.promoted ? deployResult.deployment.id : deployResult.app.liveDeploymentId;
458
+ if (knownLiveDeploymentId) await context.stateStore.setKnownLiveDeployment(projectId, deployResult.app.id, knownLiveDeploymentId);
459
+ }
460
+ async function resolveDeployBuildSettings(options) {
461
+ const { computeConfig, appDir, buildType, signal } = options;
462
+ if (computeConfig.target?.build) assertConfigBackedBuildSettings(buildType);
463
+ if (computeConfig.config && computeConfig.target?.build && isConfigBackedBuildType(buildType)) return resolveConfiguredAppBuildSettings({
464
+ appPath: appDir,
465
+ buildType,
466
+ configured: computeConfig.target.build,
467
+ configPath: computeConfig.config.configPath,
468
+ signal
469
+ });
470
+ return resolveInferredAppBuildSettings({
471
+ appPath: appDir,
472
+ buildType,
473
+ signal
474
+ });
475
+ }
422
476
  async function runAppListDeploys(context, appName, projectRef, configTarget) {
423
- ensurePreviewAppMode(context);
424
477
  const compute = await resolveComputeManagementContext(context, configTarget, "list-deploys");
425
478
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
426
479
  commandName: "app list-deploys",
@@ -463,7 +516,6 @@ async function runAppListDeploys(context, appName, projectRef, configTarget) {
463
516
  };
464
517
  }
465
518
  async function runAppShow(context, appName, projectRef, configTarget) {
466
- ensurePreviewAppMode(context);
467
519
  const compute = await resolveComputeManagementContext(context, configTarget, "show");
468
520
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
469
521
  commandName: "app show",
@@ -511,7 +563,6 @@ async function runAppShow(context, appName, projectRef, configTarget) {
511
563
  };
512
564
  }
513
565
  async function runAppShowDeploy(context, deploymentId) {
514
- ensurePreviewAppMode(context);
515
566
  const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
516
567
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
517
568
  });
@@ -527,7 +578,7 @@ async function runAppShowDeploy(context, deploymentId) {
527
578
  const workspaceId = deployment?.app ? await readCurrentWorkspaceId(context) : null;
528
579
  const rememberedProject = workspaceId ? await context.stateStore.readRememberedProject(workspaceId) : null;
529
580
  const knownLiveDeploymentId = deployment?.app && rememberedProject ? await context.stateStore.readKnownLiveDeployment(rememberedProject.id, deployment.app.id) : null;
530
- const providerLiveDeploymentId = deployment.app?.liveDeploymentId ?? null;
581
+ const liveDeploymentId = deployment.app?.liveDeploymentId || knownLiveDeploymentId;
531
582
  return {
532
583
  command: "app.show-deploy",
533
584
  result: {
@@ -537,17 +588,16 @@ async function runAppShowDeploy(context, deploymentId) {
537
588
  } : null,
538
589
  deployment: {
539
590
  ...deployment.deployment,
540
- live: providerLiveDeploymentId ? deployment.deployment.id === providerLiveDeploymentId : knownLiveDeploymentId ? deployment.deployment.id === knownLiveDeploymentId : deployment.deployment.live
591
+ live: liveDeploymentId ? deployment.deployment.id === liveDeploymentId : deployment.deployment.live
541
592
  }
542
593
  },
543
594
  warnings: [],
544
595
  nextSteps: []
545
596
  };
546
597
  }
547
- async function runAppOpen(context, appName, projectRef, configTarget) {
548
- ensurePreviewAppMode(context);
598
+ async function runAppOpen(context, appNameArg, projectRef, configTarget) {
549
599
  const compute = await resolveComputeManagementContext(context, configTarget, "open");
550
- appName = appName ?? compute.configAppName;
600
+ const appName = appNameArg ?? compute.configAppName;
551
601
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
552
602
  commandName: "app open",
553
603
  projectDir: compute.projectDir
@@ -715,10 +765,9 @@ async function runAppDomainWait(context, hostname, options) {
715
765
  });
716
766
  }
717
767
  }
718
- async function runAppLogs(context, appName, deploymentId, projectRef, configTarget) {
719
- ensurePreviewAppMode(context);
768
+ async function runAppLogs(context, appNameArg, deploymentId, projectRef, configTarget) {
720
769
  const compute = await resolveComputeManagementContext(context, configTarget, "logs");
721
- appName = appName ?? compute.configAppName;
770
+ const appName = appNameArg ?? compute.configAppName;
722
771
  const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, {
723
772
  commandName: "app logs",
724
773
  projectDir: compute.projectDir
@@ -844,10 +893,9 @@ function writeLogRecord(context, record) {
844
893
  if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
845
894
  }
846
895
  }
847
- async function runAppPromote(context, deploymentId, appName, projectRef, configTarget) {
848
- ensurePreviewAppMode(context);
896
+ async function runAppPromote(context, deploymentId, appNameArg, projectRef, configTarget) {
849
897
  const compute = await resolveComputeManagementContext(context, configTarget, "promote");
850
- appName = appName ?? compute.configAppName;
898
+ const appName = appNameArg ?? compute.configAppName;
851
899
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
852
900
  commandName: "app promote",
853
901
  projectDir: compute.projectDir
@@ -891,10 +939,9 @@ async function runAppPromote(context, deploymentId, appName, projectRef, configT
891
939
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
892
940
  };
893
941
  }
894
- async function runAppRollback(context, appName, deploymentId, projectRef, configTarget) {
895
- ensurePreviewAppMode(context);
942
+ async function runAppRollback(context, appNameArg, deploymentId, projectRef, configTarget) {
896
943
  const compute = await resolveComputeManagementContext(context, configTarget, "rollback");
897
- appName = appName ?? compute.configAppName;
944
+ const appName = appNameArg ?? compute.configAppName;
898
945
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
899
946
  commandName: "app rollback",
900
947
  projectDir: compute.projectDir
@@ -940,11 +987,18 @@ async function runAppRollback(context, appName, deploymentId, projectRef, config
940
987
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
941
988
  };
942
989
  }
943
- async function runAppRemove(context, appName, projectRef, configTarget) {
944
- ensurePreviewAppMode(context);
990
+ /**
991
+ * Removes an app and every deployment it owns in the resolved branch.
992
+ *
993
+ * @param branchName Scopes the removal to this branch. When omitted the branch
994
+ * is inferred from the local Git branch, falling back to the production branch.
995
+ */
996
+ async function runAppRemove(context, appNameArg, projectRef, configTarget, branchName) {
945
997
  const compute = await resolveComputeManagementContext(context, configTarget, "remove");
946
- appName = appName ?? compute.configAppName;
998
+ const appName = appNameArg ?? compute.configAppName;
999
+ if (branchName !== void 0 && branchName.trim() === "") throw usageError("The --branch value cannot be empty", "app remove scopes the removal to the given branch; an empty --branch would silently fall back to the inferred (possibly production) branch.", "Pass a non-empty branch name, e.g. --branch <branch>, or omit --branch to use the inferred branch.", ["prisma-cli app remove --app <name> --branch <branch>"], "app");
947
1000
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
1001
+ branch: branchName !== void 0 ? await resolveDeployBranch(context, branchName) : void 0,
948
1002
  commandName: "app remove",
949
1003
  projectDir: compute.projectDir
950
1004
  });
@@ -970,8 +1024,7 @@ async function runAppRemove(context, appName, projectRef, configTarget) {
970
1024
  };
971
1025
  }
972
1026
  async function resolveAppDomainTarget(context, options, commandName = "app domain") {
973
- ensurePreviewAppMode(context);
974
- const compute = await resolveComputeManagementContext(context, options?.configTarget, commandName.replace(/^app /, ""));
1027
+ const compute = await resolveComputeManagementContext(context, options?.configTarget, commandName.replace(APP_COMMAND_PREFIX, ""));
975
1028
  const branch = resolveDomainBranch(options?.branchName);
976
1029
  if (toBranchKind(branch.name) !== "production") throw new CliError({
977
1030
  code: "BRANCH_NOT_DEPLOYABLE",
@@ -1036,7 +1089,7 @@ async function resolveDomainByHostname(provider, appId, hostname, command, signa
1036
1089
  throw domainNotFoundError(hostname);
1037
1090
  }
1038
1091
  function normalizeDomainHostname(hostname) {
1039
- const normalized = hostname.trim().replace(/\.$/, "").toLowerCase();
1092
+ const normalized = hostname.trim().replace(TRAILING_DOT, "").toLowerCase();
1040
1093
  if (!isValidDomainHostname(normalized)) throw new CliError({
1041
1094
  code: "DOMAIN_HOSTNAME_INVALID",
1042
1095
  domain: "app",
@@ -1053,10 +1106,10 @@ function isValidDomainHostname(hostname) {
1053
1106
  if (hostname.includes("://") || hostname.includes("/") || hostname.includes(":") || hostname.startsWith("*.")) return false;
1054
1107
  const labels = hostname.split(".");
1055
1108
  if (labels.length < 2) return false;
1056
- return labels.every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label));
1109
+ return labels.every((label) => DOMAIN_LABEL.test(label));
1057
1110
  }
1058
1111
  function sameDomainHostname(left, right) {
1059
- return left.trim().replace(/\.$/, "").toLowerCase() === right.trim().replace(/\.$/, "").toLowerCase();
1112
+ return left.trim().replace(TRAILING_DOT, "").toLowerCase() === right.trim().replace(TRAILING_DOT, "").toLowerCase();
1060
1113
  }
1061
1114
  function toAppDomainSummary(domain) {
1062
1115
  return {
@@ -1064,7 +1117,7 @@ function toAppDomainSummary(domain) {
1064
1117
  type: domain.type,
1065
1118
  url: domain.url,
1066
1119
  hostname: domain.hostname,
1067
- computeServiceId: domain.computeServiceId,
1120
+ appId: domain.appId,
1068
1121
  status: domain.status,
1069
1122
  foundryStatus: domain.foundryStatus,
1070
1123
  failureReason: domain.failureReason,
@@ -1102,55 +1155,15 @@ async function confirmDomainRemoval(context, target, hostname) {
1102
1155
  if (!await confirmPrompt({
1103
1156
  input: context.runtime.stdin,
1104
1157
  output: context.output.stderr,
1158
+ signal: context.runtime.signal,
1105
1159
  message: `Detach ${hostname} from App "${target.app.name}"?`,
1106
1160
  initialValue: false
1107
1161
  })) 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");
1108
1162
  }
1109
1163
  function domainCommandError(command, error, hostname) {
1110
1164
  if (error instanceof DomainApiError) {
1111
- if (command === "add" && (error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
1112
- if (command === "add" && error.status === 400) return new CliError({
1113
- code: "DOMAIN_HOSTNAME_INVALID",
1114
- domain: "app",
1115
- summary: `Invalid custom domain "${hostname}"`,
1116
- why: error.message,
1117
- fix: "Pass a valid hostname like shop.acme.com and make sure DNS can be verified.",
1118
- debug: formatDebugDetails(error),
1119
- exitCode: 2,
1120
- nextSteps: ["prisma-cli app domain add shop.acme.com"]
1121
- });
1122
- if (command === "add" && (error.status === 429 || isDomainQuotaError(error))) return new CliError({
1123
- code: "DOMAIN_QUOTA_EXCEEDED",
1124
- domain: "app",
1125
- summary: "Custom domain quota exceeded",
1126
- why: error.message,
1127
- fix: "Remove an existing custom domain before adding another one.",
1128
- debug: formatDebugDetails(error),
1129
- exitCode: 1,
1130
- nextSteps: ["prisma-cli app domain remove <hostname>"]
1131
- });
1132
- if (command === "add" && error.status === 409) return domainAlreadyRegisteredError(hostname, error);
1133
- if (command === "add" && error.status === 422) return new CliError({
1134
- code: "NO_DEPLOYMENTS",
1135
- domain: "app",
1136
- summary: "Custom domain requires a live production deployment",
1137
- why: "The selected production app does not have a promoted version that can receive a custom domain.",
1138
- fix: "Deploy the app to the production branch, then rerun the domain command.",
1139
- debug: formatDebugDetails(error),
1140
- exitCode: 1,
1141
- nextSteps: ["prisma-cli app deploy --branch production", `prisma-cli app domain add ${hostname}`]
1142
- });
1143
- if ((command === "show" || command === "remove" || command === "retry" || command === "wait") && error.status === 404) return domainNotFoundError(hostname);
1144
- if (command === "retry" && error.status === 409) return new CliError({
1145
- code: "DOMAIN_RETRY_NOT_ELIGIBLE",
1146
- domain: "app",
1147
- summary: `Custom domain "${hostname}" is not eligible for retry`,
1148
- why: error.message,
1149
- fix: "Wait for the current verification or TLS step to finish, then rerun retry if the domain fails.",
1150
- debug: formatDebugDetails(error),
1151
- exitCode: 1,
1152
- nextSteps: [`prisma-cli app domain show ${hostname}`]
1153
- });
1165
+ const apiError = command === "add" ? domainAddApiError(hostname, error) : domainLookupApiError(command, hostname, error);
1166
+ if (apiError) return apiError;
1154
1167
  }
1155
1168
  return new CliError({
1156
1169
  code: "DEPLOY_FAILED",
@@ -1163,6 +1176,55 @@ function domainCommandError(command, error, hostname) {
1163
1176
  nextSteps: [`prisma-cli app domain show ${hostname}`]
1164
1177
  });
1165
1178
  }
1179
+ function domainAddApiError(hostname, error) {
1180
+ if ((error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
1181
+ if (error.status === 400) return new CliError({
1182
+ code: "DOMAIN_HOSTNAME_INVALID",
1183
+ domain: "app",
1184
+ summary: `Invalid custom domain "${hostname}"`,
1185
+ why: error.message,
1186
+ fix: "Pass a valid hostname like shop.acme.com and make sure DNS can be verified.",
1187
+ debug: formatDebugDetails(error),
1188
+ exitCode: 2,
1189
+ nextSteps: ["prisma-cli app domain add shop.acme.com"]
1190
+ });
1191
+ if (error.status === 429 || isDomainQuotaError(error)) return new CliError({
1192
+ code: "DOMAIN_QUOTA_EXCEEDED",
1193
+ domain: "app",
1194
+ summary: "Custom domain quota exceeded",
1195
+ why: error.message,
1196
+ fix: "Remove an existing custom domain before adding another one.",
1197
+ debug: formatDebugDetails(error),
1198
+ exitCode: 1,
1199
+ nextSteps: ["prisma-cli app domain remove <hostname>"]
1200
+ });
1201
+ if (error.status === 409) return domainAlreadyRegisteredError(hostname, error);
1202
+ if (error.status === 422) return new CliError({
1203
+ code: "NO_DEPLOYMENTS",
1204
+ domain: "app",
1205
+ summary: "Custom domain requires a live production deployment",
1206
+ why: "The selected production app does not have a promoted version that can receive a custom domain.",
1207
+ fix: "Deploy the app to the production branch, then rerun the domain command.",
1208
+ debug: formatDebugDetails(error),
1209
+ exitCode: 1,
1210
+ nextSteps: ["prisma-cli app deploy --branch production", `prisma-cli app domain add ${hostname}`]
1211
+ });
1212
+ return null;
1213
+ }
1214
+ function domainLookupApiError(command, hostname, error) {
1215
+ if (error.status === 404) return domainNotFoundError(hostname);
1216
+ if (command === "retry" && error.status === 409) return new CliError({
1217
+ code: "DOMAIN_RETRY_NOT_ELIGIBLE",
1218
+ domain: "app",
1219
+ summary: `Custom domain "${hostname}" is not eligible for retry`,
1220
+ why: error.message,
1221
+ fix: "Wait for the current verification or TLS step to finish, then rerun retry if the domain fails.",
1222
+ debug: formatDebugDetails(error),
1223
+ exitCode: 1,
1224
+ nextSteps: [`prisma-cli app domain show ${hostname}`]
1225
+ });
1226
+ return null;
1227
+ }
1166
1228
  function isDomainQuotaError(error) {
1167
1229
  if (error.status !== 409) return false;
1168
1230
  const text = `${error.message} ${error.hint ?? ""}`.toLowerCase();
@@ -1182,7 +1244,7 @@ function domainAlreadyRegisteredError(hostname, error) {
1182
1244
  }
1183
1245
  function isDomainDnsError(error) {
1184
1246
  const text = `${error.message} ${error.hint ?? ""}`.toLowerCase();
1185
- return text.includes("dns is not configured") || text.includes("dns verification failed") || text.includes("no cname") || text.includes("cname record") || text.includes("no a/aaaa") || /\bcname(?:s)?\s+to\b/.test(text);
1247
+ return text.includes("dns is not configured") || text.includes("dns verification failed") || text.includes("no cname") || text.includes("cname record") || text.includes("no a/aaaa") || CNAME_TO_PHRASE.test(text);
1186
1248
  }
1187
1249
  function domainDnsNotConfiguredError(hostname, error) {
1188
1250
  const target = extractDomainDnsTarget(error);
@@ -1200,7 +1262,7 @@ function domainDnsNotConfiguredError(hostname, error) {
1200
1262
  }
1201
1263
  function extractDomainDnsTarget(error) {
1202
1264
  const text = `${error.hint ?? ""} ${error.message}`;
1203
- return /\b((?:[a-z0-9-]+\.)+prisma\.build)\b/i.exec(text)?.[1]?.toLowerCase() ?? null;
1265
+ return PRISMA_BUILD_HOSTNAME.exec(text)?.[1]?.toLowerCase() ?? null;
1204
1266
  }
1205
1267
  function domainNotFoundError(hostname) {
1206
1268
  return new CliError({
@@ -1221,11 +1283,9 @@ function parseDomainWaitTimeout(value) {
1221
1283
  if (!value) return 900 * 1e3;
1222
1284
  const trimmed = value.trim().toLowerCase();
1223
1285
  if (trimmed === "0") return 0;
1224
- const match = /^(\d+)(ms|s|m|h)$/.exec(trimmed);
1286
+ const match = WAIT_TIMEOUT_DURATION.exec(trimmed);
1225
1287
  if (!match) throw usageError(`Invalid timeout "${value}"`, "Timeout must be a duration such as 0, 30s, 15m, or 1h.", "Pass --timeout 15m, or --timeout 0 to poll once.", ["prisma-cli app domain wait shop.acme.com --timeout 15m"], "app");
1226
- const amount = Number.parseInt(match[1], 10);
1227
- const unit = match[2];
1228
- return amount * (unit === "h" ? 3600 * 1e3 : unit === "m" ? 60 * 1e3 : unit === "s" ? 1e3 : 1);
1288
+ return Number.parseInt(match[1], 10) * (WAIT_TIMEOUT_UNIT_MULTIPLIER_MS[match[2]] ?? 1);
1229
1289
  }
1230
1290
  function readDomainWaitPollIntervalMs(context) {
1231
1291
  const raw = context.runtime.env.PRISMA_CLI_DOMAIN_WAIT_POLL_MS;
@@ -1276,27 +1336,19 @@ async function sleep(milliseconds, signal) {
1276
1336
  });
1277
1337
  }
1278
1338
  async function resolveDeployAppSelection(context, projectId, apps, options) {
1279
- if (options.explicitAppName) {
1280
- const matches = findAppsByName(apps, options.explicitAppName);
1281
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.explicitAppName, options.firstDeploy);
1282
- const matched = matches[0];
1283
- if (matched) return {
1284
- appId: matched.id,
1285
- displayName: matched.name,
1286
- annotation: "set by --app",
1287
- firstDeploy: options.firstDeploy
1288
- };
1289
- return {
1290
- appName: options.explicitAppName,
1291
- region: DEFAULT_REGION,
1292
- displayName: options.explicitAppName,
1293
- annotation: "set by --app",
1294
- firstDeploy: options.firstDeploy
1295
- };
1296
- }
1339
+ const newAppRegion = deployNewAppRegion(options.configRegion);
1340
+ if (options.explicitAppName) return resolveDeployAppByName(context, apps, {
1341
+ name: options.explicitAppName,
1342
+ matchedAnnotation: "set by --app",
1343
+ newAnnotation: "set by --app",
1344
+ requestedRegion: options.configRegion,
1345
+ newAppRegion,
1346
+ firstDeploy: options.firstDeploy
1347
+ });
1297
1348
  if (options.explicitAppId) {
1298
1349
  const matched = apps.find((app) => app.id === options.explicitAppId);
1299
1350
  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");
1351
+ assertDeployRegionMatchesExistingApp(matched, options.configRegion);
1300
1352
  return {
1301
1353
  appId: matched.id,
1302
1354
  displayName: matched.name,
@@ -1306,42 +1358,52 @@ async function resolveDeployAppSelection(context, projectId, apps, options) {
1306
1358
  }
1307
1359
  if (options.configAppName) {
1308
1360
  const configName = options.configAppName;
1309
- const matches = findAppsByName(apps, configName.value);
1310
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, configName.value, options.firstDeploy);
1311
- const matched = matches[0];
1312
- if (matched) return {
1313
- appId: matched.id,
1314
- displayName: matched.name,
1315
- annotation: configName.annotation,
1361
+ return resolveDeployAppByName(context, apps, {
1362
+ name: configName.value,
1363
+ matchedAnnotation: configName.annotation,
1364
+ newAnnotation: configName.annotation,
1365
+ requestedRegion: options.configRegion,
1366
+ newAppRegion,
1316
1367
  firstDeploy: options.firstDeploy
1317
- };
1368
+ });
1369
+ }
1370
+ const inferredName = await options.inferName();
1371
+ const newAnnotation = inferredName.source === "package-name" ? "created from package.json" : "created from directory name";
1372
+ return resolveDeployAppByName(context, apps, {
1373
+ name: inferredName.name,
1374
+ matchedAnnotation: "existing app on this branch",
1375
+ newAnnotation,
1376
+ requestedRegion: options.configRegion,
1377
+ newAppRegion,
1378
+ firstDeploy: options.firstDeploy
1379
+ });
1380
+ }
1381
+ async function resolveDeployAppByName(context, apps, options) {
1382
+ const matches = findAppsByName(apps, options.name);
1383
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.name, options.requestedRegion, options.newAppRegion, options.firstDeploy);
1384
+ const matched = matches[0];
1385
+ if (matched) {
1386
+ assertDeployRegionMatchesExistingApp(matched, options.requestedRegion);
1318
1387
  return {
1319
- appName: configName.value,
1320
- region: DEFAULT_REGION,
1321
- displayName: configName.value,
1322
- annotation: configName.annotation,
1388
+ appId: matched.id,
1389
+ displayName: matched.name,
1390
+ annotation: options.matchedAnnotation,
1323
1391
  firstDeploy: options.firstDeploy
1324
1392
  };
1325
1393
  }
1326
- const inferredName = await options.inferName();
1327
- const matches = findAppsByName(apps, inferredName.name);
1328
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
1329
- const matched = matches[0];
1330
- if (matched) return {
1331
- appId: matched.id,
1332
- displayName: matched.name,
1333
- annotation: "existing app on this branch",
1334
- firstDeploy: options.firstDeploy
1335
- };
1336
1394
  return {
1337
- appName: inferredName.name,
1338
- region: DEFAULT_REGION,
1339
- displayName: inferredName.name,
1340
- annotation: inferredName.source === "package-name" ? "created from package.json" : "created from directory name",
1395
+ appName: options.name,
1396
+ region: options.newAppRegion,
1397
+ displayName: options.name,
1398
+ annotation: options.newAnnotation,
1341
1399
  firstDeploy: options.firstDeploy
1342
1400
  };
1343
1401
  }
1344
- async function resolveAmbiguousDeployApp(context, matches, targetName, firstDeploy) {
1402
+ function assertDeployRegionMatchesExistingApp(app, requestedRegion) {
1403
+ if (requestedRegion?.annotation !== "set by --region" || !app.region || app.region === requestedRegion.value) return;
1404
+ 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");
1405
+ }
1406
+ async function resolveAmbiguousDeployApp(context, matches, targetName, requestedRegion, newAppRegion, firstDeploy) {
1345
1407
  if (canPrompt(context)) {
1346
1408
  const createNew = "__create_new_app__";
1347
1409
  const cancel = "__cancel__";
@@ -1367,11 +1429,12 @@ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDepl
1367
1429
  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");
1368
1430
  if (selected === createNew) return {
1369
1431
  appName: targetName,
1370
- region: DEFAULT_REGION,
1432
+ region: newAppRegion,
1371
1433
  displayName: targetName,
1372
1434
  annotation: "created from package.json",
1373
1435
  firstDeploy
1374
1436
  };
1437
+ assertDeployRegionMatchesExistingApp(selected, requestedRegion);
1375
1438
  return {
1376
1439
  appId: selected.id,
1377
1440
  displayName: selected.name,
@@ -1393,6 +1456,9 @@ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDepl
1393
1456
  nextSteps: ["prisma-cli app deploy --app <name>"]
1394
1457
  });
1395
1458
  }
1459
+ function deployNewAppRegion(configRegion) {
1460
+ return configRegion?.value;
1461
+ }
1396
1462
  async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
1397
1463
  if (explicitAppName) {
1398
1464
  const matched = findAppByName(apps, explicitAppName);
@@ -1435,6 +1501,7 @@ async function confirmAppRemoval(context, app) {
1435
1501
  await textPrompt({
1436
1502
  input: context.runtime.stdin,
1437
1503
  output: context.output.stderr,
1504
+ signal: context.runtime.signal,
1438
1505
  message: `Type ${app.name} to confirm app removal`,
1439
1506
  placeholder: app.name,
1440
1507
  validate: (value) => value === app.name ? void 0 : `Type "${app.name}" to confirm removal.`
@@ -1467,13 +1534,21 @@ function requireDeploymentForApp(deployments, deploymentId, appName) {
1467
1534
  nextSteps: ["prisma-cli app list-deploys"]
1468
1535
  });
1469
1536
  }
1537
+ /**
1538
+ * Resolves the app's live deployment from the app pointer, the provider's live
1539
+ * flag, then locally cached state.
1540
+ *
1541
+ * @returns the live deployment id, or null when no authoritative signal exists.
1542
+ * Callers must treat null as "not known to be live" rather than assuming the
1543
+ * newest deployment is live.
1544
+ */
1470
1545
  async function resolveCurrentLiveDeploymentId(context, projectId, app, deployments) {
1471
1546
  if (app.liveDeploymentId && deployments.some((deployment) => deployment.id === app.liveDeploymentId)) return app.liveDeploymentId;
1472
1547
  const providerLiveDeployment = deployments.find((deployment) => deployment.live === true);
1473
1548
  if (providerLiveDeployment) return providerLiveDeployment.id;
1474
1549
  const knownLiveDeploymentId = await context.stateStore.readKnownLiveDeployment(projectId, app.id);
1475
1550
  if (knownLiveDeploymentId && deployments.some((deployment) => deployment.id === knownLiveDeploymentId)) return knownLiveDeploymentId;
1476
- return deployments[0]?.id ?? null;
1551
+ return null;
1477
1552
  }
1478
1553
  function buildAppShowNextSteps(liveUrl, liveDeployment, deployments) {
1479
1554
  const nextSteps = [];
@@ -1528,7 +1603,7 @@ async function requirePreviewAppProvider(context) {
1528
1603
  return provider;
1529
1604
  }
1530
1605
  async function requirePreviewAppProviderWithClient(context) {
1531
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
1606
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
1532
1607
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
1533
1608
  return {
1534
1609
  client,
@@ -1572,15 +1647,15 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
1572
1647
  };
1573
1648
  }
1574
1649
  async function resolveProjectContext(context, client, explicitProject, options) {
1575
- const authState = await requireAuthenticatedAuthState(context);
1576
- if (!authState.workspace) throw workspaceRequiredError();
1650
+ const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1651
+ if (!workspace) throw workspaceRequiredError();
1577
1652
  const resolvedResult = await resolveProjectTarget({
1578
1653
  context,
1579
- workspace: authState.workspace,
1654
+ workspace,
1580
1655
  explicitProject,
1581
1656
  envProjectId: options?.envProjectId,
1582
1657
  projectDir: options?.projectDir,
1583
- listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
1658
+ listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
1584
1659
  commandName: options?.commandName
1585
1660
  });
1586
1661
  if (resolvedResult.isErr()) throw projectResolutionErrorToCliError(resolvedResult.error);
@@ -1604,7 +1679,7 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1604
1679
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1605
1680
  if (!workspace) throw workspaceRequiredError();
1606
1681
  const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1607
- const projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
1682
+ const projects = await listRealWorkspaceProjects(client, context.runtime.signal);
1608
1683
  if (explicitProject) return withRemoteDeployBranch(provider, {
1609
1684
  workspace,
1610
1685
  project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
@@ -1615,54 +1690,12 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1615
1690
  },
1616
1691
  localPinAction: "linked"
1617
1692
  }, branch, context.runtime.signal);
1618
- if (options.createProjectName) {
1619
- const projectName = options.createProjectName.trim();
1620
- if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1621
- return withRemoteDeployBranch(provider, {
1622
- workspace,
1623
- project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
1624
- resolution: {
1625
- projectSource: "created",
1626
- targetName: projectName,
1627
- targetNameSource: "explicit"
1628
- },
1629
- localPinAction: "created"
1630
- }, branch, context.runtime.signal);
1631
- }
1632
- if (options.envProjectId) {
1633
- const project = projects.find((candidate) => candidate.id === options.envProjectId);
1634
- if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1635
- return withRemoteDeployBranch(provider, {
1636
- workspace,
1637
- project: toProjectSummary(project),
1638
- resolution: {
1639
- projectSource: "env",
1640
- targetName: options.envProjectId,
1641
- targetNameSource: "env"
1642
- }
1643
- }, branch, context.runtime.signal);
1644
- }
1693
+ if (options.createProjectName) return withRemoteDeployBranch(provider, await resolveCreatedDeployProject(provider, options.createProjectName, workspace, context.runtime.signal, options.createProjectRegion), branch, context.runtime.signal);
1694
+ if (options.envProjectId) return withRemoteDeployBranch(provider, resolveEnvDeployProject(options.envProjectId, projects, workspace), branch, context.runtime.signal);
1645
1695
  const localPin = options.localPin;
1646
- if (localPin.kind === "present") {
1647
- if (localPin.pin.workspaceId !== workspace.id) throw localProjectWorkspaceMismatchError({
1648
- pinnedWorkspaceId: localPin.pin.workspaceId,
1649
- pinnedProjectId: localPin.pin.projectId,
1650
- activeWorkspace: workspace
1651
- });
1652
- const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1653
- if (!project) throw localResolutionPinStaleError();
1654
- return withRemoteDeployBranch(provider, {
1655
- workspace,
1656
- project: toProjectSummary(project),
1657
- resolution: {
1658
- projectSource: "local-pin",
1659
- targetName: project.name,
1660
- targetNameSource: "local-pin"
1661
- }
1662
- }, branch, context.runtime.signal);
1663
- }
1696
+ if (localPin.kind === "present") return withRemoteDeployBranch(provider, resolveLocalPinDeployProject(localPin.pin, projects, workspace), branch, context.runtime.signal);
1664
1697
  const platformMapping = await resolveDurablePlatformMapping();
1665
- if (platformMapping && platformMapping.workspace.id === workspace.id) return withRemoteDeployBranch(provider, {
1698
+ if (platformMapping && sameWorkspaceId(platformMapping.workspace.id, workspace.id)) return withRemoteDeployBranch(provider, {
1666
1699
  workspace,
1667
1700
  project: toProjectSummary(platformMapping),
1668
1701
  resolution: {
@@ -1671,14 +1704,59 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1671
1704
  targetNameSource: "platform-mapping"
1672
1705
  }
1673
1706
  }, branch, context.runtime.signal);
1674
- if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch, context.runtime.signal);
1707
+ if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects, options.createProjectRegion), branch, context.runtime.signal);
1675
1708
  throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1676
1709
  }
1677
- async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
1710
+ async function resolveCreatedDeployProject(provider, createProjectName, workspace, signal, createProjectRegion) {
1711
+ const projectName = createProjectName.trim();
1712
+ if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1713
+ return {
1714
+ workspace,
1715
+ project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, signal, createProjectRegion)),
1716
+ resolution: {
1717
+ projectSource: "created",
1718
+ targetName: projectName,
1719
+ targetNameSource: "explicit"
1720
+ },
1721
+ localPinAction: "created"
1722
+ };
1723
+ }
1724
+ function resolveEnvDeployProject(envProjectId, projects, workspace) {
1725
+ const project = projects.find((candidate) => candidate.id === envProjectId);
1726
+ if (!project) throw projectNotFoundError(envProjectId, workspace);
1727
+ return {
1728
+ workspace,
1729
+ project: toProjectSummary(project),
1730
+ resolution: {
1731
+ projectSource: "env",
1732
+ targetName: envProjectId,
1733
+ targetNameSource: "env"
1734
+ }
1735
+ };
1736
+ }
1737
+ function resolveLocalPinDeployProject(pin, projects, workspace) {
1738
+ if (!sameWorkspaceId(pin.workspaceId, workspace.id)) throw localProjectWorkspaceMismatchError({
1739
+ pinnedWorkspaceId: pin.workspaceId,
1740
+ pinnedProjectId: pin.projectId,
1741
+ activeWorkspace: workspace
1742
+ });
1743
+ const project = projects.find((candidate) => candidate.id === pin.projectId);
1744
+ if (!project) throw localResolutionPinStaleError();
1745
+ return {
1746
+ workspace,
1747
+ project: toProjectSummary(project),
1748
+ resolution: {
1749
+ projectSource: "local-pin",
1750
+ targetName: project.name,
1751
+ targetNameSource: "local-pin"
1752
+ }
1753
+ };
1754
+ }
1755
+ async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects, createProjectRegion) {
1678
1756
  const setup = await promptForProjectSetupChoice({
1679
1757
  context,
1680
1758
  projects,
1681
- createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal),
1759
+ createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal, createProjectRegion),
1682
1760
  cancel: {
1683
1761
  why: "Deploy needs a Project before it can continue.",
1684
1762
  fix: "Choose an existing Project or create a new one, then rerun deploy.",
@@ -1696,9 +1774,10 @@ async function resolveInteractiveDeployProjectSetup(context, provider, workspace
1696
1774
  localPinAction: setup.action
1697
1775
  };
1698
1776
  }
1699
- async function createProjectForDeploySetup(provider, projectName, workspace, signal) {
1777
+ async function createProjectForDeploySetup(provider, projectName, workspace, signal, region) {
1700
1778
  const created = await provider.createProject({
1701
1779
  name: projectName,
1780
+ region,
1702
1781
  signal
1703
1782
  }).catch((error) => {
1704
1783
  throw projectCreateFailedError(error, projectName, workspace, {
@@ -1714,6 +1793,7 @@ async function createProjectForDeploySetup(provider, projectName, workspace, sig
1714
1793
  return {
1715
1794
  id: created.id,
1716
1795
  name: created.name,
1796
+ ...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {},
1717
1797
  workspace
1718
1798
  };
1719
1799
  }
@@ -1794,7 +1874,7 @@ async function resolveComputeTargetOrThrow(context, configTarget, commandName, o
1794
1874
  config = loaded.value;
1795
1875
  }
1796
1876
  if (!config) {
1797
- 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");
1877
+ if (configTarget) throw usageError(`App target "${configTarget}" requires a compute config file`, `No ${COMPUTE_CONFIG_FILENAME} exists in the current directory, so there are no named app targets.`, `Create ${COMPUTE_CONFIG_FILENAME} with an apps entry named "${configTarget}", or rerun without the target argument.`, [`prisma-cli app ${commandName}`], "app");
1798
1878
  return {
1799
1879
  config: null,
1800
1880
  target: null
@@ -1937,55 +2017,21 @@ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signa
1937
2017
  }
1938
2018
  }
1939
2019
  async function detectDeployFramework(cwd, signal) {
1940
- const packageJson = await readBunPackageJson(cwd, signal);
1941
- for (const framework of FRAMEWORKS) {
1942
- if (framework.detectConfigFiles.length === 0 && framework.detectPackages.length === 0) continue;
1943
- const configFile = await detectFrameworkConfigFile(cwd, framework, signal);
1944
- if (!configFile.exists && !hasAnyPackageDependency(packageJson, framework.detectPackages)) continue;
1945
- const annotation = framework.key === "nextjs" && configFile.standalone ? "standalone output detected" : configFile.exists ? `detected from ${path.basename(configFile.path)}` : "detected from package.json";
1946
- return {
1947
- key: framework.key,
1948
- buildType: framework.buildType,
1949
- displayName: framework.displayName,
1950
- annotation
1951
- };
1952
- }
1953
- return null;
1954
- }
1955
- async function detectFrameworkConfigFile(cwd, framework, signal) {
1956
- for (const candidate of framework.detectConfigFiles) {
1957
- const filePath = path.join(cwd, candidate);
1958
- signal.throwIfAborted();
1959
- try {
1960
- const content = await readFile(filePath, {
1961
- encoding: "utf8",
1962
- signal
1963
- });
1964
- return {
1965
- exists: true,
1966
- standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),
1967
- path: filePath
1968
- };
1969
- } catch (error) {
1970
- if (signal.aborted) throw error;
1971
- if (error.code !== "ENOENT") throw error;
1972
- }
1973
- }
2020
+ const detected = await detectComputeAppFromDirectory({
2021
+ appPath: cwd,
2022
+ signal
2023
+ });
2024
+ if (!detected) return null;
2025
+ let annotation = "detected from package.json";
2026
+ if (detected.configFile?.standaloneOutput) annotation = "standalone output detected";
2027
+ else if (detected.configFile) annotation = `detected from ${path.basename(detected.configFile.path)}`;
1974
2028
  return {
1975
- exists: false,
1976
- standalone: false,
1977
- path: null
2029
+ key: detected.framework,
2030
+ buildType: detected.buildType,
2031
+ displayName: detected.frameworkName,
2032
+ annotation
1978
2033
  };
1979
2034
  }
1980
- function hasPackageDependency(packageJson, dependencyName) {
1981
- return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
1982
- }
1983
- function hasAnyPackageDependency(packageJson, dependencyNames) {
1984
- return dependencyNames.some((dependencyName) => hasPackageDependency(packageJson, dependencyName));
1985
- }
1986
- function hasDependency(dependencies, dependencyName) {
1987
- return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1988
- }
1989
2035
  function frameworkFromUserFacingValue(value, annotation) {
1990
2036
  const framework = frameworkFromAlias(value);
1991
2037
  if (!framework) throw frameworkNotDetectedError(void 0, value);
@@ -1996,11 +2042,6 @@ function frameworkFromUserFacingValue(value, annotation) {
1996
2042
  annotation
1997
2043
  };
1998
2044
  }
1999
- /**
2000
- * The nuxt and astro strategies build with their framework CLI and stage
2001
- * fixed output, so a compute config `build` block has nothing to apply to.
2002
- * Erroring beats silently ignoring committed settings.
2003
- */
2004
2045
  function assertConfigBackedBuildSettings(buildType) {
2005
2046
  if (isConfigBackedBuildType(buildType)) return;
2006
2047
  const displayName = FRAMEWORKS.find((framework) => framework.buildType === buildType)?.displayName ?? buildType;
@@ -2042,15 +2083,23 @@ function maybeRenderDeployBuildSettings(context, resolution) {
2042
2083
  if (context.flags.json || context.flags.quiet) return;
2043
2084
  const settings = resolution.settings;
2044
2085
  const title = resolution.status === "config" ? `Using ${resolution.relativeConfigPath}` : "Build settings";
2045
- context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [{
2046
- label: "Build Command",
2047
- value: settings.buildCommand ?? "none",
2048
- origin: settings.buildCommandSource ?? void 0
2049
- }, {
2050
- label: "Output Directory",
2051
- value: settings.outputDirectory,
2052
- origin: settings.outputDirectorySource ?? void 0
2053
- }]).join("\n")}\n\n`);
2086
+ context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [
2087
+ {
2088
+ label: "Build Command",
2089
+ value: settings.buildCommand ?? "none",
2090
+ origin: settings.buildCommandSource ?? void 0
2091
+ },
2092
+ {
2093
+ label: "Output Directory",
2094
+ value: settings.outputDirectory,
2095
+ origin: settings.outputDirectorySource ?? void 0
2096
+ },
2097
+ ...settings.entrypoint ? [{
2098
+ label: "Entrypoint",
2099
+ value: settings.entrypoint,
2100
+ origin: settings.entrypointSource ?? void 0
2101
+ }] : []
2102
+ ]).join("\n")}\n\n`);
2054
2103
  }
2055
2104
  function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
2056
2105
  if (context.flags.json || context.flags.quiet) return;
@@ -2068,6 +2117,7 @@ async function maybeCustomizeDeploySettings(context, options) {
2068
2117
  if (!await confirmPrompt({
2069
2118
  input: context.runtime.stdin,
2070
2119
  output: context.runtime.stderr,
2120
+ signal: context.runtime.signal,
2071
2121
  message: "Customize build settings?",
2072
2122
  initialValue: false
2073
2123
  })) return {
@@ -2077,6 +2127,7 @@ async function maybeCustomizeDeploySettings(context, options) {
2077
2127
  const framework = frameworkFromUserFacingValue(await selectPrompt({
2078
2128
  input: context.runtime.stdin,
2079
2129
  output: context.runtime.stderr,
2130
+ signal: context.runtime.signal,
2080
2131
  message: `Framework (${options.framework.displayName})`,
2081
2132
  choices: FRAMEWORKS.map((framework) => ({
2082
2133
  label: framework.displayName,
@@ -2086,6 +2137,7 @@ async function maybeCustomizeDeploySettings(context, options) {
2086
2137
  const requestedPort = await textPrompt({
2087
2138
  input: context.runtime.stdin,
2088
2139
  output: context.runtime.stderr,
2140
+ signal: context.runtime.signal,
2089
2141
  message: `HTTP port (${options.runtime.port})`,
2090
2142
  placeholder: String(options.runtime.port),
2091
2143
  validate: validateDeployHttpPortText
@@ -2149,7 +2201,7 @@ async function readCurrentWorkspaceId(context) {
2149
2201
  function normalizeBuildType(requestedBuildType) {
2150
2202
  if (!requestedBuildType) return "auto";
2151
2203
  if (isPreviewBuildType(requestedBuildType)) return requestedBuildType;
2152
- throw usageError(`Unsupported build type "${requestedBuildType}"`, `Only ${APP_BUILD_TYPES.join(", ")} are supported in the current preview.`, "Pass a supported --build-type value.", getBuildTypeExamples("build"), "app");
2204
+ 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");
2153
2205
  }
2154
2206
  function isPreviewBuildType(value) {
2155
2207
  return APP_BUILD_TYPES.includes(value);
@@ -2172,8 +2224,9 @@ function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
2172
2224
  * and Bun/Hono have dev servers in the current preview.
2173
2225
  */
2174
2226
  async function resolveLocalRunFramework(context, options) {
2227
+ if (options.requestedBuildType === "auto" && options.entrypoint) return frameworkFromUserFacingValue("bun", "set by --entry");
2175
2228
  if (LOCAL_DEV_BUILD_TYPES.includes(options.requestedBuildType)) {
2176
- if (options.configFramework && computeFrameworkToBuildType(options.configFramework) === options.requestedBuildType) return frameworkFromUserFacingValue(options.configFramework, `set by ${COMPUTE_CONFIG_FILENAME$1}`);
2229
+ if (options.configFramework && computeFrameworkToBuildType(options.configFramework) === options.requestedBuildType) return frameworkFromUserFacingValue(options.configFramework, `set by ${COMPUTE_CONFIG_FILENAME}`);
2177
2230
  return frameworkFromUserFacingValue(options.requestedBuildType, "set by --build-type");
2178
2231
  }
2179
2232
  const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
@@ -2195,9 +2248,18 @@ function parseDeployHttpPort(requestedPort) {
2195
2248
  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");
2196
2249
  return port;
2197
2250
  }
2198
- function ensurePreviewAppMode(context) {
2199
- if (isRealMode(context)) return;
2200
- 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");
2251
+ function parseDeployRegion(requestedRegion, source) {
2252
+ const region = requestedRegion.trim();
2253
+ 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");
2254
+ 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");
2255
+ return region;
2256
+ }
2257
+ function normalizeDeployRegionInput(region) {
2258
+ if (!region) return;
2259
+ return {
2260
+ ...region,
2261
+ value: parseDeployRegion(region.value, region.annotation)
2262
+ };
2201
2263
  }
2202
2264
  function deployFailedError(summary, error, nextSteps) {
2203
2265
  return new CliError({
@@ -2211,47 +2273,53 @@ function deployFailedError(summary, error, nextSteps) {
2211
2273
  nextSteps
2212
2274
  });
2213
2275
  }
2276
+ function describeStalledDeployPhase(progress) {
2277
+ if (progress.uploadCompleted) return "The artifact uploaded, but the deployment did not start.";
2278
+ if (progress.archiveReady) return "The app built locally, but the artifact did not finish uploading.";
2279
+ return "The app built locally, but the deployment did not start.";
2280
+ }
2281
+ function localBuildFailedError(why, debug) {
2282
+ const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
2283
+ const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
2284
+ const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
2285
+ const nextActions = standaloneOutputFailure ? [{
2286
+ kind: "edit-file",
2287
+ journey: "deploy-app",
2288
+ label: "Add Next.js standalone output",
2289
+ reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
2290
+ }, {
2291
+ kind: "run-command",
2292
+ journey: "deploy-app",
2293
+ label: "Rerun deploy",
2294
+ command: "prisma-cli app deploy"
2295
+ }] : [];
2296
+ return new CliError({
2297
+ code: "BUILD_FAILED",
2298
+ domain: "app",
2299
+ summary: "Build failed locally.",
2300
+ why,
2301
+ fix,
2302
+ debug,
2303
+ meta: { phase: "build" },
2304
+ humanLines: [
2305
+ "Build failed locally.",
2306
+ "",
2307
+ `✗ Built ${why}`,
2308
+ "",
2309
+ `Fix: ${fix}`
2310
+ ],
2311
+ exitCode: 1,
2312
+ nextSteps,
2313
+ nextActions
2314
+ });
2315
+ }
2214
2316
  function appDeployFailedError(error, progress) {
2215
2317
  const why = error instanceof Error ? error.message : String(error);
2216
2318
  const debug = formatDebugDetails(error);
2217
- if (progress.buildStarted && !progress.buildCompleted) {
2218
- const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
2219
- const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
2220
- const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
2221
- const nextActions = standaloneOutputFailure ? [{
2222
- kind: "edit-file",
2223
- journey: "deploy-app",
2224
- label: "Add Next.js standalone output",
2225
- reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
2226
- }, {
2227
- kind: "run-command",
2228
- journey: "deploy-app",
2229
- label: "Rerun deploy",
2230
- command: "prisma-cli app deploy"
2231
- }] : [];
2232
- return new CliError({
2233
- code: "BUILD_FAILED",
2234
- domain: "app",
2235
- summary: "Build failed locally.",
2236
- why,
2237
- fix,
2238
- debug,
2239
- meta: { phase: "build" },
2240
- humanLines: [
2241
- "Build failed locally.",
2242
- "",
2243
- `✗ Built ${why}`,
2244
- "",
2245
- `Fix: ${fix}`
2246
- ],
2247
- exitCode: 1,
2248
- nextSteps,
2249
- nextActions
2250
- });
2251
- }
2319
+ if (progress.buildStarted && !progress.buildCompleted) return localBuildFailedError(why, debug);
2252
2320
  if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
2253
2321
  const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
2254
- 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."];
2322
+ 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."];
2255
2323
  const urlLines = progress.deploymentUrl ? [
2256
2324
  "",
2257
2325
  "URL",
@@ -2268,7 +2336,7 @@ function appDeployFailedError(error, progress) {
2268
2336
  ] : [
2269
2337
  phaseHeadline,
2270
2338
  "",
2271
- progress.uploadCompleted ? "The artifact uploaded, but the deployment did not start." : progress.archiveReady ? "The app built locally, but the artifact did not finish uploading." : "The app built locally, but the deployment did not start.",
2339
+ describeStalledDeployPhase(progress),
2272
2340
  "",
2273
2341
  ...recoveryLines
2274
2342
  ];
@@ -2277,11 +2345,11 @@ function appDeployFailedError(error, progress) {
2277
2345
  domain: "app",
2278
2346
  summary: phaseHeadline,
2279
2347
  why,
2280
- fix: progress.versionId ? `Inspect logs with prisma-cli app logs --deployment ${progress.versionId}.` : "Retry the command, or rerun with --trace for more detailed diagnostics.",
2348
+ fix: progress.deploymentId ? `Inspect logs with prisma-cli app logs --deployment ${progress.deploymentId}.` : "Retry the command, or rerun with --trace for more detailed diagnostics.",
2281
2349
  debug,
2282
2350
  meta: {
2283
2351
  phase: progress.containerLive ? "runtime_ready" : "deploy",
2284
- deploymentId: progress.versionId,
2352
+ deploymentId: progress.deploymentId,
2285
2353
  deploymentUrl: progress.deploymentUrl
2286
2354
  },
2287
2355
  humanLines,
@@ -2352,7 +2420,7 @@ function projectSetupRequiredError(projects, suggestedName) {
2352
2420
  });
2353
2421
  }
2354
2422
  function isNextStandaloneOutputFailure(message) {
2355
- return /next\.?js/i.test(message) && /standalone output/i.test(message);
2423
+ return NEXTJS_MENTION.test(message) && STANDALONE_OUTPUT_MENTION.test(message);
2356
2424
  }
2357
2425
  function noDeploymentsError(summary, why) {
2358
2426
  return new CliError({
@@ -2395,15 +2463,12 @@ function isAutoBuildDetectionError(error) {
2395
2463
  return error instanceof Error && error.message.startsWith("Entrypoint is required.");
2396
2464
  }
2397
2465
  function formatBuildTypeName(buildType) {
2398
- switch (buildType) {
2399
- case "nextjs": return "Next.js";
2400
- case "nuxt": return "Nuxt";
2401
- case "astro": return "Astro";
2402
- case "nestjs": return "NestJS";
2403
- case "tanstack-start": return "TanStack Start";
2404
- case "bun": return "Bun";
2405
- case "auto": return "Auto";
2466
+ if (buildType === "auto") return "Auto";
2467
+ for (let index = FRAMEWORKS.length - 1; index >= 0; index -= 1) {
2468
+ const framework = FRAMEWORKS[index];
2469
+ if (framework?.buildType === buildType) return framework.displayName;
2406
2470
  }
2471
+ return buildType;
2407
2472
  }
2408
2473
  function removeFailedError(summary, error, nextSteps) {
2409
2474
  return new CliError({
@@ -2440,4 +2505,4 @@ function toOptionalEnvVars(envVars) {
2440
2505
  return Object.keys(envVars).length > 0 ? envVars : void 0;
2441
2506
  }
2442
2507
  //#endregion
2443
- export { runAppBuild, runAppDeploy, runAppDomainAdd, runAppDomainRemove, runAppDomainRetry, runAppDomainShow, runAppDomainWait, runAppListDeploys, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy };
2508
+ export { detectDeployFramework, runAppBuild, runAppDeploy, runAppDomainAdd, runAppDomainRemove, runAppDomainRetry, runAppDomainShow, runAppDomainWait, runAppListDeploys, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy };