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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,59 +1,78 @@
1
1
  import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
2
  import { FileTokenStorage } from "../adapters/token-storage.js";
3
3
  import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
4
+ import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
4
5
  import { renderCommandHeader } from "../shell/ui.js";
5
- import { writeJsonEvent } from "../shell/output.js";
6
6
  import { canPrompt } from "../shell/runtime.js";
7
- import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
8
- import { requireComputeAuth } from "../lib/auth/guard.js";
9
- import { readAuthState } from "../lib/auth/auth-ops.js";
10
- import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
11
- import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
12
- import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
13
- import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
14
- import { formatCommandArgument } from "../shell/command-arguments.js";
15
7
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
8
+ import { formatCommandArgument } from "../shell/command-arguments.js";
16
9
  import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
17
10
  import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
18
- import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
19
- import { readLocalGitBranch } from "../lib/git/local-branch.js";
20
- import { resolveOrCreatePreviewBuildSettings } from "../lib/app/preview-build-settings.js";
11
+ import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
12
+ import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
13
+ import { COMPUTE_CONFIG_FILENAME as COMPUTE_CONFIG_FILENAME$1, ComputeConfigTargetRequiredError, computeConfigErrorToCliError, computeFrameworkToBuildType, computeTargetAppDir, inferComputeTargetFromCwd, loadComputeConfig as loadComputeConfig$1, mergeComputeDeployInputs, mergeComputeLocalInputs, selectComputeDeployTarget } from "../lib/app/compute-config.js";
14
+ import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
15
+ import { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy } from "../lib/app/deploy-plan.js";
16
+ import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
17
+ import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
18
+ import { DEFAULT_LOCAL_DEV_PORT, runLocalApp } from "../lib/app/local-dev.js";
19
+ import { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredPreviewBuildSettings, resolveInferredPreviewBuildSettings } from "../lib/app/preview-build-settings.js";
21
20
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
22
21
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
23
- import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
24
22
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
25
23
  import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
26
24
  import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
27
- import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
25
+ import { requireComputeAuth } from "../lib/auth/guard.js";
26
+ import { readAuthState } from "../lib/auth/auth-ops.js";
27
+ import { readLocalGitBranch } from "../lib/git/local-branch.js";
28
+ import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
29
+ import { writeJsonEvent } from "../shell/output.js";
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
- import open from "open";
34
36
  import { Result, matchError } from "better-result";
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, {
@@ -1107,6 +1303,25 @@ async function resolveDeployAppSelection(context, projectId, apps, options) {
1107
1303
  firstDeploy: options.firstDeploy
1108
1304
  };
1109
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
+ }
1110
1325
  const inferredName = await options.inferName();
1111
1326
  const matches = findAppsByName(apps, inferredName.name);
1112
1327
  if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
@@ -1363,6 +1578,7 @@ async function resolveProjectContext(context, client, explicitProject, options)
1363
1578
  workspace: authState.workspace,
1364
1579
  explicitProject,
1365
1580
  envProjectId: options?.envProjectId,
1581
+ projectDir: options?.projectDir,
1366
1582
  listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
1367
1583
  commandName: options?.commandName
1368
1584
  });
@@ -1559,22 +1775,130 @@ async function resolveDeployBranch(context, explicitBranchName) {
1559
1775
  annotation: "default"
1560
1776
  };
1561
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
+ }
1562
1886
  async function resolveDeployFramework(context, options) {
1563
- if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1887
+ if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, options.requestedFrameworkAnnotation ?? "set by --framework");
1564
1888
  if (options.entrypoint) return {
1565
1889
  key: "bun",
1566
1890
  buildType: "bun",
1567
1891
  displayName: "Bun",
1568
- annotation: "set by --entry"
1892
+ annotation: options.entrypointAnnotation ?? "set by --entry"
1569
1893
  };
1570
- const detected = await detectDeployFramework(context.runtime.cwd, context.runtime.signal);
1894
+ const detected = await detectDeployFramework(options.appDir, context.runtime.signal);
1571
1895
  if (detected) return detected;
1572
- throw frameworkNotDetectedError(context.runtime.cwd);
1896
+ throw frameworkNotDetectedError(options.appDir);
1573
1897
  }
1574
- function resolveDeployRuntime(requestedHttpPort, framework) {
1898
+ function resolveDeployRuntime(requestedHttpPort, requestedHttpPortAnnotation, framework) {
1575
1899
  if (requestedHttpPort) return {
1576
1900
  port: parseDeployHttpPort(requestedHttpPort),
1577
- annotation: "set by --http-port"
1901
+ annotation: requestedHttpPortAnnotation ?? "set by --http-port"
1578
1902
  };
1579
1903
  return {
1580
1904
  port: FRAMEWORK_DEFAULT_HTTP_PORT,
@@ -1589,8 +1913,8 @@ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signa
1589
1913
  if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
1590
1914
  const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd, signal));
1591
1915
  if (packageEntrypoint) return packageEntrypoint;
1592
- if (framework.key !== "hono") return;
1593
- const defaultEntrypoint = "src/index.ts";
1916
+ const defaultEntrypoint = frameworkFromAlias(framework.key)?.defaultEntrypoint;
1917
+ if (!defaultEntrypoint) return;
1594
1918
  signal.throwIfAborted();
1595
1919
  try {
1596
1920
  await access(path.join(cwd, defaultEntrypoint));
@@ -1604,34 +1928,22 @@ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signa
1604
1928
  }
1605
1929
  async function detectDeployFramework(cwd, signal) {
1606
1930
  const packageJson = await readBunPackageJson(cwd, signal);
1607
- const nextConfig = await detectNextConfig(cwd, signal);
1608
- if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
1609
- key: "nextjs",
1610
- buildType: "nextjs",
1611
- displayName: "Next.js",
1612
- annotation: nextConfig.standalone ? "standalone output detected" : nextConfig.exists ? "detected from next.config" : "detected from package.json"
1613
- };
1614
- if (hasPackageDependency(packageJson, "hono")) return {
1615
- key: "hono",
1616
- buildType: "bun",
1617
- displayName: "Hono",
1618
- annotation: "detected from package.json"
1619
- };
1620
- if (hasAnyPackageDependency(packageJson, TANSTACK_START_PACKAGES)) return {
1621
- key: "tanstack-start",
1622
- buildType: "tanstack-start",
1623
- displayName: "TanStack Start",
1624
- annotation: "detected from package.json"
1625
- };
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
+ }
1626
1943
  return null;
1627
1944
  }
1628
- async function detectNextConfig(cwd, signal) {
1629
- for (const candidate of [
1630
- "next.config.js",
1631
- "next.config.mjs",
1632
- "next.config.ts",
1633
- "next.config.mts"
1634
- ]) {
1945
+ async function detectFrameworkConfigFile(cwd, framework, signal) {
1946
+ for (const candidate of framework.detectConfigFiles) {
1635
1947
  const filePath = path.join(cwd, candidate);
1636
1948
  signal.throwIfAborted();
1637
1949
  try {
@@ -1641,7 +1953,8 @@ async function detectNextConfig(cwd, signal) {
1641
1953
  });
1642
1954
  return {
1643
1955
  exists: true,
1644
- standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
1956
+ standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),
1957
+ path: filePath
1645
1958
  };
1646
1959
  } catch (error) {
1647
1960
  if (signal.aborted) throw error;
@@ -1650,7 +1963,8 @@ async function detectNextConfig(cwd, signal) {
1650
1963
  }
1651
1964
  return {
1652
1965
  exists: false,
1653
- standalone: false
1966
+ standalone: false,
1967
+ path: null
1654
1968
  };
1655
1969
  }
1656
1970
  function hasPackageDependency(packageJson, dependencyName) {
@@ -1663,48 +1977,41 @@ function hasDependency(dependencies, dependencyName) {
1663
1977
  return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1664
1978
  }
1665
1979
  function frameworkFromUserFacingValue(value, annotation) {
1666
- switch (value.trim().toLowerCase()) {
1667
- case "next":
1668
- case "next.js":
1669
- case "nextjs": return {
1670
- key: "nextjs",
1671
- buildType: "nextjs",
1672
- displayName: "Next.js",
1673
- annotation
1674
- };
1675
- case "hono": return {
1676
- key: "hono",
1677
- buildType: "bun",
1678
- displayName: "Hono",
1679
- annotation
1680
- };
1681
- case "bun": return {
1682
- key: "bun",
1683
- buildType: "bun",
1684
- displayName: "Bun",
1685
- annotation
1686
- };
1687
- case "tanstack":
1688
- case "tanstack-start":
1689
- case "@tanstack/react-start":
1690
- case "@tanstack/solid-start": return {
1691
- key: "tanstack-start",
1692
- buildType: "tanstack-start",
1693
- displayName: "TanStack Start",
1694
- annotation
1695
- };
1696
- default: throw frameworkNotDetectedError(void 0, value);
1697
- }
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
+ });
1698
2005
  }
1699
2006
  function frameworkNotDetectedError(cwd, requestedFramework) {
1700
- const supported = "Next.js, Hono, TanStack Start, Bun";
2007
+ const supported = FRAMEWORKS.map((framework) => framework.displayName).join(", ");
1701
2008
  const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
1702
2009
  return new CliError({
1703
2010
  code: "FRAMEWORK_NOT_DETECTED",
1704
2011
  domain: "app",
1705
2012
  summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
1706
2013
  why: `Supported Beta frameworks: ${supported}.`,
1707
- 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.`,
1708
2015
  exitCode: 2,
1709
2016
  nextSteps: [
1710
2017
  "prisma-cli app deploy --framework nextjs",
@@ -1717,14 +2024,14 @@ function frameworkNotDetectedError(cwd, requestedFramework) {
1717
2024
  }
1718
2025
  async function maybeRenderDeploySetupBlock(context, details) {
1719
2026
  if (context.flags.json || context.flags.quiet) return;
1720
- const directory = formatDeployDirectory(context.runtime.cwd);
2027
+ const directory = formatAppDirectoryLabel(context.runtime.cwd, details.appDir);
1721
2028
  const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
1722
2029
  context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1723
2030
  }
1724
2031
  function maybeRenderDeployBuildSettings(context, resolution) {
1725
2032
  if (context.flags.json || context.flags.quiet) return;
1726
2033
  const settings = resolution.settings;
1727
- const title = resolution.status === "created" ? `Created ${resolution.relativeConfigPath}` : `Using ${resolution.relativeConfigPath}`;
2034
+ const title = resolution.status === "config" ? `Using ${resolution.relativeConfigPath}` : "Build settings";
1728
2035
  context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [{
1729
2036
  label: "Build Command",
1730
2037
  value: settings.buildCommand ?? "none",
@@ -1761,9 +2068,9 @@ async function maybeCustomizeDeploySettings(context, options) {
1761
2068
  input: context.runtime.stdin,
1762
2069
  output: context.runtime.stderr,
1763
2070
  message: `Framework (${options.framework.displayName})`,
1764
- choices: DEPLOY_FRAMEWORKS.map((framework) => ({
1765
- label: frameworkDisplayName(framework),
1766
- value: framework
2071
+ choices: FRAMEWORKS.map((framework) => ({
2072
+ label: framework.displayName,
2073
+ value: framework.key
1767
2074
  }))
1768
2075
  }), "set by you");
1769
2076
  const requestedPort = await textPrompt({
@@ -1806,14 +2113,6 @@ function maybeRenderDeploySettingsPreview(context, options) {
1806
2113
  value: `HTTP ${options.runtime.port}`
1807
2114
  }]).join("\n")}\n\n`);
1808
2115
  }
1809
- function frameworkDisplayName(framework) {
1810
- switch (framework) {
1811
- case "nextjs": return "Next.js";
1812
- case "hono": return "Hono";
1813
- case "tanstack-start": return "TanStack Start";
1814
- case "bun": return "Bun";
1815
- }
1816
- }
1817
2116
  function validateDeployHttpPortText(value) {
1818
2117
  if (!value?.trim()) return;
1819
2118
  try {
@@ -1827,6 +2126,11 @@ function formatDeployDirectory(cwd) {
1827
2126
  const basename = path.basename(cwd);
1828
2127
  return basename ? `./${basename}` : ".";
1829
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
+ }
1830
2134
  async function readCurrentWorkspaceId(context) {
1831
2135
  const state = await context.stateStore.read();
1832
2136
  if (state.auth?.workspaceId) return state.auth.workspaceId;
@@ -1846,15 +2150,25 @@ function getBuildTypeExamples(commandName) {
1846
2150
  });
1847
2151
  }
1848
2152
  function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
1849
- if (buildType !== "auto" && buildType !== "bun" && entrypoint) {
2153
+ if (buildType !== "auto" && !ENTRYPOINT_BUILD_TYPES.includes(buildType) && entrypoint) {
1850
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");
1851
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");
1852
2156
  }
1853
2157
  }
1854
- async function requireLocalBuildType(context, buildType, commandName) {
1855
- const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType, context.runtime.signal);
1856
- if (resolvedBuildType) return resolvedBuildType;
1857
- throw usageError(`App ${commandName} requires an explicit framework when detection is ambiguous`, "This preview only starts local dev servers for clear Next.js or Bun project shapes.", "Pass --build-type nextjs for a Next.js app, or pass --build-type bun with --entry <path> for a Bun app.", [`prisma-cli app ${commandName} --build-type nextjs`, `prisma-cli app ${commandName} --build-type bun --entry server.ts`], "app");
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");
1858
2172
  }
1859
2173
  function parseLocalPort(requestedPort) {
1860
2174
  if (!requestedPort) return DEFAULT_LOCAL_DEV_PORT;