@prisma/cli 8.0.0-rc.10-dev.83 → 8.0.0-rc.10-dev.85

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 (2) hide show
  1. package/dist/cli.js +1212 -1723
  2. package/package.json +9 -9
package/dist/cli.js CHANGED
@@ -61,11 +61,15 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
61
61
  * unified binary's name.
62
62
  */
63
63
  const CLI_NAME = "prisma";
64
- /** The CLI docs page (also the update-check fallback instruction URL).
65
- * The old /docs/orm/tools/prisma-cli path 308-redirects to the ORM CLI
66
- * reference the wrong docs for the unified CLI — so this points at
67
- * the docs root until the unified CLI has its own page. */
68
- const CLI_DOCS_URL = "https://www.prisma.io/docs";
64
+ /** The unified CLI's docs section (also the update-check fallback
65
+ * instruction URL). */
66
+ const CLI_DOCS_URL = "https://www.prisma.io/docs/cli";
67
+ /**
68
+ * Base URL for structured-error documentation links. The engine composes
69
+ * each diagnostic's docsUrl as base + code; every code is documented at
70
+ * this page (registry: docs/reference/error-reference.md).
71
+ */
72
+ const DOCS_ERRORS_BASE_URL = "https://www.prisma.io/docs/cli/error-reference/";
69
73
  //#endregion
70
74
  //#region src/auth/client.ts
71
75
  const CLIENT_ID = "cmm3lndn701oo0uefvxzo0ivw";
@@ -2460,7 +2464,7 @@ function loginWorkspaceUnknownError() {
2460
2464
  }]
2461
2465
  });
2462
2466
  }
2463
- function nextActionsFor$5(agentSetupTipCommand) {
2467
+ function nextActionsFor$1(agentSetupTipCommand) {
2464
2468
  return [
2465
2469
  {
2466
2470
  kind: "run-command",
@@ -2511,7 +2515,7 @@ function presentationsFor$2(spec, result) {
2511
2515
  }]
2512
2516
  ],
2513
2517
  stdout: () => rows.map((row) => `${row.label}: ${row.value}`),
2514
- next: () => nextActionsFor$5(spec.agentSetupTipCommand)
2518
+ next: () => nextActionsFor$1(spec.agentSetupTipCommand)
2515
2519
  };
2516
2520
  }
2517
2521
  const authLoginCommand = defineCommand({
@@ -2966,65 +2970,6 @@ async function promptForSession(stored, select) {
2966
2970
  return requireSession(stored.sessions, workspaceId);
2967
2971
  }
2968
2972
  //#endregion
2969
- //#region src/errors.ts
2970
- var CliError = class extends Error {
2971
- code;
2972
- domain;
2973
- severity;
2974
- summary;
2975
- why;
2976
- fix;
2977
- debug;
2978
- where;
2979
- meta;
2980
- docsUrl;
2981
- exitCode;
2982
- nextSteps;
2983
- nextActions;
2984
- humanLines;
2985
- constructor(options) {
2986
- super(options.summary);
2987
- this.name = "CliError";
2988
- this.code = options.code;
2989
- this.domain = options.domain;
2990
- this.severity = "error";
2991
- this.summary = options.summary;
2992
- this.why = options.why;
2993
- this.fix = options.fix;
2994
- this.debug = options.debug ?? null;
2995
- this.where = options.where ?? null;
2996
- this.meta = options.meta ?? {};
2997
- this.docsUrl = options.docsUrl ?? null;
2998
- this.exitCode = options.exitCode ?? 1;
2999
- this.nextSteps = options.nextSteps ?? [];
3000
- this.nextActions = options.nextActions ?? [];
3001
- this.humanLines = options.humanLines && options.humanLines.length > 0 ? [...options.humanLines] : null;
3002
- }
3003
- };
3004
- function usageError(summary, why, fix, nextSteps = [], domain = "cli") {
3005
- return new CliError({
3006
- code: "USAGE_ERROR",
3007
- domain,
3008
- summary,
3009
- why,
3010
- fix,
3011
- exitCode: 2,
3012
- nextSteps
3013
- });
3014
- }
3015
- function authRequiredError(nextSteps = ["prisma auth login"], options = {}) {
3016
- return new CliError({
3017
- code: "AUTH_REQUIRED",
3018
- domain: "auth",
3019
- summary: "Authentication required",
3020
- why: "This command needs an authenticated session.",
3021
- fix: "Run prisma auth login, or rerun the command in a TTY to sign in interactively.",
3022
- debug: options.debug,
3023
- exitCode: 1,
3024
- nextSteps
3025
- });
3026
- }
3027
- //#endregion
3028
2973
  //#region src/controllers/branch.ts
3029
2974
  function sortBranches(branches) {
3030
2975
  return branches.slice().sort((left, right) => {
@@ -3067,14 +3012,17 @@ function toBranchSummary(branch) {
3067
3012
  }
3068
3013
  function branchApiError(summary, response, error) {
3069
3014
  const status = response?.status ?? 0;
3070
- return new CliError({
3071
- code: error?.error?.code ?? "BRANCH_API_ERROR",
3072
- domain: "branch",
3073
- summary,
3015
+ const apiCode = error?.error?.code;
3016
+ return new CliStructuredError("BRANCH.API_ERROR", summary, {
3074
3017
  why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
3075
- fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
3076
- exitCode: 1,
3077
- nextSteps: []
3018
+ ...status || apiCode !== void 0 ? { meta: {
3019
+ ...status ? { status } : {},
3020
+ ...apiCode !== void 0 ? { apiCode } : {}
3021
+ } } : {},
3022
+ nextActions: [{
3023
+ kind: "user-choice",
3024
+ label: error?.error?.hint ?? "Re-run with --log-level verbose for the underlying API response details."
3025
+ }]
3078
3026
  });
3079
3027
  }
3080
3028
  //#endregion
@@ -3342,54 +3290,50 @@ function createManagementProjectProvider(client) {
3342
3290
  }
3343
3291
  };
3344
3292
  }
3293
+ function userChoice$6(label) {
3294
+ return {
3295
+ kind: "user-choice",
3296
+ label
3297
+ };
3298
+ }
3345
3299
  function projectRenameFailedError(name, error) {
3346
- return new CliError({
3347
- code: "PROJECT_RENAME_FAILED",
3348
- domain: "project",
3349
- summary: "Project rename failed",
3300
+ return new CliStructuredError("PROJECT.RENAME_FAILED", "Project rename failed", {
3350
3301
  why: error?.error?.message ?? `The platform rejected the name "${name}".`,
3351
- fix: error?.error?.hint ?? "Pass a different project name and retry the rename.",
3352
- exitCode: 1,
3353
- nextSteps: []
3302
+ nextActions: [userChoice$6(error?.error?.hint ?? "Pass a different project name and retry the rename.")]
3354
3303
  });
3355
3304
  }
3356
3305
  function projectDeleteBlockedError(projectId, error) {
3357
- return new CliError({
3358
- code: "PROJECT_DELETE_BLOCKED",
3359
- domain: "project",
3360
- summary: "Project cannot be deleted yet",
3306
+ const deleteServicesCommand = formatPrismaCliCommand([
3307
+ "service",
3308
+ "delete",
3309
+ "--service",
3310
+ "<name>"
3311
+ ]);
3312
+ return new CliStructuredError("PROJECT.DELETE_BLOCKED", "Project cannot be deleted yet", {
3361
3313
  why: error?.error?.message ?? `Project "${projectId}" still has active deployments.`,
3362
- fix: "Delete the project's services first, then retry the deletion.",
3363
- exitCode: 1,
3364
- nextSteps: [formatPrismaCliCommand([
3365
- "service",
3366
- "delete",
3367
- "--service",
3368
- "<name>"
3369
- ])]
3314
+ nextActions: [userChoice$6("Delete the project's services first, then retry the deletion."), {
3315
+ kind: "run-command",
3316
+ label: deleteServicesCommand,
3317
+ command: deleteServicesCommand
3318
+ }]
3370
3319
  });
3371
3320
  }
3372
3321
  function projectTransferRejectedError(projectId, error) {
3373
- return new CliError({
3374
- code: "PROJECT_TRANSFER_REJECTED",
3375
- domain: "project",
3376
- summary: "Project transfer was rejected",
3322
+ return new CliStructuredError("PROJECT.TRANSFER_REJECTED", "Project transfer was rejected", {
3377
3323
  why: error?.error?.message ?? `The platform rejected the transfer of project "${projectId}", for example because the recipient token is invalid or expired.`,
3378
- fix: "Check the recipient workspace session or token and retry the transfer.",
3379
- exitCode: 1,
3380
- nextSteps: []
3324
+ nextActions: [userChoice$6("Check the recipient workspace session or token and retry the transfer.")]
3381
3325
  });
3382
3326
  }
3383
3327
  function projectApiError(summary, response, error) {
3384
3328
  const status = response?.status ?? 0;
3385
- return new CliError({
3386
- code: error?.error?.code ?? "PROJECT_API_ERROR",
3387
- domain: "project",
3388
- summary,
3329
+ const apiCode = error?.error?.code;
3330
+ return new CliStructuredError("PROJECT.API_ERROR", summary, {
3389
3331
  why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
3390
- fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
3391
- exitCode: 1,
3392
- nextSteps: []
3332
+ ...apiCode !== void 0 || status ? { meta: {
3333
+ ...status ? { status } : {},
3334
+ ...apiCode !== void 0 ? { apiCode } : {}
3335
+ } } : {},
3336
+ nextActions: [userChoice$6(error?.error?.hint ?? "Re-run with --log-level verbose for the underlying API response details.")]
3393
3337
  });
3394
3338
  }
3395
3339
  //#endregion
@@ -3479,60 +3423,59 @@ async function inspectProjectBinding(options) {
3479
3423
  });
3480
3424
  });
3481
3425
  }
3426
+ function runCommand$5(command, reason) {
3427
+ return {
3428
+ kind: "run-command",
3429
+ label: command,
3430
+ command,
3431
+ ...reason === void 0 ? {} : { reason }
3432
+ };
3433
+ }
3434
+ function userChoice$5(label) {
3435
+ return {
3436
+ kind: "user-choice",
3437
+ label
3438
+ };
3439
+ }
3482
3440
  function projectNotFoundError$1(projectRef, workspace) {
3483
- return projectResolutionErrorToCliError(new ProjectNotFoundError(projectRef, workspace));
3441
+ return projectResolutionErrorToStructured(new ProjectNotFoundError(projectRef, workspace));
3484
3442
  }
3485
- function projectNotFoundCliError(projectRef, workspace) {
3486
- return new CliError({
3487
- code: "PROJECT_NOT_FOUND",
3488
- domain: "project",
3489
- summary: "Project not found",
3443
+ function projectNotFoundStructuredError(projectRef, workspace) {
3444
+ return new CliStructuredError("PROJECT.NOT_FOUND", "Project not found", {
3490
3445
  why: `The project "${projectRef}" does not exist in workspace "${workspace.name}" or is not accessible.`,
3491
- fix: "Pass a project id or name from prisma project list.",
3492
- exitCode: 1,
3493
- nextSteps: ["prisma project list"]
3446
+ nextActions: [userChoice$5("Pass a project id or name from prisma project list."), runCommand$5("prisma project list")]
3494
3447
  });
3495
3448
  }
3496
3449
  function projectAmbiguousError(projectRef, matches) {
3497
- return projectResolutionErrorToCliError(new ProjectAmbiguousError(projectRef, matches));
3450
+ return projectResolutionErrorToStructured(new ProjectAmbiguousError(projectRef, matches));
3498
3451
  }
3499
- function projectAmbiguousCliError(projectRef, matches) {
3452
+ function projectAmbiguousStructuredError(projectRef, matches) {
3500
3453
  const firstMatch = matches[0];
3501
- const nextSteps = ["prisma project list"];
3502
- if (firstMatch) nextSteps.push(`prisma project link ${firstMatch.id}`);
3503
- return new CliError({
3504
- code: "PROJECT_AMBIGUOUS",
3505
- domain: "project",
3506
- summary: "Project resolution is ambiguous",
3454
+ const nextActions = [userChoice$5("Pass --project <id-or-name> to choose the project explicitly."), runCommand$5("prisma project list")];
3455
+ if (firstMatch) nextActions.push(runCommand$5(`prisma project link ${firstMatch.id}`));
3456
+ return new CliStructuredError("PROJECT.AMBIGUOUS", "Project resolution is ambiguous", {
3507
3457
  why: projectRef ? `Multiple projects matched "${projectRef}".` : "Multiple projects matched the current directory context.",
3508
- fix: "Pass --project <id-or-name> to choose the project explicitly.",
3509
3458
  meta: { matches: matches.map((project) => ({
3510
3459
  id: project.id,
3511
3460
  name: project.name
3512
3461
  })) },
3513
- exitCode: 1,
3514
- nextSteps
3462
+ nextActions
3515
3463
  });
3516
3464
  }
3517
- function localStateStaleCliError() {
3518
- return new CliError({
3519
- code: "LOCAL_STATE_STALE",
3520
- domain: "project",
3521
- summary: "Local project binding is stale",
3465
+ function localStateStaleStructuredError() {
3466
+ return new CliStructuredError("PROJECT.LOCAL_STATE_STALE", "Local project binding is stale", {
3522
3467
  why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
3523
- fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
3524
3468
  meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
3525
- exitCode: 1,
3526
- nextSteps: ["prisma project list", "prisma project link <id-or-name>"]
3469
+ nextActions: [
3470
+ userChoice$5(`Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`),
3471
+ runCommand$5("prisma project list"),
3472
+ runCommand$5("prisma project link <id-or-name>")
3473
+ ]
3527
3474
  });
3528
3475
  }
3529
- function localProjectWorkspaceMismatchCliError(options) {
3530
- return new CliError({
3531
- code: "LOCAL_PROJECT_WORKSPACE_MISMATCH",
3532
- domain: "project",
3533
- summary: "Project link uses another workspace",
3476
+ function localProjectWorkspaceMismatchStructuredError(options) {
3477
+ return new CliStructuredError("PROJECT.LOCAL_WORKSPACE_MISMATCH", "Project link uses another workspace", {
3534
3478
  why: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but your current CLI session is workspace "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
3535
- fix: "Switch to the linked workspace, or relink this directory to a project in the current workspace.",
3536
3479
  meta: {
3537
3480
  pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
3538
3481
  pinnedWorkspaceId: options.pinnedWorkspaceId,
@@ -3540,27 +3483,29 @@ function localProjectWorkspaceMismatchCliError(options) {
3540
3483
  activeWorkspaceId: options.activeWorkspace.id,
3541
3484
  activeWorkspaceName: options.activeWorkspace.name
3542
3485
  },
3543
- exitCode: 1,
3544
- nextSteps: [
3545
- `prisma auth workspace use ${options.pinnedWorkspaceId}`,
3546
- "prisma project list",
3547
- "prisma project link <id-or-name>"
3486
+ nextActions: [
3487
+ userChoice$5("Switch to the linked workspace, or relink this directory to a project in the current workspace."),
3488
+ runCommand$5(`prisma auth workspace use ${options.pinnedWorkspaceId}`),
3489
+ runCommand$5("prisma project list"),
3490
+ runCommand$5("prisma project link <id-or-name>")
3548
3491
  ]
3549
3492
  });
3550
3493
  }
3551
3494
  /**
3552
- * Converts expected project-resolution variants to command-boundary CliErrors.
3495
+ * Converts expected project-resolution variants to the structured errors
3496
+ * a command boundary raises — the codes here are the registered PROJECT.*
3497
+ * codes, assigned at origin.
3553
3498
  * `LocalResolutionPinReadAbortedError` and `UnhandledException` intentionally
3554
3499
  * propagate as exceptions; callers such as `resolveProjectShowInRealMode`
3555
3500
  * throw this helper's result, so passthrough variants should keep bubbling.
3556
3501
  */
3557
- function projectResolutionErrorToCliError(error) {
3502
+ function projectResolutionErrorToStructured(error) {
3558
3503
  return matchError(error, {
3559
- ProjectNotFoundError: (error) => projectNotFoundCliError(error.projectRef, error.workspace),
3560
- ProjectAmbiguousError: (error) => projectAmbiguousCliError(error.projectRef, error.matches),
3561
- ProjectSetupRequiredError: (error) => projectSetupRequiredCliError(error),
3562
- LocalStateStaleError: () => localStateStaleCliError(),
3563
- LocalProjectWorkspaceMismatchError: (error) => localProjectWorkspaceMismatchCliError({
3504
+ ProjectNotFoundError: (error) => projectNotFoundStructuredError(error.projectRef, error.workspace),
3505
+ ProjectAmbiguousError: (error) => projectAmbiguousStructuredError(error.projectRef, error.matches),
3506
+ ProjectSetupRequiredError: (error) => projectSetupRequiredStructuredError(error),
3507
+ LocalStateStaleError: () => localStateStaleStructuredError(),
3508
+ LocalProjectWorkspaceMismatchError: (error) => localProjectWorkspaceMismatchStructuredError({
3564
3509
  pinnedWorkspaceId: error.pinnedWorkspaceId,
3565
3510
  pinnedProjectId: error.pinnedProjectId,
3566
3511
  activeWorkspace: error.activeWorkspace
@@ -3590,17 +3535,11 @@ async function projectSetupRequiredError(options) {
3590
3535
  suggestion
3591
3536
  });
3592
3537
  }
3593
- function projectSetupRequiredCliError(error) {
3538
+ function projectSetupRequiredStructuredError(error) {
3594
3539
  const suggestion = error.suggestion;
3595
- return new CliError({
3596
- code: "PROJECT_SETUP_REQUIRED",
3597
- domain: "project",
3598
- summary: "Choose a Project before running this command",
3540
+ return new CliStructuredError("PROJECT.SETUP_REQUIRED", "Choose a Project before running this command", {
3599
3541
  why: error.message,
3600
- fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
3601
3542
  meta: { ...suggestion },
3602
- exitCode: 1,
3603
- nextSteps: ["prisma project list", ...suggestion.recoveryCommands],
3604
3543
  nextActions: buildProjectSetupNextActions({
3605
3544
  commandName: error.commandName,
3606
3545
  suggestedProjectName: suggestion.suggestedProjectName
@@ -3613,7 +3552,6 @@ function buildProjectSetupNextActions(options = {}) {
3613
3552
  const retryCommand = options.retryCommand ?? recoveryCommands[1];
3614
3553
  const actions = [{
3615
3554
  kind: "user-choice",
3616
- journey: "project-setup",
3617
3555
  label: "Ask the user whether to link an existing Project or create a new one",
3618
3556
  commands: [
3619
3557
  "prisma project list",
@@ -3623,7 +3561,6 @@ function buildProjectSetupNextActions(options = {}) {
3623
3561
  reason: options.reason ?? "This directory is not linked to a Prisma Project. Package and directory names are suggestions only, not a safe Project selection."
3624
3562
  }, {
3625
3563
  kind: "run-command",
3626
- journey: "project-setup",
3627
3564
  label: "Link the chosen Project",
3628
3565
  command: linkCommand,
3629
3566
  reason: "Linking writes the durable local Project binding for this directory."
@@ -3631,14 +3568,12 @@ function buildProjectSetupNextActions(options = {}) {
3631
3568
  const createCommand = options.createCommand ?? (options.suggestedProjectName ? `prisma project create ${formatCommandArgument(options.suggestedProjectName)}` : void 0);
3632
3569
  if (createCommand) actions.push({
3633
3570
  kind: "run-command",
3634
- journey: "project-setup",
3635
3571
  label: "Create and link a new Project",
3636
3572
  command: createCommand,
3637
3573
  reason: "Use this when the user wants a new Prisma Project instead of an existing one."
3638
3574
  });
3639
3575
  if (options.commandName) actions.push({
3640
3576
  kind: "run-command",
3641
- journey: "recover",
3642
3577
  label: "Retry with an explicit Project",
3643
3578
  command: retryCommand ?? `prisma ${options.commandName} --project <id-or-name>`
3644
3579
  });
@@ -3770,6 +3705,28 @@ function toProjectSummary$1(project) {
3770
3705
  //#region src/controllers/project.ts
3771
3706
  const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
3772
3707
  const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
3708
+ function runCommand$4(command) {
3709
+ return {
3710
+ kind: "run-command",
3711
+ label: command,
3712
+ command
3713
+ };
3714
+ }
3715
+ function userChoice$4(label) {
3716
+ return {
3717
+ kind: "user-choice",
3718
+ label
3719
+ };
3720
+ }
3721
+ /** A URL is not a command: putting one in `command` tells a consumer to
3722
+ * execute it. */
3723
+ function openUrl(url) {
3724
+ return {
3725
+ kind: "open-url",
3726
+ label: url,
3727
+ url
3728
+ };
3729
+ }
3773
3730
  async function readProjectListLocalBinding(cwd, projects, signal) {
3774
3731
  const pinResult = await readLocalResolutionPin(cwd, signal);
3775
3732
  if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
@@ -3790,37 +3747,31 @@ function localPinReadErrorToInvalidLocalBinding(error) {
3790
3747
  });
3791
3748
  }
3792
3749
  function transferRecipientRequiredError(formatCommand) {
3793
- return new CliError({
3794
- code: "TRANSFER_RECIPIENT_REQUIRED",
3795
- domain: "project",
3796
- summary: "Transfer recipient required",
3750
+ return new CliStructuredError("PROJECT.TRANSFER_RECIPIENT_REQUIRED", "Transfer recipient required", {
3797
3751
  why: "Project transfer needs the receiving workspace.",
3798
- fix: "Pass --to-workspace <id-or-name> for a locally authenticated workspace, or --recipient-token <token> for a cross-account transfer.",
3799
- exitCode: 2,
3800
- nextSteps: [formatCommand([
3801
- "auth",
3802
- "workspace",
3803
- "list"
3804
- ]), formatCommand([
3805
- "project",
3806
- "transfer",
3807
- "<project>",
3808
- "--to-workspace",
3809
- "<id-or-name>",
3810
- "--confirm",
3811
- "<project-id>"
3812
- ])]
3752
+ nextActions: [
3753
+ userChoice$4("Pass --to-workspace <id-or-name> for a locally authenticated workspace, or --recipient-token <token> for a cross-account transfer."),
3754
+ runCommand$4(formatCommand([
3755
+ "auth",
3756
+ "workspace",
3757
+ "list"
3758
+ ])),
3759
+ runCommand$4(formatCommand([
3760
+ "project",
3761
+ "transfer",
3762
+ "<project>",
3763
+ "--to-workspace",
3764
+ "<id-or-name>",
3765
+ "--confirm",
3766
+ "<project-id>"
3767
+ ]))
3768
+ ]
3813
3769
  });
3814
3770
  }
3815
3771
  function transferRecipientUnavailableError(formatCommand) {
3816
- return new CliError({
3817
- code: "TRANSFER_RECIPIENT_UNAVAILABLE",
3818
- domain: "project",
3819
- summary: "Local workspace sessions are unavailable",
3772
+ return new CliStructuredError("PROJECT.TRANSFER_RECIPIENT_UNAVAILABLE", "Local workspace sessions are unavailable", {
3820
3773
  why: `--to-workspace resolves locally stored OAuth sessions, but ${SERVICE_TOKEN_ENV_VAR} is set and service-token mode does not read them.`,
3821
- fix: "Pass --recipient-token <token> with an access token for the receiving workspace, or unset the service token.",
3822
- exitCode: 1,
3823
- nextSteps: [formatCommand([
3774
+ nextActions: [userChoice$4("Pass --recipient-token <token> with an access token for the receiving workspace, or unset the service token."), runCommand$4(formatCommand([
3824
3775
  "project",
3825
3776
  "transfer",
3826
3777
  "<project>",
@@ -3828,7 +3779,7 @@ function transferRecipientUnavailableError(formatCommand) {
3828
3779
  "<token>",
3829
3780
  "--confirm",
3830
3781
  "<project-id>"
3831
- ])]
3782
+ ]))]
3832
3783
  });
3833
3784
  }
3834
3785
  async function cleanupLocalPinForProject(context, projectId, hooks) {
@@ -3966,8 +3917,8 @@ async function findRepositoryInInstallationIfAvailable(api, installationId, repo
3966
3917
  }
3967
3918
  }
3968
3919
  function isUnavailableScmInstallationError(error) {
3969
- if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
3970
- return error.meta.status === 404 || error.meta.status === 422;
3920
+ if (!CliStructuredError.is(error) || error.code !== "GIT.REPO_CONNECTION_FAILED") return false;
3921
+ return error.meta?.status === 404 || error.meta?.status === 422;
3971
3922
  }
3972
3923
  async function createGitHubInstallIntent(api, workspaceId, signal) {
3973
3924
  const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
@@ -4020,69 +3971,50 @@ function toRepositoryConnection(record) {
4020
3971
  };
4021
3972
  }
4022
3973
  function unsupportedRepositoryProviderError() {
4023
- return new CliError({
4024
- code: "REPO_PROVIDER_UNSUPPORTED",
4025
- domain: "project",
4026
- summary: "Repository provider is not supported",
3974
+ return new CliStructuredError("GIT.REPO_PROVIDER_UNSUPPORTED", "Repository provider is not supported", {
4027
3975
  why: "Repository connection supports GitHub repository URLs only.",
4028
- fix: "Pass a GitHub repository URL such as git@github.com:prisma/prisma-cli.git.",
4029
- exitCode: 2,
4030
- nextSteps: ["prisma git connect git@github.com:owner/repo.git"]
3976
+ nextActions: [userChoice$4("Pass a GitHub repository URL such as git@github.com:prisma/prisma-cli.git."), runCommand$4("prisma git connect git@github.com:owner/repo.git")]
4031
3977
  });
4032
3978
  }
4033
3979
  function repoNotConnectedError() {
4034
- return new CliError({
4035
- code: "REPO_NOT_CONNECTED",
4036
- domain: "project",
4037
- summary: "No GitHub repository connected",
3980
+ return new CliStructuredError("GIT.REPO_NOT_CONNECTED", "No GitHub repository connected", {
4038
3981
  why: "The resolved project does not have an active GitHub repository connection.",
4039
- fix: "Run prisma git connect before disconnecting.",
4040
- exitCode: 1,
4041
- nextSteps: ["prisma git connect"]
3982
+ nextActions: [userChoice$4("Run prisma git connect before disconnecting."), runCommand$4("prisma git connect")]
4042
3983
  });
4043
3984
  }
4044
- function repoInstallationRequiredError(repository, installUrl, opened) {
4045
- return new CliError({
4046
- code: "REPO_INSTALLATION_REQUIRED",
4047
- domain: "project",
4048
- summary: "GitHub App installation required",
3985
+ function repoInstallationRequiredError(repository, installUrl) {
3986
+ return new CliStructuredError("GIT.REPO_INSTALLATION_REQUIRED", "GitHub App installation required", {
4049
3987
  why: `The selected workspace does not have a GitHub App installation that can be used to link ${repository.fullName}.`,
4050
- fix: opened ? "Finish installing the GitHub App in the browser, then rerun prisma git connect." : "Open the GitHub App installation URL, approve access, then rerun prisma git connect.",
4051
3988
  meta: {
4052
3989
  repository: repository.fullName,
4053
- installUrl,
4054
- opened
3990
+ installUrl
4055
3991
  },
4056
- exitCode: 1,
4057
- nextSteps: [installUrl, `prisma git connect ${repository.url}`]
3992
+ nextActions: [
3993
+ userChoice$4("Finish installing the GitHub App in the browser, then rerun prisma git connect."),
3994
+ openUrl(installUrl),
3995
+ runCommand$4(`prisma git connect ${repository.url}`)
3996
+ ]
4058
3997
  });
4059
3998
  }
4060
- function repoNotAccessibleError(repository, installUrl, opened) {
4061
- return new CliError({
4062
- code: "REPO_NOT_ACCESSIBLE",
4063
- domain: "project",
4064
- summary: "GitHub repository is not accessible",
3999
+ function repoNotAccessibleError(repository, installUrl) {
4000
+ return new CliStructuredError("GIT.REPO_NOT_ACCESSIBLE", "GitHub repository is not accessible", {
4065
4001
  why: `The GitHub App installations connected to this workspace do not expose ${repository.fullName}.`,
4066
- fix: "Open the GitHub App installation URL, grant access to this repository, then rerun prisma git connect.",
4067
4002
  meta: {
4068
4003
  repository: repository.fullName,
4069
- installUrl,
4070
- opened
4004
+ installUrl
4071
4005
  },
4072
- exitCode: 1,
4073
- nextSteps: [installUrl, `prisma git connect ${repository.url}`]
4006
+ nextActions: [
4007
+ userChoice$4("Open the GitHub App installation URL, grant access to this repository, then rerun prisma git connect."),
4008
+ openUrl(installUrl),
4009
+ runCommand$4(`prisma git connect ${repository.url}`)
4010
+ ]
4074
4011
  });
4075
4012
  }
4076
4013
  function repoAlreadyConnectedError(repositoryFullName) {
4077
- return new CliError({
4078
- code: "REPO_ALREADY_CONNECTED",
4079
- domain: "project",
4080
- summary: "Project already has a GitHub repository connected",
4014
+ return new CliStructuredError("GIT.REPO_ALREADY_CONNECTED", "Project already has a GitHub repository connected", {
4081
4015
  why: `The resolved project is already connected to ${repositoryFullName}.`,
4082
- fix: "Disconnect the existing repository before connecting a different one.",
4083
4016
  meta: { repository: repositoryFullName },
4084
- exitCode: 1,
4085
- nextSteps: ["prisma git disconnect"]
4017
+ nextActions: [userChoice$4("Disconnect the existing repository before connecting a different one."), runCommand$4("prisma git disconnect")]
4086
4018
  });
4087
4019
  }
4088
4020
  function repositoryFullNamesMatch(left, right) {
@@ -4093,26 +4025,22 @@ function repoConnectionApiError(summary, response, error) {
4093
4025
  const apiCode = error?.error?.code;
4094
4026
  const apiMessage = error?.error?.message;
4095
4027
  const apiHint = error?.error?.hint;
4096
- if (status === 401 || status === 403) return authRequiredError(["prisma auth login"]);
4097
- return new CliError({
4098
- code: "REPO_CONNECTION_FAILED",
4099
- domain: "project",
4100
- summary,
4101
- why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
4102
- fix: apiHint ?? repoConnectionFixForStatus(status),
4028
+ const unauthorized = status === 401 || status === 403;
4029
+ return new CliStructuredError("GIT.REPO_CONNECTION_FAILED", summary, {
4030
+ why: apiMessage ?? (unauthorized ? `The Management API rejected the request as unauthorized (HTTP ${status}).` : `The Management API returned status ${status || "unknown"}.`),
4103
4031
  meta: {
4104
4032
  status,
4105
4033
  ...apiCode ? { apiCode } : {}
4106
4034
  },
4107
- exitCode: 1,
4108
- nextSteps: ["prisma project show"]
4035
+ nextActions: [userChoice$4(apiHint ?? repoConnectionFixForStatus(status)), runCommand$4(unauthorized ? "prisma auth login" : "prisma project show")]
4109
4036
  });
4110
4037
  }
4111
4038
  function repoConnectionFixForStatus(status) {
4039
+ if (status === 401 || status === 403) return "Sign in again with prisma auth login, then rerun the command.";
4112
4040
  if (status === 404) return "Install the GitHub App for this workspace, then rerun prisma git connect.";
4113
4041
  if (status === 409) return "This project or repository is already linked. Disconnect the old link first, then try again.";
4114
4042
  if (status === 422) return "Make sure the GitHub App installation has access to this repository.";
4115
- return "Re-run with --trace for the underlying API response details.";
4043
+ return "Re-run with --log-level verbose for the underlying API response details.";
4116
4044
  }
4117
4045
  //#endregion
4118
4046
  //#region src/lib/project/setup.ts
@@ -4126,7 +4054,7 @@ function resolveProjectForSetup(projectRef, projects, workspace) {
4126
4054
  if (match !== void 0) return match;
4127
4055
  throw projectNotFoundError$1(projectRef, workspace);
4128
4056
  }
4129
- function projectDirectoryBindingErrorToCliError(error) {
4057
+ function projectDirectoryBindingErrorToStructured(error) {
4130
4058
  return matchError(error, {
4131
4059
  LocalResolutionPinSerializationError: (error) => {
4132
4060
  throw error;
@@ -4154,16 +4082,18 @@ function projectDirectoryBindingErrorToCliError(error) {
4154
4082
  });
4155
4083
  }
4156
4084
  function localStateWriteFailedError(error, options) {
4157
- return new CliError({
4158
- code: "LOCAL_STATE_WRITE_FAILED",
4159
- domain: "project",
4160
- summary: "Could not save local Project binding",
4085
+ return new CliStructuredError("PROJECT.LOCAL_STATE_WRITE_FAILED", "Could not save local Project binding", {
4161
4086
  why: options.why,
4162
- fix: "Check that this directory is writable and that .prisma/local.json and .gitignore are not blocked by directories or permissions, then retry.",
4163
- debug: formatDebugDetails(error.cause),
4164
4087
  meta: options.meta,
4165
- exitCode: 1,
4166
- nextSteps: ["prisma project link <id-or-name>"]
4088
+ cause: error.cause,
4089
+ nextActions: [{
4090
+ kind: "user-choice",
4091
+ label: "Check that this directory is writable and that .prisma/local.json and .gitignore are not blocked by directories or permissions, then retry."
4092
+ }, {
4093
+ kind: "run-command",
4094
+ label: "prisma project link <id-or-name>",
4095
+ command: "prisma project link <id-or-name>"
4096
+ }]
4167
4097
  });
4168
4098
  }
4169
4099
  function toProjectSummary(project) {
@@ -4175,29 +4105,35 @@ function toProjectSummary(project) {
4175
4105
  };
4176
4106
  }
4177
4107
  function projectSetupNameRequiredError(command) {
4178
- return usageError("Project create requires a name", "The project name must be a non-empty value.", "Pass a Project name explicitly.", [`prisma ${command} my-app`], "project");
4108
+ const example = `prisma ${command} my-app`;
4109
+ return new CliStructuredError("PROJECT.USAGE_ERROR", "Project create requires a name", {
4110
+ why: "The project name must be a non-empty value.",
4111
+ nextActions: [{
4112
+ kind: "user-choice",
4113
+ label: "Pass a Project name explicitly."
4114
+ }, {
4115
+ kind: "run-command",
4116
+ label: example,
4117
+ command: example
4118
+ }]
4119
+ });
4179
4120
  }
4180
4121
  function projectCreateFailedError(error, projectName, workspace, options) {
4181
4122
  const status = extractHttpStatus(error);
4182
- if (status === 401 || status === 403) return new CliError({
4183
- code: "PROJECT_CREATE_FAILED",
4184
- domain: "project",
4185
- summary: `Could not create Project "${projectName}"`,
4186
- why: `The platform rejected the Project create in workspace "${workspace.name}" (HTTP ${status}).`,
4187
- fix: options.permissionFix,
4188
- debug: formatDebugDetails(error),
4189
- exitCode: 1,
4190
- nextSteps: options.nextSteps
4191
- });
4192
- return new CliError({
4193
- code: "PROJECT_CREATE_FAILED",
4194
- domain: "project",
4195
- summary: `Could not create Project "${projectName}"`,
4196
- why: error instanceof Error ? error.message : String(error),
4197
- fix: options.fallbackFix,
4198
- debug: formatDebugDetails(error),
4199
- exitCode: 1,
4200
- nextSteps: options.nextSteps
4123
+ const permissionRejection = status === 401 || status === 403;
4124
+ const message = error instanceof Error ? error.message : String(error);
4125
+ const nextActions = [{
4126
+ kind: "user-choice",
4127
+ label: permissionRejection ? options.permissionFix : options.fallbackFix
4128
+ }, ...options.nextSteps.map((step) => ({
4129
+ kind: "run-command",
4130
+ label: step,
4131
+ command: step
4132
+ }))];
4133
+ return new CliStructuredError("PROJECT.CREATE_FAILED", `Could not create Project "${projectName}"`, {
4134
+ why: permissionRejection ? `The platform rejected the Project create in workspace "${workspace.name}" (HTTP ${status}).` : message,
4135
+ cause: error,
4136
+ nextActions
4201
4137
  });
4202
4138
  }
4203
4139
  const HTTP_STATUS_IN_MESSAGE = /\(HTTP (\d{3})\)/;
@@ -4212,10 +4148,6 @@ function extractHttpStatus(error) {
4212
4148
  }
4213
4149
  return null;
4214
4150
  }
4215
- function formatDebugDetails(error) {
4216
- if (error instanceof Error) return error.stack ?? error.message;
4217
- return typeof error === "string" ? error : null;
4218
- }
4219
4151
  //#endregion
4220
4152
  //#region src/commands/project/context.ts
4221
4153
  /**
@@ -4265,7 +4197,7 @@ async function resolvePinnedProject(ctx, workspace, explicitProject, commandName
4265
4197
  listProjects: () => listWorkspaceProjects$1(ctx),
4266
4198
  commandName
4267
4199
  });
4268
- if (target.isErr()) throw projectResolutionErrorToCliError(target.error);
4200
+ if (target.isErr()) throw projectResolutionErrorToStructured(target.error);
4269
4201
  return target.value;
4270
4202
  }
4271
4203
  /** Writes `.prisma/local.json` for this directory and keeps it out of
@@ -4275,9 +4207,9 @@ async function bindDirectoryToProject(ctx, workspace, project, action) {
4275
4207
  workspaceId: workspace.id,
4276
4208
  projectId: project.id
4277
4209
  }, ctx.signal);
4278
- if (written.isErr()) throw projectDirectoryBindingErrorToCliError(written.error);
4210
+ if (written.isErr()) throw projectDirectoryBindingErrorToStructured(written.error);
4279
4211
  const ignored = await ensureLocalResolutionPinGitignore(ctx.cwd, ctx.signal);
4280
- if (ignored.isErr()) throw projectDirectoryBindingErrorToCliError(ignored.error);
4212
+ if (ignored.isErr()) throw projectDirectoryBindingErrorToStructured(ignored.error);
4281
4213
  return {
4282
4214
  workspace,
4283
4215
  project,
@@ -4313,129 +4245,6 @@ async function resolveActiveWorkspace(ctx) {
4313
4245
  };
4314
4246
  }
4315
4247
  //#endregion
4316
- //#region src/commands/project/errors.ts
4317
- const PROJECT_CODE_MAP = {
4318
- USAGE_ERROR: "PROJECT.USAGE_ERROR",
4319
- PROJECT_NOT_FOUND: "PROJECT.NOT_FOUND",
4320
- PROJECT_AMBIGUOUS: "PROJECT.AMBIGUOUS",
4321
- PROJECT_SETUP_REQUIRED: "PROJECT.SETUP_REQUIRED",
4322
- LOCAL_STATE_STALE: "PROJECT.LOCAL_STATE_STALE",
4323
- LOCAL_PROJECT_WORKSPACE_MISMATCH: "PROJECT.LOCAL_WORKSPACE_MISMATCH",
4324
- LOCAL_STATE_WRITE_FAILED: "PROJECT.LOCAL_STATE_WRITE_FAILED",
4325
- PROJECT_CREATE_FAILED: "PROJECT.CREATE_FAILED",
4326
- PROJECT_RENAME_FAILED: "PROJECT.RENAME_FAILED",
4327
- PROJECT_DELETE_BLOCKED: "PROJECT.DELETE_BLOCKED",
4328
- PROJECT_TRANSFER_REJECTED: "PROJECT.TRANSFER_REJECTED",
4329
- TRANSFER_RECIPIENT_REQUIRED: "PROJECT.TRANSFER_RECIPIENT_REQUIRED",
4330
- TRANSFER_RECIPIENT_UNAVAILABLE: "PROJECT.TRANSFER_RECIPIENT_UNAVAILABLE",
4331
- CONFIRMATION_REQUIRED: "PROJECT.CONFIRMATION_REQUIRED",
4332
- PROJECT_LINK_TARGET_REQUIRED: "PROJECT.LINK_TARGET_REQUIRED",
4333
- ENV_VARIABLE_ALREADY_EXISTS: "PROJECT.ENV_VARIABLE_ALREADY_EXISTS",
4334
- ENV_VARIABLE_NOT_FOUND: "PROJECT.ENV_VARIABLE_NOT_FOUND",
4335
- ENV_BRANCH_NOT_FOUND: "PROJECT.ENV_BRANCH_NOT_FOUND",
4336
- ENV_BRANCH_SCOPE_IS_PRODUCTION: "PROJECT.ENV_BRANCH_SCOPE_IS_PRODUCTION",
4337
- ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH: "PROJECT.ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH",
4338
- ENV_FILE_APPLY_FAILED: "PROJECT.ENV_FILE_APPLY_FAILED",
4339
- ENV_API_ERROR: "PROJECT.ENV_API_ERROR",
4340
- PROJECT_API_ERROR: "PROJECT.API_ERROR"
4341
- };
4342
- const PACKAGE_RUNNER_PREFIX = /^\S+(?: -y)? @prisma\/cli@\S+ /;
4343
- const COMMENT_PREFIX$1 = /^#\s*/;
4344
- /** Ported command strings already name this binary; what still needs
4345
- * porting is the package-runner spelling the legacy formatter emitted
4346
- * (`npx -y @prisma/cli@next auth login`), which becomes a plain
4347
- * invocation. Anything else is passed through untouched. */
4348
- function portCommandString(command) {
4349
- if (command.startsWith(`prisma `)) return command;
4350
- return command.replace(PACKAGE_RUNNER_PREFIX, `${CLI_NAME} `);
4351
- }
4352
- const STALE_INTERACTIVE_SIGN_IN$4 = /, or rerun the command in a TTY to sign in interactively\./g;
4353
- const TRACE_FLAG$4 = /--trace/g;
4354
- /** `--trace` is gone in this CLI; the log level replaces it. Interactive
4355
- * sign-in is gone too (R-S2b-2), so the legacy offer to rerun in a TTY
4356
- * describes something this CLI cannot do; `auth login` is the whole remedy. */
4357
- function portFixText$4(fix) {
4358
- return fix.replace(TRACE_FLAG$4, "--log-level verbose").replace(STALE_INTERACTIVE_SIGN_IN$4, ".");
4359
- }
4360
- /** A `#`-comment line in the legacy nextSteps is not an action: it
4361
- * explains the command that follows it, so it becomes that action's
4362
- * `reason`. */
4363
- function runCommandActions$1(nextSteps) {
4364
- const actions = [];
4365
- let reason;
4366
- for (const step of nextSteps) {
4367
- if (step.startsWith("#")) {
4368
- reason = step.replace(COMMENT_PREFIX$1, "");
4369
- continue;
4370
- }
4371
- const command = portCommandString(step);
4372
- actions.push({
4373
- kind: "run-command",
4374
- label: command,
4375
- command,
4376
- ...reason === void 0 ? {} : { reason }
4377
- });
4378
- reason = void 0;
4379
- }
4380
- return actions;
4381
- }
4382
- function nextActionsFor$4(error) {
4383
- return [...error.fix ? [{
4384
- kind: "user-choice",
4385
- label: portFixText$4(error.fix)
4386
- }] : [], ...runCommandActions$1(error.nextSteps)];
4387
- }
4388
- function mapProjectOperationError(error) {
4389
- if (!(error instanceof CliError)) return null;
4390
- return new CliStructuredError(PROJECT_CODE_MAP[error.code] ?? `PROJECT.${error.code}`, error.summary, {
4391
- why: error.why ?? void 0,
4392
- meta: Object.keys(error.meta).length > 0 ? error.meta : void 0,
4393
- nextActions: nextActionsFor$4(error)
4394
- });
4395
- }
4396
- //#endregion
4397
- //#region src/commands/branch/errors.ts
4398
- const BRANCH_CODE_MAP = { BRANCH_API_ERROR: "BRANCH.API_ERROR" };
4399
- /** The project-resolution codes `branch list` can raise; they keep the
4400
- * project group's dotted codes and copy. */
4401
- const PROJECT_CODES$3 = new Set([
4402
- "PROJECT_NOT_FOUND",
4403
- "PROJECT_AMBIGUOUS",
4404
- "PROJECT_SETUP_REQUIRED",
4405
- "LOCAL_STATE_STALE",
4406
- "LOCAL_PROJECT_WORKSPACE_MISMATCH"
4407
- ]);
4408
- const STALE_INTERACTIVE_SIGN_IN$3 = /, or rerun the command in a TTY to sign in interactively\./g;
4409
- const TRACE_FLAG$3 = /--trace/g;
4410
- /** `--trace` is gone in this CLI; the log level replaces it. Interactive
4411
- * sign-in is gone too (R-S2b-2), so the legacy offer to rerun in a TTY
4412
- * describes something this CLI cannot do; `auth login` is the whole remedy. */
4413
- function portFixText$3(fix) {
4414
- return fix.replace(TRACE_FLAG$3, "--log-level verbose").replace(STALE_INTERACTIVE_SIGN_IN$3, ".");
4415
- }
4416
- function nextActionsFor$3(error) {
4417
- return [...error.fix ? [{
4418
- kind: "user-choice",
4419
- label: portFixText$3(error.fix)
4420
- }] : [], ...error.nextSteps.map((step) => {
4421
- const command = portCommandString(step);
4422
- return {
4423
- kind: "run-command",
4424
- label: command,
4425
- command
4426
- };
4427
- })];
4428
- }
4429
- function mapBranchOperationError(error) {
4430
- if (!(error instanceof CliError)) return null;
4431
- if (PROJECT_CODES$3.has(error.code)) return mapProjectOperationError(error);
4432
- return new CliStructuredError(BRANCH_CODE_MAP[error.code] ?? `BRANCH.${error.code}`, error.summary, {
4433
- why: error.why ?? void 0,
4434
- meta: Object.keys(error.meta).length > 0 ? error.meta : void 0,
4435
- nextActions: nextActionsFor$3(error)
4436
- });
4437
- }
4438
- //#endregion
4439
4248
  //#region src/commands/branch/list.ts
4440
4249
  /** The `branch list` command. */
4441
4250
  const TITLE$12 = "Listing branches for the resolved project.";
@@ -4489,20 +4298,14 @@ const branchListCommand = defineCommand({
4489
4298
  }) } },
4490
4299
  needs: { credentials: true },
4491
4300
  handler: async (args, ctx) => {
4492
- try {
4493
- const target = await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), args.flags.project, "branch list");
4494
- const branches = await listBranches$1(ctx.api, target.project.id, ctx.signal);
4495
- const result = {
4496
- projectId: target.project.id,
4497
- projectName: target.project.name,
4498
- branches: sortBranches(branches.map(toBranchSummary))
4499
- };
4500
- return ok(ctx.present({ data: result }, listPresentations$8(result)));
4501
- } catch (error) {
4502
- const mapped = mapBranchOperationError(error);
4503
- if (mapped) return notOk(mapped);
4504
- throw error;
4505
- }
4301
+ const target = await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), args.flags.project, "branch list");
4302
+ const branches = await listBranches$1(ctx.api, target.project.id, ctx.signal);
4303
+ const result = {
4304
+ projectId: target.project.id,
4305
+ projectName: target.project.name,
4306
+ branches: sortBranches(branches.map(toBranchSummary))
4307
+ };
4308
+ return ok(ctx.present({ data: result }, listPresentations$8(result)));
4506
4309
  }
4507
4310
  });
4508
4311
  //#endregion
@@ -4583,15 +4386,7 @@ function createManagementBucketProvider(client) {
4583
4386
  const accessKeyId = raw.accessKeyId;
4584
4387
  const endpoint = raw.endpoint;
4585
4388
  const bucketName = raw.bucketName;
4586
- if (!secretAccessKey || !accessKeyId || !endpoint || !bucketName) throw new CliError({
4587
- code: "BUCKET_KEY_SECRET_MISSING",
4588
- domain: "bucket",
4589
- summary: "Created bucket key did not return credentials",
4590
- why: "Bucket key credentials are one-time-view secrets, but the Management API did not include them in this create response.",
4591
- fix: "Create another bucket key and store the returned credentials immediately.",
4592
- exitCode: 1,
4593
- nextSteps: [`prisma bucket key create ${options.bucketId}`]
4594
- });
4389
+ if (!secretAccessKey || !accessKeyId || !endpoint || !bucketName) throw bucketKeySecretMissingError(options.bucketId);
4595
4390
  return {
4596
4391
  key: normalizeKey(raw),
4597
4392
  secretAccessKey,
@@ -4630,21 +4425,66 @@ function normalizeKey(raw) {
4630
4425
  createdAt: raw.createdAt
4631
4426
  };
4632
4427
  }
4428
+ const VERBOSE_LOG_FIX$1 = "Re-run with --log-level verbose for the underlying API response details.";
4429
+ function userChoice$3(label) {
4430
+ return {
4431
+ kind: "user-choice",
4432
+ label
4433
+ };
4434
+ }
4435
+ function runCommand$3(command) {
4436
+ return {
4437
+ kind: "run-command",
4438
+ label: command,
4439
+ command
4440
+ };
4441
+ }
4442
+ function bucketKeySecretMissingError(bucketId) {
4443
+ return new CliStructuredError("BUCKET.KEY_SECRET_MISSING", "Created bucket key did not return credentials", {
4444
+ why: "Bucket key credentials are one-time-view secrets, but the Management API did not include them in this create response.",
4445
+ nextActions: [userChoice$3("Create another bucket key and store the returned credentials immediately."), runCommand$3(`${CLI_NAME} bucket key create ${bucketId}`)]
4446
+ });
4447
+ }
4448
+ /** A 401 or 403 is the API refusing the caller, not a bucket problem. */
4449
+ function isRejectedCaller$1(status) {
4450
+ return status === 401 || status === 403;
4451
+ }
4452
+ function apiErrorWhy$1(status, message) {
4453
+ if (!isRejectedCaller$1(status)) return message ?? `The Management API returned status ${status || "unknown"}.`;
4454
+ const rejection = `The Management API rejected the request as ${status === 401 ? "unauthorized" : "forbidden"}.`;
4455
+ return message ? `${rejection} ${message}` : rejection;
4456
+ }
4457
+ function apiErrorMeta$1(status, apiCode) {
4458
+ if (!status && apiCode === void 0) return;
4459
+ return {
4460
+ ...status ? { status } : {},
4461
+ ...apiCode === void 0 ? {} : { apiCode }
4462
+ };
4463
+ }
4464
+ function apiErrorActions$1(status, hint) {
4465
+ if (!isRejectedCaller$1(status)) return [userChoice$3(hint ?? VERBOSE_LOG_FIX$1)];
4466
+ return [userChoice$3(hint ?? `Sign in again with prisma auth login, then retry the command.`), runCommand$3(`${CLI_NAME} auth login`)];
4467
+ }
4468
+ /**
4469
+ * Every bucket Management API failure lands on the one registered code.
4470
+ * The response's own error code is data, not an identity: it travels in
4471
+ * `meta.apiCode` beside `meta.status` so a consumer can still branch on
4472
+ * it without the CLI minting a code it never registered.
4473
+ */
4633
4474
  function bucketApiError(summary, response, error) {
4634
4475
  const status = response?.status ?? 0;
4635
- return new CliError({
4636
- code: error?.error?.code ?? "BUCKET_API_ERROR",
4637
- domain: "bucket",
4638
- summary,
4639
- why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
4640
- fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
4641
- exitCode: 1,
4642
- nextSteps: []
4476
+ const meta = apiErrorMeta$1(status, error?.error?.code);
4477
+ return new CliStructuredError("BUCKET.API_ERROR", summary, {
4478
+ why: apiErrorWhy$1(status, error?.error?.message),
4479
+ ...meta === void 0 ? {} : { meta },
4480
+ nextActions: apiErrorActions$1(status, error?.error?.hint)
4643
4481
  });
4644
4482
  }
4645
4483
  //#endregion
4646
4484
  //#region src/commands/bucket/context.ts
4647
4485
  /** Workspace, project and provider for the `bucket *` commands. */
4486
+ /** Where a caller who is missing a bucket id finds one. */
4487
+ const LIST_BUCKETS_COMMAND = `${CLI_NAME} bucket list`;
4648
4488
  const projectFlag$3 = flag.string({
4649
4489
  brief: "Project id or name",
4650
4490
  placeholder: "id-or-name"
@@ -4675,52 +4515,6 @@ function resolveBucketProviderOnly(ctx) {
4675
4515
  return createManagementBucketProvider(ctx.api);
4676
4516
  }
4677
4517
  //#endregion
4678
- //#region src/commands/bucket/errors.ts
4679
- const BUCKET_CODE_MAP = {
4680
- USAGE_ERROR: "BUCKET.USAGE_ERROR",
4681
- BUCKET_KEY_SECRET_MISSING: "BUCKET.KEY_SECRET_MISSING",
4682
- BUCKET_API_ERROR: "BUCKET.API_ERROR"
4683
- };
4684
- /** The project-resolution codes `bucket list` and `bucket create` can
4685
- * raise; they keep the project group's dotted codes and copy. */
4686
- const PROJECT_CODES$2 = new Set([
4687
- "PROJECT_NOT_FOUND",
4688
- "PROJECT_AMBIGUOUS",
4689
- "PROJECT_SETUP_REQUIRED",
4690
- "LOCAL_STATE_STALE",
4691
- "LOCAL_PROJECT_WORKSPACE_MISMATCH"
4692
- ]);
4693
- const STALE_INTERACTIVE_SIGN_IN$2 = /, or rerun the command in a TTY to sign in interactively\./g;
4694
- const TRACE_FLAG$2 = /--trace/g;
4695
- /** `--trace` is gone in this CLI; the log level replaces it. Interactive
4696
- * sign-in is gone too (R-S2b-2), so the legacy offer to rerun in a TTY
4697
- * describes something this CLI cannot do; `auth login` is the whole remedy. */
4698
- function portFixText$2(fix) {
4699
- return fix.replace(TRACE_FLAG$2, "--log-level verbose").replace(STALE_INTERACTIVE_SIGN_IN$2, ".");
4700
- }
4701
- function nextActionsFor$2(error) {
4702
- return [...error.fix ? [{
4703
- kind: "user-choice",
4704
- label: portFixText$2(error.fix)
4705
- }] : [], ...error.nextSteps.map((step) => {
4706
- const command = portCommandString(step);
4707
- return {
4708
- kind: "run-command",
4709
- label: command,
4710
- command
4711
- };
4712
- })];
4713
- }
4714
- function mapBucketOperationError(error) {
4715
- if (!(error instanceof CliError)) return null;
4716
- if (PROJECT_CODES$2.has(error.code)) return mapProjectOperationError(error);
4717
- return new CliStructuredError(BUCKET_CODE_MAP[error.code] ?? `BUCKET.${error.code}`, error.summary, {
4718
- why: error.why ?? void 0,
4719
- meta: Object.keys(error.meta).length > 0 ? error.meta : void 0,
4720
- nextActions: nextActionsFor$2(error)
4721
- });
4722
- }
4723
- //#endregion
4724
4518
  //#region src/commands/bucket/presentation.ts
4725
4519
  /** Legacy `formatBucketTarget`. */
4726
4520
  function bucketTargetLabel(projectName, branchId) {
@@ -4777,34 +4571,28 @@ const bucketCreateCommand = defineCommand({
4777
4571
  },
4778
4572
  needs: { credentials: true },
4779
4573
  handler: async (args, ctx) => {
4780
- try {
4781
- const { provider, projectId, projectName } = await resolveBucketContext(ctx, args.flags, "bucket create");
4782
- const bucket = await provider.createBucket({
4783
- projectId,
4784
- name: args.flags.name?.trim() || void 0,
4785
- branchGitName: args.flags.branch,
4786
- signal: ctx.signal
4787
- });
4788
- const result = {
4789
- projectId,
4790
- projectName,
4791
- bucket
4792
- };
4793
- return ok(ctx.present({ data: result }, {
4794
- human: () => [{
4795
- kind: "summary",
4796
- status: "ok",
4797
- text: `Created bucket "${bucket.name}" in ${bucketTargetLabel(projectName, bucket.branchId)}.`
4798
- }],
4799
- stdout: () => [],
4800
- json: () => result,
4801
- next: () => []
4802
- }));
4803
- } catch (error) {
4804
- const mapped = mapBucketOperationError(error);
4805
- if (mapped) return notOk(mapped);
4806
- throw error;
4807
- }
4574
+ const { provider, projectId, projectName } = await resolveBucketContext(ctx, args.flags, "bucket create");
4575
+ const bucket = await provider.createBucket({
4576
+ projectId,
4577
+ name: args.flags.name?.trim() || void 0,
4578
+ branchGitName: args.flags.branch,
4579
+ signal: ctx.signal
4580
+ });
4581
+ const result = {
4582
+ projectId,
4583
+ projectName,
4584
+ bucket
4585
+ };
4586
+ return ok(ctx.present({ data: result }, {
4587
+ human: () => [{
4588
+ kind: "summary",
4589
+ status: "ok",
4590
+ text: `Created bucket "${bucket.name}" in ${bucketTargetLabel(projectName, bucket.branchId)}.`
4591
+ }],
4592
+ stdout: () => [],
4593
+ json: () => result,
4594
+ next: () => []
4595
+ }));
4808
4596
  }
4809
4597
  });
4810
4598
  //#endregion
@@ -4844,18 +4632,22 @@ const bucketDeleteCommand = defineCommand({
4844
4632
  },
4845
4633
  needs: { credentials: true },
4846
4634
  handler: async (args, ctx) => {
4847
- try {
4848
- const bucketId = args.positionals.bucketId.trim();
4849
- if (!bucketId) throw usageError("Bucket id required", "Bucket deletion needs a bucket id.", "Pass the bucket id to delete.", [`${CLI_NAME} bucket list`], "bucket");
4850
- await ctx.prompt.consent(CONSENT_QUESTION$6, { token: bucketId });
4851
- await resolveBucketProviderOnly(ctx).deleteBucket(bucketId, { signal: ctx.signal });
4852
- const result = { bucket: { id: bucketId } };
4853
- return ok(ctx.present({ data: result }, deletePresentations$5(result)));
4854
- } catch (error) {
4855
- const mapped = mapBucketOperationError(error);
4856
- if (mapped) return notOk(mapped);
4857
- throw error;
4858
- }
4635
+ const bucketId = args.positionals.bucketId.trim();
4636
+ if (!bucketId) throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id required", {
4637
+ why: "Bucket deletion needs a bucket id.",
4638
+ nextActions: [{
4639
+ kind: "user-choice",
4640
+ label: "Pass the bucket id to delete."
4641
+ }, {
4642
+ kind: "run-command",
4643
+ label: LIST_BUCKETS_COMMAND,
4644
+ command: LIST_BUCKETS_COMMAND
4645
+ }]
4646
+ });
4647
+ await ctx.prompt.consent(CONSENT_QUESTION$6, { token: bucketId });
4648
+ await resolveBucketProviderOnly(ctx).deleteBucket(bucketId, { signal: ctx.signal });
4649
+ const result = { bucket: { id: bucketId } };
4650
+ return ok(ctx.present({ data: result }, deletePresentations$5(result)));
4859
4651
  }
4860
4652
  });
4861
4653
  //#endregion
@@ -4936,24 +4728,28 @@ const bucketKeyCreateCommand = defineCommand({
4936
4728
  },
4937
4729
  needs: { credentials: true },
4938
4730
  handler: async (args, ctx) => {
4939
- try {
4940
- const bucketId = args.positionals.bucketId.trim();
4941
- if (!bucketId) throw usageError("Bucket id required", "Bucket key creation needs a bucket id.", "Pass the bucket id.", ["prisma bucket list"], "bucket");
4942
- const result = {
4731
+ const bucketId = args.positionals.bucketId.trim();
4732
+ if (!bucketId) throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id required", {
4733
+ why: "Bucket key creation needs a bucket id.",
4734
+ nextActions: [{
4735
+ kind: "user-choice",
4736
+ label: "Pass the bucket id."
4737
+ }, {
4738
+ kind: "run-command",
4739
+ label: LIST_BUCKETS_COMMAND,
4740
+ command: LIST_BUCKETS_COMMAND
4741
+ }]
4742
+ });
4743
+ const result = {
4744
+ bucketId,
4745
+ ...await resolveBucketProviderOnly(ctx).createKey({
4943
4746
  bucketId,
4944
- ...await resolveBucketProviderOnly(ctx).createKey({
4945
- bucketId,
4946
- name: args.flags.name?.trim() || void 0,
4947
- role: resolveKeyRole(args.flags.role),
4948
- signal: ctx.signal
4949
- })
4950
- };
4951
- return ok(ctx.present({ data: result }, createPresentations$1(result)));
4952
- } catch (error) {
4953
- const mapped = mapBucketOperationError(error);
4954
- if (mapped) return notOk(mapped);
4955
- throw error;
4956
- }
4747
+ name: args.flags.name?.trim() || void 0,
4748
+ role: resolveKeyRole(args.flags.role),
4749
+ signal: ctx.signal
4750
+ })
4751
+ };
4752
+ return ok(ctx.present({ data: result }, createPresentations$1(result)));
4957
4753
  }
4958
4754
  });
4959
4755
  //#endregion
@@ -4998,18 +4794,25 @@ const bucketKeyDeleteCommand = defineCommand({
4998
4794
  },
4999
4795
  needs: { credentials: true },
5000
4796
  handler: async (args, ctx) => {
5001
- try {
5002
- const bucketId = args.positionals.bucketId.trim();
5003
- const keyId = args.positionals.keyId.trim();
5004
- if (!bucketId || !keyId) throw usageError("Bucket id and key id required", "Bucket key deletion needs both a bucket id and a key id.", "Pass the bucket id and key id.", ["prisma bucket key list <bucketId>"], "bucket");
5005
- await resolveBucketProviderOnly(ctx).deleteKey(bucketId, keyId, { signal: ctx.signal });
5006
- const result = { key: { id: keyId } };
5007
- return ok(ctx.present({ data: result }, deletePresentations$4(result)));
5008
- } catch (error) {
5009
- const mapped = mapBucketOperationError(error);
5010
- if (mapped) return notOk(mapped);
5011
- throw error;
4797
+ const bucketId = args.positionals.bucketId.trim();
4798
+ const keyId = args.positionals.keyId.trim();
4799
+ if (!bucketId || !keyId) {
4800
+ const listKeysCommand = `${CLI_NAME} bucket key list <bucketId>`;
4801
+ throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id and key id required", {
4802
+ why: "Bucket key deletion needs both a bucket id and a key id.",
4803
+ nextActions: [{
4804
+ kind: "user-choice",
4805
+ label: "Pass the bucket id and key id."
4806
+ }, {
4807
+ kind: "run-command",
4808
+ label: listKeysCommand,
4809
+ command: listKeysCommand
4810
+ }]
4811
+ });
5012
4812
  }
4813
+ await resolveBucketProviderOnly(ctx).deleteKey(bucketId, keyId, { signal: ctx.signal });
4814
+ const result = { key: { id: keyId } };
4815
+ return ok(ctx.present({ data: result }, deletePresentations$4(result)));
5013
4816
  }
5014
4817
  });
5015
4818
  //#endregion
@@ -5108,19 +4911,23 @@ const bucketKeyListCommand = defineCommand({
5108
4911
  },
5109
4912
  needs: { credentials: true },
5110
4913
  handler: async (args, ctx) => {
5111
- try {
5112
- const bucketId = args.positionals.bucketId.trim();
5113
- if (!bucketId) throw usageError("Bucket id required", "Bucket key listing needs a bucket id.", "Pass the bucket id.", ["prisma bucket list"], "bucket");
5114
- const result = {
5115
- bucketId,
5116
- keys: await resolveBucketProviderOnly(ctx).listKeys(bucketId, { signal: ctx.signal })
5117
- };
5118
- return ok(ctx.present({ data: result }, listPresentations$7(result)));
5119
- } catch (error) {
5120
- const mapped = mapBucketOperationError(error);
5121
- if (mapped) return notOk(mapped);
5122
- throw error;
5123
- }
4914
+ const bucketId = args.positionals.bucketId.trim();
4915
+ if (!bucketId) throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id required", {
4916
+ why: "Bucket key listing needs a bucket id.",
4917
+ nextActions: [{
4918
+ kind: "user-choice",
4919
+ label: "Pass the bucket id."
4920
+ }, {
4921
+ kind: "run-command",
4922
+ label: LIST_BUCKETS_COMMAND,
4923
+ command: LIST_BUCKETS_COMMAND
4924
+ }]
4925
+ });
4926
+ const result = {
4927
+ bucketId,
4928
+ keys: await resolveBucketProviderOnly(ctx).listKeys(bucketId, { signal: ctx.signal })
4929
+ };
4930
+ return ok(ctx.present({ data: result }, listPresentations$7(result)));
5124
4931
  }
5125
4932
  });
5126
4933
  //#endregion
@@ -5183,25 +4990,19 @@ const bucketListCommand = defineCommand({
5183
4990
  },
5184
4991
  needs: { credentials: true },
5185
4992
  handler: async (args, ctx) => {
5186
- try {
5187
- const { provider, projectId, projectName } = await resolveBucketContext(ctx, args.flags, "bucket list");
5188
- const buckets = await provider.listBuckets({
5189
- projectId,
5190
- branchName: args.flags.branch,
5191
- signal: ctx.signal
5192
- });
5193
- const result = {
5194
- projectId,
5195
- projectName,
5196
- branchName: args.flags.branch ?? null,
5197
- buckets
5198
- };
5199
- return ok(ctx.present({ data: result }, listPresentations$6(result)));
5200
- } catch (error) {
5201
- const mapped = mapBucketOperationError(error);
5202
- if (mapped) return notOk(mapped);
5203
- throw error;
5204
- }
4993
+ const { provider, projectId, projectName } = await resolveBucketContext(ctx, args.flags, "bucket list");
4994
+ const buckets = await provider.listBuckets({
4995
+ projectId,
4996
+ branchName: args.flags.branch,
4997
+ signal: ctx.signal
4998
+ });
4999
+ const result = {
5000
+ projectId,
5001
+ projectName,
5002
+ branchName: args.flags.branch ?? null,
5003
+ buckets
5004
+ };
5005
+ return ok(ctx.present({ data: result }, listPresentations$6(result)));
5205
5006
  }
5206
5007
  });
5207
5008
  //#endregion
@@ -5495,84 +5296,13 @@ async function resolveGitContext(ctx, explicitProject, commandName) {
5495
5296
  }
5496
5297
  //#endregion
5497
5298
  //#region src/commands/git/errors.ts
5498
- const GIT_CODE_MAP = {
5499
- USAGE_ERROR: "GIT.USAGE_ERROR",
5500
- REPO_PROVIDER_UNSUPPORTED: "GIT.REPO_PROVIDER_UNSUPPORTED",
5501
- REPO_ALREADY_CONNECTED: "GIT.REPO_ALREADY_CONNECTED",
5502
- REPO_INSTALLATION_REQUIRED: "GIT.REPO_INSTALLATION_REQUIRED",
5503
- REPO_NOT_ACCESSIBLE: "GIT.REPO_NOT_ACCESSIBLE",
5504
- REPO_NOT_CONNECTED: "GIT.REPO_NOT_CONNECTED",
5505
- REPO_CONNECTION_FAILED: "GIT.REPO_CONNECTION_FAILED"
5506
- };
5507
- /** The project-resolution codes the git commands can raise; they keep
5508
- * the project group's dotted codes and copy. */
5509
- const PROJECT_CODES$1 = new Set([
5510
- "PROJECT_NOT_FOUND",
5511
- "PROJECT_AMBIGUOUS",
5512
- "PROJECT_SETUP_REQUIRED",
5513
- "LOCAL_STATE_STALE",
5514
- "LOCAL_PROJECT_WORKSPACE_MISMATCH"
5515
- ]);
5516
- const STALE_INTERACTIVE_SIGN_IN$1 = /, or rerun the command in a TTY to sign in interactively\./g;
5517
- const TRACE_FLAG$1 = /--trace/g;
5518
- /** `--trace` is gone in this CLI; the log level replaces it. Interactive
5519
- * sign-in is gone too (R-S2b-2), so the legacy offer to rerun in a TTY
5520
- * describes something this CLI cannot do; `auth login` is the whole remedy. */
5521
- function portFixText$1(fix) {
5522
- return fix.replace(TRACE_FLAG$1, "--log-level verbose").replace(STALE_INTERACTIVE_SIGN_IN$1, ".");
5523
- }
5524
- /** The install-required and not-accessible errors put the raw install
5525
- * URL in their nextSteps beside real commands. A URL is not a command,
5526
- * so it becomes an `open-url` action. */
5527
- function nextStepAction(step) {
5528
- if (step.startsWith("https://") || step.startsWith("http://")) return {
5529
- kind: "open-url",
5530
- label: step,
5531
- url: step
5532
- };
5533
- const command = portCommandString(step);
5534
- return {
5535
- kind: "run-command",
5536
- label: command,
5537
- command
5538
- };
5539
- }
5540
- function nextActionsFor$1(error) {
5541
- return [...error.fix ? [{
5542
- kind: "user-choice",
5543
- label: portFixText$1(error.fix)
5544
- }] : [], ...error.nextSteps.map(nextStepAction)];
5545
- }
5546
- /** The legacy errors branch their fix text on whether a browser was
5547
- * opened. The engine's browser wait always shows the URL, so the
5548
- * opened branch is the one that describes what this CLI does. */
5549
- const BROWSER_OPENED = true;
5550
5299
  /**
5551
- * The install wait's two terminal outcomes. The legacy constructors own
5552
- * every copy string; the design drops `opened` from the meta they build
5553
- * (`browserWait` does not report it and the URL is always shown), so
5554
- * the structured error is assembled from their fields with the meta
5555
- * d3 §3.8 pins.
5300
+ * The install wait's two terminal outcomes: the workspace has an
5301
+ * inspectable installation that simply does not expose the repository,
5302
+ * or it has none at all.
5556
5303
  */
5557
5304
  function installWaitFailedError(repository, installUrl, inspectableInstallationCount) {
5558
- const legacy = inspectableInstallationCount > 0 ? repoNotAccessibleError(repository, installUrl, BROWSER_OPENED) : repoInstallationRequiredError(repository, installUrl, BROWSER_OPENED);
5559
- return new CliStructuredError(GIT_CODE_MAP[legacy.code], legacy.summary, {
5560
- why: legacy.why ?? void 0,
5561
- meta: {
5562
- repository: repository.fullName,
5563
- installUrl
5564
- },
5565
- nextActions: nextActionsFor$1(legacy)
5566
- });
5567
- }
5568
- function mapGitOperationError(error) {
5569
- if (!(error instanceof CliError)) return null;
5570
- if (PROJECT_CODES$1.has(error.code)) return mapProjectOperationError(error);
5571
- return new CliStructuredError(GIT_CODE_MAP[error.code] ?? `GIT.${error.code}`, error.summary, {
5572
- why: error.why ?? void 0,
5573
- meta: Object.keys(error.meta).length > 0 ? error.meta : void 0,
5574
- nextActions: nextActionsFor$1(error)
5575
- });
5305
+ return inspectableInstallationCount > 0 ? repoNotAccessibleError(repository, installUrl) : repoInstallationRequiredError(repository, installUrl);
5576
5306
  }
5577
5307
  //#endregion
5578
5308
  //#region src/commands/git/connect.ts
@@ -5671,43 +5401,50 @@ const gitConnectCommand = defineCommand({
5671
5401
  },
5672
5402
  needs: { credentials: true },
5673
5403
  handler: async (args, ctx) => {
5674
- try {
5675
- const { api, target } = await resolveGitContext(ctx, args.flags.project, "git connect");
5676
- const remoteUrl = args.positionals.gitUrl ?? await readGitOriginRemote(ctx.cwd, ctx.signal);
5677
- if (!remoteUrl) throw usageError("Repository connection requires a GitHub repository URL", "No git-url was provided and the local repo does not have an origin remote.", `Pass a GitHub repository URL, or add a GitHub origin remote and rerun ${CLI_NAME} git connect.`, [`${CLI_NAME} git connect git@github.com:prisma/prisma-cli.git`], "project");
5678
- const repository = parseGitHubRepositoryUrl(remoteUrl);
5679
- if (!repository) throw unsupportedRepositoryProviderError();
5680
- const existing = await readFirstSourceRepository(api, target.project.id, ctx.signal);
5681
- if (existing) {
5682
- const existingConnection = toRepositoryConnection(existing);
5683
- if (!repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) throw repoAlreadyConnectedError(existingConnection.repository.fullName);
5684
- const idempotent = {
5685
- ...target,
5686
- repositoryConnection: existingConnection
5687
- };
5688
- return ok(ctx.present({ data: idempotent }, connectPresentations(idempotent)));
5689
- }
5690
- const installed = await resolveInstalledRepository(ctx, api, target.workspace.id, repository);
5691
- const { data, error, response } = await api.POST("/v1/source-repositories", {
5692
- body: {
5693
- projectId: target.project.id,
5694
- provider: "github",
5695
- providerRepositoryId: installed.repository.id,
5696
- installationId: installed.installation.id
5697
- },
5698
- signal: ctx.signal
5404
+ const { api, target } = await resolveGitContext(ctx, args.flags.project, "git connect");
5405
+ const remoteUrl = args.positionals.gitUrl ?? await readGitOriginRemote(ctx.cwd, ctx.signal);
5406
+ if (!remoteUrl) {
5407
+ const example = `${CLI_NAME} git connect git@github.com:prisma/prisma-cli.git`;
5408
+ throw new CliStructuredError("GIT.USAGE_ERROR", "Repository connection requires a GitHub repository URL", {
5409
+ why: "No git-url was provided and the local repo does not have an origin remote.",
5410
+ nextActions: [{
5411
+ kind: "user-choice",
5412
+ label: `Pass a GitHub repository URL, or add a GitHub origin remote and rerun ${CLI_NAME} git connect.`
5413
+ }, {
5414
+ kind: "run-command",
5415
+ label: example,
5416
+ command: example
5417
+ }]
5699
5418
  });
5700
- if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
5701
- const result = {
5419
+ }
5420
+ const repository = parseGitHubRepositoryUrl(remoteUrl);
5421
+ if (!repository) throw unsupportedRepositoryProviderError();
5422
+ const existing = await readFirstSourceRepository(api, target.project.id, ctx.signal);
5423
+ if (existing) {
5424
+ const existingConnection = toRepositoryConnection(existing);
5425
+ if (!repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) throw repoAlreadyConnectedError(existingConnection.repository.fullName);
5426
+ const idempotent = {
5702
5427
  ...target,
5703
- repositoryConnection: toRepositoryConnection(data.data)
5428
+ repositoryConnection: existingConnection
5704
5429
  };
5705
- return ok(ctx.present({ data: result }, connectPresentations(result)));
5706
- } catch (error) {
5707
- const mapped = mapGitOperationError(error);
5708
- if (mapped) return notOk(mapped);
5709
- throw error;
5430
+ return ok(ctx.present({ data: idempotent }, connectPresentations(idempotent)));
5710
5431
  }
5432
+ const installed = await resolveInstalledRepository(ctx, api, target.workspace.id, repository);
5433
+ const { data, error, response } = await api.POST("/v1/source-repositories", {
5434
+ body: {
5435
+ projectId: target.project.id,
5436
+ provider: "github",
5437
+ providerRepositoryId: installed.repository.id,
5438
+ installationId: installed.installation.id
5439
+ },
5440
+ signal: ctx.signal
5441
+ });
5442
+ if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
5443
+ const result = {
5444
+ ...target,
5445
+ repositoryConnection: toRepositoryConnection(data.data)
5446
+ };
5447
+ return ok(ctx.present({ data: result }, connectPresentations(result)));
5711
5448
  }
5712
5449
  });
5713
5450
  //#endregion
@@ -5756,25 +5493,19 @@ const gitDisconnectCommand = defineCommand({
5756
5493
  },
5757
5494
  needs: { credentials: true },
5758
5495
  handler: async (args, ctx) => {
5759
- try {
5760
- const { api, target } = await resolveGitContext(ctx, args.flags.project, "git disconnect");
5761
- const existing = await readFirstSourceRepository(api, target.project.id, ctx.signal);
5762
- if (!existing) throw repoNotConnectedError();
5763
- const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
5764
- params: { path: { id: existing.id } },
5765
- signal: ctx.signal
5766
- });
5767
- if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
5768
- const result = {
5769
- ...target,
5770
- repositoryConnection: toRepositoryConnection(existing)
5771
- };
5772
- return ok(ctx.present({ data: result }, disconnectPresentations(result)));
5773
- } catch (error) {
5774
- const mapped = mapGitOperationError(error);
5775
- if (mapped) return notOk(mapped);
5776
- throw error;
5777
- }
5496
+ const { api, target } = await resolveGitContext(ctx, args.flags.project, "git disconnect");
5497
+ const existing = await readFirstSourceRepository(api, target.project.id, ctx.signal);
5498
+ if (!existing) throw repoNotConnectedError();
5499
+ const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
5500
+ params: { path: { id: existing.id } },
5501
+ signal: ctx.signal
5502
+ });
5503
+ if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
5504
+ const result = {
5505
+ ...target,
5506
+ repositoryConnection: toRepositoryConnection(existing)
5507
+ };
5508
+ return ok(ctx.present({ data: result }, disconnectPresentations(result)));
5778
5509
  }
5779
5510
  });
5780
5511
  //#endregion
@@ -6581,41 +6312,51 @@ const initCommand = defineCommand({
6581
6312
  //#region src/controllers/database.ts
6582
6313
  const USAGE_DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
6583
6314
  const USAGE_DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
6584
- function parseUsageDate(value, flagName, dayBoundary, formatCommand) {
6315
+ const LIST_DATABASES_COMMAND = `${CLI_NAME} postgres list`;
6316
+ /** The corrected `postgres usage` form both period errors point at. */
6317
+ const USAGE_PERIOD_EXAMPLE_COMMAND = `${CLI_NAME} postgres usage <database> --from 2026-06-01 --to 2026-06-30`;
6318
+ function userChoice$2(label) {
6319
+ return {
6320
+ kind: "user-choice",
6321
+ label
6322
+ };
6323
+ }
6324
+ function runCommand$2(command) {
6325
+ return {
6326
+ kind: "run-command",
6327
+ label: command,
6328
+ command
6329
+ };
6330
+ }
6331
+ function parseUsageDate(value, flagName, dayBoundary) {
6585
6332
  if (value === void 0) return;
6586
6333
  const trimmed = value.trim();
6587
6334
  if (USAGE_DATE_ONLY_PATTERN.test(trimmed) && isValidCalendarDate(trimmed)) return dayBoundary === "start" ? `${trimmed}T00:00:00.000Z` : `${trimmed}T23:59:59.999Z`;
6588
6335
  if (USAGE_DATETIME_PATTERN.test(trimmed) && !Number.isNaN(Date.parse(trimmed)) && isValidCalendarDate(trimmed.slice(0, 10))) return trimmed;
6589
- throw usageError("Invalid usage period", `${flagName} must be an ISO date such as 2026-06-01 or an ISO datetime such as 2026-06-01T12:00:00Z.`, `Pass an ISO date or datetime to ${flagName}.`, [formatCommand([
6590
- "database",
6591
- "usage",
6592
- "<database>",
6593
- "--from",
6594
- "2026-06-01",
6595
- "--to",
6596
- "2026-06-30"
6597
- ])], "database");
6336
+ throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Invalid usage period", {
6337
+ why: `${flagName} must be an ISO date such as 2026-06-01 or an ISO datetime such as 2026-06-01T12:00:00Z.`,
6338
+ nextActions: [userChoice$2(`Pass an ISO date or datetime to ${flagName}.`), runCommand$2(USAGE_PERIOD_EXAMPLE_COMMAND)]
6339
+ });
6598
6340
  }
6599
6341
  function isValidCalendarDate(datePart) {
6600
6342
  const timestamp = Date.parse(`${datePart}T00:00:00.000Z`);
6601
6343
  return !Number.isNaN(timestamp) && new Date(timestamp).toISOString().startsWith(datePart);
6602
6344
  }
6603
- function parseBackupLimit(value, formatCommand) {
6345
+ function parseBackupLimit(value) {
6604
6346
  if (value === void 0) return;
6605
6347
  const limit = Number(value.trim());
6606
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw usageError("Invalid backup limit", "--limit must be an integer between 1 and 100.", "Pass a --limit between 1 and 100.", [formatCommand([
6607
- "database",
6608
- "backup",
6609
- "list",
6610
- "<database>",
6611
- "--limit",
6612
- "50"
6613
- ])], "database");
6348
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Invalid backup limit", {
6349
+ why: "--limit must be an integer between 1 and 100.",
6350
+ nextActions: [userChoice$2("Pass a --limit between 1 and 100."), runCommand$2(`${CLI_NAME} postgres backup list <database> --limit 50`)]
6351
+ });
6614
6352
  return limit;
6615
6353
  }
6616
6354
  async function resolveDatabase(provider, target, databaseRef, branchName, signal) {
6617
6355
  const ref = databaseRef.trim();
6618
- if (!ref) throw usageError("Database id or name required", "This command needs a database id or name.", "Pass a database id or name.", ["prisma database list"], "database");
6356
+ if (!ref) throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Database id or name required", {
6357
+ why: "This command needs a database id or name.",
6358
+ nextActions: [userChoice$2("Pass a database id or name."), runCommand$2(LIST_DATABASES_COMMAND)]
6359
+ });
6619
6360
  const databases = await provider.listDatabases({
6620
6361
  projectId: target.project.id,
6621
6362
  branchName,
@@ -6634,14 +6375,9 @@ async function resolveDatabase(provider, target, databaseRef, branchName, signal
6634
6375
  return ensureProjectId(shown, target.project.id);
6635
6376
  }
6636
6377
  function databaseRemovedDuringResolutionError(database, projectName) {
6637
- return new CliError({
6638
- code: "DATABASE_NOT_FOUND",
6639
- domain: "database",
6640
- summary: "Database not found",
6378
+ return new CliStructuredError("POSTGRES.NOT_FOUND", "Database not found", {
6641
6379
  why: `"${database.name}" (${database.id}) was listed for project "${projectName}", but reading it returned 404. It was most likely removed while this command was running.`,
6642
- fix: "Re-run the command, or list the project's databases to see what is there now.",
6643
- exitCode: 1,
6644
- nextSteps: ["prisma database list"]
6380
+ nextActions: [userChoice$2("Re-run the command, or list the project's databases to see what is there now."), runCommand$2(LIST_DATABASES_COMMAND)]
6645
6381
  });
6646
6382
  }
6647
6383
  function ensureProjectId(database, projectId) {
@@ -6662,30 +6398,20 @@ function defaultConnectionName() {
6662
6398
  return `cli-${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 17)}-${randomBytes(2).toString("hex")}`;
6663
6399
  }
6664
6400
  function databaseNotFoundError(databaseRef, projectName, branchName) {
6665
- return new CliError({
6666
- code: "DATABASE_NOT_FOUND",
6667
- domain: "database",
6668
- summary: "Database not found",
6401
+ return new CliStructuredError("POSTGRES.NOT_FOUND", "Database not found", {
6669
6402
  why: `No database matched "${databaseRef}"${projectName ? ` in project "${projectName}"${branchName ? ` on branch "${branchName}"` : ""}` : ""}.`,
6670
- fix: "Pass a database id or name from prisma database list.",
6671
- exitCode: 1,
6672
- nextSteps: ["prisma database list"]
6403
+ nextActions: [userChoice$2(`Pass a database id or name from ${LIST_DATABASES_COMMAND}.`), runCommand$2(LIST_DATABASES_COMMAND)]
6673
6404
  });
6674
6405
  }
6675
6406
  function databaseAmbiguousError(databaseRef, matches, branchName) {
6676
- return new CliError({
6677
- code: "DATABASE_AMBIGUOUS",
6678
- domain: "database",
6679
- summary: "Database resolution is ambiguous",
6407
+ return new CliStructuredError("POSTGRES.AMBIGUOUS", "Database resolution is ambiguous", {
6680
6408
  why: branchName ? `Multiple databases matched "${databaseRef}" on branch "${branchName}".` : `Multiple databases matched "${databaseRef}".`,
6681
- fix: "Pass the database id, or pass --branch <git-name> to narrow the match.",
6682
- exitCode: 1,
6683
- nextSteps: ["prisma database list"],
6684
6409
  meta: { matches: matches.map((database) => ({
6685
6410
  id: database.id,
6686
6411
  name: database.name,
6687
6412
  branchName: database.branchName
6688
- })) }
6413
+ })) },
6414
+ nextActions: [userChoice$2("Pass the database id, or pass --branch <git-name> to narrow the match."), runCommand$2(LIST_DATABASES_COMMAND)]
6689
6415
  });
6690
6416
  }
6691
6417
  //#endregion
@@ -6752,8 +6478,21 @@ function serializeDatabaseBackupList(result) {
6752
6478
  //#endregion
6753
6479
  //#region src/lib/database/provider.ts
6754
6480
  const SUBSCRIPTION_LOOKUP_TIMEOUT_MS = 3e3;
6481
+ const VERBOSE_LOG_FIX = "Re-run with --log-level verbose for the underlying API response details.";
6482
+ function userChoice$1(label) {
6483
+ return {
6484
+ kind: "user-choice",
6485
+ label
6486
+ };
6487
+ }
6488
+ function runCommand$1(command) {
6489
+ return {
6490
+ kind: "run-command",
6491
+ label: command,
6492
+ command
6493
+ };
6494
+ }
6755
6495
  function createManagementDatabaseProvider(client, options) {
6756
- const formatCommand = options?.formatCommand ?? ((args) => formatPrismaCliCommand(args));
6757
6496
  const toDatabaseApiError = (summary, response, error, signal) => databaseApiError({
6758
6497
  client,
6759
6498
  workspaceId: options?.workspaceId,
@@ -6873,8 +6612,8 @@ function createManagementDatabaseProvider(client, options) {
6873
6612
  } },
6874
6613
  signal: options.signal
6875
6614
  });
6876
- if (result.response?.status === 409 && !isPlanLimitApiError(result.error)) throw restoreConflictError(options.targetDatabaseId, result.error, formatCommand);
6877
- if (result.response?.status === 404 && !isPlanLimitApiError(result.error)) throw restoreBackupNotFoundError(options, result.error, formatCommand);
6615
+ if (result.response?.status === 409 && !isPlanLimitApiError(result.error)) throw restoreConflictError(options.targetDatabaseId, result.error);
6616
+ if (result.response?.status === 404 && !isPlanLimitApiError(result.error)) throw restoreBackupNotFoundError(options, result.error);
6878
6617
  if (result.error || !result.data) throw await toDatabaseApiError("Failed to restore database", result.response, result.error, options.signal);
6879
6618
  return normalizeDatabase(result.data.data, options.projectId);
6880
6619
  },
@@ -6911,14 +6650,9 @@ function normalizeConnection(connection, fallbackDatabaseId) {
6911
6650
  }
6912
6651
  function normalizeCreatedDatabase(database, fallbackProjectId) {
6913
6652
  const rawConnection = database.connections?.[0];
6914
- if (!rawConnection) throw new CliError({
6915
- code: "DATABASE_CONNECTION_MISSING",
6916
- domain: "database",
6917
- summary: "Created database did not return a connection string",
6653
+ if (!rawConnection) throw new CliStructuredError("POSTGRES.CONNECTION_MISSING", "Created database did not return a connection string", {
6918
6654
  why: "The Management API created the database but did not include the one-time connection payload.",
6919
- fix: "Create a connection explicitly with prisma database connection create <database>.",
6920
- exitCode: 1,
6921
- nextSteps: [`prisma database connection create ${database.id}`]
6655
+ nextActions: [userChoice$1(`Create a connection explicitly with ${CLI_NAME} postgres connection create <database>.`), runCommand$1(`${CLI_NAME} postgres connection create ${database.id}`)]
6922
6656
  });
6923
6657
  return {
6924
6658
  database: normalizeDatabase(database, fallbackProjectId),
@@ -6927,14 +6661,9 @@ function normalizeCreatedDatabase(database, fallbackProjectId) {
6927
6661
  }
6928
6662
  function normalizeCreatedConnection(connection, fallbackDatabaseId) {
6929
6663
  const connectionString = extractConnectionString(connection);
6930
- if (!connectionString) throw new CliError({
6931
- code: "DATABASE_CONNECTION_STRING_MISSING",
6932
- domain: "database",
6933
- summary: "Created connection did not return a connection string",
6664
+ if (!connectionString) throw new CliStructuredError("POSTGRES.CONNECTION_STRING_MISSING", "Created connection did not return a connection string", {
6934
6665
  why: "Database connection strings are one-time-view secrets, but the Management API did not include one in this create response.",
6935
- fix: "Create another database connection and store the returned URL immediately.",
6936
- exitCode: 1,
6937
- nextSteps: [`prisma database connection create ${fallbackDatabaseId}`]
6666
+ nextActions: [userChoice$1("Create another database connection and store the returned URL immediately."), runCommand$1(`${CLI_NAME} postgres connection create ${fallbackDatabaseId}`)]
6938
6667
  });
6939
6668
  return {
6940
6669
  connection: normalizeConnection(connection, fallbackDatabaseId),
@@ -6948,14 +6677,9 @@ function normalizeRegion(database) {
6948
6677
  function requireDatabaseProjectId(database, fallbackProjectId) {
6949
6678
  const projectId = database.projectId ?? fallbackProjectId;
6950
6679
  if (projectId) return projectId;
6951
- throw new CliError({
6952
- code: "DATABASE_API_ERROR",
6953
- domain: "database",
6954
- summary: "Database response did not include a project id",
6680
+ throw new CliStructuredError("POSTGRES.API_ERROR", "Database response did not include a project id", {
6955
6681
  why: "The Management API returned database metadata without project context.",
6956
- fix: "Re-run with --trace for the underlying API response details.",
6957
- exitCode: 1,
6958
- nextSteps: []
6682
+ nextActions: [userChoice$1(VERBOSE_LOG_FIX)]
6959
6683
  });
6960
6684
  }
6961
6685
  function extractConnectionString(connection) {
@@ -7000,14 +6724,9 @@ function normalizeBackupList(body) {
7000
6724
  }
7001
6725
  function normalizeRotatedConnection(connection) {
7002
6726
  const connectionString = extractConnectionString(connection);
7003
- if (!connectionString) throw new CliError({
7004
- code: "DATABASE_CONNECTION_STRING_MISSING",
7005
- domain: "database",
7006
- summary: "Rotated connection did not return a connection string",
6727
+ if (!connectionString) throw new CliStructuredError("POSTGRES.CONNECTION_STRING_MISSING", "Rotated connection did not return a connection string", {
7007
6728
  why: "Rotated connection strings are one-time-view secrets, but the Management API did not include one in this rotate response.",
7008
- fix: "Re-run the rotation, or create a replacement connection and store the returned URL immediately.",
7009
- exitCode: 1,
7010
- nextSteps: []
6729
+ nextActions: [userChoice$1("Re-run the rotation, or create a replacement connection and store the returned URL immediately.")]
7011
6730
  });
7012
6731
  const database = connection.database?.id && connection.database?.name ? {
7013
6732
  id: connection.database.id,
@@ -7020,74 +6739,68 @@ function normalizeRotatedConnection(connection) {
7020
6739
  };
7021
6740
  }
7022
6741
  function backupsUnsupportedError(databaseId, error) {
7023
- return new CliError({
7024
- code: "DATABASE_BACKUPS_UNSUPPORTED",
7025
- domain: "database",
7026
- summary: "Backups are not available for this database",
6742
+ return new CliStructuredError("POSTGRES.BACKUPS_UNSUPPORTED", "Backups are not available for this database", {
7027
6743
  why: error?.error?.message ?? `The platform does not manage backups for database "${databaseId}", for example because it is a remote/BYO database.`,
7028
- fix: "Use your own backup tooling for externally managed databases.",
7029
- exitCode: 1,
7030
- nextSteps: []
6744
+ nextActions: [userChoice$1("Use your own backup tooling for externally managed databases.")]
7031
6745
  });
7032
6746
  }
7033
- function restoreBackupNotFoundError(options, error, formatCommand) {
7034
- const listCommand = formatCommand([
7035
- "database",
7036
- "backup",
7037
- "list",
7038
- options.sourceDatabaseId
7039
- ]);
7040
- return new CliError({
7041
- code: "DATABASE_BACKUP_NOT_FOUND",
7042
- domain: "database",
7043
- summary: "Database backup not found",
6747
+ function restoreBackupNotFoundError(options, error) {
6748
+ const listCommand = `${CLI_NAME} postgres backup list ${options.sourceDatabaseId}`;
6749
+ return new CliStructuredError("POSTGRES.BACKUP_NOT_FOUND", "Database backup not found", {
7044
6750
  why: error?.error?.message ?? `No backup matched "${options.backupId}" for database "${options.sourceDatabaseId}".`,
7045
- fix: `Pass a backup id from ${listCommand}.`,
7046
- exitCode: 1,
7047
- nextSteps: [listCommand]
6751
+ nextActions: [userChoice$1(`Pass a backup id from ${listCommand}.`), runCommand$1(listCommand)]
7048
6752
  });
7049
6753
  }
7050
- function restoreConflictError(targetDatabaseId, error, formatCommand) {
7051
- return new CliError({
7052
- code: "DATABASE_RESTORE_CONFLICT",
7053
- domain: "database",
7054
- summary: "Database cannot be restored right now",
6754
+ function restoreConflictError(targetDatabaseId, error) {
6755
+ return new CliStructuredError("POSTGRES.RESTORE_CONFLICT", "Database cannot be restored right now", {
7055
6756
  why: error?.error?.message ?? `Database "${targetDatabaseId}" is provisioning or already recovering.`,
7056
- fix: "Wait for the database to become ready, then retry the restore.",
7057
- exitCode: 1,
7058
- nextSteps: [formatCommand([
7059
- "database",
7060
- "show",
7061
- targetDatabaseId
7062
- ])]
6757
+ nextActions: [userChoice$1("Wait for the database to become ready, then retry the restore."), runCommand$1(`${CLI_NAME} postgres show ${targetDatabaseId}`)]
7063
6758
  });
7064
6759
  }
6760
+ /** A 401 or 403 is the API refusing the caller, not a database problem. */
6761
+ function isRejectedCaller(status) {
6762
+ return status === 401 || status === 403;
6763
+ }
6764
+ function apiErrorWhy(status, message) {
6765
+ if (!isRejectedCaller(status)) return message ?? `The Management API returned status ${status || "unknown"}.`;
6766
+ const rejection = `The Management API rejected the request as ${status === 401 ? "unauthorized" : "forbidden"}.`;
6767
+ return message ? `${rejection} ${message}` : rejection;
6768
+ }
6769
+ function apiErrorMeta(status, apiCode) {
6770
+ if (!status && apiCode === void 0) return;
6771
+ return {
6772
+ ...status ? { status } : {},
6773
+ ...apiCode === void 0 ? {} : { apiCode }
6774
+ };
6775
+ }
6776
+ function apiErrorActions(status, hint) {
6777
+ if (!isRejectedCaller(status)) return [userChoice$1(hint ?? VERBOSE_LOG_FIX)];
6778
+ return [userChoice$1(hint ?? `Sign in again with prisma auth login, then retry the command.`), runCommand$1(`${CLI_NAME} auth login`)];
6779
+ }
6780
+ /**
6781
+ * Every database Management API failure that is not a plan limit lands
6782
+ * on the one registered code. The response's own error code is data, not
6783
+ * an identity: it travels in `meta.apiCode` beside `meta.status` so a
6784
+ * consumer can still branch on it without the CLI minting a code it
6785
+ * never registered.
6786
+ */
7065
6787
  async function databaseApiError(options) {
7066
6788
  if (isPlanLimitApiError(options.error)) return planLimitReachedError(options);
7067
6789
  const status = options.response?.status ?? 0;
7068
- return new CliError({
7069
- code: options.error?.error?.code ?? "DATABASE_API_ERROR",
7070
- domain: "database",
7071
- summary: options.summary,
7072
- why: options.error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
7073
- fix: options.error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
7074
- exitCode: 1,
7075
- nextSteps: []
6790
+ const meta = apiErrorMeta(status, options.error?.error?.code);
6791
+ return new CliStructuredError("POSTGRES.API_ERROR", options.summary, {
6792
+ why: apiErrorWhy(status, options.error?.error?.message),
6793
+ ...meta === void 0 ? {} : { meta },
6794
+ nextActions: apiErrorActions(status, options.error?.error?.hint)
7076
6795
  });
7077
6796
  }
7078
6797
  async function planLimitReachedError(options) {
7079
6798
  const subscription = options.workspaceId ? await readWorkspaceSubscription(options.client, options.workspaceId, options.signal) : null;
7080
- const workspaceLine = options.workspaceId ? `Workspace: ${options.workspaceId}` : "Workspace: unavailable";
7081
6799
  const planName = subscription?.planName || null;
7082
6800
  const usageBlocked = subscription?.usageBlocked ?? null;
7083
6801
  const upgradeUrl = subscription?.upgradeUrl || null;
7084
- const recoveryLines = [...planName ? [`Current plan: ${planName}`] : [], upgradeUrl ? `Upgrade: ${upgradeUrl}` : "Upgrade: Open Prisma Console and upgrade the affected workspace plan."];
7085
- return new CliError({
7086
- code: "PLAN_LIMIT_REACHED",
7087
- domain: "database",
7088
- summary: "Workspace plan limit reached",
6802
+ return new CliStructuredError("POSTGRES.PLAN_LIMIT_REACHED", "Workspace plan limit reached", {
7089
6803
  why: "Database operations are blocked because this workspace has used the operations included in its plan. This is a workspace plan limit, not a Prisma outage.",
7090
- fix: upgradeUrl ? `Upgrade the workspace plan at ${upgradeUrl}.` : "Open Prisma Console and upgrade the affected workspace plan.",
7091
6804
  meta: {
7092
6805
  workspaceId: options.workspaceId ?? null,
7093
6806
  blockedFeature: null,
@@ -7095,16 +6808,11 @@ async function planLimitReachedError(options) {
7095
6808
  usageBlocked,
7096
6809
  upgradeUrl
7097
6810
  },
7098
- exitCode: 1,
7099
- nextSteps: [],
7100
- humanLines: [
7101
- "Workspace plan limit reached [PLAN_LIMIT_REACHED]",
7102
- "",
7103
- "Database operations are blocked because this workspace has used the operations included in its plan. This is a workspace plan limit, not a Prisma outage.",
7104
- "",
7105
- workspaceLine,
7106
- ...recoveryLines
7107
- ]
6811
+ nextActions: [{
6812
+ kind: "user-choice",
6813
+ label: "Upgrade the workspace plan",
6814
+ reason: upgradeUrl ? `Upgrade at ${upgradeUrl}${planName ? ` (current plan: ${planName})` : ""}.` : "Open Prisma Console and upgrade the affected workspace plan."
6815
+ }]
7108
6816
  });
7109
6817
  }
7110
6818
  function isPlanLimitApiError(error) {
@@ -7145,10 +6853,6 @@ const databasePositional = positional.string({
7145
6853
  brief: "Database id or name",
7146
6854
  placeholder: "database"
7147
6855
  });
7148
- /** The legacy helpers build their nextSteps through a command
7149
- * formatter. This CLI phrases every command string as `${CLI_NAME} …`; the
7150
- * error mapper rewrites the `database` group name to `postgres`. */
7151
- const legacyCommandFormatter = (args) => [CLI_NAME, ...args].join(" ");
7152
6856
  async function resolvePostgresContext(ctx, flags, commandName) {
7153
6857
  const workspace = await resolveActiveWorkspace(ctx);
7154
6858
  const target = await resolvePinnedProject(ctx, workspace, flags.project, commandName);
@@ -7167,99 +6871,6 @@ async function resolvePostgresProviderOnly(ctx) {
7167
6871
  return createManagementDatabaseProvider(ctx.api, { ...workspaceId === void 0 ? {} : { workspaceId } });
7168
6872
  }
7169
6873
  //#endregion
7170
- //#region src/commands/postgres/errors.ts
7171
- const POSTGRES_CODE_MAP = {
7172
- USAGE_ERROR: "POSTGRES.USAGE_ERROR",
7173
- DATABASE_NOT_FOUND: "POSTGRES.NOT_FOUND",
7174
- DATABASE_AMBIGUOUS: "POSTGRES.AMBIGUOUS",
7175
- DATABASE_CONNECTION_MISSING: "POSTGRES.CONNECTION_MISSING",
7176
- DATABASE_CONNECTION_STRING_MISSING: "POSTGRES.CONNECTION_STRING_MISSING",
7177
- DATABASE_BACKUPS_UNSUPPORTED: "POSTGRES.BACKUPS_UNSUPPORTED",
7178
- DATABASE_RESTORE_CONFLICT: "POSTGRES.RESTORE_CONFLICT",
7179
- DATABASE_BACKUP_NOT_FOUND: "POSTGRES.BACKUP_NOT_FOUND",
7180
- DATABASE_API_ERROR: "POSTGRES.API_ERROR"
7181
- };
7182
- /** The project-resolution codes this group can raise; they keep the
7183
- * project group's dotted codes and copy. */
7184
- const PROJECT_CODES = new Set([
7185
- "PROJECT_NOT_FOUND",
7186
- "PROJECT_AMBIGUOUS",
7187
- "PROJECT_SETUP_REQUIRED",
7188
- "LOCAL_STATE_STALE",
7189
- "LOCAL_PROJECT_WORKSPACE_MISMATCH"
7190
- ]);
7191
- const PACKAGE_RUNNER = /[\w./@-]+(?: [\w.-]+)? @prisma\/cli@\S+ /g;
7192
- const COMMENT_PREFIX = /^#\s*/;
7193
- const LEGACY_GROUP = new RegExp(`${CLI_NAME} database `, "g");
7194
- /** §0 rename: wherever a legacy command reference appears — a
7195
- * nextSteps string or prose inside `fix` — the package-runner prefix
7196
- * becomes `${CLI_NAME}` and the `database` group becomes `postgres`.
7197
- * The resource noun "database" in prose is left alone. */
7198
- function portCommandReferences(text) {
7199
- return text.replace(PACKAGE_RUNNER, `${CLI_NAME} `).replace(LEGACY_GROUP, `${CLI_NAME} postgres `);
7200
- }
7201
- function portPostgresCommand(command) {
7202
- const named = portCommandReferences(command);
7203
- return named.startsWith(`prisma `) ? named : `${CLI_NAME} ${named}`;
7204
- }
7205
- const STALE_INTERACTIVE_SIGN_IN = /, or rerun the command in a TTY to sign in interactively\./g;
7206
- const TRACE_FLAG = /--trace/g;
7207
- /** `--trace` is gone in this CLI; the log level replaces it. Interactive
7208
- * sign-in is gone too (R-S2b-2), so the legacy offer to rerun in a TTY
7209
- * describes something this CLI cannot do; `auth login` is the whole remedy. */
7210
- function portFixText(fix) {
7211
- return portCommandReferences(fix).replace(TRACE_FLAG, "--log-level verbose").replace(STALE_INTERACTIVE_SIGN_IN, ".");
7212
- }
7213
- function runCommandActions(nextSteps) {
7214
- const actions = [];
7215
- let reason;
7216
- for (const step of nextSteps) {
7217
- if (step.startsWith("#")) {
7218
- reason = step.replace(COMMENT_PREFIX, "");
7219
- continue;
7220
- }
7221
- const command = portPostgresCommand(step);
7222
- actions.push({
7223
- kind: "run-command",
7224
- label: command,
7225
- command,
7226
- ...reason === void 0 ? {} : { reason }
7227
- });
7228
- reason = void 0;
7229
- }
7230
- return actions;
7231
- }
7232
- /** PR #127's plan-limit error: the legacy full-page `humanLines`
7233
- * rendering does not port, so the recovery guidance becomes the one
7234
- * nextAction beside the verbatim why and meta. */
7235
- function planLimitError(error) {
7236
- const upgradeUrl = error.meta.upgradeUrl;
7237
- const planName = error.meta.planName;
7238
- const reason = typeof upgradeUrl === "string" && upgradeUrl ? `Upgrade at ${upgradeUrl}${typeof planName === "string" && planName ? ` (current plan: ${planName})` : ""}.` : "Open Prisma Console and upgrade the affected workspace plan.";
7239
- return new CliStructuredError("POSTGRES.PLAN_LIMIT_REACHED", error.summary, {
7240
- why: error.why ?? void 0,
7241
- meta: error.meta,
7242
- nextActions: [{
7243
- kind: "user-choice",
7244
- label: "Upgrade the workspace plan",
7245
- reason
7246
- }]
7247
- });
7248
- }
7249
- function mapPostgresOperationError(error) {
7250
- if (!(error instanceof CliError)) return null;
7251
- if (error.code === "PLAN_LIMIT_REACHED") return planLimitError(error);
7252
- if (PROJECT_CODES.has(error.code)) return mapProjectOperationError(error);
7253
- return new CliStructuredError(POSTGRES_CODE_MAP[error.code] ?? `POSTGRES.${error.code}`, error.summary, {
7254
- why: error.why ?? void 0,
7255
- meta: Object.keys(error.meta).length > 0 ? error.meta : void 0,
7256
- nextActions: [...error.fix ? [{
7257
- kind: "user-choice",
7258
- label: portFixText(error.fix)
7259
- }] : [], ...runCommandActions(error.nextSteps)]
7260
- });
7261
- }
7262
- //#endregion
7263
6874
  //#region src/commands/postgres/presentation.ts
7264
6875
  /** Legacy `formatDatabaseTarget`. */
7265
6876
  function postgresTargetLabel(projectName, branchName) {
@@ -7418,28 +7029,22 @@ const postgresBackupListCommand = defineCommand({
7418
7029
  },
7419
7030
  needs: { credentials: true },
7420
7031
  handler: async (args, ctx) => {
7421
- try {
7422
- const limit = parseBackupLimit(args.flags.limit, legacyCommandFormatter);
7423
- const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres backup list");
7424
- const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7425
- const backups = await provider.listBackups(database.id, {
7426
- limit,
7427
- signal: ctx.signal
7428
- });
7429
- const result = {
7430
- projectId,
7431
- projectName,
7432
- database,
7433
- backups: backups.backups,
7434
- retentionDays: backups.retentionDays,
7435
- hasMore: backups.hasMore
7436
- };
7437
- return ok(ctx.present({ data: result }, backupListPresentations(result)));
7438
- } catch (error) {
7439
- const mapped = mapPostgresOperationError(error);
7440
- if (mapped) return notOk(mapped);
7441
- throw error;
7442
- }
7032
+ const limit = parseBackupLimit(args.flags.limit);
7033
+ const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres backup list");
7034
+ const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7035
+ const backups = await provider.listBackups(database.id, {
7036
+ limit,
7037
+ signal: ctx.signal
7038
+ });
7039
+ const result = {
7040
+ projectId,
7041
+ projectName,
7042
+ database,
7043
+ backups: backups.backups,
7044
+ retentionDays: backups.retentionDays,
7045
+ hasMore: backups.hasMore
7046
+ };
7047
+ return ok(ctx.present({ data: result }, backupListPresentations(result)));
7443
7048
  }
7444
7049
  });
7445
7050
  //#endregion
@@ -7519,44 +7124,41 @@ const postgresBackupRestoreCommand = defineCommand({
7519
7124
  },
7520
7125
  needs: { credentials: true },
7521
7126
  handler: async (args, ctx) => {
7522
- try {
7523
- const backupId = args.flags.backup?.trim();
7524
- if (!backupId) throw usageError("Backup id required", "Database restore needs the backup to restore from.", `Pass --backup <backup-id> from ${legacyCommandFormatter([
7525
- "postgres",
7526
- "backup",
7527
- "list",
7528
- "<database>"
7529
- ])}.`, [legacyCommandFormatter([
7530
- "postgres",
7531
- "backup",
7532
- "list",
7533
- "<database>"
7534
- ])], "database");
7535
- const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres backup restore");
7536
- const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7537
- const sourceDatabase = args.flags.sourceDatabase ? await resolveDatabase(provider, target, args.flags.sourceDatabase, args.flags.branch, ctx.signal) : database;
7538
- await ctx.prompt.consent(CONSENT_QUESTION$5, { token: database.id });
7539
- const result = {
7540
- projectId,
7541
- projectName,
7542
- database: await provider.restoreDatabase({
7543
- targetDatabaseId: database.id,
7544
- sourceDatabaseId: sourceDatabase.id,
7545
- backupId,
7546
- projectId,
7547
- signal: ctx.signal
7548
- }),
7549
- source: {
7550
- databaseId: sourceDatabase.id,
7551
- backupId
7552
- }
7553
- };
7554
- return ok(ctx.present({ data: result }, restorePresentations(result, sourceDatabase.id === database.id ? null : sourceDatabase.id, database.id)));
7555
- } catch (error) {
7556
- const mapped = mapPostgresOperationError(error);
7557
- if (mapped) return notOk(mapped);
7558
- throw error;
7127
+ const backupId = args.flags.backup?.trim();
7128
+ if (!backupId) {
7129
+ const listCommand = `${CLI_NAME} postgres backup list <database>`;
7130
+ throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Backup id required", {
7131
+ why: "Database restore needs the backup to restore from.",
7132
+ nextActions: [{
7133
+ kind: "user-choice",
7134
+ label: `Pass --backup <backup-id> from ${listCommand}.`
7135
+ }, {
7136
+ kind: "run-command",
7137
+ label: listCommand,
7138
+ command: listCommand
7139
+ }]
7140
+ });
7559
7141
  }
7142
+ const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres backup restore");
7143
+ const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7144
+ const sourceDatabase = args.flags.sourceDatabase ? await resolveDatabase(provider, target, args.flags.sourceDatabase, args.flags.branch, ctx.signal) : database;
7145
+ await ctx.prompt.consent(CONSENT_QUESTION$5, { token: database.id });
7146
+ const result = {
7147
+ projectId,
7148
+ projectName,
7149
+ database: await provider.restoreDatabase({
7150
+ targetDatabaseId: database.id,
7151
+ sourceDatabaseId: sourceDatabase.id,
7152
+ backupId,
7153
+ projectId,
7154
+ signal: ctx.signal
7155
+ }),
7156
+ source: {
7157
+ databaseId: sourceDatabase.id,
7158
+ backupId
7159
+ }
7160
+ };
7161
+ return ok(ctx.present({ data: result }, restorePresentations(result, sourceDatabase.id === database.id ? null : sourceDatabase.id, database.id)));
7560
7162
  }
7561
7163
  });
7562
7164
  //#endregion
@@ -7580,32 +7182,26 @@ const postgresConnectionCreateCommand = defineCommand({
7580
7182
  },
7581
7183
  needs: { credentials: true },
7582
7184
  handler: async (args, ctx) => {
7583
- try {
7584
- const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres connection create");
7585
- const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7586
- const created = await provider.createConnection({
7587
- databaseId: database.id,
7588
- name: args.flags.name?.trim() || defaultConnectionName(),
7589
- signal: ctx.signal
7590
- });
7591
- const result = {
7592
- projectId,
7593
- projectName,
7594
- database,
7595
- connection: created.connection,
7596
- connectionString: created.connectionString
7597
- };
7598
- return ok(ctx.present({ data: result }, {
7599
- human: () => secretBlocks(`Added a connection to "${database.name}" in ${postgresTargetLabel(projectName, database.branchName)}.`, result.connectionString),
7600
- stdout: () => [result.connectionString],
7601
- json: () => result,
7602
- next: () => []
7603
- }));
7604
- } catch (error) {
7605
- const mapped = mapPostgresOperationError(error);
7606
- if (mapped) return notOk(mapped);
7607
- throw error;
7608
- }
7185
+ const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres connection create");
7186
+ const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7187
+ const created = await provider.createConnection({
7188
+ databaseId: database.id,
7189
+ name: args.flags.name?.trim() || defaultConnectionName(),
7190
+ signal: ctx.signal
7191
+ });
7192
+ const result = {
7193
+ projectId,
7194
+ projectName,
7195
+ database,
7196
+ connection: created.connection,
7197
+ connectionString: created.connectionString
7198
+ };
7199
+ return ok(ctx.present({ data: result }, {
7200
+ human: () => secretBlocks(`Added a connection to "${database.name}" in ${postgresTargetLabel(projectName, database.branchName)}.`, result.connectionString),
7201
+ stdout: () => [result.connectionString],
7202
+ json: () => result,
7203
+ next: () => []
7204
+ }));
7609
7205
  }
7610
7206
  });
7611
7207
  //#endregion
@@ -7623,40 +7219,47 @@ const postgresConnectionDeleteCommand = defineCommand({
7623
7219
  },
7624
7220
  needs: { credentials: true },
7625
7221
  handler: async (args, ctx) => {
7626
- try {
7627
- const connectionId = args.positionals.connection.trim();
7628
- if (!connectionId) throw usageError("Connection id required", "Database connection deletion needs a connection id.", "Pass the connection id to delete.", [`${CLI_NAME} postgres connection delete <connection-id> --confirm <connection-id>`], "database");
7629
- await ctx.prompt.consent(CONSENT_QUESTION$4, { token: connectionId });
7630
- await (await resolvePostgresProviderOnly(ctx)).removeConnection(connectionId, { signal: ctx.signal });
7631
- const result = { connection: { id: connectionId } };
7632
- return ok(ctx.present({ data: result }, {
7633
- human: () => [
7634
- {
7635
- kind: "summary",
7636
- status: "ok",
7637
- text: "Deleting database connection."
7638
- },
7639
- {
7640
- kind: "fields",
7641
- rows: [{
7642
- label: "connection",
7643
- value: connectionId
7644
- }]
7645
- },
7646
- {
7647
- kind: "list",
7648
- items: ["The connection metadata was deleted. Existing one-time secrets were not shown."]
7649
- }
7650
- ],
7651
- stdout: () => [],
7652
- json: () => ({ connection: result.connection }),
7653
- next: () => []
7654
- }));
7655
- } catch (error) {
7656
- const mapped = mapPostgresOperationError(error);
7657
- if (mapped) return notOk(mapped);
7658
- throw error;
7222
+ const connectionId = args.positionals.connection.trim();
7223
+ if (!connectionId) {
7224
+ const example = `${CLI_NAME} postgres connection delete <connection-id> --confirm <connection-id>`;
7225
+ throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Connection id required", {
7226
+ why: "Database connection deletion needs a connection id.",
7227
+ nextActions: [{
7228
+ kind: "user-choice",
7229
+ label: "Pass the connection id to delete."
7230
+ }, {
7231
+ kind: "run-command",
7232
+ label: example,
7233
+ command: example
7234
+ }]
7235
+ });
7659
7236
  }
7237
+ await ctx.prompt.consent(CONSENT_QUESTION$4, { token: connectionId });
7238
+ await (await resolvePostgresProviderOnly(ctx)).removeConnection(connectionId, { signal: ctx.signal });
7239
+ const result = { connection: { id: connectionId } };
7240
+ return ok(ctx.present({ data: result }, {
7241
+ human: () => [
7242
+ {
7243
+ kind: "summary",
7244
+ status: "ok",
7245
+ text: "Deleting database connection."
7246
+ },
7247
+ {
7248
+ kind: "fields",
7249
+ rows: [{
7250
+ label: "connection",
7251
+ value: connectionId
7252
+ }]
7253
+ },
7254
+ {
7255
+ kind: "list",
7256
+ items: ["The connection metadata was deleted. Existing one-time secrets were not shown."]
7257
+ }
7258
+ ],
7259
+ stdout: () => [],
7260
+ json: () => ({ connection: result.connection }),
7261
+ next: () => []
7262
+ }));
7660
7263
  }
7661
7264
  });
7662
7265
  //#endregion
@@ -7728,21 +7331,15 @@ const postgresConnectionListCommand = defineCommand({
7728
7331
  },
7729
7332
  needs: { credentials: true },
7730
7333
  handler: async (args, ctx) => {
7731
- try {
7732
- const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres connection list");
7733
- const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7734
- const result = {
7735
- projectId,
7736
- projectName,
7737
- database,
7738
- connections: await provider.listConnections(database.id, { signal: ctx.signal })
7739
- };
7740
- return ok(ctx.present({ data: result }, listPresentations$4(result)));
7741
- } catch (error) {
7742
- const mapped = mapPostgresOperationError(error);
7743
- if (mapped) return notOk(mapped);
7744
- throw error;
7745
- }
7334
+ const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres connection list");
7335
+ const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7336
+ const result = {
7337
+ projectId,
7338
+ projectName,
7339
+ database,
7340
+ connections: await provider.listConnections(database.id, { signal: ctx.signal })
7341
+ };
7342
+ return ok(ctx.present({ data: result }, listPresentations$4(result)));
7746
7343
  }
7747
7344
  });
7748
7345
  //#endregion
@@ -7760,35 +7357,35 @@ const postgresConnectionRotateCommand = defineCommand({
7760
7357
  },
7761
7358
  needs: { credentials: true },
7762
7359
  handler: async (args, ctx) => {
7763
- try {
7764
- const connectionId = args.positionals.connection.trim();
7765
- if (!connectionId) throw usageError("Connection id required", "Database connection rotation needs a connection id.", "Pass the connection id to rotate.", [legacyCommandFormatter([
7766
- "database",
7767
- "connection",
7768
- "rotate",
7769
- "<connection-id>",
7770
- "--confirm",
7771
- "<connection-id>"
7772
- ])], "database");
7773
- await ctx.prompt.consent(CONSENT_QUESTION$3, { token: connectionId });
7774
- const rotated = await (await resolvePostgresProviderOnly(ctx)).rotateConnection(connectionId, { signal: ctx.signal });
7775
- const result = {
7776
- connection: rotated.connection,
7777
- database: rotated.database,
7778
- connectionString: rotated.connectionString
7779
- };
7780
- const subject = result.database ? `"${result.database.name}"` : `connection ${result.connection.id}`;
7781
- return ok(ctx.present({ data: result }, {
7782
- human: () => secretBlocks(`Rotated credentials for ${subject}. The previous credentials no longer work.`, result.connectionString),
7783
- stdout: () => [result.connectionString],
7784
- json: () => result,
7785
- next: () => []
7786
- }));
7787
- } catch (error) {
7788
- const mapped = mapPostgresOperationError(error);
7789
- if (mapped) return notOk(mapped);
7790
- throw error;
7360
+ const connectionId = args.positionals.connection.trim();
7361
+ if (!connectionId) {
7362
+ const example = `${CLI_NAME} postgres connection rotate <connection-id> --confirm <connection-id>`;
7363
+ throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Connection id required", {
7364
+ why: "Database connection rotation needs a connection id.",
7365
+ nextActions: [{
7366
+ kind: "user-choice",
7367
+ label: "Pass the connection id to rotate."
7368
+ }, {
7369
+ kind: "run-command",
7370
+ label: example,
7371
+ command: example
7372
+ }]
7373
+ });
7791
7374
  }
7375
+ await ctx.prompt.consent(CONSENT_QUESTION$3, { token: connectionId });
7376
+ const rotated = await (await resolvePostgresProviderOnly(ctx)).rotateConnection(connectionId, { signal: ctx.signal });
7377
+ const result = {
7378
+ connection: rotated.connection,
7379
+ database: rotated.database,
7380
+ connectionString: rotated.connectionString
7381
+ };
7382
+ const subject = result.database ? `"${result.database.name}"` : `connection ${result.connection.id}`;
7383
+ return ok(ctx.present({ data: result }, {
7384
+ human: () => secretBlocks(`Rotated credentials for ${subject}. The previous credentials no longer work.`, result.connectionString),
7385
+ stdout: () => [result.connectionString],
7386
+ json: () => result,
7387
+ next: () => []
7388
+ }));
7792
7389
  }
7793
7390
  });
7794
7391
  //#endregion
@@ -7815,35 +7412,42 @@ const postgresCreateCommand = defineCommand({
7815
7412
  },
7816
7413
  needs: { credentials: true },
7817
7414
  handler: async (args, ctx) => {
7818
- try {
7819
- const name = args.positionals.name.trim();
7820
- if (!name) throw usageError("Database name required", "Database create needs a non-empty name.", "Pass a database name.", [`${CLI_NAME} postgres create <name>`], "database");
7821
- const { provider, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres create");
7822
- const created = await provider.createDatabase({
7823
- projectId,
7824
- name,
7825
- branchName: args.flags.branch,
7826
- region: args.flags.region,
7827
- signal: ctx.signal
7415
+ const name = args.positionals.name.trim();
7416
+ if (!name) {
7417
+ const example = `${CLI_NAME} postgres create <name>`;
7418
+ throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Database name required", {
7419
+ why: "Database create needs a non-empty name.",
7420
+ nextActions: [{
7421
+ kind: "user-choice",
7422
+ label: "Pass a database name."
7423
+ }, {
7424
+ kind: "run-command",
7425
+ label: example,
7426
+ command: example
7427
+ }]
7828
7428
  });
7829
- const result = {
7830
- projectId,
7831
- projectName,
7832
- database: ensureProjectId(created.database, projectId),
7833
- connection: created.connection,
7834
- connectionString: created.connectionString
7835
- };
7836
- return ok(ctx.present({ data: result }, {
7837
- human: () => secretBlocks(`Created database "${result.database.name}" in ${postgresTargetLabel(projectName, result.database.branchName)}.`, result.connectionString),
7838
- stdout: () => [result.connectionString],
7839
- json: () => result,
7840
- next: () => []
7841
- }));
7842
- } catch (error) {
7843
- const mapped = mapPostgresOperationError(error);
7844
- if (mapped) return notOk(mapped);
7845
- throw error;
7846
7429
  }
7430
+ const { provider, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres create");
7431
+ const created = await provider.createDatabase({
7432
+ projectId,
7433
+ name,
7434
+ branchName: args.flags.branch,
7435
+ region: args.flags.region,
7436
+ signal: ctx.signal
7437
+ });
7438
+ const result = {
7439
+ projectId,
7440
+ projectName,
7441
+ database: ensureProjectId(created.database, projectId),
7442
+ connection: created.connection,
7443
+ connectionString: created.connectionString
7444
+ };
7445
+ return ok(ctx.present({ data: result }, {
7446
+ human: () => secretBlocks(`Created database "${result.database.name}" in ${postgresTargetLabel(projectName, result.database.branchName)}.`, result.connectionString),
7447
+ stdout: () => [result.connectionString],
7448
+ json: () => result,
7449
+ next: () => []
7450
+ }));
7847
7451
  }
7848
7452
  });
7849
7453
  //#endregion
@@ -7899,22 +7503,16 @@ const postgresDeleteCommand = defineCommand({
7899
7503
  },
7900
7504
  needs: { credentials: true },
7901
7505
  handler: async (args, ctx) => {
7902
- try {
7903
- const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres delete");
7904
- const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7905
- await ctx.prompt.consent(CONSENT_QUESTION$2, { token: database.id });
7906
- await provider.removeDatabase(database.id, { signal: ctx.signal });
7907
- const result = {
7908
- projectId,
7909
- projectName,
7910
- database
7911
- };
7912
- return ok(ctx.present({ data: result }, deletePresentations$3(result)));
7913
- } catch (error) {
7914
- const mapped = mapPostgresOperationError(error);
7915
- if (mapped) return notOk(mapped);
7916
- throw error;
7917
- }
7506
+ const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres delete");
7507
+ const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7508
+ await ctx.prompt.consent(CONSENT_QUESTION$2, { token: database.id });
7509
+ await provider.removeDatabase(database.id, { signal: ctx.signal });
7510
+ const result = {
7511
+ projectId,
7512
+ projectName,
7513
+ database
7514
+ };
7515
+ return ok(ctx.present({ data: result }, deletePresentations$3(result)));
7918
7516
  }
7919
7517
  });
7920
7518
  //#endregion
@@ -7997,25 +7595,19 @@ const postgresListCommand = defineCommand({
7997
7595
  },
7998
7596
  needs: { credentials: true },
7999
7597
  handler: async (args, ctx) => {
8000
- try {
8001
- const { provider, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres list");
8002
- const databases = sortDatabases(await provider.listDatabases({
8003
- projectId,
8004
- branchName: args.flags.branch,
8005
- signal: ctx.signal
8006
- }));
8007
- const result = {
8008
- projectId,
8009
- projectName,
8010
- branchName: args.flags.branch ?? null,
8011
- databases
8012
- };
8013
- return ok(ctx.present({ data: result }, listPresentations$3(result)));
8014
- } catch (error) {
8015
- const mapped = mapPostgresOperationError(error);
8016
- if (mapped) return notOk(mapped);
8017
- throw error;
8018
- }
7598
+ const { provider, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres list");
7599
+ const databases = sortDatabases(await provider.listDatabases({
7600
+ projectId,
7601
+ branchName: args.flags.branch,
7602
+ signal: ctx.signal
7603
+ }));
7604
+ const result = {
7605
+ projectId,
7606
+ projectName,
7607
+ branchName: args.flags.branch ?? null,
7608
+ databases
7609
+ };
7610
+ return ok(ctx.present({ data: result }, listPresentations$3(result)));
8019
7611
  }
8020
7612
  });
8021
7613
  //#endregion
@@ -8119,21 +7711,15 @@ const postgresShowCommand = defineCommand({
8119
7711
  },
8120
7712
  needs: { credentials: true },
8121
7713
  handler: async (args, ctx) => {
8122
- try {
8123
- const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres show");
8124
- const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
8125
- const result = {
8126
- projectId,
8127
- projectName,
8128
- database,
8129
- connections: await provider.listConnections(database.id, { signal: ctx.signal })
8130
- };
8131
- return ok(ctx.present({ data: result }, showPresentations$2(result)));
8132
- } catch (error) {
8133
- const mapped = mapPostgresOperationError(error);
8134
- if (mapped) return notOk(mapped);
8135
- throw error;
8136
- }
7714
+ const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres show");
7715
+ const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7716
+ const result = {
7717
+ projectId,
7718
+ projectName,
7719
+ database,
7720
+ connections: await provider.listConnections(database.id, { signal: ctx.signal })
7721
+ };
7722
+ return ok(ctx.present({ data: result }, showPresentations$2(result)));
8137
7723
  }
8138
7724
  });
8139
7725
  //#endregion
@@ -8251,39 +7837,35 @@ const postgresUsageCommand = defineCommand({
8251
7837
  },
8252
7838
  needs: { credentials: true },
8253
7839
  handler: async (args, ctx) => {
8254
- try {
8255
- const from = parseUsageDate(args.flags.from, "--from", "start", legacyCommandFormatter);
8256
- const to = parseUsageDate(args.flags.to, "--to", "end", legacyCommandFormatter);
8257
- if (from && to && Date.parse(from) > Date.parse(to)) throw usageError("Invalid usage period", "--from must not be later than --to.", "Pass a --from date that is on or before the --to date.", [legacyCommandFormatter([
8258
- "database",
8259
- "usage",
8260
- "<database>",
8261
- "--from",
8262
- "2026-06-01",
8263
- "--to",
8264
- "2026-06-30"
8265
- ])], "database");
8266
- const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres usage");
8267
- const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
8268
- const usage = await provider.getUsage(database.id, {
8269
- from,
8270
- to,
8271
- signal: ctx.signal
8272
- });
8273
- const result = {
8274
- projectId,
8275
- projectName,
8276
- database,
8277
- period: usage.period,
8278
- metrics: usage.metrics,
8279
- generatedAt: usage.generatedAt
8280
- };
8281
- return ok(ctx.present({ data: result }, usagePresentations(result)));
8282
- } catch (error) {
8283
- const mapped = mapPostgresOperationError(error);
8284
- if (mapped) return notOk(mapped);
8285
- throw error;
8286
- }
7840
+ const from = parseUsageDate(args.flags.from, "--from", "start");
7841
+ const to = parseUsageDate(args.flags.to, "--to", "end");
7842
+ if (from && to && Date.parse(from) > Date.parse(to)) throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Invalid usage period", {
7843
+ why: "--from must not be later than --to.",
7844
+ nextActions: [{
7845
+ kind: "user-choice",
7846
+ label: "Pass a --from date that is on or before the --to date."
7847
+ }, {
7848
+ kind: "run-command",
7849
+ label: USAGE_PERIOD_EXAMPLE_COMMAND,
7850
+ command: USAGE_PERIOD_EXAMPLE_COMMAND
7851
+ }]
7852
+ });
7853
+ const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres usage");
7854
+ const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
7855
+ const usage = await provider.getUsage(database.id, {
7856
+ from,
7857
+ to,
7858
+ signal: ctx.signal
7859
+ });
7860
+ const result = {
7861
+ projectId,
7862
+ projectName,
7863
+ database,
7864
+ period: usage.period,
7865
+ metrics: usage.metrics,
7866
+ generatedAt: usage.generatedAt
7867
+ };
7868
+ return ok(ctx.present({ data: result }, usagePresentations(result)));
8287
7869
  }
8288
7870
  });
8289
7871
  //#endregion
@@ -8933,17 +8515,6 @@ function localPinDiagnostics(warnings) {
8933
8515
  nextActions: []
8934
8516
  }));
8935
8517
  }
8936
- /** The legacy NextAction shape minus its `journey` field, which the engine
8937
- * protocol does not carry. */
8938
- function toNextActions(actions) {
8939
- return actions.map((action) => ({
8940
- kind: action.kind,
8941
- label: action.label,
8942
- ...action.command ? { command: portCommandString(action.command) } : {},
8943
- ...action.commands ? { commands: action.commands.map(portCommandString) } : {},
8944
- ...action.reason ? { reason: action.reason } : {}
8945
- }));
8946
- }
8947
8518
  function setupPresentations(result) {
8948
8519
  return {
8949
8520
  stdout: () => [],
@@ -8988,38 +8559,32 @@ const projectCreateCommand = defineCommand({
8988
8559
  },
8989
8560
  needs: { credentials: true },
8990
8561
  handler: async (args, ctx) => {
8991
- try {
8992
- const workspace = await resolveActiveWorkspace(ctx);
8993
- if (!isValidProjectSetupName(args.positionals.name)) throw projectSetupNameRequiredError("project create");
8994
- const name = args.positionals.name.trim();
8995
- const created = await createAppProvider(ctx.api).createProject({
8996
- name,
8997
- region: args.flags.region,
8998
- signal: ctx.signal
8999
- }).catch((error) => {
9000
- /** A cancelled run is cancelled, not a failed creation. The
9001
- * provider flattens the underlying AbortError into a plain
9002
- * Error, which the engine would settle as a bug, so hand it
9003
- * back its own abort reason and let it settle the run as
9004
- * cancelled. */
9005
- if (ctx.signal.aborted) throw ctx.signal.reason;
9006
- throw projectCreateFailedError(error, name, workspace, {
9007
- nextSteps: ["prisma project list", "prisma project link <id-or-name>"],
9008
- permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
9009
- fallbackFix: "Retry the command, or choose an existing Project with prisma project link <id-or-name>."
9010
- });
8562
+ const workspace = await resolveActiveWorkspace(ctx);
8563
+ if (!isValidProjectSetupName(args.positionals.name)) throw projectSetupNameRequiredError("project create");
8564
+ const name = args.positionals.name.trim();
8565
+ const created = await createAppProvider(ctx.api).createProject({
8566
+ name,
8567
+ region: args.flags.region,
8568
+ signal: ctx.signal
8569
+ }).catch((error) => {
8570
+ /** A cancelled run is cancelled, not a failed creation. The
8571
+ * provider flattens the underlying AbortError into a plain
8572
+ * Error, which the engine would settle as a bug, so hand it
8573
+ * back its own abort reason and let it settle the run as
8574
+ * cancelled. */
8575
+ if (ctx.signal.aborted) throw ctx.signal.reason;
8576
+ throw projectCreateFailedError(error, name, workspace, {
8577
+ nextSteps: ["prisma project list", "prisma project link <id-or-name>"],
8578
+ permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
8579
+ fallbackFix: "Retry the command, or choose an existing Project with prisma project link <id-or-name>."
9011
8580
  });
9012
- const result = await bindDirectoryToProject(ctx, workspace, {
9013
- id: created.id,
9014
- name: created.name,
9015
- ...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {}
9016
- }, "created");
9017
- return ok(ctx.present({ data: result }, setupPresentations(result)));
9018
- } catch (error) {
9019
- const mapped = mapProjectOperationError(error);
9020
- if (mapped) return notOk(mapped);
9021
- throw error;
9022
- }
8581
+ });
8582
+ const result = await bindDirectoryToProject(ctx, workspace, {
8583
+ id: created.id,
8584
+ name: created.name,
8585
+ ...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {}
8586
+ }, "created");
8587
+ return ok(ctx.present({ data: result }, setupPresentations(result)));
9023
8588
  }
9024
8589
  });
9025
8590
  //#endregion
@@ -9072,34 +8637,56 @@ const projectDeleteCommand = defineCommand({
9072
8637
  },
9073
8638
  needs: { credentials: true },
9074
8639
  handler: async (args, ctx) => {
9075
- try {
9076
- const workspace = await resolveActiveWorkspace(ctx);
9077
- const projects = await listWorkspaceProjects$1(ctx);
9078
- const project = toProjectSummary(resolveProjectForSetup(args.positionals.project.trim(), projects, workspace));
9079
- await ctx.prompt.consent(CONSENT_QUESTION$1, { token: project.id });
9080
- await createManagementProjectProvider(ctx.api).removeProject({
9081
- projectId: project.id,
9082
- signal: ctx.signal
9083
- });
9084
- const warnings = [];
9085
- const result = {
9086
- workspace,
9087
- project,
9088
- localPin: { cleared: await cleanupLocalPinForProject(legacyOperationContext(ctx), project.id, { onError: (message) => warnings.push(message) }) }
9089
- };
9090
- const diagnostics = localPinDiagnostics(warnings);
9091
- return ok(ctx.present({
9092
- data: result,
9093
- diagnostics
9094
- }, deletePresentations$2(result)));
9095
- } catch (error) {
9096
- const mapped = mapProjectOperationError(error);
9097
- if (mapped) return notOk(mapped);
9098
- throw error;
9099
- }
8640
+ const workspace = await resolveActiveWorkspace(ctx);
8641
+ const projects = await listWorkspaceProjects$1(ctx);
8642
+ const project = toProjectSummary(resolveProjectForSetup(args.positionals.project.trim(), projects, workspace));
8643
+ await ctx.prompt.consent(CONSENT_QUESTION$1, { token: project.id });
8644
+ await createManagementProjectProvider(ctx.api).removeProject({
8645
+ projectId: project.id,
8646
+ signal: ctx.signal
8647
+ });
8648
+ const warnings = [];
8649
+ const result = {
8650
+ workspace,
8651
+ project,
8652
+ localPin: { cleared: await cleanupLocalPinForProject(legacyOperationContext(ctx), project.id, { onError: (message) => warnings.push(message) }) }
8653
+ };
8654
+ const diagnostics = localPinDiagnostics(warnings);
8655
+ return ok(ctx.present({
8656
+ data: result,
8657
+ diagnostics
8658
+ }, deletePresentations$2(result)));
9100
8659
  }
9101
8660
  });
9102
8661
  //#endregion
8662
+ //#region src/lib/app/env-errors.ts
8663
+ /**
8664
+ * The structured errors the `project env` code paths raise, with the
8665
+ * registered PROJECT.* codes assigned at origin. This is the lowest
8666
+ * layer the env commands, controllers and parsers share, so the
8667
+ * parsers can raise without depending on the controllers.
8668
+ */
8669
+ function userChoice(label) {
8670
+ return {
8671
+ kind: "user-choice",
8672
+ label
8673
+ };
8674
+ }
8675
+ function runCommand(command, reason) {
8676
+ return {
8677
+ kind: "run-command",
8678
+ label: command,
8679
+ command,
8680
+ ...reason === void 0 ? {} : { reason }
8681
+ };
8682
+ }
8683
+ function envUsageError(summary, why, fix, commands = []) {
8684
+ return new CliStructuredError("PROJECT.USAGE_ERROR", summary, {
8685
+ why,
8686
+ nextActions: [userChoice(fix), ...commands.map((step) => runCommand(step))]
8687
+ });
8688
+ }
8689
+ //#endregion
9103
8690
  //#region src/lib/app/env-config.ts
9104
8691
  const VALID_ROLES = new Set(["production", "preview"]);
9105
8692
  function positionalHint(command) {
@@ -9108,9 +8695,9 @@ function positionalHint(command) {
9108
8695
  return "";
9109
8696
  }
9110
8697
  function resolveEnvScope(flags, options) {
9111
- if (flags.roleName && flags.branchName) throw usageError(`prisma project env ${options.command} accepts either --role or --branch`, "--role targets a project-level config map; --branch targets a preview branch override.", "Pass exactly one scope flag.", [`prisma project env ${options.command} ${positionalHint(options.command)}--role preview`, `prisma project env ${options.command} ${positionalHint(options.command)}--branch feature/foo`], "app");
8698
+ if (flags.roleName && flags.branchName) throw envUsageError(`prisma project env ${options.command} accepts either --role or --branch`, "--role targets a project-level config map; --branch targets a preview branch override.", "Pass exactly one scope flag.", [`prisma project env ${options.command} ${positionalHint(options.command)}--role preview`, `prisma project env ${options.command} ${positionalHint(options.command)}--branch feature/foo`]);
9112
8699
  if (flags.roleName) {
9113
- if (!VALID_ROLES.has(flags.roleName)) throw usageError(`Unknown role "${flags.roleName}"`, "--role accepts production or preview.", "Pass --role production or --role preview.", [`prisma project env ${options.command} --role production`, `prisma project env ${options.command} --role preview`], "app");
8700
+ if (!VALID_ROLES.has(flags.roleName)) throw envUsageError(`Unknown role "${flags.roleName}"`, "--role accepts production or preview.", "Pass --role production or --role preview.", [`prisma project env ${options.command} --role production`, `prisma project env ${options.command} --role preview`]);
9114
8701
  return {
9115
8702
  kind: "role",
9116
8703
  role: flags.roleName
@@ -9122,16 +8709,16 @@ function resolveEnvScope(flags, options) {
9122
8709
  };
9123
8710
  if (options.requireExplicit) {
9124
8711
  const positional = positionalHint(options.command);
9125
- throw usageError(`prisma project env ${options.command} requires --role or --branch`, "Writing without an explicit scope is rejected so the command never silently targets production.", "Pass --role production, --role preview, or --branch <git-name>.", [
8712
+ throw envUsageError(`prisma project env ${options.command} requires --role or --branch`, "Writing without an explicit scope is rejected so the command never silently targets production.", "Pass --role production, --role preview, or --branch <git-name>.", [
9126
8713
  `prisma project env ${options.command} ${positional}--role production`,
9127
8714
  `prisma project env ${options.command} ${positional}--role preview`,
9128
8715
  `prisma project env ${options.command} ${positional}--branch feature/foo`
9129
- ], "app");
8716
+ ]);
9130
8717
  }
9131
8718
  return null;
9132
8719
  }
9133
8720
  function parseKeyValuePositional(raw, command, env = process.env) {
9134
- if (!raw) throw usageError(`prisma project env ${command} requires KEY=VALUE`, "No KEY=VALUE positional argument was supplied.", "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma project env ${command} STRIPE_KEY=sk_test_xxx --role production`], "app");
8721
+ if (!raw) throw envUsageError(`prisma project env ${command} requires KEY=VALUE`, "No KEY=VALUE positional argument was supplied.", "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma project env ${command} STRIPE_KEY=sk_test_xxx --role production`]);
9135
8722
  const separatorIndex = raw.indexOf("=");
9136
8723
  if (separatorIndex === -1) {
9137
8724
  if (KEY_SHAPE.test(raw)) {
@@ -9141,14 +8728,14 @@ function parseKeyValuePositional(raw, command, env = process.env) {
9141
8728
  key: raw,
9142
8729
  value
9143
8730
  };
9144
- throw usageError(`Value for "${raw}" was not provided`, `No KEY=VALUE assignment was supplied, and ${raw} is not set in the current environment.`, "Pass KEY=VALUE or export the variable before running the command.", [`prisma project env ${command} ${raw}=value --role production`, `${raw}=value prisma project env ${command} ${raw} --role production`], "app");
8731
+ throw envUsageError(`Value for "${raw}" was not provided`, `No KEY=VALUE assignment was supplied, and ${raw} is not set in the current environment.`, "Pass KEY=VALUE or export the variable before running the command.", [`prisma project env ${command} ${raw}=value --role production`, `${raw}=value prisma project env ${command} ${raw} --role production`]);
9145
8732
  }
9146
- throw usageError(`KEY=VALUE argument is missing the = separator`, `"${raw}" does not contain an = character.`, "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma project env ${command} STRIPE_KEY=sk_test_xxx --role production`], "app");
8733
+ throw envUsageError(`KEY=VALUE argument is missing the = separator`, `"${raw}" does not contain an = character.`, "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma project env ${command} STRIPE_KEY=sk_test_xxx --role production`]);
9147
8734
  }
9148
8735
  const key = raw.slice(0, separatorIndex);
9149
8736
  const value = raw.slice(separatorIndex + 1);
9150
8737
  validateKey(key, command);
9151
- if (value.length === 0) throw usageError(`KEY=VALUE argument has an empty value`, `"${raw}" has an empty value after the = separator.`, `Pass a non-empty value, or use prisma project env delete to delete a variable.`, [`prisma project env ${command} ${key}=value --role production`], "app");
8738
+ if (value.length === 0) throw envUsageError(`KEY=VALUE argument has an empty value`, `"${raw}" has an empty value after the = separator.`, `Pass a non-empty value, or use prisma project env delete to delete a variable.`, [`prisma project env ${command} ${key}=value --role production`]);
9152
8739
  return {
9153
8740
  key,
9154
8741
  value
@@ -9156,9 +8743,9 @@ function parseKeyValuePositional(raw, command, env = process.env) {
9156
8743
  }
9157
8744
  const KEY_SHAPE = /^[A-Z_][A-Z0-9_]*$/;
9158
8745
  function validateKey(key, command) {
9159
- if (key.length === 0) throw usageError(`Variable key cannot be empty`, "An empty key was passed.", "Pass an env-var key, e.g. STRIPE_KEY.", [`prisma project env ${command} STRIPE_KEY=value --role production`], "app");
9160
- if (key.length > 256) throw usageError(`Variable key "${key}" exceeds the 256-character limit`, "Env-var keys are capped at 256 characters by the platform.", "Use a shorter key.", [], "app");
9161
- if (!KEY_SHAPE.test(key)) throw usageError(`Variable key "${key}" must match the POSIX env-var shape`, "Keys must start with an uppercase letter or underscore and contain only uppercase letters, digits, and underscores.", "Rename the key to match [A-Z_][A-Z0-9_]*.", [`prisma project env ${command} STRIPE_KEY=value --role production`], "app");
8746
+ if (key.length === 0) throw envUsageError(`Variable key cannot be empty`, "An empty key was passed.", "Pass an env-var key, e.g. STRIPE_KEY.", [`prisma project env ${command} STRIPE_KEY=value --role production`]);
8747
+ if (key.length > 256) throw envUsageError(`Variable key "${key}" exceeds the 256-character limit`, "Env-var keys are capped at 256 characters by the platform.", "Use a shorter key.");
8748
+ if (!KEY_SHAPE.test(key)) throw envUsageError(`Variable key "${key}" must match the POSIX env-var shape`, "Keys must start with an uppercase letter or underscore and contain only uppercase letters, digits, and underscores.", "Rename the key to match [A-Z_][A-Z0-9_]*.", [`prisma project env ${command} STRIPE_KEY=value --role production`]);
9162
8749
  }
9163
8750
  function formatScopeLabel(scope) {
9164
8751
  if (scope.kind === "role") return scope.role;
@@ -9173,18 +8760,18 @@ async function readEnvFileAssignments(cwd, filePath, command) {
9173
8760
  try {
9174
8761
  contents = await readFile(resolvedPath, "utf8");
9175
8762
  } catch (error) {
9176
- throw usageError(`Failed to read env file "${filePath}"`, error instanceof Error ? error.message : "The file could not be read.", "Pass a readable dotenv file path.", [`prisma project env ${command} --file .env --role preview`], "app");
8763
+ throw envUsageError(`Failed to read env file "${filePath}"`, error instanceof Error ? error.message : "The file could not be read.", "Pass a readable dotenv file path.", [`prisma project env ${command} --file .env --role preview`]);
9177
8764
  }
9178
8765
  return parseEnvFileContents(contents, filePath, command);
9179
8766
  }
9180
8767
  function parseEnvFileContents(contents, filePath, command) {
9181
8768
  const parsedKeys = extractParsedKeys(contents);
9182
- if (parsedKeys.length === 0) throw usageError(`No environment variables found in "${filePath}"`, "The file does not contain any KEY=VALUE assignments.", "Pass a dotenv file with at least one non-empty variable.", [], "app");
8769
+ if (parsedKeys.length === 0) throw envUsageError(`No environment variables found in "${filePath}"`, "The file does not contain any KEY=VALUE assignments.", "Pass a dotenv file with at least one non-empty variable.");
9183
8770
  const seen = /* @__PURE__ */ new Map();
9184
8771
  for (const entry of parsedKeys) {
9185
8772
  validateEnvFileKey(entry.key, entry.line, filePath, command);
9186
8773
  const firstLine = seen.get(entry.key);
9187
- if (firstLine !== void 0) throw usageError(`Duplicate environment variable "${entry.key}" in "${filePath}"`, `Lines ${firstLine} and ${entry.line} both define ${entry.key}.`, "Keep one assignment for each key before importing the file.", [], "app");
8774
+ if (firstLine !== void 0) throw envUsageError(`Duplicate environment variable "${entry.key}" in "${filePath}"`, `Lines ${firstLine} and ${entry.line} both define ${entry.key}.`, "Keep one assignment for each key before importing the file.");
9188
8775
  seen.set(entry.key, entry.line);
9189
8776
  }
9190
8777
  const parsedValues = parse(contents);
@@ -9192,7 +8779,7 @@ function parseEnvFileContents(contents, filePath, command) {
9192
8779
  const value = parsedValues[key];
9193
8780
  if (typeof value !== "string" || value.length === 0) {
9194
8781
  const line = seen.get(key);
9195
- throw usageError(`Environment variable "${key}" in "${filePath}" has an empty value`, line === void 0 ? `${key} has an empty value.` : `Line ${line} defines ${key} with an empty value.`, "Pass a non-empty value, or omit the key from the file.", [], "app");
8782
+ throw envUsageError(`Environment variable "${key}" in "${filePath}" has an empty value`, line === void 0 ? `${key} has an empty value.` : `Line ${line} defines ${key} with an empty value.`, "Pass a non-empty value, or omit the key from the file.");
9196
8783
  }
9197
8784
  return {
9198
8785
  key,
@@ -9228,7 +8815,7 @@ function validateEnvFileKey(key, line, filePath, command) {
9228
8815
  validateKey(key, command);
9229
8816
  } catch (error) {
9230
8817
  const reason = error instanceof Error && error.message.length > 0 ? error.message : "Invalid environment variable key.";
9231
- throw usageError(`Invalid environment variable "${key}" in "${filePath}"`, `Line ${line}: ${reason}`, "Use a valid env-var key and retry the import.", [], "app");
8818
+ throw envUsageError(`Invalid environment variable "${key}" in "${filePath}"`, `Line ${line}: ${reason}`, "Use a valid env-var key and retry the import.");
9232
8819
  }
9233
8820
  }
9234
8821
  function hasClosingQuote(value, quote, startIndex) {
@@ -9277,15 +8864,18 @@ function apiCallError(summary, response, error) {
9277
8864
  const apiCode = error?.error?.code;
9278
8865
  const apiMessage = error?.error?.message;
9279
8866
  const apiHint = error?.error?.hint;
9280
- if (status === 401 || status === 403) return authRequiredError(["prisma auth login"]);
9281
- return new CliError({
9282
- code: apiCode ?? "ENV_API_ERROR",
9283
- domain: "app",
9284
- summary,
8867
+ if (status === 401 || status === 403) return new CliStructuredError("PROJECT.ENV_API_ERROR", summary, {
8868
+ why: "The Management API rejected the request as unauthorized or forbidden.",
8869
+ meta: { status },
8870
+ nextActions: [runCommand("prisma auth login")]
8871
+ });
8872
+ return new CliStructuredError("PROJECT.ENV_API_ERROR", summary, {
9285
8873
  why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
9286
- fix: apiHint ?? "Re-run with --trace for the underlying API response details.",
9287
- exitCode: 1,
9288
- nextSteps: []
8874
+ ...status || apiCode !== void 0 ? { meta: {
8875
+ ...status ? { status } : {},
8876
+ ...apiCode !== void 0 ? { apiCode } : {}
8877
+ } } : {},
8878
+ nextActions: [userChoice(apiHint ?? "Re-run with --log-level verbose for the underlying API response details.")]
9289
8879
  });
9290
8880
  }
9291
8881
  function formatDescriptorLabel(scope) {
@@ -9296,15 +8886,15 @@ function formatDescriptorLabel(scope) {
9296
8886
  //#endregion
9297
8887
  //#region src/controllers/app-env.ts
9298
8888
  function resolveEnvWriteSource(rawAssignment, filePath, command) {
9299
- if (filePath !== void 0 && rawAssignment !== void 0) throw usageError(`prisma project env ${command} accepts either KEY=VALUE or --file`, "The command received both a positional assignment and a dotenv file path.", "Pass one input source.", [`prisma project env ${command} KEY=value --role preview`, `prisma project env ${command} --file .env --role preview`], "app");
8889
+ if (filePath !== void 0 && rawAssignment !== void 0) throw envUsageError(`prisma project env ${command} accepts either KEY=VALUE or --file`, "The command received both a positional assignment and a dotenv file path.", "Pass one input source.", [`prisma project env ${command} KEY=value --role preview`, `prisma project env ${command} --file .env --role preview`]);
9300
8890
  if (filePath !== void 0) {
9301
- if (filePath.length === 0) throw usageError(`prisma project env ${command} --file requires a path`, "The --file flag was passed without a file path.", "Pass a readable dotenv file path.", [`prisma project env ${command} --file .env --role preview`], "app");
8891
+ if (filePath.length === 0) throw envUsageError(`prisma project env ${command} --file requires a path`, "The --file flag was passed without a file path.", "Pass a readable dotenv file path.", [`prisma project env ${command} --file .env --role preview`]);
9302
8892
  return {
9303
8893
  kind: "file",
9304
8894
  filePath
9305
8895
  };
9306
8896
  }
9307
- if (rawAssignment === void 0) throw usageError(`prisma project env ${command} requires KEY=VALUE or --file`, "No environment variable input was supplied.", "Pass a single KEY=VALUE assignment or a dotenv file path.", [`prisma project env ${command} KEY=value --role preview`, `prisma project env ${command} --file .env --role preview`], "app");
8897
+ if (rawAssignment === void 0) throw envUsageError(`prisma project env ${command} requires KEY=VALUE or --file`, "No environment variable input was supplied.", "Pass a single KEY=VALUE assignment or a dotenv file path.", [`prisma project env ${command} KEY=value --role preview`, `prisma project env ${command} --file .env --role preview`]);
9308
8898
  return {
9309
8899
  kind: "single",
9310
8900
  rawAssignment
@@ -9334,14 +8924,9 @@ async function resolveScopeToApi(client, projectId, scope, options) {
9334
8924
  }
9335
8925
  };
9336
8926
  const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName, options.signal) : await resolveExistingBranch(client, projectId, scope.branchName, options.signal);
9337
- if (branch.role === "production") throw new CliError({
9338
- code: "ENV_BRANCH_SCOPE_IS_PRODUCTION",
9339
- domain: "app",
9340
- summary: `Branch "${scope.branchName}" is the production branch`,
8927
+ if (branch.role === "production") throw new CliStructuredError("PROJECT.ENV_BRANCH_SCOPE_IS_PRODUCTION", `Branch "${scope.branchName}" is the production branch`, {
9341
8928
  why: "Production variables are project-level only; branch overrides apply to preview branches.",
9342
- fix: "Use --role production for the production branch.",
9343
- exitCode: 1,
9344
- nextSteps: ["prisma project env list --role production"]
8929
+ nextActions: [userChoice("Use --role production for the production branch."), runCommand("prisma project env list --role production")]
9345
8930
  });
9346
8931
  return {
9347
8932
  scope,
@@ -9420,28 +9005,18 @@ async function listBranchesByName(client, projectId, branchName, signal) {
9420
9005
  }
9421
9006
  async function resolveExistingBranch(client, projectId, branchName, signal) {
9422
9007
  const branch = (await listBranchesByName(client, projectId, branchName, signal))[0];
9423
- if (!branch) throw new CliError({
9424
- code: "ENV_BRANCH_NOT_FOUND",
9425
- domain: "app",
9426
- summary: `Branch "${branchName}" not found`,
9008
+ if (!branch) throw new CliStructuredError("PROJECT.ENV_BRANCH_NOT_FOUND", `Branch "${branchName}" not found`, {
9427
9009
  why: "Branch update, list, and delete commands only target existing preview branches.",
9428
- fix: "Create the branch by deploying it, or use `project env add --branch` to create its first override.",
9429
- exitCode: 1,
9430
- nextSteps: [`prisma project env add KEY=value --branch ${branchName}`]
9010
+ nextActions: [userChoice("Create the branch by deploying it, or use `project env add --branch` to create its first override."), runCommand(`prisma project env add KEY=value --branch ${branchName}`)]
9431
9011
  });
9432
9012
  return branch;
9433
9013
  }
9434
9014
  async function resolveOrCreateBranch(client, projectId, branchName, signal) {
9435
9015
  const existing = (await listBranchesByName(client, projectId, branchName, signal))[0];
9436
9016
  if (existing) return existing;
9437
- if (!await projectHasDefaultBranch(client, projectId, signal)) throw new CliError({
9438
- code: "ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH",
9439
- domain: "app",
9440
- summary: `Cannot create branch "${branchName}" from project env`,
9017
+ if (!await projectHasDefaultBranch(client, projectId, signal)) throw new CliStructuredError("PROJECT.ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH", `Cannot create branch "${branchName}" from project env`, {
9441
9018
  why: "Creating the first branch would make it the project default, but branch overrides are preview-only.",
9442
- fix: "Create or deploy the default branch first, then add the branch override.",
9443
- exitCode: 1,
9444
- nextSteps: ["prisma git connect <repository-url>"]
9019
+ nextActions: [userChoice("Create or deploy the default branch first, then add the branch override."), runCommand("prisma git connect <repository-url>")]
9445
9020
  });
9446
9021
  const { data, error, response } = await client.POST("/v1/projects/{projectId}/branches", {
9447
9022
  params: { path: { projectId } },
@@ -9529,18 +9104,13 @@ function materializeEffectiveRows(rows, resolved) {
9529
9104
  async function runEnvAddFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
9530
9105
  const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
9531
9106
  const existingKeys = assignments.map((assignment) => assignment.key).filter((key) => existing.has(key));
9532
- if (existingKeys.length > 0) throw new CliError({
9533
- code: "ENV_VARIABLE_ALREADY_EXISTS",
9534
- domain: "app",
9535
- summary: `${existingKeys.length} environment variable(s) already exist in ${formatScopeLabel(resolved.scope)}`,
9107
+ if (existingKeys.length > 0) throw new CliStructuredError("PROJECT.ENV_VARIABLE_ALREADY_EXISTS", `${existingKeys.length} environment variable(s) already exist in ${formatScopeLabel(resolved.scope)}`, {
9536
9108
  why: `Existing keys: ${formatKeyList(existingKeys)}.`,
9537
- fix: "Split the input file by key state: update existing keys and add new keys separately.",
9538
- exitCode: 1,
9539
- nextSteps: splitFileNextSteps(filePath, resolved.scope, {
9109
+ meta: { keys: existingKeys },
9110
+ nextActions: [userChoice("Split the input file by key state: update existing keys and add new keys separately."), ...splitFileActions(filePath, resolved.scope, {
9540
9111
  existingKeys,
9541
9112
  first: "update-existing"
9542
- }),
9543
- meta: { keys: existingKeys }
9113
+ })]
9544
9114
  });
9545
9115
  const warnings = await missingPreviewDefaultWarnings(client, projectId, resolved.scope, assignments.map((assignment) => assignment.key), context.runtime.signal);
9546
9116
  const variables = [];
@@ -9572,25 +9142,19 @@ async function runEnvAddFile(context, client, projectId, resolved, filePath, ass
9572
9142
  count: variables.length
9573
9143
  }
9574
9144
  },
9575
- warnings,
9576
- nextSteps: []
9145
+ warnings
9577
9146
  };
9578
9147
  }
9579
9148
  async function runEnvUpdateFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
9580
9149
  const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
9581
9150
  const missingKeys = assignments.map((assignment) => assignment.key).filter((key) => !existing.has(key));
9582
- if (missingKeys.length > 0) throw new CliError({
9583
- code: "ENV_VARIABLE_NOT_FOUND",
9584
- domain: "app",
9585
- summary: `${missingKeys.length} environment variable(s) not found in ${formatScopeLabel(resolved.scope)}`,
9151
+ if (missingKeys.length > 0) throw new CliStructuredError("PROJECT.ENV_VARIABLE_NOT_FOUND", `${missingKeys.length} environment variable(s) not found in ${formatScopeLabel(resolved.scope)}`, {
9586
9152
  why: `Missing keys: ${formatKeyList(missingKeys)}.`,
9587
- fix: "Split the input file by key state: add missing keys and update existing keys separately.",
9588
- exitCode: 1,
9589
- nextSteps: splitFileNextSteps(filePath, resolved.scope, {
9153
+ meta: { keys: missingKeys },
9154
+ nextActions: [userChoice("Split the input file by key state: add missing keys and update existing keys separately."), ...splitFileActions(filePath, resolved.scope, {
9590
9155
  missingKeys,
9591
9156
  first: "add-missing"
9592
- }),
9593
- meta: { keys: missingKeys }
9157
+ })]
9594
9158
  });
9595
9159
  const variables = [];
9596
9160
  for (const assignment of assignments) {
@@ -9620,8 +9184,7 @@ async function runEnvUpdateFile(context, client, projectId, resolved, filePath,
9620
9184
  count: variables.length
9621
9185
  }
9622
9186
  },
9623
- warnings: [],
9624
- nextSteps: []
9187
+ warnings: []
9625
9188
  };
9626
9189
  }
9627
9190
  async function findVariablesByNaturalKey(client, projectId, keys, resolved, signal) {
@@ -9656,20 +9219,20 @@ async function missingPreviewDefaultWarnings(client, projectId, scope, keys, sig
9656
9219
  }
9657
9220
  function envFileApplyFailedError(command, filePath, scope, failedKey, writtenVariables, error) {
9658
9221
  const writtenKeys = writtenVariables.map((variable) => variable.key);
9659
- const cause = error instanceof CliError ? error.summary : error instanceof Error ? error.message : "Unknown error.";
9660
- return new CliError({
9661
- code: "ENV_FILE_APPLY_FAILED",
9662
- domain: "app",
9663
- summary: `Failed to ${command} "${failedKey}" from "${filePath}"`,
9222
+ const cause = error instanceof Error ? error.message : "Unknown error.";
9223
+ return new CliStructuredError("PROJECT.ENV_FILE_APPLY_FAILED", `Failed to ${command} "${failedKey}" from "${filePath}"`, {
9664
9224
  why: writtenKeys.length === 0 ? `No variables were written before ${failedKey} failed. Cause: ${cause}` : `Written keys before failure: ${formatKeyList(writtenKeys)}. Cause: ${cause}`,
9665
- fix: "Inspect the target scope, then retry the remaining keys once the API issue is resolved.",
9666
- exitCode: 1,
9667
- nextSteps: [`prisma project env list ${formatScopeFlag(scope)}`, retryStepForApplyFailure(command, filePath, scope, writtenKeys)],
9668
9225
  meta: {
9669
9226
  file: filePath,
9670
9227
  failedKey,
9671
9228
  writtenKeys
9672
- }
9229
+ },
9230
+ cause: error,
9231
+ nextActions: [
9232
+ userChoice("Inspect the target scope, then retry the remaining keys once the API issue is resolved."),
9233
+ runCommand(`prisma project env list ${formatScopeFlag(scope)}`),
9234
+ runCommand(retryStepForApplyFailure(command, filePath, scope, writtenKeys))
9235
+ ]
9673
9236
  });
9674
9237
  }
9675
9238
  function retryStepForApplyFailure(command, filePath, scope, writtenKeys) {
@@ -9677,22 +9240,13 @@ function retryStepForApplyFailure(command, filePath, scope, writtenKeys) {
9677
9240
  if (writtenKeys.length === 0) return `prisma project env add --file ${filePath} ${formatScopeFlag(scope)}`;
9678
9241
  return `prisma project env add --file <remaining.env> ${formatScopeFlag(scope)}`;
9679
9242
  }
9680
- function splitFileNextSteps(filePath, scope, options) {
9243
+ /** Each command carries the key list it applies to as its reason. */
9244
+ function splitFileActions(filePath, scope, options) {
9681
9245
  const scopeFlag = formatScopeFlag(scope);
9682
9246
  const existingFile = `${filePath}.existing`;
9683
9247
  const newFile = `${filePath}.new`;
9684
- if (options.first === "update-existing") return [
9685
- `# existing keys: ${formatKeyList(options.existingKeys)}`,
9686
- `prisma project env update --file ${existingFile} ${scopeFlag}`,
9687
- "# new keys only",
9688
- `prisma project env add --file ${newFile} ${scopeFlag}`
9689
- ];
9690
- return [
9691
- `# missing keys: ${formatKeyList(options.missingKeys)}`,
9692
- `prisma project env add --file ${newFile} ${scopeFlag}`,
9693
- "# existing keys only",
9694
- `prisma project env update --file ${existingFile} ${scopeFlag}`
9695
- ];
9248
+ if (options.first === "update-existing") return [runCommand(`prisma project env update --file ${existingFile} ${scopeFlag}`, `existing keys: ${formatKeyList(options.existingKeys)}`), runCommand(`prisma project env add --file ${newFile} ${scopeFlag}`, "new keys only")];
9249
+ return [runCommand(`prisma project env add --file ${newFile} ${scopeFlag}`, `missing keys: ${formatKeyList(options.missingKeys)}`), runCommand(`prisma project env update --file ${existingFile} ${scopeFlag}`, "existing keys only")];
9696
9250
  }
9697
9251
  function formatKeyList(keys) {
9698
9252
  return keys.map((key) => `"${key}"`).join(", ");
@@ -9769,7 +9323,7 @@ function requireEnvScope(flags, command) {
9769
9323
  requireExplicit: true,
9770
9324
  command
9771
9325
  });
9772
- if (!scope) throw usageError(`prisma project env ${command} requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma project env ${command} KEY=value --role production`], "app");
9326
+ if (!scope) throw envUsageError(`prisma project env ${command} requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma project env ${command} KEY=value --role production`]);
9773
9327
  return scope;
9774
9328
  }
9775
9329
  /** Workspace, pinned project and the API scope every env write needs. */
@@ -9923,74 +9477,63 @@ const projectEnvAddCommand = defineCommand({
9923
9477
  },
9924
9478
  needs: { credentials: true },
9925
9479
  handler: async (args, ctx) => {
9926
- try {
9927
- const source = resolveEnvWriteSource(args.positionals.assignment, args.flags.file, "add");
9928
- const scope = requireEnvScope(args.flags, "add");
9929
- const input = await resolveEnvWriteInput(legacyOperationContext(ctx), source, "add");
9930
- const { projectId, verboseContext, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env add", true);
9931
- if (input.kind === "file") {
9932
- const written = await runEnvAddFile(legacyOperationContext(ctx), ctx.api, projectId, resolved, input.filePath, input.assignments, verboseContext);
9933
- const result = {
9934
- projectId,
9935
- scope: resolved.descriptor,
9936
- variables: written.result.variables,
9937
- file: written.result.file
9938
- };
9939
- return ok(ctx.present({
9940
- data: result,
9941
- diagnostics: previewDefaultDiagnostics(written.warnings)
9942
- }, fileWritePresentations({
9943
- title: "Setting new environment variables from file.",
9944
- emptyMessage: "No environment variables imported.",
9945
- scope: result.scope,
9946
- filePath: result.file.path,
9947
- variables: result.variables
9948
- }, result)));
9949
- }
9950
- if (await findVariableByNaturalKey(ctx.api, projectId, input.key, resolved, ctx.signal)) throw new CliError({
9951
- code: "ENV_VARIABLE_ALREADY_EXISTS",
9952
- domain: "app",
9953
- summary: `Variable "${input.key}" already exists in ${formatScopeLabel(scope)}`,
9954
- why: "A variable with this key already exists in the targeted scope.",
9955
- fix: "Use `prisma project env update` to change an existing variable's value.",
9956
- exitCode: 1,
9957
- nextSteps: [`prisma project env update ${input.key}=<new-value> ${formatScopeFlag$1(scope)}`]
9958
- });
9959
- const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(ctx.api, projectId, input.key, {
9960
- descriptor: {
9961
- kind: "role",
9962
- role: "preview"
9963
- },
9964
- apiTarget: {
9965
- class: "preview",
9966
- branchId: null
9967
- }
9968
- }, ctx.signal) ? [`Variable "${input.key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
9969
- const { data, error, response } = await ctx.api.POST("/v1/environment-variables", {
9970
- body: {
9971
- projectId,
9972
- class: resolved.apiTarget.class,
9973
- ...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
9974
- key: input.key,
9975
- value: input.value
9976
- },
9977
- signal: ctx.signal
9978
- });
9979
- if (error || !data) throw apiCallError(`Failed to add ${input.key}`, response, error);
9480
+ const source = resolveEnvWriteSource(args.positionals.assignment, args.flags.file, "add");
9481
+ const scope = requireEnvScope(args.flags, "add");
9482
+ const input = await resolveEnvWriteInput(legacyOperationContext(ctx), source, "add");
9483
+ const { projectId, verboseContext, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env add", true);
9484
+ if (input.kind === "file") {
9485
+ const written = await runEnvAddFile(legacyOperationContext(ctx), ctx.api, projectId, resolved, input.filePath, input.assignments, verboseContext);
9980
9486
  const result = {
9981
9487
  projectId,
9982
9488
  scope: resolved.descriptor,
9983
- variable: toMetadata(data.data, resolved.descriptor)
9489
+ variables: written.result.variables,
9490
+ file: written.result.file
9984
9491
  };
9985
9492
  return ok(ctx.present({
9986
9493
  data: result,
9987
- diagnostics: previewDefaultDiagnostics(warnings)
9988
- }, singlePresentations$1(result)));
9989
- } catch (error) {
9990
- const mapped = mapProjectOperationError(error);
9991
- if (mapped) return notOk(mapped);
9992
- throw error;
9993
- }
9494
+ diagnostics: previewDefaultDiagnostics(written.warnings)
9495
+ }, fileWritePresentations({
9496
+ title: "Setting new environment variables from file.",
9497
+ emptyMessage: "No environment variables imported.",
9498
+ scope: result.scope,
9499
+ filePath: result.file.path,
9500
+ variables: result.variables
9501
+ }, result)));
9502
+ }
9503
+ if (await findVariableByNaturalKey(ctx.api, projectId, input.key, resolved, ctx.signal)) throw new CliStructuredError("PROJECT.ENV_VARIABLE_ALREADY_EXISTS", `Variable "${input.key}" already exists in ${formatScopeLabel(scope)}`, {
9504
+ why: "A variable with this key already exists in the targeted scope.",
9505
+ nextActions: [userChoice("Use `prisma project env update` to change an existing variable's value."), runCommand(`prisma project env update ${input.key}=<new-value> ${formatScopeFlag$1(scope)}`)]
9506
+ });
9507
+ const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(ctx.api, projectId, input.key, {
9508
+ descriptor: {
9509
+ kind: "role",
9510
+ role: "preview"
9511
+ },
9512
+ apiTarget: {
9513
+ class: "preview",
9514
+ branchId: null
9515
+ }
9516
+ }, ctx.signal) ? [`Variable "${input.key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
9517
+ const { data, error, response } = await ctx.api.POST("/v1/environment-variables", {
9518
+ body: {
9519
+ projectId,
9520
+ class: resolved.apiTarget.class,
9521
+ ...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
9522
+ key: input.key,
9523
+ value: input.value
9524
+ },
9525
+ signal: ctx.signal
9526
+ });
9527
+ if (error || !data) throw apiCallError(`Failed to add ${input.key}`, response, error);
9528
+ const result = {
9529
+ projectId,
9530
+ scope: resolved.descriptor,
9531
+ variable: toMetadata(data.data, resolved.descriptor)
9532
+ };
9533
+ return ok(ctx.present({
9534
+ data: result,
9535
+ diagnostics: previewDefaultDiagnostics(warnings)
9536
+ }, singlePresentations$1(result)));
9994
9537
  }
9995
9538
  });
9996
9539
  //#endregion
@@ -10047,36 +9590,25 @@ const projectEnvDeleteCommand = defineCommand({
10047
9590
  },
10048
9591
  needs: { credentials: true },
10049
9592
  handler: async (args, ctx) => {
10050
- try {
10051
- const key = args.positionals.key;
10052
- const scope = requireEnvScope(args.flags, "delete");
10053
- const { projectId, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env delete", false);
10054
- const existing = await findVariableByNaturalKey(ctx.api, projectId, key, resolved, ctx.signal);
10055
- if (!existing) throw new CliError({
10056
- code: "ENV_VARIABLE_NOT_FOUND",
10057
- domain: "app",
10058
- summary: `Variable "${key}" not found in ${formatScopeLabel(scope)}`,
10059
- why: "No variable with this key exists in the targeted scope, so there is nothing to delete.",
10060
- fix: "Run prisma project env list with the same scope to see the available variables.",
10061
- exitCode: 1,
10062
- nextSteps: [`prisma project env list ${formatScopeFlag$1(scope)}`]
10063
- });
10064
- const { error, response } = await ctx.api.DELETE("/v1/environment-variables/{envVarId}", {
10065
- params: { path: { envVarId: existing.id } },
10066
- signal: ctx.signal
10067
- });
10068
- if (error) throw apiCallError(`Failed to delete ${key}`, response, error);
10069
- const result = {
10070
- projectId,
10071
- scope: resolved.descriptor,
10072
- key
10073
- };
10074
- return ok(ctx.present({ data: result }, deletePresentations$1(result)));
10075
- } catch (error) {
10076
- const mapped = mapProjectOperationError(error);
10077
- if (mapped) return notOk(mapped);
10078
- throw error;
10079
- }
9593
+ const key = args.positionals.key;
9594
+ const scope = requireEnvScope(args.flags, "delete");
9595
+ const { projectId, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env delete", false);
9596
+ const existing = await findVariableByNaturalKey(ctx.api, projectId, key, resolved, ctx.signal);
9597
+ if (!existing) throw new CliStructuredError("PROJECT.ENV_VARIABLE_NOT_FOUND", `Variable "${key}" not found in ${formatScopeLabel(scope)}`, {
9598
+ why: "No variable with this key exists in the targeted scope, so there is nothing to delete.",
9599
+ nextActions: [userChoice("Run prisma project env list with the same scope to see the available variables."), runCommand(`prisma project env list ${formatScopeFlag$1(scope)}`)]
9600
+ });
9601
+ const { error, response } = await ctx.api.DELETE("/v1/environment-variables/{envVarId}", {
9602
+ params: { path: { envVarId: existing.id } },
9603
+ signal: ctx.signal
9604
+ });
9605
+ if (error) throw apiCallError(`Failed to delete ${key}`, response, error);
9606
+ const result = {
9607
+ projectId,
9608
+ scope: resolved.descriptor,
9609
+ key
9610
+ };
9611
+ return ok(ctx.present({ data: result }, deletePresentations$1(result)));
10080
9612
  }
10081
9613
  });
10082
9614
  //#endregion
@@ -10143,33 +9675,27 @@ const projectEnvListCommand = defineCommand({
10143
9675
  },
10144
9676
  needs: { credentials: true },
10145
9677
  handler: async (args, ctx) => {
10146
- try {
10147
- const explicit = resolveEnvScope({
10148
- roleName: args.flags.role,
10149
- branchName: args.flags.branch
10150
- }, {
10151
- requireExplicit: false,
10152
- command: "list"
10153
- });
10154
- const projectId = (await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), args.flags.project, "project env list")).project.id;
10155
- const resolved = await resolveListScopeToApi(ctx.api, projectId, explicit ?? void 0, { signal: ctx.signal });
10156
- const rows = resolved.kind === "scoped" ? await listVariables(ctx.api, projectId, {
10157
- scope: resolved.addScope,
10158
- descriptor: resolved.descriptor,
10159
- apiTarget: resolved.apiTarget
10160
- }, ctx.signal) : await listOverviewVariables(ctx.api, projectId, ctx.signal);
10161
- const result = {
10162
- projectId,
10163
- scope: resolved.descriptor,
10164
- target: resolved.target,
10165
- variables: rows.map((row) => toMetadata(row, resolved.descriptor))
10166
- };
10167
- return ok(ctx.present({ data: result }, listPresentations$2(result, formatScopeFlag$1(resolved.addScope))));
10168
- } catch (error) {
10169
- const mapped = mapProjectOperationError(error);
10170
- if (mapped) return notOk(mapped);
10171
- throw error;
10172
- }
9678
+ const explicit = resolveEnvScope({
9679
+ roleName: args.flags.role,
9680
+ branchName: args.flags.branch
9681
+ }, {
9682
+ requireExplicit: false,
9683
+ command: "list"
9684
+ });
9685
+ const projectId = (await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), args.flags.project, "project env list")).project.id;
9686
+ const resolved = await resolveListScopeToApi(ctx.api, projectId, explicit ?? void 0, { signal: ctx.signal });
9687
+ const rows = resolved.kind === "scoped" ? await listVariables(ctx.api, projectId, {
9688
+ scope: resolved.addScope,
9689
+ descriptor: resolved.descriptor,
9690
+ apiTarget: resolved.apiTarget
9691
+ }, ctx.signal) : await listOverviewVariables(ctx.api, projectId, ctx.signal);
9692
+ const result = {
9693
+ projectId,
9694
+ scope: resolved.descriptor,
9695
+ target: resolved.target,
9696
+ variables: rows.map((row) => toMetadata(row, resolved.descriptor))
9697
+ };
9698
+ return ok(ctx.present({ data: result }, listPresentations$2(result, formatScopeFlag$1(resolved.addScope))));
10173
9699
  }
10174
9700
  });
10175
9701
  //#endregion
@@ -10215,54 +9741,43 @@ const projectEnvUpdateCommand = defineCommand({
10215
9741
  },
10216
9742
  needs: { credentials: true },
10217
9743
  handler: async (args, ctx) => {
10218
- try {
10219
- const source = resolveEnvWriteSource(args.positionals.assignment, args.flags.file, "update");
10220
- const scope = requireEnvScope(args.flags, "update");
10221
- const input = await resolveEnvWriteInput(legacyOperationContext(ctx), source, "update");
10222
- const { projectId, verboseContext, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env update", false);
10223
- if (input.kind === "file") {
10224
- const written = await runEnvUpdateFile(legacyOperationContext(ctx), ctx.api, projectId, resolved, input.filePath, input.assignments, verboseContext);
10225
- const result = {
10226
- projectId,
10227
- scope: resolved.descriptor,
10228
- variables: written.result.variables,
10229
- file: written.result.file
10230
- };
10231
- return ok(ctx.present({ data: result }, fileWritePresentations({
10232
- title: "Replacing environment variable values from file.",
10233
- emptyMessage: "No environment variables updated.",
10234
- scope: result.scope,
10235
- filePath: result.file.path,
10236
- variables: result.variables
10237
- }, result)));
10238
- }
10239
- const existing = await findVariableByNaturalKey(ctx.api, projectId, input.key, resolved, ctx.signal);
10240
- if (!existing) throw new CliError({
10241
- code: "ENV_VARIABLE_NOT_FOUND",
10242
- domain: "app",
10243
- summary: `Variable "${input.key}" not found in ${formatScopeLabel(scope)}`,
10244
- why: "No variable with this key exists in the targeted scope.",
10245
- fix: "Use `prisma project env add` to create a new variable.",
10246
- exitCode: 1,
10247
- nextSteps: [`prisma project env add ${input.key}=<value> ${formatScopeFlag$1(scope)}`]
10248
- });
10249
- const { data, error, response } = await ctx.api.PATCH("/v1/environment-variables/{envVarId}", {
10250
- params: { path: { envVarId: existing.id } },
10251
- body: { value: input.value },
10252
- signal: ctx.signal
10253
- });
10254
- if (error || !data) throw apiCallError(`Failed to update value for ${input.key}`, response, error);
9744
+ const source = resolveEnvWriteSource(args.positionals.assignment, args.flags.file, "update");
9745
+ const scope = requireEnvScope(args.flags, "update");
9746
+ const input = await resolveEnvWriteInput(legacyOperationContext(ctx), source, "update");
9747
+ const { projectId, verboseContext, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env update", false);
9748
+ if (input.kind === "file") {
9749
+ const written = await runEnvUpdateFile(legacyOperationContext(ctx), ctx.api, projectId, resolved, input.filePath, input.assignments, verboseContext);
10255
9750
  const result = {
10256
9751
  projectId,
10257
9752
  scope: resolved.descriptor,
10258
- variable: toMetadata(data.data, resolved.descriptor)
9753
+ variables: written.result.variables,
9754
+ file: written.result.file
10259
9755
  };
10260
- return ok(ctx.present({ data: result }, singlePresentations(result)));
10261
- } catch (error) {
10262
- const mapped = mapProjectOperationError(error);
10263
- if (mapped) return notOk(mapped);
10264
- throw error;
10265
- }
9756
+ return ok(ctx.present({ data: result }, fileWritePresentations({
9757
+ title: "Replacing environment variable values from file.",
9758
+ emptyMessage: "No environment variables updated.",
9759
+ scope: result.scope,
9760
+ filePath: result.file.path,
9761
+ variables: result.variables
9762
+ }, result)));
9763
+ }
9764
+ const existing = await findVariableByNaturalKey(ctx.api, projectId, input.key, resolved, ctx.signal);
9765
+ if (!existing) throw new CliStructuredError("PROJECT.ENV_VARIABLE_NOT_FOUND", `Variable "${input.key}" not found in ${formatScopeLabel(scope)}`, {
9766
+ why: "No variable with this key exists in the targeted scope.",
9767
+ nextActions: [userChoice("Use `prisma project env add` to create a new variable."), runCommand(`prisma project env add ${input.key}=<value> ${formatScopeFlag$1(scope)}`)]
9768
+ });
9769
+ const { data, error, response } = await ctx.api.PATCH("/v1/environment-variables/{envVarId}", {
9770
+ params: { path: { envVarId: existing.id } },
9771
+ body: { value: input.value },
9772
+ signal: ctx.signal
9773
+ });
9774
+ if (error || !data) throw apiCallError(`Failed to update value for ${input.key}`, response, error);
9775
+ const result = {
9776
+ projectId,
9777
+ scope: resolved.descriptor,
9778
+ variable: toMetadata(data.data, resolved.descriptor)
9779
+ };
9780
+ return ok(ctx.present({ data: result }, singlePresentations(result)));
10266
9781
  }
10267
9782
  });
10268
9783
  //#endregion
@@ -10271,7 +9786,25 @@ const projectEnvUpdateCommand = defineCommand({
10271
9786
  const CREATE_CHOICE = "__create__";
10272
9787
  const CANCEL_CHOICE = "__cancel__";
10273
9788
  function setupCanceledError() {
10274
- return usageError("Project setup canceled", "Project link needs a Project before it can continue.", "Choose an existing Project or create a new one, then rerun project link.", ["prisma project link <id-or-name>", "prisma project create <name>"], "project");
9789
+ return new CliStructuredError("PROJECT.USAGE_ERROR", "Project setup canceled", {
9790
+ why: "Project link needs a Project before it can continue.",
9791
+ nextActions: [
9792
+ {
9793
+ kind: "user-choice",
9794
+ label: "Choose an existing Project or create a new one, then rerun project link."
9795
+ },
9796
+ {
9797
+ kind: "run-command",
9798
+ label: "prisma project link <id-or-name>",
9799
+ command: "prisma project link <id-or-name>"
9800
+ },
9801
+ {
9802
+ kind: "run-command",
9803
+ label: "prisma project create <name>",
9804
+ command: "prisma project create <name>"
9805
+ }
9806
+ ]
9807
+ });
10275
9808
  }
10276
9809
  function choiceOptions(projects) {
10277
9810
  const sorted = sortProjects(projects);
@@ -10356,14 +9889,8 @@ const projectLinkCommand = defineCommand({
10356
9889
  },
10357
9890
  needs: { credentials: true },
10358
9891
  handler: async (args, ctx) => {
10359
- try {
10360
- const result = await linkDirectoryToProject(ctx, args.positionals.project);
10361
- return ok(ctx.present({ data: result }, setupPresentations(result)));
10362
- } catch (error) {
10363
- const mapped = mapProjectOperationError(error);
10364
- if (mapped) return notOk(mapped);
10365
- throw error;
10366
- }
9892
+ const result = await linkDirectoryToProject(ctx, args.positionals.project);
9893
+ return ok(ctx.present({ data: result }, setupPresentations(result)));
10367
9894
  }
10368
9895
  });
10369
9896
  //#endregion
@@ -10390,10 +9917,10 @@ function projectStdoutRows(result) {
10390
9917
  }
10391
9918
  function nextActionsFor(result) {
10392
9919
  if (result.localBinding?.status === "linked") return [];
10393
- return toNextActions(buildProjectSetupNextActions({
9920
+ return buildProjectSetupNextActions({
10394
9921
  createCommand: `${CLI_NAME} project create <name>`,
10395
9922
  reason: result.localBinding?.status === "invalid" ? "This directory has an invalid local Project binding. Ask the user which Prisma Project to link before running Project-scoped commands." : "This directory is not linked to a Prisma Project. Project list shows available Projects, but none is selected for this directory."
10396
- }));
9923
+ });
10397
9924
  }
10398
9925
  function listPresentations$1(result) {
10399
9926
  const rows = projectRows(result);
@@ -10438,21 +9965,15 @@ const projectListCommand = defineCommand({
10438
9965
  },
10439
9966
  needs: { credentials: true },
10440
9967
  handler: async (_args, ctx) => {
10441
- try {
10442
- const workspace = await resolveActiveWorkspace(ctx);
10443
- const projects = sortProjects(await listWorkspaceProjects$1(ctx));
10444
- const localBinding = await readProjectListLocalBinding(ctx.cwd, projects, ctx.signal);
10445
- const result = {
10446
- workspace,
10447
- projects: projects.map(toProjectSummary),
10448
- localBinding
10449
- };
10450
- return ok(ctx.present({ data: result }, listPresentations$1(result)));
10451
- } catch (error) {
10452
- const mapped = mapProjectOperationError(error);
10453
- if (mapped) return notOk(mapped);
10454
- throw error;
10455
- }
9968
+ const workspace = await resolveActiveWorkspace(ctx);
9969
+ const projects = sortProjects(await listWorkspaceProjects$1(ctx));
9970
+ const localBinding = await readProjectListLocalBinding(ctx.cwd, projects, ctx.signal);
9971
+ const result = {
9972
+ workspace,
9973
+ projects: projects.map(toProjectSummary),
9974
+ localBinding
9975
+ };
9976
+ return ok(ctx.present({ data: result }, listPresentations$1(result)));
10456
9977
  }
10457
9978
  });
10458
9979
  //#endregion
@@ -10510,26 +10031,20 @@ const projectRenameCommand = defineCommand({
10510
10031
  },
10511
10032
  needs: { credentials: true },
10512
10033
  handler: async (args, ctx) => {
10513
- try {
10514
- const workspace = await resolveActiveWorkspace(ctx);
10515
- const name = args.positionals.name.trim();
10516
- if (!isValidProjectSetupName(name)) throw projectSetupNameRequiredError("project rename");
10517
- const target = await resolvePinnedProject(ctx, workspace, args.flags.project, "project rename");
10518
- const result = {
10519
- workspace,
10520
- project: await createManagementProjectProvider(ctx.api).renameProject({
10521
- projectId: target.project.id,
10522
- name,
10523
- signal: ctx.signal
10524
- }),
10525
- previousName: target.project.name
10526
- };
10527
- return ok(ctx.present({ data: result }, renamePresentations(result)));
10528
- } catch (error) {
10529
- const mapped = mapProjectOperationError(error);
10530
- if (mapped) return notOk(mapped);
10531
- throw error;
10532
- }
10034
+ const workspace = await resolveActiveWorkspace(ctx);
10035
+ const name = args.positionals.name.trim();
10036
+ if (!isValidProjectSetupName(name)) throw projectSetupNameRequiredError("project rename");
10037
+ const target = await resolvePinnedProject(ctx, workspace, args.flags.project, "project rename");
10038
+ const result = {
10039
+ workspace,
10040
+ project: await createManagementProjectProvider(ctx.api).renameProject({
10041
+ projectId: target.project.id,
10042
+ name,
10043
+ signal: ctx.signal
10044
+ }),
10045
+ previousName: target.project.name
10046
+ };
10047
+ return ok(ctx.present({ data: result }, renamePresentations(result)));
10533
10048
  }
10534
10049
  });
10535
10050
  //#endregion
@@ -10631,12 +10146,12 @@ function showPresentations$1(result, cwd, env) {
10631
10146
  rows
10632
10147
  }],
10633
10148
  stdout: () => stdoutFieldRows(result, cwd).map((row) => `${row.label}: ${row.value}`),
10634
- next: () => result.project === null ? toNextActions(buildProjectSetupNextActions({
10149
+ next: () => result.project === null ? buildProjectSetupNextActions({
10635
10150
  commandName: "project show",
10636
10151
  retryCommand: "prisma project show <id-or-name>",
10637
10152
  suggestedProjectName: result.suggestedProjectName,
10638
10153
  reason: "This directory is not linked to a Prisma Project. Package and directory names can suggest setup defaults, but they do not select a Project."
10639
- })) : []
10154
+ }) : []
10640
10155
  };
10641
10156
  }
10642
10157
  const projectShowCommand = defineCommand({
@@ -10650,23 +10165,17 @@ const projectShowCommand = defineCommand({
10650
10165
  },
10651
10166
  needs: { credentials: true },
10652
10167
  handler: async (args, ctx) => {
10653
- try {
10654
- const workspace = await resolveActiveWorkspace(ctx);
10655
- const inspected = await inspectProjectBinding({
10656
- context: legacyOperationContext(ctx),
10657
- workspace,
10658
- explicitProject: args.positionals.project,
10659
- listProjects: () => listWorkspaceProjects$1(ctx),
10660
- commandName: "project show"
10661
- });
10662
- if (inspected.isErr()) throw projectResolutionErrorToCliError(inspected.error);
10663
- const result = inspected.value;
10664
- return ok(ctx.present({ data: result }, showPresentations$1(result, ctx.cwd, ctx.env)));
10665
- } catch (error) {
10666
- const mapped = mapProjectOperationError(error);
10667
- if (mapped) return notOk(mapped);
10668
- throw error;
10669
- }
10168
+ const workspace = await resolveActiveWorkspace(ctx);
10169
+ const inspected = await inspectProjectBinding({
10170
+ context: legacyOperationContext(ctx),
10171
+ workspace,
10172
+ explicitProject: args.positionals.project,
10173
+ listProjects: () => listWorkspaceProjects$1(ctx),
10174
+ commandName: "project show"
10175
+ });
10176
+ if (inspected.isErr()) throw projectResolutionErrorToStructured(inspected.error);
10177
+ const result = inspected.value;
10178
+ return ok(ctx.present({ data: result }, showPresentations$1(result, ctx.cwd, ctx.env)));
10670
10179
  }
10671
10180
  });
10672
10181
  //#endregion
@@ -10850,11 +10359,11 @@ const projectTransferCommand = defineCommand({
10850
10359
  },
10851
10360
  needs: { credentials: true },
10852
10361
  handler: async (args, ctx) => {
10853
- try {
10854
- const workspace = await resolveActiveWorkspace(ctx);
10855
- const toWorkspace = args.flags.toWorkspace?.trim() || void 0;
10856
- const recipientToken = args.flags.recipientToken?.trim() || void 0;
10857
- if (toWorkspace && recipientToken) throw usageError("Choose one transfer recipient source", "--to-workspace and --recipient-token are mutually exclusive.", "Pass either --to-workspace <id-or-name> or --recipient-token <token>.", [formatCommand([
10362
+ const workspace = await resolveActiveWorkspace(ctx);
10363
+ const toWorkspace = args.flags.toWorkspace?.trim() || void 0;
10364
+ const recipientToken = args.flags.recipientToken?.trim() || void 0;
10365
+ if (toWorkspace && recipientToken) {
10366
+ const retry = formatCommand([
10858
10367
  "project",
10859
10368
  "transfer",
10860
10369
  "<project>",
@@ -10862,42 +10371,49 @@ const projectTransferCommand = defineCommand({
10862
10371
  "<id-or-name>",
10863
10372
  "--confirm",
10864
10373
  "<project-id>"
10865
- ])], "project");
10866
- if (!toWorkspace && !recipientToken) throw transferRecipientRequiredError(formatCommand);
10867
- const projects = await listWorkspaceProjects$1(ctx);
10868
- const project = toProjectSummary(resolveProjectForSetup(args.positionals.project.trim(), projects, workspace));
10869
- await ctx.prompt.consent(CONSENT_QUESTION, { token: project.id });
10870
- const recipient = await resolveRecipient(ctx, {
10871
- toWorkspace,
10872
- recipientToken
10873
- });
10874
- await createManagementProjectProvider(ctx.api).transferProject({
10875
- projectId: project.id,
10876
- recipientAccessToken: recipient.accessToken,
10877
- signal: ctx.signal
10374
+ ]);
10375
+ throw new CliStructuredError("PROJECT.USAGE_ERROR", "Choose one transfer recipient source", {
10376
+ why: "--to-workspace and --recipient-token are mutually exclusive.",
10377
+ nextActions: [{
10378
+ kind: "user-choice",
10379
+ label: "Pass either --to-workspace <id-or-name> or --recipient-token <token>."
10380
+ }, {
10381
+ kind: "run-command",
10382
+ label: retry,
10383
+ command: retry
10384
+ }]
10878
10385
  });
10879
- const warnings = [];
10880
- const action = await rewriteOrClearLocalPinForProject(legacyOperationContext(ctx), project.id, recipient.workspaceId, { onError: (message) => warnings.push(message) });
10881
- const result = {
10882
- workspace,
10883
- project,
10884
- recipient: {
10885
- workspaceId: recipient.workspaceId,
10886
- workspaceName: recipient.workspaceName,
10887
- source: recipient.source
10888
- },
10889
- localPin: { action }
10890
- };
10891
- const diagnostics = localPinDiagnostics(warnings);
10892
- return ok(ctx.present({
10893
- data: result,
10894
- diagnostics
10895
- }, transferPresentations(result, toWorkspace)));
10896
- } catch (error) {
10897
- const mapped = mapProjectOperationError(error);
10898
- if (mapped) return notOk(mapped);
10899
- throw error;
10900
10386
  }
10387
+ if (!toWorkspace && !recipientToken) throw transferRecipientRequiredError(formatCommand);
10388
+ const projects = await listWorkspaceProjects$1(ctx);
10389
+ const project = toProjectSummary(resolveProjectForSetup(args.positionals.project.trim(), projects, workspace));
10390
+ await ctx.prompt.consent(CONSENT_QUESTION, { token: project.id });
10391
+ const recipient = await resolveRecipient(ctx, {
10392
+ toWorkspace,
10393
+ recipientToken
10394
+ });
10395
+ await createManagementProjectProvider(ctx.api).transferProject({
10396
+ projectId: project.id,
10397
+ recipientAccessToken: recipient.accessToken,
10398
+ signal: ctx.signal
10399
+ });
10400
+ const warnings = [];
10401
+ const action = await rewriteOrClearLocalPinForProject(legacyOperationContext(ctx), project.id, recipient.workspaceId, { onError: (message) => warnings.push(message) });
10402
+ const result = {
10403
+ workspace,
10404
+ project,
10405
+ recipient: {
10406
+ workspaceId: recipient.workspaceId,
10407
+ workspaceName: recipient.workspaceName,
10408
+ source: recipient.source
10409
+ },
10410
+ localPin: { action }
10411
+ };
10412
+ const diagnostics = localPinDiagnostics(warnings);
10413
+ return ok(ctx.present({
10414
+ data: result,
10415
+ diagnostics
10416
+ }, transferPresentations(result, toWorkspace)));
10901
10417
  }
10902
10418
  });
10903
10419
  //#endregion
@@ -10928,40 +10444,9 @@ function adviceAction(label) {
10928
10444
  label
10929
10445
  };
10930
10446
  }
10931
- function toEngineNextAction(action) {
10932
- return {
10933
- kind: action.kind,
10934
- label: action.label,
10935
- ...action.command !== void 0 ? { command: action.command } : {},
10936
- ...action.commands !== void 0 ? { commands: action.commands } : {},
10937
- ...action.reason !== void 0 ? { reason: action.reason } : {}
10938
- };
10939
- }
10940
10447
  const CNAME_HINT = /\bcname(?:s)?\s+to\b/;
10941
10448
  const PRISMA_BUILD_HOST = /\b((?:[a-z0-9-]+\.)+prisma\.build)\b/i;
10942
10449
  /**
10943
- * Maps a legacy CliError onto the engine error protocol: the flat code
10944
- * becomes `SERVICE.<code>`, the free-text fix becomes a user-choice
10945
- * action carried alongside any typed legacy actions, and each nextSteps
10946
- * command line becomes a run-command action. Copy passes through
10947
- * unchanged: the producers write the commands a user types today.
10948
- */
10949
- function fromLegacyCliError(error) {
10950
- const fixAction = error.fix ? [adviceAction(error.fix)] : [];
10951
- const nextActions = error.nextActions.length > 0 ? [...error.nextActions.map(toEngineNextAction), ...fixAction] : [...fixAction, ...error.nextSteps.map((step) => ({
10952
- kind: "run-command",
10953
- label: "Run",
10954
- command: step
10955
- }))];
10956
- return new CliStructuredError(`SERVICE.${error.code}`, error.summary, {
10957
- ...error.why ? { why: error.why } : {},
10958
- nextActions,
10959
- ...error.where ? { where: { path: error.where } } : {},
10960
- ...Object.keys(error.meta).length > 0 ? { meta: error.meta } : {},
10961
- ...error.docsUrl ? { docsUrl: error.docsUrl } : {}
10962
- });
10963
- }
10964
- /**
10965
10450
  * Consent declined interactively. The engine settles this code as a
10966
10451
  * user cancellation (exit 3).
10967
10452
  */
@@ -11826,7 +11311,7 @@ function toBranchKind(name) {
11826
11311
  */
11827
11312
  async function listWorkspaceProjects(ctx) {
11828
11313
  const { data, error, response } = await ctx.api.GET("/v1/projects", { signal: ctx.signal });
11829
- if (error || !data) throw fromLegacyCliError(projectApiError("Failed to list projects", response, error));
11314
+ if (error || !data) throw projectApiError("Failed to list projects", response, error);
11830
11315
  return sortProjects((data.data ?? []).map((project) => ({
11831
11316
  id: project.id,
11832
11317
  name: project.name,
@@ -11855,7 +11340,7 @@ async function resolveServiceProjectContext(ctx, explicitProject, options) {
11855
11340
  listProjects: () => Promise.resolve(projects),
11856
11341
  commandName: options.commandName
11857
11342
  });
11858
- if (resolvedResult.isErr()) throw fromLegacyCliError(projectResolutionErrorToCliError(resolvedResult.error));
11343
+ if (resolvedResult.isErr()) throw projectResolutionErrorToStructured(resolvedResult.error);
11859
11344
  const resolved = resolvedResult.value;
11860
11345
  const requested = options.branchName ? {
11861
11346
  name: options.branchName,
@@ -13374,6 +12859,7 @@ const serviceVersionStopCommand = defineCommand({
13374
12859
  * part of either product's. */
13375
12860
  const skillsCommandFamily = defineCommandFamily({
13376
12861
  configSection: skillsConfigSection,
12862
+ docsBaseUrl: DOCS_ERRORS_BASE_URL,
13377
12863
  commands: {
13378
12864
  sync: skillsSyncCommand,
13379
12865
  list: defineCommand({
@@ -13417,63 +12903,66 @@ const skillsCommandFamily = defineCommandFamily({
13417
12903
  });
13418
12904
  //#endregion
13419
12905
  //#region src/cli.ts
13420
- const platformCommandFamily = defineCommandFamily({ commands: {
13421
- login: authLoginCommand,
13422
- logout: authLogoutCommand,
13423
- whoami: authWhoamiCommand,
13424
- workspaceList: authWorkspaceListCommand,
13425
- workspaceUse: authWorkspaceUseCommand,
13426
- workspaceLogout: authWorkspaceLogoutCommand,
13427
- projectList: projectListCommand,
13428
- projectShow: projectShowCommand,
13429
- projectCreate: projectCreateCommand,
13430
- projectLink: projectLinkCommand,
13431
- projectRename: projectRenameCommand,
13432
- projectDelete: projectDeleteCommand,
13433
- projectTransfer: projectTransferCommand,
13434
- projectEnvAdd: projectEnvAddCommand,
13435
- projectEnvUpdate: projectEnvUpdateCommand,
13436
- projectEnvList: projectEnvListCommand,
13437
- projectEnvDelete: projectEnvDeleteCommand,
13438
- postgresList: postgresListCommand,
13439
- postgresShow: postgresShowCommand,
13440
- postgresCreate: postgresCreateCommand,
13441
- postgresUsage: postgresUsageCommand,
13442
- postgresBackupRestore: postgresBackupRestoreCommand,
13443
- postgresDelete: postgresDeleteCommand,
13444
- postgresBackupList: postgresBackupListCommand,
13445
- postgresConnectionList: postgresConnectionListCommand,
13446
- postgresConnectionCreate: postgresConnectionCreateCommand,
13447
- postgresConnectionRotate: postgresConnectionRotateCommand,
13448
- postgresConnectionDelete: postgresConnectionDeleteCommand,
13449
- bucketList: bucketListCommand,
13450
- bucketCreate: bucketCreateCommand,
13451
- bucketDelete: bucketDeleteCommand,
13452
- bucketKeyList: bucketKeyListCommand,
13453
- bucketKeyCreate: bucketKeyCreateCommand,
13454
- bucketKeyDelete: bucketKeyDeleteCommand,
13455
- branchList: branchListCommand,
13456
- gitConnect: gitConnectCommand,
13457
- gitDisconnect: gitDisconnectCommand,
13458
- serviceList: serviceListCommand,
13459
- serviceLogs: serviceLogsCommand,
13460
- serviceCreate: serviceCreateCommand,
13461
- serviceShow: serviceShowCommand,
13462
- serviceOpen: serviceOpenCommand,
13463
- serviceVersionList: serviceVersionListCommand,
13464
- serviceVersionShow: serviceVersionShowCommand,
13465
- serviceVersionPromote: serviceVersionPromoteCommand,
13466
- serviceVersionRollback: serviceVersionRollbackCommand,
13467
- serviceVersionStart: serviceVersionStartCommand,
13468
- serviceVersionStop: serviceVersionStopCommand,
13469
- serviceVersionDelete: serviceVersionDeleteCommand,
13470
- serviceDelete: serviceDeleteCommand,
13471
- serviceDomainAdd: serviceDomainAddCommand,
13472
- serviceDomainShow: serviceDomainShowCommand,
13473
- serviceDomainDelete: serviceDomainDeleteCommand,
13474
- serviceDomainRetry: serviceDomainRetryCommand,
13475
- serviceDomainWait: serviceDomainWaitCommand
13476
- } });
12906
+ const platformCommandFamily = defineCommandFamily({
12907
+ docsBaseUrl: DOCS_ERRORS_BASE_URL,
12908
+ commands: {
12909
+ login: authLoginCommand,
12910
+ logout: authLogoutCommand,
12911
+ whoami: authWhoamiCommand,
12912
+ workspaceList: authWorkspaceListCommand,
12913
+ workspaceUse: authWorkspaceUseCommand,
12914
+ workspaceLogout: authWorkspaceLogoutCommand,
12915
+ projectList: projectListCommand,
12916
+ projectShow: projectShowCommand,
12917
+ projectCreate: projectCreateCommand,
12918
+ projectLink: projectLinkCommand,
12919
+ projectRename: projectRenameCommand,
12920
+ projectDelete: projectDeleteCommand,
12921
+ projectTransfer: projectTransferCommand,
12922
+ projectEnvAdd: projectEnvAddCommand,
12923
+ projectEnvUpdate: projectEnvUpdateCommand,
12924
+ projectEnvList: projectEnvListCommand,
12925
+ projectEnvDelete: projectEnvDeleteCommand,
12926
+ postgresList: postgresListCommand,
12927
+ postgresShow: postgresShowCommand,
12928
+ postgresCreate: postgresCreateCommand,
12929
+ postgresUsage: postgresUsageCommand,
12930
+ postgresBackupRestore: postgresBackupRestoreCommand,
12931
+ postgresDelete: postgresDeleteCommand,
12932
+ postgresBackupList: postgresBackupListCommand,
12933
+ postgresConnectionList: postgresConnectionListCommand,
12934
+ postgresConnectionCreate: postgresConnectionCreateCommand,
12935
+ postgresConnectionRotate: postgresConnectionRotateCommand,
12936
+ postgresConnectionDelete: postgresConnectionDeleteCommand,
12937
+ bucketList: bucketListCommand,
12938
+ bucketCreate: bucketCreateCommand,
12939
+ bucketDelete: bucketDeleteCommand,
12940
+ bucketKeyList: bucketKeyListCommand,
12941
+ bucketKeyCreate: bucketKeyCreateCommand,
12942
+ bucketKeyDelete: bucketKeyDeleteCommand,
12943
+ branchList: branchListCommand,
12944
+ gitConnect: gitConnectCommand,
12945
+ gitDisconnect: gitDisconnectCommand,
12946
+ serviceList: serviceListCommand,
12947
+ serviceLogs: serviceLogsCommand,
12948
+ serviceCreate: serviceCreateCommand,
12949
+ serviceShow: serviceShowCommand,
12950
+ serviceOpen: serviceOpenCommand,
12951
+ serviceVersionList: serviceVersionListCommand,
12952
+ serviceVersionShow: serviceVersionShowCommand,
12953
+ serviceVersionPromote: serviceVersionPromoteCommand,
12954
+ serviceVersionRollback: serviceVersionRollbackCommand,
12955
+ serviceVersionStart: serviceVersionStartCommand,
12956
+ serviceVersionStop: serviceVersionStopCommand,
12957
+ serviceVersionDelete: serviceVersionDeleteCommand,
12958
+ serviceDelete: serviceDeleteCommand,
12959
+ serviceDomainAdd: serviceDomainAddCommand,
12960
+ serviceDomainShow: serviceDomainShowCommand,
12961
+ serviceDomainDelete: serviceDomainDeleteCommand,
12962
+ serviceDomainRetry: serviceDomainRetryCommand,
12963
+ serviceDomainWait: serviceDomainWaitCommand
12964
+ }
12965
+ });
13477
12966
  /**
13478
12967
  * Composer's commands, contributed by composer's own package and run by
13479
12968
  * this process, mounted as shipped. Only the command definitions and