@prisma/cli 3.0.0-beta.0 → 3.0.0-beta.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 (65) hide show
  1. package/README.md +18 -3
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +12 -4
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +63 -22
  6. package/dist/cli.js +17 -2
  7. package/dist/cli2.js +7 -3
  8. package/dist/commands/app/index.js +26 -13
  9. package/dist/commands/branch/index.js +2 -27
  10. package/dist/commands/database/index.js +159 -0
  11. package/dist/commands/env.js +8 -4
  12. package/dist/commands/project/index.js +28 -2
  13. package/dist/controllers/app-env-api.js +54 -0
  14. package/dist/controllers/app-env-file.js +181 -0
  15. package/dist/controllers/app-env.js +284 -132
  16. package/dist/controllers/app.js +493 -344
  17. package/dist/controllers/auth.js +8 -8
  18. package/dist/controllers/branch.js +78 -48
  19. package/dist/controllers/database.js +318 -0
  20. package/dist/controllers/project.js +302 -75
  21. package/dist/lib/app/branch-database-deploy.js +373 -0
  22. package/dist/lib/app/branch-database.js +316 -0
  23. package/dist/lib/app/bun-project.js +12 -5
  24. package/dist/lib/app/deploy-output.js +10 -1
  25. package/dist/lib/app/env-config.js +1 -1
  26. package/dist/lib/app/env-file.js +82 -0
  27. package/dist/lib/app/env-vars.js +28 -2
  28. package/dist/lib/app/local-dev.js +34 -18
  29. package/dist/lib/app/preview-branch-database.js +102 -0
  30. package/dist/lib/app/preview-build-settings.js +385 -0
  31. package/dist/lib/app/preview-build.js +272 -81
  32. package/dist/lib/app/preview-provider.js +163 -54
  33. package/dist/lib/app/production-deploy-gate.js +161 -0
  34. package/dist/lib/auth/auth-ops.js +69 -19
  35. package/dist/lib/auth/guard.js +3 -2
  36. package/dist/lib/auth/login.js +109 -14
  37. package/dist/lib/database/provider.js +167 -0
  38. package/dist/lib/diagnostics.js +15 -0
  39. package/dist/lib/git/local-branch.js +41 -0
  40. package/dist/lib/git/local-status.js +57 -0
  41. package/dist/lib/project/interactive-setup.js +56 -0
  42. package/dist/lib/project/local-pin.js +182 -33
  43. package/dist/lib/project/resolution.js +287 -105
  44. package/dist/lib/project/setup.js +132 -0
  45. package/dist/presenters/app-env.js +149 -14
  46. package/dist/presenters/app.js +170 -20
  47. package/dist/presenters/auth.js +19 -6
  48. package/dist/presenters/branch.js +37 -102
  49. package/dist/presenters/database.js +274 -0
  50. package/dist/presenters/project.js +100 -47
  51. package/dist/presenters/verbose-context.js +64 -0
  52. package/dist/shell/command-arguments.js +6 -0
  53. package/dist/shell/command-meta.js +139 -16
  54. package/dist/shell/command-runner.js +38 -8
  55. package/dist/shell/diagnostics-output.js +63 -0
  56. package/dist/shell/errors.js +14 -1
  57. package/dist/shell/output.js +13 -2
  58. package/dist/shell/runtime.js +3 -3
  59. package/dist/shell/ui.js +23 -1
  60. package/dist/shell/update-check.js +247 -0
  61. package/dist/use-cases/auth.js +15 -4
  62. package/dist/use-cases/branch.js +20 -68
  63. package/dist/use-cases/create-cli-gateways.js +2 -17
  64. package/dist/use-cases/project.js +2 -1
  65. package/package.json +12 -10
@@ -7,16 +7,23 @@ import { canPrompt } from "../shell/runtime.js";
7
7
  import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
8
8
  import { requireComputeAuth } from "../lib/auth/guard.js";
9
9
  import { readAuthState } from "../lib/auth/auth-ops.js";
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";
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
13
  import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.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";
14
+ import { formatCommandArgument } from "../shell/command-arguments.js";
15
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
16
+ import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
17
+ 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";
16
21
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
17
22
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
23
+ import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
18
24
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
19
25
  import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
26
+ import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
20
27
  import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
21
28
  import { createSelectPromptPort } from "./select-prompt-port.js";
22
29
  import { requireAuthenticatedAuthState } from "./auth.js";
@@ -24,12 +31,15 @@ import { listRealWorkspaceProjects } from "./project.js";
24
31
  import { access, readFile } from "node:fs/promises";
25
32
  import path from "node:path";
26
33
  import open from "open";
34
+ import { Result, matchError } from "better-result";
27
35
  //#region src/controllers/app.ts
28
36
  const DEPLOY_FRAMEWORKS = [
29
37
  "nextjs",
30
38
  "hono",
31
- "tanstack-start"
39
+ "tanstack-start",
40
+ "bun"
32
41
  ];
42
+ const TANSTACK_START_PACKAGES = ["@tanstack/react-start", "@tanstack/solid-start"];
33
43
  const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
34
44
  const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
35
45
  const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
@@ -43,7 +53,8 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
43
53
  const { artifact, buildType: actualBuildType } = await executePreviewBuild({
44
54
  appPath: context.runtime.cwd,
45
55
  entrypoint,
46
- buildType
56
+ buildType,
57
+ signal: context.runtime.signal
47
58
  });
48
59
  return {
49
60
  command: "app.build",
@@ -73,12 +84,13 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
73
84
  buildType: resolvedBuildType,
74
85
  entrypoint,
75
86
  port,
76
- env: context.runtime.env
87
+ env: context.runtime.env,
88
+ signal: context.runtime.signal
77
89
  });
78
90
  } catch (error) {
79
91
  throw runFailedError("Local app run failed", error);
80
92
  }
81
- if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") process.exitCode = runResult.signal === "SIGINT" ? 130 : 143;
93
+ if (runResult.signal === "SIGINT" || runResult.signal === "SIGTERM") throw new DOMException("Command canceled", "AbortError");
82
94
  else if (runResult.exitCode !== 0) throw runFailedError("Local app run failed", `The ${formatFrameworkName(runResult.framework)} process exited with code ${runResult.exitCode}.`, runResult.exitCode);
83
95
  return {
84
96
  command: "app.run",
@@ -96,67 +108,84 @@ async function runAppDeploy(context, appName, options) {
96
108
  ensurePreviewAppMode(context);
97
109
  const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
98
110
  const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
99
- const skipLocalPin = Boolean(envProjectId || envAppId);
100
- const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
101
- if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
102
- const explicitBuildType = Boolean(options?.buildType && options.buildType !== "auto");
111
+ assertExclusiveDeployProjectInputs({
112
+ projectRef: options?.projectRef,
113
+ createProjectName: options?.createProjectName,
114
+ envProjectId
115
+ });
116
+ const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
117
+ if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
118
+ const localPin = localPinReadResult.value;
103
119
  const branch = await resolveDeployBranch(context, options?.branchName);
104
120
  if (options?.httpPort) parseDeployHttpPort(options.httpPort);
105
- let framework = await resolveDeployFramework(context, {
121
+ assertSupportedEntrypointForRequestedDeployShape({
106
122
  requestedFramework: options?.framework,
107
- requestedBuildType: options?.buildType,
108
- explicitBuildType
123
+ entrypoint: options?.entrypoint
109
124
  });
110
- let runtime = resolveDeployRuntime(options?.httpPort, framework);
111
- assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
112
- const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
113
- const firstDeploy = !skipLocalPin && localPin.kind === "missing";
114
125
  const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
115
- allowCreate: true,
116
126
  branch,
127
+ createProjectName: options?.createProjectName,
117
128
  envProjectId,
118
129
  localPin
119
130
  });
131
+ let localPinResult;
132
+ if (target.localPinAction) {
133
+ const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction);
134
+ if (setupResult.isErr()) throw projectDirectoryBindingErrorToCliError(setupResult.error);
135
+ const projectSetup = setupResult.value;
136
+ localPinResult = projectSetup.localPin;
137
+ maybeRenderProjectLinked(context, projectSetup.directory, projectSetup.project.name, projectSetup.localPin.path);
138
+ }
139
+ 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" }));
120
146
  const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
121
147
  explicitAppName: appName,
122
148
  explicitAppId: envAppId,
123
- firstDeploy,
124
- inferName: () => inferTargetName(context.runtime.cwd)
149
+ firstDeploy: Boolean(target.localPinAction),
150
+ inferName: () => inferTargetName(context.runtime.cwd, context.runtime.signal)
125
151
  });
126
152
  await maybeRenderDeploySetupBlock(context, {
127
- firstDeploy: selectedApp.firstDeploy,
128
- workspaceName: target.workspace.name,
153
+ includeDirectory: !target.localPinAction,
129
154
  projectName: target.project.name,
130
- projectAnnotation: annotationForProjectResolution(target.resolution),
131
155
  branchName: target.branch.name,
132
- branchAnnotation: branch.annotation,
133
- appName: selectedApp.displayName,
134
- appAnnotation: selectedApp.annotation,
135
- framework,
136
- runtime
156
+ appName: selectedApp.displayName
137
157
  });
138
158
  const customized = await maybeCustomizeDeploySettings(context, {
139
159
  framework,
140
160
  runtime,
141
161
  firstDeploy: selectedApp.firstDeploy,
142
162
  explicitFramework: Boolean(options?.framework),
143
- explicitBuildType,
163
+ explicitEntrypoint: Boolean(options?.entrypoint),
144
164
  explicitHttpPort: Boolean(options?.httpPort)
145
165
  });
146
166
  framework = customized.framework;
147
167
  runtime = customized.runtime;
168
+ const productionDeployGate = await enforceProductionDeployGate(context, provider, {
169
+ appId: selectedApp.appId,
170
+ appName: selectedApp.displayName,
171
+ branchKind: target.branch.kind,
172
+ prod: options?.prod === true
173
+ });
148
174
  const buildType = framework.buildType;
149
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,
179
+ buildType,
180
+ signal: context.runtime.signal
181
+ });
182
+ maybeRenderDeployBuildSettings(context, buildSettingsResolution);
150
183
  const portMapping = parseDeployPortMapping(String(runtime.port));
151
- const shouldWriteLocalPin = firstDeploy && !skipLocalPin;
152
- if (shouldWriteLocalPin) {
153
- await writeLocalResolutionPin(context.runtime.cwd, {
154
- workspaceId: target.workspace.id,
155
- projectId: target.project.id
156
- });
157
- await ensureLocalResolutionPinGitignore(context.runtime.cwd);
158
- maybeRenderLocalPinBound(context, target.project.name);
159
- }
184
+ const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
185
+ db: options?.db,
186
+ providedEnvVars: envVars,
187
+ firstProductionDeploy: productionDeployGate.firstProductionDeploy
188
+ });
160
189
  const progressState = createPreviewDeployProgressState();
161
190
  const deployStartedAt = Date.now();
162
191
  const deployResult = await provider.deployApp({
@@ -166,11 +195,13 @@ async function runAppDeploy(context, appName, options) {
166
195
  appId: selectedApp.appId,
167
196
  appName: selectedApp.appName,
168
197
  region: selectedApp.region,
169
- entrypoint: options?.entrypoint,
198
+ entrypoint,
170
199
  buildType,
200
+ buildSettings: buildSettingsResolution.settings,
171
201
  portMapping,
172
202
  envVars,
173
203
  interaction: void 0,
204
+ signal: context.runtime.signal,
174
205
  progress: createPreviewDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
175
206
  }).catch((error) => {
176
207
  throw appDeployFailedError(error, progressState);
@@ -186,38 +217,61 @@ async function runAppDeploy(context, appName, options) {
186
217
  result: {
187
218
  workspace: target.workspace,
188
219
  project: target.project,
189
- branch: target.branch,
220
+ branch: toResultBranch(target.branch),
190
221
  resolution: target.resolution,
222
+ branchDatabase: branchDatabaseSetup.result,
191
223
  app: {
192
224
  id: deployResult.app.id,
193
225
  name: deployResult.app.name
194
226
  },
195
227
  deployment: deployResult.deployment,
228
+ deploySettings: {
229
+ config: {
230
+ path: buildSettingsResolution.relativeConfigPath,
231
+ status: buildSettingsResolution.status
232
+ },
233
+ buildCommand: {
234
+ value: buildSettingsResolution.settings.buildCommand,
235
+ source: buildSettingsResolution.settings.buildCommandSource
236
+ },
237
+ outputDirectory: {
238
+ value: buildSettingsResolution.settings.outputDirectory,
239
+ source: buildSettingsResolution.settings.outputDirectorySource
240
+ },
241
+ framework: {
242
+ key: framework.key,
243
+ buildType,
244
+ name: framework.displayName,
245
+ source: framework.annotation
246
+ },
247
+ entrypoint: entrypoint ?? null,
248
+ httpPort: runtime.port,
249
+ region: selectedApp.region ?? null,
250
+ envVars: envVarNames(envVars)
251
+ },
196
252
  durationMs: deployDurationMs,
197
- localPin: shouldWriteLocalPin ? {
198
- path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
199
- written: true
200
- } : void 0
253
+ localPin: localPinResult
201
254
  },
202
- warnings: [],
255
+ warnings: branchDatabaseSetup.warnings,
203
256
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
204
257
  };
205
258
  }
206
259
  async function runAppListDeploys(context, appName, projectRef) {
207
260
  ensurePreviewAppMode(context);
208
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
261
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app list-deploys" });
209
262
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
210
263
  if (!selectedApp) return {
211
264
  command: "app.list-deploys",
212
265
  result: {
213
266
  projectId,
267
+ verboseContext: toAppVerboseContext(target),
214
268
  app: null,
215
269
  deployments: []
216
270
  },
217
271
  warnings: [],
218
272
  nextSteps: ["prisma-cli app deploy"]
219
273
  };
220
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
274
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
221
275
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app deploy"]);
222
276
  });
223
277
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -230,6 +284,7 @@ async function runAppListDeploys(context, appName, projectRef) {
230
284
  command: "app.list-deploys",
231
285
  result: {
232
286
  projectId,
287
+ verboseContext: toAppVerboseContext(target),
233
288
  app: {
234
289
  id: deploymentsResult.app.id,
235
290
  name: deploymentsResult.app.name
@@ -242,12 +297,13 @@ async function runAppListDeploys(context, appName, projectRef) {
242
297
  }
243
298
  async function runAppShow(context, appName, projectRef) {
244
299
  ensurePreviewAppMode(context);
245
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
300
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app show" });
246
301
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
247
302
  if (!selectedApp) return {
248
303
  command: "app.show",
249
304
  result: {
250
305
  projectId,
306
+ verboseContext: toAppVerboseContext(target),
251
307
  app: null,
252
308
  liveDeployment: null,
253
309
  liveUrl: null,
@@ -256,7 +312,7 @@ async function runAppShow(context, appName, projectRef) {
256
312
  warnings: [],
257
313
  nextSteps: ["prisma-cli app deploy"]
258
314
  };
259
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
315
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
260
316
  throw deployFailedError("Failed to inspect app", error, ["prisma-cli app list-deploys"]);
261
317
  });
262
318
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -270,6 +326,7 @@ async function runAppShow(context, appName, projectRef) {
270
326
  command: "app.show",
271
327
  result: {
272
328
  projectId,
329
+ verboseContext: toAppVerboseContext(target),
273
330
  app: {
274
331
  id: deploymentsResult.app.id,
275
332
  name: deploymentsResult.app.name
@@ -284,7 +341,7 @@ async function runAppShow(context, appName, projectRef) {
284
341
  }
285
342
  async function runAppShowDeploy(context, deploymentId) {
286
343
  ensurePreviewAppMode(context);
287
- const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId).catch((error) => {
344
+ const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
288
345
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
289
346
  });
290
347
  if (!deployment) throw new CliError({
@@ -318,10 +375,10 @@ async function runAppShowDeploy(context, deploymentId) {
318
375
  }
319
376
  async function runAppOpen(context, appName, projectRef) {
320
377
  ensurePreviewAppMode(context);
321
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
378
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
322
379
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
323
380
  if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
324
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
381
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
325
382
  throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
326
383
  });
327
384
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -334,11 +391,16 @@ async function runAppOpen(context, appName, projectRef) {
334
391
  if (!liveDeployment) throw noDeploymentsError("No deployments available to open", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
335
392
  if (!deploymentsResult.app.liveUrl) throw featureUnavailableError("Live URL is not available for the selected app", "Deployments exist, but the provider does not expose a stable live service URL for this app yet.", "Run prisma-cli app show to inspect the current deployment state and try again after the app reports a live URL.", ["prisma-cli app show"], "app");
336
393
  const shouldOpen = canPrompt(context);
337
- if (shouldOpen) await open(deploymentsResult.app.liveUrl);
394
+ if (shouldOpen) {
395
+ context.runtime.signal.throwIfAborted();
396
+ await open(deploymentsResult.app.liveUrl);
397
+ context.runtime.signal.throwIfAborted();
398
+ }
338
399
  return {
339
400
  command: "app.open",
340
401
  result: {
341
402
  projectId,
403
+ verboseContext: toAppVerboseContext(target),
342
404
  app: {
343
405
  id: deploymentsResult.app.id,
344
406
  name: deploymentsResult.app.name
@@ -352,10 +414,11 @@ async function runAppOpen(context, appName, projectRef) {
352
414
  }
353
415
  async function runAppDomainAdd(context, hostname, options) {
354
416
  const normalizedHostname = normalizeDomainHostname(hostname);
355
- const target = await resolveAppDomainTarget(context, options);
417
+ const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
356
418
  const added = await target.provider.addDomain({
357
419
  appId: target.app.id,
358
- hostname: normalizedHostname
420
+ hostname: normalizedHostname,
421
+ signal: context.runtime.signal
359
422
  }).catch((error) => {
360
423
  throw domainCommandError("add", error, normalizedHostname);
361
424
  });
@@ -372,9 +435,9 @@ async function runAppDomainAdd(context, hostname, options) {
372
435
  }
373
436
  async function runAppDomainShow(context, hostname, options) {
374
437
  const normalizedHostname = normalizeDomainHostname(hostname);
375
- const target = await resolveAppDomainTarget(context, options);
376
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show");
377
- const detail = await target.provider.showDomain(domain.id).catch((error) => {
438
+ const target = await resolveAppDomainTarget(context, options, `app domain show ${normalizedHostname}`);
439
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show", context.runtime.signal);
440
+ const detail = await target.provider.showDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
378
441
  throw domainCommandError("show", error, normalizedHostname);
379
442
  });
380
443
  return {
@@ -389,10 +452,10 @@ async function runAppDomainShow(context, hostname, options) {
389
452
  }
390
453
  async function runAppDomainRemove(context, hostname, options) {
391
454
  const normalizedHostname = normalizeDomainHostname(hostname);
392
- const target = await resolveAppDomainTarget(context, options);
393
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
455
+ const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
456
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove", context.runtime.signal);
394
457
  await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
395
- await target.provider.removeDomain(domain.id).catch((error) => {
458
+ await target.provider.removeDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
396
459
  throw domainCommandError("remove", error, normalizedHostname);
397
460
  });
398
461
  return {
@@ -408,9 +471,9 @@ async function runAppDomainRemove(context, hostname, options) {
408
471
  }
409
472
  async function runAppDomainRetry(context, hostname, options) {
410
473
  const normalizedHostname = normalizeDomainHostname(hostname);
411
- const target = await resolveAppDomainTarget(context, options);
412
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry");
413
- const retried = await target.provider.retryDomain(domain.id).catch((error) => {
474
+ const target = await resolveAppDomainTarget(context, options, `app domain retry ${normalizedHostname}`);
475
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry", context.runtime.signal);
476
+ const retried = await target.provider.retryDomain(domain.id, { signal: context.runtime.signal }).catch((error) => {
414
477
  throw domainCommandError("retry", error, normalizedHostname);
415
478
  });
416
479
  return {
@@ -426,8 +489,8 @@ async function runAppDomainRetry(context, hostname, options) {
426
489
  async function runAppDomainWait(context, hostname, options) {
427
490
  const normalizedHostname = normalizeDomainHostname(hostname);
428
491
  const timeoutMs = parseDomainWaitTimeout(options?.timeout);
429
- const target = await resolveAppDomainTarget(context, options);
430
- const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
492
+ const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
493
+ const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait", context.runtime.signal);
431
494
  if (!context.flags.json && !context.flags.quiet) context.output.stderr.write([
432
495
  `app domain wait -> Waiting for ${normalizedHostname} to become active.`,
433
496
  "",
@@ -470,15 +533,15 @@ async function runAppDomainWait(context, hostname, options) {
470
533
  exitCode: 1,
471
534
  nextSteps: [`prisma-cli app domain show ${normalizedHostname}`]
472
535
  });
473
- await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)));
474
- current = await target.provider.showDomain(current.id).catch((error) => {
536
+ await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)), context.runtime.signal);
537
+ current = await target.provider.showDomain(current.id, { signal: context.runtime.signal }).catch((error) => {
475
538
  throw domainCommandError("wait", error, normalizedHostname);
476
539
  });
477
540
  }
478
541
  }
479
542
  async function runAppLogs(context, appName, deploymentId, projectRef) {
480
543
  ensurePreviewAppMode(context);
481
- const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef);
544
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app logs" });
482
545
  const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
483
546
  if (!context.flags.json && !context.flags.quiet) {
484
547
  const lines = renderCommandHeader(context.ui, {
@@ -504,6 +567,7 @@ async function runAppLogs(context, appName, deploymentId, projectRef) {
504
567
  }
505
568
  await provider.streamDeploymentLogs({
506
569
  deploymentId: target.deployment.id,
570
+ signal: context.runtime.signal,
507
571
  onRecord: (record) => writeLogRecord(context, record)
508
572
  }).catch((error) => {
509
573
  throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
@@ -513,7 +577,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
513
577
  if (appName) {
514
578
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
515
579
  if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
516
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
580
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
517
581
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
518
582
  });
519
583
  const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
@@ -526,7 +590,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
526
590
  deployment
527
591
  };
528
592
  }
529
- const shown = await provider.showDeployment(deploymentId).catch((error) => {
593
+ const shown = await provider.showDeployment(deploymentId, { signal: context.runtime.signal }).catch((error) => {
530
594
  throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
531
595
  });
532
596
  if (!shown) throw new CliError({
@@ -569,7 +633,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, branch
569
633
  async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
570
634
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
571
635
  if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
572
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
636
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
573
637
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
574
638
  });
575
639
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -602,9 +666,9 @@ function writeLogRecord(context, record) {
602
666
  }
603
667
  async function runAppPromote(context, deploymentId, appName, projectRef) {
604
668
  ensurePreviewAppMode(context);
605
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
669
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
606
670
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
607
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
671
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
608
672
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
609
673
  });
610
674
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -617,6 +681,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
617
681
  if (!targetAlreadyLive) await provider.promoteDeployment({
618
682
  appId: selectedApp.id,
619
683
  deploymentId: targetDeployment.id,
684
+ signal: context.runtime.signal,
620
685
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
621
686
  }).catch((error) => {
622
687
  throw deployFailedError("Failed to promote deployment", error, ["prisma-cli app list-deploys"]);
@@ -626,6 +691,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
626
691
  command: "app.promote",
627
692
  result: {
628
693
  projectId,
694
+ verboseContext: toAppVerboseContext(target),
629
695
  app: {
630
696
  id: deploymentsResult.app.id,
631
697
  name: deploymentsResult.app.name
@@ -642,9 +708,9 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
642
708
  }
643
709
  async function runAppRollback(context, appName, deploymentId, projectRef) {
644
710
  ensurePreviewAppMode(context);
645
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
711
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
646
712
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
647
- const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
713
+ const deploymentsResult = await provider.listDeployments(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
648
714
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
649
715
  });
650
716
  const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
@@ -658,6 +724,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
658
724
  if (!targetAlreadyLive) await provider.promoteDeployment({
659
725
  appId: selectedApp.id,
660
726
  deploymentId: targetDeployment.id,
727
+ signal: context.runtime.signal,
661
728
  progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
662
729
  }).catch((error) => {
663
730
  throw deployFailedError("Failed to roll back deployment", error, ["prisma-cli app list-deploys"]);
@@ -667,6 +734,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
667
734
  command: "app.rollback",
668
735
  result: {
669
736
  projectId,
737
+ verboseContext: toAppVerboseContext(target),
670
738
  app: {
671
739
  id: deploymentsResult.app.id,
672
740
  name: deploymentsResult.app.name
@@ -684,10 +752,10 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
684
752
  }
685
753
  async function runAppRemove(context, appName, projectRef) {
686
754
  ensurePreviewAppMode(context);
687
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
755
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
688
756
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
689
757
  await confirmAppRemoval(context, selectedApp);
690
- const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
758
+ const removedApp = await provider.removeApp(selectedApp.id, { signal: context.runtime.signal }).catch((error) => {
691
759
  throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
692
760
  });
693
761
  const warnings = await cleanupRemovedAppState(context, projectId, removedApp.id);
@@ -695,6 +763,7 @@ async function runAppRemove(context, appName, projectRef) {
695
763
  command: "app.remove",
696
764
  result: {
697
765
  projectId,
766
+ verboseContext: toAppVerboseContext(target),
698
767
  app: {
699
768
  id: removedApp.id,
700
769
  name: removedApp.name
@@ -705,7 +774,7 @@ async function runAppRemove(context, appName, projectRef) {
705
774
  nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
706
775
  };
707
776
  }
708
- async function resolveAppDomainTarget(context, options) {
777
+ async function resolveAppDomainTarget(context, options, commandName = "app domain") {
709
778
  ensurePreviewAppMode(context);
710
779
  const branch = resolveDomainBranch(options?.branchName);
711
780
  if (toBranchKind(branch.name) !== "production") throw new CliError({
@@ -719,14 +788,10 @@ async function resolveAppDomainTarget(context, options) {
719
788
  });
720
789
  const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
721
790
  const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
722
- const skipLocalPin = Boolean(envProjectId || envAppId);
723
- const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
724
- if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
725
- const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
726
- allowCreate: false,
791
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
727
792
  branch,
728
- envProjectId,
729
- localPin
793
+ commandName,
794
+ envProjectId
730
795
  });
731
796
  const selectedApp = await resolveDomainAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
732
797
  explicitAppName: options?.appName,
@@ -742,7 +807,7 @@ async function resolveAppDomainTarget(context, options) {
742
807
  resultTarget: {
743
808
  workspace: target.workspace,
744
809
  project: target.project,
745
- branch: target.branch,
810
+ branch: toResultBranch(target.branch),
746
811
  app: {
747
812
  id: selectedApp.id,
748
813
  name: selectedApp.name
@@ -766,8 +831,8 @@ async function resolveDomainAppSelection(context, projectId, apps, options) {
766
831
  if (selectedApp) return selectedApp;
767
832
  throw usageError("Custom domain requires an existing app on the production branch", "The resolved production branch does not have an app that can receive a custom domain.", "Deploy or promote an app to production first, then rerun the domain command.", ["prisma-cli app deploy --branch production", "prisma-cli app show"], "app");
768
833
  }
769
- async function resolveDomainByHostname(provider, appId, hostname, command) {
770
- const matched = (await provider.listDomains(appId).catch((error) => {
834
+ async function resolveDomainByHostname(provider, appId, hostname, command, signal) {
835
+ const matched = (await provider.listDomains(appId, { signal }).catch((error) => {
771
836
  throw domainCommandError(command, error, hostname);
772
837
  })).find((domain) => sameDomainHostname(domain.hostname, hostname));
773
838
  if (matched) return matched;
@@ -998,9 +1063,20 @@ function formatElapsed(milliseconds) {
998
1063
  const remainingSeconds = seconds % 60;
999
1064
  return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
1000
1065
  }
1001
- async function sleep(milliseconds) {
1066
+ async function sleep(milliseconds, signal) {
1002
1067
  if (milliseconds <= 0) return;
1003
- await new Promise((resolve) => setTimeout(resolve, milliseconds));
1068
+ signal.throwIfAborted();
1069
+ await new Promise((resolve, reject) => {
1070
+ const onAbort = () => {
1071
+ clearTimeout(timeout);
1072
+ reject(signal.reason);
1073
+ };
1074
+ const timeout = setTimeout(() => {
1075
+ signal.removeEventListener("abort", onAbort);
1076
+ resolve();
1077
+ }, milliseconds);
1078
+ signal.addEventListener("abort", onAbort, { once: true });
1079
+ });
1004
1080
  }
1005
1081
  async function resolveDeployAppSelection(context, projectId, apps, options) {
1006
1082
  if (options.explicitAppName) {
@@ -1215,15 +1291,18 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
1215
1291
  });
1216
1292
  }
1217
1293
  async function listApps(context, provider, projectId, branchName) {
1218
- return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
1294
+ return provider.listApps(projectId, {
1295
+ branchName,
1296
+ signal: context.runtime.signal
1297
+ }).then(sortApps).catch((error) => {
1219
1298
  if (isMissingProjectError(error)) throw new CliError({
1220
1299
  code: "PROJECT_NOT_FOUND",
1221
1300
  domain: "project",
1222
1301
  summary: "Project not found",
1223
1302
  why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
1224
- fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect resolution for this directory.",
1303
+ fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect this directory's binding.",
1225
1304
  exitCode: 1,
1226
- nextSteps: ["prisma-cli project show", "prisma-cli app deploy --project <id-or-name>"]
1305
+ nextSteps: ["prisma-cli project show", "prisma-cli project link <id-or-name>"]
1227
1306
  });
1228
1307
  throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
1229
1308
  });
@@ -1233,20 +1312,20 @@ async function requirePreviewAppProvider(context) {
1233
1312
  return provider;
1234
1313
  }
1235
1314
  async function requirePreviewAppProviderWithClient(context) {
1236
- const client = await requireComputeAuth(context.runtime.env);
1315
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
1237
1316
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
1238
1317
  return {
1239
1318
  client,
1240
- provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
1319
+ provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env, context.runtime.signal))
1241
1320
  };
1242
1321
  }
1243
- function createPreviewLogAuthOptions(env) {
1322
+ function createPreviewLogAuthOptions(env, signal) {
1244
1323
  const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
1245
1324
  if (rawToken) return {
1246
1325
  baseUrl: getApiBaseUrl(env),
1247
1326
  getToken: async () => rawToken
1248
1327
  };
1249
- const tokenStorage = new FileTokenStorage(env);
1328
+ const tokenStorage = new FileTokenStorage(env, signal);
1250
1329
  return {
1251
1330
  baseUrl: getApiBaseUrl(env),
1252
1331
  getToken: async () => {
@@ -1258,7 +1337,7 @@ function createPreviewLogAuthOptions(env) {
1258
1337
  }
1259
1338
  async function requireProviderAndProjectContext(context, explicitProject, options) {
1260
1339
  const { client, provider } = await requirePreviewAppProviderWithClient(context);
1261
- const target = await resolveProjectContext(context, client, provider, explicitProject, options);
1340
+ const target = await resolveProjectContext(context, client, explicitProject, options);
1262
1341
  return {
1263
1342
  client,
1264
1343
  provider,
@@ -1276,36 +1355,24 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
1276
1355
  projectId: target.project.id
1277
1356
  };
1278
1357
  }
1279
- async function resolveProjectContext(context, client, provider, explicitProject, options) {
1358
+ async function resolveProjectContext(context, client, explicitProject, options) {
1280
1359
  const authState = await requireAuthenticatedAuthState(context);
1281
1360
  if (!authState.workspace) throw workspaceRequiredError();
1282
- const resolved = await resolveProjectTarget({
1361
+ const resolvedResult = await resolveProjectTarget({
1283
1362
  context,
1284
1363
  workspace: authState.workspace,
1285
1364
  explicitProject,
1286
- listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
1287
- createProject: options?.allowCreate ? async (name) => {
1288
- const project = await provider.createProject({ name }).catch((error) => {
1289
- throw createProjectOnFirstDeployError({
1290
- error,
1291
- inferredName: name,
1292
- workspaceName: authState.workspace.name
1293
- });
1294
- });
1295
- return {
1296
- id: project.id,
1297
- name: project.name,
1298
- workspace: authState.workspace
1299
- };
1300
- } : void 0,
1301
- allowCreate: options?.allowCreate,
1302
- prompt: createSelectPromptPort(context),
1303
- remember: true
1365
+ envProjectId: options?.envProjectId,
1366
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
1367
+ commandName: options?.commandName
1304
1368
  });
1369
+ if (resolvedResult.isErr()) throw projectResolutionErrorToCliError(resolvedResult.error);
1370
+ const resolved = resolvedResult.value;
1305
1371
  const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
1306
1372
  return {
1307
1373
  ...resolved,
1308
1374
  branch: {
1375
+ id: null,
1309
1376
  name: branch.name,
1310
1377
  kind: toBranchKind(branch.name)
1311
1378
  }
@@ -1315,35 +1382,35 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1315
1382
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1316
1383
  if (!workspace) throw workspaceRequiredError();
1317
1384
  const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1318
- const projects = await listRealWorkspaceProjects(client, workspace);
1319
- const createProject = options.allowCreate ? async (name) => {
1320
- const project = await provider.createProject({ name }).catch((error) => {
1321
- throw createProjectOnFirstDeployError({
1322
- error,
1323
- inferredName: name,
1324
- workspaceName: workspace.name
1325
- });
1326
- });
1327
- return {
1328
- id: project.id,
1329
- name: project.name,
1330
- workspace
1331
- };
1332
- } : void 0;
1333
- if (explicitProject) return withDeployBranch(await resolveProjectTarget({
1334
- context,
1385
+ const projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
1386
+ if (explicitProject) return withRemoteDeployBranch(provider, {
1335
1387
  workspace,
1336
- explicitProject,
1337
- listProjects: async () => projects,
1338
- createProject,
1339
- allowCreate: options.allowCreate,
1340
- prompt: createSelectPromptPort(context),
1341
- remember: true
1342
- }), branch);
1388
+ project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
1389
+ resolution: {
1390
+ projectSource: "explicit",
1391
+ targetName: explicitProject,
1392
+ targetNameSource: "explicit"
1393
+ },
1394
+ localPinAction: "linked"
1395
+ }, branch, context.runtime.signal);
1396
+ if (options.createProjectName) {
1397
+ const projectName = options.createProjectName.trim();
1398
+ if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
1399
+ return withRemoteDeployBranch(provider, {
1400
+ workspace,
1401
+ project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
1402
+ resolution: {
1403
+ projectSource: "created",
1404
+ targetName: projectName,
1405
+ targetNameSource: "explicit"
1406
+ },
1407
+ localPinAction: "created"
1408
+ }, branch, context.runtime.signal);
1409
+ }
1343
1410
  if (options.envProjectId) {
1344
1411
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
1345
1412
  if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1346
- return withDeployBranch({
1413
+ return withRemoteDeployBranch(provider, {
1347
1414
  workspace,
1348
1415
  project: toProjectSummary(project),
1349
1416
  resolution: {
@@ -1351,14 +1418,14 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1351
1418
  targetName: options.envProjectId,
1352
1419
  targetNameSource: "env"
1353
1420
  }
1354
- }, branch);
1421
+ }, branch, context.runtime.signal);
1355
1422
  }
1356
1423
  const localPin = options.localPin;
1357
1424
  if (localPin.kind === "present") {
1358
1425
  if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1359
1426
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
1360
1427
  if (!project) throw localResolutionPinStaleError();
1361
- return withDeployBranch({
1428
+ return withRemoteDeployBranch(provider, {
1362
1429
  workspace,
1363
1430
  project: toProjectSummary(project),
1364
1431
  resolution: {
@@ -1366,42 +1433,123 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
1366
1433
  targetName: project.name,
1367
1434
  targetNameSource: "local-pin"
1368
1435
  }
1369
- }, branch);
1436
+ }, branch, context.runtime.signal);
1370
1437
  }
1371
- return withDeployBranch(await resolveProjectTarget({
1438
+ const platformMapping = await resolveDurablePlatformMapping();
1439
+ if (platformMapping && platformMapping.workspace.id === workspace.id) return withRemoteDeployBranch(provider, {
1440
+ workspace,
1441
+ project: toProjectSummary(platformMapping),
1442
+ resolution: {
1443
+ projectSource: "platform-mapping",
1444
+ targetName: platformMapping.name,
1445
+ targetNameSource: "platform-mapping"
1446
+ }
1447
+ }, branch, context.runtime.signal);
1448
+ if (canPrompt(context) && !context.flags.yes) return withRemoteDeployBranch(provider, await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch, context.runtime.signal);
1449
+ throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
1450
+ }
1451
+ async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
1452
+ const setup = await promptForProjectSetupChoice({
1372
1453
  context,
1454
+ projects,
1455
+ createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal),
1456
+ cancel: {
1457
+ why: "Deploy needs a Project before it can continue.",
1458
+ fix: "Choose an existing Project or create a new one, then rerun deploy.",
1459
+ nextSteps: ["prisma-cli app deploy --project <id-or-name>", "prisma-cli app deploy --create-project <name>"]
1460
+ }
1461
+ });
1462
+ return {
1373
1463
  workspace,
1374
- listProjects: async () => projects,
1375
- createProject,
1376
- allowCreate: options.allowCreate,
1377
- prompt: createSelectPromptPort(context),
1378
- remember: true
1379
- }), branch);
1380
- }
1381
- function withDeployBranch(target, branch) {
1464
+ project: setup.project,
1465
+ resolution: {
1466
+ projectSource: setup.action === "created" ? "created" : "prompt",
1467
+ targetName: setup.targetName,
1468
+ targetNameSource: setup.targetNameSource
1469
+ },
1470
+ localPinAction: setup.action
1471
+ };
1472
+ }
1473
+ async function createProjectForDeploySetup(provider, projectName, workspace, signal) {
1474
+ const created = await provider.createProject({
1475
+ name: projectName,
1476
+ signal
1477
+ }).catch((error) => {
1478
+ throw projectCreateFailedError(error, projectName, workspace, {
1479
+ nextSteps: [
1480
+ "prisma-cli project list",
1481
+ "prisma-cli app deploy --project <id-or-name>",
1482
+ `prisma-cli app deploy --create-project ${formatCommandArgument(projectName)}`
1483
+ ],
1484
+ permissionFix: "Choose an existing Project with --project, or grant the token permission to create Projects in this workspace.",
1485
+ fallbackFix: "Choose an existing Project with --project, or retry after addressing the platform error above."
1486
+ });
1487
+ });
1488
+ return {
1489
+ id: created.id,
1490
+ name: created.name,
1491
+ workspace
1492
+ };
1493
+ }
1494
+ async function withRemoteDeployBranch(provider, target, branch, signal) {
1495
+ const remoteBranch = await provider.resolveBranch(target.project.id, {
1496
+ branchName: branch.name,
1497
+ signal
1498
+ });
1382
1499
  return {
1383
1500
  ...target,
1384
1501
  branch: {
1385
- name: branch.name,
1386
- kind: toBranchKind(branch.name)
1502
+ id: remoteBranch.id,
1503
+ name: remoteBranch.name,
1504
+ kind: remoteBranch.role
1387
1505
  }
1388
1506
  };
1389
1507
  }
1390
- function toProjectSummary(project) {
1508
+ function toBranchKind(name) {
1509
+ return name === "production" || name === "main" ? "production" : "preview";
1510
+ }
1511
+ function toResultBranch(branch) {
1391
1512
  return {
1392
- id: project.id,
1393
- name: project.name
1513
+ id: branch.id,
1514
+ name: branch.name,
1515
+ kind: branch.kind
1394
1516
  };
1395
1517
  }
1396
- function toBranchKind(name) {
1397
- return name === "production" || name === "main" ? "production" : "preview";
1518
+ function toAppVerboseContext(target) {
1519
+ return {
1520
+ workspace: target.workspace,
1521
+ project: target.project,
1522
+ branch: target.branch,
1523
+ resolution: target.resolution
1524
+ };
1525
+ }
1526
+ function toBranchDatabaseDeployBranch(branch) {
1527
+ if (!branch.id) throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);
1528
+ return {
1529
+ id: branch.id,
1530
+ name: branch.name,
1531
+ kind: branch.kind
1532
+ };
1533
+ }
1534
+ function assertExclusiveDeployProjectInputs(options) {
1535
+ const provided = [
1536
+ options.projectRef ? "--project" : null,
1537
+ options.createProjectName ? "--create-project" : null,
1538
+ options.envProjectId ? PRISMA_PROJECT_ID_ENV_VAR : null
1539
+ ].filter((value) => Boolean(value));
1540
+ if (provided.length <= 1) return;
1541
+ throw usageError("Project selection is ambiguous", `${provided.join(", ")} cannot be used together.`, "Choose exactly one Project source for this deploy.", [
1542
+ "prisma-cli app deploy --project <id-or-name>",
1543
+ "prisma-cli app deploy --create-project <name>",
1544
+ `unset ${PRISMA_PROJECT_ID_ENV_VAR}`
1545
+ ], "project");
1398
1546
  }
1399
1547
  async function resolveDeployBranch(context, explicitBranchName) {
1400
1548
  if (explicitBranchName) return {
1401
1549
  name: explicitBranchName,
1402
1550
  annotation: "set by --branch"
1403
1551
  };
1404
- const gitBranch = await readLocalGitBranch(context.runtime.cwd);
1552
+ const gitBranch = await readLocalGitBranch(context.runtime.cwd, context.runtime.signal);
1405
1553
  if (gitBranch) return {
1406
1554
  name: gitBranch,
1407
1555
  annotation: "from local Git branch"
@@ -1411,41 +1559,15 @@ async function resolveDeployBranch(context, explicitBranchName) {
1411
1559
  annotation: "default"
1412
1560
  };
1413
1561
  }
1414
- async function readLocalGitBranch(cwd) {
1415
- const headPath = await resolveGitHeadPath(path.join(cwd, ".git"));
1416
- if (!headPath) return null;
1417
- try {
1418
- const head = (await readFile(headPath, "utf8")).trim();
1419
- if (head.startsWith("ref: refs/heads/")) return head.slice(16);
1420
- } catch {
1421
- return null;
1422
- }
1423
- return null;
1424
- }
1425
- async function resolveGitHeadPath(gitPath) {
1426
- try {
1427
- const raw = await readFile(gitPath, "utf8");
1428
- if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
1429
- } catch {}
1430
- try {
1431
- await access(path.join(gitPath, "HEAD"));
1432
- return path.join(gitPath, "HEAD");
1433
- } catch {
1434
- return null;
1435
- }
1436
- }
1437
1562
  async function resolveDeployFramework(context, options) {
1438
1563
  if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1439
- if (options.explicitBuildType) {
1440
- const buildType = normalizeBuildType(options.requestedBuildType);
1441
- if (buildType !== "auto") return {
1442
- key: buildType,
1443
- buildType,
1444
- displayName: formatBuildTypeName(buildType),
1445
- annotation: "set by --build-type"
1446
- };
1447
- }
1448
- const detected = await detectDeployFramework(context.runtime.cwd);
1564
+ if (options.entrypoint) return {
1565
+ key: "bun",
1566
+ buildType: "bun",
1567
+ displayName: "Bun",
1568
+ annotation: "set by --entry"
1569
+ };
1570
+ const detected = await detectDeployFramework(context.runtime.cwd, context.runtime.signal);
1449
1571
  if (detected) return detected;
1450
1572
  throw frameworkNotDetectedError(context.runtime.cwd);
1451
1573
  }
@@ -1459,9 +1581,30 @@ function resolveDeployRuntime(requestedHttpPort, framework) {
1459
1581
  annotation: `${framework.displayName} default`
1460
1582
  };
1461
1583
  }
1462
- async function detectDeployFramework(cwd) {
1463
- const packageJson = await readBunPackageJson(cwd);
1464
- const nextConfig = await detectNextConfig(cwd);
1584
+ function assertSupportedEntrypointForRequestedDeployShape(options) {
1585
+ if (!options.requestedFramework) return;
1586
+ assertSupportedEntrypoint(frameworkFromUserFacingValue(options.requestedFramework, "set by --framework").buildType, options.entrypoint, "deploy");
1587
+ }
1588
+ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signal) {
1589
+ if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
1590
+ const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd, signal));
1591
+ if (packageEntrypoint) return packageEntrypoint;
1592
+ if (framework.key !== "hono") return;
1593
+ const defaultEntrypoint = "src/index.ts";
1594
+ signal.throwIfAborted();
1595
+ try {
1596
+ await access(path.join(cwd, defaultEntrypoint));
1597
+ signal.throwIfAborted();
1598
+ return defaultEntrypoint;
1599
+ } catch (error) {
1600
+ if (signal.aborted) throw error;
1601
+ if (error.code !== "ENOENT") throw error;
1602
+ return;
1603
+ }
1604
+ }
1605
+ async function detectDeployFramework(cwd, signal) {
1606
+ const packageJson = await readBunPackageJson(cwd, signal);
1607
+ const nextConfig = await detectNextConfig(cwd, signal);
1465
1608
  if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
1466
1609
  key: "nextjs",
1467
1610
  buildType: "nextjs",
@@ -1474,7 +1617,7 @@ async function detectDeployFramework(cwd) {
1474
1617
  displayName: "Hono",
1475
1618
  annotation: "detected from package.json"
1476
1619
  };
1477
- if (hasPackageDependency(packageJson, "@tanstack/start")) return {
1620
+ if (hasAnyPackageDependency(packageJson, TANSTACK_START_PACKAGES)) return {
1478
1621
  key: "tanstack-start",
1479
1622
  buildType: "tanstack-start",
1480
1623
  displayName: "TanStack Start",
@@ -1482,21 +1625,26 @@ async function detectDeployFramework(cwd) {
1482
1625
  };
1483
1626
  return null;
1484
1627
  }
1485
- async function detectNextConfig(cwd) {
1628
+ async function detectNextConfig(cwd, signal) {
1486
1629
  for (const candidate of [
1487
1630
  "next.config.js",
1488
1631
  "next.config.mjs",
1489
- "next.config.cjs",
1490
- "next.config.ts"
1632
+ "next.config.ts",
1633
+ "next.config.mts"
1491
1634
  ]) {
1492
1635
  const filePath = path.join(cwd, candidate);
1636
+ signal.throwIfAborted();
1493
1637
  try {
1494
- const content = await readFile(filePath, "utf8");
1638
+ const content = await readFile(filePath, {
1639
+ encoding: "utf8",
1640
+ signal
1641
+ });
1495
1642
  return {
1496
1643
  exists: true,
1497
1644
  standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
1498
1645
  };
1499
1646
  } catch (error) {
1647
+ if (signal.aborted) throw error;
1500
1648
  if (error.code !== "ENOENT") throw error;
1501
1649
  }
1502
1650
  }
@@ -1508,6 +1656,9 @@ async function detectNextConfig(cwd) {
1508
1656
  function hasPackageDependency(packageJson, dependencyName) {
1509
1657
  return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
1510
1658
  }
1659
+ function hasAnyPackageDependency(packageJson, dependencyNames) {
1660
+ return dependencyNames.some((dependencyName) => hasPackageDependency(packageJson, dependencyName));
1661
+ }
1511
1662
  function hasDependency(dependencies, dependencyName) {
1512
1663
  return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1513
1664
  }
@@ -1527,9 +1678,16 @@ function frameworkFromUserFacingValue(value, annotation) {
1527
1678
  displayName: "Hono",
1528
1679
  annotation
1529
1680
  };
1681
+ case "bun": return {
1682
+ key: "bun",
1683
+ buildType: "bun",
1684
+ displayName: "Bun",
1685
+ annotation
1686
+ };
1530
1687
  case "tanstack":
1531
1688
  case "tanstack-start":
1532
- case "@tanstack/start": return {
1689
+ case "@tanstack/react-start":
1690
+ case "@tanstack/solid-start": return {
1533
1691
  key: "tanstack-start",
1534
1692
  buildType: "tanstack-start",
1535
1693
  displayName: "TanStack Start",
@@ -1539,82 +1697,61 @@ function frameworkFromUserFacingValue(value, annotation) {
1539
1697
  }
1540
1698
  }
1541
1699
  function frameworkNotDetectedError(cwd, requestedFramework) {
1542
- const supported = "Next.js, Hono, TanStack Start";
1700
+ const supported = "Next.js, Hono, TanStack Start, Bun";
1543
1701
  const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
1544
1702
  return new CliError({
1545
1703
  code: "FRAMEWORK_NOT_DETECTED",
1546
1704
  domain: "app",
1547
1705
  summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
1548
1706
  why: `Supported Beta frameworks: ${supported}.`,
1549
- fix: "Add one of these frameworks as a dependency, or pass --framework <nextjs|hono|tanstack-start>.",
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.",
1550
1708
  exitCode: 2,
1551
1709
  nextSteps: [
1552
1710
  "prisma-cli app deploy --framework nextjs",
1553
1711
  "prisma-cli app deploy --framework hono",
1554
- "prisma-cli app deploy --framework tanstack-start"
1712
+ "prisma-cli app deploy --framework tanstack-start",
1713
+ "prisma-cli app deploy --framework bun --entry server.ts",
1714
+ "prisma-cli app deploy --entry server.ts"
1555
1715
  ]
1556
1716
  });
1557
1717
  }
1558
1718
  async function maybeRenderDeploySetupBlock(context, details) {
1559
1719
  if (context.flags.json || context.flags.quiet) return;
1560
1720
  const directory = formatDeployDirectory(context.runtime.cwd);
1561
- if (!details.firstDeploy) {
1562
- context.output.stderr.write(`Deploying ${directory} to ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1563
- return;
1564
- }
1565
- const title = `Setting up your local directory ${formatLocalDirectory(context.runtime.cwd, context.runtime.env)}`;
1566
- const rows = details.firstDeploy ? [
1567
- {
1568
- label: "Workspace",
1569
- value: details.workspaceName
1570
- },
1571
- {
1572
- label: "Project",
1573
- value: details.projectName,
1574
- origin: details.projectAnnotation
1575
- },
1576
- {
1577
- label: "Branch",
1578
- value: details.branchName,
1579
- origin: details.branchAnnotation
1580
- },
1581
- {
1582
- label: "App",
1583
- value: details.appName,
1584
- origin: details.appAnnotation
1585
- },
1586
- {
1587
- label: "Framework",
1588
- value: details.framework.displayName,
1589
- origin: details.framework.annotation
1590
- },
1591
- {
1592
- label: "Runtime",
1593
- value: `HTTP ${details.runtime.port}`,
1594
- origin: details.runtime.annotation
1595
- }
1596
- ] : [];
1597
- const lines = [
1598
- title,
1599
- "",
1600
- ...renderDeployOutputRows(context.ui, rows),
1601
- ""
1602
- ];
1603
- context.output.stderr.write(`${lines.join("\n")}\n`);
1721
+ const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
1722
+ context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
1604
1723
  }
1605
- function maybeRenderLocalPinBound(context, projectName) {
1724
+ function maybeRenderDeployBuildSettings(context, resolution) {
1725
+ if (context.flags.json || context.flags.quiet) return;
1726
+ const settings = resolution.settings;
1727
+ const title = resolution.status === "created" ? `Created ${resolution.relativeConfigPath}` : `Using ${resolution.relativeConfigPath}`;
1728
+ context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [{
1729
+ label: "Build Command",
1730
+ value: settings.buildCommand ?? "none",
1731
+ origin: settings.buildCommandSource ?? void 0
1732
+ }, {
1733
+ label: "Output Directory",
1734
+ value: settings.outputDirectory,
1735
+ origin: settings.outputDirectorySource ?? void 0
1736
+ }]).join("\n")}\n\n`);
1737
+ }
1738
+ function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
1606
1739
  if (context.flags.json || context.flags.quiet) return;
1607
- context.output.stderr.write(`This directory is now linked to project ${projectName}.\n\n`);
1740
+ context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
1608
1741
  }
1609
1742
  async function maybeCustomizeDeploySettings(context, options) {
1610
- if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitBuildType || options.explicitHttpPort || !canPrompt(context)) return {
1743
+ if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitEntrypoint || options.explicitHttpPort || !canPrompt(context)) return {
1611
1744
  framework: options.framework,
1612
1745
  runtime: options.runtime
1613
1746
  };
1747
+ maybeRenderDeploySettingsPreview(context, {
1748
+ framework: options.framework,
1749
+ runtime: options.runtime
1750
+ });
1614
1751
  if (!await confirmPrompt({
1615
1752
  input: context.runtime.stdin,
1616
1753
  output: context.runtime.stderr,
1617
- message: "Customize settings?",
1754
+ message: "Customize build settings?",
1618
1755
  initialValue: false
1619
1756
  })) return {
1620
1757
  framework: options.framework,
@@ -1659,24 +1796,22 @@ async function maybeCustomizeDeploySettings(context, options) {
1659
1796
  runtime
1660
1797
  };
1661
1798
  }
1662
- function annotationForProjectResolution(resolution) {
1663
- switch (resolution.projectSource) {
1664
- case "explicit": return "set by --project";
1665
- case "env": return `from ${PRISMA_PROJECT_ID_ENV_VAR}`;
1666
- case "local-pin": return "from local pin";
1667
- case "created": return resolution.targetNameSource === "directory-name" ? "created from directory name" : "created from package.json";
1668
- case "package-name":
1669
- case "directory-name": return "linked to existing project";
1670
- case "platform-mapping":
1671
- case "remembered-local": return "linked to existing project";
1672
- case "prompt": return "selected by you";
1673
- }
1799
+ function maybeRenderDeploySettingsPreview(context, options) {
1800
+ if (context.flags.quiet || context.flags.json) return;
1801
+ context.output.stderr.write(`Detected ${options.framework.displayName}\n${renderDeploySettingsPreview(context.ui, [{
1802
+ key: "framework",
1803
+ value: options.framework.displayName
1804
+ }, {
1805
+ key: "runtime",
1806
+ value: `HTTP ${options.runtime.port}`
1807
+ }]).join("\n")}\n\n`);
1674
1808
  }
1675
1809
  function frameworkDisplayName(framework) {
1676
1810
  switch (framework) {
1677
1811
  case "nextjs": return "Next.js";
1678
1812
  case "hono": return "Hono";
1679
1813
  case "tanstack-start": return "TanStack Start";
1814
+ case "bun": return "Bun";
1680
1815
  }
1681
1816
  }
1682
1817
  function validateDeployHttpPortText(value) {
@@ -1692,19 +1827,10 @@ function formatDeployDirectory(cwd) {
1692
1827
  const basename = path.basename(cwd);
1693
1828
  return basename ? `./${basename}` : ".";
1694
1829
  }
1695
- function formatLocalDirectory(cwd, env) {
1696
- const resolved = path.resolve(cwd);
1697
- const home = env.HOME ? path.resolve(env.HOME) : null;
1698
- if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
1699
- const relative = path.relative(home, resolved);
1700
- return relative ? `~/${relative}` : "~";
1701
- }
1702
- return resolved;
1703
- }
1704
1830
  async function readCurrentWorkspaceId(context) {
1705
1831
  const state = await context.stateStore.read();
1706
1832
  if (state.auth?.workspaceId) return state.auth.workspaceId;
1707
- return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
1833
+ return (await readAuthState(context.runtime.env, context.runtime.signal)).workspace?.id ?? null;
1708
1834
  }
1709
1835
  function normalizeBuildType(requestedBuildType) {
1710
1836
  if (!requestedBuildType) return "auto";
@@ -1720,10 +1846,13 @@ function getBuildTypeExamples(commandName) {
1720
1846
  });
1721
1847
  }
1722
1848
  function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
1723
- 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");
1849
+ if (buildType !== "auto" && buildType !== "bun" && entrypoint) {
1850
+ 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
+ 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
+ }
1724
1853
  }
1725
1854
  async function requireLocalBuildType(context, buildType, commandName) {
1726
- const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
1855
+ const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType, context.runtime.signal);
1727
1856
  if (resolvedBuildType) return resolvedBuildType;
1728
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");
1729
1858
  }
@@ -1761,24 +1890,41 @@ function deployFailedError(summary, error, nextSteps) {
1761
1890
  function appDeployFailedError(error, progress) {
1762
1891
  const why = error instanceof Error ? error.message : String(error);
1763
1892
  const debug = formatDebugDetails(error);
1764
- if (progress.buildStarted && !progress.buildCompleted) return new CliError({
1765
- code: "BUILD_FAILED",
1766
- domain: "app",
1767
- summary: "Build failed locally.",
1768
- why,
1769
- fix: "Inspect the build output above, fix the error, and redeploy.",
1770
- debug,
1771
- meta: { phase: "build" },
1772
- humanLines: [
1773
- "Build failed locally.",
1774
- "",
1775
- `✗ Built ${why}`,
1776
- "",
1777
- "Fix: Inspect the build output above, fix the error, and redeploy."
1778
- ],
1779
- exitCode: 1,
1780
- nextSteps: []
1781
- });
1893
+ if (progress.buildStarted && !progress.buildCompleted) {
1894
+ const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
1895
+ const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
1896
+ const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
1897
+ const nextActions = standaloneOutputFailure ? [{
1898
+ kind: "edit-file",
1899
+ journey: "deploy-app",
1900
+ label: "Add Next.js standalone output",
1901
+ reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
1902
+ }, {
1903
+ kind: "run-command",
1904
+ journey: "deploy-app",
1905
+ label: "Rerun deploy",
1906
+ command: "prisma-cli app deploy"
1907
+ }] : [];
1908
+ return new CliError({
1909
+ code: "BUILD_FAILED",
1910
+ domain: "app",
1911
+ summary: "Build failed locally.",
1912
+ why,
1913
+ fix,
1914
+ debug,
1915
+ meta: { phase: "build" },
1916
+ humanLines: [
1917
+ "Build failed locally.",
1918
+ "",
1919
+ `✗ Built ${why}`,
1920
+ "",
1921
+ `Fix: ${fix}`
1922
+ ],
1923
+ exitCode: 1,
1924
+ nextSteps,
1925
+ nextActions
1926
+ });
1927
+ }
1782
1928
  if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
1783
1929
  const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
1784
1930
  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."];
@@ -1825,61 +1971,64 @@ function localResolutionPinStaleError() {
1825
1971
  domain: "project",
1826
1972
  summary: "Local project binding is stale",
1827
1973
  why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
1828
- fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} and re-run to re-bootstrap.`,
1974
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
1829
1975
  meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
1830
1976
  exitCode: 1,
1831
- nextSteps: ["prisma-cli app deploy"]
1977
+ nextSteps: [
1978
+ "prisma-cli project list",
1979
+ "prisma-cli project link <id-or-name>",
1980
+ "prisma-cli app deploy --project <id-or-name>"
1981
+ ]
1982
+ });
1983
+ }
1984
+ function localPinReadErrorToDeployError(error) {
1985
+ return matchError(error, {
1986
+ LocalResolutionPinInvalidJsonError: () => localResolutionPinStaleError(),
1987
+ LocalResolutionPinInvalidShapeError: () => localResolutionPinStaleError(),
1988
+ LocalResolutionPinReadAbortedError: (error) => {
1989
+ throw error;
1990
+ },
1991
+ UnhandledException: (error) => {
1992
+ throw error;
1993
+ }
1832
1994
  });
1833
1995
  }
1834
1996
  function readDeployEnvOverride(context, name) {
1835
1997
  const value = context.runtime.env[name]?.trim();
1836
1998
  return value ? value : void 0;
1837
1999
  }
1838
- /**
1839
- * `app deploy` falls into "create a new project on first deploy" when no
1840
- * existing project matches the package.json name (or the cwd basename as a
1841
- * fallback). When the create call fails the user often doesn't realise the
1842
- * CLI was attempting to create a project at all — they thought the deploy
1843
- * would find an existing project. Surface that context, and recommend the
1844
- * explicit `--project` flag as the unambiguous way out.
1845
- */
1846
- function createProjectOnFirstDeployError(options) {
1847
- const { error, inferredName, workspaceName } = options;
1848
- const status = extractHttpStatus(error);
1849
- const errorMessage = error instanceof Error ? error.message : String(error);
1850
- const inferredContext = `No existing project matched the package.json name \`${inferredName}\`, so the CLI attempted to create one.`;
1851
- const nextSteps = ["prisma-cli project list", "prisma-cli app deploy --project <id-or-name>"];
1852
- if (status === 401 || status === 403) return new CliError({
1853
- code: "AUTH_FORBIDDEN",
1854
- domain: "auth",
1855
- summary: "Could not create a new project for this deploy",
1856
- why: `${inferredContext} The platform rejected the create (HTTP ${status}).`,
1857
- fix: `Pass --project <id-or-name> to deploy into an existing project, or grant the service token project-create permission on workspace \`${workspaceName}\`.`,
1858
- debug: formatDebugDetails(error),
1859
- exitCode: 1,
1860
- nextSteps
1861
- });
2000
+ function projectSetupRequiredError(projects, suggestedName) {
2001
+ const createCommand = `prisma-cli app deploy --create-project ${formatCommandArgument(suggestedName.name)}`;
1862
2002
  return new CliError({
1863
- code: "DEPLOY_FAILED",
1864
- domain: "app",
1865
- summary: "Could not create a new project for this deploy",
1866
- why: `${inferredContext} ${errorMessage}`.trim(),
1867
- fix: "Pass --project <id-or-name> to deploy into an existing project, or retry after addressing the platform error above.",
1868
- debug: formatDebugDetails(error),
2003
+ code: "PROJECT_SETUP_REQUIRED",
2004
+ domain: "project",
2005
+ summary: "Choose a Project before deploying this directory",
2006
+ why: "This directory is not linked to a Prisma Project, and deploy will not choose or create one implicitly.",
2007
+ fix: "Choose an existing Project with --project, create one with --create-project, or rerun interactively to pick from the setup list.",
2008
+ meta: {
2009
+ candidates: sortProjects(projects).map((project) => ({
2010
+ id: project.id,
2011
+ name: project.name
2012
+ })),
2013
+ suggestedProjectName: suggestedName.name,
2014
+ suggestedProjectNameSource: suggestedName.source,
2015
+ recoveryCommands: ["prisma-cli app deploy --project <id-or-name>", createCommand]
2016
+ },
1869
2017
  exitCode: 1,
1870
- nextSteps
2018
+ nextSteps: [
2019
+ "prisma-cli project list",
2020
+ "prisma-cli app deploy --project <id-or-name>",
2021
+ createCommand
2022
+ ],
2023
+ nextActions: buildProjectSetupNextActions({
2024
+ commandName: "app deploy",
2025
+ createCommand,
2026
+ reason: "This directory is not linked to a Prisma Project. Ask the user which Project to use before deploying; package and directory names are setup suggestions only."
2027
+ })
1871
2028
  });
1872
2029
  }
1873
- function extractHttpStatus(error) {
1874
- if (!error || typeof error !== "object") return null;
1875
- const candidate = error;
1876
- if (typeof candidate.statusCode === "number") return candidate.statusCode;
1877
- if (typeof candidate.status === "number") return candidate.status;
1878
- if (typeof candidate.message === "string") {
1879
- const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
1880
- if (match) return Number.parseInt(match[1], 10);
1881
- }
1882
- return null;
2030
+ function isNextStandaloneOutputFailure(message) {
2031
+ return /next\.?js/i.test(message) && /standalone output/i.test(message);
1883
2032
  }
1884
2033
  function noDeploymentsError(summary, why) {
1885
2034
  return new CliError({