@prisma/cli 3.0.0-dev.97.1 → 8.0.0-rc.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 (99) hide show
  1. package/README.md +3 -14
  2. package/dist/adapters/local-state.js +14 -3
  3. package/dist/{lib/auth → auth}/client.js +11 -3
  4. package/dist/auth/errors.js +50 -0
  5. package/dist/{lib/auth → auth}/guard.js +7 -6
  6. package/dist/{lib/auth → auth}/login.js +147 -6
  7. package/dist/{lib/auth/auth-ops.js → auth/operations.js} +63 -13
  8. package/dist/auth/recipient.js +42 -0
  9. package/dist/{adapters → auth}/token-storage.js +28 -7
  10. package/dist/auth/workspaces.js +171 -0
  11. package/dist/cli-command.js +14 -0
  12. package/dist/cli-name.js +12 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/cli2.js +25 -8
  15. package/dist/command-arguments.js +12 -0
  16. package/dist/commands/agent/index.js +60 -0
  17. package/dist/commands/app/index.js +11 -6
  18. package/dist/commands/auth/index.js +2 -3
  19. package/dist/commands/bucket/index.js +123 -0
  20. package/dist/commands/build/index.js +29 -0
  21. package/dist/commands/database/index.js +92 -2
  22. package/dist/commands/feedback/index.js +20 -0
  23. package/dist/commands/git/index.js +1 -1
  24. package/dist/commands/init/index.js +33 -0
  25. package/dist/commands/project/index.js +54 -5
  26. package/dist/controllers/agent-setup.js +52 -0
  27. package/dist/controllers/agent.js +228 -0
  28. package/dist/controllers/app-env-api.js +3 -2
  29. package/dist/controllers/app-env-file.js +1 -1
  30. package/dist/controllers/app-env.js +5 -5
  31. package/dist/controllers/app.js +423 -358
  32. package/dist/controllers/auth.js +62 -254
  33. package/dist/controllers/branch.js +5 -16
  34. package/dist/controllers/bucket.js +184 -0
  35. package/dist/controllers/build.js +88 -0
  36. package/dist/controllers/database.js +230 -85
  37. package/dist/controllers/feedback.js +90 -0
  38. package/dist/controllers/init.js +814 -0
  39. package/dist/controllers/project.js +345 -184
  40. package/dist/controllers/select-prompt-port.js +1 -0
  41. package/dist/{shell/errors.js → errors.js} +8 -40
  42. package/dist/lib/agent/cli-command.js +20 -0
  43. package/dist/lib/agent/constants.js +12 -0
  44. package/dist/lib/agent/package-manager.js +99 -0
  45. package/dist/lib/agent/setup-status.js +83 -0
  46. package/dist/lib/app/app-provider.js +168 -95
  47. package/dist/lib/app/branch-database-deploy.js +70 -42
  48. package/dist/lib/app/branch-database.js +39 -20
  49. package/dist/lib/app/build-settings.js +8 -3
  50. package/dist/lib/app/build.js +16 -14
  51. package/dist/lib/app/bun-project.js +1 -1
  52. package/dist/lib/app/compute-config.js +29 -31
  53. package/dist/lib/app/deploy-plan.js +1 -0
  54. package/dist/lib/app/deploy-progress.js +7 -7
  55. package/dist/lib/app/env-config.js +1 -1
  56. package/dist/lib/app/env-file.js +2 -2
  57. package/dist/lib/app/env-vars.js +1 -1
  58. package/dist/lib/app/local-dev.js +1 -1
  59. package/dist/lib/app/production-deploy-gate.js +3 -2
  60. package/dist/lib/bucket/provider.js +139 -0
  61. package/dist/lib/database/provider.js +235 -17
  62. package/dist/lib/diagnostics.js +2 -1
  63. package/dist/lib/feedback.js +15 -0
  64. package/dist/lib/git/local-branch.js +1 -1
  65. package/dist/lib/project/interactive-setup.js +6 -5
  66. package/dist/lib/project/local-pin.js +1 -1
  67. package/dist/lib/project/provider.js +93 -0
  68. package/dist/lib/project/resolution.js +11 -8
  69. package/dist/lib/project/setup.js +9 -6
  70. package/dist/lib/version.js +3 -2
  71. package/dist/lib/workspace-id.js +18 -0
  72. package/dist/payload-B88Qvzxk-CrtTXSOe.js +34 -0
  73. package/dist/presenters/agent.js +74 -0
  74. package/dist/presenters/app.js +33 -7
  75. package/dist/presenters/auth.js +3 -2
  76. package/dist/presenters/bucket.js +174 -0
  77. package/dist/presenters/database.js +193 -5
  78. package/dist/presenters/feedback.js +26 -0
  79. package/dist/presenters/init.js +30 -0
  80. package/dist/presenters/project.js +98 -4
  81. package/dist/shell/cli-command.js +2 -0
  82. package/dist/shell/command-meta.js +303 -4
  83. package/dist/shell/command-runner.js +13 -6
  84. package/dist/shell/help.js +6 -5
  85. package/dist/shell/output.js +65 -3
  86. package/dist/shell/prompt.js +12 -5
  87. package/dist/shell/runtime.js +4 -21
  88. package/dist/state-dir.js +12 -0
  89. package/dist/{shell/update-check.js → update-check.js} +6 -5
  90. package/dist/v8/cli.js +14699 -0
  91. package/dist/v8/sender.js +192 -0
  92. package/package.json +21 -13
  93. package/dist/adapters/mock-api.js +0 -136
  94. package/dist/lib/app/app-interaction.js +0 -5
  95. package/dist/shell/command-arguments.js +0 -6
  96. package/dist/use-cases/auth.js +0 -145
  97. package/dist/use-cases/branch.js +0 -47
  98. package/dist/use-cases/create-cli-gateways.js +0 -68
  99. package/dist/use-cases/project.js +0 -30
@@ -1,30 +1,33 @@
1
- import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
- import { createAppProvider } from "../lib/app/app-provider.js";
1
+ import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command.js";
2
+ import { formatCommandArgument } from "../command-arguments.js";
3
+ import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../errors.js";
3
4
  import { renderSummaryLine } from "../shell/ui.js";
5
+ import { WorkspaceSelectionError } from "../auth/token-storage.js";
6
+ import { authenticatedManagementApiClient } from "../auth/guard.js";
4
7
  import { canPrompt } from "../shell/runtime.js";
5
- import { readLocalResolutionPin } from "../lib/project/local-pin.js";
6
- import { formatCommandArgument } from "../shell/command-arguments.js";
8
+ import { createAppProvider } from "../lib/app/app-provider.js";
9
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
7
10
  import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
8
11
  import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
9
- import { requireComputeAuth } from "../lib/auth/guard.js";
10
12
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
11
- import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
13
+ import { workspaceAmbiguousError, workspaceNotAuthenticatedError } from "../auth/errors.js";
12
14
  import { requireAuthenticatedAuthState } from "./auth.js";
13
15
  import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
14
- import { createProjectUseCases } from "../use-cases/project.js";
15
- import { matchError } from "better-result";
16
+ import { RecipientSessionInvalidError, resolveRecipientWorkspaceSession } from "../auth/recipient.js";
17
+ import { createManagementProjectProvider, projectApiError } from "../lib/project/provider.js";
18
+ import path from "node:path";
19
+ import { unlink } from "node:fs/promises";
20
+ import { SERVICE_TOKEN_ENV_VAR } from "@prisma/cli-engine";
16
21
  import open from "open";
22
+ import { matchError } from "better-result";
17
23
  //#region src/controllers/project.ts
18
24
  const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
19
25
  const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
20
- function isRealMode(context) {
21
- return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
22
- }
23
- async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
26
+ async function readProjectListLocalBinding(cwd, projects, signal) {
24
27
  const pinResult = await readLocalResolutionPin(cwd, signal);
25
28
  if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
26
29
  const pin = pinResult.value;
27
- if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
30
+ if (pin.kind === "present") return projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
28
31
  return { status: "not-linked" };
29
32
  }
30
33
  function localPinReadErrorToInvalidLocalBinding(error) {
@@ -40,34 +43,18 @@ function localPinReadErrorToInvalidLocalBinding(error) {
40
43
  });
41
44
  }
42
45
  async function runProjectList(context) {
43
- const authState = await requireAuthenticatedAuthState(context);
44
- const workspace = authState.workspace;
46
+ const workspace = (await requireAuthenticatedAuthState(context)).workspace;
45
47
  if (!workspace) throw workspaceRequiredError();
46
- if (isRealMode(context)) {
47
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
48
- if (!client) throw authRequiredError();
49
- const projects = sortProjects(await listRealWorkspaceProjects(client, workspace, context.runtime.signal));
50
- const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
51
- const nextActions = buildProjectListNextActions(localBinding);
52
- return {
53
- command: "project.list",
54
- result: {
55
- workspace,
56
- projects: projects.map(toProjectSummary),
57
- localBinding
58
- },
59
- warnings: [],
60
- nextSteps: [],
61
- nextActions
62
- };
63
- }
64
- const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
65
- const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
48
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
49
+ if (!client) throw authRequiredError();
50
+ const projects = sortProjects(await listRealWorkspaceProjects(client, context.runtime.signal));
51
+ const localBinding = await readProjectListLocalBinding(context.runtime.cwd, projects, context.runtime.signal);
66
52
  const nextActions = buildProjectListNextActions(localBinding);
67
53
  return {
68
54
  command: "project.list",
69
55
  result: {
70
- ...result,
56
+ workspace,
57
+ projects: projects.map(toProjectSummary),
71
58
  localBinding
72
59
  },
73
60
  warnings: [],
@@ -84,7 +71,7 @@ function buildProjectListNextActions(localBinding) {
84
71
  async function runProjectShow(context, explicitProject) {
85
72
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
86
73
  if (!workspace) throw workspaceRequiredError();
87
- const result = isRealMode(context) ? await resolveProjectShowInRealMode(context, workspace, explicitProject) : await resolveProjectShowInFixtureMode(context, workspace, explicitProject);
74
+ const result = await resolveProjectShowInRealMode(context, workspace, explicitProject);
88
75
  return {
89
76
  command: "project.show",
90
77
  result,
@@ -97,17 +84,17 @@ async function runProjectShow(context, explicitProject) {
97
84
  }) : []
98
85
  };
99
86
  }
100
- async function runProjectCreate(context, projectName) {
87
+ async function runProjectCreate(context, projectName, options) {
101
88
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
102
89
  if (!workspace) throw workspaceRequiredError();
103
90
  if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
104
- if (!isRealMode(context)) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
105
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
91
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
106
92
  if (!client) throw authRequiredError();
107
93
  const provider = createAppProvider(client);
108
94
  const name = projectName.trim();
109
95
  const created = await provider.createProject({
110
96
  name,
97
+ region: options?.region,
111
98
  signal: context.runtime.signal
112
99
  }).catch((error) => {
113
100
  throw projectCreateFailedError(error, name, workspace, {
@@ -118,7 +105,8 @@ async function runProjectCreate(context, projectName) {
118
105
  });
119
106
  const bindResult = await bindProjectToDirectory(context, workspace, {
120
107
  id: created.id,
121
- name: created.name
108
+ name: created.name,
109
+ ...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {}
122
110
  }, "created");
123
111
  if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
124
112
  return {
@@ -131,14 +119,10 @@ async function runProjectCreate(context, projectName) {
131
119
  async function runProjectLink(context, projectRef) {
132
120
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
133
121
  if (!workspace) throw workspaceRequiredError();
134
- let provider = null;
135
- let projects;
136
- if (isRealMode(context)) {
137
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
138
- if (!client) throw authRequiredError();
139
- provider = createAppProvider(client);
140
- projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
141
- } else projects = listFixtureWorkspaceProjects(context, workspace);
122
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
123
+ if (!client) throw authRequiredError();
124
+ const provider = createAppProvider(client);
125
+ const projects = await listRealWorkspaceProjects(client, context.runtime.signal);
142
126
  let result;
143
127
  if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
144
128
  else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
@@ -154,10 +138,7 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
154
138
  const setup = await promptForProjectSetupChoice({
155
139
  context,
156
140
  projects,
157
- createProject: (projectName) => {
158
- if (!provider) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
159
- return createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal);
160
- },
141
+ createProject: (projectName) => createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal),
161
142
  cancel: {
162
143
  why: "Project link needs a Project before it can continue.",
163
144
  fix: "Choose an existing Project or create a new one, then rerun project link.",
@@ -217,54 +198,288 @@ async function projectLinkTargetRequiredError(context, projects) {
217
198
  })
218
199
  });
219
200
  }
220
- async function runGitConnect(context, gitUrl, options = {}) {
201
+ async function runProjectRename(context, newName, options) {
221
202
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
222
203
  if (!workspace) throw workspaceRequiredError();
223
- if (isRealMode(context)) {
224
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
225
- if (!client) throw authRequiredError();
226
- const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
227
- const repository = await resolveRepositoryForConnect(context, gitUrl);
228
- const api = client;
229
- const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
230
- if (existing) {
231
- const existingConnection = toRepositoryConnection(existing);
232
- if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
233
- command: "git.connect",
234
- result: {
235
- ...target,
236
- repositoryConnection: existingConnection
237
- },
238
- warnings: [],
239
- nextSteps: []
240
- };
241
- throw repoAlreadyConnectedError(existingConnection.repository.fullName);
242
- }
243
- const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
244
- const { data, error, response } = await api.POST("/v1/source-repositories", {
245
- body: {
204
+ const name = newName.trim();
205
+ if (!isValidProjectSetupName(name)) throw projectSetupNameRequiredError("project rename");
206
+ const { provider, target } = await requireProjectCommandContext(context, workspace, options.project, "project rename");
207
+ const previousName = target.project.name;
208
+ return {
209
+ command: "project.rename",
210
+ result: {
211
+ workspace,
212
+ project: await provider.renameProject({
246
213
  projectId: target.project.id,
247
- provider: "github",
248
- providerRepositoryId: resolvedRepository.repository.id,
249
- installationId: resolvedRepository.installation.id
214
+ name,
215
+ signal: context.runtime.signal
216
+ }),
217
+ previousName
218
+ },
219
+ warnings: [],
220
+ nextSteps: []
221
+ };
222
+ }
223
+ async function runProjectRemove(context, projectRef, options) {
224
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
225
+ const workspace = (await requireAuthenticatedAuthState(context)).workspace;
226
+ if (!workspace) throw workspaceRequiredError();
227
+ const { provider, projects } = await requireProjectMutationContext(context, workspace);
228
+ const project = toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace));
229
+ requireProjectExactConfirmation({
230
+ id: project.id,
231
+ confirm: options.confirm,
232
+ summary: "Confirm project removal",
233
+ why: "Removing a project is permanent, deletes its databases, and stops its apps, so it requires the exact project id.",
234
+ nextStep: formatCommand([
235
+ "project",
236
+ "remove",
237
+ project.id,
238
+ "--confirm",
239
+ project.id
240
+ ])
241
+ });
242
+ await provider.removeProject({
243
+ projectId: project.id,
244
+ signal: context.runtime.signal
245
+ });
246
+ const warnings = [];
247
+ return {
248
+ command: "project.remove",
249
+ result: {
250
+ workspace,
251
+ project,
252
+ localPin: { cleared: await cleanupLocalPinForProject(context, project.id, { onError: (message) => warnings.push(message) }) }
253
+ },
254
+ warnings,
255
+ nextSteps: []
256
+ };
257
+ }
258
+ async function runProjectTransfer(context, projectRef, options) {
259
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
260
+ const workspace = (await requireAuthenticatedAuthState(context)).workspace;
261
+ if (!workspace) throw workspaceRequiredError();
262
+ if (options.toWorkspace && options.recipientToken) throw usageError("Choose one transfer recipient source", "--to-workspace and --recipient-token are mutually exclusive.", "Pass either --to-workspace <id-or-name> or --recipient-token <token>.", [formatCommand([
263
+ "project",
264
+ "transfer",
265
+ "<project>",
266
+ "--to-workspace",
267
+ "<id-or-name>",
268
+ "--confirm",
269
+ "<project-id>"
270
+ ])], "project");
271
+ if (!options.toWorkspace?.trim() && !options.recipientToken?.trim()) throw transferRecipientRequiredError(formatCommand);
272
+ const { provider, projects } = await requireProjectMutationContext(context, workspace);
273
+ const project = toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace));
274
+ requireProjectExactConfirmation({
275
+ id: project.id,
276
+ confirm: options.confirm,
277
+ summary: "Confirm project transfer",
278
+ why: "Transferring moves the project to another workspace and this workspace loses access, so it requires the exact project id.",
279
+ nextStep: `${formatCommand([
280
+ "project",
281
+ "transfer",
282
+ project.id
283
+ ])} ${options.toWorkspace ? `--to-workspace ${formatCommandArgument(options.toWorkspace)}` : "--recipient-token <token>"} --confirm ${project.id}`
284
+ });
285
+ const recipient = await resolveTransferRecipient(context, options);
286
+ await provider.transferProject({
287
+ projectId: project.id,
288
+ recipientAccessToken: recipient.accessToken,
289
+ signal: context.runtime.signal
290
+ });
291
+ const warnings = [];
292
+ const pinAction = await rewriteOrClearLocalPinForProject(context, project.id, recipient.workspaceId, { onError: (message) => warnings.push(message) });
293
+ return {
294
+ command: "project.transfer",
295
+ result: {
296
+ workspace,
297
+ project,
298
+ recipient: {
299
+ workspaceId: recipient.workspaceId,
300
+ workspaceName: recipient.workspaceName,
301
+ source: recipient.source
250
302
  },
251
- signal: context.runtime.signal
252
- });
253
- if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
303
+ localPin: { action: pinAction }
304
+ },
305
+ warnings,
306
+ nextSteps: options.toWorkspace ? [`${formatCommand([
307
+ "auth",
308
+ "workspace",
309
+ "use"
310
+ ])} ${formatCommandArgument(options.toWorkspace)}`] : []
311
+ };
312
+ }
313
+ async function resolveTransferRecipient(context, options) {
314
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
315
+ const recipientToken = options.recipientToken?.trim();
316
+ if (recipientToken) return {
317
+ accessToken: recipientToken,
318
+ workspaceId: null,
319
+ workspaceName: null,
320
+ source: "recipient-token"
321
+ };
322
+ const workspaceRef = options.toWorkspace?.trim();
323
+ if (!workspaceRef) throw transferRecipientRequiredError(formatCommand);
324
+ if (context.runtime.env[SERVICE_TOKEN_ENV_VAR] !== void 0) throw transferRecipientUnavailableError(formatCommand);
325
+ return resolveTransferRecipientFromWorkspaceSession(context, workspaceRef);
326
+ }
327
+ async function resolveTransferRecipientFromWorkspaceSession(context, workspaceRef) {
328
+ try {
329
+ const session = await resolveRecipientWorkspaceSession(workspaceRef, context.runtime.env, context.runtime.signal);
254
330
  return {
255
- command: "git.connect",
256
- result: {
257
- ...target,
258
- repositoryConnection: toRepositoryConnection(data.data)
259
- },
260
- warnings: [],
261
- nextSteps: []
331
+ accessToken: session.accessToken,
332
+ workspaceId: session.workspace.id,
333
+ workspaceName: session.workspace.name,
334
+ source: "workspace-session"
262
335
  };
336
+ } catch (error) {
337
+ throw recipientWorkspaceSessionFailure(error, workspaceRef);
263
338
  }
264
- const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git connect");
339
+ }
340
+ function recipientWorkspaceSessionFailure(error, workspaceRef) {
341
+ if (error instanceof WorkspaceSelectionError) {
342
+ if (error.reason === "ambiguous") return workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
343
+ id: match.id,
344
+ name: match.name,
345
+ credentialWorkspaceId: match.credentialWorkspaceId
346
+ })));
347
+ return workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
348
+ }
349
+ if (error instanceof RecipientSessionInvalidError) return workspaceNotAuthenticatedError(error.workspaceRef);
350
+ return error;
351
+ }
352
+ async function requireProjectMutationContext(context, _workspace) {
353
+ const client = await requireProjectClient(context);
354
+ return {
355
+ provider: createManagementProjectProvider(client),
356
+ projects: await listRealWorkspaceProjects(client, context.runtime.signal)
357
+ };
358
+ }
359
+ async function requireProjectCommandContext(context, workspace, explicitProject, commandName) {
360
+ const client = await requireProjectClient(context);
361
+ const listProjects = async () => listRealWorkspaceProjects(client, context.runtime.signal);
362
+ const targetResult = await resolveProjectTarget({
363
+ context,
364
+ workspace,
365
+ explicitProject,
366
+ listProjects,
367
+ commandName
368
+ });
369
+ if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
370
+ return {
371
+ provider: createManagementProjectProvider(client),
372
+ target: targetResult.value
373
+ };
374
+ }
375
+ async function requireProjectClient(context) {
376
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
377
+ if (!client) throw authRequiredError();
378
+ return client;
379
+ }
380
+ function requireProjectExactConfirmation(options) {
381
+ if (options.confirm === options.id) return;
382
+ throw new CliError({
383
+ code: "CONFIRMATION_REQUIRED",
384
+ domain: "project",
385
+ summary: options.summary,
386
+ why: options.why,
387
+ fix: `Rerun with --confirm ${options.id}.`,
388
+ exitCode: 2,
389
+ nextSteps: [options.nextStep],
390
+ meta: {
391
+ expectedConfirm: options.id,
392
+ receivedConfirm: options.confirm ?? null
393
+ }
394
+ });
395
+ }
396
+ function transferRecipientRequiredError(formatCommand) {
397
+ return new CliError({
398
+ code: "TRANSFER_RECIPIENT_REQUIRED",
399
+ domain: "project",
400
+ summary: "Transfer recipient required",
401
+ why: "Project transfer needs the receiving workspace.",
402
+ fix: "Pass --to-workspace <id-or-name> for a locally authenticated workspace, or --recipient-token <token> for a cross-account transfer.",
403
+ exitCode: 2,
404
+ nextSteps: [formatCommand([
405
+ "auth",
406
+ "workspace",
407
+ "list"
408
+ ]), formatCommand([
409
+ "project",
410
+ "transfer",
411
+ "<project>",
412
+ "--to-workspace",
413
+ "<id-or-name>",
414
+ "--confirm",
415
+ "<project-id>"
416
+ ])]
417
+ });
418
+ }
419
+ function transferRecipientUnavailableError(formatCommand) {
420
+ return new CliError({
421
+ code: "TRANSFER_RECIPIENT_UNAVAILABLE",
422
+ domain: "project",
423
+ summary: "Local workspace sessions are unavailable",
424
+ why: `--to-workspace resolves locally stored OAuth sessions, but ${SERVICE_TOKEN_ENV_VAR} is set and service-token mode does not read them.`,
425
+ fix: "Pass --recipient-token <token> with an access token for the receiving workspace, or unset the service token.",
426
+ exitCode: 1,
427
+ nextSteps: [formatCommand([
428
+ "project",
429
+ "transfer",
430
+ "<project>",
431
+ "--recipient-token",
432
+ "<token>",
433
+ "--confirm",
434
+ "<project-id>"
435
+ ])]
436
+ });
437
+ }
438
+ async function cleanupLocalPinForProject(context, projectId, hooks) {
439
+ const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
440
+ if (pinResult.isErr()) return false;
441
+ const pin = pinResult.value;
442
+ if (pin.kind !== "present" || pin.pin.projectId !== projectId) return false;
443
+ try {
444
+ await unlink(path.join(context.runtime.cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
445
+ return true;
446
+ } catch {
447
+ hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the removed project but could not be deleted.`);
448
+ return false;
449
+ }
450
+ }
451
+ async function rewriteOrClearLocalPinForProject(context, projectId, recipientWorkspaceId, hooks) {
452
+ const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
453
+ if (pinResult.isErr()) return "none";
454
+ const pin = pinResult.value;
455
+ if (pin.kind !== "present" || pin.pin.projectId !== projectId) return "none";
456
+ if (recipientWorkspaceId) {
457
+ if ((await writeLocalResolutionPin(context.runtime.cwd, {
458
+ workspaceId: recipientWorkspaceId,
459
+ projectId
460
+ }, context.runtime.signal)).isOk()) return "rewritten";
461
+ hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be rewritten.`);
462
+ return "none";
463
+ }
464
+ try {
465
+ await unlink(path.join(context.runtime.cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
466
+ return "cleared";
467
+ } catch {
468
+ hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be cleared.`);
469
+ return "none";
470
+ }
471
+ }
472
+ async function runGitConnect(context, gitUrl, options = {}) {
473
+ const workspace = (await requireAuthenticatedAuthState(context)).workspace;
474
+ if (!workspace) throw workspaceRequiredError();
475
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
476
+ if (!client) throw authRequiredError();
477
+ const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
265
478
  const repository = await resolveRepositoryForConnect(context, gitUrl);
266
- const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
267
- if (existingConnection) {
479
+ const api = client;
480
+ const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
481
+ if (existing) {
482
+ const existingConnection = toRepositoryConnection(existing);
268
483
  if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
269
484
  command: "git.connect",
270
485
  result: {
@@ -276,13 +491,22 @@ async function runGitConnect(context, gitUrl, options = {}) {
276
491
  };
277
492
  throw repoAlreadyConnectedError(existingConnection.repository.fullName);
278
493
  }
279
- const connection = createPendingRepositoryConnection(repository);
280
- await context.stateStore.setRepositoryConnection(target.project.id, connection);
494
+ const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
495
+ const { data, error, response } = await api.POST("/v1/source-repositories", {
496
+ body: {
497
+ projectId: target.project.id,
498
+ provider: "github",
499
+ providerRepositoryId: resolvedRepository.repository.id,
500
+ installationId: resolvedRepository.installation.id
501
+ },
502
+ signal: context.runtime.signal
503
+ });
504
+ if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
281
505
  return {
282
506
  command: "git.connect",
283
507
  result: {
284
508
  ...target,
285
- repositoryConnection: connection
509
+ repositoryConnection: toRepositoryConnection(data.data)
286
510
  },
287
511
  warnings: [],
288
512
  nextSteps: []
@@ -291,96 +515,64 @@ async function runGitConnect(context, gitUrl, options = {}) {
291
515
  async function runGitDisconnect(context, options = {}) {
292
516
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
293
517
  if (!workspace) throw workspaceRequiredError();
294
- if (isRealMode(context)) {
295
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
296
- if (!client) throw authRequiredError();
297
- const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
298
- const api = client;
299
- const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
300
- if (!existing) throw repoNotConnectedError();
301
- const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
302
- params: { path: { id: existing.id } },
303
- signal: context.runtime.signal
304
- });
305
- if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
306
- return {
307
- command: "git.disconnect",
308
- result: {
309
- ...target,
310
- repositoryConnection: toRepositoryConnection(existing)
311
- },
312
- warnings: [],
313
- nextSteps: []
314
- };
315
- }
316
- const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git disconnect");
317
- const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
318
- if (!existingConnection) throw repoNotConnectedError();
319
- await context.stateStore.clearRepositoryConnection(target.project.id);
518
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
519
+ if (!client) throw authRequiredError();
520
+ const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
521
+ const api = client;
522
+ const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
523
+ if (!existing) throw repoNotConnectedError();
524
+ const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
525
+ params: { path: { id: existing.id } },
526
+ signal: context.runtime.signal
527
+ });
528
+ if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
320
529
  return {
321
530
  command: "git.disconnect",
322
531
  result: {
323
532
  ...target,
324
- repositoryConnection: existingConnection
533
+ repositoryConnection: toRepositoryConnection(existing)
325
534
  },
326
535
  warnings: [],
327
536
  nextSteps: []
328
537
  };
329
538
  }
330
539
  async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
331
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
540
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
332
541
  if (!client) throw authRequiredError();
333
542
  const result = await inspectProjectBinding({
334
543
  context,
335
544
  workspace,
336
545
  explicitProject,
337
- listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
546
+ listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
338
547
  commandName: "project show"
339
548
  });
340
549
  if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
341
550
  return result.value;
342
551
  }
343
552
  async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
344
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
553
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
345
554
  if (!client) throw authRequiredError();
346
555
  const result = await resolveProjectTarget({
347
556
  context,
348
557
  workspace,
349
558
  explicitProject,
350
- listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
559
+ listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
351
560
  commandName
352
561
  });
353
562
  if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
354
563
  return result.value;
355
564
  }
356
- async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
357
- const result = await inspectProjectBinding({
358
- context,
359
- workspace,
360
- explicitProject,
361
- listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
362
- commandName: "project show"
363
- });
364
- if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
365
- return result.value;
366
- }
367
- async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
368
- const result = await resolveProjectTarget({
369
- context,
370
- workspace,
371
- explicitProject,
372
- listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
373
- commandName
374
- });
375
- if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
376
- return result.value;
377
- }
378
- async function listRealWorkspaceProjects(client, workspace, signal) {
379
- const { data } = await client.GET("/v1/projects", { signal });
380
- return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
565
+ /** The projects the API returns for the active credential, sorted.
566
+ * Takes no workspace: the credential names one, and the API answers
567
+ * within it. */
568
+ async function listRealWorkspaceProjects(client, signal) {
569
+ const { data, error, response } = await client.GET("/v1/projects", { signal });
570
+ if (error || !data) throw projectApiError("Failed to list projects", response, error);
571
+ return sortProjects((data.data ?? []).map((project) => ({
381
572
  id: project.id,
382
573
  name: project.name,
383
574
  ..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
575
+ ..."defaultRegion" in project ? { defaultRegion: project.defaultRegion } : {},
384
576
  slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
385
577
  workspace: {
386
578
  id: project.workspace.id,
@@ -388,15 +580,6 @@ async function listRealWorkspaceProjects(client, workspace, signal) {
388
580
  }
389
581
  })));
390
582
  }
391
- function listFixtureWorkspaceProjects(context, workspace) {
392
- return sortProjects(context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
393
- id: project.id,
394
- name: project.name,
395
- ...project.url ? { url: project.url } : {},
396
- slug: project.slug,
397
- workspace
398
- })));
399
- }
400
583
  async function resolveRepositoryForConnect(context, gitUrl) {
401
584
  const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
402
585
  if (!remoteUrl) throw usageError("Repository connection requires a GitHub repository URL", "No git-url was provided and the local repo does not have an origin remote.", "Pass a GitHub repository URL, or add a GitHub origin remote and rerun prisma-cli git connect.", ["prisma-cli git connect git@github.com:prisma/prisma-cli.git"], "project");
@@ -582,28 +765,6 @@ async function readFirstSourceRepository(api, projectId, signal) {
582
765
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
583
766
  return data.data[0] ?? null;
584
767
  }
585
- function createPendingRepositoryConnection(repository) {
586
- return {
587
- id: null,
588
- provider: "github",
589
- repoId: null,
590
- repository,
591
- defaultBranch: null,
592
- isPrivate: null,
593
- status: "pending",
594
- installation: {
595
- id: null,
596
- status: "pending"
597
- },
598
- automation: {
599
- branches: false,
600
- pullRequests: false,
601
- comments: false
602
- },
603
- connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
604
- updatedAt: null
605
- };
606
- }
607
768
  function toRepositoryConnection(record) {
608
769
  const [owner = "", name = ""] = record.repoFullName.split("/");
609
770
  return {
@@ -728,4 +889,4 @@ function repoConnectionFixForStatus(status) {
728
889
  return "Re-run with --trace for the underlying API response details.";
729
890
  }
730
891
  //#endregion
731
- export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
892
+ export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectRemove, runProjectRename, runProjectShow, runProjectTransfer };