@prisma/cli 3.0.0-alpha.1 → 3.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +1 -16
  2. package/dist/adapters/git.js +49 -0
  3. package/dist/adapters/local-state.js +39 -1
  4. package/dist/cli2.js +60 -4
  5. package/dist/commands/app/index.js +41 -21
  6. package/dist/commands/auth/index.js +3 -2
  7. package/dist/commands/branch/index.js +2 -1
  8. package/dist/commands/env.js +87 -0
  9. package/dist/commands/git/index.js +36 -0
  10. package/dist/commands/project/index.js +12 -14
  11. package/dist/commands/version/index.js +18 -0
  12. package/dist/controllers/app-env.js +223 -0
  13. package/dist/controllers/app.js +1026 -169
  14. package/dist/controllers/auth.js +9 -9
  15. package/dist/controllers/branch.js +6 -6
  16. package/dist/controllers/project.js +451 -161
  17. package/dist/controllers/version.js +12 -0
  18. package/dist/lib/app/bun-project.js +1 -1
  19. package/dist/lib/app/deploy-output.js +15 -0
  20. package/dist/lib/app/env-config.js +57 -0
  21. package/dist/lib/app/env-vars.js +4 -4
  22. package/dist/lib/app/local-dev.js +1 -1
  23. package/dist/lib/app/preview-build.js +128 -1
  24. package/dist/lib/app/preview-interaction.js +2 -35
  25. package/dist/lib/app/preview-progress.js +43 -58
  26. package/dist/lib/app/preview-provider.js +125 -24
  27. package/dist/lib/auth/auth-ops.js +58 -13
  28. package/dist/lib/auth/client.js +1 -1
  29. package/dist/lib/auth/guard.js +1 -1
  30. package/dist/lib/auth/login.js +115 -4
  31. package/dist/lib/project/local-pin.js +51 -0
  32. package/dist/lib/project/resolution.js +201 -0
  33. package/dist/lib/version.js +55 -0
  34. package/dist/output/patterns.js +15 -18
  35. package/dist/presenters/app-env.js +129 -0
  36. package/dist/presenters/app.js +16 -29
  37. package/dist/presenters/auth.js +2 -2
  38. package/dist/presenters/branch.js +6 -6
  39. package/dist/presenters/project.js +87 -44
  40. package/dist/presenters/version.js +29 -0
  41. package/dist/shell/command-meta.js +148 -91
  42. package/dist/shell/command-runner.js +32 -2
  43. package/dist/shell/errors.js +8 -3
  44. package/dist/shell/global-flags.js +13 -1
  45. package/dist/shell/help.js +8 -7
  46. package/dist/shell/output.js +29 -12
  47. package/dist/shell/prompt.js +12 -2
  48. package/dist/shell/runtime.js +1 -1
  49. package/dist/shell/ui.js +19 -1
  50. package/dist/use-cases/auth.js +9 -12
  51. package/dist/use-cases/branch.js +20 -20
  52. package/dist/use-cases/create-cli-gateways.js +3 -13
  53. package/dist/use-cases/project.js +2 -48
  54. package/package.json +3 -3
  55. package/dist/adapters/config.js +0 -74
@@ -1,19 +1,37 @@
1
- import { UnsafeConfigWriteError, assertLinkedProjectIdWritable, readLinkedProjectId, writeLinkedProjectId } from "../adapters/config.js";
2
- import { CliError, authRequiredError, featureUnavailableError, usageError } from "../shell/errors.js";
1
+ import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
+ import { FileTokenStorage } from "../adapters/token-storage.js";
3
+ import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
4
+ import { renderCommandHeader } from "../shell/ui.js";
5
+ import { writeJsonEvent } from "../shell/output.js";
3
6
  import { canPrompt } from "../shell/runtime.js";
4
- import { textPrompt } from "../shell/prompt.js";
7
+ import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
5
8
  import { requireComputeAuth } from "../lib/auth/guard.js";
9
+ import { readAuthState } from "../lib/auth/auth-ops.js";
6
10
  import { parseEnvAssignments } from "../lib/app/env-vars.js";
11
+ import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
12
+ import { readBunPackageJson } from "../lib/app/bun-project.js";
7
13
  import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
8
- import { projectNotFoundError } from "../use-cases/project.js";
14
+ import { inferTargetName, projectNotFoundError, resolveProjectTarget } from "../lib/project/resolution.js";
15
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
9
16
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
10
- import { PREVIEW_DEFAULT_REGION, createPreviewDeployInteraction } from "../lib/app/preview-interaction.js";
11
- import { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
17
+ import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
18
+ import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
12
19
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
13
20
  import { createSelectPromptPort } from "./select-prompt-port.js";
21
+ import { requireAuthenticatedAuthState } from "./auth.js";
22
+ import { listRealWorkspaceProjects } from "./project.js";
23
+ import { access, readFile } from "node:fs/promises";
14
24
  import path from "node:path";
15
25
  import open from "open";
16
26
  //#region src/controllers/app.ts
27
+ const DEPLOY_FRAMEWORKS = [
28
+ "nextjs",
29
+ "hono",
30
+ "tanstack-start"
31
+ ];
32
+ const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
33
+ const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
34
+ const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
17
35
  function isRealMode(context) {
18
36
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
19
37
  }
@@ -34,7 +52,7 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
34
52
  buildType: actualBuildType
35
53
  },
36
54
  warnings: [],
37
- nextSteps: ["prisma app deploy"]
55
+ nextSteps: ["prisma-cli app deploy"]
38
56
  };
39
57
  } catch (error) {
40
58
  if (buildType === "auto" && isAutoBuildDetectionError(error)) throw usageError("App build requires an explicit framework when detection is ambiguous", `This preview auto-detects clear project shapes for ${RESOLVED_PREVIEW_BUILD_TYPES.map(formatBuildTypeName).join(", ")}.`, "Pass a supported --build-type value, or pass --entry <path> for a Bun app.", getBuildTypeExamples("build"), "app");
@@ -42,7 +60,7 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
42
60
  }
43
61
  }
44
62
  async function runAppRun(context, entrypoint, requestedBuildType, requestedPort) {
45
- 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 app run"], "app");
63
+ 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");
46
64
  const buildType = normalizeBuildType(requestedBuildType);
47
65
  assertSupportedEntrypoint(buildType, entrypoint, "run");
48
66
  const port = parseLocalPort(requestedPort);
@@ -75,16 +93,75 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
75
93
  }
76
94
  async function runAppDeploy(context, appName, options) {
77
95
  ensurePreviewAppMode(context);
78
- const buildType = normalizeBuildType(options?.buildType);
79
- assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
80
- const portMapping = parseDeployPortMapping(options?.httpPort);
96
+ const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
97
+ const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
98
+ const skipLocalPin = Boolean(envProjectId || envAppId);
99
+ const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
100
+ if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
101
+ const explicitBuildType = Boolean(options?.buildType && options.buildType !== "auto");
102
+ const branch = await resolveDeployBranch(context, options?.branchName);
103
+ if (options?.httpPort) parseDeployHttpPort(options.httpPort);
104
+ let framework = await resolveDeployFramework(context, {
105
+ requestedFramework: options?.framework,
106
+ requestedBuildType: options?.buildType,
107
+ explicitBuildType
108
+ });
109
+ let runtime = resolveDeployRuntime(options?.httpPort, framework);
110
+ assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
81
111
  const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
82
- const provider = await requirePreviewAppProvider(context);
83
- const projectId = await resolveProjectIdForDeploy(context, provider);
84
- const selectedApp = await resolveDeploySelection(context, projectId, await listApps(context, provider, projectId), appName);
112
+ const firstDeploy = !skipLocalPin && localPin.kind === "missing";
113
+ const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
114
+ allowCreate: true,
115
+ branch,
116
+ envProjectId,
117
+ localPin
118
+ });
119
+ const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
120
+ explicitAppName: appName,
121
+ explicitAppId: envAppId,
122
+ firstDeploy,
123
+ inferName: () => inferTargetName(context.runtime.cwd)
124
+ });
125
+ await maybeRenderDeploySetupBlock(context, {
126
+ firstDeploy: selectedApp.firstDeploy,
127
+ workspaceName: target.workspace.name,
128
+ projectName: target.project.name,
129
+ projectAnnotation: annotationForProjectResolution(target.resolution),
130
+ branchName: target.branch.name,
131
+ branchAnnotation: branch.annotation,
132
+ appName: selectedApp.displayName,
133
+ appAnnotation: selectedApp.annotation,
134
+ framework,
135
+ runtime
136
+ });
137
+ const customized = await maybeCustomizeDeploySettings(context, {
138
+ framework,
139
+ runtime,
140
+ firstDeploy: selectedApp.firstDeploy,
141
+ explicitFramework: Boolean(options?.framework),
142
+ explicitBuildType,
143
+ explicitHttpPort: Boolean(options?.httpPort)
144
+ });
145
+ framework = customized.framework;
146
+ runtime = customized.runtime;
147
+ const buildType = framework.buildType;
148
+ assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
149
+ const portMapping = parseDeployPortMapping(String(runtime.port));
150
+ const shouldWriteLocalPin = firstDeploy && !skipLocalPin;
151
+ if (shouldWriteLocalPin) {
152
+ await writeLocalResolutionPin(context.runtime.cwd, {
153
+ workspaceId: target.workspace.id,
154
+ projectId: target.project.id
155
+ });
156
+ await ensureLocalResolutionPinGitignore(context.runtime.cwd);
157
+ maybeRenderLocalPinBound(context, target.project.name);
158
+ }
159
+ const progressState = createPreviewDeployProgressState();
160
+ const deployStartedAt = Date.now();
85
161
  const deployResult = await provider.deployApp({
86
162
  cwd: context.runtime.cwd,
87
163
  projectId,
164
+ branchName: target.branch.name,
88
165
  appId: selectedApp.appId,
89
166
  appName: selectedApp.appName,
90
167
  region: selectedApp.region,
@@ -92,11 +169,12 @@ async function runAppDeploy(context, appName, options) {
92
169
  buildType,
93
170
  portMapping,
94
171
  envVars,
95
- interaction: selectedApp.useInteractiveSelection ? createPreviewDeployInteraction(context) : void 0,
96
- progress: createPreviewDeployProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
172
+ interaction: void 0,
173
+ progress: createPreviewDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
97
174
  }).catch((error) => {
98
- throw deployFailedError("App deploy failed", error, ["prisma app list-deploys"]);
175
+ throw appDeployFailedError(error, progressState);
99
176
  });
177
+ const deployDurationMs = Date.now() - deployStartedAt;
100
178
  await context.stateStore.setSelectedApp(projectId, {
101
179
  id: deployResult.app.id,
102
180
  name: deployResult.app.name
@@ -105,29 +183,37 @@ async function runAppDeploy(context, appName, options) {
105
183
  return {
106
184
  command: "app.deploy",
107
185
  result: {
108
- projectId: deployResult.projectId,
186
+ workspace: target.workspace,
187
+ project: target.project,
188
+ branch: target.branch,
189
+ resolution: target.resolution,
109
190
  app: {
110
191
  id: deployResult.app.id,
111
192
  name: deployResult.app.name
112
193
  },
113
- deployment: deployResult.deployment
194
+ deployment: deployResult.deployment,
195
+ durationMs: deployDurationMs,
196
+ localPin: shouldWriteLocalPin ? {
197
+ path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
198
+ written: true
199
+ } : void 0
114
200
  },
115
201
  warnings: [],
116
- nextSteps: ["prisma app list-deploys", `prisma app show-deploy ${deployResult.deployment.id}`]
202
+ nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
117
203
  };
118
204
  }
119
- async function runAppUpdateEnv(context, appName, envAssignments) {
205
+ async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
120
206
  ensurePreviewAppMode(context);
207
+ emitLegacyEnvDeprecationWarning(context, "app update-env", "project env add");
121
208
  const envVars = parseEnvAssignments(envAssignments, {
122
209
  commandName: "update-env",
123
210
  requireAtLeastOne: true
124
211
  });
125
- const projectId = await requireLinkedProjectId(context);
126
- const provider = await requirePreviewAppProvider(context);
127
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
128
- if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The linked project does not have any deployed app yet.");
212
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
213
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
214
+ if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The resolved project does not have any deployed app yet.");
129
215
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
130
- throw deployFailedError("Failed to inspect app deployments", error, ["prisma app list-deploys"]);
216
+ throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
131
217
  });
132
218
  if (deploymentsResult.deployments.length === 0) throw noDeploymentsError("No deployments available to update environment variables", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
133
219
  const updateResult = await provider.updateAppEnv({
@@ -136,7 +222,7 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
136
222
  progress: createPreviewUpdateEnvProgress(context.output.stderr, !context.flags.json && !context.flags.quiet),
137
223
  promoteProgress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
138
224
  }).catch((error) => {
139
- throw deployFailedError("Failed to update app environment variables", error, ["prisma app list-env"]);
225
+ throw deployFailedError("Failed to update app environment variables", error, ["prisma-cli app list-env"]);
140
226
  });
141
227
  await context.stateStore.setSelectedApp(projectId, {
142
228
  id: updateResult.app.id,
@@ -155,14 +241,14 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
155
241
  variables: updateResult.variables
156
242
  },
157
243
  warnings: [],
158
- nextSteps: ["prisma app list-env", `prisma app show-deploy ${updateResult.deployment.id}`]
244
+ nextSteps: ["prisma-cli app list-env", `prisma-cli app show-deploy ${updateResult.deployment.id}`]
159
245
  };
160
246
  }
161
- async function runAppListEnv(context, appName) {
247
+ async function runAppListEnv(context, appName, projectRef) {
162
248
  ensurePreviewAppMode(context);
163
- const projectId = await requireLinkedProjectId(context);
164
- const provider = await requirePreviewAppProvider(context);
165
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
249
+ emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
250
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
251
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
166
252
  if (!selectedApp) return {
167
253
  command: "app.list-env",
168
254
  result: {
@@ -172,10 +258,10 @@ async function runAppListEnv(context, appName) {
172
258
  variables: []
173
259
  },
174
260
  warnings: [],
175
- nextSteps: ["prisma app deploy"]
261
+ nextSteps: ["prisma-cli app deploy"]
176
262
  };
177
263
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
178
- throw deployFailedError("Failed to inspect app deployments", error, ["prisma app list-deploys"]);
264
+ throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
179
265
  });
180
266
  const knownLiveDeploymentId = await context.stateStore.readKnownLiveDeployment(projectId, deploymentsResult.app.id);
181
267
  const missingKnownLiveDeploymentId = knownLiveDeploymentId && !deploymentsResult.deployments.some((candidate) => candidate.id === knownLiveDeploymentId) ? knownLiveDeploymentId : null;
@@ -191,7 +277,7 @@ async function runAppListEnv(context, appName) {
191
277
  appId: deploymentsResult.app.id,
192
278
  deploymentId: missingKnownLiveDeploymentId
193
279
  }).catch((error) => {
194
- throw deployFailedError("Failed to inspect app environment variables", error, ["prisma app list-deploys"]);
280
+ throw deployFailedError("Failed to inspect app environment variables", error, ["prisma-cli app list-deploys"]);
195
281
  });
196
282
  return {
197
283
  command: "app.list-env",
@@ -205,7 +291,7 @@ async function runAppListEnv(context, appName) {
205
291
  variables: envResult.variables
206
292
  },
207
293
  warnings: [],
208
- nextSteps: [`prisma app show-deploy ${envResult.deployment.id}`]
294
+ nextSteps: [`prisma-cli app show-deploy ${envResult.deployment.id}`]
209
295
  };
210
296
  }
211
297
  if (!deployment) return {
@@ -220,13 +306,13 @@ async function runAppListEnv(context, appName) {
220
306
  variables: []
221
307
  },
222
308
  warnings: [],
223
- nextSteps: ["prisma app deploy"]
309
+ nextSteps: ["prisma-cli app deploy"]
224
310
  };
225
311
  const envResult = await provider.listAppEnvNames({
226
312
  appId: deploymentsResult.app.id,
227
313
  deploymentId: deployment.id
228
314
  }).catch((error) => {
229
- throw deployFailedError("Failed to inspect app environment variables", error, ["prisma app list-deploys"]);
315
+ throw deployFailedError("Failed to inspect app environment variables", error, ["prisma-cli app list-deploys"]);
230
316
  });
231
317
  return {
232
318
  command: "app.list-env",
@@ -243,14 +329,13 @@ async function runAppListEnv(context, appName) {
243
329
  variables: envResult.variables
244
330
  },
245
331
  warnings: [],
246
- nextSteps: deployment.id ? [`prisma app show-deploy ${deployment.id}`] : ["prisma app deploy"]
332
+ nextSteps: deployment.id ? [`prisma-cli app show-deploy ${deployment.id}`] : ["prisma-cli app deploy"]
247
333
  };
248
334
  }
249
- async function runAppListDeploys(context, appName) {
335
+ async function runAppListDeploys(context, appName, projectRef) {
250
336
  ensurePreviewAppMode(context);
251
- const projectId = await requireLinkedProjectId(context);
252
- const provider = await requirePreviewAppProvider(context);
253
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
337
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
338
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
254
339
  if (!selectedApp) return {
255
340
  command: "app.list-deploys",
256
341
  result: {
@@ -259,10 +344,10 @@ async function runAppListDeploys(context, appName) {
259
344
  deployments: []
260
345
  },
261
346
  warnings: [],
262
- nextSteps: ["prisma app deploy"]
347
+ nextSteps: ["prisma-cli app deploy"]
263
348
  };
264
349
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
265
- throw deployFailedError("Failed to list app deployments", error, ["prisma app deploy"]);
350
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app deploy"]);
266
351
  });
267
352
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
268
353
  const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId).slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
@@ -281,14 +366,13 @@ async function runAppListDeploys(context, appName) {
281
366
  deployments
282
367
  },
283
368
  warnings: [],
284
- nextSteps: deployments.length > 0 ? [`prisma app show-deploy ${deployments[0]?.id}`] : ["prisma app deploy"]
369
+ nextSteps: deployments.length > 0 ? [`prisma-cli app show-deploy ${deployments[0]?.id}`] : ["prisma-cli app deploy"]
285
370
  };
286
371
  }
287
- async function runAppShow(context, appName) {
372
+ async function runAppShow(context, appName, projectRef) {
288
373
  ensurePreviewAppMode(context);
289
- const projectId = await requireLinkedProjectId(context);
290
- const provider = await requirePreviewAppProvider(context);
291
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
374
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
375
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
292
376
  if (!selectedApp) return {
293
377
  command: "app.show",
294
378
  result: {
@@ -299,10 +383,10 @@ async function runAppShow(context, appName) {
299
383
  recentDeployments: []
300
384
  },
301
385
  warnings: [],
302
- nextSteps: ["prisma app deploy"]
386
+ nextSteps: ["prisma-cli app deploy"]
303
387
  };
304
388
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
305
- throw deployFailedError("Failed to inspect app", error, ["prisma app list-deploys"]);
389
+ throw deployFailedError("Failed to inspect app", error, ["prisma-cli app list-deploys"]);
306
390
  });
307
391
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
308
392
  const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId).slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
@@ -330,19 +414,20 @@ async function runAppShow(context, appName) {
330
414
  async function runAppShowDeploy(context, deploymentId) {
331
415
  ensurePreviewAppMode(context);
332
416
  const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId).catch((error) => {
333
- throw deployFailedError("Failed to show deployment", error, ["prisma app list-deploys"]);
417
+ throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
334
418
  });
335
419
  if (!deployment) throw new CliError({
336
420
  code: "DEPLOYMENT_NOT_FOUND",
337
421
  domain: "app",
338
422
  summary: `Deployment "${deploymentId}" not found`,
339
423
  why: "The requested deployment does not exist or is no longer available.",
340
- fix: "Run prisma app list-deploys to choose an available deployment id.",
424
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id.",
341
425
  exitCode: 1,
342
- nextSteps: ["prisma app list-deploys"]
426
+ nextSteps: ["prisma-cli app list-deploys"]
343
427
  });
344
- const linkedProjectId = deployment?.app ? await readLinkedProjectId(context.runtime.cwd) : null;
345
- const knownLiveDeploymentId = deployment?.app && linkedProjectId ? await context.stateStore.readKnownLiveDeployment(linkedProjectId, deployment.app.id) : null;
428
+ const workspaceId = deployment?.app ? await readCurrentWorkspaceId(context) : null;
429
+ const rememberedProject = workspaceId ? await context.stateStore.readRememberedProject(workspaceId) : null;
430
+ const knownLiveDeploymentId = deployment?.app && rememberedProject ? await context.stateStore.readKnownLiveDeployment(rememberedProject.id, deployment.app.id) : null;
346
431
  const providerLiveDeploymentId = deployment.app?.liveDeploymentId ?? null;
347
432
  return {
348
433
  command: "app.show-deploy",
@@ -360,14 +445,13 @@ async function runAppShowDeploy(context, deploymentId) {
360
445
  nextSteps: []
361
446
  };
362
447
  }
363
- async function runAppOpen(context, appName) {
448
+ async function runAppOpen(context, appName, projectRef) {
364
449
  ensurePreviewAppMode(context);
365
- const projectId = await requireLinkedProjectId(context);
366
- const provider = await requirePreviewAppProvider(context);
367
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
368
- if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The linked project does not have any deployed app yet.");
450
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
451
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
452
+ if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
369
453
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
370
- throw deployFailedError("Failed to resolve app URL", error, ["prisma app show"]);
454
+ throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
371
455
  });
372
456
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
373
457
  const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId).slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
@@ -377,7 +461,7 @@ async function runAppOpen(context, appName) {
377
461
  name: deploymentsResult.app.name
378
462
  });
379
463
  if (!liveDeployment) throw noDeploymentsError("No deployments available to open", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
380
- if (!deploymentsResult.app.liveUrl) throw featureUnavailableError("Live URL is not available for the selected app", "Deployments exist, but the provider does not expose a stable live service URL for this app yet.", "Run prisma app show to inspect the current deployment state and try again after the app reports a live URL.", ["prisma app show"], "app");
464
+ if (!deploymentsResult.app.liveUrl) throw featureUnavailableError("Live URL is not available for the selected app", "Deployments exist, but the provider does not expose a stable live service URL for this app yet.", "Run prisma-cli app show to inspect the current deployment state and try again after the app reports a live URL.", ["prisma-cli app show"], "app");
381
465
  const shouldOpen = canPrompt(context);
382
466
  if (shouldOpen) await open(deploymentsResult.app.liveUrl);
383
467
  return {
@@ -392,20 +476,139 @@ async function runAppOpen(context, appName) {
392
476
  opened: shouldOpen
393
477
  },
394
478
  warnings: [],
395
- nextSteps: ["prisma app show", `prisma app show-deploy ${liveDeployment.id}`]
479
+ nextSteps: ["prisma-cli app show", `prisma-cli app show-deploy ${liveDeployment.id}`]
396
480
  };
397
481
  }
398
- async function runAppLogs(context, _appName, _deploymentId) {
482
+ async function runAppLogs(context, appName, deploymentId, projectRef) {
399
483
  ensurePreviewAppMode(context);
400
- throw blockedPreviewAppCommandError("App logs are not available in this preview", "The current preview cannot stream app logs yet.");
484
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef);
485
+ const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
486
+ if (!context.flags.json && !context.flags.quiet) {
487
+ const lines = renderCommandHeader(context.ui, {
488
+ commandLabel: "app logs",
489
+ description: "Streaming logs for the selected deployment.",
490
+ docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
491
+ rows: [
492
+ {
493
+ key: "project",
494
+ value: projectId
495
+ },
496
+ {
497
+ key: "app",
498
+ value: target.app.name
499
+ },
500
+ {
501
+ key: "deployment",
502
+ value: target.deployment.id
503
+ }
504
+ ]
505
+ });
506
+ if (lines.length > 0) context.output.stderr.write(`${lines.join("\n")}\n`);
507
+ }
508
+ await provider.streamDeploymentLogs({
509
+ deploymentId: target.deployment.id,
510
+ onRecord: (record) => writeLogRecord(context, record)
511
+ }).catch((error) => {
512
+ throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
513
+ });
514
+ }
515
+ async function resolveExplicitLogDeployment(context, provider, projectId, branchName, appName, deploymentId) {
516
+ if (appName) {
517
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
518
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
519
+ const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
520
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
521
+ });
522
+ const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
523
+ await context.stateStore.setSelectedApp(projectId, {
524
+ id: deploymentsResult.app.id,
525
+ name: deploymentsResult.app.name
526
+ });
527
+ return {
528
+ app: deploymentsResult.app,
529
+ deployment
530
+ };
531
+ }
532
+ const shown = await provider.showDeployment(deploymentId).catch((error) => {
533
+ throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
534
+ });
535
+ if (!shown) throw new CliError({
536
+ code: "DEPLOYMENT_NOT_FOUND",
537
+ domain: "app",
538
+ summary: `Deployment "${deploymentId}" not found`,
539
+ why: "The requested deployment does not exist or is no longer available.",
540
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id.",
541
+ exitCode: 1,
542
+ nextSteps: ["prisma-cli app list-deploys"]
543
+ });
544
+ if (!shown.app) throw new CliError({
545
+ code: "DEPLOYMENT_NOT_FOUND",
546
+ domain: "app",
547
+ summary: `Deployment "${deploymentId}" is not attached to an app`,
548
+ why: "The requested deployment could be found, but its app could not be resolved.",
549
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id for the selected app.",
550
+ exitCode: 1,
551
+ nextSteps: ["prisma-cli app list-deploys"]
552
+ });
553
+ const resolvedProjectApp = (await listApps(context, provider, projectId, branchName)).find((app) => app.id === shown.app?.id);
554
+ if (!resolvedProjectApp) throw new CliError({
555
+ code: "DEPLOYMENT_NOT_FOUND",
556
+ domain: "app",
557
+ summary: `Deployment "${deploymentId}" not found in the resolved project`,
558
+ why: "The requested deployment does not belong to an app in the resolved project.",
559
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id for this project.",
560
+ exitCode: 1,
561
+ nextSteps: ["prisma-cli app list-deploys"]
562
+ });
563
+ await context.stateStore.setSelectedApp(projectId, {
564
+ id: resolvedProjectApp.id,
565
+ name: resolvedProjectApp.name
566
+ });
567
+ return {
568
+ app: resolvedProjectApp,
569
+ deployment: shown.deployment
570
+ };
571
+ }
572
+ async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
573
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
574
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
575
+ const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
576
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
577
+ });
578
+ const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
579
+ const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId);
580
+ const deployment = currentLiveDeploymentId ? deployments.find((candidate) => candidate.id === currentLiveDeploymentId) ?? null : null;
581
+ await context.stateStore.setSelectedApp(projectId, {
582
+ id: deploymentsResult.app.id,
583
+ name: deploymentsResult.app.name
584
+ });
585
+ if (!deployment) throw noDeploymentsError("No deployments available to stream logs", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
586
+ return {
587
+ app: deploymentsResult.app,
588
+ deployment
589
+ };
590
+ }
591
+ function writeLogRecord(context, record) {
592
+ if (context.flags.json) {
593
+ writeJsonEvent(context.output, {
594
+ type: record.type,
595
+ command: "app.logs",
596
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
597
+ data: record
598
+ });
599
+ return;
600
+ }
601
+ if (record.type === "log") {
602
+ context.output.stdout.write(record.text);
603
+ if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
604
+ }
401
605
  }
402
- async function runAppPromote(context, deploymentId, appName) {
606
+ async function runAppPromote(context, deploymentId, appName, projectRef) {
403
607
  ensurePreviewAppMode(context);
404
- const projectId = await requireLinkedProjectId(context);
405
- const provider = await requirePreviewAppProvider(context);
406
- const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "promote");
608
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
609
+ const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
407
610
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
408
- throw deployFailedError("Failed to list app deployments", error, ["prisma app list-deploys"]);
611
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
409
612
  });
410
613
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
411
614
  const targetDeployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
@@ -419,7 +622,7 @@ async function runAppPromote(context, deploymentId, appName) {
419
622
  deploymentId: targetDeployment.id,
420
623
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
421
624
  }).catch((error) => {
422
- throw deployFailedError("Failed to promote deployment", error, ["prisma app list-deploys"]);
625
+ throw deployFailedError("Failed to promote deployment", error, ["prisma-cli app list-deploys"]);
423
626
  });
424
627
  await context.stateStore.setKnownLiveDeployment(projectId, deploymentsResult.app.id, targetDeployment.id);
425
628
  return {
@@ -437,16 +640,15 @@ async function runAppPromote(context, deploymentId, appName) {
437
640
  }
438
641
  },
439
642
  warnings: targetAlreadyLive ? ["The selected deployment is already live for this app."] : [],
440
- nextSteps: ["prisma app list-deploys", `prisma app show-deploy ${targetDeployment.id}`]
643
+ nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
441
644
  };
442
645
  }
443
- async function runAppRollback(context, appName, deploymentId) {
646
+ async function runAppRollback(context, appName, deploymentId, projectRef) {
444
647
  ensurePreviewAppMode(context);
445
- const projectId = await requireLinkedProjectId(context);
446
- const provider = await requirePreviewAppProvider(context);
447
- const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "rollback");
648
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
649
+ const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
448
650
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
449
- throw deployFailedError("Failed to list app deployments", error, ["prisma app list-deploys"]);
651
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
450
652
  });
451
653
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
452
654
  const currentLiveDeployment = currentLiveDeploymentId ? deploymentsResult.deployments.find((deployment) => deployment.id === currentLiveDeploymentId) ?? null : null;
@@ -461,7 +663,7 @@ async function runAppRollback(context, appName, deploymentId) {
461
663
  deploymentId: targetDeployment.id,
462
664
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
463
665
  }).catch((error) => {
464
- throw deployFailedError("Failed to roll back deployment", error, ["prisma app list-deploys"]);
666
+ throw deployFailedError("Failed to roll back deployment", error, ["prisma-cli app list-deploys"]);
465
667
  });
466
668
  await context.stateStore.setKnownLiveDeployment(projectId, deploymentsResult.app.id, targetDeployment.id);
467
669
  return {
@@ -480,17 +682,16 @@ async function runAppRollback(context, appName, deploymentId) {
480
682
  previousLiveDeploymentId: currentLiveDeployment?.id ?? null
481
683
  },
482
684
  warnings: targetAlreadyLive ? ["The selected deployment is already live for this app."] : [],
483
- nextSteps: ["prisma app list-deploys", `prisma app show-deploy ${targetDeployment.id}`]
685
+ nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
484
686
  };
485
687
  }
486
- async function runAppRemove(context, appName) {
688
+ async function runAppRemove(context, appName, projectRef) {
487
689
  ensurePreviewAppMode(context);
488
- const projectId = await requireLinkedProjectId(context);
489
- const provider = await requirePreviewAppProvider(context);
490
- const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "remove");
690
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
691
+ const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
491
692
  await confirmAppRemoval(context, selectedApp);
492
693
  const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
493
- throw removeFailedError("Failed to remove app", error, ["prisma app show", "prisma app list-deploys"]);
694
+ throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
494
695
  });
495
696
  const warnings = await cleanupRemovedAppState(context, projectId, removedApp.id);
496
697
  return {
@@ -504,48 +705,122 @@ async function runAppRemove(context, appName) {
504
705
  removed: true
505
706
  },
506
707
  warnings,
507
- nextSteps: ["prisma app deploy", "prisma app list-deploys"]
708
+ nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
508
709
  };
509
710
  }
510
- async function resolveDeploySelection(context, projectId, apps, explicitAppName) {
511
- if (explicitAppName) {
512
- const matched = findAppByName(apps, explicitAppName);
711
+ async function resolveDeployAppSelection(context, projectId, apps, options) {
712
+ if (options.explicitAppName) {
713
+ const matches = findAppsByName(apps, options.explicitAppName);
714
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.explicitAppName, options.firstDeploy);
715
+ const matched = matches[0];
513
716
  if (matched) return {
514
717
  appId: matched.id,
515
- useInteractiveSelection: false
718
+ displayName: matched.name,
719
+ annotation: "set by --app",
720
+ firstDeploy: options.firstDeploy
516
721
  };
517
722
  return {
518
- appName: explicitAppName,
723
+ appName: options.explicitAppName,
519
724
  region: PREVIEW_DEFAULT_REGION,
520
- useInteractiveSelection: false
725
+ displayName: options.explicitAppName,
726
+ annotation: "set by --app",
727
+ firstDeploy: options.firstDeploy
521
728
  };
522
729
  }
523
- const savedSelection = await context.stateStore.readSelectedApp(projectId);
524
- if (savedSelection) {
525
- const matched = apps.find((app) => app.id === savedSelection.id) ?? findAppByName(apps, savedSelection.name);
526
- if (matched) return {
730
+ if (options.explicitAppId) {
731
+ const matched = apps.find((app) => app.id === options.explicitAppId);
732
+ if (!matched) throw usageError("Selected app does not exist in the resolved project", `The app "${options.explicitAppId}" from ${PRISMA_APP_ID_ENV_VAR} could not be found in resolved project "${projectId}".`, `Unset ${PRISMA_APP_ID_ENV_VAR}, pass --app <name>, or choose an app from prisma-cli app list-deploys.`, ["prisma-cli app list-deploys"], "app");
733
+ return {
527
734
  appId: matched.id,
528
- useInteractiveSelection: false
735
+ displayName: matched.name,
736
+ annotation: `from ${PRISMA_APP_ID_ENV_VAR}`,
737
+ firstDeploy: options.firstDeploy
529
738
  };
530
- if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the linked project.", "Pass --app <name>, or rerun prisma app deploy in a TTY to choose or create an app again.", ["prisma app deploy"], "app");
531
739
  }
532
- if (!canPrompt(context)) throw usageError("App deploy requires an app selection in non-interactive mode", "This command cannot choose or create an app in the current mode.", "Pass --app <name>, or rerun prisma app deploy in a TTY to choose or create an app.", ["prisma app deploy --app hello-world"], "app");
533
- return { useInteractiveSelection: true };
740
+ const inferredName = await options.inferName();
741
+ const matches = findAppsByName(apps, inferredName.name);
742
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
743
+ const matched = matches[0];
744
+ if (matched) return {
745
+ appId: matched.id,
746
+ displayName: matched.name,
747
+ annotation: "existing app on this branch",
748
+ firstDeploy: options.firstDeploy
749
+ };
750
+ return {
751
+ appName: inferredName.name,
752
+ region: PREVIEW_DEFAULT_REGION,
753
+ displayName: inferredName.name,
754
+ annotation: inferredName.source === "package-name" ? "created from package.json" : "created from directory name",
755
+ firstDeploy: options.firstDeploy
756
+ };
757
+ }
758
+ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDeploy) {
759
+ if (canPrompt(context)) {
760
+ const createNew = "__create_new_app__";
761
+ const cancel = "__cancel__";
762
+ const selected = await selectPrompt({
763
+ input: context.runtime.stdin,
764
+ output: context.runtime.stderr,
765
+ message: `Multiple apps are named "${targetName}"`,
766
+ choices: [
767
+ ...sortApps(matches).map((app) => ({
768
+ label: `${app.name} (${app.id})`,
769
+ value: app
770
+ })),
771
+ {
772
+ label: `Create a new app named "${targetName}"`,
773
+ value: createNew
774
+ },
775
+ {
776
+ label: "Cancel",
777
+ value: cancel
778
+ }
779
+ ]
780
+ });
781
+ if (selected === cancel) throw usageError("App selection canceled", "The command was canceled before an app was selected.", "Re-run the command and choose an app, or pass --app <name>.", ["prisma-cli app deploy --app <name>"], "app");
782
+ if (selected === createNew) return {
783
+ appName: targetName,
784
+ region: PREVIEW_DEFAULT_REGION,
785
+ displayName: targetName,
786
+ annotation: "created from package.json",
787
+ firstDeploy
788
+ };
789
+ return {
790
+ appId: selected.id,
791
+ displayName: selected.name,
792
+ annotation: "selected by you",
793
+ firstDeploy
794
+ };
795
+ }
796
+ throw new CliError({
797
+ code: "APP_AMBIGUOUS",
798
+ domain: "app",
799
+ summary: "App resolution is ambiguous",
800
+ why: `Multiple apps matched "${targetName}".`,
801
+ fix: "Pass --app <name> to choose the app explicitly.",
802
+ meta: { candidates: matches.map((app) => ({
803
+ id: app.id,
804
+ name: app.name
805
+ })) },
806
+ exitCode: 2,
807
+ nextSteps: ["prisma-cli app deploy --app <name>"]
808
+ });
534
809
  }
535
810
  async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
536
811
  if (explicitAppName) {
537
812
  const matched = findAppByName(apps, explicitAppName);
538
- if (!matched) throw usageError("Selected app does not exist in the linked project", `The app "${explicitAppName}" could not be found in linked project "${projectId}".`, "Pass the name of an existing app, or rerun prisma app list-deploys in a TTY to choose one.", ["prisma app list-deploys"], "app");
813
+ if (!matched) throw usageError("Selected app does not exist in the resolved project", `The app "${explicitAppName}" could not be found in resolved project "${projectId}".`, "Pass the name of an existing app, or rerun prisma-cli app list-deploys in a TTY to choose one.", ["prisma-cli app list-deploys"], "app");
539
814
  return matched;
540
815
  }
541
816
  const savedSelection = await context.stateStore.readSelectedApp(projectId);
542
817
  if (savedSelection) {
543
818
  const matched = apps.find((app) => app.id === savedSelection.id) ?? findAppByName(apps, savedSelection.name);
544
819
  if (matched) return matched;
545
- if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the linked project.", "Pass --app <name>, or rerun prisma app list-deploys in a TTY to choose an available app.", ["prisma app list-deploys"], "app");
820
+ if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the resolved project.", "Pass --app <name>, or rerun prisma-cli app list-deploys in a TTY to choose an available app.", ["prisma-cli app list-deploys"], "app");
546
821
  }
547
822
  if (apps.length === 0) return null;
548
- if (!canPrompt(context)) throw usageError("App selection required in non-interactive mode", "This command cannot choose an app in the current mode.", "Pass --app <name>, or rerun prisma app list-deploys in a TTY to choose an app.", ["prisma app list-deploys"], "app");
823
+ if (!canPrompt(context)) throw usageError("App selection required in non-interactive mode", "This command cannot choose an app in the current mode.", "Pass --app <name>, or rerun prisma-cli app list-deploys in a TTY to choose an app.", ["prisma-cli app list-deploys"], "app");
549
824
  const selectedId = await createSelectPromptPort(context).select({
550
825
  message: "Select an app",
551
826
  choices: sortApps(apps).map((app) => ({
@@ -558,7 +833,7 @@ async function resolveExistingAppSelection(context, projectId, apps, explicitApp
558
833
  async function requireReleaseAppSelection(context, projectId, apps, explicitAppName, commandName) {
559
834
  const selectedApp = await resolveExistingAppSelection(context, projectId, apps, explicitAppName);
560
835
  if (selectedApp) return selectedApp;
561
- throw usageError(`App ${commandName} requires an existing app`, "The linked project does not have an app that can be selected for this command.", `Deploy an app first, or rerun prisma app ${commandName} with --app <name> after an app exists.`, ["prisma app deploy", "prisma app list-deploys"], "app");
836
+ throw usageError(`App ${commandName} requires an existing app`, "The resolved project does not have an app that can be selected for this command.", `Deploy an app first, or rerun prisma-cli app ${commandName} with --app <name> after an app exists.`, ["prisma-cli app deploy", "prisma-cli app list-deploys"], "app");
562
837
  }
563
838
  async function confirmAppRemoval(context, app) {
564
839
  if (context.flags.yes) return;
@@ -567,9 +842,9 @@ async function confirmAppRemoval(context, app) {
567
842
  domain: "app",
568
843
  summary: "App remove requires confirmation in the current mode",
569
844
  why: "This command is destructive and cannot prompt for confirmation in the current mode.",
570
- fix: `Pass --yes to confirm removal of "${app.name}", or rerun prisma app remove in an interactive TTY.`,
845
+ fix: `Pass --yes to confirm removal of "${app.name}", or rerun prisma-cli app remove in an interactive TTY.`,
571
846
  exitCode: 1,
572
- nextSteps: [`prisma app remove --app ${app.name} --yes`]
847
+ nextSteps: [`prisma-cli app remove --app ${app.name} --yes`]
573
848
  });
574
849
  await textPrompt({
575
850
  input: context.runtime.stdin,
@@ -601,9 +876,9 @@ function requireDeploymentForApp(deployments, deploymentId, appName) {
601
876
  domain: "app",
602
877
  summary: `Deployment "${deploymentId}" not found for app "${appName}"`,
603
878
  why: "The requested deployment does not belong to the resolved app or is no longer available.",
604
- fix: "Run prisma app list-deploys to choose an available deployment id for this app.",
879
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id for this app.",
605
880
  exitCode: 1,
606
- nextSteps: ["prisma app list-deploys"]
881
+ nextSteps: ["prisma-cli app list-deploys"]
607
882
  });
608
883
  }
609
884
  async function resolveCurrentLiveDeploymentId(context, projectId, app, deployments) {
@@ -616,10 +891,10 @@ async function resolveCurrentLiveDeploymentId(context, projectId, app, deploymen
616
891
  }
617
892
  function buildAppShowNextSteps(liveUrl, liveDeployment, deployments) {
618
893
  const nextSteps = [];
619
- if (liveUrl) nextSteps.push("prisma app open");
620
- if (liveDeployment) nextSteps.push(`prisma app show-deploy ${liveDeployment.id}`);
621
- else if (deployments[0]) nextSteps.push(`prisma app show-deploy ${deployments[0].id}`);
622
- else nextSteps.push("prisma app deploy");
894
+ if (liveUrl) nextSteps.push("prisma-cli app open");
895
+ if (liveDeployment) nextSteps.push(`prisma-cli app show-deploy ${liveDeployment.id}`);
896
+ else if (deployments[0]) nextSteps.push(`prisma-cli app show-deploy ${deployments[0].id}`);
897
+ else nextSteps.push("prisma-cli app deploy");
623
898
  return nextSteps;
624
899
  }
625
900
  function applyLiveDeploymentHint(deployments, currentLiveDeploymentId) {
@@ -640,63 +915,503 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
640
915
  domain: "app",
641
916
  summary: "No previous deployment available for rollback",
642
917
  why: "The selected app does not have an earlier deployment to switch back to.",
643
- fix: "Deploy a second version first, or rerun prisma app rollback --to <deployment-id> for a specific earlier deployment.",
918
+ fix: "Deploy a second version first, or rerun prisma-cli app rollback --to <deployment-id> for a specific earlier deployment.",
644
919
  exitCode: 1,
645
- nextSteps: ["prisma app deploy", "prisma app list-deploys"]
920
+ nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
646
921
  });
647
922
  }
648
- async function listApps(context, provider, projectId) {
649
- return provider.listApps(projectId).then(sortApps).catch((error) => {
650
- if (isMissingProjectError(error)) throw projectNotFoundError(`The linked project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`, "Run prisma project show to inspect the current link, then relink the repo or rerun prisma app deploy to bootstrap a new project.", [
651
- "prisma project show",
652
- "prisma project link",
653
- "prisma app deploy"
654
- ]);
655
- throw deployFailedError("Failed to list apps", error, ["prisma project show"]);
923
+ async function listApps(context, provider, projectId, branchName) {
924
+ return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
925
+ if (isMissingProjectError(error)) throw new CliError({
926
+ code: "PROJECT_NOT_FOUND",
927
+ domain: "project",
928
+ summary: "Project not found",
929
+ why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
930
+ fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect resolution for this directory.",
931
+ exitCode: 1,
932
+ nextSteps: ["prisma-cli project show", "prisma-cli app deploy --project <id-or-name>"]
933
+ });
934
+ throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
656
935
  });
657
936
  }
658
937
  async function requirePreviewAppProvider(context) {
938
+ const { provider } = await requirePreviewAppProviderWithClient(context);
939
+ return provider;
940
+ }
941
+ async function requirePreviewAppProviderWithClient(context) {
659
942
  const client = await requireComputeAuth(context.runtime.env);
660
- if (!client) throw authRequiredError(["prisma auth login"]);
661
- return createPreviewAppProvider(client);
943
+ if (!client) throw authRequiredError(["prisma-cli auth login"]);
944
+ return {
945
+ client,
946
+ provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
947
+ };
662
948
  }
663
- async function requireLinkedProjectId(context) {
664
- const projectId = await readLinkedProjectId(context.runtime.cwd);
665
- if (!projectId) throw new CliError({
666
- code: "PROJECT_NOT_LINKED",
667
- domain: "project",
668
- summary: "Project link required",
669
- why: "This command needs a linked project for the current repo.",
670
- fix: "Run prisma project link before deploying or inspecting app deployments.",
671
- exitCode: 1,
672
- nextSteps: ["prisma project link"]
673
- });
674
- return projectId;
949
+ function createPreviewLogAuthOptions(env) {
950
+ const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
951
+ if (rawToken) return {
952
+ baseUrl: getApiBaseUrl(env),
953
+ getToken: async () => rawToken
954
+ };
955
+ const tokenStorage = new FileTokenStorage(env);
956
+ return {
957
+ baseUrl: getApiBaseUrl(env),
958
+ getToken: async () => {
959
+ const tokens = await tokenStorage.getTokens();
960
+ if (!tokens) throw new Error("Authentication token is no longer available. Run prisma-cli auth login and try again.");
961
+ return tokens.accessToken;
962
+ }
963
+ };
964
+ }
965
+ async function requireProviderAndProjectContext(context, explicitProject, options) {
966
+ const { client, provider } = await requirePreviewAppProviderWithClient(context);
967
+ const target = await resolveProjectContext(context, client, provider, explicitProject, options);
968
+ return {
969
+ client,
970
+ provider,
971
+ target,
972
+ projectId: target.project.id
973
+ };
974
+ }
975
+ async function requireProviderAndDeployProjectContext(context, explicitProject, options) {
976
+ const { client, provider } = await requirePreviewAppProviderWithClient(context);
977
+ const target = await resolveDeployProjectContext(context, client, provider, explicitProject, options);
978
+ return {
979
+ client,
980
+ provider,
981
+ target,
982
+ projectId: target.project.id
983
+ };
675
984
  }
676
- async function resolveProjectIdForDeploy(context, provider) {
677
- const linkedProjectId = await readLinkedProjectId(context.runtime.cwd);
678
- if (linkedProjectId) return linkedProjectId;
679
- await assertProjectLinkWritableForDeploy(context);
680
- const projectName = path.basename(context.runtime.cwd);
681
- const project = await provider.createProject({ name: projectName }).catch((error) => {
682
- throw deployFailedError("Failed to create project for first deploy", error, ["prisma app deploy"]);
985
+ async function resolveProjectContext(context, client, provider, explicitProject, options) {
986
+ const authState = await requireAuthenticatedAuthState(context);
987
+ if (!authState.workspace) throw workspaceRequiredError();
988
+ const resolved = await resolveProjectTarget({
989
+ context,
990
+ workspace: authState.workspace,
991
+ explicitProject,
992
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
993
+ createProject: options?.allowCreate ? async (name) => {
994
+ const project = await provider.createProject({ name }).catch((error) => {
995
+ throw createProjectOnFirstDeployError({
996
+ error,
997
+ inferredName: name,
998
+ workspaceName: authState.workspace.name
999
+ });
1000
+ });
1001
+ return {
1002
+ id: project.id,
1003
+ name: project.name,
1004
+ workspace: authState.workspace
1005
+ };
1006
+ } : void 0,
1007
+ allowCreate: options?.allowCreate,
1008
+ prompt: createSelectPromptPort(context),
1009
+ remember: true
683
1010
  });
1011
+ const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
1012
+ return {
1013
+ ...resolved,
1014
+ branch: {
1015
+ name: branch.name,
1016
+ kind: toBranchKind(branch.name)
1017
+ }
1018
+ };
1019
+ }
1020
+ async function resolveDeployProjectContext(context, client, provider, explicitProject, options) {
1021
+ const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1022
+ if (!workspace) throw workspaceRequiredError();
1023
+ const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1024
+ const projects = await listRealWorkspaceProjects(client, workspace);
1025
+ const createProject = options.allowCreate ? async (name) => {
1026
+ const project = await provider.createProject({ name }).catch((error) => {
1027
+ throw createProjectOnFirstDeployError({
1028
+ error,
1029
+ inferredName: name,
1030
+ workspaceName: workspace.name
1031
+ });
1032
+ });
1033
+ return {
1034
+ id: project.id,
1035
+ name: project.name,
1036
+ workspace
1037
+ };
1038
+ } : void 0;
1039
+ if (explicitProject) return withDeployBranch(await resolveProjectTarget({
1040
+ context,
1041
+ workspace,
1042
+ explicitProject,
1043
+ listProjects: async () => projects,
1044
+ createProject,
1045
+ allowCreate: options.allowCreate,
1046
+ prompt: createSelectPromptPort(context),
1047
+ remember: true
1048
+ }), branch);
1049
+ if (options.envProjectId) {
1050
+ const project = projects.find((candidate) => candidate.id === options.envProjectId);
1051
+ if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1052
+ return withDeployBranch({
1053
+ workspace,
1054
+ project: toProjectSummary(project),
1055
+ resolution: {
1056
+ projectSource: "env",
1057
+ targetName: options.envProjectId,
1058
+ targetNameSource: "env"
1059
+ }
1060
+ }, branch);
1061
+ }
1062
+ const localPin = options.localPin;
1063
+ if (localPin.kind === "present") {
1064
+ if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1065
+ const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1066
+ if (!project) throw localResolutionPinStaleError();
1067
+ return withDeployBranch({
1068
+ workspace,
1069
+ project: toProjectSummary(project),
1070
+ resolution: {
1071
+ projectSource: "local-pin",
1072
+ targetName: project.name,
1073
+ targetNameSource: "local-pin"
1074
+ }
1075
+ }, branch);
1076
+ }
1077
+ return withDeployBranch(await resolveProjectTarget({
1078
+ context,
1079
+ workspace,
1080
+ listProjects: async () => projects,
1081
+ createProject,
1082
+ allowCreate: options.allowCreate,
1083
+ prompt: createSelectPromptPort(context),
1084
+ remember: true
1085
+ }), branch);
1086
+ }
1087
+ function withDeployBranch(target, branch) {
1088
+ return {
1089
+ ...target,
1090
+ branch: {
1091
+ name: branch.name,
1092
+ kind: toBranchKind(branch.name)
1093
+ }
1094
+ };
1095
+ }
1096
+ function toProjectSummary(project) {
1097
+ return {
1098
+ id: project.id,
1099
+ name: project.name
1100
+ };
1101
+ }
1102
+ function toBranchKind(name) {
1103
+ return name === "production" || name === "main" ? "production" : "preview";
1104
+ }
1105
+ async function resolveDeployBranch(context, explicitBranchName) {
1106
+ if (explicitBranchName) return {
1107
+ name: explicitBranchName,
1108
+ annotation: "set by --branch"
1109
+ };
1110
+ const gitBranch = await readLocalGitBranch(context.runtime.cwd);
1111
+ if (gitBranch) return {
1112
+ name: gitBranch,
1113
+ annotation: "from local Git branch"
1114
+ };
1115
+ return {
1116
+ name: "main",
1117
+ annotation: "default"
1118
+ };
1119
+ }
1120
+ async function readLocalGitBranch(cwd) {
1121
+ const headPath = await resolveGitHeadPath(path.join(cwd, ".git"));
1122
+ if (!headPath) return null;
684
1123
  try {
685
- await writeLinkedProjectId(context.runtime.cwd, project.id);
686
- } catch (error) {
687
- const cause = error instanceof Error ? error.message : String(error);
688
- throw deployFailedError("Failed to link created project", `Project "${project.name}" (${project.id}) was created remotely but could not be linked locally: ${cause}`, ["prisma project show", "prisma app deploy"]);
1124
+ const head = (await readFile(headPath, "utf8")).trim();
1125
+ if (head.startsWith("ref: refs/heads/")) return head.slice(16);
1126
+ } catch {
1127
+ return null;
689
1128
  }
690
- return project.id;
1129
+ return null;
691
1130
  }
692
- async function assertProjectLinkWritableForDeploy(context) {
1131
+ async function resolveGitHeadPath(gitPath) {
1132
+ try {
1133
+ const raw = await readFile(gitPath, "utf8");
1134
+ if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
1135
+ } catch {}
693
1136
  try {
694
- await assertLinkedProjectIdWritable(context.runtime.cwd);
1137
+ await access(path.join(gitPath, "HEAD"));
1138
+ return path.join(gitPath, "HEAD");
1139
+ } catch {
1140
+ return null;
1141
+ }
1142
+ }
1143
+ async function resolveDeployFramework(context, options) {
1144
+ if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1145
+ if (options.explicitBuildType) {
1146
+ const buildType = normalizeBuildType(options.requestedBuildType);
1147
+ if (buildType !== "auto") return {
1148
+ key: buildType,
1149
+ buildType,
1150
+ displayName: formatBuildTypeName(buildType),
1151
+ annotation: "set by --build-type"
1152
+ };
1153
+ }
1154
+ const detected = await detectDeployFramework(context.runtime.cwd);
1155
+ if (detected) return detected;
1156
+ throw frameworkNotDetectedError(context.runtime.cwd);
1157
+ }
1158
+ function resolveDeployRuntime(requestedHttpPort, framework) {
1159
+ if (requestedHttpPort) return {
1160
+ port: parseDeployHttpPort(requestedHttpPort),
1161
+ annotation: "set by --http-port"
1162
+ };
1163
+ return {
1164
+ port: FRAMEWORK_DEFAULT_HTTP_PORT,
1165
+ annotation: `${framework.displayName} default`
1166
+ };
1167
+ }
1168
+ async function detectDeployFramework(cwd) {
1169
+ const packageJson = await readBunPackageJson(cwd);
1170
+ const nextConfig = await detectNextConfig(cwd);
1171
+ if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
1172
+ key: "nextjs",
1173
+ buildType: "nextjs",
1174
+ displayName: "Next.js",
1175
+ annotation: nextConfig.standalone ? "standalone output detected" : nextConfig.exists ? "detected from next.config" : "detected from package.json"
1176
+ };
1177
+ if (hasPackageDependency(packageJson, "hono")) return {
1178
+ key: "hono",
1179
+ buildType: "bun",
1180
+ displayName: "Hono",
1181
+ annotation: "detected from package.json"
1182
+ };
1183
+ if (hasPackageDependency(packageJson, "@tanstack/start")) return {
1184
+ key: "tanstack-start",
1185
+ buildType: "tanstack-start",
1186
+ displayName: "TanStack Start",
1187
+ annotation: "detected from package.json"
1188
+ };
1189
+ return null;
1190
+ }
1191
+ async function detectNextConfig(cwd) {
1192
+ for (const candidate of [
1193
+ "next.config.js",
1194
+ "next.config.mjs",
1195
+ "next.config.cjs",
1196
+ "next.config.ts"
1197
+ ]) {
1198
+ const filePath = path.join(cwd, candidate);
1199
+ try {
1200
+ const content = await readFile(filePath, "utf8");
1201
+ return {
1202
+ exists: true,
1203
+ standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
1204
+ };
1205
+ } catch (error) {
1206
+ if (error.code !== "ENOENT") throw error;
1207
+ }
1208
+ }
1209
+ return {
1210
+ exists: false,
1211
+ standalone: false
1212
+ };
1213
+ }
1214
+ function hasPackageDependency(packageJson, dependencyName) {
1215
+ return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
1216
+ }
1217
+ function hasDependency(dependencies, dependencyName) {
1218
+ return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1219
+ }
1220
+ function frameworkFromUserFacingValue(value, annotation) {
1221
+ switch (value.trim().toLowerCase()) {
1222
+ case "next":
1223
+ case "next.js":
1224
+ case "nextjs": return {
1225
+ key: "nextjs",
1226
+ buildType: "nextjs",
1227
+ displayName: "Next.js",
1228
+ annotation
1229
+ };
1230
+ case "hono": return {
1231
+ key: "hono",
1232
+ buildType: "bun",
1233
+ displayName: "Hono",
1234
+ annotation
1235
+ };
1236
+ case "tanstack":
1237
+ case "tanstack-start":
1238
+ case "@tanstack/start": return {
1239
+ key: "tanstack-start",
1240
+ buildType: "tanstack-start",
1241
+ displayName: "TanStack Start",
1242
+ annotation
1243
+ };
1244
+ default: throw frameworkNotDetectedError(void 0, value);
1245
+ }
1246
+ }
1247
+ function frameworkNotDetectedError(cwd, requestedFramework) {
1248
+ const supported = "Next.js, Hono, TanStack Start";
1249
+ const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
1250
+ return new CliError({
1251
+ code: "FRAMEWORK_NOT_DETECTED",
1252
+ domain: "app",
1253
+ summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
1254
+ why: `Supported Beta frameworks: ${supported}.`,
1255
+ fix: "Add one of these frameworks as a dependency, or pass --framework <nextjs|hono|tanstack-start>.",
1256
+ exitCode: 2,
1257
+ nextSteps: [
1258
+ "prisma-cli app deploy --framework nextjs",
1259
+ "prisma-cli app deploy --framework hono",
1260
+ "prisma-cli app deploy --framework tanstack-start"
1261
+ ]
1262
+ });
1263
+ }
1264
+ async function maybeRenderDeploySetupBlock(context, details) {
1265
+ if (context.flags.json || context.flags.quiet) return;
1266
+ const directory = formatDeployDirectory(context.runtime.cwd);
1267
+ if (!details.firstDeploy) {
1268
+ context.output.stderr.write(`Deploying ${directory} to ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1269
+ return;
1270
+ }
1271
+ const title = `Setting up your local directory ${formatLocalDirectory(context.runtime.cwd, context.runtime.env)}`;
1272
+ const rows = details.firstDeploy ? [
1273
+ {
1274
+ label: "Workspace",
1275
+ value: details.workspaceName
1276
+ },
1277
+ {
1278
+ label: "Project",
1279
+ value: details.projectName,
1280
+ origin: details.projectAnnotation
1281
+ },
1282
+ {
1283
+ label: "Branch",
1284
+ value: details.branchName,
1285
+ origin: details.branchAnnotation
1286
+ },
1287
+ {
1288
+ label: "App",
1289
+ value: details.appName,
1290
+ origin: details.appAnnotation
1291
+ },
1292
+ {
1293
+ label: "Framework",
1294
+ value: details.framework.displayName,
1295
+ origin: details.framework.annotation
1296
+ },
1297
+ {
1298
+ label: "Runtime",
1299
+ value: `HTTP ${details.runtime.port}`,
1300
+ origin: details.runtime.annotation
1301
+ }
1302
+ ] : [];
1303
+ const lines = [
1304
+ title,
1305
+ "",
1306
+ ...renderDeployOutputRows(context.ui, rows),
1307
+ ""
1308
+ ];
1309
+ context.output.stderr.write(`${lines.join("\n")}\n`);
1310
+ }
1311
+ function maybeRenderLocalPinBound(context, projectName) {
1312
+ if (context.flags.json || context.flags.quiet) return;
1313
+ context.output.stderr.write(`This directory is now linked to project ${projectName}.\n\n`);
1314
+ }
1315
+ async function maybeCustomizeDeploySettings(context, options) {
1316
+ if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitBuildType || options.explicitHttpPort || !canPrompt(context)) return {
1317
+ framework: options.framework,
1318
+ runtime: options.runtime
1319
+ };
1320
+ if (!await confirmPrompt({
1321
+ input: context.runtime.stdin,
1322
+ output: context.runtime.stderr,
1323
+ message: "Customize settings?",
1324
+ initialValue: false
1325
+ })) return {
1326
+ framework: options.framework,
1327
+ runtime: options.runtime
1328
+ };
1329
+ const framework = frameworkFromUserFacingValue(await selectPrompt({
1330
+ input: context.runtime.stdin,
1331
+ output: context.runtime.stderr,
1332
+ message: `Framework (${options.framework.displayName})`,
1333
+ choices: DEPLOY_FRAMEWORKS.map((framework) => ({
1334
+ label: frameworkDisplayName(framework),
1335
+ value: framework
1336
+ }))
1337
+ }), "set by you");
1338
+ const requestedPort = await textPrompt({
1339
+ input: context.runtime.stdin,
1340
+ output: context.runtime.stderr,
1341
+ message: `HTTP port (${options.runtime.port})`,
1342
+ placeholder: String(options.runtime.port),
1343
+ validate: validateDeployHttpPortText
1344
+ });
1345
+ const runtime = {
1346
+ port: requestedPort.trim() ? parseDeployHttpPort(requestedPort) : options.runtime.port,
1347
+ annotation: "set by you"
1348
+ };
1349
+ const changedRows = [framework.key !== options.framework.key ? {
1350
+ label: "Framework",
1351
+ value: framework.displayName,
1352
+ annotation: framework.annotation
1353
+ } : null, runtime.port !== options.runtime.port ? {
1354
+ label: "Runtime",
1355
+ value: `HTTP ${runtime.port}`,
1356
+ annotation: runtime.annotation
1357
+ } : null].filter((row) => Boolean(row));
1358
+ if (changedRows.length > 0 && !context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderDeployOutputRows(context.ui, changedRows.map((row) => ({
1359
+ label: row.label,
1360
+ value: row.value,
1361
+ origin: row.annotation
1362
+ }))).join("\n")}\n\n`);
1363
+ return {
1364
+ framework,
1365
+ runtime
1366
+ };
1367
+ }
1368
+ function annotationForProjectResolution(resolution) {
1369
+ switch (resolution.projectSource) {
1370
+ case "explicit": return "set by --project";
1371
+ case "env": return `from ${PRISMA_PROJECT_ID_ENV_VAR}`;
1372
+ case "local-pin": return "from local pin";
1373
+ case "created": return resolution.targetNameSource === "directory-name" ? "created from directory name" : "created from package.json";
1374
+ case "package-name":
1375
+ case "directory-name": return "linked to existing project";
1376
+ case "platform-mapping":
1377
+ case "remembered-local": return "linked to existing project";
1378
+ case "prompt": return "selected by you";
1379
+ }
1380
+ }
1381
+ function frameworkDisplayName(framework) {
1382
+ switch (framework) {
1383
+ case "nextjs": return "Next.js";
1384
+ case "hono": return "Hono";
1385
+ case "tanstack-start": return "TanStack Start";
1386
+ }
1387
+ }
1388
+ function validateDeployHttpPortText(value) {
1389
+ if (!value?.trim()) return;
1390
+ try {
1391
+ parseDeployHttpPort(value);
1392
+ return;
695
1393
  } catch (error) {
696
- if (error instanceof UnsafeConfigWriteError) throw usageError("Project bootstrap requires a writable Prisma config", error.message, "Update prisma.config.ts to use a recognizable project field, or remove it and rerun prisma app deploy.", ["prisma app deploy --app hello-world"], "app");
697
- throw error;
1394
+ return error instanceof CliError ? error.summary : String(error);
698
1395
  }
699
1396
  }
1397
+ function formatDeployDirectory(cwd) {
1398
+ const basename = path.basename(cwd);
1399
+ return basename ? `./${basename}` : ".";
1400
+ }
1401
+ function formatLocalDirectory(cwd, env) {
1402
+ const resolved = path.resolve(cwd);
1403
+ const home = env.HOME ? path.resolve(env.HOME) : null;
1404
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
1405
+ const relative = path.relative(home, resolved);
1406
+ return relative ? `~/${relative}` : "~";
1407
+ }
1408
+ return resolved;
1409
+ }
1410
+ async function readCurrentWorkspaceId(context) {
1411
+ const state = await context.stateStore.read();
1412
+ if (state.auth?.workspaceId) return state.auth.workspaceId;
1413
+ return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
1414
+ }
700
1415
  function normalizeBuildType(requestedBuildType) {
701
1416
  if (!requestedBuildType) return "auto";
702
1417
  if (isPreviewBuildType(requestedBuildType)) return requestedBuildType;
@@ -707,35 +1422,35 @@ function isPreviewBuildType(value) {
707
1422
  }
708
1423
  function getBuildTypeExamples(commandName) {
709
1424
  return RESOLVED_PREVIEW_BUILD_TYPES.map((buildType) => {
710
- return `prisma app ${commandName} --build-type ${buildType}${buildType === "bun" ? " --entry server.ts" : ""}`;
1425
+ return `prisma-cli app ${commandName} --build-type ${buildType}${buildType === "bun" ? " --entry server.ts" : ""}`;
711
1426
  });
712
1427
  }
713
1428
  function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
714
- if (buildType !== "auto" && buildType !== "bun" && entrypoint) 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 app ${commandName} with --build-type bun when you want to target a Bun entrypoint directly.`, [`prisma app ${commandName} --build-type ${buildType}`, `prisma app ${commandName} --build-type bun --entry server.ts`], "app");
1429
+ if (buildType !== "auto" && buildType !== "bun" && entrypoint) 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");
715
1430
  }
716
1431
  async function requireLocalBuildType(context, buildType, commandName) {
717
1432
  const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
718
1433
  if (resolvedBuildType) return resolvedBuildType;
719
- 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 app ${commandName} --build-type nextjs`, `prisma app ${commandName} --build-type bun --entry server.ts`], "app");
1434
+ 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");
720
1435
  }
721
1436
  function parseLocalPort(requestedPort) {
722
1437
  if (!requestedPort) return DEFAULT_LOCAL_DEV_PORT;
723
1438
  const port = Number.parseInt(requestedPort, 10);
724
- if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid port "${requestedPort}"`, "Port must be an integer between 1 and 65535.", "Pass --port <number> with a valid local port value.", ["prisma app run --port 3000"], "app");
1439
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid port "${requestedPort}"`, "Port must be an integer between 1 and 65535.", "Pass --port <number> with a valid local port value.", ["prisma-cli app run --port 3000"], "app");
725
1440
  return port;
726
1441
  }
727
1442
  function parseDeployPortMapping(requestedPort) {
728
1443
  if (!requestedPort) return;
1444
+ return { http: parseDeployHttpPort(requestedPort) };
1445
+ }
1446
+ function parseDeployHttpPort(requestedPort) {
729
1447
  const port = Number.parseInt(requestedPort, 10);
730
- if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid HTTP port "${requestedPort}"`, "HTTP port must be an integer between 1 and 65535.", "Pass --http-port <number> with a valid port value.", ["prisma app deploy --http-port 3000"], "app");
731
- return { http: port };
1448
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid HTTP port "${requestedPort}"`, "HTTP port must be an integer between 1 and 65535.", "Pass --http-port <number> with a valid port value.", ["prisma-cli app deploy --http-port 3000"], "app");
1449
+ return port;
732
1450
  }
733
1451
  function ensurePreviewAppMode(context) {
734
1452
  if (isRealMode(context)) return;
735
- throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma auth login", "prisma project link"], "app");
736
- }
737
- function blockedPreviewAppCommandError(summary, why) {
738
- return featureUnavailableError(summary, why, "Use prisma app show, prisma app open, prisma app deploy, or prisma app list-deploys in the current preview.", ["prisma app show", "prisma app list-deploys"], "app");
1453
+ throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project show"], "app");
739
1454
  }
740
1455
  function deployFailedError(summary, error, nextSteps) {
741
1456
  return new CliError({
@@ -749,15 +1464,138 @@ function deployFailedError(summary, error, nextSteps) {
749
1464
  nextSteps
750
1465
  });
751
1466
  }
1467
+ function appDeployFailedError(error, progress) {
1468
+ const why = error instanceof Error ? error.message : String(error);
1469
+ const debug = formatDebugDetails(error);
1470
+ if (progress.buildStarted && !progress.buildCompleted) return new CliError({
1471
+ code: "BUILD_FAILED",
1472
+ domain: "app",
1473
+ summary: "Build failed locally.",
1474
+ why,
1475
+ fix: "Inspect the build output above, fix the error, and redeploy.",
1476
+ debug,
1477
+ meta: { phase: "build" },
1478
+ humanLines: [
1479
+ "Build failed locally.",
1480
+ "",
1481
+ `✗ Built ${why}`,
1482
+ "",
1483
+ "Fix: Inspect the build output above, fix the error, and redeploy."
1484
+ ],
1485
+ exitCode: 1,
1486
+ nextSteps: []
1487
+ });
1488
+ if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
1489
+ const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
1490
+ 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."];
1491
+ const urlLines = progress.deploymentUrl ? [
1492
+ "",
1493
+ "URL",
1494
+ progress.deploymentUrl
1495
+ ] : [];
1496
+ const humanLines = progress.containerLive ? [
1497
+ phaseHeadline,
1498
+ "",
1499
+ "This is usually a missing env var, a failed DB connection,",
1500
+ "or a crash on startup.",
1501
+ "",
1502
+ ...recoveryLines,
1503
+ ...urlLines
1504
+ ] : [
1505
+ phaseHeadline,
1506
+ "",
1507
+ progress.uploadCompleted ? "The artifact uploaded, but the deployment did not start." : progress.archiveReady ? "The app built locally, but the artifact did not finish uploading." : "The app built locally, but the deployment did not start.",
1508
+ "",
1509
+ ...recoveryLines
1510
+ ];
1511
+ return new CliError({
1512
+ code: "DEPLOY_FAILED",
1513
+ domain: "app",
1514
+ summary: phaseHeadline,
1515
+ why,
1516
+ fix: progress.versionId ? `Inspect logs with prisma-cli app logs --deployment ${progress.versionId}.` : "Retry the command, or rerun with --trace for more detailed diagnostics.",
1517
+ debug,
1518
+ meta: {
1519
+ phase: progress.containerLive ? "runtime_ready" : "deploy",
1520
+ deploymentId: progress.versionId,
1521
+ deploymentUrl: progress.deploymentUrl
1522
+ },
1523
+ humanLines,
1524
+ exitCode: 1,
1525
+ nextSteps: []
1526
+ });
1527
+ }
1528
+ function localResolutionPinStaleError() {
1529
+ return new CliError({
1530
+ code: "LOCAL_STATE_STALE",
1531
+ domain: "project",
1532
+ summary: "Local project binding is stale",
1533
+ why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
1534
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} and re-run to re-bootstrap.`,
1535
+ meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
1536
+ exitCode: 1,
1537
+ nextSteps: ["prisma-cli app deploy"]
1538
+ });
1539
+ }
1540
+ function readDeployEnvOverride(context, name) {
1541
+ const value = context.runtime.env[name]?.trim();
1542
+ return value ? value : void 0;
1543
+ }
1544
+ /**
1545
+ * `app deploy` falls into "create a new project on first deploy" when no
1546
+ * existing project matches the package.json name (or the cwd basename as a
1547
+ * fallback). When the create call fails the user often doesn't realise the
1548
+ * CLI was attempting to create a project at all — they thought the deploy
1549
+ * would find an existing project. Surface that context, and recommend the
1550
+ * explicit `--project` flag as the unambiguous way out.
1551
+ */
1552
+ function createProjectOnFirstDeployError(options) {
1553
+ const { error, inferredName, workspaceName } = options;
1554
+ const status = extractHttpStatus(error);
1555
+ const errorMessage = error instanceof Error ? error.message : String(error);
1556
+ const inferredContext = `No existing project matched the package.json name \`${inferredName}\`, so the CLI attempted to create one.`;
1557
+ const nextSteps = ["prisma-cli project list", "prisma-cli app deploy --project <id-or-name>"];
1558
+ if (status === 401 || status === 403) return new CliError({
1559
+ code: "AUTH_FORBIDDEN",
1560
+ domain: "auth",
1561
+ summary: "Could not create a new project for this deploy",
1562
+ why: `${inferredContext} The platform rejected the create (HTTP ${status}).`,
1563
+ fix: `Pass --project <id-or-name> to deploy into an existing project, or grant the service token project-create permission on workspace \`${workspaceName}\`.`,
1564
+ debug: formatDebugDetails(error),
1565
+ exitCode: 1,
1566
+ nextSteps
1567
+ });
1568
+ return new CliError({
1569
+ code: "DEPLOY_FAILED",
1570
+ domain: "app",
1571
+ summary: "Could not create a new project for this deploy",
1572
+ why: `${inferredContext} ${errorMessage}`.trim(),
1573
+ fix: "Pass --project <id-or-name> to deploy into an existing project, or retry after addressing the platform error above.",
1574
+ debug: formatDebugDetails(error),
1575
+ exitCode: 1,
1576
+ nextSteps
1577
+ });
1578
+ }
1579
+ function extractHttpStatus(error) {
1580
+ if (!error || typeof error !== "object") return null;
1581
+ const candidate = error;
1582
+ if (typeof candidate.statusCode === "number") return candidate.statusCode;
1583
+ if (typeof candidate.status === "number") return candidate.status;
1584
+ if (typeof candidate.message === "string") {
1585
+ const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
1586
+ if (match) return Number.parseInt(match[1], 10);
1587
+ }
1588
+ return null;
1589
+ }
752
1590
  function noDeploymentsError(summary, why) {
753
1591
  return new CliError({
754
1592
  code: "NO_DEPLOYMENTS",
755
1593
  domain: "app",
756
1594
  summary,
757
1595
  why,
758
- fix: "Run prisma app deploy first, or use prisma app show to inspect the current app state.",
1596
+ fix: "Run prisma-cli app deploy first, or use prisma-cli app show to inspect the current app state.",
759
1597
  exitCode: 1,
760
- nextSteps: ["prisma app deploy", "prisma app show"]
1598
+ nextSteps: ["prisma-cli app deploy", "prisma-cli app show"]
761
1599
  });
762
1600
  }
763
1601
  function buildFailedError(summary, error) {
@@ -766,10 +1604,10 @@ function buildFailedError(summary, error) {
766
1604
  domain: "app",
767
1605
  summary,
768
1606
  why: error instanceof Error ? error.message : String(error),
769
- fix: "Inspect the framework output, fix the build issue, and rerun prisma app build.",
1607
+ fix: "Inspect the framework output, fix the build issue, and rerun prisma-cli app build.",
770
1608
  debug: formatDebugDetails(error),
771
1609
  exitCode: 1,
772
- nextSteps: ["prisma app build", "prisma app deploy"]
1610
+ nextSteps: ["prisma-cli app build", "prisma-cli app deploy"]
773
1611
  });
774
1612
  }
775
1613
  function runFailedError(summary, error, exitCode = 1) {
@@ -778,9 +1616,9 @@ function runFailedError(summary, error, exitCode = 1) {
778
1616
  domain: "app",
779
1617
  summary,
780
1618
  why: error instanceof Error ? error.message : String(error),
781
- fix: "Inspect the framework output above, fix the issue, and rerun prisma app run.",
1619
+ fix: "Inspect the framework output above, fix the issue, and rerun prisma-cli app run.",
782
1620
  exitCode,
783
- nextSteps: ["prisma app run"]
1621
+ nextSteps: ["prisma-cli app run"]
784
1622
  });
785
1623
  }
786
1624
  function formatFrameworkName(framework) {
@@ -824,11 +1662,30 @@ function isMissingProjectError(error) {
824
1662
  function findAppByName(apps, name) {
825
1663
  return apps.find((app) => app.name === name);
826
1664
  }
1665
+ function findAppsByName(apps, name) {
1666
+ return apps.filter((app) => app.name === name);
1667
+ }
827
1668
  function sortApps(apps) {
828
1669
  return apps.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
829
1670
  }
830
1671
  function toOptionalEnvVars(envVars) {
831
1672
  return Object.keys(envVars).length > 0 ? envVars : void 0;
832
1673
  }
1674
+ /**
1675
+ * Emits a deprecation banner to stderr when the legacy single-shot
1676
+ * env-var commands are invoked. The banner is suppressed in --json
1677
+ * mode so machine consumers keep their JSON channel clean; --json
1678
+ * users discover the deprecation via release notes and the new
1679
+ * `prisma-cli project env` namespace's output anyway.
1680
+ *
1681
+ * Removal of these legacy commands is deliberately scoped out of the
1682
+ * Public Beta — see the Compute Beta plan, sub-track 3B.1, where the
1683
+ * Terminal team picks an explicit removal milestone.
1684
+ */
1685
+ function emitLegacyEnvDeprecationWarning(context, legacyCommand, replacement) {
1686
+ if (context.flags.json) return;
1687
+ const message = `[deprecation] \`prisma-cli ${legacyCommand}\` is deprecated. Use \`prisma-cli ${replacement}\` instead.`;
1688
+ context.runtime.stderr.write(`${message}\n`);
1689
+ }
833
1690
  //#endregion
834
1691
  export { runAppBuild, runAppDeploy, runAppListDeploys, runAppListEnv, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy, runAppUpdateEnv };