@prisma/cli 3.0.0-alpha.6 → 3.0.0-alpha.7

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.
@@ -4,21 +4,33 @@ import { CliError, authRequiredError, featureUnavailableError, usageError, works
4
4
  import { renderCommandHeader } from "../shell/ui.js";
5
5
  import { writeJsonEvent } from "../shell/output.js";
6
6
  import { canPrompt } from "../shell/runtime.js";
7
- import { textPrompt } from "../shell/prompt.js";
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
10
  import { parseEnvAssignments } from "../lib/app/env-vars.js";
11
+ import { readBunPackageJson } from "../lib/app/bun-project.js";
11
12
  import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
12
- import { resolveProjectTarget } from "../lib/project/resolution.js";
13
+ import { inferTargetName, projectNotFoundError, resolveProjectTarget } from "../lib/project/resolution.js";
14
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
13
15
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
14
- import { PREVIEW_DEFAULT_REGION, createPreviewDeployInteraction } from "../lib/app/preview-interaction.js";
16
+ import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
15
17
  import { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
16
18
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
17
19
  import { createSelectPromptPort } from "./select-prompt-port.js";
18
20
  import { requireAuthenticatedAuthState } from "./auth.js";
19
21
  import { listRealWorkspaceProjects } from "./project.js";
22
+ import { access, readFile } from "node:fs/promises";
23
+ import path from "node:path";
20
24
  import open from "open";
21
25
  //#region src/controllers/app.ts
26
+ const DEPLOY_FRAMEWORKS = [
27
+ "nextjs",
28
+ "hono",
29
+ "tanstack-start"
30
+ ];
31
+ const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
32
+ const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
33
+ const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
22
34
  function isRealMode(context) {
23
35
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
24
36
  }
@@ -80,15 +92,65 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
80
92
  }
81
93
  async function runAppDeploy(context, appName, options) {
82
94
  ensurePreviewAppMode(context);
83
- const buildType = normalizeBuildType(options?.buildType);
84
- assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
85
- const portMapping = parseDeployPortMapping(options?.httpPort);
95
+ const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
96
+ const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
97
+ const skipLocalPin = Boolean(envProjectId || envAppId);
98
+ const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
99
+ if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
100
+ const explicitBuildType = Boolean(options?.buildType && options.buildType !== "auto");
101
+ const branch = await resolveDeployBranch(context, options?.branchName);
102
+ if (options?.httpPort) parseDeployHttpPort(options.httpPort);
103
+ let framework = await resolveDeployFramework(context, {
104
+ requestedFramework: options?.framework,
105
+ requestedBuildType: options?.buildType,
106
+ explicitBuildType
107
+ });
108
+ let runtime = resolveDeployRuntime(options?.httpPort, framework);
109
+ assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
86
110
  const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
87
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, { allowCreate: true });
88
- const selectedApp = await resolveDeploySelection(context, projectId, await listApps(context, provider, projectId), appName);
111
+ const firstDeploy = !skipLocalPin && localPin.kind === "missing";
112
+ const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
113
+ allowCreate: true,
114
+ branch,
115
+ envProjectId,
116
+ localPin
117
+ });
118
+ const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
119
+ explicitAppName: appName,
120
+ explicitAppId: envAppId,
121
+ firstDeploy,
122
+ inferName: () => inferTargetName(context.runtime.cwd)
123
+ });
124
+ await maybeRenderDeploySetupBlock(context, {
125
+ firstDeploy: selectedApp.firstDeploy,
126
+ showSubsequentAnnotations: skipLocalPin,
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));
89
150
  const deployResult = await provider.deployApp({
90
151
  cwd: context.runtime.cwd,
91
152
  projectId,
153
+ branchName: target.branch.name,
92
154
  appId: selectedApp.appId,
93
155
  appName: selectedApp.appName,
94
156
  region: selectedApp.region,
@@ -96,7 +158,7 @@ async function runAppDeploy(context, appName, options) {
96
158
  buildType,
97
159
  portMapping,
98
160
  envVars,
99
- interaction: selectedApp.useInteractiveSelection ? createPreviewDeployInteraction(context) : void 0,
161
+ interaction: void 0,
100
162
  progress: createPreviewDeployProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
101
163
  }).catch((error) => {
102
164
  throw deployFailedError("App deploy failed", error, ["prisma-cli app list-deploys"]);
@@ -106,6 +168,14 @@ async function runAppDeploy(context, appName, options) {
106
168
  name: deployResult.app.name
107
169
  });
108
170
  await context.stateStore.setKnownLiveDeployment(projectId, deployResult.app.id, deployResult.deployment.id);
171
+ const shouldWriteLocalPin = firstDeploy && !skipLocalPin;
172
+ if (shouldWriteLocalPin) {
173
+ await writeLocalResolutionPin(context.runtime.cwd, {
174
+ workspaceId: target.workspace.id,
175
+ projectId: target.project.id
176
+ });
177
+ await ensureLocalResolutionPinGitignore(context.runtime.cwd);
178
+ }
109
179
  return {
110
180
  command: "app.deploy",
111
181
  result: {
@@ -117,7 +187,11 @@ async function runAppDeploy(context, appName, options) {
117
187
  id: deployResult.app.id,
118
188
  name: deployResult.app.name
119
189
  },
120
- deployment: deployResult.deployment
190
+ deployment: deployResult.deployment,
191
+ localPin: shouldWriteLocalPin ? {
192
+ path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
193
+ written: true
194
+ } : void 0
121
195
  },
122
196
  warnings: [],
123
197
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
@@ -130,8 +204,8 @@ async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
130
204
  commandName: "update-env",
131
205
  requireAtLeastOne: true
132
206
  });
133
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
134
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
207
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
208
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
135
209
  if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The resolved project does not have any deployed app yet.");
136
210
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
137
211
  throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
@@ -168,8 +242,8 @@ async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
168
242
  async function runAppListEnv(context, appName, projectRef) {
169
243
  ensurePreviewAppMode(context);
170
244
  emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
171
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
172
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
245
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
246
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
173
247
  if (!selectedApp) return {
174
248
  command: "app.list-env",
175
249
  result: {
@@ -255,8 +329,8 @@ async function runAppListEnv(context, appName, projectRef) {
255
329
  }
256
330
  async function runAppListDeploys(context, appName, projectRef) {
257
331
  ensurePreviewAppMode(context);
258
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
259
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
332
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
333
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
260
334
  if (!selectedApp) return {
261
335
  command: "app.list-deploys",
262
336
  result: {
@@ -292,8 +366,8 @@ async function runAppListDeploys(context, appName, projectRef) {
292
366
  }
293
367
  async function runAppShow(context, appName, projectRef) {
294
368
  ensurePreviewAppMode(context);
295
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
296
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
369
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
370
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
297
371
  if (!selectedApp) return {
298
372
  command: "app.show",
299
373
  result: {
@@ -368,8 +442,8 @@ async function runAppShowDeploy(context, deploymentId) {
368
442
  }
369
443
  async function runAppOpen(context, appName, projectRef) {
370
444
  ensurePreviewAppMode(context);
371
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
372
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
445
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
446
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
373
447
  if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
374
448
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
375
449
  throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
@@ -402,8 +476,8 @@ async function runAppOpen(context, appName, projectRef) {
402
476
  }
403
477
  async function runAppLogs(context, appName, deploymentId, projectRef) {
404
478
  ensurePreviewAppMode(context);
405
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
406
- const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, appName);
479
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef);
480
+ const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
407
481
  if (!context.flags.json && !context.flags.quiet) {
408
482
  const lines = renderCommandHeader(context.ui, {
409
483
  commandLabel: "app logs",
@@ -433,9 +507,9 @@ async function runAppLogs(context, appName, deploymentId, projectRef) {
433
507
  throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
434
508
  });
435
509
  }
436
- async function resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) {
510
+ async function resolveExplicitLogDeployment(context, provider, projectId, branchName, appName, deploymentId) {
437
511
  if (appName) {
438
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
512
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
439
513
  if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
440
514
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
441
515
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -471,7 +545,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, appNam
471
545
  exitCode: 1,
472
546
  nextSteps: ["prisma-cli app list-deploys"]
473
547
  });
474
- const resolvedProjectApp = (await listApps(context, provider, projectId)).find((app) => app.id === shown.app?.id);
548
+ const resolvedProjectApp = (await listApps(context, provider, projectId, branchName)).find((app) => app.id === shown.app?.id);
475
549
  if (!resolvedProjectApp) throw new CliError({
476
550
  code: "DEPLOYMENT_NOT_FOUND",
477
551
  domain: "app",
@@ -490,8 +564,8 @@ async function resolveExplicitLogDeployment(context, provider, projectId, appNam
490
564
  deployment: shown.deployment
491
565
  };
492
566
  }
493
- async function resolveLiveLogDeployment(context, provider, projectId, appName) {
494
- const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
567
+ async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
568
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
495
569
  if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
496
570
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
497
571
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -526,8 +600,8 @@ function writeLogRecord(context, record) {
526
600
  }
527
601
  async function runAppPromote(context, deploymentId, appName, projectRef) {
528
602
  ensurePreviewAppMode(context);
529
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
530
- const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "promote");
603
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
604
+ const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
531
605
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
532
606
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
533
607
  });
@@ -566,8 +640,8 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
566
640
  }
567
641
  async function runAppRollback(context, appName, deploymentId, projectRef) {
568
642
  ensurePreviewAppMode(context);
569
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
570
- const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "rollback");
643
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
644
+ const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
571
645
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
572
646
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
573
647
  });
@@ -608,8 +682,8 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
608
682
  }
609
683
  async function runAppRemove(context, appName, projectRef) {
610
684
  ensurePreviewAppMode(context);
611
- const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
612
- const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "remove");
685
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
686
+ const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
613
687
  await confirmAppRemoval(context, selectedApp);
614
688
  const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
615
689
  throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
@@ -629,30 +703,104 @@ async function runAppRemove(context, appName, projectRef) {
629
703
  nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
630
704
  };
631
705
  }
632
- async function resolveDeploySelection(context, projectId, apps, explicitAppName) {
633
- if (explicitAppName) {
634
- const matched = findAppByName(apps, explicitAppName);
706
+ async function resolveDeployAppSelection(context, projectId, apps, options) {
707
+ if (options.explicitAppName) {
708
+ const matches = findAppsByName(apps, options.explicitAppName);
709
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.explicitAppName, options.firstDeploy);
710
+ const matched = matches[0];
635
711
  if (matched) return {
636
712
  appId: matched.id,
637
- useInteractiveSelection: false
713
+ displayName: matched.name,
714
+ annotation: "set by --app",
715
+ firstDeploy: options.firstDeploy
638
716
  };
639
717
  return {
640
- appName: explicitAppName,
718
+ appName: options.explicitAppName,
641
719
  region: PREVIEW_DEFAULT_REGION,
642
- useInteractiveSelection: false
720
+ displayName: options.explicitAppName,
721
+ annotation: "set by --app",
722
+ firstDeploy: options.firstDeploy
643
723
  };
644
724
  }
645
- const savedSelection = await context.stateStore.readSelectedApp(projectId);
646
- if (savedSelection) {
647
- const matched = apps.find((app) => app.id === savedSelection.id) ?? findAppByName(apps, savedSelection.name);
648
- if (matched) return {
725
+ if (options.explicitAppId) {
726
+ const matched = apps.find((app) => app.id === options.explicitAppId);
727
+ 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");
728
+ return {
649
729
  appId: matched.id,
650
- useInteractiveSelection: false
730
+ displayName: matched.name,
731
+ annotation: `from ${PRISMA_APP_ID_ENV_VAR}`,
732
+ firstDeploy: options.firstDeploy
651
733
  };
652
- 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 deploy in a TTY to choose or create an app again.", ["prisma-cli app deploy"], "app");
653
734
  }
654
- 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-cli app deploy in a TTY to choose or create an app.", ["prisma-cli app deploy --app hello-world"], "app");
655
- return { useInteractiveSelection: true };
735
+ const inferredName = await options.inferName();
736
+ const matches = findAppsByName(apps, inferredName.name);
737
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
738
+ const matched = matches[0];
739
+ if (matched) return {
740
+ appId: matched.id,
741
+ displayName: matched.name,
742
+ annotation: "existing app on this branch",
743
+ firstDeploy: options.firstDeploy
744
+ };
745
+ return {
746
+ appName: inferredName.name,
747
+ region: PREVIEW_DEFAULT_REGION,
748
+ displayName: inferredName.name,
749
+ annotation: inferredName.source === "package-name" ? "created from package.json" : "created from directory name",
750
+ firstDeploy: options.firstDeploy
751
+ };
752
+ }
753
+ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDeploy) {
754
+ if (canPrompt(context)) {
755
+ const createNew = "__create_new_app__";
756
+ const cancel = "__cancel__";
757
+ const selected = await selectPrompt({
758
+ input: context.runtime.stdin,
759
+ output: context.runtime.stderr,
760
+ message: `Multiple apps are named "${targetName}"`,
761
+ choices: [
762
+ ...sortApps(matches).map((app) => ({
763
+ label: `${app.name} (${app.id})`,
764
+ value: app
765
+ })),
766
+ {
767
+ label: `Create a new app named "${targetName}"`,
768
+ value: createNew
769
+ },
770
+ {
771
+ label: "Cancel",
772
+ value: cancel
773
+ }
774
+ ]
775
+ });
776
+ 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");
777
+ if (selected === createNew) return {
778
+ appName: targetName,
779
+ region: PREVIEW_DEFAULT_REGION,
780
+ displayName: targetName,
781
+ annotation: "created from package.json",
782
+ firstDeploy
783
+ };
784
+ return {
785
+ appId: selected.id,
786
+ displayName: selected.name,
787
+ annotation: "selected by you",
788
+ firstDeploy
789
+ };
790
+ }
791
+ throw new CliError({
792
+ code: "APP_AMBIGUOUS",
793
+ domain: "app",
794
+ summary: "App resolution is ambiguous",
795
+ why: `Multiple apps matched "${targetName}".`,
796
+ fix: "Pass --app <name> to choose the app explicitly.",
797
+ meta: { candidates: matches.map((app) => ({
798
+ id: app.id,
799
+ name: app.name
800
+ })) },
801
+ exitCode: 2,
802
+ nextSteps: ["prisma-cli app deploy --app <name>"]
803
+ });
656
804
  }
657
805
  async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
658
806
  if (explicitAppName) {
@@ -767,8 +915,8 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
767
915
  nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
768
916
  });
769
917
  }
770
- async function listApps(context, provider, projectId) {
771
- return provider.listApps(projectId).then(sortApps).catch((error) => {
918
+ async function listApps(context, provider, projectId, branchName) {
919
+ return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
772
920
  if (isMissingProjectError(error)) throw new CliError({
773
921
  code: "PROJECT_NOT_FOUND",
774
922
  domain: "project",
@@ -819,6 +967,16 @@ async function requireProviderAndProjectContext(context, explicitProject, option
819
967
  projectId: target.project.id
820
968
  };
821
969
  }
970
+ async function requireProviderAndDeployProjectContext(context, explicitProject, options) {
971
+ const { client, provider } = await requirePreviewAppProviderWithClient(context);
972
+ const target = await resolveDeployProjectContext(context, client, provider, explicitProject, options);
973
+ return {
974
+ client,
975
+ provider,
976
+ target,
977
+ projectId: target.project.id
978
+ };
979
+ }
822
980
  async function resolveProjectContext(context, client, provider, explicitProject, options) {
823
981
  const authState = await requireAuthenticatedAuthState(context);
824
982
  if (!authState.workspace) throw workspaceRequiredError();
@@ -845,17 +1003,412 @@ async function resolveProjectContext(context, client, provider, explicitProject,
845
1003
  prompt: createSelectPromptPort(context),
846
1004
  remember: true
847
1005
  });
848
- const branchName = await context.stateStore.read().then((state) => state.branch.active);
1006
+ const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
849
1007
  return {
850
1008
  ...resolved,
851
1009
  branch: {
852
- name: branchName,
853
- kind: toBranchKind(branchName)
1010
+ name: branch.name,
1011
+ kind: toBranchKind(branch.name)
1012
+ }
1013
+ };
1014
+ }
1015
+ async function resolveDeployProjectContext(context, client, provider, explicitProject, options) {
1016
+ const workspace = (await requireAuthenticatedAuthState(context)).workspace;
1017
+ if (!workspace) throw workspaceRequiredError();
1018
+ const branch = options.branch ?? await resolveDeployBranch(context, void 0);
1019
+ const projects = await listRealWorkspaceProjects(client, workspace);
1020
+ const createProject = options.allowCreate ? async (name) => {
1021
+ const project = await provider.createProject({ name }).catch((error) => {
1022
+ throw createProjectOnFirstDeployError({
1023
+ error,
1024
+ inferredName: name,
1025
+ workspaceName: workspace.name
1026
+ });
1027
+ });
1028
+ return {
1029
+ id: project.id,
1030
+ name: project.name,
1031
+ workspace
1032
+ };
1033
+ } : void 0;
1034
+ if (explicitProject) return withDeployBranch(await resolveProjectTarget({
1035
+ context,
1036
+ workspace,
1037
+ explicitProject,
1038
+ listProjects: async () => projects,
1039
+ createProject,
1040
+ allowCreate: options.allowCreate,
1041
+ prompt: createSelectPromptPort(context),
1042
+ remember: true
1043
+ }), branch);
1044
+ if (options.envProjectId) {
1045
+ const project = projects.find((candidate) => candidate.id === options.envProjectId);
1046
+ if (!project) throw projectNotFoundError(options.envProjectId, workspace);
1047
+ return withDeployBranch({
1048
+ workspace,
1049
+ project: toProjectSummary(project),
1050
+ resolution: {
1051
+ projectSource: "env",
1052
+ targetName: options.envProjectId,
1053
+ targetNameSource: "env"
1054
+ }
1055
+ }, branch);
1056
+ }
1057
+ if (options.localPin.kind === "present") {
1058
+ if (options.localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
1059
+ const project = projects.find((candidate) => candidate.id === options.localPin.pin.projectId);
1060
+ if (!project) throw localResolutionPinStaleError();
1061
+ return withDeployBranch({
1062
+ workspace,
1063
+ project: toProjectSummary(project),
1064
+ resolution: {
1065
+ projectSource: "local-pin",
1066
+ targetName: project.name,
1067
+ targetNameSource: "local-pin"
1068
+ }
1069
+ }, branch);
1070
+ }
1071
+ return withDeployBranch(await resolveProjectTarget({
1072
+ context,
1073
+ workspace,
1074
+ listProjects: async () => projects,
1075
+ createProject,
1076
+ allowCreate: options.allowCreate,
1077
+ prompt: createSelectPromptPort(context),
1078
+ remember: true
1079
+ }), branch);
1080
+ }
1081
+ function withDeployBranch(target, branch) {
1082
+ return {
1083
+ ...target,
1084
+ branch: {
1085
+ name: branch.name,
1086
+ kind: toBranchKind(branch.name)
854
1087
  }
855
1088
  };
856
1089
  }
1090
+ function toProjectSummary(project) {
1091
+ return {
1092
+ id: project.id,
1093
+ name: project.name
1094
+ };
1095
+ }
857
1096
  function toBranchKind(name) {
858
- return name === "production" ? "production" : "preview";
1097
+ return name === "production" || name === "main" ? "production" : "preview";
1098
+ }
1099
+ async function resolveDeployBranch(context, explicitBranchName) {
1100
+ if (explicitBranchName) return {
1101
+ name: explicitBranchName,
1102
+ annotation: "set by --branch"
1103
+ };
1104
+ const gitBranch = await readLocalGitBranch(context.runtime.cwd);
1105
+ if (gitBranch) return {
1106
+ name: gitBranch,
1107
+ annotation: "from local Git branch"
1108
+ };
1109
+ return {
1110
+ name: "main",
1111
+ annotation: "default"
1112
+ };
1113
+ }
1114
+ async function readLocalGitBranch(cwd) {
1115
+ const headPath = await resolveGitHeadPath(path.join(cwd, ".git"));
1116
+ if (!headPath) return null;
1117
+ try {
1118
+ const head = (await readFile(headPath, "utf8")).trim();
1119
+ if (head.startsWith("ref: refs/heads/")) return head.slice(16);
1120
+ } catch {
1121
+ return null;
1122
+ }
1123
+ return null;
1124
+ }
1125
+ async function resolveGitHeadPath(gitPath) {
1126
+ try {
1127
+ const raw = await readFile(gitPath, "utf8");
1128
+ if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
1129
+ } catch {}
1130
+ try {
1131
+ await access(path.join(gitPath, "HEAD"));
1132
+ return path.join(gitPath, "HEAD");
1133
+ } catch {
1134
+ return null;
1135
+ }
1136
+ }
1137
+ async function resolveDeployFramework(context, options) {
1138
+ if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1139
+ if (options.explicitBuildType) {
1140
+ const buildType = normalizeBuildType(options.requestedBuildType);
1141
+ if (buildType !== "auto") return {
1142
+ key: buildType,
1143
+ buildType,
1144
+ displayName: formatBuildTypeName(buildType),
1145
+ annotation: "set by --build-type"
1146
+ };
1147
+ }
1148
+ const detected = await detectDeployFramework(context.runtime.cwd);
1149
+ if (detected) return detected;
1150
+ throw frameworkNotDetectedError(context.runtime.cwd);
1151
+ }
1152
+ function resolveDeployRuntime(requestedHttpPort, framework) {
1153
+ if (requestedHttpPort) return {
1154
+ port: parseDeployHttpPort(requestedHttpPort),
1155
+ annotation: "set by --http-port"
1156
+ };
1157
+ return {
1158
+ port: FRAMEWORK_DEFAULT_HTTP_PORT,
1159
+ annotation: `${framework.displayName} default`
1160
+ };
1161
+ }
1162
+ async function detectDeployFramework(cwd) {
1163
+ const packageJson = await readBunPackageJson(cwd);
1164
+ const nextConfig = await detectNextConfig(cwd);
1165
+ if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
1166
+ key: "nextjs",
1167
+ buildType: "nextjs",
1168
+ displayName: "Next.js",
1169
+ annotation: nextConfig.standalone ? "standalone output detected" : nextConfig.exists ? "detected from next.config" : "detected from package.json"
1170
+ };
1171
+ if (hasPackageDependency(packageJson, "hono")) return {
1172
+ key: "hono",
1173
+ buildType: "bun",
1174
+ displayName: "Hono",
1175
+ annotation: "detected from package.json"
1176
+ };
1177
+ if (hasPackageDependency(packageJson, "@tanstack/start")) return {
1178
+ key: "tanstack-start",
1179
+ buildType: "tanstack-start",
1180
+ displayName: "TanStack Start",
1181
+ annotation: "detected from package.json"
1182
+ };
1183
+ return null;
1184
+ }
1185
+ async function detectNextConfig(cwd) {
1186
+ for (const candidate of [
1187
+ "next.config.js",
1188
+ "next.config.mjs",
1189
+ "next.config.cjs",
1190
+ "next.config.ts"
1191
+ ]) {
1192
+ const filePath = path.join(cwd, candidate);
1193
+ try {
1194
+ const content = await readFile(filePath, "utf8");
1195
+ return {
1196
+ exists: true,
1197
+ standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
1198
+ };
1199
+ } catch (error) {
1200
+ if (error.code !== "ENOENT") throw error;
1201
+ }
1202
+ }
1203
+ return {
1204
+ exists: false,
1205
+ standalone: false
1206
+ };
1207
+ }
1208
+ function hasPackageDependency(packageJson, dependencyName) {
1209
+ return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
1210
+ }
1211
+ function hasDependency(dependencies, dependencyName) {
1212
+ return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
1213
+ }
1214
+ function frameworkFromUserFacingValue(value, annotation) {
1215
+ switch (value.trim().toLowerCase()) {
1216
+ case "next":
1217
+ case "next.js":
1218
+ case "nextjs": return {
1219
+ key: "nextjs",
1220
+ buildType: "nextjs",
1221
+ displayName: "Next.js",
1222
+ annotation
1223
+ };
1224
+ case "hono": return {
1225
+ key: "hono",
1226
+ buildType: "bun",
1227
+ displayName: "Hono",
1228
+ annotation
1229
+ };
1230
+ case "tanstack":
1231
+ case "tanstack-start":
1232
+ case "@tanstack/start": return {
1233
+ key: "tanstack-start",
1234
+ buildType: "tanstack-start",
1235
+ displayName: "TanStack Start",
1236
+ annotation
1237
+ };
1238
+ default: throw frameworkNotDetectedError(void 0, value);
1239
+ }
1240
+ }
1241
+ function frameworkNotDetectedError(cwd, requestedFramework) {
1242
+ const supported = "Next.js, Hono, TanStack Start";
1243
+ const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
1244
+ return new CliError({
1245
+ code: "FRAMEWORK_NOT_DETECTED",
1246
+ domain: "app",
1247
+ summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
1248
+ why: `Supported Beta frameworks: ${supported}.`,
1249
+ fix: "Add one of these frameworks as a dependency, or pass --framework <nextjs|hono|tanstack-start>.",
1250
+ exitCode: 2,
1251
+ nextSteps: [
1252
+ "prisma-cli app deploy --framework nextjs",
1253
+ "prisma-cli app deploy --framework hono",
1254
+ "prisma-cli app deploy --framework tanstack-start"
1255
+ ]
1256
+ });
1257
+ }
1258
+ async function maybeRenderDeploySetupBlock(context, details) {
1259
+ if (context.flags.json || context.flags.quiet) return;
1260
+ const title = `${details.firstDeploy ? "Set up" : "Deploying"} ${formatDeployDirectory(context.runtime.cwd)}`;
1261
+ const rows = details.firstDeploy ? [
1262
+ {
1263
+ label: "Workspace",
1264
+ value: details.workspaceName
1265
+ },
1266
+ {
1267
+ label: "Project",
1268
+ value: details.projectName,
1269
+ annotation: details.projectAnnotation
1270
+ },
1271
+ {
1272
+ label: "Branch",
1273
+ value: details.branchName,
1274
+ annotation: details.branchAnnotation
1275
+ },
1276
+ {
1277
+ label: "App",
1278
+ value: details.appName,
1279
+ annotation: details.appAnnotation
1280
+ },
1281
+ {
1282
+ label: "Framework",
1283
+ value: details.framework.displayName,
1284
+ annotation: details.framework.annotation
1285
+ },
1286
+ {
1287
+ label: "Runtime",
1288
+ value: `HTTP ${details.runtime.port}`,
1289
+ annotation: details.runtime.annotation
1290
+ }
1291
+ ] : [
1292
+ {
1293
+ label: "Workspace",
1294
+ value: details.workspaceName
1295
+ },
1296
+ {
1297
+ label: "Project",
1298
+ value: details.projectName,
1299
+ annotation: details.showSubsequentAnnotations ? details.projectAnnotation : void 0
1300
+ },
1301
+ {
1302
+ label: "Branch",
1303
+ value: details.branchName,
1304
+ annotation: details.showSubsequentAnnotations ? details.branchAnnotation : void 0
1305
+ },
1306
+ {
1307
+ label: "App",
1308
+ value: details.appName,
1309
+ annotation: details.showSubsequentAnnotations ? details.appAnnotation : void 0
1310
+ }
1311
+ ];
1312
+ const lines = details.firstDeploy ? [
1313
+ title,
1314
+ "",
1315
+ ...renderDeploySetupRows(context, rows),
1316
+ ""
1317
+ ] : [
1318
+ title,
1319
+ ...renderDeploySetupRows(context, rows),
1320
+ ""
1321
+ ];
1322
+ context.output.stderr.write(`${lines.join("\n")}\n`);
1323
+ }
1324
+ async function maybeCustomizeDeploySettings(context, options) {
1325
+ if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitBuildType || options.explicitHttpPort || !canPrompt(context)) return {
1326
+ framework: options.framework,
1327
+ runtime: options.runtime
1328
+ };
1329
+ if (!await confirmPrompt({
1330
+ input: context.runtime.stdin,
1331
+ output: context.runtime.stderr,
1332
+ message: "Customize settings?",
1333
+ initialValue: false
1334
+ })) return {
1335
+ framework: options.framework,
1336
+ runtime: options.runtime
1337
+ };
1338
+ const framework = frameworkFromUserFacingValue(await selectPrompt({
1339
+ input: context.runtime.stdin,
1340
+ output: context.runtime.stderr,
1341
+ message: `Framework (${options.framework.displayName})`,
1342
+ choices: DEPLOY_FRAMEWORKS.map((framework) => ({
1343
+ label: frameworkDisplayName(framework),
1344
+ value: framework
1345
+ }))
1346
+ }), "set by you");
1347
+ const requestedPort = await textPrompt({
1348
+ input: context.runtime.stdin,
1349
+ output: context.runtime.stderr,
1350
+ message: `HTTP port (${options.runtime.port})`,
1351
+ placeholder: String(options.runtime.port),
1352
+ validate: validateDeployHttpPortText
1353
+ });
1354
+ const runtime = {
1355
+ port: requestedPort.trim() ? parseDeployHttpPort(requestedPort) : options.runtime.port,
1356
+ annotation: "set by you"
1357
+ };
1358
+ const changedRows = [framework.key !== options.framework.key ? {
1359
+ label: "Framework",
1360
+ value: framework.displayName,
1361
+ annotation: framework.annotation
1362
+ } : null, runtime.port !== options.runtime.port ? {
1363
+ label: "Runtime",
1364
+ value: `HTTP ${runtime.port}`,
1365
+ annotation: runtime.annotation
1366
+ } : null].filter((row) => Boolean(row));
1367
+ if (changedRows.length > 0 && !context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderDeploySetupRows(context, changedRows).join("\n")}\n\n`);
1368
+ return {
1369
+ framework,
1370
+ runtime
1371
+ };
1372
+ }
1373
+ function renderDeploySetupRows(context, rows) {
1374
+ const labelWidth = Math.max(...rows.map((row) => row.label.length));
1375
+ const valueWidth = Math.max(...rows.map((row) => row.value.length));
1376
+ return rows.map((row) => {
1377
+ return ` ${row.label.padEnd(labelWidth)} ${row.value.padEnd(valueWidth)}${row.annotation ? ` ${context.ui.dim(row.annotation)}` : ""}`.trimEnd();
1378
+ });
1379
+ }
1380
+ function annotationForProjectResolution(resolution) {
1381
+ switch (resolution.projectSource) {
1382
+ case "explicit": return "set by --project";
1383
+ case "env": return `from ${PRISMA_PROJECT_ID_ENV_VAR}`;
1384
+ case "local-pin": return "from local pin";
1385
+ case "created": return resolution.targetNameSource === "directory-name" ? "created from directory name" : "created from package.json";
1386
+ case "package-name":
1387
+ case "directory-name": return "linked to existing project";
1388
+ case "platform-mapping":
1389
+ case "remembered-local": return "linked to existing project";
1390
+ case "prompt": return "selected by you";
1391
+ }
1392
+ }
1393
+ function frameworkDisplayName(framework) {
1394
+ switch (framework) {
1395
+ case "nextjs": return "Next.js";
1396
+ case "hono": return "Hono";
1397
+ case "tanstack-start": return "TanStack Start";
1398
+ }
1399
+ }
1400
+ function validateDeployHttpPortText(value) {
1401
+ if (!value?.trim()) return;
1402
+ try {
1403
+ parseDeployHttpPort(value);
1404
+ return;
1405
+ } catch (error) {
1406
+ return error instanceof CliError ? error.summary : String(error);
1407
+ }
1408
+ }
1409
+ function formatDeployDirectory(cwd) {
1410
+ const basename = path.basename(cwd);
1411
+ return basename ? `./${basename}` : ".";
859
1412
  }
860
1413
  async function readCurrentWorkspaceId(context) {
861
1414
  const state = await context.stateStore.read();
@@ -891,9 +1444,12 @@ function parseLocalPort(requestedPort) {
891
1444
  }
892
1445
  function parseDeployPortMapping(requestedPort) {
893
1446
  if (!requestedPort) return;
1447
+ return { http: parseDeployHttpPort(requestedPort) };
1448
+ }
1449
+ function parseDeployHttpPort(requestedPort) {
894
1450
  const port = Number.parseInt(requestedPort, 10);
895
1451
  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");
896
- return { http: port };
1452
+ return port;
897
1453
  }
898
1454
  function ensurePreviewAppMode(context) {
899
1455
  if (isRealMode(context)) return;
@@ -911,6 +1467,22 @@ function deployFailedError(summary, error, nextSteps) {
911
1467
  nextSteps
912
1468
  });
913
1469
  }
1470
+ function localResolutionPinStaleError() {
1471
+ return new CliError({
1472
+ code: "LOCAL_STATE_STALE",
1473
+ domain: "project",
1474
+ summary: "Local project binding is stale",
1475
+ why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
1476
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} and re-run to re-bootstrap.`,
1477
+ meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
1478
+ exitCode: 1,
1479
+ nextSteps: ["prisma-cli app deploy"]
1480
+ });
1481
+ }
1482
+ function readDeployEnvOverride(context, name) {
1483
+ const value = context.runtime.env[name]?.trim();
1484
+ return value ? value : void 0;
1485
+ }
914
1486
  /**
915
1487
  * `app deploy` falls into "create a new project on first deploy" when no
916
1488
  * existing project matches the package.json name (or the cwd basename as a
@@ -1032,6 +1604,9 @@ function isMissingProjectError(error) {
1032
1604
  function findAppByName(apps, name) {
1033
1605
  return apps.find((app) => app.name === name);
1034
1606
  }
1607
+ function findAppsByName(apps, name) {
1608
+ return apps.filter((app) => app.name === name);
1609
+ }
1035
1610
  function sortApps(apps) {
1036
1611
  return apps.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
1037
1612
  }