@prisma/cli 3.0.0-beta.2 → 3.0.0-beta.21

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 (87) hide show
  1. package/README.md +5 -16
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +26 -7
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +344 -25
  6. package/dist/cli.js +18 -3
  7. package/dist/cli2.js +12 -4
  8. package/dist/commands/agent/index.js +60 -0
  9. package/dist/commands/app/index.js +90 -61
  10. package/dist/commands/auth/index.js +55 -2
  11. package/dist/commands/branch/index.js +2 -27
  12. package/dist/commands/build/index.js +29 -0
  13. package/dist/commands/database/index.js +159 -0
  14. package/dist/commands/env.js +8 -4
  15. package/dist/commands/git/index.js +1 -1
  16. package/dist/commands/project/index.js +1 -1
  17. package/dist/controllers/agent.js +228 -0
  18. package/dist/controllers/app-env-api.js +55 -0
  19. package/dist/controllers/app-env-file.js +181 -0
  20. package/dist/controllers/app-env.js +286 -131
  21. package/dist/controllers/app.js +849 -309
  22. package/dist/controllers/auth.js +255 -11
  23. package/dist/controllers/branch.js +78 -48
  24. package/dist/controllers/build.js +88 -0
  25. package/dist/controllers/database.js +318 -0
  26. package/dist/controllers/project.js +156 -87
  27. package/dist/controllers/select-prompt-port.js +1 -0
  28. package/dist/lib/agent/cli-command.js +17 -0
  29. package/dist/lib/agent/constants.js +12 -0
  30. package/dist/lib/agent/package-manager.js +99 -0
  31. package/dist/lib/agent/setup-status.js +83 -0
  32. package/dist/lib/app/app-interaction.js +5 -0
  33. package/dist/lib/app/{preview-provider.js → app-provider.js} +220 -104
  34. package/dist/lib/app/branch-database-api.js +102 -0
  35. package/dist/lib/app/branch-database-deploy.js +326 -0
  36. package/dist/lib/app/branch-database.js +216 -0
  37. package/dist/lib/app/build-settings.js +93 -0
  38. package/dist/lib/app/build.js +82 -0
  39. package/dist/lib/app/bun-project.js +15 -9
  40. package/dist/lib/app/compute-config.js +145 -0
  41. package/dist/lib/app/deploy-plan.js +59 -0
  42. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
  43. package/dist/lib/app/env-config.js +1 -1
  44. package/dist/lib/app/env-file.js +82 -0
  45. package/dist/lib/app/env-vars.js +28 -2
  46. package/dist/lib/app/local-dev.js +8 -49
  47. package/dist/lib/app/production-deploy-gate.js +162 -0
  48. package/dist/lib/app/read-branch.js +30 -0
  49. package/dist/lib/auth/auth-ops.js +38 -23
  50. package/dist/lib/auth/guard.js +6 -2
  51. package/dist/lib/auth/login.js +117 -15
  52. package/dist/lib/database/provider.js +167 -0
  53. package/dist/lib/diagnostics.js +15 -0
  54. package/dist/lib/fs/home-path.js +24 -0
  55. package/dist/lib/git/local-branch.js +53 -0
  56. package/dist/lib/git/local-status.js +57 -0
  57. package/dist/lib/project/interactive-setup.js +2 -1
  58. package/dist/lib/project/local-pin.js +183 -34
  59. package/dist/lib/project/resolution.js +206 -50
  60. package/dist/lib/project/setup.js +64 -18
  61. package/dist/output/patterns.js +1 -1
  62. package/dist/presenters/agent.js +74 -0
  63. package/dist/presenters/app-env.js +149 -14
  64. package/dist/presenters/app.js +187 -26
  65. package/dist/presenters/auth.js +99 -2
  66. package/dist/presenters/branch.js +37 -102
  67. package/dist/presenters/database.js +274 -0
  68. package/dist/presenters/project.js +44 -26
  69. package/dist/presenters/verbose-context.js +64 -0
  70. package/dist/shell/cli-command.js +12 -0
  71. package/dist/shell/command-arguments.js +7 -1
  72. package/dist/shell/command-meta.js +243 -15
  73. package/dist/shell/command-runner.js +60 -21
  74. package/dist/shell/diagnostics-output.js +57 -0
  75. package/dist/shell/errors.js +61 -1
  76. package/dist/shell/help.js +31 -20
  77. package/dist/shell/output.js +10 -1
  78. package/dist/shell/prompt.js +7 -3
  79. package/dist/shell/runtime.js +10 -6
  80. package/dist/shell/ui.js +42 -3
  81. package/dist/shell/update-check.js +247 -0
  82. package/dist/use-cases/auth.js +68 -1
  83. package/dist/use-cases/branch.js +20 -68
  84. package/dist/use-cases/create-cli-gateways.js +2 -17
  85. package/package.json +27 -10
  86. package/dist/lib/app/preview-build.js +0 -284
  87. package/dist/lib/app/preview-interaction.js +0 -5
@@ -1,36 +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
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
+ }));
15
69
  });
16
70
  }
17
71
  async function inspectProjectBinding(options) {
18
- const projects = await options.listProjects();
19
- const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
20
- if (target) return target;
21
- return {
22
- workspace: options.workspace,
23
- project: null,
24
- localBinding: { status: "not-linked" },
25
- resolution: { projectSource: "unbound" },
26
- ...await buildProjectSetupSuggestion({
27
- cwd: options.context.runtime.cwd,
28
- projects,
29
- commandName: options.commandName ?? "project show"
30
- })
31
- };
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
+ });
32
93
  }
33
94
  function projectNotFoundError(projectRef, workspace) {
95
+ return projectResolutionErrorToCliError(new ProjectNotFoundError(projectRef, workspace));
96
+ }
97
+ function projectNotFoundCliError(projectRef, workspace) {
34
98
  return new CliError({
35
99
  code: "PROJECT_NOT_FOUND",
36
100
  domain: "project",
@@ -42,6 +106,9 @@ function projectNotFoundError(projectRef, workspace) {
42
106
  });
43
107
  }
44
108
  function projectAmbiguousError(projectRef, matches) {
109
+ return projectResolutionErrorToCliError(new ProjectAmbiguousError(projectRef, matches));
110
+ }
111
+ function projectAmbiguousCliError(projectRef, matches) {
45
112
  const firstMatch = matches[0];
46
113
  const nextSteps = ["prisma-cli project list"];
47
114
  if (firstMatch) nextSteps.push(`prisma-cli app deploy --project ${firstMatch.id}`);
@@ -59,7 +126,7 @@ function projectAmbiguousError(projectRef, matches) {
59
126
  nextSteps
60
127
  });
61
128
  }
62
- function localStateStaleError() {
129
+ function localStateStaleCliError() {
63
130
  return new CliError({
64
131
  code: "LOCAL_STATE_STALE",
65
132
  domain: "project",
@@ -71,8 +138,58 @@ function localStateStaleError() {
71
138
  nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
72
139
  });
73
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
+ }
74
191
  async function buildProjectSetupSuggestion(options) {
75
- const suggestedName = await inferTargetName(options.cwd);
192
+ const suggestedName = await inferTargetName(options.cwd, options.signal);
76
193
  const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
77
194
  return {
78
195
  suggestedProjectName: suggestedName.name,
@@ -83,17 +200,24 @@ async function buildProjectSetupSuggestion(options) {
83
200
  }
84
201
  async function projectSetupRequiredError(options) {
85
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;
86
210
  return new CliError({
87
211
  code: "PROJECT_SETUP_REQUIRED",
88
212
  domain: "project",
89
213
  summary: "Choose a Project before running this command",
90
- 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,
91
215
  fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
92
216
  meta: { ...suggestion },
93
217
  exitCode: 1,
94
218
  nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
95
219
  nextActions: buildProjectSetupNextActions({
96
- commandName: options.commandName,
220
+ commandName: error.commandName,
97
221
  suggestedProjectName: suggestion.suggestedProjectName
98
222
  })
99
223
  });
@@ -131,9 +255,13 @@ function buildProjectSetupNextActions(options = {}) {
131
255
  });
132
256
  return actions;
133
257
  }
134
- async function readPackageName(cwd) {
258
+ async function readPackageName(cwd, signal) {
259
+ signal?.throwIfAborted();
135
260
  try {
136
- const raw = await readFile(path.join(cwd, "package.json"), "utf8");
261
+ const raw = await readFile(path.join(cwd, "package.json"), {
262
+ encoding: "utf8",
263
+ signal
264
+ });
137
265
  const parsed = JSON.parse(raw);
138
266
  if (!parsed || typeof parsed !== "object") return null;
139
267
  const packageName = "name" in parsed ? parsed.name : null;
@@ -144,8 +272,8 @@ async function readPackageName(cwd) {
144
272
  throw error;
145
273
  }
146
274
  }
147
- async function inferTargetName(cwd) {
148
- const packageName = await readPackageName(cwd);
275
+ async function inferTargetName(cwd, signal) {
276
+ const packageName = await readPackageName(cwd, signal);
149
277
  if (packageName && isValidInferredTargetName(packageName)) return {
150
278
  name: packageName,
151
279
  source: "package-name"
@@ -163,9 +291,9 @@ function sortProjects(projects) {
163
291
  }
164
292
  function resolveExplicitProject(projectRef, projects, workspace) {
165
293
  const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
166
- if (matches.length === 1) return matches[0];
167
- if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
168
- 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));
169
297
  }
170
298
  function projectMatchesSuggestedName(project, suggestedName) {
171
299
  return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
@@ -174,35 +302,63 @@ async function resolveDurablePlatformMapping() {
174
302
  return null;
175
303
  }
176
304
  async function resolveBoundProjectTarget(options, projects, settings) {
177
- if (options.explicitProject) return resolvedTarget(options.workspace, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
178
- targetName: options.explicitProject,
179
- targetNameSource: "explicit"
180
- });
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
+ }
181
313
  if (settings.allowEnvProjectId && options.envProjectId) {
182
314
  const project = projects.find((candidate) => candidate.id === options.envProjectId);
183
- if (!project) throw projectNotFoundError(options.envProjectId, options.workspace);
184
- 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", {
185
317
  targetName: options.envProjectId,
186
318
  targetNameSource: "env"
187
- });
319
+ }));
188
320
  }
189
- const localPin = await readLocalResolutionPin(options.context.runtime.cwd);
190
- if (localPin.kind === "invalid") throw localStateStaleError();
321
+ const localPin = settings.localPin;
322
+ if (!localPin) return Result.ok(null);
191
323
  if (localPin.kind === "present") {
192
- 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
+ }));
193
329
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
194
- if (!project) throw localStateStaleError();
195
- 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", {
196
332
  targetName: project.name,
197
333
  targetNameSource: "local-pin"
198
- });
334
+ }));
199
335
  }
200
336
  const platformMapping = await resolveDurablePlatformMapping();
201
- 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", {
202
338
  targetName: platformMapping.name,
203
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
204
361
  });
205
- return null;
206
362
  }
207
363
  function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
208
364
  return {
@@ -227,4 +383,4 @@ function toProjectSummary(project) {
227
383
  };
228
384
  }
229
385
  //#endregion
230
- export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
386
+ export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, localProjectWorkspaceMismatchError, projectAmbiguousError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
@@ -1,7 +1,10 @@
1
- import { CliError, usageError } from "../../shell/errors.js";
2
1
  import "../../shell/command-arguments.js";
2
+ import { CliError, usageError } from "../../shell/errors.js";
3
+ import { shortenHomePath } from "../fs/home-path.js";
3
4
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
4
5
  import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
6
+ import path from "node:path";
7
+ import { Result, matchError } from "better-result";
5
8
  //#region src/lib/project/setup.ts
6
9
  function isValidProjectSetupName(projectName) {
7
10
  return projectName.trim().length > 0;
@@ -16,22 +19,64 @@ function resolveProjectForSetup(projectRef, projects, workspace) {
16
19
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
17
20
  throw projectNotFoundError(projectRef, workspace);
18
21
  }
19
- async function bindProjectToDirectory(context, workspace, project, action) {
20
- await writeLocalResolutionPin(context.runtime.cwd, {
21
- workspaceId: workspace.id,
22
- projectId: project.id
22
+ async function bindProjectToDirectory(context, workspace, project, action, directory = context.runtime.cwd) {
23
+ return Result.gen(async function* () {
24
+ yield* Result.await(writeLocalResolutionPin(directory, {
25
+ workspaceId: workspace.id,
26
+ projectId: project.id
27
+ }, context.runtime.signal));
28
+ yield* Result.await(ensureLocalResolutionPinGitignore(directory, context.runtime.signal));
29
+ return Result.ok({
30
+ workspace,
31
+ project,
32
+ directory: formatSetupDirectory(directory, context),
33
+ localPin: {
34
+ path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
35
+ written: true
36
+ },
37
+ action
38
+ });
23
39
  });
24
- await ensureLocalResolutionPinGitignore(context.runtime.cwd);
25
- return {
26
- workspace,
27
- project,
28
- directory: formatSetupDirectory(context.runtime.cwd),
29
- localPin: {
30
- path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
31
- written: true
40
+ }
41
+ function projectDirectoryBindingErrorToCliError(error) {
42
+ return matchError(error, {
43
+ LocalResolutionPinSerializationError: (error) => {
44
+ throw error;
32
45
  },
33
- action
34
- };
46
+ LocalResolutionPinWriteAbortedError: (error) => {
47
+ throw error;
48
+ },
49
+ LocalResolutionPinWriteFailedError: (error) => localStateWriteFailedError(error, {
50
+ why: `The CLI could not write ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
51
+ meta: {
52
+ pinPath: error.pinPath,
53
+ operation: error.operation
54
+ }
55
+ }),
56
+ LocalResolutionPinGitignoreUpdateAbortedError: (error) => {
57
+ throw error;
58
+ },
59
+ LocalResolutionPinGitignoreUpdateFailedError: (error) => localStateWriteFailedError(error, {
60
+ why: "The CLI could not update .gitignore to keep local Project binding state out of git.",
61
+ meta: {
62
+ gitignorePath: error.gitignorePath,
63
+ operation: error.operation
64
+ }
65
+ })
66
+ });
67
+ }
68
+ function localStateWriteFailedError(error, options) {
69
+ return new CliError({
70
+ code: "LOCAL_STATE_WRITE_FAILED",
71
+ domain: "project",
72
+ summary: "Could not save local Project binding",
73
+ why: options.why,
74
+ fix: "Check that this directory is writable and that .prisma/local.json and .gitignore are not blocked by directories or permissions, then retry.",
75
+ debug: formatDebugDetails(error.cause),
76
+ meta: options.meta,
77
+ exitCode: 1,
78
+ nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli app deploy --project <id-or-name>"]
79
+ });
35
80
  }
36
81
  function toProjectSummary(project) {
37
82
  return {
@@ -66,8 +111,9 @@ function projectCreateFailedError(error, projectName, workspace, options) {
66
111
  nextSteps: options.nextSteps
67
112
  });
68
113
  }
69
- function formatSetupDirectory(cwd) {
70
- const basename = cwd.split(/[\\/]/).filter(Boolean).pop();
114
+ function formatSetupDirectory(directory, context) {
115
+ if (path.resolve(directory) !== path.resolve(context.runtime.cwd)) return shortenHomePath(directory, context.runtime.env);
116
+ const basename = directory.split(/[\\/]/).filter(Boolean).pop();
71
117
  return basename ? `./${basename}` : ".";
72
118
  }
73
119
  function extractHttpStatus(error) {
@@ -86,4 +132,4 @@ function formatDebugDetails(error) {
86
132
  return typeof error === "string" ? error : null;
87
133
  }
88
134
  //#endregion
89
- export { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary, validateProjectSetupNameText };
135
+ export { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary, validateProjectSetupNameText };
@@ -1,5 +1,5 @@
1
- import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
2
1
  import { formatDescriptorLabel } from "../shell/command-meta.js";
2
+ import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
3
3
  import stringWidth from "string-width";
4
4
  //#region src/output/patterns.ts
5
5
  function renderList(input, ui) {
@@ -0,0 +1,74 @@
1
+ import { formatShellCommand } from "../shell/command-arguments.js";
2
+ import { formatDescriptorLabel } from "../shell/command-meta.js";
3
+ import { renderSummaryLine } from "../shell/ui.js";
4
+ //#region src/presenters/agent.ts
5
+ function renderAgentInstall(context, descriptor, result) {
6
+ const ui = context.ui;
7
+ const rail = ui.dim("│");
8
+ return [
9
+ renderSummaryLine(ui, "success", `${formatDescriptorLabel(descriptor)} → ${operationSummary(result)} Prisma skills.`),
10
+ "",
11
+ `${rail} ${ui.accent("skills:")} ${result.skills.status.replace("-", " ")}`,
12
+ `${rail} ${ui.dim(formatShellCommand(result.skills.command))}`
13
+ ];
14
+ }
15
+ function renderAgentStatus(context, descriptor, result) {
16
+ const ui = context.ui;
17
+ const rail = ui.dim("│");
18
+ return [
19
+ `${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim(`Checking ${result.statusScope} Prisma skills.`)}`,
20
+ "",
21
+ `${rail} ${ui.accent("skills:")} ${result.skillsInstalled ? "installed" : "not found"}`,
22
+ ...renderInstalledSkills(context, result.skills),
23
+ `${rail}`,
24
+ `${rail} ${ui.accent("source:")} ${formatStatusSource(result)}`,
25
+ `${rail} ${ui.dim(formatShellCommand(result.skillsListCommand))}`,
26
+ ...renderProjectStatusDetails(context, result)
27
+ ];
28
+ }
29
+ function serializeAgentInstall(result) {
30
+ return result;
31
+ }
32
+ function serializeAgentStatus(result) {
33
+ return result;
34
+ }
35
+ function formatSetupPromptStatus(result) {
36
+ if (result.skillsInstalled) return "not needed";
37
+ if (result.promptDismissedAt) return `dismissed ${result.promptDismissedAt}`;
38
+ return "active";
39
+ }
40
+ function formatStatusSource(result) {
41
+ if (result.statusSource === "skills-cli") return result.statusScope === "global" ? "skills list -g --json" : "skills list --json";
42
+ if (result.statusSource === "skills-lock") return result.skillsLockPath;
43
+ return "unavailable";
44
+ }
45
+ function renderProjectStatusDetails(context, result) {
46
+ if (result.statusScope !== "project") return [];
47
+ const ui = context.ui;
48
+ const rail = ui.dim("│");
49
+ return [
50
+ `${rail}`,
51
+ `${rail} ${ui.accent("skills lock:")} ${result.skillsLockInstalled ? "installed" : "not found"}`,
52
+ `${rail} ${ui.dim(result.skillsLockPath)}`,
53
+ `${rail}`,
54
+ `${rail} ${ui.accent("setup prompt:")} ${formatSetupPromptStatus(result)}`
55
+ ];
56
+ }
57
+ function renderInstalledSkills(context, skills) {
58
+ const ui = context.ui;
59
+ const rail = ui.dim("│");
60
+ if (skills.length === 0) return [`${rail} ${ui.dim("No Prisma skills reported.")}`];
61
+ return skills.map((skill) => {
62
+ const agents = skill.agents.length > 0 ? skill.agents.join(", ") : "no agents reported";
63
+ return `${rail} ${skill.name} ${ui.dim(`${skill.scope}; ${agents}`)}`;
64
+ });
65
+ }
66
+ function operationSummary(result) {
67
+ if (result.skills.status === "would-install") return "Would install";
68
+ return operationLabel(result.operation);
69
+ }
70
+ function operationLabel(operation) {
71
+ return operation === "update" ? "Updated" : "Installed";
72
+ }
73
+ //#endregion
74
+ export { renderAgentInstall, renderAgentStatus, serializeAgentInstall, serializeAgentStatus };