@prisma/cli 3.0.0-alpha.3 → 3.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,22 @@
1
- import { UnsafeConfigWriteError, assertLinkedProjectIdWritable, readLinkedProjectId, writeLinkedProjectId } from "../adapters/config.js";
2
1
  import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
3
2
  import { FileTokenStorage } from "../adapters/token-storage.js";
4
- import { CliError, authRequiredError, featureUnavailableError, usageError } from "../shell/errors.js";
3
+ import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
5
4
  import { renderCommandHeader } from "../shell/ui.js";
6
5
  import { writeJsonEvent } from "../shell/output.js";
7
6
  import { canPrompt } from "../shell/runtime.js";
8
7
  import { textPrompt } from "../shell/prompt.js";
9
8
  import { requireComputeAuth } from "../lib/auth/guard.js";
9
+ import { readAuthState } from "../lib/auth/auth-ops.js";
10
10
  import { parseEnvAssignments } from "../lib/app/env-vars.js";
11
11
  import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
12
- import { projectNotFoundError } from "../use-cases/project.js";
12
+ import { resolveProjectTarget } from "../lib/project/resolution.js";
13
13
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
14
14
  import { PREVIEW_DEFAULT_REGION, createPreviewDeployInteraction } from "../lib/app/preview-interaction.js";
15
15
  import { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
16
16
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
17
17
  import { createSelectPromptPort } from "./select-prompt-port.js";
18
- import path from "node:path";
18
+ import { requireAuthenticatedAuthState } from "./auth.js";
19
+ import { listRealWorkspaceProjects } from "./project.js";
19
20
  import open from "open";
20
21
  //#region src/controllers/app.ts
21
22
  function isRealMode(context) {
@@ -83,8 +84,7 @@ async function runAppDeploy(context, appName, options) {
83
84
  assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
84
85
  const portMapping = parseDeployPortMapping(options?.httpPort);
85
86
  const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
86
- const provider = await requirePreviewAppProvider(context);
87
- const projectId = await resolveProjectIdForDeploy(context, provider);
87
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, { allowCreate: true });
88
88
  const selectedApp = await resolveDeploySelection(context, projectId, await listApps(context, provider, projectId), appName);
89
89
  const deployResult = await provider.deployApp({
90
90
  cwd: context.runtime.cwd,
@@ -109,7 +109,10 @@ async function runAppDeploy(context, appName, options) {
109
109
  return {
110
110
  command: "app.deploy",
111
111
  result: {
112
- projectId: deployResult.projectId,
112
+ workspace: target.workspace,
113
+ project: target.project,
114
+ branch: target.branch,
115
+ resolution: target.resolution,
113
116
  app: {
114
117
  id: deployResult.app.id,
115
118
  name: deployResult.app.name
@@ -120,17 +123,16 @@ async function runAppDeploy(context, appName, options) {
120
123
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
121
124
  };
122
125
  }
123
- async function runAppUpdateEnv(context, appName, envAssignments) {
126
+ async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
124
127
  ensurePreviewAppMode(context);
125
128
  emitLegacyEnvDeprecationWarning(context, "app update-env", "project env add");
126
129
  const envVars = parseEnvAssignments(envAssignments, {
127
130
  commandName: "update-env",
128
131
  requireAtLeastOne: true
129
132
  });
130
- const projectId = await requireLinkedProjectId(context);
131
- const provider = await requirePreviewAppProvider(context);
133
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
132
134
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
133
- if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The linked project does not have any deployed app yet.");
135
+ if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The resolved project does not have any deployed app yet.");
134
136
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
135
137
  throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
136
138
  });
@@ -163,11 +165,10 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
163
165
  nextSteps: ["prisma-cli app list-env", `prisma-cli app show-deploy ${updateResult.deployment.id}`]
164
166
  };
165
167
  }
166
- async function runAppListEnv(context, appName) {
168
+ async function runAppListEnv(context, appName, projectRef) {
167
169
  ensurePreviewAppMode(context);
168
170
  emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
169
- const projectId = await requireLinkedProjectId(context);
170
- const provider = await requirePreviewAppProvider(context);
171
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
171
172
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
172
173
  if (!selectedApp) return {
173
174
  command: "app.list-env",
@@ -252,10 +253,9 @@ async function runAppListEnv(context, appName) {
252
253
  nextSteps: deployment.id ? [`prisma-cli app show-deploy ${deployment.id}`] : ["prisma-cli app deploy"]
253
254
  };
254
255
  }
255
- async function runAppListDeploys(context, appName) {
256
+ async function runAppListDeploys(context, appName, projectRef) {
256
257
  ensurePreviewAppMode(context);
257
- const projectId = await requireLinkedProjectId(context);
258
- const provider = await requirePreviewAppProvider(context);
258
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
259
259
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
260
260
  if (!selectedApp) return {
261
261
  command: "app.list-deploys",
@@ -290,10 +290,9 @@ async function runAppListDeploys(context, appName) {
290
290
  nextSteps: deployments.length > 0 ? [`prisma-cli app show-deploy ${deployments[0]?.id}`] : ["prisma-cli app deploy"]
291
291
  };
292
292
  }
293
- async function runAppShow(context, appName) {
293
+ async function runAppShow(context, appName, projectRef) {
294
294
  ensurePreviewAppMode(context);
295
- const projectId = await requireLinkedProjectId(context);
296
- const provider = await requirePreviewAppProvider(context);
295
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
297
296
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
298
297
  if (!selectedApp) return {
299
298
  command: "app.show",
@@ -347,8 +346,9 @@ async function runAppShowDeploy(context, deploymentId) {
347
346
  exitCode: 1,
348
347
  nextSteps: ["prisma-cli app list-deploys"]
349
348
  });
350
- const linkedProjectId = deployment?.app ? await readLinkedProjectId(context.runtime.cwd) : null;
351
- const knownLiveDeploymentId = deployment?.app && linkedProjectId ? await context.stateStore.readKnownLiveDeployment(linkedProjectId, deployment.app.id) : null;
349
+ const workspaceId = deployment?.app ? await readCurrentWorkspaceId(context) : null;
350
+ const rememberedProject = workspaceId ? await context.stateStore.readRememberedProject(workspaceId) : null;
351
+ const knownLiveDeploymentId = deployment?.app && rememberedProject ? await context.stateStore.readKnownLiveDeployment(rememberedProject.id, deployment.app.id) : null;
352
352
  const providerLiveDeploymentId = deployment.app?.liveDeploymentId ?? null;
353
353
  return {
354
354
  command: "app.show-deploy",
@@ -366,12 +366,11 @@ async function runAppShowDeploy(context, deploymentId) {
366
366
  nextSteps: []
367
367
  };
368
368
  }
369
- async function runAppOpen(context, appName) {
369
+ async function runAppOpen(context, appName, projectRef) {
370
370
  ensurePreviewAppMode(context);
371
- const projectId = await requireLinkedProjectId(context);
372
- const provider = await requirePreviewAppProvider(context);
371
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
373
372
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
374
- if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The linked project does not have any deployed app yet.");
373
+ if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
375
374
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
376
375
  throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
377
376
  });
@@ -401,10 +400,9 @@ async function runAppOpen(context, appName) {
401
400
  nextSteps: ["prisma-cli app show", `prisma-cli app show-deploy ${liveDeployment.id}`]
402
401
  };
403
402
  }
404
- async function runAppLogs(context, appName, deploymentId) {
403
+ async function runAppLogs(context, appName, deploymentId, projectRef) {
405
404
  ensurePreviewAppMode(context);
406
- const projectId = await requireLinkedProjectId(context);
407
- const provider = await requirePreviewAppProvider(context);
405
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
408
406
  const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, appName);
409
407
  if (!context.flags.json && !context.flags.quiet) {
410
408
  const lines = renderCommandHeader(context.ui, {
@@ -438,7 +436,7 @@ async function runAppLogs(context, appName, deploymentId) {
438
436
  async function resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) {
439
437
  if (appName) {
440
438
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
441
- if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The linked project does not have any deployed app yet.");
439
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
442
440
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
443
441
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
444
442
  });
@@ -473,28 +471,28 @@ async function resolveExplicitLogDeployment(context, provider, projectId, appNam
473
471
  exitCode: 1,
474
472
  nextSteps: ["prisma-cli app list-deploys"]
475
473
  });
476
- const linkedProjectApp = (await listApps(context, provider, projectId)).find((app) => app.id === shown.app?.id);
477
- if (!linkedProjectApp) throw new CliError({
474
+ const resolvedProjectApp = (await listApps(context, provider, projectId)).find((app) => app.id === shown.app?.id);
475
+ if (!resolvedProjectApp) throw new CliError({
478
476
  code: "DEPLOYMENT_NOT_FOUND",
479
477
  domain: "app",
480
- summary: `Deployment "${deploymentId}" not found in the linked project`,
481
- why: "The requested deployment does not belong to an app in the linked project.",
478
+ summary: `Deployment "${deploymentId}" not found in the resolved project`,
479
+ why: "The requested deployment does not belong to an app in the resolved project.",
482
480
  fix: "Run prisma-cli app list-deploys to choose an available deployment id for this project.",
483
481
  exitCode: 1,
484
482
  nextSteps: ["prisma-cli app list-deploys"]
485
483
  });
486
484
  await context.stateStore.setSelectedApp(projectId, {
487
- id: linkedProjectApp.id,
488
- name: linkedProjectApp.name
485
+ id: resolvedProjectApp.id,
486
+ name: resolvedProjectApp.name
489
487
  });
490
488
  return {
491
- app: linkedProjectApp,
489
+ app: resolvedProjectApp,
492
490
  deployment: shown.deployment
493
491
  };
494
492
  }
495
493
  async function resolveLiveLogDeployment(context, provider, projectId, appName) {
496
494
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
497
- if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The linked project does not have any deployed app yet.");
495
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
498
496
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
499
497
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
500
498
  });
@@ -526,10 +524,9 @@ function writeLogRecord(context, record) {
526
524
  if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
527
525
  }
528
526
  }
529
- async function runAppPromote(context, deploymentId, appName) {
527
+ async function runAppPromote(context, deploymentId, appName, projectRef) {
530
528
  ensurePreviewAppMode(context);
531
- const projectId = await requireLinkedProjectId(context);
532
- const provider = await requirePreviewAppProvider(context);
529
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
533
530
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "promote");
534
531
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
535
532
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -567,10 +564,9 @@ async function runAppPromote(context, deploymentId, appName) {
567
564
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
568
565
  };
569
566
  }
570
- async function runAppRollback(context, appName, deploymentId) {
567
+ async function runAppRollback(context, appName, deploymentId, projectRef) {
571
568
  ensurePreviewAppMode(context);
572
- const projectId = await requireLinkedProjectId(context);
573
- const provider = await requirePreviewAppProvider(context);
569
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
574
570
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "rollback");
575
571
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
576
572
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -610,10 +606,9 @@ async function runAppRollback(context, appName, deploymentId) {
610
606
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
611
607
  };
612
608
  }
613
- async function runAppRemove(context, appName) {
609
+ async function runAppRemove(context, appName, projectRef) {
614
610
  ensurePreviewAppMode(context);
615
- const projectId = await requireLinkedProjectId(context);
616
- const provider = await requirePreviewAppProvider(context);
611
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
617
612
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "remove");
618
613
  await confirmAppRemoval(context, selectedApp);
619
614
  const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
@@ -654,7 +649,7 @@ async function resolveDeploySelection(context, projectId, apps, explicitAppName)
654
649
  appId: matched.id,
655
650
  useInteractiveSelection: false
656
651
  };
657
- if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the linked project.", "Pass --app <name>, or rerun prisma-cli app deploy in a TTY to choose or create an app again.", ["prisma-cli app deploy"], "app");
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");
658
653
  }
659
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");
660
655
  return { useInteractiveSelection: true };
@@ -662,14 +657,14 @@ async function resolveDeploySelection(context, projectId, apps, explicitAppName)
662
657
  async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
663
658
  if (explicitAppName) {
664
659
  const matched = findAppByName(apps, explicitAppName);
665
- if (!matched) throw usageError("Selected app does not exist in the linked project", `The app "${explicitAppName}" could not be found in linked project "${projectId}".`, "Pass the name of an existing app, or rerun prisma-cli app list-deploys in a TTY to choose one.", ["prisma-cli app list-deploys"], "app");
660
+ if (!matched) throw usageError("Selected app does not exist in the resolved project", `The app "${explicitAppName}" could not be found in resolved project "${projectId}".`, "Pass the name of an existing app, or rerun prisma-cli app list-deploys in a TTY to choose one.", ["prisma-cli app list-deploys"], "app");
666
661
  return matched;
667
662
  }
668
663
  const savedSelection = await context.stateStore.readSelectedApp(projectId);
669
664
  if (savedSelection) {
670
665
  const matched = apps.find((app) => app.id === savedSelection.id) ?? findAppByName(apps, savedSelection.name);
671
666
  if (matched) return matched;
672
- if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the linked project.", "Pass --app <name>, or rerun prisma-cli app list-deploys in a TTY to choose an available app.", ["prisma-cli app list-deploys"], "app");
667
+ if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the resolved project.", "Pass --app <name>, or rerun prisma-cli app list-deploys in a TTY to choose an available app.", ["prisma-cli app list-deploys"], "app");
673
668
  }
674
669
  if (apps.length === 0) return null;
675
670
  if (!canPrompt(context)) throw usageError("App selection required in non-interactive mode", "This command cannot choose an app in the current mode.", "Pass --app <name>, or rerun prisma-cli app list-deploys in a TTY to choose an app.", ["prisma-cli app list-deploys"], "app");
@@ -685,7 +680,7 @@ async function resolveExistingAppSelection(context, projectId, apps, explicitApp
685
680
  async function requireReleaseAppSelection(context, projectId, apps, explicitAppName, commandName) {
686
681
  const selectedApp = await resolveExistingAppSelection(context, projectId, apps, explicitAppName);
687
682
  if (selectedApp) return selectedApp;
688
- throw usageError(`App ${commandName} requires an existing app`, "The linked project does not have an app that can be selected for this command.", `Deploy an app first, or rerun prisma-cli app ${commandName} with --app <name> after an app exists.`, ["prisma-cli app deploy", "prisma-cli app list-deploys"], "app");
683
+ throw usageError(`App ${commandName} requires an existing app`, "The resolved project does not have an app that can be selected for this command.", `Deploy an app first, or rerun prisma-cli app ${commandName} with --app <name> after an app exists.`, ["prisma-cli app deploy", "prisma-cli app list-deploys"], "app");
689
684
  }
690
685
  async function confirmAppRemoval(context, app) {
691
686
  if (context.flags.yes) return;
@@ -774,18 +769,29 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
774
769
  }
775
770
  async function listApps(context, provider, projectId) {
776
771
  return provider.listApps(projectId).then(sortApps).catch((error) => {
777
- if (isMissingProjectError(error)) throw projectNotFoundError(`The linked project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`, "Run prisma-cli project show to inspect the current link, then relink the repo or rerun prisma-cli app deploy to bootstrap a new project.", [
778
- "prisma-cli project show",
779
- "prisma-cli project link",
780
- "prisma-cli app deploy"
781
- ]);
772
+ if (isMissingProjectError(error)) throw new CliError({
773
+ code: "PROJECT_NOT_FOUND",
774
+ domain: "project",
775
+ summary: "Project not found",
776
+ why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
777
+ fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect resolution for this directory.",
778
+ exitCode: 1,
779
+ nextSteps: ["prisma-cli project show", "prisma-cli app deploy --project <id-or-name>"]
780
+ });
782
781
  throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
783
782
  });
784
783
  }
785
784
  async function requirePreviewAppProvider(context) {
785
+ const { provider } = await requirePreviewAppProviderWithClient(context);
786
+ return provider;
787
+ }
788
+ async function requirePreviewAppProviderWithClient(context) {
786
789
  const client = await requireComputeAuth(context.runtime.env);
787
790
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
788
- return createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env));
791
+ return {
792
+ client,
793
+ provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
794
+ };
789
795
  }
790
796
  function createPreviewLogAuthOptions(env) {
791
797
  const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
@@ -803,42 +809,58 @@ function createPreviewLogAuthOptions(env) {
803
809
  }
804
810
  };
805
811
  }
806
- async function requireLinkedProjectId(context) {
807
- const projectId = await readLinkedProjectId(context.runtime.cwd);
808
- if (!projectId) throw new CliError({
809
- code: "PROJECT_NOT_LINKED",
810
- domain: "project",
811
- summary: "Project link required",
812
- why: "This command needs a linked project for the current repo.",
813
- fix: "Run prisma-cli project link before deploying or inspecting app deployments.",
814
- exitCode: 1,
815
- nextSteps: ["prisma-cli project link"]
816
- });
817
- return projectId;
812
+ async function requireProviderAndProjectContext(context, explicitProject, options) {
813
+ const { client, provider } = await requirePreviewAppProviderWithClient(context);
814
+ const target = await resolveProjectContext(context, client, provider, explicitProject, options);
815
+ return {
816
+ client,
817
+ provider,
818
+ target,
819
+ projectId: target.project.id
820
+ };
818
821
  }
819
- async function resolveProjectIdForDeploy(context, provider) {
820
- const linkedProjectId = await readLinkedProjectId(context.runtime.cwd);
821
- if (linkedProjectId) return linkedProjectId;
822
- await assertProjectLinkWritableForDeploy(context);
823
- const projectName = path.basename(context.runtime.cwd);
824
- const project = await provider.createProject({ name: projectName }).catch((error) => {
825
- throw deployFailedError("Failed to create project for first deploy", error, ["prisma-cli app deploy"]);
822
+ async function resolveProjectContext(context, client, provider, explicitProject, options) {
823
+ const authState = await requireAuthenticatedAuthState(context);
824
+ if (!authState.workspace) throw workspaceRequiredError();
825
+ const resolved = await resolveProjectTarget({
826
+ context,
827
+ workspace: authState.workspace,
828
+ explicitProject,
829
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
830
+ createProject: options?.allowCreate ? async (name) => {
831
+ const project = await provider.createProject({ name }).catch((error) => {
832
+ throw createProjectOnFirstDeployError({
833
+ error,
834
+ inferredName: name,
835
+ workspaceName: authState.workspace.name
836
+ });
837
+ });
838
+ return {
839
+ id: project.id,
840
+ name: project.name,
841
+ workspace: authState.workspace
842
+ };
843
+ } : void 0,
844
+ allowCreate: options?.allowCreate,
845
+ prompt: createSelectPromptPort(context),
846
+ remember: true
826
847
  });
827
- try {
828
- await writeLinkedProjectId(context.runtime.cwd, project.id);
829
- } catch (error) {
830
- const cause = error instanceof Error ? error.message : String(error);
831
- throw deployFailedError("Failed to link created project", `Project "${project.name}" (${project.id}) was created remotely but could not be linked locally: ${cause}`, ["prisma-cli project show", "prisma-cli app deploy"]);
832
- }
833
- return project.id;
848
+ const branchName = await context.stateStore.read().then((state) => state.branch.active);
849
+ return {
850
+ ...resolved,
851
+ branch: {
852
+ name: branchName,
853
+ kind: toBranchKind(branchName)
854
+ }
855
+ };
834
856
  }
835
- async function assertProjectLinkWritableForDeploy(context) {
836
- try {
837
- await assertLinkedProjectIdWritable(context.runtime.cwd);
838
- } catch (error) {
839
- if (error instanceof UnsafeConfigWriteError) throw usageError("Project bootstrap requires a writable Prisma config", error.message, "Update prisma.config.ts to use a recognizable project field, or remove it and rerun prisma-cli app deploy.", ["prisma-cli app deploy --app hello-world"], "app");
840
- throw error;
841
- }
857
+ function toBranchKind(name) {
858
+ return name === "production" ? "production" : "preview";
859
+ }
860
+ async function readCurrentWorkspaceId(context) {
861
+ const state = await context.stateStore.read();
862
+ if (state.auth?.workspaceId) return state.auth.workspaceId;
863
+ return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
842
864
  }
843
865
  function normalizeBuildType(requestedBuildType) {
844
866
  if (!requestedBuildType) return "auto";
@@ -875,7 +897,7 @@ function parseDeployPortMapping(requestedPort) {
875
897
  }
876
898
  function ensurePreviewAppMode(context) {
877
899
  if (isRealMode(context)) return;
878
- throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project link"], "app");
900
+ throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project show"], "app");
879
901
  }
880
902
  function deployFailedError(summary, error, nextSteps) {
881
903
  return new CliError({
@@ -889,6 +911,52 @@ function deployFailedError(summary, error, nextSteps) {
889
911
  nextSteps
890
912
  });
891
913
  }
914
+ /**
915
+ * `app deploy` falls into "create a new project on first deploy" when no
916
+ * existing project matches the package.json name (or the cwd basename as a
917
+ * fallback). When the create call fails the user often doesn't realise the
918
+ * CLI was attempting to create a project at all — they thought the deploy
919
+ * would find an existing project. Surface that context, and recommend the
920
+ * explicit `--project` flag as the unambiguous way out.
921
+ */
922
+ function createProjectOnFirstDeployError(options) {
923
+ const { error, inferredName, workspaceName } = options;
924
+ const status = extractHttpStatus(error);
925
+ const errorMessage = error instanceof Error ? error.message : String(error);
926
+ const inferredContext = `No existing project matched the package.json name \`${inferredName}\`, so the CLI attempted to create one.`;
927
+ const nextSteps = ["prisma-cli project list", "prisma-cli app deploy --project <id-or-name>"];
928
+ if (status === 401 || status === 403) return new CliError({
929
+ code: "AUTH_FORBIDDEN",
930
+ domain: "auth",
931
+ summary: "Could not create a new project for this deploy",
932
+ why: `${inferredContext} The platform rejected the create (HTTP ${status}).`,
933
+ fix: `Pass --project <id-or-name> to deploy into an existing project, or grant the service token project-create permission on workspace \`${workspaceName}\`.`,
934
+ debug: formatDebugDetails(error),
935
+ exitCode: 1,
936
+ nextSteps
937
+ });
938
+ return new CliError({
939
+ code: "DEPLOY_FAILED",
940
+ domain: "app",
941
+ summary: "Could not create a new project for this deploy",
942
+ why: `${inferredContext} ${errorMessage}`.trim(),
943
+ fix: "Pass --project <id-or-name> to deploy into an existing project, or retry after addressing the platform error above.",
944
+ debug: formatDebugDetails(error),
945
+ exitCode: 1,
946
+ nextSteps
947
+ });
948
+ }
949
+ function extractHttpStatus(error) {
950
+ if (!error || typeof error !== "object") return null;
951
+ const candidate = error;
952
+ if (typeof candidate.statusCode === "number") return candidate.statusCode;
953
+ if (typeof candidate.status === "number") return candidate.status;
954
+ if (typeof candidate.message === "string") {
955
+ const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
956
+ if (match) return Number.parseInt(match[1], 10);
957
+ }
958
+ return null;
959
+ }
892
960
  function noDeploymentsError(summary, why) {
893
961
  return new CliError({
894
962
  code: "NO_DEPLOYMENTS",
@@ -1,9 +1,9 @@
1
1
  import { authRequiredError, usageError } from "../shell/errors.js";
2
2
  import { canPrompt } from "../shell/runtime.js";
3
- import { createSelectPromptPort } from "./select-prompt-port.js";
3
+ import { performLogin, performLogout, readAuthState } from "../lib/auth/auth-ops.js";
4
4
  import { createAuthUseCases } from "../use-cases/auth.js";
5
5
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
6
- import { performLogin, performLogout, readAuthState } from "../lib/auth/auth-ops.js";
6
+ import { createSelectPromptPort } from "./select-prompt-port.js";
7
7
  //#region src/controllers/auth.ts
8
8
  function isRealMode(context) {
9
9
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
@@ -1,7 +1,7 @@
1
1
  import { featureUnavailableError, usageError } from "../shell/errors.js";
2
2
  import { canPrompt } from "../shell/runtime.js";
3
- import { createSelectPromptPort } from "./select-prompt-port.js";
4
3
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
4
+ import { createSelectPromptPort } from "./select-prompt-port.js";
5
5
  import { createBranchUseCases } from "../use-cases/branch.js";
6
6
  //#region src/controllers/branch.ts
7
7
  const PREVIEW_BRANCH_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;