@prisma/cli 3.0.0-beta.4 → 3.0.0-beta.6

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.
@@ -1,25 +1,25 @@
1
- import { padDisplay, renderNextSteps, renderSummaryLine } from "../shell/ui.js";
1
+ import { padDisplay, renderNextSteps, renderSummaryLine, renderVerboseBlock } from "../shell/ui.js";
2
2
  import { formatDescriptorLabel } from "../shell/command-meta.js";
3
3
  import { formatCommandArgument } from "../shell/command-arguments.js";
4
- import { renderList, renderMutate, renderShow, serializeList } from "../output/patterns.js";
4
+ import { renderMutate, renderShow, serializeList } from "../output/patterns.js";
5
+ import { renderResolvedProjectContextBlock } from "./verbose-context.js";
5
6
  import path from "node:path";
7
+ import stringWidth from "string-width";
6
8
  //#region src/presenters/project.ts
7
9
  function renderProjectList(context, descriptor, result) {
8
- const lines = renderList({
9
- title: "Listing projects for the authenticated workspace.",
10
- descriptor,
11
- parentContext: {
12
- key: "workspace",
13
- value: result.workspace.name
14
- },
15
- items: result.projects.map((project) => ({
16
- noun: "project",
17
- label: project.name,
18
- id: project.id,
19
- status: null
20
- })),
21
- emptyMessage: "No projects found."
22
- }, context.ui);
10
+ const ui = context.ui;
11
+ const rail = ui.dim("│");
12
+ const lines = [`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing projects for the authenticated workspace.")}`, ""];
13
+ lines.push(`${rail} ${ui.accent("workspace:")} ${result.workspace.name}`);
14
+ if (result.projects.length === 0) {
15
+ lines.push(`${rail} ${ui.dim("No projects found.")}`);
16
+ if (result.localBinding?.status === "not-linked" || result.localBinding?.status === "invalid") lines.push(...renderNextSteps(["Link an existing Project you choose: prisma-cli project link <id-or-name>", "Create a new Project: prisma-cli project create <name>"]));
17
+ return lines;
18
+ }
19
+ const nameWidth = Math.max(4, ...result.projects.map((project) => stringWidth(project.name)));
20
+ lines.push(rail);
21
+ lines.push(`${rail} ${ui.accent(padDisplay("name", nameWidth))} ${ui.accent("id")}`);
22
+ for (const project of result.projects) lines.push(`${rail} ${padDisplay(project.name, nameWidth)} ${project.id}`);
23
23
  if (result.localBinding?.status === "not-linked" || result.localBinding?.status === "invalid") lines.push(...renderNextSteps(["Link an existing Project you choose: prisma-cli project link <id-or-name>", "Create a new Project: prisma-cli project create <name>"]));
24
24
  return lines;
25
25
  }
@@ -51,6 +51,25 @@ function renderProjectShow(context, descriptor, result) {
51
51
  tone: "warning"
52
52
  }]
53
53
  }, context.ui);
54
+ lines.push(...renderVerboseBlock(context.ui, [
55
+ {
56
+ key: "workspace",
57
+ value: result.workspace.name
58
+ },
59
+ {
60
+ key: "workspace id",
61
+ value: result.workspace.id,
62
+ tone: "dim"
63
+ },
64
+ {
65
+ key: "project source",
66
+ value: "unbound"
67
+ },
68
+ {
69
+ key: "suggested name",
70
+ value: `${result.suggestedProjectName} (${result.suggestedProjectNameSource})`
71
+ }
72
+ ], { title: "Resolved context" }));
54
73
  lines.push(...renderNextSteps(["Link an existing Project you choose: prisma-cli project link <id-or-name>", `Create a new Project: prisma-cli project create ${formatCommandArgument(result.suggestedProjectName)}`]));
55
74
  return lines;
56
75
  }
@@ -133,6 +152,11 @@ function renderBoundProjectShow(context, descriptor, result) {
133
152
  lines.push(rail);
134
153
  lines.push(`${rail} ${ui.dim("→")} ${ui.link(result.project.url)}`);
135
154
  }
155
+ lines.push(...renderResolvedProjectContextBlock(context.ui, {
156
+ workspace: result.workspace,
157
+ project: result.project,
158
+ resolution: result.resolution
159
+ }));
136
160
  return lines;
137
161
  }
138
162
  function formatLocalRepoPath(cwd, env) {
@@ -0,0 +1,64 @@
1
+ import { renderVerboseBlock } from "../shell/ui.js";
2
+ //#region src/presenters/verbose-context.ts
3
+ function renderResolvedProjectContextBlock(ui, context, options = {}) {
4
+ if (!context) return [];
5
+ return renderVerboseBlock(ui, [...projectResolutionRows(context), ...options.extraRows ?? []], { title: options.title ?? "Resolved context" });
6
+ }
7
+ function projectResolutionRows(context) {
8
+ return [
9
+ {
10
+ key: "workspace",
11
+ value: context.workspace.name
12
+ },
13
+ {
14
+ key: "workspace id",
15
+ value: context.workspace.id,
16
+ tone: "dim"
17
+ },
18
+ {
19
+ key: "project",
20
+ value: context.project.name
21
+ },
22
+ {
23
+ key: "project id",
24
+ value: context.project.id,
25
+ tone: "dim"
26
+ },
27
+ {
28
+ key: "project source",
29
+ value: formatProjectSource(context.resolution.projectSource)
30
+ },
31
+ ...context.resolution.targetName ? [{
32
+ key: "target name",
33
+ value: formatTargetName(context.resolution)
34
+ }] : [],
35
+ ...context.branch ? [{
36
+ key: "branch",
37
+ value: `${context.branch.name} (${context.branch.kind})`
38
+ }, ...context.branch.id ? [{
39
+ key: "branch id",
40
+ value: context.branch.id,
41
+ tone: "dim"
42
+ }] : []] : []
43
+ ];
44
+ }
45
+ function stripVerboseContext(result) {
46
+ const { verboseContext: _verboseContext, ...serialized } = result;
47
+ return serialized;
48
+ }
49
+ function formatProjectSource(source) {
50
+ switch (source) {
51
+ case "explicit": return "--project";
52
+ case "env": return "environment";
53
+ case "local-pin": return ".prisma/local.json";
54
+ case "platform-mapping": return "platform mapping";
55
+ case "created": return "created";
56
+ case "prompt": return "prompt";
57
+ case "unbound": return "unbound";
58
+ }
59
+ }
60
+ function formatTargetName(resolution) {
61
+ return resolution.targetNameSource ? `${resolution.targetName} (${resolution.targetNameSource})` : String(resolution.targetName);
62
+ }
63
+ //#endregion
64
+ export { renderResolvedProjectContextBlock, stripVerboseContext };
@@ -194,6 +194,8 @@ const DESCRIPTORS = [
194
194
  "prisma-cli app deploy --project proj_123",
195
195
  "prisma-cli app deploy --create-project my-app --yes",
196
196
  "prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
197
+ "prisma-cli app deploy --db",
198
+ "prisma-cli app deploy --db --yes",
197
199
  "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
198
200
  "prisma-cli app deploy --branch feat-login --framework hono",
199
201
  "prisma-cli app deploy --prod --yes",
@@ -361,6 +363,7 @@ const DESCRIPTORS = [
361
363
  examples: [
362
364
  "prisma-cli project env list",
363
365
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
366
+ "prisma-cli project env add --file .env --role preview",
364
367
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
365
368
  "prisma-cli project env remove STRIPE_KEY --role preview"
366
369
  ]
@@ -377,7 +380,9 @@ const DESCRIPTORS = [
377
380
  examples: [
378
381
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
379
382
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role preview",
383
+ "prisma-cli project env add --file .env --role preview",
380
384
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
385
+ "prisma-cli project env add --file .env.local --branch feature/foo",
381
386
  "API_URL=https://api.example prisma-cli project env add API_URL --project proj_123 --role preview"
382
387
  ]
383
388
  },
@@ -393,6 +398,7 @@ const DESCRIPTORS = [
393
398
  examples: [
394
399
  "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role production",
395
400
  "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role preview",
401
+ "prisma-cli project env update --file .env --role production",
396
402
  "prisma-cli project env update DATABASE_URL=postgresql://branch --branch feature/foo"
397
403
  ]
398
404
  },
@@ -3,6 +3,8 @@ import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, write
3
3
  import { getCommandDescriptor } from "./command-meta.js";
4
4
  import { resolveGlobalFlags } from "./global-flags.js";
5
5
  import { createCommandContext } from "./runtime.js";
6
+ import { collectCommandDiagnostics } from "../lib/diagnostics.js";
7
+ import { renderCommandDiagnostics } from "./diagnostics-output.js";
6
8
  import { AuthError } from "@prisma/management-api-sdk";
7
9
  //#region src/shell/command-runner.ts
8
10
  function toCliError(error, runtime) {
@@ -19,6 +21,7 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
19
21
  const flags = resolveGlobalFlags(runtime.argv, options);
20
22
  const context = await createCommandContext(runtime, flags);
21
23
  const descriptor = getCommandDescriptor(commandName);
24
+ const startedAt = Date.now();
22
25
  try {
23
26
  const success = await handler(context);
24
27
  if (flags.json) {
@@ -29,7 +32,12 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
29
32
  return;
30
33
  }
31
34
  if (flags.quiet) return;
32
- writeHumanLines(context.output, presenter.renderHuman(context, descriptor, success.result));
35
+ const rendered = presenter.renderHuman(context, descriptor, success.result);
36
+ const diagnostics = await renderBestEffortCommandDiagnostics(context, {
37
+ enabled: flags.verbose && rendered.length > 0,
38
+ durationMs: Date.now() - startedAt
39
+ });
40
+ writeHumanLines(context.output, [...rendered, ...diagnostics]);
33
41
  } catch (error) {
34
42
  const cliError = toCliError(error, runtime);
35
43
  if (cliError) {
@@ -41,6 +49,14 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
41
49
  throw error;
42
50
  }
43
51
  }
52
+ async function renderBestEffortCommandDiagnostics(context, options) {
53
+ if (!options.enabled) return [];
54
+ try {
55
+ return renderCommandDiagnostics(context, await collectCommandDiagnostics(context, { durationMs: options.durationMs }));
56
+ } catch {
57
+ return [];
58
+ }
59
+ }
44
60
  async function runStreamingCommand(runtime, commandName, options, handler) {
45
61
  const flags = resolveGlobalFlags(runtime.argv, options);
46
62
  const context = await createCommandContext(runtime, flags);
@@ -0,0 +1,63 @@
1
+ import { renderVerboseBlock } from "./ui.js";
2
+ import path from "node:path";
3
+ //#region src/shell/diagnostics-output.ts
4
+ function renderCommandDiagnostics(context, diagnostics, rows = [], options = {}) {
5
+ if (!diagnostics) return [];
6
+ const { env } = context.runtime;
7
+ const git = diagnostics.git;
8
+ return renderVerboseBlock(context.ui, [
9
+ ...rows,
10
+ ...diagnostics.durationMs === void 0 ? [] : [{
11
+ key: "duration",
12
+ value: formatDuration(diagnostics.durationMs)
13
+ }],
14
+ {
15
+ key: "cwd",
16
+ value: formatLocalPath(diagnostics.cwd, env)
17
+ },
18
+ {
19
+ key: "state file",
20
+ value: formatLocalPath(diagnostics.stateFilePath, env)
21
+ },
22
+ ...git ? [
23
+ {
24
+ key: "git ref",
25
+ value: git.ref ?? "detached",
26
+ tone: git.ref ? "default" : "dim"
27
+ },
28
+ {
29
+ key: "git sha",
30
+ value: git.sha ?? "unknown",
31
+ tone: git.sha ? "default" : "dim"
32
+ },
33
+ {
34
+ key: "git dirty",
35
+ value: formatDirtyState(git.dirty),
36
+ tone: git.dirty ? "warning" : "dim"
37
+ }
38
+ ] : [{
39
+ key: "git",
40
+ value: "not detected",
41
+ tone: "dim"
42
+ }]
43
+ ], { title: options.title ?? "Local context" });
44
+ }
45
+ function formatLocalPath(value, env) {
46
+ const resolved = path.resolve(value);
47
+ const home = env.HOME ? path.resolve(env.HOME) : null;
48
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
49
+ const relative = path.relative(home, resolved);
50
+ return relative ? `~/${relative}` : "~";
51
+ }
52
+ return resolved;
53
+ }
54
+ function formatDirtyState(dirty) {
55
+ if (dirty === null) return "unknown";
56
+ return dirty ? "yes" : "no";
57
+ }
58
+ function formatDuration(durationMs) {
59
+ if (durationMs < 1e3) return `${durationMs}ms`;
60
+ return `${(durationMs / 1e3).toFixed(1)}s`;
61
+ }
62
+ //#endregion
63
+ export { renderCommandDiagnostics };
@@ -23,6 +23,15 @@ function cliErrorToJson(error) {
23
23
  docsUrl: error.docsUrl
24
24
  };
25
25
  }
26
+ function formatUnexpectedError(error, trace) {
27
+ const debug = error instanceof Error ? error.stack ?? error.message : String(error);
28
+ if (trace) return `${debug}\n`;
29
+ return [
30
+ `Unexpected CLI error: ${error instanceof Error && error.message ? error.message : String(error)}`,
31
+ "More: Re-run with --trace for deeper diagnostics",
32
+ ""
33
+ ].join("\n");
34
+ }
26
35
  function writeJsonError(output, command, error) {
27
36
  output.stdout.write(`${JSON.stringify({
28
37
  ok: false,
@@ -70,4 +79,4 @@ function writeHumanError(output, ui, error, options) {
70
79
  writeHumanLines(output, lines);
71
80
  }
72
81
  //#endregion
73
- export { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess };
82
+ export { cliErrorToJson, formatUnexpectedError, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess };
@@ -48,4 +48,4 @@ function canPrompt(context) {
48
48
  return Boolean(context.runtime.stdin.isTTY && context.runtime.stderr.isTTY);
49
49
  }
50
50
  //#endregion
51
- export { canPrompt, configureRuntimeCommand, createCommandContext };
51
+ export { canPrompt, configureRuntimeCommand, createCommandContext, resolveStateDir };
package/dist/shell/ui.js CHANGED
@@ -48,6 +48,17 @@ function renderNextSteps(steps) {
48
48
  ...steps.map((step) => `- ${step}`)
49
49
  ];
50
50
  }
51
+ function renderVerboseBlock(ui, rows, options = {}) {
52
+ if (!ui.verbose || rows.length === 0) return [];
53
+ const title = options.title ?? "Details";
54
+ const keyWidth = Math.max(...rows.map((row) => stringWidth(`${row.key}:`)));
55
+ const rail = ui.dim("│");
56
+ return [
57
+ "",
58
+ `${ui.dim(title)}:`,
59
+ ...rows.map((row) => `${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${formatVerboseValue(ui, row)}`)
60
+ ];
61
+ }
51
62
  function formatColumns(columns, widths) {
52
63
  return columns.map((value, index) => padDisplay(value, widths[index])).join(" ").trimEnd();
53
64
  }
@@ -76,5 +87,13 @@ function formatHeaderValue(ui, row) {
76
87
  if (row.tone === "link") return ui.link(value);
77
88
  return value;
78
89
  }
90
+ function formatVerboseValue(ui, row) {
91
+ const value = row.sensitive ? maskValue(row.value) : row.value;
92
+ if (row.tone === "dim") return ui.dim(value);
93
+ if (row.tone === "success") return ui.success(value);
94
+ if (row.tone === "warning") return ui.warning(value);
95
+ if (row.tone === "link") return ui.link(value);
96
+ return value;
97
+ }
79
98
  //#endregion
80
- export { createShellUi, formatColumns, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
99
+ export { createShellUi, formatColumns, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, renderVerboseBlock, wrapText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "3.0.0-beta.4",
3
+ "version": "3.0.0-beta.6",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,12 +36,13 @@
36
36
  "license": "Apache-2.0",
37
37
  "dependencies": {
38
38
  "@clack/prompts": "^1.5.0",
39
- "@prisma/compute-sdk": "^0.20.0",
39
+ "@prisma/compute-sdk": "^0.21.0",
40
40
  "@prisma/credentials-store": "^7.8.0",
41
- "@prisma/management-api-sdk": "^1.35.0",
41
+ "@prisma/management-api-sdk": "^1.37.0",
42
42
  "c12": "4.0.0-beta.5",
43
43
  "colorette": "^2.0.20",
44
44
  "commander": "^14.0.3",
45
+ "dotenv": "^17.4.2",
45
46
  "magicast": "^0.5.3",
46
47
  "open": "^11.0.0",
47
48
  "string-width": "^8.2.1",