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

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 +54 -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,9 +1,15 @@
1
- import { authRequiredError, usageError } from "../shell/errors.js";
1
+ import { resolvePrismaCliPackageCommand } from "../lib/agent/cli-command.js";
2
+ import { PRISMA_AGENT_INSTALL_ARGS } from "../lib/agent/constants.js";
3
+ import { isLikelyProjectDirectory, readPrismaAgentSetupStatus, resolvePrismaAgentSetupCwd, shouldOfferPrismaAgentSetup } from "../lib/agent/setup-status.js";
4
+ import { authRequiredError, usageError, workspaceAmbiguousError, workspaceNotAuthenticatedError, workspaceSwitchUnavailableError } from "../shell/errors.js";
2
5
  import { canPrompt } from "../shell/runtime.js";
6
+ import { CLIENT_ID, SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
7
+ import { FileTokenStorage, WorkspaceSelectionError } from "../adapters/token-storage.js";
3
8
  import { performLogin, performLogout, readAuthState } from "../lib/auth/auth-ops.js";
4
9
  import { createAuthUseCases } from "../use-cases/auth.js";
5
10
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
6
11
  import { createSelectPromptPort } from "./select-prompt-port.js";
12
+ import { createManagementApiSdk } from "@prisma/management-api-sdk";
7
13
  //#region src/controllers/auth.ts
8
14
  function isRealMode(context) {
9
15
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
@@ -11,32 +17,70 @@ function isRealMode(context) {
11
17
  async function runAuthLogin(context, options) {
12
18
  let result;
13
19
  if (isRealMode(context)) {
14
- await performLogin(context.runtime.env);
15
- result = await readAuthState(context.runtime.env);
20
+ await performLogin(context.runtime.env, context.runtime.signal);
21
+ result = await readAuthState(context.runtime.env, context.runtime.signal);
16
22
  } else result = await loginWithSelectionFlow(context, createAuthUseCases(createCliUseCaseGateways(context)), options);
17
- return createAuthSuccess("auth.login", result, ["prisma-cli auth whoami", "prisma-cli project list"]);
23
+ const agentSetupTipCommand = await resolveAgentSetupTipCommand(context);
24
+ if (agentSetupTipCommand) result = {
25
+ ...result,
26
+ agentSetupTip: { command: agentSetupTipCommand }
27
+ };
28
+ return createAuthSuccess("auth.login", result, [
29
+ "prisma-cli auth whoami",
30
+ "prisma-cli project list",
31
+ ...result.agentSetupTip ? [result.agentSetupTip.command] : []
32
+ ]);
18
33
  }
19
34
  async function runAuthLogout(context) {
20
35
  let result;
21
36
  if (isRealMode(context)) {
22
- await performLogout(context.runtime.env);
23
- result = await readAuthState(context.runtime.env);
37
+ await performLogout(context.runtime.env, context.runtime.signal);
38
+ result = await readAuthState(context.runtime.env, context.runtime.signal);
24
39
  } else result = await createAuthUseCases(createCliUseCaseGateways(context)).logout();
25
40
  return createAuthSuccess("auth.logout", result, ["prisma-cli auth login"]);
26
41
  }
27
42
  async function runAuthWhoAmI(context) {
28
43
  let result;
29
- if (isRealMode(context)) result = await readAuthState(context.runtime.env);
44
+ if (isRealMode(context)) result = await readAuthState(context.runtime.env, context.runtime.signal);
30
45
  else result = await createAuthUseCases(createCliUseCaseGateways(context)).whoami();
31
46
  return createAuthSuccess("auth.whoami", result, result.authenticated ? [] : ["prisma-cli auth login"]);
32
47
  }
48
+ async function runAuthWorkspaceList(context) {
49
+ const result = isRealMode(context) ? await listRealAuthWorkspaces(context) : await createAuthUseCases(createCliUseCaseGateways(context)).listWorkspaces();
50
+ return {
51
+ command: "auth.workspace.list",
52
+ result,
53
+ warnings: [],
54
+ nextSteps: result.workspaces.length === 0 ? ["prisma-cli auth login"] : []
55
+ };
56
+ }
57
+ async function runAuthWorkspaceUse(context, workspaceRef) {
58
+ const trimmedWorkspaceRef = workspaceRef?.trim();
59
+ const selectedWorkspaceRef = trimmedWorkspaceRef ? trimmedWorkspaceRef : await selectWorkspaceSession(context);
60
+ return {
61
+ command: "auth.workspace.use",
62
+ result: isRealMode(context) ? await useRealAuthWorkspace(context, selectedWorkspaceRef) : await createAuthUseCases(createCliUseCaseGateways(context)).useWorkspace(selectedWorkspaceRef),
63
+ warnings: [],
64
+ nextSteps: ["prisma-cli auth whoami", "prisma-cli project list"]
65
+ };
66
+ }
67
+ async function runAuthWorkspaceLogout(context, workspaceRef) {
68
+ if (!workspaceRef?.trim()) throw usageError("Workspace required", "auth workspace logout needs a workspace id or cached workspace name.", "Pass a workspace from prisma-cli auth workspace list.", ["prisma-cli auth workspace list"], "auth");
69
+ const result = isRealMode(context) ? await logoutRealAuthWorkspace(context, workspaceRef) : await createAuthUseCases(createCliUseCaseGateways(context)).logoutWorkspace(workspaceRef);
70
+ return {
71
+ command: "auth.workspace.logout",
72
+ result,
73
+ warnings: [],
74
+ nextSteps: result.activeWorkspace ? ["prisma-cli auth workspace list"] : ["prisma-cli auth workspace list", "prisma-cli auth workspace use <id>"]
75
+ };
76
+ }
33
77
  async function requireAuthenticatedAuthState(context) {
34
78
  if (isRealMode(context)) {
35
- const current = await readAuthState(context.runtime.env);
79
+ const current = await readAuthState(context.runtime.env, context.runtime.signal);
36
80
  if (current.authenticated) return current;
37
81
  if (!canPrompt(context)) throw authRequiredError();
38
- await performLogin(context.runtime.env);
39
- return readAuthState(context.runtime.env);
82
+ await performLogin(context.runtime.env, context.runtime.signal);
83
+ return readAuthState(context.runtime.env, context.runtime.signal);
40
84
  }
41
85
  const useCases = createAuthUseCases(createCliUseCaseGateways(context));
42
86
  const current = await useCases.whoami();
@@ -44,6 +88,183 @@ async function requireAuthenticatedAuthState(context) {
44
88
  if (!canPrompt(context)) throw authRequiredError();
45
89
  return loginWithSelectionFlow(context, useCases, {});
46
90
  }
91
+ async function listRealAuthWorkspaces(context) {
92
+ const rawServiceToken = context.runtime.env[SERVICE_TOKEN_ENV_VAR];
93
+ const storage = new FileTokenStorage(context.runtime.env, context.runtime.signal);
94
+ const localWorkspaces = await hydrateLocalAuthWorkspaces(context, storage, await storage.listWorkspaces());
95
+ if (rawServiceToken !== void 0) {
96
+ const authState = await readAuthState(context.runtime.env, context.runtime.signal);
97
+ return {
98
+ authSource: authState.authenticated ? "service_token" : "none",
99
+ activeWorkspace: authState.workspace,
100
+ workspaces: [...authState.workspace ? [{
101
+ ...authState.workspace,
102
+ credentialWorkspaceId: null,
103
+ active: true,
104
+ source: "service_token",
105
+ switchable: false,
106
+ lastSeenAt: null
107
+ }] : [], ...localWorkspaces.map((workspace) => ({
108
+ ...toAuthWorkspace(workspace),
109
+ credentialWorkspaceId: workspace.credentialWorkspaceId,
110
+ active: false,
111
+ source: "oauth",
112
+ switchable: false,
113
+ lastSeenAt: workspace.lastSeenAt
114
+ }))]
115
+ };
116
+ }
117
+ const active = localWorkspaces.find((workspace) => workspace.active) ?? null;
118
+ return {
119
+ authSource: localWorkspaces.length > 0 ? "oauth" : "none",
120
+ activeWorkspace: active ? toAuthWorkspace(active) : null,
121
+ workspaces: localWorkspaces.map((workspace) => ({
122
+ ...toAuthWorkspace(workspace),
123
+ credentialWorkspaceId: workspace.credentialWorkspaceId,
124
+ active: workspace.active,
125
+ source: "oauth",
126
+ switchable: true,
127
+ lastSeenAt: workspace.lastSeenAt
128
+ }))
129
+ };
130
+ }
131
+ async function useRealAuthWorkspace(context, workspaceRef) {
132
+ if (context.runtime.env["PRISMA_SERVICE_TOKEN"] !== void 0) throw workspaceSwitchUnavailableError();
133
+ const storage = new FileTokenStorage(context.runtime.env, context.runtime.signal);
134
+ await hydrateLocalAuthWorkspaces(context, storage, await storage.listWorkspaces());
135
+ try {
136
+ const result = await storage.useWorkspace(workspaceRef);
137
+ return {
138
+ previousWorkspace: result.previous ? toAuthWorkspace(result.previous) : null,
139
+ workspace: toAuthWorkspace(result.selected)
140
+ };
141
+ } catch (error) {
142
+ if (error instanceof WorkspaceSelectionError) {
143
+ if (error.reason === "ambiguous") throw workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
144
+ id: match.id,
145
+ name: match.name,
146
+ credentialWorkspaceId: match.credentialWorkspaceId
147
+ })));
148
+ throw workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
149
+ }
150
+ throw error;
151
+ }
152
+ }
153
+ async function logoutRealAuthWorkspace(context, workspaceRef) {
154
+ const storage = new FileTokenStorage(context.runtime.env, context.runtime.signal);
155
+ await hydrateLocalAuthWorkspaces(context, storage, await storage.listWorkspaces());
156
+ try {
157
+ const result = await storage.logoutWorkspace(workspaceRef);
158
+ return {
159
+ workspace: toAuthWorkspace(result.workspace),
160
+ wasActive: result.wasActive,
161
+ activeWorkspace: result.activeWorkspace ? toAuthWorkspace(result.activeWorkspace) : null
162
+ };
163
+ } catch (error) {
164
+ if (error instanceof WorkspaceSelectionError) {
165
+ if (error.reason === "ambiguous") throw workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
166
+ id: match.id,
167
+ name: match.name,
168
+ credentialWorkspaceId: match.credentialWorkspaceId
169
+ })));
170
+ throw workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
171
+ }
172
+ throw error;
173
+ }
174
+ }
175
+ async function selectWorkspaceSession(context) {
176
+ const realMode = isRealMode(context);
177
+ if (realMode && context.runtime.env["PRISMA_SERVICE_TOKEN"] !== void 0) throw workspaceSwitchUnavailableError();
178
+ const workspaces = (realMode ? await listRealAuthWorkspaces(context) : await createAuthUseCases(createCliUseCaseGateways(context)).listWorkspaces()).workspaces.filter((workspace) => workspace.switchable);
179
+ if (workspaces.length === 0) throw usageError("No authenticated workspaces", "There are no local OAuth workspace sessions to select.", "Run prisma-cli auth login and authorize a workspace.", ["prisma-cli auth login"], "auth");
180
+ if (workspaces.length === 1) return workspaces[0].id;
181
+ if (!canPrompt(context)) throw usageError("Interactive workspace selection unavailable", "auth workspace use needs an interactive terminal when no workspace is provided and more than one workspace is available.", "Run prisma-cli auth workspace use <id-or-name> with a workspace from prisma-cli auth workspace list.", ["prisma-cli auth workspace list"], "auth");
182
+ return (await createSelectPromptPort(context).select({
183
+ message: "Select a workspace",
184
+ choices: workspaces.map((workspace) => ({
185
+ label: `${workspace.name} (${workspace.id})${workspace.active ? " active" : ""}`,
186
+ value: workspace
187
+ }))
188
+ })).id;
189
+ }
190
+ async function hydrateLocalAuthWorkspaces(context, storage, workspaces) {
191
+ const candidates = workspaces.filter(needsWorkspaceMetadataHydration);
192
+ if (candidates.length === 0) return workspaces;
193
+ const tokensByCredentialWorkspaceId = new Map((await storage.listWorkspaceTokens()).map((tokens) => [tokens.workspaceId, tokens]));
194
+ let nextWorkspaces = workspaces;
195
+ for (const workspace of candidates) {
196
+ const tokens = tokensByCredentialWorkspaceId.get(workspace.credentialWorkspaceId);
197
+ if (!tokens) continue;
198
+ const resolved = await resolveOAuthWorkspaceMetadata(context, tokens);
199
+ if (!resolved) continue;
200
+ await rememberResolvedWorkspaceMetadata(context, storage, tokens, resolved);
201
+ nextWorkspaces = nextWorkspaces.map((candidate) => candidate.credentialWorkspaceId === workspace.credentialWorkspaceId ? {
202
+ ...candidate,
203
+ id: resolved.id,
204
+ name: resolved.name,
205
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
206
+ } : candidate);
207
+ }
208
+ return nextWorkspaces;
209
+ }
210
+ async function rememberResolvedWorkspaceMetadata(context, storage, tokens, resolved) {
211
+ try {
212
+ await storage.rememberWorkspace(tokens.workspaceId, resolved);
213
+ } catch {
214
+ context.runtime.signal?.throwIfAborted();
215
+ }
216
+ }
217
+ function needsWorkspaceMetadataHydration(workspace) {
218
+ return workspace.id === workspace.credentialWorkspaceId || workspace.name === "Unknown workspace" || workspace.name === workspace.credentialWorkspaceId;
219
+ }
220
+ async function resolveOAuthWorkspaceMetadata(context, tokens) {
221
+ const sdk = createManagementApiSdk({
222
+ clientId: CLIENT_ID,
223
+ redirectUri: "http://localhost:0/auth/callback",
224
+ tokenStorage: createSingleWorkspaceTokenStorage(new FileTokenStorage(context.runtime.env, context.runtime.signal, { activateOnSetTokens: false }), tokens),
225
+ apiBaseUrl: getApiBaseUrl(context.runtime.env)
226
+ });
227
+ try {
228
+ const { data } = await sdk.client.GET("/v1/workspaces/{id}", {
229
+ params: { path: { id: tokens.workspaceId } },
230
+ signal: context.runtime.signal
231
+ });
232
+ const id = stringOrNull(data?.data?.id) ?? tokens.workspaceId;
233
+ const name = stringOrNull(data?.data?.name) ?? id;
234
+ if (id === tokens.workspaceId && name === tokens.workspaceId) return null;
235
+ return {
236
+ id,
237
+ name
238
+ };
239
+ } catch {
240
+ context.runtime.signal?.throwIfAborted();
241
+ return null;
242
+ }
243
+ }
244
+ function createSingleWorkspaceTokenStorage(storage, initialTokens) {
245
+ let currentTokens = initialTokens;
246
+ return {
247
+ getTokens: async () => currentTokens,
248
+ setTokens: async (tokens) => {
249
+ currentTokens = tokens;
250
+ await storage.setTokens(tokens);
251
+ },
252
+ clearTokens: async () => {
253
+ const tokens = currentTokens;
254
+ currentTokens = null;
255
+ if (tokens) await storage.clearTokensIfCurrent(tokens);
256
+ }
257
+ };
258
+ }
259
+ function stringOrNull(value) {
260
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
261
+ }
262
+ function toAuthWorkspace(workspace) {
263
+ return {
264
+ id: workspace.id,
265
+ name: workspace.name
266
+ };
267
+ }
47
268
  async function loginWithSelectionFlow(context, useCases, options) {
48
269
  const selection = await resolveLoginSelection(useCases, canPrompt(context) ? createSelectPromptPort(context) : null, options);
49
270
  return useCases.login(selection);
@@ -103,5 +324,28 @@ function createAuthSuccess(command, result, nextSteps) {
103
324
  nextSteps
104
325
  };
105
326
  }
327
+ async function resolveAgentSetupTipCommand(context) {
328
+ if (context.flags.json || context.flags.quiet) return null;
329
+ if (context.runtime.env.CI && context.flags.interactive !== true) return null;
330
+ if (!context.runtime.stderr.isTTY && context.flags.interactive !== true) return null;
331
+ const setupCwd = await resolvePrismaAgentSetupCwd({
332
+ cwd: context.runtime.cwd,
333
+ signal: context.runtime.signal
334
+ });
335
+ if (!await isLikelyProjectDirectory({
336
+ cwd: setupCwd,
337
+ signal: context.runtime.signal
338
+ })) return null;
339
+ if (!shouldOfferPrismaAgentSetup(await readPrismaAgentSetupStatus({
340
+ cwd: setupCwd,
341
+ stateStore: context.stateStore,
342
+ signal: context.runtime.signal
343
+ }))) return null;
344
+ return await resolvePrismaCliPackageCommand({
345
+ cwd: setupCwd,
346
+ signal: context.runtime.signal,
347
+ args: PRISMA_AGENT_INSTALL_ARGS
348
+ });
349
+ }
106
350
  //#endregion
107
- export { requireAuthenticatedAuthState, runAuthLogin, runAuthLogout, runAuthWhoAmI };
351
+ export { requireAuthenticatedAuthState, runAuthLogin, runAuthLogout, runAuthWhoAmI, runAuthWorkspaceList, runAuthWorkspaceLogout, runAuthWorkspaceUse };
@@ -1,73 +1,103 @@
1
- import { featureUnavailableError, usageError } from "../shell/errors.js";
2
- import { canPrompt } from "../shell/runtime.js";
1
+ import { CliError, authRequiredError, workspaceRequiredError } from "../shell/errors.js";
2
+ import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
3
+ import { requireComputeAuth } from "../lib/auth/guard.js";
3
4
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
4
- import { createSelectPromptPort } from "./select-prompt-port.js";
5
+ import { requireAuthenticatedAuthState } from "./auth.js";
6
+ import { listRealWorkspaceProjects } from "./project.js";
5
7
  import { createBranchUseCases } from "../use-cases/branch.js";
6
8
  //#region src/controllers/branch.ts
7
- const PREVIEW_BRANCH_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
8
9
  function isRealMode(context) {
9
10
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
10
11
  }
11
12
  async function runBranchList(context) {
12
- if (isRealMode(context)) throw branchCommandsUnavailableError();
13
- return {
13
+ if (isRealMode(context)) return {
14
14
  command: "branch.list",
15
- result: await createBranchUseCases(createCliUseCaseGateways(context)).list(),
15
+ result: await listRealBranches(context),
16
16
  warnings: [],
17
17
  nextSteps: []
18
18
  };
19
- }
20
- async function runBranchShow(context) {
21
- if (isRealMode(context)) throw branchCommandsUnavailableError();
22
- const result = await createBranchUseCases(createCliUseCaseGateways(context)).show();
23
19
  return {
24
- command: "branch.show",
25
- result,
20
+ command: "branch.list",
21
+ result: await createBranchUseCases(createCliUseCaseGateways(context)).list(),
26
22
  warnings: [],
27
- nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma-cli app deploy"] : []
23
+ nextSteps: []
28
24
  };
29
25
  }
30
- async function runBranchUse(context, branchName) {
31
- if (isRealMode(context)) throw branchCommandsUnavailableError();
32
- const useCases = createBranchUseCases(createCliUseCaseGateways(context));
33
- const resolvedBranchName = await resolveBranchNameForUse(context, useCases, branchName);
34
- validateBranchName(resolvedBranchName);
35
- const result = await useCases.use(resolvedBranchName);
26
+ async function listRealBranches(context) {
27
+ const authState = await requireAuthenticatedAuthState(context);
28
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
29
+ if (!client) throw authRequiredError(["prisma-cli auth login"]);
30
+ const workspace = authState.workspace;
31
+ if (!workspace) throw workspaceRequiredError();
32
+ const targetResult = await resolveProjectTarget({
33
+ context,
34
+ workspace,
35
+ listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal)
36
+ });
37
+ if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
38
+ const target = targetResult.value;
39
+ const branches = await listBranches(client, target.project.id, context.runtime.signal);
36
40
  return {
37
- command: "branch.use",
38
- result,
39
- warnings: result.branch.kind === "production" ? ["Production is protected and durable. Use with care."] : [],
40
- nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma-cli branch show", "prisma-cli app deploy"] : ["prisma-cli branch show"]
41
+ projectId: target.project.id,
42
+ projectName: target.project.name,
43
+ verboseContext: {
44
+ workspace,
45
+ project: target.project,
46
+ resolution: target.resolution
47
+ },
48
+ branches: sortBranches(branches.map(toBranchSummary))
41
49
  };
42
50
  }
43
- async function resolveBranchNameForUse(context, useCases, branchName) {
44
- if (branchName) return branchName;
45
- if (!canPrompt(context)) throw branchSelectionRequiredError();
46
- const result = await useCases.list();
47
- return createSelectPromptPort(context).select({
48
- message: "Select a branch",
49
- choices: result.branches.map((branch) => ({
50
- label: renderBranchChoiceLabel(branch),
51
- value: branch.name
52
- }))
51
+ function sortBranches(branches) {
52
+ return branches.slice().sort((left, right) => {
53
+ const leftRank = branchOrder(left);
54
+ const rightRank = branchOrder(right);
55
+ if (leftRank !== rightRank) return leftRank - rightRank;
56
+ return left.name.localeCompare(right.name);
53
57
  });
54
58
  }
55
- function renderBranchChoiceLabel(branch) {
56
- const markers = [];
57
- if (branch.active) markers.push("active");
58
- if (!branch.remoteState) markers.push("not created yet");
59
- return markers.length > 0 ? `${branch.name} (${markers.join(", ")})` : branch.name;
59
+ function branchOrder(branch) {
60
+ return branch.role === "production" ? 0 : 1;
60
61
  }
61
- function validateBranchName(branchName) {
62
- if (branchName === "production") return;
63
- if (PREVIEW_BRANCH_PATTERN.test(branchName)) return;
64
- throw usageError("Branch name must use the documented form", "Branch names must be production or a lowercase preview slug such as preview or feat-auth.", "Use production or a lowercase preview branch name with letters, numbers, and hyphens.", ["prisma-cli branch list"], "branch");
62
+ async function listBranches(client, projectId, signal) {
63
+ const collected = [];
64
+ let cursor;
65
+ while (true) {
66
+ const query = {};
67
+ if (cursor !== void 0) query.cursor = cursor;
68
+ const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
69
+ params: {
70
+ path: { projectId },
71
+ query
72
+ },
73
+ signal
74
+ });
75
+ if (error || !data) throw branchApiError("Failed to list branches", response, error);
76
+ collected.push(...data.data);
77
+ if (!data.pagination.hasMore || !data.pagination.nextCursor) break;
78
+ cursor = data.pagination.nextCursor;
79
+ }
80
+ return collected;
65
81
  }
66
- function branchSelectionRequiredError() {
67
- return usageError("Branch use requires a target in non-interactive mode", "This command cannot prompt for branch selection in the current mode.", "Re-run prisma-cli branch use in a TTY, or pass a branch name explicitly.", ["prisma-cli branch list"], "branch");
82
+ function toBranchSummary(branch) {
83
+ return {
84
+ id: branch.id,
85
+ name: branch.gitName,
86
+ role: branch.role,
87
+ envMap: branch.role
88
+ };
68
89
  }
69
- function branchCommandsUnavailableError() {
70
- return featureUnavailableError("Branch commands are not available in this preview", "The current preview cannot resolve or change remote branch context yet.", "Use prisma-cli app deploy for preview app deployment workflows.", ["prisma-cli app deploy --app <name>"], "branch");
90
+ function branchApiError(summary, response, error) {
91
+ const status = response?.status ?? 0;
92
+ return new CliError({
93
+ code: error?.error?.code ?? "BRANCH_API_ERROR",
94
+ domain: "branch",
95
+ summary,
96
+ why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
97
+ fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
98
+ exitCode: 1,
99
+ nextSteps: []
100
+ });
71
101
  }
72
102
  //#endregion
73
- export { runBranchList, runBranchShow, runBranchUse };
103
+ export { runBranchList };
@@ -0,0 +1,88 @@
1
+ import { CliError, authRequiredError } from "../shell/errors.js";
2
+ import { writeJsonEvent } from "../shell/output.js";
3
+ import { requireComputeAuth } from "../lib/auth/guard.js";
4
+ //#region src/controllers/build.ts
5
+ async function runBuildLogs(context, buildId, options = {}) {
6
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
7
+ if (!client) throw authRequiredError(["prisma-cli auth login"]);
8
+ if (!context.flags.json && !context.flags.quiet) context.output.stderr.write(`build logs → Streaming logs for build ${buildId}\n\n`);
9
+ const { data, response } = await client.GET("/v1/builds/{buildId}/logs", {
10
+ params: {
11
+ path: { buildId },
12
+ query: {
13
+ ...options.follow ? { follow: "true" } : {},
14
+ ...options.cursor ? { cursor: options.cursor } : {}
15
+ }
16
+ },
17
+ parseAs: "stream",
18
+ signal: context.runtime.signal
19
+ });
20
+ const body = data;
21
+ if (!response.ok || !body) throw buildLogsRequestError(buildId, response.status);
22
+ let sawError = false;
23
+ await forEachNdjsonRecord(body, (record) => {
24
+ if (record.type === "terminal" && record.kind === "error") sawError = true;
25
+ writeBuildLogRecord(context, record);
26
+ });
27
+ if (sawError) process.exitCode = 1;
28
+ }
29
+ function writeBuildLogRecord(context, record) {
30
+ if (context.flags.json) {
31
+ writeJsonEvent(context.output, {
32
+ type: record.type,
33
+ command: "build.logs",
34
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
35
+ data: record
36
+ });
37
+ return;
38
+ }
39
+ if (record.type === "log") {
40
+ const stream = record.source === "stderr" || record.level === "error" ? context.output.stderr : context.output.stdout;
41
+ stream.write(record.text);
42
+ if (!record.text.endsWith("\n")) stream.write("\n");
43
+ return;
44
+ }
45
+ if (record.code !== "end") context.output.stderr.write(`${record.message}\n`);
46
+ }
47
+ /** Reads a newline-delimited JSON body line by line, parsing each into a record. */
48
+ async function forEachNdjsonRecord(body, onRecord) {
49
+ const reader = body.getReader();
50
+ const decoder = new TextDecoder();
51
+ let buffer = "";
52
+ for (;;) {
53
+ const { done, value } = await reader.read();
54
+ if (value) buffer += decoder.decode(value, { stream: true });
55
+ let newlineIndex = buffer.indexOf("\n");
56
+ while (newlineIndex !== -1) {
57
+ const line = buffer.slice(0, newlineIndex).trim();
58
+ buffer = buffer.slice(newlineIndex + 1);
59
+ if (line) onRecord(JSON.parse(line));
60
+ newlineIndex = buffer.indexOf("\n");
61
+ }
62
+ if (done) {
63
+ const tail = buffer.trim();
64
+ if (tail) onRecord(JSON.parse(tail));
65
+ return;
66
+ }
67
+ }
68
+ }
69
+ function buildLogsRequestError(buildId, status) {
70
+ if (status === 404) return new CliError({
71
+ code: "BUILD_NOT_FOUND",
72
+ domain: "app",
73
+ summary: `Build ${buildId} was not found`,
74
+ why: "The build does not exist, or your workspace does not have access to it.",
75
+ fix: "Check the build id, or run prisma-cli auth login to switch to the workspace that owns it.",
76
+ exitCode: 1
77
+ });
78
+ return new CliError({
79
+ code: "BUILD_LOGS_FAILED",
80
+ domain: "app",
81
+ summary: `Failed to read logs for build ${buildId}`,
82
+ why: `The Management API returned HTTP ${status}.`,
83
+ fix: "Retry the command, or rerun with --trace for more detail.",
84
+ exitCode: 1
85
+ });
86
+ }
87
+ //#endregion
88
+ export { runBuildLogs };