@prisma/cli 3.0.0-alpha.2 → 3.0.0-alpha.3

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.
@@ -4,7 +4,7 @@ import { configureRuntimeCommand } from "../../shell/runtime.js";
4
4
  import { PREVIEW_BUILD_TYPES } from "../../lib/app/preview-build.js";
5
5
  import { runAppBuild, runAppDeploy, runAppListDeploys, runAppListEnv, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy, runAppUpdateEnv } from "../../controllers/app.js";
6
6
  import { renderAppBuild, renderAppDeploy, renderAppListDeploys, renderAppListEnv, renderAppOpen, renderAppPromote, renderAppRemove, renderAppRollback, renderAppRun, renderAppShow, renderAppShowDeploy, renderAppUpdateEnv, serializeAppBuild, serializeAppDeploy, serializeAppListDeploys, serializeAppListEnv, serializeAppOpen, serializeAppPromote, serializeAppRemove, serializeAppRollback, serializeAppRun, serializeAppShow, serializeAppShowDeploy, serializeAppUpdateEnv } from "../../presenters/app.js";
7
- import { runCommand } from "../../shell/command-runner.js";
7
+ import { runCommand, runStreamingCommand } from "../../shell/command-runner.js";
8
8
  import { Command, Option } from "commander";
9
9
  //#region src/commands/app/index.ts
10
10
  function createAppCommand(runtime) {
@@ -140,7 +140,7 @@ function createLogsCommand(runtime) {
140
140
  command.action(async (options) => {
141
141
  const appName = options.app;
142
142
  const deploymentId = options.deployment;
143
- await runCommand(runtime, "app.logs", options, (context) => runAppLogs(context, appName, deploymentId), { renderHuman: () => [] });
143
+ await runStreamingCommand(runtime, "app.logs", options, (context) => runAppLogs(context, appName, deploymentId));
144
144
  });
145
145
  return command;
146
146
  }
@@ -0,0 +1,71 @@
1
+ import { attachCommandDescriptor } from "../shell/command-meta.js";
2
+ import { addGlobalFlags } from "../shell/global-flags.js";
3
+ import { configureRuntimeCommand } from "../shell/runtime.js";
4
+ import { runCommand } from "../shell/command-runner.js";
5
+ import { runEnvAdd, runEnvList, runEnvRm, runEnvUpdate } from "../controllers/app-env.js";
6
+ import { renderEnvAdd, renderEnvList, renderEnvRm, renderEnvUpdate, serializeEnvAdd, serializeEnvList, serializeEnvRm, serializeEnvUpdate } from "../presenters/app-env.js";
7
+ import { Command, Option } from "commander";
8
+ //#region src/commands/env.ts
9
+ function createEnvCommand(runtime) {
10
+ const env = attachCommandDescriptor(configureRuntimeCommand(new Command("env"), runtime), "project.env");
11
+ env.description("Manage environment variables for the linked project.");
12
+ env.addCommand(createEnvAddCommand(runtime));
13
+ env.addCommand(createEnvUpdateCommand(runtime));
14
+ env.addCommand(createEnvListCommand(runtime));
15
+ env.addCommand(createEnvRmCommand(runtime));
16
+ return env;
17
+ }
18
+ function createEnvAddCommand(runtime) {
19
+ const command = attachCommandDescriptor(configureRuntimeCommand(new Command("add"), runtime), "project.env.add");
20
+ command.argument("<assignment>", "Variable assignment in KEY=VALUE form").addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"]));
21
+ addGlobalFlags(command);
22
+ command.action(async (assignment, options) => {
23
+ const roleName = options.role;
24
+ await runCommand(runtime, "project.env.add", options, (context) => runEnvAdd(context, assignment, { roleName }), {
25
+ renderHuman: (context, descriptor, result) => renderEnvAdd(context, descriptor, result),
26
+ renderJson: (result) => serializeEnvAdd(result)
27
+ });
28
+ });
29
+ return command;
30
+ }
31
+ function createEnvUpdateCommand(runtime) {
32
+ const command = attachCommandDescriptor(configureRuntimeCommand(new Command("update"), runtime), "project.env.update");
33
+ command.argument("<assignment>", "Variable assignment in KEY=VALUE form").addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"]));
34
+ addGlobalFlags(command);
35
+ command.action(async (assignment, options) => {
36
+ const roleName = options.role;
37
+ await runCommand(runtime, "project.env.update", options, (context) => runEnvUpdate(context, assignment, { roleName }), {
38
+ renderHuman: (context, descriptor, result) => renderEnvUpdate(context, descriptor, result),
39
+ renderJson: (result) => serializeEnvUpdate(result)
40
+ });
41
+ });
42
+ return command;
43
+ }
44
+ function createEnvListCommand(runtime) {
45
+ const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "project.env.list");
46
+ command.addOption(new Option("--role <role>", "Project template scope").choices(["production", "preview"]));
47
+ addGlobalFlags(command);
48
+ command.action(async (options) => {
49
+ const roleName = options.role;
50
+ await runCommand(runtime, "project.env.list", options, (context) => runEnvList(context, { roleName }), {
51
+ renderHuman: (context, descriptor, result) => renderEnvList(context, descriptor, result),
52
+ renderJson: (result) => serializeEnvList(result)
53
+ });
54
+ });
55
+ return command;
56
+ }
57
+ function createEnvRmCommand(runtime) {
58
+ const command = attachCommandDescriptor(configureRuntimeCommand(new Command("rm"), runtime), "project.env.rm");
59
+ command.argument("<key>", "Variable key to remove").addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"]));
60
+ addGlobalFlags(command);
61
+ command.action(async (key, options) => {
62
+ const roleName = options.role;
63
+ await runCommand(runtime, "project.env.rm", options, (context) => runEnvRm(context, key, { roleName }), {
64
+ renderHuman: (context, descriptor, result) => renderEnvRm(context, descriptor, result),
65
+ renderJson: (result) => serializeEnvRm(result)
66
+ });
67
+ });
68
+ return command;
69
+ }
70
+ //#endregion
71
+ export { createEnvCommand };
@@ -4,6 +4,7 @@ import { configureRuntimeCommand } from "../../shell/runtime.js";
4
4
  import { runCommand } from "../../shell/command-runner.js";
5
5
  import { runProjectLink, runProjectList, runProjectShow } from "../../controllers/project.js";
6
6
  import { renderProjectLink, renderProjectList, renderProjectShow, serializeProjectList } from "../../presenters/project.js";
7
+ import { createEnvCommand } from "../env.js";
7
8
  import { Command } from "commander";
8
9
  //#region src/commands/project/index.ts
9
10
  function createProjectCommand(runtime) {
@@ -12,6 +13,7 @@ function createProjectCommand(runtime) {
12
13
  project.addCommand(createProjectListCommand(runtime));
13
14
  project.addCommand(createProjectShowCommand(runtime));
14
15
  project.addCommand(createProjectLinkCommand(runtime));
16
+ project.addCommand(createEnvCommand(runtime));
15
17
  return project;
16
18
  }
17
19
  function createProjectListCommand(runtime) {
@@ -0,0 +1,221 @@
1
+ import { readLinkedProjectId } from "../adapters/config.js";
2
+ import { CliError, authRequiredError, usageError } from "../shell/errors.js";
3
+ import { requireComputeAuth } from "../lib/auth/guard.js";
4
+ import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
5
+ //#region src/controllers/app-env.ts
6
+ function defaultRoleScope() {
7
+ return {
8
+ kind: "role",
9
+ role: "production"
10
+ };
11
+ }
12
+ async function runEnvAdd(context, rawAssignment, flags) {
13
+ const { key, value } = parseKeyValuePositional(rawAssignment, "add");
14
+ const scope = resolveEnvScope(flags, {
15
+ requireExplicit: true,
16
+ command: "add"
17
+ });
18
+ if (!scope) throw usageError(`prisma-cli project env add requires --role`, "Writing without an explicit scope is rejected.", "Pass --role production or --role preview.", [`prisma-cli project env add ${key}=${value} --role production`], "app");
19
+ const { client, projectId } = await requireClientAndProject(context);
20
+ const resolved = resolveScopeToApi(scope);
21
+ if (await findVariableByNaturalKey(client, projectId, key, resolved)) throw new CliError({
22
+ code: "ENV_VARIABLE_ALREADY_EXISTS",
23
+ domain: "app",
24
+ summary: `Variable "${key}" already exists in ${formatScopeLabel(scope)}`,
25
+ why: "A variable with this key already exists in the targeted scope.",
26
+ fix: "Use `prisma-cli project env update` to change an existing variable's value.",
27
+ exitCode: 1,
28
+ nextSteps: [`prisma-cli project env update ${key}=<new-value> --role ${scope.role}`]
29
+ });
30
+ const { data, error, response } = await client.POST("/v1/environment-variables", { body: {
31
+ projectId,
32
+ class: resolved.apiTarget.class,
33
+ key,
34
+ value
35
+ } });
36
+ if (error || !data) throw apiCallError(`Failed to add ${key}`, response, error);
37
+ return {
38
+ command: "project.env.add",
39
+ result: {
40
+ projectId,
41
+ scope: resolved.descriptor,
42
+ variable: toMetadata(data.data, resolved.descriptor)
43
+ },
44
+ warnings: [],
45
+ nextSteps: []
46
+ };
47
+ }
48
+ async function runEnvUpdate(context, rawAssignment, flags) {
49
+ const { key, value } = parseKeyValuePositional(rawAssignment, "update");
50
+ const scope = resolveEnvScope(flags, {
51
+ requireExplicit: true,
52
+ command: "update"
53
+ });
54
+ if (!scope) throw usageError(`prisma-cli project env update requires --role`, "Writing without an explicit scope is rejected.", "Pass --role production or --role preview.", [`prisma-cli project env update ${key}=${value} --role production`], "app");
55
+ const { client, projectId } = await requireClientAndProject(context);
56
+ const resolved = resolveScopeToApi(scope);
57
+ const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
58
+ if (!existing) throw new CliError({
59
+ code: "ENV_VARIABLE_NOT_FOUND",
60
+ domain: "app",
61
+ summary: `Variable "${key}" not found in ${formatScopeLabel(scope)}`,
62
+ why: "No variable with this key exists in the targeted scope.",
63
+ fix: "Use `prisma-cli project env add` to create a new variable.",
64
+ exitCode: 1,
65
+ nextSteps: [`prisma-cli project env add ${key}=<value> --role ${scope.role}`]
66
+ });
67
+ const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
68
+ params: { path: { envVarId: existing.id } },
69
+ body: { value }
70
+ });
71
+ if (error || !data) throw apiCallError(`Failed to update value for ${key}`, response, error);
72
+ return {
73
+ command: "project.env.update",
74
+ result: {
75
+ projectId,
76
+ scope: resolved.descriptor,
77
+ variable: toMetadata(data.data, resolved.descriptor)
78
+ },
79
+ warnings: [],
80
+ nextSteps: []
81
+ };
82
+ }
83
+ async function runEnvList(context, flags) {
84
+ const scope = resolveEnvScope(flags, {
85
+ requireExplicit: false,
86
+ command: "list"
87
+ }) ?? defaultRoleScope();
88
+ const { client, projectId } = await requireClientAndProject(context);
89
+ const resolved = resolveScopeToApi(scope);
90
+ const variables = await listVariables(client, projectId, resolved);
91
+ return {
92
+ command: "project.env.list",
93
+ result: {
94
+ projectId,
95
+ scope: resolved.descriptor,
96
+ variables: variables.map((row) => toMetadata(row, resolved.descriptor))
97
+ },
98
+ warnings: [],
99
+ nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value --role ${scope.role}`] : []
100
+ };
101
+ }
102
+ async function runEnvRm(context, key, flags) {
103
+ if (!key) throw usageError("prisma-cli project env rm requires KEY", "No KEY positional argument was supplied.", "Pass the variable name to remove, e.g. STRIPE_KEY.", ["prisma-cli project env rm STRIPE_KEY --role production"], "app");
104
+ const scope = resolveEnvScope(flags, {
105
+ requireExplicit: true,
106
+ command: "rm"
107
+ });
108
+ if (!scope) throw usageError("prisma-cli project env rm requires --role", "Writing without an explicit scope is rejected.", "Pass --role production or --role preview.", [`prisma-cli project env rm ${key} --role production`], "app");
109
+ const { client, projectId } = await requireClientAndProject(context);
110
+ const resolved = resolveScopeToApi(scope);
111
+ const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
112
+ if (!existing) throw new CliError({
113
+ code: "ENV_VARIABLE_NOT_FOUND",
114
+ domain: "app",
115
+ summary: `Variable "${key}" not found in ${formatScopeLabel(scope)}`,
116
+ why: "No variable with this key exists in the targeted scope, so there is nothing to remove.",
117
+ fix: "Run prisma-cli project env list with the same scope to see the available variables.",
118
+ exitCode: 1,
119
+ nextSteps: [`prisma-cli project env list --role ${scope.role}`]
120
+ });
121
+ const { error, response } = await client.DELETE("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: existing.id } } });
122
+ if (error) throw apiCallError(`Failed to remove ${key}`, response, error);
123
+ return {
124
+ command: "project.env.rm",
125
+ result: {
126
+ projectId,
127
+ scope: resolved.descriptor,
128
+ key
129
+ },
130
+ warnings: [],
131
+ nextSteps: []
132
+ };
133
+ }
134
+ async function requireClientAndProject(context) {
135
+ const projectId = await readLinkedProjectId(context.runtime.cwd);
136
+ if (!projectId) throw new CliError({
137
+ code: "PROJECT_NOT_LINKED",
138
+ domain: "project",
139
+ summary: "Project link required",
140
+ why: "prisma-cli project env needs a linked project for the current repo.",
141
+ fix: "Run prisma project link before managing environment variables.",
142
+ exitCode: 1,
143
+ nextSteps: ["prisma project link"]
144
+ });
145
+ const client = await requireComputeAuth(context.runtime.env);
146
+ if (!client) throw authRequiredError(["prisma auth login"]);
147
+ return {
148
+ client,
149
+ projectId
150
+ };
151
+ }
152
+ function resolveScopeToApi(scope) {
153
+ return {
154
+ scope,
155
+ descriptor: {
156
+ kind: "role",
157
+ role: scope.role
158
+ },
159
+ apiTarget: {
160
+ class: scope.role,
161
+ branchId: null
162
+ }
163
+ };
164
+ }
165
+ async function findVariableByNaturalKey(client, projectId, key, resolved) {
166
+ const { data, error, response } = await client.GET("/v1/environment-variables", { params: { query: {
167
+ projectId,
168
+ class: resolved.apiTarget.class,
169
+ key
170
+ } } });
171
+ if (error || !data) throw apiCallError(`Failed to look up ${key}`, response, error);
172
+ return data.data.filter((row) => rowMatchesScope(row, resolved))[0] ?? null;
173
+ }
174
+ async function listVariables(client, projectId, resolved) {
175
+ const collected = [];
176
+ let cursor;
177
+ while (true) {
178
+ const query = {
179
+ projectId,
180
+ class: resolved.apiTarget.class
181
+ };
182
+ if (cursor !== void 0) query.cursor = cursor;
183
+ const result = await client.GET("/v1/environment-variables", { params: { query } });
184
+ if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
185
+ const page = result.data.data.filter((row) => rowMatchesScope(row, resolved));
186
+ collected.push(...page);
187
+ if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
188
+ cursor = result.data.pagination.nextCursor;
189
+ }
190
+ return collected;
191
+ }
192
+ function rowMatchesScope(row, resolved) {
193
+ return row.branchId === null && row.class === resolved.apiTarget.class;
194
+ }
195
+ function toMetadata(row, scope) {
196
+ return {
197
+ id: row.id,
198
+ key: row.key,
199
+ scope,
200
+ isManagedBySystem: row.isManagedBySystem,
201
+ updatedAt: row.updatedAt
202
+ };
203
+ }
204
+ function apiCallError(summary, response, error) {
205
+ const status = response?.status ?? 0;
206
+ const apiCode = error?.error?.code;
207
+ const apiMessage = error?.error?.message;
208
+ const apiHint = error?.error?.hint;
209
+ if (status === 401 || status === 403) return authRequiredError(["prisma auth login"]);
210
+ return new CliError({
211
+ code: apiCode ?? "ENV_API_ERROR",
212
+ domain: "app",
213
+ summary,
214
+ why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
215
+ fix: apiHint ?? "Re-run with --trace for the underlying API response details.",
216
+ exitCode: 1,
217
+ nextSteps: []
218
+ });
219
+ }
220
+ //#endregion
221
+ export { runEnvAdd, runEnvList, runEnvRm, runEnvUpdate };
@@ -1,5 +1,9 @@
1
1
  import { UnsafeConfigWriteError, assertLinkedProjectIdWritable, readLinkedProjectId, writeLinkedProjectId } from "../adapters/config.js";
2
+ import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
3
+ import { FileTokenStorage } from "../adapters/token-storage.js";
2
4
  import { CliError, authRequiredError, featureUnavailableError, usageError } from "../shell/errors.js";
5
+ import { renderCommandHeader } from "../shell/ui.js";
6
+ import { writeJsonEvent } from "../shell/output.js";
3
7
  import { canPrompt } from "../shell/runtime.js";
4
8
  import { textPrompt } from "../shell/prompt.js";
5
9
  import { requireComputeAuth } from "../lib/auth/guard.js";
@@ -118,6 +122,7 @@ async function runAppDeploy(context, appName, options) {
118
122
  }
119
123
  async function runAppUpdateEnv(context, appName, envAssignments) {
120
124
  ensurePreviewAppMode(context);
125
+ emitLegacyEnvDeprecationWarning(context, "app update-env", "project env add");
121
126
  const envVars = parseEnvAssignments(envAssignments, {
122
127
  commandName: "update-env",
123
128
  requireAtLeastOne: true
@@ -160,6 +165,7 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
160
165
  }
161
166
  async function runAppListEnv(context, appName) {
162
167
  ensurePreviewAppMode(context);
168
+ emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
163
169
  const projectId = await requireLinkedProjectId(context);
164
170
  const provider = await requirePreviewAppProvider(context);
165
171
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
@@ -395,9 +401,130 @@ async function runAppOpen(context, appName) {
395
401
  nextSteps: ["prisma-cli app show", `prisma-cli app show-deploy ${liveDeployment.id}`]
396
402
  };
397
403
  }
398
- async function runAppLogs(context, _appName, _deploymentId) {
404
+ async function runAppLogs(context, appName, deploymentId) {
399
405
  ensurePreviewAppMode(context);
400
- throw blockedPreviewAppCommandError("App logs are not available in this preview", "The current preview cannot stream app logs yet.");
406
+ const projectId = await requireLinkedProjectId(context);
407
+ const provider = await requirePreviewAppProvider(context);
408
+ const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, appName);
409
+ if (!context.flags.json && !context.flags.quiet) {
410
+ const lines = renderCommandHeader(context.ui, {
411
+ commandLabel: "app logs",
412
+ description: "Streaming logs for the selected deployment.",
413
+ docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
414
+ rows: [
415
+ {
416
+ key: "project",
417
+ value: projectId
418
+ },
419
+ {
420
+ key: "app",
421
+ value: target.app.name
422
+ },
423
+ {
424
+ key: "deployment",
425
+ value: target.deployment.id
426
+ }
427
+ ]
428
+ });
429
+ if (lines.length > 0) context.output.stderr.write(`${lines.join("\n")}\n`);
430
+ }
431
+ await provider.streamDeploymentLogs({
432
+ deploymentId: target.deployment.id,
433
+ onRecord: (record) => writeLogRecord(context, record)
434
+ }).catch((error) => {
435
+ throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
436
+ });
437
+ }
438
+ async function resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) {
439
+ if (appName) {
440
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
441
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The linked project does not have any deployed app yet.");
442
+ const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
443
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
444
+ });
445
+ const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
446
+ await context.stateStore.setSelectedApp(projectId, {
447
+ id: deploymentsResult.app.id,
448
+ name: deploymentsResult.app.name
449
+ });
450
+ return {
451
+ app: deploymentsResult.app,
452
+ deployment
453
+ };
454
+ }
455
+ const shown = await provider.showDeployment(deploymentId).catch((error) => {
456
+ throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
457
+ });
458
+ if (!shown) throw new CliError({
459
+ code: "DEPLOYMENT_NOT_FOUND",
460
+ domain: "app",
461
+ summary: `Deployment "${deploymentId}" not found`,
462
+ why: "The requested deployment does not exist or is no longer available.",
463
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id.",
464
+ exitCode: 1,
465
+ nextSteps: ["prisma-cli app list-deploys"]
466
+ });
467
+ if (!shown.app) throw new CliError({
468
+ code: "DEPLOYMENT_NOT_FOUND",
469
+ domain: "app",
470
+ summary: `Deployment "${deploymentId}" is not attached to an app`,
471
+ why: "The requested deployment could be found, but its app could not be resolved.",
472
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id for the selected app.",
473
+ exitCode: 1,
474
+ nextSteps: ["prisma-cli app list-deploys"]
475
+ });
476
+ const linkedProjectApp = (await listApps(context, provider, projectId)).find((app) => app.id === shown.app?.id);
477
+ if (!linkedProjectApp) throw new CliError({
478
+ code: "DEPLOYMENT_NOT_FOUND",
479
+ domain: "app",
480
+ summary: `Deployment "${deploymentId}" not found in the linked project`,
481
+ why: "The requested deployment does not belong to an app in the linked project.",
482
+ fix: "Run prisma-cli app list-deploys to choose an available deployment id for this project.",
483
+ exitCode: 1,
484
+ nextSteps: ["prisma-cli app list-deploys"]
485
+ });
486
+ await context.stateStore.setSelectedApp(projectId, {
487
+ id: linkedProjectApp.id,
488
+ name: linkedProjectApp.name
489
+ });
490
+ return {
491
+ app: linkedProjectApp,
492
+ deployment: shown.deployment
493
+ };
494
+ }
495
+ async function resolveLiveLogDeployment(context, provider, projectId, appName) {
496
+ const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
497
+ if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The linked project does not have any deployed app yet.");
498
+ const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
499
+ throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
500
+ });
501
+ const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
502
+ const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId);
503
+ const deployment = currentLiveDeploymentId ? deployments.find((candidate) => candidate.id === currentLiveDeploymentId) ?? null : null;
504
+ await context.stateStore.setSelectedApp(projectId, {
505
+ id: deploymentsResult.app.id,
506
+ name: deploymentsResult.app.name
507
+ });
508
+ if (!deployment) throw noDeploymentsError("No deployments available to stream logs", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
509
+ return {
510
+ app: deploymentsResult.app,
511
+ deployment
512
+ };
513
+ }
514
+ function writeLogRecord(context, record) {
515
+ if (context.flags.json) {
516
+ writeJsonEvent(context.output, {
517
+ type: record.type,
518
+ command: "app.logs",
519
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
520
+ data: record
521
+ });
522
+ return;
523
+ }
524
+ if (record.type === "log") {
525
+ context.output.stdout.write(record.text);
526
+ if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
527
+ }
401
528
  }
402
529
  async function runAppPromote(context, deploymentId, appName) {
403
530
  ensurePreviewAppMode(context);
@@ -658,7 +785,23 @@ async function listApps(context, provider, projectId) {
658
785
  async function requirePreviewAppProvider(context) {
659
786
  const client = await requireComputeAuth(context.runtime.env);
660
787
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
661
- return createPreviewAppProvider(client);
788
+ return createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env));
789
+ }
790
+ function createPreviewLogAuthOptions(env) {
791
+ const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
792
+ if (rawToken) return {
793
+ baseUrl: getApiBaseUrl(env),
794
+ getToken: async () => rawToken
795
+ };
796
+ const tokenStorage = new FileTokenStorage(env);
797
+ return {
798
+ baseUrl: getApiBaseUrl(env),
799
+ getToken: async () => {
800
+ const tokens = await tokenStorage.getTokens();
801
+ if (!tokens) throw new Error("Authentication token is no longer available. Run prisma-cli auth login and try again.");
802
+ return tokens.accessToken;
803
+ }
804
+ };
662
805
  }
663
806
  async function requireLinkedProjectId(context) {
664
807
  const projectId = await readLinkedProjectId(context.runtime.cwd);
@@ -734,9 +877,6 @@ function ensurePreviewAppMode(context) {
734
877
  if (isRealMode(context)) return;
735
878
  throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project link"], "app");
736
879
  }
737
- function blockedPreviewAppCommandError(summary, why) {
738
- return featureUnavailableError(summary, why, "Use prisma-cli app show, prisma-cli app open, prisma-cli app deploy, or prisma-cli app list-deploys in the current preview.", ["prisma-cli app show", "prisma-cli app list-deploys"], "app");
739
- }
740
880
  function deployFailedError(summary, error, nextSteps) {
741
881
  return new CliError({
742
882
  code: "DEPLOY_FAILED",
@@ -830,5 +970,21 @@ function sortApps(apps) {
830
970
  function toOptionalEnvVars(envVars) {
831
971
  return Object.keys(envVars).length > 0 ? envVars : void 0;
832
972
  }
973
+ /**
974
+ * Emits a deprecation banner to stderr when the legacy single-shot
975
+ * env-var commands are invoked. The banner is suppressed in --json
976
+ * mode so machine consumers keep their JSON channel clean; --json
977
+ * users discover the deprecation via release notes and the new
978
+ * `prisma-cli project env` namespace's output anyway.
979
+ *
980
+ * Removal of these legacy commands is deliberately scoped out of the
981
+ * Public Beta — see the Compute Beta plan, sub-track 3B.1, where the
982
+ * Terminal team picks an explicit removal milestone.
983
+ */
984
+ function emitLegacyEnvDeprecationWarning(context, legacyCommand, replacement) {
985
+ if (context.flags.json) return;
986
+ const message = `[deprecation] \`prisma-cli ${legacyCommand}\` is deprecated. Use \`prisma-cli ${replacement}\` instead.`;
987
+ context.runtime.stderr.write(`${message}\n`);
988
+ }
833
989
  //#endregion
834
990
  export { runAppBuild, runAppDeploy, runAppListDeploys, runAppListEnv, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy, runAppUpdateEnv };
@@ -0,0 +1,46 @@
1
+ import { usageError } from "../../shell/errors.js";
2
+ //#region src/lib/app/env-config.ts
3
+ const VALID_ROLES = new Set(["production", "preview"]);
4
+ function positionalHint(command) {
5
+ if (command === "add" || command === "update") return "KEY=value ";
6
+ if (command === "rm") return "KEY ";
7
+ return "";
8
+ }
9
+ function resolveEnvScope(flags, options) {
10
+ if (flags.roleName) {
11
+ if (!VALID_ROLES.has(flags.roleName)) throw usageError(`Unknown role "${flags.roleName}"`, "--role accepts production or preview.", "Pass --role production or --role preview.", [`prisma-cli project env ${options.command} --role production`, `prisma-cli project env ${options.command} --role preview`], "app");
12
+ return {
13
+ kind: "role",
14
+ role: flags.roleName
15
+ };
16
+ }
17
+ if (options.requireExplicit) {
18
+ const positional = positionalHint(options.command);
19
+ throw usageError(`prisma-cli project env ${options.command} requires --role`, "Writing without an explicit scope is rejected so the command never silently targets production.", "Pass --role production or --role preview.", [`prisma-cli project env ${options.command} ${positional}--role production`, `prisma-cli project env ${options.command} ${positional}--role preview`], "app");
20
+ }
21
+ return null;
22
+ }
23
+ function parseKeyValuePositional(raw, command) {
24
+ if (!raw) throw usageError(`prisma-cli project env ${command} requires KEY=VALUE`, "No KEY=VALUE positional argument was supplied.", "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma-cli project env ${command} STRIPE_KEY=sk_test_xxx --role production`], "app");
25
+ const separatorIndex = raw.indexOf("=");
26
+ if (separatorIndex === -1) throw usageError(`KEY=VALUE argument is missing the = separator`, `"${raw}" does not contain an = character.`, "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma-cli project env ${command} STRIPE_KEY=sk_test_xxx --role production`], "app");
27
+ const key = raw.slice(0, separatorIndex);
28
+ const value = raw.slice(separatorIndex + 1);
29
+ validateKey(key, command);
30
+ if (value.length === 0) throw usageError(`KEY=VALUE argument has an empty value`, `"${raw}" has an empty value after the = separator.`, `Pass a non-empty value, or use prisma-cli project env rm to remove a variable.`, [`prisma-cli project env ${command} ${key}=value --role production`], "app");
31
+ return {
32
+ key,
33
+ value
34
+ };
35
+ }
36
+ const KEY_SHAPE = /^[A-Z_][A-Z0-9_]*$/;
37
+ function validateKey(key, command) {
38
+ if (key.length === 0) throw usageError(`Variable key cannot be empty`, "An empty key was passed.", "Pass an env-var key, e.g. STRIPE_KEY.", [`prisma-cli project env ${command} STRIPE_KEY${command === "rm" ? "" : "=value"} --role production`], "app");
39
+ if (key.length > 256) throw usageError(`Variable key "${key}" exceeds the 256-character limit`, "Env-var keys are capped at 256 characters by the platform.", "Use a shorter key.", [], "app");
40
+ if (!KEY_SHAPE.test(key)) throw usageError(`Variable key "${key}" must match the POSIX env-var shape`, "Keys must start with an uppercase letter or underscore and contain only uppercase letters, digits, and underscores.", "Rename the key to match [A-Z_][A-Z0-9_]*.", [`prisma-cli project env ${command} STRIPE_KEY${command === "rm" ? "" : "=value"} --role production`], "app");
41
+ }
42
+ function formatScopeLabel(scope) {
43
+ return scope.role;
44
+ }
45
+ //#endregion
46
+ export { formatScopeLabel, parseKeyValuePositional, resolveEnvScope };
@@ -1,9 +1,9 @@
1
1
  import { envVarNames } from "./env-vars.js";
2
2
  import { PreviewBuildStrategy } from "./preview-build.js";
3
3
  import path from "node:path";
4
- import { ApiError, ComputeClient } from "@prisma/compute-sdk";
4
+ import { ApiError, CancelledError, ComputeClient, streamLogs } from "@prisma/compute-sdk";
5
5
  //#region src/lib/app/preview-provider.ts
6
- function createPreviewAppProvider(client) {
6
+ function createPreviewAppProvider(client, options) {
7
7
  const sdk = new ComputeClient(client);
8
8
  return {
9
9
  async createProject(options) {
@@ -197,6 +197,19 @@ function createPreviewAppProvider(client) {
197
197
  live: null
198
198
  }
199
199
  };
200
+ },
201
+ async streamDeploymentLogs(streamOptions) {
202
+ if (!options?.baseUrl || !options.getToken) throw new Error("Log streaming requires an authenticated API base URL and token.");
203
+ const result = await streamLogs({
204
+ baseUrl: options.baseUrl,
205
+ token: await options.getToken(),
206
+ versionId: streamOptions.deploymentId,
207
+ signal: streamOptions.signal
208
+ }, streamOptions.onRecord);
209
+ if (result.isErr()) {
210
+ if (CancelledError.is(result.error)) return;
211
+ throw result.error;
212
+ }
200
213
  }
201
214
  };
202
215
  }
@@ -11,6 +11,10 @@ function decodeJwtPayload(token) {
11
11
  return {};
12
12
  }
13
13
  }
14
+ function emailFromClaims(claims) {
15
+ const email = claims.email;
16
+ return typeof email === "string" && email.trim().length > 0 ? email.trim() : null;
17
+ }
14
18
  async function performLogin(env) {
15
19
  await login({
16
20
  tokenStorage: new FileTokenStorage(env),
@@ -26,7 +30,7 @@ async function readAuthState(env) {
26
30
  workspace: null,
27
31
  linkedProjectId: null
28
32
  };
29
- const claims = decodeJwtPayload(tokens.accessToken);
33
+ const email = emailFromClaims(decodeJwtPayload(tokens.accessToken));
30
34
  const client = await requireComputeAuth(env);
31
35
  let workspaceId = tokens.workspaceId;
32
36
  let workspaceName = tokens.workspaceId;
@@ -38,11 +42,7 @@ async function readAuthState(env) {
38
42
  return {
39
43
  authenticated: true,
40
44
  provider: null,
41
- user: {
42
- id: claims.sub ?? "",
43
- name: claims.name ?? "",
44
- email: claims.email ?? ""
45
- },
45
+ user: email ? { email } : null,
46
46
  workspace: {
47
47
  id: workspaceId,
48
48
  name: workspaceName
@@ -1,5 +1,5 @@
1
- import { formatDescriptorLabel } from "../shell/command-meta.js";
2
1
  import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
2
+ import { formatDescriptorLabel } from "../shell/command-meta.js";
3
3
  import stringWidth from "string-width";
4
4
  //#region src/output/patterns.ts
5
5
  function renderList(input, ui) {
@@ -0,0 +1,129 @@
1
+ import { renderList, renderShow, serializeList } from "../output/patterns.js";
2
+ //#region src/presenters/app-env.ts
3
+ function scopeLabel(scope) {
4
+ return scope.role;
5
+ }
6
+ function renderEnvAdd(context, descriptor, result) {
7
+ return renderShow({
8
+ title: "Setting a new environment variable.",
9
+ descriptor,
10
+ fields: [
11
+ {
12
+ key: "project",
13
+ value: result.projectId
14
+ },
15
+ {
16
+ key: "scope",
17
+ value: scopeLabel(result.scope)
18
+ },
19
+ {
20
+ key: "key",
21
+ value: result.variable.key
22
+ },
23
+ {
24
+ key: "id",
25
+ value: result.variable.id,
26
+ tone: "dim"
27
+ },
28
+ {
29
+ key: "last updated",
30
+ value: result.variable.updatedAt,
31
+ tone: "dim"
32
+ }
33
+ ]
34
+ }, context.ui);
35
+ }
36
+ function serializeEnvAdd(result) {
37
+ return result;
38
+ }
39
+ function renderEnvUpdate(context, descriptor, result) {
40
+ return renderShow({
41
+ title: "Replacing the environment variable's value.",
42
+ descriptor,
43
+ fields: [
44
+ {
45
+ key: "project",
46
+ value: result.projectId
47
+ },
48
+ {
49
+ key: "scope",
50
+ value: scopeLabel(result.scope)
51
+ },
52
+ {
53
+ key: "key",
54
+ value: result.variable.key
55
+ },
56
+ {
57
+ key: "id",
58
+ value: result.variable.id,
59
+ tone: "dim"
60
+ },
61
+ {
62
+ key: "last updated",
63
+ value: result.variable.updatedAt,
64
+ tone: "dim"
65
+ }
66
+ ]
67
+ }, context.ui);
68
+ }
69
+ function serializeEnvUpdate(result) {
70
+ return result;
71
+ }
72
+ function renderEnvList(context, descriptor, result) {
73
+ return renderList({
74
+ title: "Listing environment variables for the selected scope.",
75
+ descriptor,
76
+ parentContext: {
77
+ key: "scope",
78
+ value: scopeLabel(result.scope)
79
+ },
80
+ items: result.variables.map((variable) => ({
81
+ noun: "variable",
82
+ label: variable.key,
83
+ id: variable.id,
84
+ status: variable.isManagedBySystem ? "default" : null
85
+ })),
86
+ emptyMessage: "No environment variables defined in this scope."
87
+ }, context.ui);
88
+ }
89
+ function serializeEnvList(result) {
90
+ return {
91
+ projectId: result.projectId,
92
+ scope: result.scope,
93
+ ...serializeList({
94
+ context: { scope: scopeLabel(result.scope) },
95
+ items: result.variables.map((variable) => ({
96
+ noun: "variable",
97
+ label: variable.key,
98
+ id: variable.id,
99
+ status: variable.isManagedBySystem ? "default" : null
100
+ }))
101
+ }),
102
+ variables: result.variables
103
+ };
104
+ }
105
+ function renderEnvRm(context, descriptor, result) {
106
+ return renderShow({
107
+ title: "Removing the environment variable from the scope.",
108
+ descriptor,
109
+ fields: [
110
+ {
111
+ key: "project",
112
+ value: result.projectId
113
+ },
114
+ {
115
+ key: "scope",
116
+ value: scopeLabel(result.scope)
117
+ },
118
+ {
119
+ key: "key",
120
+ value: result.key
121
+ }
122
+ ]
123
+ }, context.ui);
124
+ }
125
+ function serializeEnvRm(result) {
126
+ return result;
127
+ }
128
+ //#endregion
129
+ export { renderEnvAdd, renderEnvList, renderEnvRm, renderEnvUpdate, serializeEnvAdd, serializeEnvList, serializeEnvRm, serializeEnvUpdate };
@@ -9,7 +9,7 @@ function renderAuthSuccess(context, descriptor, command, result) {
9
9
  });
10
10
  if (result.user) rows.push({
11
11
  key: "user",
12
- value: `${result.user.name} <${result.user.email}>`
12
+ value: result.user.email
13
13
  });
14
14
  if (result.workspace?.name) rows.push({
15
15
  key: "workspace",
@@ -47,7 +47,7 @@ function renderAuthSuccess(context, descriptor, command, result) {
47
47
  },
48
48
  ...result.user ? [{
49
49
  key: "user",
50
- value: `${result.user.name} <${result.user.email}>`
50
+ value: result.user.email
51
51
  }] : [],
52
52
  ...result.provider ? [{
53
53
  key: "provider",
@@ -260,6 +260,64 @@ const DESCRIPTORS = [
260
260
  ],
261
261
  description: "Remove the app from the current branch",
262
262
  examples: ["prisma-cli app remove --app hello-world", "prisma-cli app remove --app hello-world --yes"]
263
+ },
264
+ {
265
+ id: "project.env",
266
+ path: [
267
+ "prisma",
268
+ "project",
269
+ "env"
270
+ ],
271
+ description: "Manage environment variables for the linked project.",
272
+ examples: [
273
+ "prisma-cli project env list --role production",
274
+ "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
275
+ "prisma-cli project env rm STRIPE_KEY --role preview"
276
+ ]
277
+ },
278
+ {
279
+ id: "project.env.add",
280
+ path: [
281
+ "prisma",
282
+ "project",
283
+ "env",
284
+ "add"
285
+ ],
286
+ description: "Create a new environment variable.",
287
+ examples: ["prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production", "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role preview"]
288
+ },
289
+ {
290
+ id: "project.env.update",
291
+ path: [
292
+ "prisma",
293
+ "project",
294
+ "env",
295
+ "update"
296
+ ],
297
+ description: "Replace an existing environment variable's value.",
298
+ examples: ["prisma-cli project env update STRIPE_KEY=sk_new_xxx --role production", "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role preview"]
299
+ },
300
+ {
301
+ id: "project.env.list",
302
+ path: [
303
+ "prisma",
304
+ "project",
305
+ "env",
306
+ "list"
307
+ ],
308
+ description: "List environment variable metadata for a scope (no values).",
309
+ examples: ["prisma-cli project env list --role production", "prisma-cli project env list --role preview"]
310
+ },
311
+ {
312
+ id: "project.env.rm",
313
+ path: [
314
+ "prisma",
315
+ "project",
316
+ "env",
317
+ "rm"
318
+ ],
319
+ description: "Remove an environment variable from a scope.",
320
+ examples: ["prisma-cli project env rm STRIPE_KEY --role production", "prisma-cli project env rm STRIPE_KEY --role preview"]
263
321
  }
264
322
  ];
265
323
  const DESCRIPTORS_BY_ID = new Map(DESCRIPTORS.map((descriptor) => [descriptor.id, descriptor]));
@@ -1,8 +1,8 @@
1
1
  import { CliError } from "./errors.js";
2
+ import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
2
3
  import { getCommandDescriptor } from "./command-meta.js";
3
4
  import { resolveGlobalFlags } from "./global-flags.js";
4
5
  import { createCommandContext } from "./runtime.js";
5
- import { writeHumanError, writeHumanLines, writeJsonError, writeJsonSuccess } from "./output.js";
6
6
  //#region src/shell/command-runner.ts
7
7
  async function runCommand(runtime, commandName, options, handler, presenter) {
8
8
  const flags = resolveGlobalFlags(runtime.argv, options);
@@ -29,5 +29,35 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
29
29
  throw error;
30
30
  }
31
31
  }
32
+ async function runStreamingCommand(runtime, commandName, options, handler) {
33
+ const flags = resolveGlobalFlags(runtime.argv, options);
34
+ const context = await createCommandContext(runtime, flags);
35
+ try {
36
+ await handler(context);
37
+ if (flags.json) writeJsonEvent(context.output, {
38
+ type: "success",
39
+ command: commandName,
40
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
41
+ result: null,
42
+ warnings: [],
43
+ nextSteps: []
44
+ });
45
+ } catch (error) {
46
+ if (error instanceof CliError) {
47
+ if (flags.json) writeJsonEvent(context.output, {
48
+ type: "error",
49
+ command: commandName,
50
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
51
+ error: cliErrorToJson(error),
52
+ warnings: [],
53
+ nextSteps: error.nextSteps
54
+ });
55
+ else writeHumanError(context.output, context.ui, error, { trace: flags.trace });
56
+ process.exitCode = error.exitCode;
57
+ return;
58
+ }
59
+ throw error;
60
+ }
61
+ }
32
62
  //#endregion
33
- export { runCommand };
63
+ export { runCommand, runStreamingCommand };
@@ -1,6 +1,6 @@
1
+ import { createShellUi, padDisplay, wrapText } from "./ui.js";
1
2
  import { formatDescriptorLabel, getDescriptorForCommand } from "./command-meta.js";
2
3
  import { COMPACT_GLOBAL_OPTION_FLAGS, resolveGlobalFlags } from "./global-flags.js";
3
- import { createShellUi, padDisplay, wrapText } from "./ui.js";
4
4
  //#region src/shell/help.ts
5
5
  function renderHelp(command, runtime) {
6
6
  const descriptor = getDescriptorForCommand(command);
@@ -6,21 +6,27 @@ function writeJsonSuccess(output, success) {
6
6
  ...success
7
7
  }, null, 2)}\n`);
8
8
  }
9
+ function writeJsonEvent(output, event) {
10
+ output.stdout.write(`${JSON.stringify(event)}\n`);
11
+ }
12
+ function cliErrorToJson(error) {
13
+ return {
14
+ code: error.code,
15
+ domain: error.domain,
16
+ severity: error.severity,
17
+ summary: error.summary,
18
+ why: error.why,
19
+ fix: error.fix,
20
+ where: error.where,
21
+ meta: error.meta,
22
+ docsUrl: error.docsUrl
23
+ };
24
+ }
9
25
  function writeJsonError(output, command, error) {
10
26
  output.stdout.write(`${JSON.stringify({
11
27
  ok: false,
12
28
  command,
13
- error: {
14
- code: error.code,
15
- domain: error.domain,
16
- severity: error.severity,
17
- summary: error.summary,
18
- why: error.why,
19
- fix: error.fix,
20
- where: error.where,
21
- meta: error.meta,
22
- docsUrl: error.docsUrl
23
- },
29
+ error: cliErrorToJson(error),
24
30
  warnings: [],
25
31
  nextSteps: error.nextSteps
26
32
  }, null, 2)}\n`);
@@ -51,4 +57,4 @@ function writeHumanError(output, ui, error, options) {
51
57
  writeHumanLines(output, lines);
52
58
  }
53
59
  //#endregion
54
- export { writeHumanError, writeHumanLines, writeJsonError, writeJsonSuccess };
60
+ export { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess };
@@ -1,6 +1,6 @@
1
+ import { createShellUi } from "./ui.js";
1
2
  import { LocalStateStore } from "../adapters/local-state.js";
2
3
  import { MockApi } from "../adapters/mock-api.js";
3
- import { createShellUi } from "./ui.js";
4
4
  import { renderHelp } from "./help.js";
5
5
  import path from "node:path";
6
6
  //#region src/shell/runtime.ts
package/dist/shell/ui.js CHANGED
@@ -25,6 +25,18 @@ function createShellUi(runtime, flags) {
25
25
  strong: (text) => colors.bold(text)
26
26
  };
27
27
  }
28
+ function renderCommandHeader(ui, options) {
29
+ if (!ui.isTTY) return [];
30
+ const rows = options.rows ?? [];
31
+ const lines = [`${ui.strong(options.commandLabel)} ${ui.dim("→")} ${ui.dim(options.description)}`, ""];
32
+ const rail = ui.dim("│");
33
+ const keyWidth = rows.length > 0 ? Math.max(...rows.map((row) => stringWidth(`${row.key}:`)), stringWidth("Read more")) : stringWidth("Read more");
34
+ for (const row of rows) lines.push(`${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${formatHeaderValue(ui, row)}`);
35
+ if (rows.length > 0 || options.docsPath) lines.push(rail);
36
+ if (options.docsPath) lines.push(`${rail} ${ui.accent(padDisplay("Read more", keyWidth))} ${ui.link(options.docsPath)}`);
37
+ lines.push("");
38
+ return lines;
39
+ }
28
40
  function renderSummaryLine(ui, status, text) {
29
41
  return `${status === "success" ? ui.success("✔") : status === "error" ? ui.error("✘") : status === "warning" ? ui.warning("⚠") : ui.info("ℹ")} ${text}`;
30
42
  }
@@ -55,5 +67,11 @@ function resolveColorEnabled(runtime, flags, isTTY) {
55
67
  if (runtime.env.NO_COLOR !== void 0) return false;
56
68
  return isTTY;
57
69
  }
70
+ function formatHeaderValue(ui, row) {
71
+ const value = row.sensitive ? maskValue(row.value) : row.value;
72
+ if (row.tone === "dim") return ui.dim(value);
73
+ if (row.tone === "link") return ui.link(value);
74
+ return value;
75
+ }
58
76
  //#endregion
59
- export { createShellUi, maskValue, padDisplay, renderNextSteps, renderSummaryLine, wrapText };
77
+ export { createShellUi, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
@@ -61,7 +61,7 @@ async function resolveCurrentAuthState(dependencies) {
61
61
  return {
62
62
  authenticated: true,
63
63
  provider: provider.id,
64
- user,
64
+ user: { email: user.email },
65
65
  workspace,
66
66
  linkedProjectId
67
67
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "3.0.0-alpha.2",
3
+ "version": "3.0.0-alpha.3",
4
4
  "description": "Preview of the unified Prisma CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "@prisma/compute-sdk": "^0.17.0",
40
40
  "c12": "4.0.0-beta.4",
41
41
  "@prisma/credentials-store": "^7.7.0",
42
- "@prisma/management-api-sdk": "^1.24.0",
42
+ "@prisma/management-api-sdk": "^1.27.0",
43
43
  "colorette": "^2.0.20",
44
44
  "commander": "^12.1.0",
45
45
  "magicast": "^0.3.5",