@prisma/cli 3.0.0-beta.9 → 3.0.0-dev.102.1

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 (53) hide show
  1. package/README.md +0 -13
  2. package/dist/adapters/token-storage.js +311 -33
  3. package/dist/cli.js +7 -7
  4. package/dist/cli2.js +3 -3
  5. package/dist/commands/app/index.js +65 -52
  6. package/dist/commands/auth/index.js +55 -2
  7. package/dist/controllers/app-env.js +10 -7
  8. package/dist/controllers/app.js +539 -210
  9. package/dist/controllers/auth.js +211 -2
  10. package/dist/controllers/branch.js +5 -6
  11. package/dist/controllers/database.js +7 -5
  12. package/dist/controllers/project.js +33 -18
  13. package/dist/lib/app/app-interaction.js +5 -0
  14. package/dist/lib/app/{preview-provider.js → app-provider.js} +82 -78
  15. package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
  16. package/dist/lib/app/branch-database-deploy.js +12 -60
  17. package/dist/lib/app/branch-database.js +1 -102
  18. package/dist/lib/app/build-settings.js +93 -0
  19. package/dist/lib/app/build.js +82 -0
  20. package/dist/lib/app/bun-project.js +2 -3
  21. package/dist/lib/app/compute-config.js +147 -0
  22. package/dist/lib/app/deploy-plan.js +58 -0
  23. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
  24. package/dist/lib/app/local-dev.js +3 -60
  25. package/dist/lib/app/production-deploy-gate.js +3 -3
  26. package/dist/lib/app/read-branch.js +30 -0
  27. package/dist/lib/auth/auth-ops.js +10 -4
  28. package/dist/lib/auth/guard.js +4 -1
  29. package/dist/lib/auth/login.js +32 -25
  30. package/dist/lib/diagnostics.js +1 -1
  31. package/dist/lib/fs/home-path.js +24 -0
  32. package/dist/lib/git/local-branch.js +15 -3
  33. package/dist/lib/project/local-pin.js +106 -26
  34. package/dist/lib/project/resolution.js +162 -71
  35. package/dist/lib/project/setup.js +65 -19
  36. package/dist/output/patterns.js +1 -1
  37. package/dist/presenters/app.js +44 -39
  38. package/dist/presenters/auth.js +98 -1
  39. package/dist/presenters/branch.js +1 -1
  40. package/dist/presenters/database.js +2 -2
  41. package/dist/presenters/project.js +3 -9
  42. package/dist/shell/command-meta.js +52 -2
  43. package/dist/shell/command-runner.js +39 -28
  44. package/dist/shell/diagnostics-output.js +2 -8
  45. package/dist/shell/errors.js +50 -1
  46. package/dist/shell/help.js +30 -20
  47. package/dist/shell/runtime.js +8 -4
  48. package/dist/shell/ui.js +19 -2
  49. package/dist/use-cases/auth.js +68 -1
  50. package/package.json +18 -4
  51. package/dist/lib/app/preview-build-settings.js +0 -385
  52. package/dist/lib/app/preview-build.js +0 -475
  53. package/dist/lib/app/preview-interaction.js +0 -5
@@ -106,35 +106,42 @@ async function consumePastedCallback(options) {
106
106
  });
107
107
  try {
108
108
  for (;;) {
109
- let answer;
110
- try {
111
- answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
112
- } catch (error) {
113
- if (error?.name === "AbortError") return;
114
- throw error;
115
- }
116
- const trimmed = answer.trim().replace(/^["']|["']$/g, "");
117
- let url;
118
- try {
119
- if (!trimmed) throw new Error("empty input");
120
- url = new URL(trimmed);
121
- } catch {
122
- options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
123
- continue;
124
- }
125
- try {
126
- await options.complete(url);
127
- return;
128
- } catch (error) {
129
- const message = error instanceof Error ? error.message : String(error);
130
- options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
131
- continue;
132
- }
109
+ const url = await readPastedCallbackUrl(rl, options);
110
+ if (url === null) return;
111
+ if (url === void 0) continue;
112
+ if (await tryCompletePastedCallback(url, options)) return;
133
113
  }
134
114
  } finally {
135
115
  rl.close();
136
116
  }
137
117
  }
118
+ async function readPastedCallbackUrl(rl, options) {
119
+ let answer;
120
+ try {
121
+ answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
122
+ } catch (error) {
123
+ if (error?.name === "AbortError") return null;
124
+ throw error;
125
+ }
126
+ const trimmed = answer.trim().replace(/^["']|["']$/g, "");
127
+ try {
128
+ if (!trimmed) throw new Error("empty input");
129
+ return new URL(trimmed);
130
+ } catch {
131
+ options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
132
+ return;
133
+ }
134
+ }
135
+ async function tryCompletePastedCallback(url, options) {
136
+ try {
137
+ await options.complete(url);
138
+ return true;
139
+ } catch (error) {
140
+ const message = error instanceof Error ? error.message : String(error);
141
+ options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
142
+ return false;
143
+ }
144
+ }
138
145
  var LoginState = class {
139
146
  latestVerifier;
140
147
  latestState;
@@ -144,7 +151,7 @@ var LoginState = class {
144
151
  output;
145
152
  constructor(options) {
146
153
  this.options = options;
147
- this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal);
154
+ this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal, { activateOnSetTokens: true });
148
155
  this.sdk = createManagementApiSdk({
149
156
  clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
150
157
  redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
@@ -3,7 +3,7 @@ import { resolveStateDir } from "../shell/runtime.js";
3
3
  import { readLocalGitState } from "./git/local-status.js";
4
4
  //#region src/lib/diagnostics.ts
5
5
  async function collectCommandDiagnostics(context, options = {}) {
6
- const stateDir = resolveStateDir(context.runtime);
6
+ const stateDir = await resolveStateDir(context.runtime);
7
7
  return {
8
8
  cwd: context.runtime.cwd,
9
9
  stateFilePath: resolveLocalStateFilePath(stateDir),
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ //#region src/lib/fs/home-path.ts
3
+ /**
4
+ * Shortens a path under the user's home directory to `~/...` for display,
5
+ * posix-style on every platform. Falls back to the Windows home variables
6
+ * when `HOME` is unset (native cmd/PowerShell sessions).
7
+ */
8
+ function shortenHomePath(value, env) {
9
+ const resolved = path.resolve(value);
10
+ const home = resolveHomeDirectory(env);
11
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
12
+ const relative = path.relative(home, resolved).split(path.sep).join("/");
13
+ return relative ? `~/${relative}` : "~";
14
+ }
15
+ return resolved;
16
+ }
17
+ function resolveHomeDirectory(env) {
18
+ if (env.HOME) return path.resolve(env.HOME);
19
+ if (env.USERPROFILE) return path.resolve(env.USERPROFILE);
20
+ if (env.HOMEDRIVE && env.HOMEPATH) return path.resolve(`${env.HOMEDRIVE}${env.HOMEPATH}`);
21
+ return null;
22
+ }
23
+ //#endregion
24
+ export { shortenHomePath };
@@ -1,9 +1,22 @@
1
1
  import { access, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  //#region src/lib/git/local-branch.ts
4
+ /**
5
+ * Resolves the checked-out branch the way git does: the nearest `.git`
6
+ * (directory or worktree file) from `cwd` upward owns the answer, so
7
+ * monorepo commands run from inside a package see the repository branch.
8
+ * Returns null for detached HEAD or when no repository contains `cwd`.
9
+ */
4
10
  async function readLocalGitBranch(cwd, signal) {
5
- const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
6
- if (!headPath) return null;
11
+ for (let directory = path.resolve(cwd);;) {
12
+ const headPath = await resolveGitHeadPath(path.join(directory, ".git"), signal);
13
+ if (headPath) return readBranchFromHead(headPath, signal);
14
+ const parent = path.dirname(directory);
15
+ if (parent === directory) return null;
16
+ directory = parent;
17
+ }
18
+ }
19
+ async function readBranchFromHead(headPath, signal) {
7
20
  try {
8
21
  const head = (await readFile(headPath, {
9
22
  encoding: "utf8",
@@ -12,7 +25,6 @@ async function readLocalGitBranch(cwd, signal) {
12
25
  if (head.startsWith("ref: refs/heads/")) return head.slice(16);
13
26
  } catch (error) {
14
27
  if (signal.aborted) throw error;
15
- return null;
16
28
  }
17
29
  return null;
18
30
  }
@@ -29,6 +29,53 @@ var LocalResolutionPinReadAbortedError = class extends TaggedError("LocalResolut
29
29
  });
30
30
  }
31
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"
76
+ });
77
+ }
78
+ };
32
79
  async function readLocalResolutionPin(cwd, signal) {
33
80
  return Result.gen(async function* () {
34
81
  yield* ensureLocalResolutionPinReadNotAborted(signal);
@@ -72,41 +119,74 @@ function parseLocalResolutionPin(raw) {
72
119
  });
73
120
  }
74
121
  async function writeLocalResolutionPin(cwd, pin, signal) {
75
- const prismaDir = path.join(cwd, ".prisma");
76
- signal?.throwIfAborted();
77
- await mkdir(prismaDir, { recursive: true });
78
- const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
79
- const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
80
- await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, {
81
- encoding: "utf8",
82
- 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);
83
136
  });
84
- signal?.throwIfAborted();
85
- await rename(tmpPath, pinPath);
86
137
  }
87
138
  async function ensureLocalResolutionPinGitignore(cwd, signal) {
88
139
  const gitignorePath = path.join(cwd, ".gitignore");
89
140
  let existing = null;
90
- signal?.throwIfAborted();
91
- try {
92
- 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, {
93
145
  encoding: "utf8",
94
146
  signal
95
- });
96
- } catch (error) {
97
- if (error.code !== "ENOENT") throw error;
98
- }
99
- if (existing === null) {
100
- 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, {
101
186
  encoding: "utf8",
102
187
  signal
103
- });
104
- return;
105
- }
106
- if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
107
- await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, {
108
- encoding: "utf8",
109
- signal
188
+ }),
189
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("write", cause)
110
190
  });
111
191
  }
112
192
  function isLocalResolutionPin(value) {
@@ -1,47 +1,100 @@
1
1
  import { CliError } from "../../shell/errors.js";
2
- import { formatCommandArgument } from "../../shell/command-arguments.js";
3
2
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
3
+ import { formatCommandArgument } from "../../shell/command-arguments.js";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
- import { matchError } from "better-result";
6
+ import { Result, TaggedError, matchError } from "better-result";
7
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
+ };
8
54
  async function resolveProjectTarget(options) {
9
- const localPin = await readImplicitLocalPin(options, { allowEnvProjectId: true });
10
- const projects = await options.listProjects();
11
- const target = await resolveBoundProjectTarget(options, projects, {
12
- allowEnvProjectId: true,
13
- localPin
14
- });
15
- if (target) return target;
16
- throw await projectSetupRequiredError({
17
- cwd: options.context.runtime.cwd,
18
- projects,
19
- commandName: options.commandName,
20
- signal: options.context.runtime.signal
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({
64
+ cwd: options.context.runtime.cwd,
65
+ projects,
66
+ commandName: options.commandName,
67
+ signal: options.context.runtime.signal
68
+ }));
21
69
  });
22
70
  }
23
71
  async function inspectProjectBinding(options) {
24
- const localPin = await readImplicitLocalPin(options, { allowEnvProjectId: false });
25
- const projects = await options.listProjects();
26
- const target = await resolveBoundProjectTarget(options, projects, {
27
- allowEnvProjectId: false,
28
- localPin
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
+ });
29
92
  });
30
- if (target) return target;
31
- return {
32
- workspace: options.workspace,
33
- project: null,
34
- localBinding: { status: "not-linked" },
35
- resolution: { projectSource: "unbound" },
36
- ...await buildProjectSetupSuggestion({
37
- cwd: options.context.runtime.cwd,
38
- projects,
39
- commandName: options.commandName ?? "project show",
40
- signal: options.context.runtime.signal
41
- })
42
- };
43
93
  }
44
94
  function projectNotFoundError(projectRef, workspace) {
95
+ return projectResolutionErrorToCliError(new ProjectNotFoundError(projectRef, workspace));
96
+ }
97
+ function projectNotFoundCliError(projectRef, workspace) {
45
98
  return new CliError({
46
99
  code: "PROJECT_NOT_FOUND",
47
100
  domain: "project",
@@ -53,6 +106,9 @@ function projectNotFoundError(projectRef, workspace) {
53
106
  });
54
107
  }
55
108
  function projectAmbiguousError(projectRef, matches) {
109
+ return projectResolutionErrorToCliError(new ProjectAmbiguousError(projectRef, matches));
110
+ }
111
+ function projectAmbiguousCliError(projectRef, matches) {
56
112
  const firstMatch = matches[0];
57
113
  const nextSteps = ["prisma-cli project list"];
58
114
  if (firstMatch) nextSteps.push(`prisma-cli app deploy --project ${firstMatch.id}`);
@@ -70,7 +126,7 @@ function projectAmbiguousError(projectRef, matches) {
70
126
  nextSteps
71
127
  });
72
128
  }
73
- function localStateStaleError() {
129
+ function localStateStaleCliError() {
74
130
  return new CliError({
75
131
  code: "LOCAL_STATE_STALE",
76
132
  domain: "project",
@@ -83,12 +139,15 @@ function localStateStaleError() {
83
139
  });
84
140
  }
85
141
  function localProjectWorkspaceMismatchError(options) {
142
+ return projectResolutionErrorToCliError(new LocalProjectWorkspaceMismatchError(options));
143
+ }
144
+ function localProjectWorkspaceMismatchCliError(options) {
86
145
  return new CliError({
87
146
  code: "LOCAL_PROJECT_WORKSPACE_MISMATCH",
88
147
  domain: "project",
89
148
  summary: "Project link uses another workspace",
90
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}).`,
91
- fix: "Sign in to the linked workspace, or relink this directory to a project in the current workspace.",
150
+ fix: "Switch to the linked workspace, or relink this directory to a project in the current workspace.",
92
151
  meta: {
93
152
  pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
94
153
  pinnedWorkspaceId: options.pinnedWorkspaceId,
@@ -98,12 +157,37 @@ function localProjectWorkspaceMismatchError(options) {
98
157
  },
99
158
  exitCode: 1,
100
159
  nextSteps: [
101
- "prisma-cli auth login",
160
+ `prisma-cli auth workspace use ${options.pinnedWorkspaceId}`,
102
161
  "prisma-cli project list",
103
162
  "prisma-cli project link <id-or-name>"
104
163
  ]
105
164
  });
106
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
+ }
107
191
  async function buildProjectSetupSuggestion(options) {
108
192
  const suggestedName = await inferTargetName(options.cwd, options.signal);
109
193
  const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
@@ -116,17 +200,24 @@ async function buildProjectSetupSuggestion(options) {
116
200
  }
117
201
  async function projectSetupRequiredError(options) {
118
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;
119
210
  return new CliError({
120
211
  code: "PROJECT_SETUP_REQUIRED",
121
212
  domain: "project",
122
213
  summary: "Choose a Project before running this command",
123
- 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,
124
215
  fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
125
216
  meta: { ...suggestion },
126
217
  exitCode: 1,
127
218
  nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
128
219
  nextActions: buildProjectSetupNextActions({
129
- commandName: options.commandName,
220
+ commandName: error.commandName,
130
221
  suggestedProjectName: suggestion.suggestedProjectName
131
222
  })
132
223
  });
@@ -200,9 +291,9 @@ function sortProjects(projects) {
200
291
  }
201
292
  function resolveExplicitProject(projectRef, projects, workspace) {
202
293
  const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
203
- if (matches.length === 1) return matches[0];
204
- if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
205
- 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));
206
297
  }
207
298
  function projectMatchesSuggestedName(project, suggestedName) {
208
299
  return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
@@ -211,62 +302,62 @@ async function resolveDurablePlatformMapping() {
211
302
  return null;
212
303
  }
213
304
  async function resolveBoundProjectTarget(options, projects, settings) {
214
- if (options.explicitProject) return resolvedTarget(options.workspace, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
215
- targetName: options.explicitProject,
216
- targetNameSource: "explicit"
217
- });
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
+ }
218
313
  if (settings.allowEnvProjectId && options.envProjectId) {
219
314
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
220
- if (!project) throw projectNotFoundError(options.envProjectId, options.workspace);
221
- 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", {
222
317
  targetName: options.envProjectId,
223
318
  targetNameSource: "env"
224
- });
319
+ }));
225
320
  }
226
321
  const localPin = settings.localPin;
227
- if (!localPin) return null;
322
+ if (!localPin) return Result.ok(null);
228
323
  if (localPin.kind === "present") {
229
- if (localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
324
+ if (localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
230
325
  pinnedWorkspaceId: localPin.pin.workspaceId,
231
326
  pinnedProjectId: localPin.pin.projectId,
232
327
  activeWorkspace: options.workspace
233
- });
328
+ }));
234
329
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
235
- if (!project) throw localStateStaleError();
236
- 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", {
237
332
  targetName: project.name,
238
333
  targetNameSource: "local-pin"
239
- });
334
+ }));
240
335
  }
241
336
  const platformMapping = await resolveDurablePlatformMapping();
242
- 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", {
243
338
  targetName: platformMapping.name,
244
339
  targetNameSource: "platform-mapping"
245
- });
246
- return null;
340
+ }));
341
+ return Result.ok(null);
247
342
  }
248
343
  async function readImplicitLocalPin(options, settings) {
249
- if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return null;
250
- const localPinResult = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
251
- if (localPinResult.isErr()) throw localPinReadErrorToProjectError(localPinResult.error);
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));
252
347
  const localPin = localPinResult.value;
253
- if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
348
+ if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
254
349
  pinnedWorkspaceId: localPin.pin.workspaceId,
255
350
  pinnedProjectId: localPin.pin.projectId,
256
351
  activeWorkspace: options.workspace
257
- });
258
- return localPin;
352
+ }));
353
+ return Result.ok(localPin);
259
354
  }
260
355
  function localPinReadErrorToProjectError(error) {
261
356
  return matchError(error, {
262
- LocalResolutionPinInvalidJsonError: () => localStateStaleError(),
263
- LocalResolutionPinInvalidShapeError: () => localStateStaleError(),
264
- LocalResolutionPinReadAbortedError: (error) => {
265
- throw error;
266
- },
267
- UnhandledException: (error) => {
268
- throw error;
269
- }
357
+ LocalResolutionPinInvalidJsonError: () => new LocalStateStaleError(),
358
+ LocalResolutionPinInvalidShapeError: () => new LocalStateStaleError(),
359
+ LocalResolutionPinReadAbortedError: (error) => error,
360
+ UnhandledException: (error) => error
270
361
  });
271
362
  }
272
363
  function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
@@ -292,4 +383,4 @@ function toProjectSummary(project) {
292
383
  };
293
384
  }
294
385
  //#endregion
295
- export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
386
+ export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, localProjectWorkspaceMismatchError, projectAmbiguousError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };