@prisma/cli 3.0.0-alpha.2 → 3.0.0-alpha.4

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 (37) hide show
  1. package/dist/adapters/git.js +49 -0
  2. package/dist/adapters/local-state.js +38 -0
  3. package/dist/cli2.js +3 -1
  4. package/dist/commands/app/index.js +31 -20
  5. package/dist/commands/auth/index.js +1 -1
  6. package/dist/commands/env.js +87 -0
  7. package/dist/commands/git/index.js +36 -0
  8. package/dist/commands/project/index.js +10 -13
  9. package/dist/controllers/app-env.js +223 -0
  10. package/dist/controllers/app.js +260 -86
  11. package/dist/controllers/auth.js +2 -2
  12. package/dist/controllers/branch.js +1 -1
  13. package/dist/controllers/project.js +451 -161
  14. package/dist/lib/app/env-config.js +57 -0
  15. package/dist/lib/app/preview-provider.js +15 -2
  16. package/dist/lib/auth/auth-ops.js +8 -10
  17. package/dist/lib/auth/client.js +1 -1
  18. package/dist/lib/project/resolution.js +148 -0
  19. package/dist/output/patterns.js +1 -2
  20. package/dist/presenters/app-env.js +129 -0
  21. package/dist/presenters/app.js +9 -1
  22. package/dist/presenters/auth.js +2 -2
  23. package/dist/presenters/branch.js +6 -6
  24. package/dist/presenters/project.js +84 -44
  25. package/dist/shell/command-meta.js +91 -9
  26. package/dist/shell/command-runner.js +32 -2
  27. package/dist/shell/errors.js +4 -1
  28. package/dist/shell/help.js +1 -1
  29. package/dist/shell/output.js +18 -12
  30. package/dist/shell/runtime.js +1 -1
  31. package/dist/shell/ui.js +19 -1
  32. package/dist/use-cases/auth.js +5 -8
  33. package/dist/use-cases/branch.js +20 -20
  34. package/dist/use-cases/create-cli-gateways.js +3 -13
  35. package/dist/use-cases/project.js +2 -48
  36. package/package.json +2 -2
  37. package/dist/adapters/config.js +0 -74
@@ -1,17 +1,22 @@
1
- import { UnsafeConfigWriteError, assertLinkedProjectIdWritable, readLinkedProjectId, writeLinkedProjectId } from "../adapters/config.js";
2
- import { CliError, authRequiredError, featureUnavailableError, usageError } from "../shell/errors.js";
1
+ import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
+ import { FileTokenStorage } from "../adapters/token-storage.js";
3
+ import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
4
+ import { renderCommandHeader } from "../shell/ui.js";
5
+ import { writeJsonEvent } from "../shell/output.js";
3
6
  import { canPrompt } from "../shell/runtime.js";
4
7
  import { textPrompt } from "../shell/prompt.js";
5
8
  import { requireComputeAuth } from "../lib/auth/guard.js";
9
+ import { readAuthState } from "../lib/auth/auth-ops.js";
6
10
  import { parseEnvAssignments } from "../lib/app/env-vars.js";
7
11
  import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
8
- import { projectNotFoundError } from "../use-cases/project.js";
12
+ import { resolveProjectTarget } from "../lib/project/resolution.js";
9
13
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
10
14
  import { PREVIEW_DEFAULT_REGION, createPreviewDeployInteraction } from "../lib/app/preview-interaction.js";
11
15
  import { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
12
16
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
13
17
  import { createSelectPromptPort } from "./select-prompt-port.js";
14
- import path from "node:path";
18
+ import { requireAuthenticatedAuthState } from "./auth.js";
19
+ import { listRealWorkspaceProjects } from "./project.js";
15
20
  import open from "open";
16
21
  //#region src/controllers/app.ts
17
22
  function isRealMode(context) {
@@ -79,8 +84,7 @@ async function runAppDeploy(context, appName, options) {
79
84
  assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
80
85
  const portMapping = parseDeployPortMapping(options?.httpPort);
81
86
  const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
82
- const provider = await requirePreviewAppProvider(context);
83
- const projectId = await resolveProjectIdForDeploy(context, provider);
87
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, { allowCreate: true });
84
88
  const selectedApp = await resolveDeploySelection(context, projectId, await listApps(context, provider, projectId), appName);
85
89
  const deployResult = await provider.deployApp({
86
90
  cwd: context.runtime.cwd,
@@ -105,7 +109,10 @@ async function runAppDeploy(context, appName, options) {
105
109
  return {
106
110
  command: "app.deploy",
107
111
  result: {
108
- projectId: deployResult.projectId,
112
+ workspace: target.workspace,
113
+ project: target.project,
114
+ branch: target.branch,
115
+ resolution: target.resolution,
109
116
  app: {
110
117
  id: deployResult.app.id,
111
118
  name: deployResult.app.name
@@ -116,16 +123,16 @@ async function runAppDeploy(context, appName, options) {
116
123
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
117
124
  };
118
125
  }
119
- async function runAppUpdateEnv(context, appName, envAssignments) {
126
+ async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
120
127
  ensurePreviewAppMode(context);
128
+ emitLegacyEnvDeprecationWarning(context, "app update-env", "project env add");
121
129
  const envVars = parseEnvAssignments(envAssignments, {
122
130
  commandName: "update-env",
123
131
  requireAtLeastOne: true
124
132
  });
125
- const projectId = await requireLinkedProjectId(context);
126
- const provider = await requirePreviewAppProvider(context);
133
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
127
134
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
128
- if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The linked project does not have any deployed app yet.");
135
+ if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The resolved project does not have any deployed app yet.");
129
136
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
130
137
  throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
131
138
  });
@@ -158,10 +165,10 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
158
165
  nextSteps: ["prisma-cli app list-env", `prisma-cli app show-deploy ${updateResult.deployment.id}`]
159
166
  };
160
167
  }
161
- async function runAppListEnv(context, appName) {
168
+ async function runAppListEnv(context, appName, projectRef) {
162
169
  ensurePreviewAppMode(context);
163
- const projectId = await requireLinkedProjectId(context);
164
- const provider = await requirePreviewAppProvider(context);
170
+ emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
171
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
165
172
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
166
173
  if (!selectedApp) return {
167
174
  command: "app.list-env",
@@ -246,10 +253,9 @@ async function runAppListEnv(context, appName) {
246
253
  nextSteps: deployment.id ? [`prisma-cli app show-deploy ${deployment.id}`] : ["prisma-cli app deploy"]
247
254
  };
248
255
  }
249
- async function runAppListDeploys(context, appName) {
256
+ async function runAppListDeploys(context, appName, projectRef) {
250
257
  ensurePreviewAppMode(context);
251
- const projectId = await requireLinkedProjectId(context);
252
- const provider = await requirePreviewAppProvider(context);
258
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
253
259
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
254
260
  if (!selectedApp) return {
255
261
  command: "app.list-deploys",
@@ -284,10 +290,9 @@ async function runAppListDeploys(context, appName) {
284
290
  nextSteps: deployments.length > 0 ? [`prisma-cli app show-deploy ${deployments[0]?.id}`] : ["prisma-cli app deploy"]
285
291
  };
286
292
  }
287
- async function runAppShow(context, appName) {
293
+ async function runAppShow(context, appName, projectRef) {
288
294
  ensurePreviewAppMode(context);
289
- const projectId = await requireLinkedProjectId(context);
290
- const provider = await requirePreviewAppProvider(context);
295
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
291
296
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
292
297
  if (!selectedApp) return {
293
298
  command: "app.show",
@@ -341,8 +346,9 @@ async function runAppShowDeploy(context, deploymentId) {
341
346
  exitCode: 1,
342
347
  nextSteps: ["prisma-cli app list-deploys"]
343
348
  });
344
- const linkedProjectId = deployment?.app ? await readLinkedProjectId(context.runtime.cwd) : null;
345
- const knownLiveDeploymentId = deployment?.app && linkedProjectId ? await context.stateStore.readKnownLiveDeployment(linkedProjectId, deployment.app.id) : null;
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;
346
352
  const providerLiveDeploymentId = deployment.app?.liveDeploymentId ?? null;
347
353
  return {
348
354
  command: "app.show-deploy",
@@ -360,12 +366,11 @@ async function runAppShowDeploy(context, deploymentId) {
360
366
  nextSteps: []
361
367
  };
362
368
  }
363
- async function runAppOpen(context, appName) {
369
+ async function runAppOpen(context, appName, projectRef) {
364
370
  ensurePreviewAppMode(context);
365
- const projectId = await requireLinkedProjectId(context);
366
- const provider = await requirePreviewAppProvider(context);
371
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
367
372
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
368
- if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The linked project does not have any deployed app yet.");
373
+ if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
369
374
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
370
375
  throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
371
376
  });
@@ -395,14 +400,133 @@ async function runAppOpen(context, appName) {
395
400
  nextSteps: ["prisma-cli app show", `prisma-cli app show-deploy ${liveDeployment.id}`]
396
401
  };
397
402
  }
398
- async function runAppLogs(context, _appName, _deploymentId) {
403
+ async function runAppLogs(context, appName, deploymentId, projectRef) {
399
404
  ensurePreviewAppMode(context);
400
- throw blockedPreviewAppCommandError("App logs are not available in this preview", "The current preview cannot stream app logs yet.");
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);
407
+ if (!context.flags.json && !context.flags.quiet) {
408
+ const lines = renderCommandHeader(context.ui, {
409
+ commandLabel: "app logs",
410
+ description: "Streaming logs for the selected deployment.",
411
+ docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
412
+ rows: [
413
+ {
414
+ key: "project",
415
+ value: projectId
416
+ },
417
+ {
418
+ key: "app",
419
+ value: target.app.name
420
+ },
421
+ {
422
+ key: "deployment",
423
+ value: target.deployment.id
424
+ }
425
+ ]
426
+ });
427
+ if (lines.length > 0) context.output.stderr.write(`${lines.join("\n")}\n`);
428
+ }
429
+ await provider.streamDeploymentLogs({
430
+ deploymentId: target.deployment.id,
431
+ onRecord: (record) => writeLogRecord(context, record)
432
+ }).catch((error) => {
433
+ throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
434
+ });
435
+ }
436
+ async function resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) {
437
+ if (appName) {
438
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
439
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
440
+ const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
441
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
442
+ });
443
+ const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
444
+ await context.stateStore.setSelectedApp(projectId, {
445
+ id: deploymentsResult.app.id,
446
+ name: deploymentsResult.app.name
447
+ });
448
+ return {
449
+ app: deploymentsResult.app,
450
+ deployment
451
+ };
452
+ }
453
+ const shown = await provider.showDeployment(deploymentId).catch((error) => {
454
+ throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
455
+ });
456
+ if (!shown) throw new CliError({
457
+ code: "DEPLOYMENT_NOT_FOUND",
458
+ domain: "app",
459
+ summary: `Deployment "${deploymentId}" not found`,
460
+ why: "The requested deployment does not exist or is no longer available.",
461
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id.",
462
+ exitCode: 1,
463
+ nextSteps: ["prisma-cli app list-deploys"]
464
+ });
465
+ if (!shown.app) throw new CliError({
466
+ code: "DEPLOYMENT_NOT_FOUND",
467
+ domain: "app",
468
+ summary: `Deployment "${deploymentId}" is not attached to an app`,
469
+ why: "The requested deployment could be found, but its app could not be resolved.",
470
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id for the selected app.",
471
+ exitCode: 1,
472
+ nextSteps: ["prisma-cli app list-deploys"]
473
+ });
474
+ const resolvedProjectApp = (await listApps(context, provider, projectId)).find((app) => app.id === shown.app?.id);
475
+ if (!resolvedProjectApp) throw new CliError({
476
+ code: "DEPLOYMENT_NOT_FOUND",
477
+ domain: "app",
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.",
480
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id for this project.",
481
+ exitCode: 1,
482
+ nextSteps: ["prisma-cli app list-deploys"]
483
+ });
484
+ await context.stateStore.setSelectedApp(projectId, {
485
+ id: resolvedProjectApp.id,
486
+ name: resolvedProjectApp.name
487
+ });
488
+ return {
489
+ app: resolvedProjectApp,
490
+ deployment: shown.deployment
491
+ };
492
+ }
493
+ async function resolveLiveLogDeployment(context, provider, projectId, appName) {
494
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
495
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
496
+ const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
497
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
498
+ });
499
+ const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
500
+ const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId);
501
+ const deployment = currentLiveDeploymentId ? deployments.find((candidate) => candidate.id === currentLiveDeploymentId) ?? null : null;
502
+ await context.stateStore.setSelectedApp(projectId, {
503
+ id: deploymentsResult.app.id,
504
+ name: deploymentsResult.app.name
505
+ });
506
+ if (!deployment) throw noDeploymentsError("No deployments available to stream logs", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
507
+ return {
508
+ app: deploymentsResult.app,
509
+ deployment
510
+ };
401
511
  }
402
- async function runAppPromote(context, deploymentId, appName) {
512
+ function writeLogRecord(context, record) {
513
+ if (context.flags.json) {
514
+ writeJsonEvent(context.output, {
515
+ type: record.type,
516
+ command: "app.logs",
517
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
518
+ data: record
519
+ });
520
+ return;
521
+ }
522
+ if (record.type === "log") {
523
+ context.output.stdout.write(record.text);
524
+ if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
525
+ }
526
+ }
527
+ async function runAppPromote(context, deploymentId, appName, projectRef) {
403
528
  ensurePreviewAppMode(context);
404
- const projectId = await requireLinkedProjectId(context);
405
- const provider = await requirePreviewAppProvider(context);
529
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
406
530
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "promote");
407
531
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
408
532
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -440,10 +564,9 @@ async function runAppPromote(context, deploymentId, appName) {
440
564
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
441
565
  };
442
566
  }
443
- async function runAppRollback(context, appName, deploymentId) {
567
+ async function runAppRollback(context, appName, deploymentId, projectRef) {
444
568
  ensurePreviewAppMode(context);
445
- const projectId = await requireLinkedProjectId(context);
446
- const provider = await requirePreviewAppProvider(context);
569
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
447
570
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "rollback");
448
571
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
449
572
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -483,10 +606,9 @@ async function runAppRollback(context, appName, deploymentId) {
483
606
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
484
607
  };
485
608
  }
486
- async function runAppRemove(context, appName) {
609
+ async function runAppRemove(context, appName, projectRef) {
487
610
  ensurePreviewAppMode(context);
488
- const projectId = await requireLinkedProjectId(context);
489
- const provider = await requirePreviewAppProvider(context);
611
+ const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
490
612
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "remove");
491
613
  await confirmAppRemoval(context, selectedApp);
492
614
  const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
@@ -527,7 +649,7 @@ async function resolveDeploySelection(context, projectId, apps, explicitAppName)
527
649
  appId: matched.id,
528
650
  useInteractiveSelection: false
529
651
  };
530
- if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the linked project.", "Pass --app <name>, or rerun prisma-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");
531
653
  }
532
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");
533
655
  return { useInteractiveSelection: true };
@@ -535,14 +657,14 @@ async function resolveDeploySelection(context, projectId, apps, explicitAppName)
535
657
  async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
536
658
  if (explicitAppName) {
537
659
  const matched = findAppByName(apps, explicitAppName);
538
- if (!matched) throw usageError("Selected app does not exist in the linked project", `The app "${explicitAppName}" could not be found in linked project "${projectId}".`, "Pass the name of an existing app, or rerun prisma-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");
539
661
  return matched;
540
662
  }
541
663
  const savedSelection = await context.stateStore.readSelectedApp(projectId);
542
664
  if (savedSelection) {
543
665
  const matched = apps.find((app) => app.id === savedSelection.id) ?? findAppByName(apps, savedSelection.name);
544
666
  if (matched) return matched;
545
- if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the linked project.", "Pass --app <name>, or rerun prisma-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");
546
668
  }
547
669
  if (apps.length === 0) return null;
548
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");
@@ -558,7 +680,7 @@ async function resolveExistingAppSelection(context, projectId, apps, explicitApp
558
680
  async function requireReleaseAppSelection(context, projectId, apps, explicitAppName, commandName) {
559
681
  const selectedApp = await resolveExistingAppSelection(context, projectId, apps, explicitAppName);
560
682
  if (selectedApp) return selectedApp;
561
- throw usageError(`App ${commandName} requires an existing app`, "The linked project does not have an app that can be selected for this command.", `Deploy an app first, or rerun prisma-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");
562
684
  }
563
685
  async function confirmAppRemoval(context, app) {
564
686
  if (context.flags.yes) return;
@@ -647,55 +769,94 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
647
769
  }
648
770
  async function listApps(context, provider, projectId) {
649
771
  return provider.listApps(projectId).then(sortApps).catch((error) => {
650
- if (isMissingProjectError(error)) throw projectNotFoundError(`The linked project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`, "Run prisma-cli project show to inspect the current link, then relink the repo or rerun prisma-cli app deploy to bootstrap a new project.", [
651
- "prisma-cli project show",
652
- "prisma-cli project link",
653
- "prisma-cli app deploy"
654
- ]);
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
+ });
655
781
  throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
656
782
  });
657
783
  }
658
784
  async function requirePreviewAppProvider(context) {
785
+ const { provider } = await requirePreviewAppProviderWithClient(context);
786
+ return provider;
787
+ }
788
+ async function requirePreviewAppProviderWithClient(context) {
659
789
  const client = await requireComputeAuth(context.runtime.env);
660
790
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
661
- return createPreviewAppProvider(client);
662
- }
663
- async function requireLinkedProjectId(context) {
664
- const projectId = await readLinkedProjectId(context.runtime.cwd);
665
- if (!projectId) throw new CliError({
666
- code: "PROJECT_NOT_LINKED",
667
- domain: "project",
668
- summary: "Project link required",
669
- why: "This command needs a linked project for the current repo.",
670
- fix: "Run prisma-cli project link before deploying or inspecting app deployments.",
671
- exitCode: 1,
672
- nextSteps: ["prisma-cli project link"]
673
- });
674
- return projectId;
791
+ return {
792
+ client,
793
+ provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
794
+ };
675
795
  }
676
- async function resolveProjectIdForDeploy(context, provider) {
677
- const linkedProjectId = await readLinkedProjectId(context.runtime.cwd);
678
- if (linkedProjectId) return linkedProjectId;
679
- await assertProjectLinkWritableForDeploy(context);
680
- const projectName = path.basename(context.runtime.cwd);
681
- const project = await provider.createProject({ name: projectName }).catch((error) => {
682
- throw deployFailedError("Failed to create project for first deploy", error, ["prisma-cli app deploy"]);
683
- });
684
- try {
685
- await writeLinkedProjectId(context.runtime.cwd, project.id);
686
- } catch (error) {
687
- const cause = error instanceof Error ? error.message : String(error);
688
- throw deployFailedError("Failed to link created project", `Project "${project.name}" (${project.id}) was created remotely but could not be linked locally: ${cause}`, ["prisma-cli project show", "prisma-cli app deploy"]);
689
- }
690
- return project.id;
796
+ function createPreviewLogAuthOptions(env) {
797
+ const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
798
+ if (rawToken) return {
799
+ baseUrl: getApiBaseUrl(env),
800
+ getToken: async () => rawToken
801
+ };
802
+ const tokenStorage = new FileTokenStorage(env);
803
+ return {
804
+ baseUrl: getApiBaseUrl(env),
805
+ getToken: async () => {
806
+ const tokens = await tokenStorage.getTokens();
807
+ if (!tokens) throw new Error("Authentication token is no longer available. Run prisma-cli auth login and try again.");
808
+ return tokens.accessToken;
809
+ }
810
+ };
691
811
  }
692
- async function assertProjectLinkWritableForDeploy(context) {
693
- try {
694
- await assertLinkedProjectIdWritable(context.runtime.cwd);
695
- } catch (error) {
696
- if (error instanceof UnsafeConfigWriteError) throw usageError("Project bootstrap requires a writable Prisma config", error.message, "Update prisma.config.ts to use a recognizable project field, or remove it and rerun prisma-cli app deploy.", ["prisma-cli app deploy --app hello-world"], "app");
697
- throw error;
698
- }
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
+ };
821
+ }
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 deployFailedError("Failed to create project for first deploy", error, ["prisma-cli app deploy"]);
833
+ });
834
+ return {
835
+ id: project.id,
836
+ name: project.name,
837
+ workspace: authState.workspace
838
+ };
839
+ } : void 0,
840
+ allowCreate: options?.allowCreate,
841
+ prompt: createSelectPromptPort(context),
842
+ remember: true
843
+ });
844
+ const branchName = await context.stateStore.read().then((state) => state.branch.active);
845
+ return {
846
+ ...resolved,
847
+ branch: {
848
+ name: branchName,
849
+ kind: toBranchKind(branchName)
850
+ }
851
+ };
852
+ }
853
+ function toBranchKind(name) {
854
+ return name === "production" ? "production" : "preview";
855
+ }
856
+ async function readCurrentWorkspaceId(context) {
857
+ const state = await context.stateStore.read();
858
+ if (state.auth?.workspaceId) return state.auth.workspaceId;
859
+ return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
699
860
  }
700
861
  function normalizeBuildType(requestedBuildType) {
701
862
  if (!requestedBuildType) return "auto";
@@ -732,10 +893,7 @@ function parseDeployPortMapping(requestedPort) {
732
893
  }
733
894
  function ensurePreviewAppMode(context) {
734
895
  if (isRealMode(context)) return;
735
- throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project link"], "app");
736
- }
737
- function blockedPreviewAppCommandError(summary, why) {
738
- return featureUnavailableError(summary, why, "Use prisma-cli app show, prisma-cli app open, prisma-cli app deploy, or prisma-cli app list-deploys in the current preview.", ["prisma-cli app show", "prisma-cli app list-deploys"], "app");
896
+ throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project show"], "app");
739
897
  }
740
898
  function deployFailedError(summary, error, nextSteps) {
741
899
  return new CliError({
@@ -830,5 +988,21 @@ function sortApps(apps) {
830
988
  function toOptionalEnvVars(envVars) {
831
989
  return Object.keys(envVars).length > 0 ? envVars : void 0;
832
990
  }
991
+ /**
992
+ * Emits a deprecation banner to stderr when the legacy single-shot
993
+ * env-var commands are invoked. The banner is suppressed in --json
994
+ * mode so machine consumers keep their JSON channel clean; --json
995
+ * users discover the deprecation via release notes and the new
996
+ * `prisma-cli project env` namespace's output anyway.
997
+ *
998
+ * Removal of these legacy commands is deliberately scoped out of the
999
+ * Public Beta — see the Compute Beta plan, sub-track 3B.1, where the
1000
+ * Terminal team picks an explicit removal milestone.
1001
+ */
1002
+ function emitLegacyEnvDeprecationWarning(context, legacyCommand, replacement) {
1003
+ if (context.flags.json) return;
1004
+ const message = `[deprecation] \`prisma-cli ${legacyCommand}\` is deprecated. Use \`prisma-cli ${replacement}\` instead.`;
1005
+ context.runtime.stderr.write(`${message}\n`);
1006
+ }
833
1007
  //#endregion
834
1008
  export { runAppBuild, runAppDeploy, runAppListDeploys, runAppListEnv, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy, runAppUpdateEnv };
@@ -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]+)*$/;