@prisma/cli 3.0.0-dev.87.1 → 3.0.0-dev.88.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.
@@ -10,11 +10,13 @@ import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, pr
10
10
  import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
11
11
  import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
12
12
  import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
13
+ import { COMPUTE_CONFIG_FILENAME as COMPUTE_CONFIG_FILENAME$1, ComputeConfigTargetRequiredError, computeConfigErrorToCliError, computeFrameworkToBuildType, computeTargetAppDir, inferComputeTargetFromCwd, loadComputeConfig as loadComputeConfig$1, mergeComputeDeployInputs, mergeComputeLocalInputs, selectComputeDeployTarget } from "../lib/app/compute-config.js";
13
14
  import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
15
+ import { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy } from "../lib/app/deploy-plan.js";
14
16
  import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
15
17
  import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
16
- import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
17
- import { resolveOrCreatePreviewBuildSettings } from "../lib/app/preview-build-settings.js";
18
+ import { DEFAULT_LOCAL_DEV_PORT, runLocalApp } from "../lib/app/local-dev.js";
19
+ import { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredPreviewBuildSettings, resolveInferredPreviewBuildSettings } from "../lib/app/preview-build-settings.js";
18
20
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
19
21
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
20
22
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
@@ -28,32 +30,49 @@ import { writeJsonEvent } from "../shell/output.js";
28
30
  import { createSelectPromptPort } from "./select-prompt-port.js";
29
31
  import { requireAuthenticatedAuthState } from "./auth.js";
30
32
  import { listRealWorkspaceProjects } from "./project.js";
33
+ import { ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, frameworkByKey, frameworkFromAlias, isConfigBackedBuildType } from "@prisma/compute-sdk/config";
31
34
  import { access, readFile } from "node:fs/promises";
32
35
  import path from "node:path";
33
36
  import { Result, matchError } from "better-result";
34
37
  import open from "open";
35
38
  //#region src/controllers/app.ts
36
- const DEPLOY_FRAMEWORKS = [
37
- "nextjs",
38
- "hono",
39
- "tanstack-start",
40
- "bun"
41
- ];
42
- const TANSTACK_START_PACKAGES = ["@tanstack/react-start", "@tanstack/solid-start"];
43
39
  const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
44
40
  const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
45
41
  const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
46
42
  function isRealMode(context) {
47
43
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
48
44
  }
49
- async function runAppBuild(context, entrypoint, requestedBuildType) {
50
- const buildType = normalizeBuildType(requestedBuildType);
51
- assertSupportedEntrypoint(buildType, entrypoint, "build");
45
+ async function runAppBuild(context, options) {
46
+ const compute = await resolveComputeTargetOrThrow(context, options?.configTarget, "build");
47
+ const merged = mergeComputeLocalInputs({
48
+ cli: {
49
+ entrypoint: options?.entrypoint,
50
+ buildType: options?.buildType
51
+ },
52
+ target: compute.target
53
+ });
54
+ const appDir = await resolveComputeAppDir(context, compute);
55
+ let buildType = normalizeBuildType(merged.buildType);
56
+ if (compute.target?.build && buildType === "auto") {
57
+ const detected = await detectDeployFramework(appDir, context.runtime.signal);
58
+ if (!detected) throw frameworkNotDetectedError(appDir);
59
+ buildType = detected.buildType;
60
+ }
61
+ assertSupportedEntrypoint(buildType, merged.entrypoint, "build");
62
+ if (compute.target?.build && buildType !== "auto") assertConfigBackedBuildSettings(buildType);
63
+ const buildSettings = compute.config && compute.target?.build && isConfigBackedBuildType(buildType) ? (await resolveConfiguredPreviewBuildSettings({
64
+ appPath: appDir,
65
+ buildType,
66
+ configured: compute.target.build,
67
+ configPath: compute.config.configPath,
68
+ signal: context.runtime.signal
69
+ })).settings : void 0;
52
70
  try {
53
71
  const { artifact, buildType: actualBuildType } = await executePreviewBuild({
54
- appPath: context.runtime.cwd,
55
- entrypoint,
72
+ appPath: appDir,
73
+ entrypoint: merged.entrypoint,
56
74
  buildType,
75
+ buildSettings,
57
76
  signal: context.runtime.signal
58
77
  });
59
78
  return {
@@ -71,17 +90,33 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
71
90
  throw buildFailedError("Local app build failed", error);
72
91
  }
73
92
  }
74
- async function runAppRun(context, entrypoint, requestedBuildType, requestedPort) {
93
+ async function runAppRun(context, options) {
75
94
  if (context.flags.json) throw usageError("App run does not support --json", "This command streams the framework dev server directly and cannot return structured JSON.", "Rerun without --json to pass framework logs through directly.", ["prisma-cli app run"], "app");
76
- const buildType = normalizeBuildType(requestedBuildType);
77
- assertSupportedEntrypoint(buildType, entrypoint, "run");
78
- const port = parseLocalPort(requestedPort);
79
- const resolvedBuildType = await requireLocalBuildType(context, buildType, "run");
95
+ const compute = await resolveComputeTargetOrThrow(context, options?.configTarget, "run");
96
+ const merged = mergeComputeLocalInputs({
97
+ cli: {
98
+ entrypoint: options?.entrypoint,
99
+ buildType: options?.buildType,
100
+ port: options?.port
101
+ },
102
+ target: compute.target
103
+ });
104
+ if (merged.buildTypeFromConfig && compute.target?.framework && !frameworkByKey(compute.target.framework).hasLocalDevServer) throw usageError(`App run does not support the ${compute.target?.framework} framework yet`, `${compute.config?.relativeConfigPath ?? COMPUTE_CONFIG_FILENAME$1} sets a framework that has no local dev server in the current preview.`, "Run the framework dev server directly, or pass --build-type nextjs or --build-type bun to override.", ["prisma-cli app run --build-type nextjs", "prisma-cli app run --build-type bun --entry server.ts"], "app");
105
+ const appDir = await resolveComputeAppDir(context, compute);
106
+ const buildType = normalizeBuildType(merged.buildType);
107
+ assertSupportedEntrypoint(buildType, merged.entrypoint, "run");
108
+ const port = parseLocalPort(merged.port);
109
+ const framework = await resolveLocalRunFramework(context, {
110
+ requestedBuildType: buildType,
111
+ configFramework: compute.target?.framework ?? null,
112
+ appDir
113
+ });
114
+ const entrypoint = framework.buildType === "bun" ? await resolveDeployEntrypoint(appDir, framework, merged.entrypoint, context.runtime.signal) : merged.entrypoint;
80
115
  let runResult;
81
116
  try {
82
117
  runResult = await runLocalApp({
83
- appPath: context.runtime.cwd,
84
- buildType: resolvedBuildType,
118
+ appPath: appDir,
119
+ buildType: framework.buildType,
85
120
  entrypoint,
86
121
  port,
87
122
  env: context.runtime.env,
@@ -106,6 +141,106 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
106
141
  }
107
142
  async function runAppDeploy(context, appName, options) {
108
143
  ensurePreviewAppMode(context);
144
+ const loaded = await loadComputeConfig$1(context.runtime.cwd, context.runtime.signal);
145
+ if (loaded.isErr()) throw computeConfigErrorToCliError(loaded.error, "deploy");
146
+ const config = loaded.value;
147
+ const plan = planAppDeploy({
148
+ config,
149
+ requestedTarget: options?.configTarget ?? (config ? inferComputeTargetFromCwd(config, context.runtime.cwd) : void 0),
150
+ hasCreateProject: options?.createProjectName !== void 0
151
+ });
152
+ if (plan.mode === "all") return runAppDeployAll(context, config, plan.targets, appName, options);
153
+ return runSingleAppDeploy(context, appName, options, config);
154
+ }
155
+ async function runAppDeployAll(context, config, plannedTargets, appName, options) {
156
+ assertNoPerAppInputsForDeployAll(context, config, appName, options);
157
+ const deployments = [];
158
+ const warnings = [];
159
+ for (const planned of plannedTargets) {
160
+ maybeRenderDeployAllTargetHeader(context, planned);
161
+ const targetOptions = {
162
+ ...options,
163
+ configTarget: planned.targetKey,
164
+ createProjectName: planned.bindsCreateProject ? options?.createProjectName : void 0
165
+ };
166
+ try {
167
+ const single = await runSingleAppDeploy(context, void 0, targetOptions, config);
168
+ deployments.push({
169
+ target: planned.targetKey,
170
+ result: single.result
171
+ });
172
+ warnings.push(...single.warnings);
173
+ } catch (error) {
174
+ throw deployAllFailedError(error, config, planned.index, deployments);
175
+ }
176
+ }
177
+ return {
178
+ command: "app.deploy",
179
+ result: { deployments },
180
+ warnings,
181
+ nextSteps: ["prisma-cli app list-deploys <app>"]
182
+ };
183
+ }
184
+ function assertNoPerAppInputsForDeployAll(context, config, appName, options) {
185
+ const used = perAppInputsForDeployAll({
186
+ appName,
187
+ framework: options?.framework,
188
+ entrypoint: options?.entrypoint,
189
+ httpPort: options?.httpPort,
190
+ envAssignments: options?.envAssignments,
191
+ appIdEnvVar: {
192
+ name: PRISMA_APP_ID_ENV_VAR,
193
+ value: readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR)
194
+ }
195
+ });
196
+ if (used.length === 0) return;
197
+ const targets = config.targets.map((target) => target.key).join(", ");
198
+ throw usageError(`Deploying all apps does not accept ${used.join(", ")}`, `Without a target, app deploy deploys every configured app (${targets}), so per-app inputs are ambiguous.`, "Pass the app target to apply per-app inputs to one app, or remove them to deploy all apps.", config.targets.map((target) => `prisma-cli app deploy ${target.key}`), "app");
199
+ }
200
+ function maybeRenderDeployAllTargetHeader(context, planned) {
201
+ if (context.flags.json || context.flags.quiet) return;
202
+ context.output.stderr.write(`${planned.index > 0 ? "\n" : ""}── ${planned.targetKey} (${planned.index + 1}/${planned.total}) ──\n\n`);
203
+ }
204
+ function deployAllFailedError(error, config, failedIndex, deployments) {
205
+ if (!(error instanceof CliError)) return error;
206
+ const failure = describeDeployAllFailure({
207
+ targetKeys: config.targets.map((target) => target.key),
208
+ failedIndex,
209
+ completed: deployments.map(({ target, result }) => ({
210
+ target,
211
+ deploymentId: result.deployment.id,
212
+ url: result.deployment.url
213
+ }))
214
+ });
215
+ const contextSentence = failure.contextLines.join(" ");
216
+ return new CliError({
217
+ code: error.code,
218
+ domain: error.domain,
219
+ summary: error.summary,
220
+ why: error.humanLines ? error.why : [error.why, contextSentence].filter(Boolean).join(" "),
221
+ fix: error.fix,
222
+ debug: error.debug,
223
+ where: error.where,
224
+ meta: {
225
+ ...error.meta,
226
+ deployAll: {
227
+ failedTarget: failure.failedTarget,
228
+ completed: failure.completed,
229
+ notAttempted: failure.notAttempted
230
+ }
231
+ },
232
+ docsUrl: error.docsUrl,
233
+ exitCode: error.exitCode,
234
+ nextSteps: error.nextSteps,
235
+ nextActions: error.nextActions,
236
+ humanLines: error.humanLines ? [
237
+ ...error.humanLines,
238
+ "",
239
+ ...failure.contextLines
240
+ ] : void 0
241
+ });
242
+ }
243
+ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
109
244
  const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
110
245
  const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
111
246
  assertExclusiveDeployProjectInputs({
@@ -113,14 +248,27 @@ async function runAppDeploy(context, appName, options) {
113
248
  createProjectName: options?.createProjectName,
114
249
  envProjectId
115
250
  });
116
- const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
251
+ const computeConfig = await resolveComputeTargetOrThrow(context, options?.configTarget, "deploy", { preloaded: preloadedConfig });
252
+ const merged = mergeComputeDeployInputs({
253
+ cli: {
254
+ framework: options?.framework,
255
+ entrypoint: options?.entrypoint,
256
+ httpPort: options?.httpPort,
257
+ envInputs: options?.envAssignments
258
+ },
259
+ target: computeConfig.target,
260
+ configFilename: computeConfig.config?.relativeConfigPath ?? COMPUTE_CONFIG_FILENAME$1
261
+ });
262
+ const appDir = await resolveComputeAppDir(context, computeConfig);
263
+ const projectDir = computeConfig.config?.configDir ?? context.runtime.cwd;
264
+ const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(projectDir, context.runtime.signal);
117
265
  if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
118
266
  const localPin = localPinReadResult.value;
119
267
  const branch = await resolveDeployBranch(context, options?.branchName);
120
- if (options?.httpPort) parseDeployHttpPort(options.httpPort);
268
+ if (merged.httpPort) parseDeployHttpPort(merged.httpPort.value);
121
269
  assertSupportedEntrypointForRequestedDeployShape({
122
- requestedFramework: options?.framework,
123
- entrypoint: options?.entrypoint
270
+ requestedFramework: merged.framework?.value,
271
+ entrypoint: merged.entrypoint?.value
124
272
  });
125
273
  const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
126
274
  branch,
@@ -130,27 +278,32 @@ async function runAppDeploy(context, appName, options) {
130
278
  });
131
279
  let localPinResult;
132
280
  if (target.localPinAction) {
133
- const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction);
281
+ const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction, projectDir);
134
282
  if (setupResult.isErr()) throw projectDirectoryBindingErrorToCliError(setupResult.error);
135
283
  const projectSetup = setupResult.value;
136
284
  localPinResult = projectSetup.localPin;
137
285
  maybeRenderProjectLinked(context, projectSetup.directory, projectSetup.project.name, projectSetup.localPin.path);
138
286
  }
139
287
  let framework = await resolveDeployFramework(context, {
140
- requestedFramework: options?.framework,
141
- entrypoint: options?.entrypoint
142
- });
143
- let runtime = resolveDeployRuntime(options?.httpPort, framework);
144
- assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
145
- const envVars = toOptionalEnvVars(await parseEnvInputs(context.runtime.cwd, options?.envAssignments, { commandName: "deploy" }));
288
+ requestedFramework: merged.framework?.value,
289
+ requestedFrameworkAnnotation: merged.framework?.annotation,
290
+ entrypoint: merged.entrypoint?.value,
291
+ entrypointAnnotation: merged.entrypoint?.annotation,
292
+ appDir
293
+ });
294
+ let runtime = resolveDeployRuntime(merged.httpPort?.value, merged.httpPort?.annotation, framework);
295
+ assertSupportedEntrypoint(framework.buildType, merged.entrypoint?.value, "deploy");
296
+ const envVars = toOptionalEnvVars(await parseEnvInputs(merged.envInputsFromConfig ? projectDir : context.runtime.cwd, merged.envInputs, { commandName: "deploy" }));
146
297
  const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
147
298
  explicitAppName: appName,
148
299
  explicitAppId: envAppId,
300
+ configAppName: merged.configAppName,
149
301
  firstDeploy: Boolean(target.localPinAction),
150
- inferName: () => inferTargetName(context.runtime.cwd, context.runtime.signal)
302
+ inferName: () => inferTargetName(appDir, context.runtime.signal)
151
303
  });
152
304
  await maybeRenderDeploySetupBlock(context, {
153
305
  includeDirectory: !target.localPinAction,
306
+ appDir,
154
307
  projectName: target.project.name,
155
308
  branchName: target.branch.name,
156
309
  appName: selectedApp.displayName
@@ -159,9 +312,9 @@ async function runAppDeploy(context, appName, options) {
159
312
  framework,
160
313
  runtime,
161
314
  firstDeploy: selectedApp.firstDeploy,
162
- explicitFramework: Boolean(options?.framework),
163
- explicitEntrypoint: Boolean(options?.entrypoint),
164
- explicitHttpPort: Boolean(options?.httpPort)
315
+ explicitFramework: Boolean(merged.framework),
316
+ explicitEntrypoint: Boolean(merged.entrypoint),
317
+ explicitHttpPort: Boolean(merged.httpPort)
165
318
  });
166
319
  framework = customized.framework;
167
320
  runtime = customized.runtime;
@@ -172,24 +325,33 @@ async function runAppDeploy(context, appName, options) {
172
325
  prod: options?.prod === true
173
326
  });
174
327
  const buildType = framework.buildType;
175
- assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
176
- const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint, context.runtime.signal);
177
- const buildSettingsResolution = await resolveOrCreatePreviewBuildSettings({
178
- appPath: context.runtime.cwd,
328
+ assertSupportedEntrypoint(buildType, merged.entrypoint?.value, "deploy");
329
+ const entrypoint = await resolveDeployEntrypoint(appDir, framework, merged.entrypoint?.value, context.runtime.signal);
330
+ if (computeConfig.target?.build) assertConfigBackedBuildSettings(buildType);
331
+ const buildSettingsResolution = computeConfig.config && computeConfig.target?.build && isConfigBackedBuildType(buildType) ? await resolveConfiguredPreviewBuildSettings({
332
+ appPath: appDir,
333
+ buildType,
334
+ configured: computeConfig.target.build,
335
+ configPath: computeConfig.config.configPath,
336
+ signal: context.runtime.signal
337
+ }) : await resolveInferredPreviewBuildSettings({
338
+ appPath: appDir,
179
339
  buildType,
180
340
  signal: context.runtime.signal
181
341
  });
342
+ const legacyWarnings = await handleLegacyBuildSettings(context, appDir, buildSettingsResolution.settings);
182
343
  maybeRenderDeployBuildSettings(context, buildSettingsResolution);
183
344
  const portMapping = parseDeployPortMapping(String(runtime.port));
184
345
  const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
185
346
  db: options?.db,
186
347
  providedEnvVars: envVars,
187
- firstProductionDeploy: productionDeployGate.firstProductionDeploy
348
+ firstProductionDeploy: productionDeployGate.firstProductionDeploy,
349
+ projectDir
188
350
  });
189
351
  const progressState = createPreviewDeployProgressState();
190
352
  const deployStartedAt = Date.now();
191
353
  const deployResult = await provider.deployApp({
192
- cwd: context.runtime.cwd,
354
+ cwd: appDir,
193
355
  projectId,
194
356
  branchName: target.branch.name,
195
357
  appId: selectedApp.appId,
@@ -252,14 +414,18 @@ async function runAppDeploy(context, appName, options) {
252
414
  durationMs: deployDurationMs,
253
415
  localPin: localPinResult
254
416
  },
255
- warnings: branchDatabaseSetup.warnings,
417
+ warnings: [...legacyWarnings, ...branchDatabaseSetup.warnings],
256
418
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
257
419
  };
258
420
  }
259
- async function runAppListDeploys(context, appName, projectRef) {
421
+ async function runAppListDeploys(context, appName, projectRef, configTarget) {
260
422
  ensurePreviewAppMode(context);
261
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app list-deploys" });
262
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
423
+ const compute = await resolveComputeManagementContext(context, configTarget, "list-deploys");
424
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
425
+ commandName: "app list-deploys",
426
+ projectDir: compute.projectDir
427
+ });
428
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName ?? compute.configAppName);
263
429
  if (!selectedApp) return {
264
430
  command: "app.list-deploys",
265
431
  result: {
@@ -295,10 +461,14 @@ async function runAppListDeploys(context, appName, projectRef) {
295
461
  nextSteps: deployments.length > 0 ? [`prisma-cli app show-deploy ${deployments[0]?.id}`] : ["prisma-cli app deploy"]
296
462
  };
297
463
  }
298
- async function runAppShow(context, appName, projectRef) {
464
+ async function runAppShow(context, appName, projectRef, configTarget) {
299
465
  ensurePreviewAppMode(context);
300
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app show" });
301
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
466
+ const compute = await resolveComputeManagementContext(context, configTarget, "show");
467
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
468
+ commandName: "app show",
469
+ projectDir: compute.projectDir
470
+ });
471
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName ?? compute.configAppName);
302
472
  if (!selectedApp) return {
303
473
  command: "app.show",
304
474
  result: {
@@ -373,9 +543,14 @@ async function runAppShowDeploy(context, deploymentId) {
373
543
  nextSteps: []
374
544
  };
375
545
  }
376
- async function runAppOpen(context, appName, projectRef) {
546
+ async function runAppOpen(context, appName, projectRef, configTarget) {
377
547
  ensurePreviewAppMode(context);
378
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
548
+ const compute = await resolveComputeManagementContext(context, configTarget, "open");
549
+ appName = appName ?? compute.configAppName;
550
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
551
+ commandName: "app open",
552
+ projectDir: compute.projectDir
553
+ });
379
554
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
380
555
  if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
381
556
  const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
@@ -539,15 +714,19 @@ async function runAppDomainWait(context, hostname, options) {
539
714
  });
540
715
  }
541
716
  }
542
- async function runAppLogs(context, appName, deploymentId, projectRef) {
717
+ async function runAppLogs(context, appName, deploymentId, projectRef, configTarget) {
543
718
  ensurePreviewAppMode(context);
544
- const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app logs" });
719
+ const compute = await resolveComputeManagementContext(context, configTarget, "logs");
720
+ appName = appName ?? compute.configAppName;
721
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, {
722
+ commandName: "app logs",
723
+ projectDir: compute.projectDir
724
+ });
545
725
  const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
546
726
  if (!context.flags.json && !context.flags.quiet) {
547
727
  const lines = renderCommandHeader(context.ui, {
548
728
  commandLabel: "app logs",
549
729
  description: "Streaming logs for the selected deployment.",
550
- docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
551
730
  rows: [
552
731
  {
553
732
  key: "project",
@@ -664,9 +843,14 @@ function writeLogRecord(context, record) {
664
843
  if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
665
844
  }
666
845
  }
667
- async function runAppPromote(context, deploymentId, appName, projectRef) {
846
+ async function runAppPromote(context, deploymentId, appName, projectRef, configTarget) {
668
847
  ensurePreviewAppMode(context);
669
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
848
+ const compute = await resolveComputeManagementContext(context, configTarget, "promote");
849
+ appName = appName ?? compute.configAppName;
850
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
851
+ commandName: "app promote",
852
+ projectDir: compute.projectDir
853
+ });
670
854
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
671
855
  const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
672
856
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -706,9 +890,14 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
706
890
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
707
891
  };
708
892
  }
709
- async function runAppRollback(context, appName, deploymentId, projectRef) {
893
+ async function runAppRollback(context, appName, deploymentId, projectRef, configTarget) {
710
894
  ensurePreviewAppMode(context);
711
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
895
+ const compute = await resolveComputeManagementContext(context, configTarget, "rollback");
896
+ appName = appName ?? compute.configAppName;
897
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
898
+ commandName: "app rollback",
899
+ projectDir: compute.projectDir
900
+ });
712
901
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
713
902
  const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
714
903
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -750,9 +939,14 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
750
939
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
751
940
  };
752
941
  }
753
- async function runAppRemove(context, appName, projectRef) {
942
+ async function runAppRemove(context, appName, projectRef, configTarget) {
754
943
  ensurePreviewAppMode(context);
755
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
944
+ const compute = await resolveComputeManagementContext(context, configTarget, "remove");
945
+ appName = appName ?? compute.configAppName;
946
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, {
947
+ commandName: "app remove",
948
+ projectDir: compute.projectDir
949
+ });
756
950
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
757
951
  await confirmAppRemoval(context, selectedApp);
758
952
  const removedApp = await provider.removeApp(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
@@ -776,6 +970,7 @@ async function runAppRemove(context, appName, projectRef) {
776
970
  }
777
971
  async function resolveAppDomainTarget(context, options, commandName = "app domain") {
778
972
  ensurePreviewAppMode(context);
973
+ const compute = await resolveComputeManagementContext(context, options?.configTarget, commandName.replace(/^app /, ""));
779
974
  const branch = resolveDomainBranch(options?.branchName);
780
975
  if (toBranchKind(branch.name) !== "production") throw new CliError({
781
976
  code: "BRANCH_NOT_DEPLOYABLE",
@@ -791,10 +986,11 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
791
986
  const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
792
987
  branch,
793
988
  commandName,
794
- envProjectId
989
+ envProjectId,
990
+ projectDir: compute.projectDir
795
991
  });
796
992
  const selectedApp = await resolveDomainAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
797
- explicitAppName: options?.appName,
993
+ explicitAppName: options?.appName ?? compute.configAppName,
798
994
  explicitAppId: envAppId
799
995
  });
800
996
  await context.stateStore.setSelectedApp(projectId, {
@@ -910,72 +1106,51 @@ async function confirmDomainRemoval(context, target, hostname) {
910
1106
  })) 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");
911
1107
  }
912
1108
  function domainCommandError(command, error, hostname) {
913
- if (error instanceof PreviewDomainApiError) return domainApiCommandError(command, error, hostname);
914
- return new CliError({
915
- code: "DEPLOY_FAILED",
916
- domain: "app",
917
- summary: `Custom domain ${command} failed`,
918
- why: error instanceof Error ? error.message : String(error),
919
- fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
920
- debug: formatDebugDetails(error),
921
- exitCode: 1,
922
- nextSteps: [`prisma-cli app domain show ${hostname}`]
923
- });
924
- }
925
- function domainApiCommandError(command, error, hostname) {
926
- if (command === "add") return domainAddCommandError(error, hostname);
927
- if (error.status === 404) return domainNotFoundError(hostname);
928
- if (command === "retry" && error.status === 409) return domainRetryNotEligibleError(error, hostname);
929
- return domainGenericCommandError(command, error, hostname);
930
- }
931
- function domainAddCommandError(error, hostname) {
932
- if ((error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
933
- if (error.status === 400) return new CliError({
934
- code: "DOMAIN_HOSTNAME_INVALID",
935
- domain: "app",
936
- summary: `Invalid custom domain "${hostname}"`,
937
- why: error.message,
938
- fix: "Pass a valid hostname like shop.acme.com and make sure DNS can be verified.",
939
- debug: formatDebugDetails(error),
940
- exitCode: 2,
941
- nextSteps: ["prisma-cli app domain add shop.acme.com"]
942
- });
943
- if (error.status === 429 || isDomainQuotaError(error)) return new CliError({
944
- code: "DOMAIN_QUOTA_EXCEEDED",
945
- domain: "app",
946
- summary: "Custom domain quota exceeded",
947
- why: error.message,
948
- fix: "Remove an existing custom domain before adding another one.",
949
- debug: formatDebugDetails(error),
950
- exitCode: 1,
951
- nextSteps: ["prisma-cli app domain remove <hostname>"]
952
- });
953
- if (error.status === 409) return domainAlreadyRegisteredError(hostname, error);
954
- if (error.status === 422) return new CliError({
955
- code: "NO_DEPLOYMENTS",
956
- domain: "app",
957
- summary: "Custom domain requires a live production deployment",
958
- why: "The selected production app does not have a promoted version that can receive a custom domain.",
959
- fix: "Deploy the app to the production branch, then rerun the domain command.",
960
- debug: formatDebugDetails(error),
961
- exitCode: 1,
962
- nextSteps: ["prisma-cli app deploy --branch production", `prisma-cli app domain add ${hostname}`]
963
- });
964
- return domainGenericCommandError("add", error, hostname);
965
- }
966
- function domainRetryNotEligibleError(error, hostname) {
967
- return new CliError({
968
- code: "DOMAIN_RETRY_NOT_ELIGIBLE",
969
- domain: "app",
970
- summary: `Custom domain "${hostname}" is not eligible for retry`,
971
- why: error.message,
972
- fix: "Wait for the current verification or TLS step to finish, then rerun retry if the domain fails.",
973
- debug: formatDebugDetails(error),
974
- exitCode: 1,
975
- nextSteps: [`prisma-cli app domain show ${hostname}`]
976
- });
977
- }
978
- function domainGenericCommandError(command, error, hostname) {
1109
+ if (error instanceof PreviewDomainApiError) {
1110
+ if (command === "add" && (error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
1111
+ if (command === "add" && error.status === 400) return new CliError({
1112
+ code: "DOMAIN_HOSTNAME_INVALID",
1113
+ domain: "app",
1114
+ summary: `Invalid custom domain "${hostname}"`,
1115
+ why: error.message,
1116
+ fix: "Pass a valid hostname like shop.acme.com and make sure DNS can be verified.",
1117
+ debug: formatDebugDetails(error),
1118
+ exitCode: 2,
1119
+ nextSteps: ["prisma-cli app domain add shop.acme.com"]
1120
+ });
1121
+ if (command === "add" && (error.status === 429 || isDomainQuotaError(error))) return new CliError({
1122
+ code: "DOMAIN_QUOTA_EXCEEDED",
1123
+ domain: "app",
1124
+ summary: "Custom domain quota exceeded",
1125
+ why: error.message,
1126
+ fix: "Remove an existing custom domain before adding another one.",
1127
+ debug: formatDebugDetails(error),
1128
+ exitCode: 1,
1129
+ nextSteps: ["prisma-cli app domain remove <hostname>"]
1130
+ });
1131
+ if (command === "add" && error.status === 409) return domainAlreadyRegisteredError(hostname, error);
1132
+ if (command === "add" && error.status === 422) return new CliError({
1133
+ code: "NO_DEPLOYMENTS",
1134
+ domain: "app",
1135
+ summary: "Custom domain requires a live production deployment",
1136
+ why: "The selected production app does not have a promoted version that can receive a custom domain.",
1137
+ fix: "Deploy the app to the production branch, then rerun the domain command.",
1138
+ debug: formatDebugDetails(error),
1139
+ exitCode: 1,
1140
+ nextSteps: ["prisma-cli app deploy --branch production", `prisma-cli app domain add ${hostname}`]
1141
+ });
1142
+ if ((command === "show" || command === "remove" || command === "retry" || command === "wait") && error.status === 404) return domainNotFoundError(hostname);
1143
+ if (command === "retry" && error.status === 409) return new CliError({
1144
+ code: "DOMAIN_RETRY_NOT_ELIGIBLE",
1145
+ domain: "app",
1146
+ summary: `Custom domain "${hostname}" is not eligible for retry`,
1147
+ why: error.message,
1148
+ fix: "Wait for the current verification or TLS step to finish, then rerun retry if the domain fails.",
1149
+ debug: formatDebugDetails(error),
1150
+ exitCode: 1,
1151
+ nextSteps: [`prisma-cli app domain show ${hostname}`]
1152
+ });
1153
+ }
979
1154
  return new CliError({
980
1155
  code: "DEPLOY_FAILED",
981
1156
  domain: "app",
@@ -1128,6 +1303,25 @@ async function resolveDeployAppSelection(context, projectId, apps, options) {
1128
1303
  firstDeploy: options.firstDeploy
1129
1304
  };
1130
1305
  }
1306
+ if (options.configAppName) {
1307
+ const configName = options.configAppName;
1308
+ const matches = findAppsByName(apps, configName.value);
1309
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, configName.value, options.firstDeploy);
1310
+ const matched = matches[0];
1311
+ if (matched) return {
1312
+ appId: matched.id,
1313
+ displayName: matched.name,
1314
+ annotation: configName.annotation,
1315
+ firstDeploy: options.firstDeploy
1316
+ };
1317
+ return {
1318
+ appName: configName.value,
1319
+ region: PREVIEW_DEFAULT_REGION,
1320
+ displayName: configName.value,
1321
+ annotation: configName.annotation,
1322
+ firstDeploy: options.firstDeploy
1323
+ };
1324
+ }
1131
1325
  const inferredName = await options.inferName();
1132
1326
  const matches = findAppsByName(apps, inferredName.name);
1133
1327
  if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
@@ -1377,14 +1571,15 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
1377
1571
  };
1378
1572
  }
1379
1573
  async function resolveProjectContext(context, client, explicitProject, options) {
1380
- const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1381
- if (!workspace) throw workspaceRequiredError();
1574
+ const authState = await requireAuthenticatedAuthState(context);
1575
+ if (!authState.workspace) throw workspaceRequiredError();
1382
1576
  const resolvedResult = await resolveProjectTarget({
1383
1577
  context,
1384
- workspace,
1578
+ workspace: authState.workspace,
1385
1579
  explicitProject,
1386
1580
  envProjectId: options?.envProjectId,
1387
- listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
1581
+ projectDir: options?.projectDir,
1582
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
1388
1583
  commandName: options?.commandName
1389
1584
  });
1390
1585
  if (resolvedResult.isErr()) throw projectResolutionErrorToCliError(resolvedResult.error);
@@ -1403,16 +1598,8 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1403
1598
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1404
1599
  if (!workspace) throw workspaceRequiredError();
1405
1600
  const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1406
- return withRemoteDeployBranch(provider, await resolveDeployProjectSetup(context, provider, workspace, await listRealWorkspaceProjects(client, workspace, context.runtime.signal), explicitProject, options), branch, context.runtime.signal);
1407
- }
1408
- async function resolveDeployProjectSetup(context, provider, workspace, projects, explicitProject, options) {
1409
- const selected = await resolveNonInteractiveDeployProjectSetup(context, provider, workspace, projects, explicitProject, options);
1410
- if (selected) return selected;
1411
- if (canPrompt(context) && !context.flags.yes) return resolveInteractiveDeployProjectSetup(context, provider, workspace, projects);
1412
- throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1413
- }
1414
- async function resolveNonInteractiveDeployProjectSetup(context, provider, workspace, projects, explicitProject, options) {
1415
- if (explicitProject) return {
1601
+ const projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
1602
+ if (explicitProject) return withRemoteDeployBranch(provider, {
1416
1603
  workspace,
1417
1604
  project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
1418
1605
  resolution: {
@@ -1421,11 +1608,11 @@ async function resolveNonInteractiveDeployProjectSetup(context, provider, worksp
1421
1608
  targetNameSource: "explicit"
1422
1609
  },
1423
1610
  localPinAction: "linked"
1424
- };
1611
+ }, branch, context.runtime.signal);
1425
1612
  if (options.createProjectName) {
1426
1613
  const projectName = options.createProjectName.trim();
1427
1614
  if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1428
- return {
1615
+ return withRemoteDeployBranch(provider, {
1429
1616
  workspace,
1430
1617
  project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
1431
1618
  resolution: {
@@ -1434,12 +1621,12 @@ async function resolveNonInteractiveDeployProjectSetup(context, provider, worksp
1434
1621
  targetNameSource: "explicit"
1435
1622
  },
1436
1623
  localPinAction: "created"
1437
- };
1624
+ }, branch, context.runtime.signal);
1438
1625
  }
1439
1626
  if (options.envProjectId) {
1440
1627
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
1441
1628
  if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1442
- return {
1629
+ return withRemoteDeployBranch(provider, {
1443
1630
  workspace,
1444
1631
  project: toProjectSummary(project),
1445
1632
  resolution: {
@@ -1447,14 +1634,14 @@ async function resolveNonInteractiveDeployProjectSetup(context, provider, worksp
1447
1634
  targetName: options.envProjectId,
1448
1635
  targetNameSource: "env"
1449
1636
  }
1450
- };
1637
+ }, branch, context.runtime.signal);
1451
1638
  }
1452
1639
  const localPin = options.localPin;
1453
1640
  if (localPin.kind === "present") {
1454
1641
  if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1455
1642
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1456
1643
  if (!project) throw localResolutionPinStaleError();
1457
- return {
1644
+ return withRemoteDeployBranch(provider, {
1458
1645
  workspace,
1459
1646
  project: toProjectSummary(project),
1460
1647
  resolution: {
@@ -1462,10 +1649,10 @@ async function resolveNonInteractiveDeployProjectSetup(context, provider, worksp
1462
1649
  targetName: project.name,
1463
1650
  targetNameSource: "local-pin"
1464
1651
  }
1465
- };
1652
+ }, branch, context.runtime.signal);
1466
1653
  }
1467
1654
  const platformMapping = await resolveDurablePlatformMapping();
1468
- if (platformMapping && platformMapping.workspace.id === workspace.id) return {
1655
+ if (platformMapping && platformMapping.workspace.id === workspace.id) return withRemoteDeployBranch(provider, {
1469
1656
  workspace,
1470
1657
  project: toProjectSummary(platformMapping),
1471
1658
  resolution: {
@@ -1473,8 +1660,9 @@ async function resolveNonInteractiveDeployProjectSetup(context, provider, worksp
1473
1660
  targetName: platformMapping.name,
1474
1661
  targetNameSource: "platform-mapping"
1475
1662
  }
1476
- };
1477
- return null;
1663
+ }, branch, context.runtime.signal);
1664
+ if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch, context.runtime.signal);
1665
+ throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1478
1666
  }
1479
1667
  async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
1480
1668
  const setup = await promptForProjectSetupChoice({
@@ -1587,22 +1775,130 @@ async function resolveDeployBranch(context, explicitBranchName) {
1587
1775
  annotation: "default"
1588
1776
  };
1589
1777
  }
1778
+ async function resolveComputeTargetOrThrow(context, configTarget, commandName, options) {
1779
+ let config;
1780
+ if (options?.preloaded !== void 0) config = options.preloaded;
1781
+ else {
1782
+ const loaded = await loadComputeConfig$1(context.runtime.cwd, context.runtime.signal);
1783
+ if (loaded.isErr()) throw computeConfigErrorToCliError(loaded.error, commandName);
1784
+ config = loaded.value;
1785
+ }
1786
+ if (!config) {
1787
+ if (configTarget) throw usageError(`App target "${configTarget}" requires a compute config file`, `No ${COMPUTE_CONFIG_FILENAME$1} exists in the current directory, so there are no named app targets.`, `Create ${COMPUTE_CONFIG_FILENAME$1} with an apps entry named "${configTarget}", or rerun without the target argument.`, [`prisma-cli app ${commandName}`], "app");
1788
+ return {
1789
+ config: null,
1790
+ target: null
1791
+ };
1792
+ }
1793
+ const requestedTarget = configTarget ?? inferComputeTargetFromCwd(config, context.runtime.cwd);
1794
+ const selected = selectComputeDeployTarget(config, requestedTarget);
1795
+ if (selected.isErr()) {
1796
+ if (options?.targetOptional && selected.error instanceof ComputeConfigTargetRequiredError) return {
1797
+ config,
1798
+ target: null
1799
+ };
1800
+ throw computeConfigErrorToCliError(selected.error, commandName);
1801
+ }
1802
+ return {
1803
+ config,
1804
+ target: selected.value
1805
+ };
1806
+ }
1807
+ /**
1808
+ * Compute-config context for app management commands: the project directory
1809
+ * (where `.prisma/local.json` lives) and the config-selected app name, which
1810
+ * ranks below `--app` but above the remembered app selection.
1811
+ */
1812
+ async function resolveComputeManagementContext(context, configTarget, commandName) {
1813
+ const compute = await resolveComputeTargetOrThrow(context, configTarget, commandName, { targetOptional: true });
1814
+ return {
1815
+ projectDir: compute.config?.configDir ?? context.runtime.cwd,
1816
+ configAppName: compute.target?.name ?? compute.target?.key ?? void 0
1817
+ };
1818
+ }
1819
+ async function resolveComputeAppDir(context, compute) {
1820
+ if (!compute.config || !compute.target) return context.runtime.cwd;
1821
+ const appDir = computeTargetAppDir(compute.config, compute.target);
1822
+ if (!compute.target.root) return appDir;
1823
+ context.runtime.signal.throwIfAborted();
1824
+ try {
1825
+ await access(appDir);
1826
+ context.runtime.signal.throwIfAborted();
1827
+ } catch (error) {
1828
+ if (context.runtime.signal.aborted) throw error;
1829
+ throw new CliError({
1830
+ code: "COMPUTE_CONFIG_INVALID",
1831
+ domain: "app",
1832
+ summary: `App root "${compute.target.root}" does not exist`,
1833
+ why: `${compute.config.relativeConfigPath} points the selected app at "${compute.target.root}", but that directory does not exist.`,
1834
+ fix: `Fix the root path in ${compute.config.relativeConfigPath} or create the directory.`,
1835
+ where: appDir,
1836
+ meta: {
1837
+ appRoot: compute.target.root,
1838
+ appDir
1839
+ },
1840
+ exitCode: 2,
1841
+ nextSteps: ["prisma-cli app deploy"]
1842
+ });
1843
+ }
1844
+ return appDir;
1845
+ }
1846
+ /**
1847
+ * `prisma.app.json` is no longer read or written. A leftover file that
1848
+ * matches the effective settings only warns; one with custom values fails
1849
+ * with migration guidance so builds never silently change.
1850
+ */
1851
+ async function handleLegacyBuildSettings(context, appDir, effective) {
1852
+ const legacy = await detectLegacyBuildSettings({
1853
+ appPath: appDir,
1854
+ effective,
1855
+ signal: context.runtime.signal
1856
+ });
1857
+ switch (legacy.kind) {
1858
+ case "absent": return [];
1859
+ case "matching": return [`${PRISMA_APP_CONFIG_FILENAME} is no longer used and matches the resolved build settings. Delete it.`];
1860
+ case "invalid": return [`${PRISMA_APP_CONFIG_FILENAME} is no longer used and could not be parsed. Delete it.`];
1861
+ case "custom": {
1862
+ const buildBlock = [
1863
+ "build: {",
1864
+ ` command: ${legacy.buildCommand === null ? "null" : JSON.stringify(legacy.buildCommand)},`,
1865
+ ` outputDirectory: ${JSON.stringify(legacy.outputDirectory)},`,
1866
+ "}"
1867
+ ].join(" ");
1868
+ throw new CliError({
1869
+ code: "BUILD_SETTINGS_MIGRATION_REQUIRED",
1870
+ domain: "app",
1871
+ summary: `${PRISMA_APP_CONFIG_FILENAME} is no longer supported`,
1872
+ why: `${PRISMA_APP_CONFIG_FILENAME} contains custom build settings that differ from the resolved defaults, and the file is no longer read.`,
1873
+ fix: `Move the settings into prisma.compute.ts as \`${buildBlock}\` on this app, then delete ${PRISMA_APP_CONFIG_FILENAME}.`,
1874
+ where: legacy.configPath,
1875
+ meta: {
1876
+ configPath: legacy.configPath,
1877
+ buildCommand: legacy.buildCommand,
1878
+ outputDirectory: legacy.outputDirectory
1879
+ },
1880
+ exitCode: 2,
1881
+ nextSteps: ["prisma-cli app deploy"]
1882
+ });
1883
+ }
1884
+ }
1885
+ }
1590
1886
  async function resolveDeployFramework(context, options) {
1591
- if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1887
+ if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, options.requestedFrameworkAnnotation ?? "set by --framework");
1592
1888
  if (options.entrypoint) return {
1593
1889
  key: "bun",
1594
1890
  buildType: "bun",
1595
1891
  displayName: "Bun",
1596
- annotation: "set by --entry"
1892
+ annotation: options.entrypointAnnotation ?? "set by --entry"
1597
1893
  };
1598
- const detected = await detectDeployFramework(context.runtime.cwd, context.runtime.signal);
1894
+ const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
1599
1895
  if (detected) return detected;
1600
- throw frameworkNotDetectedError(context.runtime.cwd);
1896
+ throw frameworkNotDetectedError(options.appDir);
1601
1897
  }
1602
- function resolveDeployRuntime(requestedHttpPort, framework) {
1898
+ function resolveDeployRuntime(requestedHttpPort, requestedHttpPortAnnotation, framework) {
1603
1899
  if (requestedHttpPort) return {
1604
1900
  port: parseDeployHttpPort(requestedHttpPort),
1605
- annotation: "set by --http-port"
1901
+ annotation: requestedHttpPortAnnotation ?? "set by --http-port"
1606
1902
  };
1607
1903
  return {
1608
1904
  port: FRAMEWORK_DEFAULT_HTTP_PORT,
@@ -1617,8 +1913,8 @@ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signa
1617
1913
  if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
1618
1914
  const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd, signal));
1619
1915
  if (packageEntrypoint) return packageEntrypoint;
1620
- if (framework.key !== "hono") return;
1621
- const defaultEntrypoint = "src/index.ts";
1916
+ const defaultEntrypoint = frameworkFromAlias(framework.key)?.defaultEntrypoint;
1917
+ if (!defaultEntrypoint) return;
1622
1918
  signal.throwIfAborted();
1623
1919
  try {
1624
1920
  await access(path.join(cwd, defaultEntrypoint));
@@ -1632,34 +1928,22 @@ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signa
1632
1928
  }
1633
1929
  async function detectDeployFramework(cwd, signal) {
1634
1930
  const packageJson = await readBunPackageJson(cwd, signal);
1635
- const nextConfig = await detectNextConfig(cwd, signal);
1636
- if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
1637
- key: "nextjs",
1638
- buildType: "nextjs",
1639
- displayName: "Next.js",
1640
- annotation: nextConfig.standalone ? "standalone output detected" : nextConfig.exists ? "detected from next.config" : "detected from package.json"
1641
- };
1642
- if (hasPackageDependency(packageJson, "hono")) return {
1643
- key: "hono",
1644
- buildType: "bun",
1645
- displayName: "Hono",
1646
- annotation: "detected from package.json"
1647
- };
1648
- if (hasAnyPackageDependency(packageJson, TANSTACK_START_PACKAGES)) return {
1649
- key: "tanstack-start",
1650
- buildType: "tanstack-start",
1651
- displayName: "TanStack Start",
1652
- annotation: "detected from package.json"
1653
- };
1931
+ for (const framework of FRAMEWORKS) {
1932
+ if (framework.detectConfigFiles.length === 0 && framework.detectPackages.length === 0) continue;
1933
+ const configFile = await detectFrameworkConfigFile(cwd, framework, signal);
1934
+ if (!configFile.exists && !hasAnyPackageDependency(packageJson, framework.detectPackages)) continue;
1935
+ const annotation = framework.key === "nextjs" && configFile.standalone ? "standalone output detected" : configFile.exists ? `detected from ${path.basename(configFile.path)}` : "detected from package.json";
1936
+ return {
1937
+ key: framework.key,
1938
+ buildType: framework.buildType,
1939
+ displayName: framework.displayName,
1940
+ annotation
1941
+ };
1942
+ }
1654
1943
  return null;
1655
1944
  }
1656
- async function detectNextConfig(cwd, signal) {
1657
- for (const candidate of [
1658
- "next.config.js",
1659
- "next.config.mjs",
1660
- "next.config.ts",
1661
- "next.config.mts"
1662
- ]) {
1945
+ async function detectFrameworkConfigFile(cwd, framework, signal) {
1946
+ for (const candidate of framework.detectConfigFiles) {
1663
1947
  const filePath = path.join(cwd, candidate);
1664
1948
  signal.throwIfAborted();
1665
1949
  try {
@@ -1669,7 +1953,8 @@ async function detectNextConfig(cwd, signal) {
1669
1953
  });
1670
1954
  return {
1671
1955
  exists: true,
1672
- standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
1956
+ standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),
1957
+ path: filePath
1673
1958
  };
1674
1959
  } catch (error) {
1675
1960
  if (signal.aborted) throw error;
@@ -1678,7 +1963,8 @@ async function detectNextConfig(cwd, signal) {
1678
1963
  }
1679
1964
  return {
1680
1965
  exists: false,
1681
- standalone: false
1966
+ standalone: false,
1967
+ path: null
1682
1968
  };
1683
1969
  }
1684
1970
  function hasPackageDependency(packageJson, dependencyName) {
@@ -1691,48 +1977,41 @@ function hasDependency(dependencies, dependencyName) {
1691
1977
  return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1692
1978
  }
1693
1979
  function frameworkFromUserFacingValue(value, annotation) {
1694
- switch (value.trim().toLowerCase()) {
1695
- case "next":
1696
- case "next.js":
1697
- case "nextjs": return {
1698
- key: "nextjs",
1699
- buildType: "nextjs",
1700
- displayName: "Next.js",
1701
- annotation
1702
- };
1703
- case "hono": return {
1704
- key: "hono",
1705
- buildType: "bun",
1706
- displayName: "Hono",
1707
- annotation
1708
- };
1709
- case "bun": return {
1710
- key: "bun",
1711
- buildType: "bun",
1712
- displayName: "Bun",
1713
- annotation
1714
- };
1715
- case "tanstack":
1716
- case "tanstack-start":
1717
- case "@tanstack/react-start":
1718
- case "@tanstack/solid-start": return {
1719
- key: "tanstack-start",
1720
- buildType: "tanstack-start",
1721
- displayName: "TanStack Start",
1722
- annotation
1723
- };
1724
- default: throw frameworkNotDetectedError(void 0, value);
1725
- }
1980
+ const framework = frameworkFromAlias(value);
1981
+ if (!framework) throw frameworkNotDetectedError(void 0, value);
1982
+ return {
1983
+ key: framework.key,
1984
+ buildType: framework.buildType,
1985
+ displayName: framework.displayName,
1986
+ annotation
1987
+ };
1988
+ }
1989
+ /**
1990
+ * The nuxt and astro strategies build with their framework CLI and stage
1991
+ * fixed output, so a compute config `build` block has nothing to apply to.
1992
+ * Erroring beats silently ignoring committed settings.
1993
+ */
1994
+ function assertConfigBackedBuildSettings(buildType) {
1995
+ if (isConfigBackedBuildType(buildType)) return;
1996
+ const displayName = FRAMEWORKS.find((framework) => framework.buildType === buildType)?.displayName ?? buildType;
1997
+ throw new CliError({
1998
+ code: "BUILD_SETTINGS_UNSUPPORTED",
1999
+ domain: "app",
2000
+ summary: `build settings are not supported for ${displayName} apps`,
2001
+ why: `${displayName} deploys run \`${buildType} build\` and package its output automatically.`,
2002
+ fix: "Remove the `build` block from prisma.compute.ts for this app.",
2003
+ exitCode: 2
2004
+ });
1726
2005
  }
1727
2006
  function frameworkNotDetectedError(cwd, requestedFramework) {
1728
- const supported = "Next.js, Hono, TanStack Start, Bun";
2007
+ const supported = FRAMEWORKS.map((framework) => framework.displayName).join(", ");
1729
2008
  const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
1730
2009
  return new CliError({
1731
2010
  code: "FRAMEWORK_NOT_DETECTED",
1732
2011
  domain: "app",
1733
2012
  summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
1734
2013
  why: `Supported Beta frameworks: ${supported}.`,
1735
- fix: "Add one of these frameworks as a dependency, pass --framework <nextjs|hono|tanstack-start|bun>, or pass --entry <path> for a Bun app.",
2014
+ fix: `Add one of these frameworks as a dependency, pass --framework <${FRAMEWORKS.map((framework) => framework.key).join("|")}>, or pass --entry <path> for a Bun app.`,
1736
2015
  exitCode: 2,
1737
2016
  nextSteps: [
1738
2017
  "prisma-cli app deploy --framework nextjs",
@@ -1745,14 +2024,14 @@ function frameworkNotDetectedError(cwd, requestedFramework) {
1745
2024
  }
1746
2025
  async function maybeRenderDeploySetupBlock(context, details) {
1747
2026
  if (context.flags.json || context.flags.quiet) return;
1748
- const directory = formatDeployDirectory(context.runtime.cwd);
2027
+ const directory = formatAppDirectoryLabel(context.runtime.cwd, details.appDir);
1749
2028
  const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
1750
2029
  context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1751
2030
  }
1752
2031
  function maybeRenderDeployBuildSettings(context, resolution) {
1753
2032
  if (context.flags.json || context.flags.quiet) return;
1754
2033
  const settings = resolution.settings;
1755
- const title = resolution.status === "created" ? `Created ${resolution.relativeConfigPath}` : `Using ${resolution.relativeConfigPath}`;
2034
+ const title = resolution.status === "config" ? `Using ${resolution.relativeConfigPath}` : "Build settings";
1756
2035
  context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [{
1757
2036
  label: "Build Command",
1758
2037
  value: settings.buildCommand ?? "none",
@@ -1789,9 +2068,9 @@ async function maybeCustomizeDeploySettings(context, options) {
1789
2068
  input: context.runtime.stdin,
1790
2069
  output: context.runtime.stderr,
1791
2070
  message: `Framework (${options.framework.displayName})`,
1792
- choices: DEPLOY_FRAMEWORKS.map((framework) => ({
1793
- label: frameworkDisplayName(framework),
1794
- value: framework
2071
+ choices: FRAMEWORKS.map((framework) => ({
2072
+ label: framework.displayName,
2073
+ value: framework.key
1795
2074
  }))
1796
2075
  }), "set by you");
1797
2076
  const requestedPort = await textPrompt({
@@ -1834,14 +2113,6 @@ function maybeRenderDeploySettingsPreview(context, options) {
1834
2113
  value: `HTTP ${options.runtime.port}`
1835
2114
  }]).join("\n")}\n\n`);
1836
2115
  }
1837
- function frameworkDisplayName(framework) {
1838
- switch (framework) {
1839
- case "nextjs": return "Next.js";
1840
- case "hono": return "Hono";
1841
- case "tanstack-start": return "TanStack Start";
1842
- case "bun": return "Bun";
1843
- }
1844
- }
1845
2116
  function validateDeployHttpPortText(value) {
1846
2117
  if (!value?.trim()) return;
1847
2118
  try {
@@ -1855,6 +2126,11 @@ function formatDeployDirectory(cwd) {
1855
2126
  const basename = path.basename(cwd);
1856
2127
  return basename ? `./${basename}` : ".";
1857
2128
  }
2129
+ function formatAppDirectoryLabel(cwd, appDir) {
2130
+ if (appDir === cwd) return formatDeployDirectory(cwd);
2131
+ const relative = path.relative(cwd, appDir).split(path.sep).join("/");
2132
+ return relative.startsWith("..") ? relative : `./${relative}`;
2133
+ }
1858
2134
  async function readCurrentWorkspaceId(context) {
1859
2135
  const state = await context.stateStore.read();
1860
2136
  if (state.auth?.workspaceId) return state.auth.workspaceId;
@@ -1874,15 +2150,25 @@ function getBuildTypeExamples(commandName) {
1874
2150
  });
1875
2151
  }
1876
2152
  function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
1877
- if (buildType !== "auto" && buildType !== "bun" && entrypoint) {
2153
+ if (buildType !== "auto" && !ENTRYPOINT_BUILD_TYPES.includes(buildType) && entrypoint) {
1878
2154
  if (commandName === "deploy") throw usageError(`App deploy does not accept --entry with ${formatBuildTypeName(buildType)}`, `${formatBuildTypeName(buildType)} apps derive their runtime entrypoint from build output.`, "Remove --entry, or use --framework bun when you want to target a Bun entrypoint directly.", [`prisma-cli app deploy --framework ${buildType}`, "prisma-cli app deploy --framework bun --entry server.ts"], "app");
1879
2155
  throw usageError(`App ${commandName} does not accept --entry with --build-type ${buildType}`, `${formatBuildTypeName(buildType)} apps do not use an entrypoint flag in the current preview.`, `Remove --entry, or rerun prisma-cli app ${commandName} with --build-type bun when you want to target a Bun entrypoint directly.`, [`prisma-cli app ${commandName} --build-type ${buildType}`, `prisma-cli app ${commandName} --build-type bun --entry server.ts`], "app");
1880
2156
  }
1881
2157
  }
1882
- async function requireLocalBuildType(context, buildType, commandName) {
1883
- const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType, context.runtime.signal);
1884
- if (resolvedBuildType) return resolvedBuildType;
1885
- throw usageError(`App ${commandName} requires an explicit framework when detection is ambiguous`, "This preview only starts local dev servers for clear Next.js or Bun project shapes.", "Pass --build-type nextjs for a Next.js app, or pass --build-type bun with --entry <path> for a Bun app.", [`prisma-cli app ${commandName} --build-type nextjs`, `prisma-cli app ${commandName} --build-type bun --entry server.ts`], "app");
2158
+ /**
2159
+ * Resolves the framework for `app run` with the same detection as deploy, so
2160
+ * a repo that deploys without flags also runs without flags. Local dev server
2161
+ * support is intentionally narrower than deploy build support: only Next.js
2162
+ * and Bun/Hono have dev servers in the current preview.
2163
+ */
2164
+ async function resolveLocalRunFramework(context, options) {
2165
+ if (LOCAL_DEV_BUILD_TYPES.includes(options.requestedBuildType)) {
2166
+ if (options.configFramework && computeFrameworkToBuildType(options.configFramework) === options.requestedBuildType) return frameworkFromUserFacingValue(options.configFramework, `set by ${COMPUTE_CONFIG_FILENAME$1}`);
2167
+ return frameworkFromUserFacingValue(options.requestedBuildType, "set by --build-type");
2168
+ }
2169
+ const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
2170
+ if (detected && LOCAL_DEV_BUILD_TYPES.includes(detected.buildType)) return detected;
2171
+ throw usageError("App run requires an explicit framework when detection is ambiguous", "This preview only starts local dev servers for clear Next.js or Bun project shapes.", "Pass --build-type nextjs for a Next.js app, or pass --build-type bun with --entry <path> for a Bun app.", ["prisma-cli app run --build-type nextjs", "prisma-cli app run --build-type bun --entry server.ts"], "app");
1886
2172
  }
1887
2173
  function parseLocalPort(requestedPort) {
1888
2174
  if (!requestedPort) return DEFAULT_LOCAL_DEV_PORT;
@@ -1918,7 +2204,41 @@ function deployFailedError(summary, error, nextSteps) {
1918
2204
  function appDeployFailedError(error, progress) {
1919
2205
  const why = error instanceof Error ? error.message : String(error);
1920
2206
  const debug = formatDebugDetails(error);
1921
- if (progress.buildStarted && !progress.buildCompleted) return appBuildFailedError(why, debug);
2207
+ if (progress.buildStarted && !progress.buildCompleted) {
2208
+ const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
2209
+ const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
2210
+ const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
2211
+ const nextActions = standaloneOutputFailure ? [{
2212
+ kind: "edit-file",
2213
+ journey: "deploy-app",
2214
+ label: "Add Next.js standalone output",
2215
+ reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
2216
+ }, {
2217
+ kind: "run-command",
2218
+ journey: "deploy-app",
2219
+ label: "Rerun deploy",
2220
+ command: "prisma-cli app deploy"
2221
+ }] : [];
2222
+ return new CliError({
2223
+ code: "BUILD_FAILED",
2224
+ domain: "app",
2225
+ summary: "Build failed locally.",
2226
+ why,
2227
+ fix,
2228
+ debug,
2229
+ meta: { phase: "build" },
2230
+ humanLines: [
2231
+ "Build failed locally.",
2232
+ "",
2233
+ `✗ Built ${why}`,
2234
+ "",
2235
+ `Fix: ${fix}`
2236
+ ],
2237
+ exitCode: 1,
2238
+ nextSteps,
2239
+ nextActions
2240
+ });
2241
+ }
1922
2242
  if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
1923
2243
  const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
1924
2244
  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."];
@@ -1959,41 +2279,6 @@ function appDeployFailedError(error, progress) {
1959
2279
  nextSteps: []
1960
2280
  });
1961
2281
  }
1962
- function appBuildFailedError(why, debug) {
1963
- const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
1964
- const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
1965
- const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
1966
- const nextActions = standaloneOutputFailure ? [{
1967
- kind: "edit-file",
1968
- journey: "deploy-app",
1969
- label: "Add Next.js standalone output",
1970
- reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
1971
- }, {
1972
- kind: "run-command",
1973
- journey: "deploy-app",
1974
- label: "Rerun deploy",
1975
- command: "prisma-cli app deploy"
1976
- }] : [];
1977
- return new CliError({
1978
- code: "BUILD_FAILED",
1979
- domain: "app",
1980
- summary: "Build failed locally.",
1981
- why,
1982
- fix,
1983
- debug,
1984
- meta: { phase: "build" },
1985
- humanLines: [
1986
- "Build failed locally.",
1987
- "",
1988
- `✗ Built ${why}`,
1989
- "",
1990
- `Fix: ${fix}`
1991
- ],
1992
- exitCode: 1,
1993
- nextSteps,
1994
- nextActions
1995
- });
1996
- }
1997
2282
  function localResolutionPinStaleError() {
1998
2283
  return new CliError({
1999
2284
  code: "LOCAL_STATE_STALE",