@prisma/cli 3.0.0-beta.3 → 3.0.0-beta.30

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 (99) hide show
  1. package/README.md +6 -15
  2. package/dist/adapters/local-state.js +15 -4
  3. package/dist/adapters/mock-api.js +244 -0
  4. package/dist/adapters/token-storage.js +335 -34
  5. package/dist/cli.js +7 -7
  6. package/dist/cli2.js +24 -5
  7. package/dist/commands/agent/index.js +60 -0
  8. package/dist/commands/app/index.js +91 -61
  9. package/dist/commands/auth/index.js +55 -2
  10. package/dist/commands/branch/index.js +2 -27
  11. package/dist/commands/bucket/index.js +123 -0
  12. package/dist/commands/build/index.js +29 -0
  13. package/dist/commands/database/index.js +249 -0
  14. package/dist/commands/env.js +8 -4
  15. package/dist/commands/feedback/index.js +20 -0
  16. package/dist/commands/git/index.js +1 -1
  17. package/dist/commands/init/index.js +33 -0
  18. package/dist/commands/project/index.js +54 -5
  19. package/dist/controllers/agent-setup.js +52 -0
  20. package/dist/controllers/agent.js +228 -0
  21. package/dist/controllers/app-env-api.js +55 -0
  22. package/dist/controllers/app-env-file.js +181 -0
  23. package/dist/controllers/app-env.js +227 -104
  24. package/dist/controllers/app.js +746 -306
  25. package/dist/controllers/auth.js +247 -3
  26. package/dist/controllers/branch.js +78 -48
  27. package/dist/controllers/bucket.js +278 -0
  28. package/dist/controllers/build.js +88 -0
  29. package/dist/controllers/database.js +567 -0
  30. package/dist/controllers/feedback.js +86 -0
  31. package/dist/controllers/init.js +753 -0
  32. package/dist/controllers/project.js +377 -22
  33. package/dist/controllers/select-prompt-port.js +1 -0
  34. package/dist/lib/agent/cli-command.js +20 -0
  35. package/dist/lib/agent/constants.js +12 -0
  36. package/dist/lib/agent/package-manager.js +99 -0
  37. package/dist/lib/agent/setup-status.js +83 -0
  38. package/dist/lib/app/{preview-provider.js → app-provider.js} +137 -88
  39. package/dist/lib/app/branch-database-api.js +102 -0
  40. package/dist/lib/app/branch-database-deploy.js +326 -0
  41. package/dist/lib/app/branch-database.js +216 -0
  42. package/dist/lib/app/build-settings.js +93 -0
  43. package/dist/lib/app/build.js +83 -0
  44. package/dist/lib/app/bun-project.js +3 -4
  45. package/dist/lib/app/compute-config.js +145 -0
  46. package/dist/lib/app/deploy-plan.js +59 -0
  47. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
  48. package/dist/lib/app/env-config.js +1 -1
  49. package/dist/lib/app/env-file.js +82 -0
  50. package/dist/lib/app/env-vars.js +28 -2
  51. package/dist/lib/app/local-dev.js +3 -60
  52. package/dist/lib/app/production-deploy-gate.js +162 -0
  53. package/dist/lib/app/read-branch.js +30 -0
  54. package/dist/lib/auth/auth-ops.js +10 -4
  55. package/dist/lib/auth/guard.js +4 -1
  56. package/dist/lib/auth/login.js +33 -26
  57. package/dist/lib/auth/recipient.js +42 -0
  58. package/dist/lib/bucket/provider.js +139 -0
  59. package/dist/lib/database/provider.js +378 -0
  60. package/dist/lib/diagnostics.js +15 -0
  61. package/dist/lib/fs/home-path.js +24 -0
  62. package/dist/lib/git/local-branch.js +53 -0
  63. package/dist/lib/git/local-status.js +57 -0
  64. package/dist/lib/project/interactive-setup.js +5 -4
  65. package/dist/lib/project/local-pin.js +171 -41
  66. package/dist/lib/project/provider.js +92 -0
  67. package/dist/lib/project/resolution.js +199 -48
  68. package/dist/lib/project/setup.js +67 -20
  69. package/dist/output/patterns.js +1 -1
  70. package/dist/presenters/agent.js +74 -0
  71. package/dist/presenters/app-env.js +149 -14
  72. package/dist/presenters/app.js +208 -27
  73. package/dist/presenters/auth.js +99 -2
  74. package/dist/presenters/branch.js +37 -102
  75. package/dist/presenters/bucket.js +174 -0
  76. package/dist/presenters/database.js +448 -0
  77. package/dist/presenters/feedback.js +26 -0
  78. package/dist/presenters/init.js +30 -0
  79. package/dist/presenters/project.js +139 -27
  80. package/dist/presenters/verbose-context.js +64 -0
  81. package/dist/shell/cli-command.js +12 -0
  82. package/dist/shell/command-arguments.js +7 -1
  83. package/dist/shell/command-meta.js +458 -17
  84. package/dist/shell/command-runner.js +58 -18
  85. package/dist/shell/diagnostics-output.js +57 -0
  86. package/dist/shell/errors.js +56 -1
  87. package/dist/shell/help.js +31 -20
  88. package/dist/shell/output.js +72 -1
  89. package/dist/shell/prompt.js +12 -5
  90. package/dist/shell/runtime.js +8 -4
  91. package/dist/shell/ui.js +42 -3
  92. package/dist/shell/update-check.js +2 -2
  93. package/dist/use-cases/auth.js +68 -1
  94. package/dist/use-cases/branch.js +20 -68
  95. package/dist/use-cases/create-cli-gateways.js +2 -17
  96. package/dist/use-cases/project.js +2 -1
  97. package/package.json +21 -4
  98. package/dist/lib/app/preview-build.js +0 -312
  99. package/dist/lib/app/preview-interaction.js +0 -5
@@ -1,62 +1,192 @@
1
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
1
  import path from "node:path";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { Result, TaggedError, UnhandledException } from "better-result";
3
4
  //#region src/lib/project/local-pin.ts
4
5
  const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
5
- async function readLocalResolutionPin(cwd, signal) {
6
- signal?.throwIfAborted();
7
- try {
8
- const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
9
- encoding: "utf8",
10
- signal
6
+ var LocalResolutionPinInvalidJsonError = class extends TaggedError("LocalResolutionPinInvalidJsonError")() {
7
+ constructor(cause) {
8
+ super({
9
+ message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} contains invalid JSON.`,
10
+ cause,
11
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
12
+ });
13
+ }
14
+ };
15
+ var LocalResolutionPinInvalidShapeError = class extends TaggedError("LocalResolutionPinInvalidShapeError")() {
16
+ constructor() {
17
+ super({
18
+ message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} must contain workspaceId and projectId string fields only.`,
19
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
20
+ });
21
+ }
22
+ };
23
+ var LocalResolutionPinReadAbortedError = class extends TaggedError("LocalResolutionPinReadAbortedError")() {
24
+ constructor(cause) {
25
+ super({
26
+ message: `Reading ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
27
+ cause,
28
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
29
+ });
30
+ }
31
+ };
32
+ var LocalResolutionPinSerializationError = class extends TaggedError("LocalResolutionPinSerializationError")() {
33
+ constructor(cause) {
34
+ super({
35
+ message: `Could not serialize ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
36
+ cause,
37
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
38
+ });
39
+ }
40
+ };
41
+ var LocalResolutionPinWriteAbortedError = class extends TaggedError("LocalResolutionPinWriteAbortedError")() {
42
+ constructor(cause) {
43
+ super({
44
+ message: `Writing ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
45
+ cause,
46
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
47
+ });
48
+ }
49
+ };
50
+ var LocalResolutionPinWriteFailedError = class extends TaggedError("LocalResolutionPinWriteFailedError")() {
51
+ constructor(operation, cause) {
52
+ super({
53
+ message: `Could not write ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
54
+ cause,
55
+ operation,
56
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
57
+ });
58
+ }
59
+ };
60
+ var LocalResolutionPinGitignoreUpdateAbortedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateAbortedError")() {
61
+ constructor(cause) {
62
+ super({
63
+ message: "Updating .gitignore for the local Project binding was aborted.",
64
+ cause,
65
+ gitignorePath: ".gitignore"
66
+ });
67
+ }
68
+ };
69
+ var LocalResolutionPinGitignoreUpdateFailedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateFailedError")() {
70
+ constructor(operation, cause) {
71
+ super({
72
+ message: "Could not update .gitignore for the local Project binding.",
73
+ cause,
74
+ operation,
75
+ gitignorePath: ".gitignore"
11
76
  });
12
- const parsed = JSON.parse(raw);
13
- if (!isLocalResolutionPin(parsed)) return { kind: "invalid" };
14
- return {
77
+ }
78
+ };
79
+ async function readLocalResolutionPin(cwd, signal) {
80
+ return Result.gen(async function* () {
81
+ yield* ensureLocalResolutionPinReadNotAborted(signal);
82
+ const file = yield* Result.await(readLocalResolutionPinFile(cwd, signal));
83
+ if (file.kind === "missing") return Result.ok({ kind: "missing" });
84
+ const parsed = yield* parseLocalResolutionPin(file.raw);
85
+ if (!isLocalResolutionPin(parsed)) return Result.err(new LocalResolutionPinInvalidShapeError());
86
+ return Result.ok({
15
87
  kind: "present",
16
88
  pin: parsed
17
- };
18
- } catch (error) {
19
- if (error.code === "ENOENT") return { kind: "missing" };
20
- if (error instanceof SyntaxError) return { kind: "invalid" };
21
- throw error;
89
+ });
90
+ });
91
+ }
92
+ function ensureLocalResolutionPinReadNotAborted(signal) {
93
+ return Result.try({
94
+ try: () => signal?.throwIfAborted(),
95
+ catch: (cause) => new LocalResolutionPinReadAbortedError(cause)
96
+ });
97
+ }
98
+ async function readLocalResolutionPinFile(cwd, signal) {
99
+ const readResult = await Result.tryPromise({
100
+ try: () => readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
101
+ encoding: "utf8",
102
+ signal
103
+ }),
104
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinReadAbortedError(cause) : new UnhandledException({ cause })
105
+ });
106
+ if (readResult.isErr()) {
107
+ if (readResult.error instanceof UnhandledException && readResult.error.cause.code === "ENOENT") return Result.ok({ kind: "missing" });
108
+ return Result.err(readResult.error);
22
109
  }
110
+ return Result.ok({
111
+ kind: "present",
112
+ raw: readResult.value
113
+ });
114
+ }
115
+ function parseLocalResolutionPin(raw) {
116
+ return Result.try({
117
+ try: () => JSON.parse(raw),
118
+ catch: (cause) => cause instanceof SyntaxError ? new LocalResolutionPinInvalidJsonError(cause) : new UnhandledException({ cause })
119
+ });
23
120
  }
24
121
  async function writeLocalResolutionPin(cwd, pin, signal) {
25
- const prismaDir = path.join(cwd, ".prisma");
26
- signal?.throwIfAborted();
27
- await mkdir(prismaDir, { recursive: true });
28
- const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
29
- const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
30
- await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, {
31
- encoding: "utf8",
32
- signal
122
+ return Result.gen(async function* () {
123
+ const prismaDir = path.join(cwd, ".prisma");
124
+ yield* ensureLocalResolutionPinWriteNotAborted(signal);
125
+ yield* Result.await(writeLocalResolutionPinBoundary(() => mkdir(prismaDir, { recursive: true }), "create-directory", signal));
126
+ const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
127
+ const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
128
+ const serialized = yield* serializeLocalResolutionPin(pin);
129
+ yield* Result.await(writeLocalResolutionPinBoundary(() => writeFile(tmpPath, serialized, {
130
+ encoding: "utf8",
131
+ signal
132
+ }), "write-temp-file", signal));
133
+ yield* ensureLocalResolutionPinWriteNotAborted(signal);
134
+ yield* Result.await(writeLocalResolutionPinBoundary(() => rename(tmpPath, pinPath), "rename-temp-file", signal));
135
+ return Result.ok(void 0);
33
136
  });
34
- signal?.throwIfAborted();
35
- await rename(tmpPath, pinPath);
36
137
  }
37
138
  async function ensureLocalResolutionPinGitignore(cwd, signal) {
38
139
  const gitignorePath = path.join(cwd, ".gitignore");
39
140
  let existing = null;
40
- signal?.throwIfAborted();
41
- try {
42
- existing = await readFile(gitignorePath, {
141
+ const notAborted = ensureLocalResolutionPinGitignoreUpdateNotAborted(signal);
142
+ if (notAborted.isErr()) return Result.err(notAborted.error);
143
+ const existingResult = await Result.tryPromise({
144
+ try: () => readFile(gitignorePath, {
43
145
  encoding: "utf8",
44
146
  signal
45
- });
46
- } catch (error) {
47
- if (error.code !== "ENOENT") throw error;
48
- }
49
- if (existing === null) {
50
- await writeFile(gitignorePath, ".prisma/\n", {
147
+ }),
148
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("read", cause)
149
+ });
150
+ if (existingResult.isErr()) if (existingResult.error instanceof LocalResolutionPinGitignoreUpdateFailedError && existingResult.error.cause.code === "ENOENT") existing = null;
151
+ else return Result.err(existingResult.error);
152
+ else existing = existingResult.value;
153
+ if (existing === null) return writeLocalResolutionPinGitignore(gitignorePath, ".prisma/\n", signal);
154
+ if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return Result.ok(void 0);
155
+ return writeLocalResolutionPinGitignore(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, signal);
156
+ }
157
+ function ensureLocalResolutionPinWriteNotAborted(signal) {
158
+ return Result.try({
159
+ try: () => signal?.throwIfAborted(),
160
+ catch: (cause) => new LocalResolutionPinWriteAbortedError(cause)
161
+ });
162
+ }
163
+ function serializeLocalResolutionPin(pin) {
164
+ return Result.try({
165
+ try: () => `${JSON.stringify(pin, null, 2)}\n`,
166
+ catch: (cause) => new LocalResolutionPinSerializationError(cause)
167
+ });
168
+ }
169
+ function writeLocalResolutionPinBoundary(run, operation, signal) {
170
+ return Result.tryPromise({
171
+ try: async () => {
172
+ await run();
173
+ },
174
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinWriteAbortedError(cause) : new LocalResolutionPinWriteFailedError(operation, cause)
175
+ });
176
+ }
177
+ function ensureLocalResolutionPinGitignoreUpdateNotAborted(signal) {
178
+ return Result.try({
179
+ try: () => signal?.throwIfAborted(),
180
+ catch: (cause) => new LocalResolutionPinGitignoreUpdateAbortedError(cause)
181
+ });
182
+ }
183
+ function writeLocalResolutionPinGitignore(gitignorePath, contents, signal) {
184
+ return Result.tryPromise({
185
+ try: () => writeFile(gitignorePath, contents, {
51
186
  encoding: "utf8",
52
187
  signal
53
- });
54
- return;
55
- }
56
- if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
57
- await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, {
58
- encoding: "utf8",
59
- signal
188
+ }),
189
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("write", cause)
60
190
  });
61
191
  }
62
192
  function isLocalResolutionPin(value) {
@@ -0,0 +1,92 @@
1
+ import { formatPrismaCliCommand } from "../../shell/cli-command.js";
2
+ import { CliError } from "../../shell/errors.js";
3
+ //#region src/lib/project/provider.ts
4
+ function createManagementProjectProvider(client) {
5
+ return {
6
+ async renameProject(options) {
7
+ const result = await client.PATCH("/v1/projects/{id}", {
8
+ params: { path: { id: options.projectId } },
9
+ body: { name: options.name },
10
+ signal: options.signal
11
+ });
12
+ const status = result.response?.status ?? 0;
13
+ if (status === 400 || status === 422) throw projectRenameFailedError(options.name, result.error);
14
+ if (result.error || !result.data) throw projectApiError("Failed to rename project", result.response, result.error);
15
+ const project = result.data.data;
16
+ return {
17
+ id: project.id,
18
+ name: project.name,
19
+ ...project.url ? { url: project.url } : {}
20
+ };
21
+ },
22
+ async removeProject(options) {
23
+ const result = await client.DELETE("/v1/projects/{id}", {
24
+ params: { path: { id: options.projectId } },
25
+ signal: options.signal
26
+ });
27
+ if (result.response?.status === 400) throw projectRemoveBlockedError(options.projectId, result.error);
28
+ if (result.error) throw projectApiError("Failed to remove project", result.response, result.error);
29
+ },
30
+ async transferProject(options) {
31
+ const result = await client.POST("/v1/projects/{id}/transfer", {
32
+ params: { path: { id: options.projectId } },
33
+ body: { recipientAccessToken: options.recipientAccessToken },
34
+ signal: options.signal
35
+ });
36
+ if (result.response?.status === 400) throw projectTransferRejectedError(options.projectId, result.error);
37
+ if (result.error) throw projectApiError("Failed to transfer project", result.response, result.error);
38
+ }
39
+ };
40
+ }
41
+ function projectRenameFailedError(name, error) {
42
+ return new CliError({
43
+ code: "PROJECT_RENAME_FAILED",
44
+ domain: "project",
45
+ summary: "Project rename failed",
46
+ why: error?.error?.message ?? `The platform rejected the name "${name}".`,
47
+ fix: error?.error?.hint ?? "Pass a different project name and retry the rename.",
48
+ exitCode: 1,
49
+ nextSteps: []
50
+ });
51
+ }
52
+ function projectRemoveBlockedError(projectId, error) {
53
+ return new CliError({
54
+ code: "PROJECT_REMOVE_BLOCKED",
55
+ domain: "project",
56
+ summary: "Project cannot be removed yet",
57
+ why: error?.error?.message ?? `Project "${projectId}" still has active deployments.`,
58
+ fix: "Remove the project's apps first, then retry the removal.",
59
+ exitCode: 1,
60
+ nextSteps: [formatPrismaCliCommand([
61
+ "app",
62
+ "remove",
63
+ "--app",
64
+ "<name>"
65
+ ])]
66
+ });
67
+ }
68
+ function projectTransferRejectedError(projectId, error) {
69
+ return new CliError({
70
+ code: "PROJECT_TRANSFER_REJECTED",
71
+ domain: "project",
72
+ summary: "Project transfer was rejected",
73
+ why: error?.error?.message ?? `The platform rejected the transfer of project "${projectId}", for example because the recipient token is invalid or expired.`,
74
+ fix: "Check the recipient workspace session or token and retry the transfer.",
75
+ exitCode: 1,
76
+ nextSteps: []
77
+ });
78
+ }
79
+ function projectApiError(summary, response, error) {
80
+ const status = response?.status ?? 0;
81
+ return new CliError({
82
+ code: error?.error?.code ?? "PROJECT_API_ERROR",
83
+ domain: "project",
84
+ summary,
85
+ why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
86
+ fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
87
+ exitCode: 1,
88
+ nextSteps: []
89
+ });
90
+ }
91
+ //#endregion
92
+ export { createManagementProjectProvider, projectRemoveBlockedError, projectRenameFailedError, projectTransferRejectedError };
@@ -1,38 +1,100 @@
1
- import { CliError } from "../../shell/errors.js";
2
1
  import { formatCommandArgument } from "../../shell/command-arguments.js";
2
+ import { CliError } from "../../shell/errors.js";
3
3
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
4
- import { readFile } from "node:fs/promises";
5
4
  import path from "node:path";
5
+ import { readFile } from "node:fs/promises";
6
+ import { Result, TaggedError, matchError } from "better-result";
6
7
  //#region src/lib/project/resolution.ts
8
+ var ProjectNotFoundError = class extends TaggedError("ProjectNotFoundError")() {
9
+ constructor(projectRef, workspace) {
10
+ super({
11
+ message: `Project "${projectRef}" was not found in workspace "${workspace.name}".`,
12
+ projectRef,
13
+ workspace
14
+ });
15
+ }
16
+ };
17
+ var ProjectAmbiguousError = class extends TaggedError("ProjectAmbiguousError")() {
18
+ constructor(projectRef, matches) {
19
+ super({
20
+ message: projectRef ? `Multiple projects matched "${projectRef}".` : "Multiple projects matched the current directory context.",
21
+ projectRef,
22
+ matches
23
+ });
24
+ }
25
+ };
26
+ var LocalStateStaleError = class extends TaggedError("LocalStateStaleError")() {
27
+ constructor() {
28
+ super({
29
+ message: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
30
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
31
+ });
32
+ }
33
+ };
34
+ var LocalProjectWorkspaceMismatchError = class extends TaggedError("LocalProjectWorkspaceMismatchError")() {
35
+ constructor(options) {
36
+ super({
37
+ message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but the active workspace is "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
38
+ pinnedWorkspaceId: options.pinnedWorkspaceId,
39
+ pinnedProjectId: options.pinnedProjectId,
40
+ activeWorkspace: options.activeWorkspace
41
+ });
42
+ }
43
+ };
44
+ var ProjectSetupRequiredError = class extends TaggedError("ProjectSetupRequiredError")() {
45
+ constructor(options) {
46
+ const commandLabel = options.commandName ? `prisma-cli ${options.commandName}` : "this command";
47
+ super({
48
+ message: `This directory is not linked to a Prisma Project, and ${commandLabel} will not choose one from package or directory names.`,
49
+ commandName: options.commandName,
50
+ suggestion: options.suggestion
51
+ });
52
+ }
53
+ };
7
54
  async function resolveProjectTarget(options) {
8
- const projects = await options.listProjects();
9
- const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: true });
10
- if (target) return target;
11
- throw await projectSetupRequiredError({
12
- cwd: options.context.runtime.cwd,
13
- projects,
14
- commandName: options.commandName,
15
- signal: options.context.runtime.signal
16
- });
17
- }
18
- async function inspectProjectBinding(options) {
19
- const projects = await options.listProjects();
20
- const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
21
- if (target) return target;
22
- return {
23
- workspace: options.workspace,
24
- project: null,
25
- localBinding: { status: "not-linked" },
26
- resolution: { projectSource: "unbound" },
27
- ...await buildProjectSetupSuggestion({
55
+ return Result.gen(async function* () {
56
+ const localPin = yield* Result.await(readImplicitLocalPin(options, { allowEnvProjectId: true }));
57
+ const projects = await options.listProjects();
58
+ const target = yield* Result.await(resolveBoundProjectTarget(options, projects, {
59
+ allowEnvProjectId: true,
60
+ localPin
61
+ }));
62
+ if (target) return Result.ok(target);
63
+ return Result.err(await projectSetupRequiredError({
28
64
  cwd: options.context.runtime.cwd,
29
65
  projects,
30
- commandName: options.commandName ?? "project show",
66
+ commandName: options.commandName,
31
67
  signal: options.context.runtime.signal
32
- })
33
- };
68
+ }));
69
+ });
70
+ }
71
+ async function inspectProjectBinding(options) {
72
+ return Result.gen(async function* () {
73
+ const localPin = yield* Result.await(readImplicitLocalPin(options, { allowEnvProjectId: false }));
74
+ const projects = await options.listProjects();
75
+ const target = yield* Result.await(resolveBoundProjectTarget(options, projects, {
76
+ allowEnvProjectId: false,
77
+ localPin
78
+ }));
79
+ if (target) return Result.ok(target);
80
+ return Result.ok({
81
+ workspace: options.workspace,
82
+ project: null,
83
+ localBinding: { status: "not-linked" },
84
+ resolution: { projectSource: "unbound" },
85
+ ...await buildProjectSetupSuggestion({
86
+ cwd: options.context.runtime.cwd,
87
+ projects,
88
+ commandName: options.commandName ?? "project show",
89
+ signal: options.context.runtime.signal
90
+ })
91
+ });
92
+ });
34
93
  }
35
94
  function projectNotFoundError(projectRef, workspace) {
95
+ return projectResolutionErrorToCliError(new ProjectNotFoundError(projectRef, workspace));
96
+ }
97
+ function projectNotFoundCliError(projectRef, workspace) {
36
98
  return new CliError({
37
99
  code: "PROJECT_NOT_FOUND",
38
100
  domain: "project",
@@ -44,6 +106,9 @@ function projectNotFoundError(projectRef, workspace) {
44
106
  });
45
107
  }
46
108
  function projectAmbiguousError(projectRef, matches) {
109
+ return projectResolutionErrorToCliError(new ProjectAmbiguousError(projectRef, matches));
110
+ }
111
+ function projectAmbiguousCliError(projectRef, matches) {
47
112
  const firstMatch = matches[0];
48
113
  const nextSteps = ["prisma-cli project list"];
49
114
  if (firstMatch) nextSteps.push(`prisma-cli app deploy --project ${firstMatch.id}`);
@@ -61,7 +126,7 @@ function projectAmbiguousError(projectRef, matches) {
61
126
  nextSteps
62
127
  });
63
128
  }
64
- function localStateStaleError() {
129
+ function localStateStaleCliError() {
65
130
  return new CliError({
66
131
  code: "LOCAL_STATE_STALE",
67
132
  domain: "project",
@@ -73,6 +138,56 @@ function localStateStaleError() {
73
138
  nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
74
139
  });
75
140
  }
141
+ function localProjectWorkspaceMismatchError(options) {
142
+ return projectResolutionErrorToCliError(new LocalProjectWorkspaceMismatchError(options));
143
+ }
144
+ function localProjectWorkspaceMismatchCliError(options) {
145
+ return new CliError({
146
+ code: "LOCAL_PROJECT_WORKSPACE_MISMATCH",
147
+ domain: "project",
148
+ summary: "Project link uses another workspace",
149
+ 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}).`,
150
+ fix: "Switch to the linked workspace, or relink this directory to a project in the current workspace.",
151
+ meta: {
152
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
153
+ pinnedWorkspaceId: options.pinnedWorkspaceId,
154
+ pinnedProjectId: options.pinnedProjectId,
155
+ activeWorkspaceId: options.activeWorkspace.id,
156
+ activeWorkspaceName: options.activeWorkspace.name
157
+ },
158
+ exitCode: 1,
159
+ nextSteps: [
160
+ `prisma-cli auth workspace use ${options.pinnedWorkspaceId}`,
161
+ "prisma-cli project list",
162
+ "prisma-cli project link <id-or-name>"
163
+ ]
164
+ });
165
+ }
166
+ /**
167
+ * Converts expected project-resolution variants to command-boundary CliErrors.
168
+ * `LocalResolutionPinReadAbortedError` and `UnhandledException` intentionally
169
+ * propagate as exceptions; callers such as `resolveProjectShowInRealMode`
170
+ * throw this helper's result, so passthrough variants should keep bubbling.
171
+ */
172
+ function projectResolutionErrorToCliError(error) {
173
+ return matchError(error, {
174
+ ProjectNotFoundError: (error) => projectNotFoundCliError(error.projectRef, error.workspace),
175
+ ProjectAmbiguousError: (error) => projectAmbiguousCliError(error.projectRef, error.matches),
176
+ ProjectSetupRequiredError: (error) => projectSetupRequiredCliError(error),
177
+ LocalStateStaleError: () => localStateStaleCliError(),
178
+ LocalProjectWorkspaceMismatchError: (error) => localProjectWorkspaceMismatchCliError({
179
+ pinnedWorkspaceId: error.pinnedWorkspaceId,
180
+ pinnedProjectId: error.pinnedProjectId,
181
+ activeWorkspace: error.activeWorkspace
182
+ }),
183
+ LocalResolutionPinReadAbortedError: (error) => {
184
+ throw error;
185
+ },
186
+ UnhandledException: (error) => {
187
+ throw error;
188
+ }
189
+ });
190
+ }
76
191
  async function buildProjectSetupSuggestion(options) {
77
192
  const suggestedName = await inferTargetName(options.cwd, options.signal);
78
193
  const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
@@ -85,17 +200,24 @@ async function buildProjectSetupSuggestion(options) {
85
200
  }
86
201
  async function projectSetupRequiredError(options) {
87
202
  const suggestion = await buildProjectSetupSuggestion(options);
203
+ return new ProjectSetupRequiredError({
204
+ commandName: options.commandName,
205
+ suggestion
206
+ });
207
+ }
208
+ function projectSetupRequiredCliError(error) {
209
+ const suggestion = error.suggestion;
88
210
  return new CliError({
89
211
  code: "PROJECT_SETUP_REQUIRED",
90
212
  domain: "project",
91
213
  summary: "Choose a Project before running this command",
92
- why: `This directory is not linked to a Prisma Project, and ${options.commandName ? `prisma-cli ${options.commandName}` : "this command"} will not choose one from package or directory names.`,
214
+ why: error.message,
93
215
  fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
94
216
  meta: { ...suggestion },
95
217
  exitCode: 1,
96
218
  nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
97
219
  nextActions: buildProjectSetupNextActions({
98
- commandName: options.commandName,
220
+ commandName: error.commandName,
99
221
  suggestedProjectName: suggestion.suggestedProjectName
100
222
  })
101
223
  });
@@ -169,9 +291,9 @@ function sortProjects(projects) {
169
291
  }
170
292
  function resolveExplicitProject(projectRef, projects, workspace) {
171
293
  const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
172
- if (matches.length === 1) return matches[0];
173
- if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
174
- throw projectNotFoundError(projectRef, workspace);
294
+ if (matches.length === 1) return Result.ok(matches[0]);
295
+ if (matches.length > 1) return Result.err(new ProjectAmbiguousError(projectRef, matches));
296
+ return Result.err(new ProjectNotFoundError(projectRef, workspace));
175
297
  }
176
298
  function projectMatchesSuggestedName(project, suggestedName) {
177
299
  return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
@@ -180,35 +302,63 @@ async function resolveDurablePlatformMapping() {
180
302
  return null;
181
303
  }
182
304
  async function resolveBoundProjectTarget(options, projects, settings) {
183
- if (options.explicitProject) return resolvedTarget(options.workspace, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
184
- targetName: options.explicitProject,
185
- targetNameSource: "explicit"
186
- });
305
+ if (options.explicitProject) {
306
+ const projectResult = resolveExplicitProject(options.explicitProject, projects, options.workspace);
307
+ if (projectResult.isErr()) return Result.err(projectResult.error);
308
+ return Result.ok(resolvedTarget(options.workspace, projectResult.value, "explicit", {
309
+ targetName: options.explicitProject,
310
+ targetNameSource: "explicit"
311
+ }));
312
+ }
187
313
  if (settings.allowEnvProjectId && options.envProjectId) {
188
314
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
189
- if (!project) throw projectNotFoundError(options.envProjectId, options.workspace);
190
- return resolvedTarget(options.workspace, project, "env", {
315
+ if (!project) return Result.err(new ProjectNotFoundError(options.envProjectId, options.workspace));
316
+ return Result.ok(resolvedTarget(options.workspace, project, "env", {
191
317
  targetName: options.envProjectId,
192
318
  targetNameSource: "env"
193
- });
319
+ }));
194
320
  }
195
- const localPin = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
196
- if (localPin.kind === "invalid") throw localStateStaleError();
321
+ const localPin = settings.localPin;
322
+ if (!localPin) return Result.ok(null);
197
323
  if (localPin.kind === "present") {
198
- if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
324
+ if (localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
325
+ pinnedWorkspaceId: localPin.pin.workspaceId,
326
+ pinnedProjectId: localPin.pin.projectId,
327
+ activeWorkspace: options.workspace
328
+ }));
199
329
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
200
- if (!project) throw localStateStaleError();
201
- return resolvedTarget(options.workspace, project, "local-pin", {
330
+ if (!project) return Result.err(new LocalStateStaleError());
331
+ return Result.ok(resolvedTarget(options.workspace, project, "local-pin", {
202
332
  targetName: project.name,
203
333
  targetNameSource: "local-pin"
204
- });
334
+ }));
205
335
  }
206
336
  const platformMapping = await resolveDurablePlatformMapping();
207
- if (platformMapping && platformMapping.workspace.id === options.workspace.id) return resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
337
+ if (platformMapping && platformMapping.workspace.id === options.workspace.id) return Result.ok(resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
208
338
  targetName: platformMapping.name,
209
339
  targetNameSource: "platform-mapping"
340
+ }));
341
+ return Result.ok(null);
342
+ }
343
+ async function readImplicitLocalPin(options, settings) {
344
+ if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return Result.ok(null);
345
+ const localPinResult = await readLocalResolutionPin(options.projectDir ?? options.context.runtime.cwd, options.context.runtime.signal);
346
+ if (localPinResult.isErr()) return Result.err(localPinReadErrorToProjectError(localPinResult.error));
347
+ const localPin = localPinResult.value;
348
+ if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
349
+ pinnedWorkspaceId: localPin.pin.workspaceId,
350
+ pinnedProjectId: localPin.pin.projectId,
351
+ activeWorkspace: options.workspace
352
+ }));
353
+ return Result.ok(localPin);
354
+ }
355
+ function localPinReadErrorToProjectError(error) {
356
+ return matchError(error, {
357
+ LocalResolutionPinInvalidJsonError: () => new LocalStateStaleError(),
358
+ LocalResolutionPinInvalidShapeError: () => new LocalStateStaleError(),
359
+ LocalResolutionPinReadAbortedError: (error) => error,
360
+ UnhandledException: (error) => error
210
361
  });
211
- return null;
212
362
  }
213
363
  function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
214
364
  return {
@@ -229,8 +379,9 @@ function toProjectSummary(project) {
229
379
  return {
230
380
  id: project.id,
231
381
  name: project.name,
232
- ...project.url ? { url: project.url } : {}
382
+ ...project.url ? { url: project.url } : {},
383
+ ...project.defaultRegion != null ? { defaultRegion: project.defaultRegion } : {}
233
384
  };
234
385
  }
235
386
  //#endregion
236
- export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
387
+ export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, localProjectWorkspaceMismatchError, projectAmbiguousError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };