@prisma/cli 3.0.0-beta.1 → 3.0.0-beta.11

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 (66) hide show
  1. package/README.md +5 -3
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +12 -4
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +64 -23
  6. package/dist/cli.js +18 -3
  7. package/dist/cli2.js +9 -5
  8. package/dist/commands/app/index.js +84 -59
  9. package/dist/commands/branch/index.js +2 -27
  10. package/dist/commands/database/index.js +159 -0
  11. package/dist/commands/env.js +8 -4
  12. package/dist/controllers/app-env-api.js +54 -0
  13. package/dist/controllers/app-env-file.js +181 -0
  14. package/dist/controllers/app-env.js +286 -131
  15. package/dist/controllers/app.js +699 -244
  16. package/dist/controllers/auth.js +8 -8
  17. package/dist/controllers/branch.js +78 -48
  18. package/dist/controllers/database.js +318 -0
  19. package/dist/controllers/project.js +156 -85
  20. package/dist/lib/app/branch-database-deploy.js +325 -0
  21. package/dist/lib/app/branch-database.js +215 -0
  22. package/dist/lib/app/bun-project.js +12 -5
  23. package/dist/lib/app/compute-config.js +147 -0
  24. package/dist/lib/app/deploy-output.js +10 -1
  25. package/dist/lib/app/deploy-plan.js +58 -0
  26. package/dist/lib/app/env-config.js +1 -1
  27. package/dist/lib/app/env-file.js +82 -0
  28. package/dist/lib/app/env-vars.js +28 -2
  29. package/dist/lib/app/local-dev.js +8 -49
  30. package/dist/lib/app/preview-branch-database.js +102 -0
  31. package/dist/lib/app/preview-build-settings.js +376 -0
  32. package/dist/lib/app/preview-build.js +314 -97
  33. package/dist/lib/app/preview-provider.js +178 -65
  34. package/dist/lib/app/production-deploy-gate.js +161 -0
  35. package/dist/lib/auth/auth-ops.js +30 -21
  36. package/dist/lib/auth/guard.js +3 -2
  37. package/dist/lib/auth/login.js +116 -14
  38. package/dist/lib/database/provider.js +167 -0
  39. package/dist/lib/diagnostics.js +15 -0
  40. package/dist/lib/fs/home-path.js +24 -0
  41. package/dist/lib/git/local-branch.js +53 -0
  42. package/dist/lib/git/local-status.js +57 -0
  43. package/dist/lib/project/interactive-setup.js +1 -1
  44. package/dist/lib/project/local-pin.js +182 -33
  45. package/dist/lib/project/resolution.js +204 -50
  46. package/dist/lib/project/setup.js +66 -19
  47. package/dist/output/patterns.js +1 -1
  48. package/dist/presenters/app-env.js +149 -14
  49. package/dist/presenters/app.js +178 -23
  50. package/dist/presenters/branch.js +37 -102
  51. package/dist/presenters/database.js +274 -0
  52. package/dist/presenters/project.js +57 -39
  53. package/dist/presenters/verbose-context.js +64 -0
  54. package/dist/shell/command-meta.js +103 -13
  55. package/dist/shell/command-runner.js +57 -19
  56. package/dist/shell/diagnostics-output.js +57 -0
  57. package/dist/shell/errors.js +12 -1
  58. package/dist/shell/help.js +30 -20
  59. package/dist/shell/output.js +10 -1
  60. package/dist/shell/runtime.js +11 -7
  61. package/dist/shell/ui.js +42 -3
  62. package/dist/shell/update-check.js +247 -0
  63. package/dist/use-cases/branch.js +20 -68
  64. package/dist/use-cases/create-cli-gateways.js +2 -17
  65. package/dist/use-cases/project.js +2 -1
  66. package/package.json +26 -10
@@ -69,8 +69,18 @@ const DESCRIPTORS = [
69
69
  {
70
70
  id: "branch",
71
71
  path: ["prisma", "branch"],
72
- description: "View your active Platform branches",
73
- examples: ["prisma-cli branch list", "prisma-cli branch show"]
72
+ description: "View your Platform branches",
73
+ examples: ["prisma-cli branch list"]
74
+ },
75
+ {
76
+ id: "database",
77
+ path: ["prisma", "database"],
78
+ description: "Manage Prisma Postgres databases for a project",
79
+ examples: [
80
+ "prisma-cli database list",
81
+ "prisma-cli database create my-db",
82
+ "prisma-cli database connection create db_123"
83
+ ]
74
84
  },
75
85
  {
76
86
  id: "git",
@@ -153,28 +163,99 @@ const DESCRIPTORS = [
153
163
  "branch",
154
164
  "list"
155
165
  ],
156
- description: "List active Platform branches for the resolved project",
166
+ description: "List Platform branches for the resolved project",
157
167
  examples: ["prisma-cli branch list", "prisma-cli branch list --json"]
158
168
  },
159
169
  {
160
- id: "branch.show",
170
+ id: "database.list",
161
171
  path: [
162
172
  "prisma",
163
- "branch",
173
+ "database",
174
+ "list"
175
+ ],
176
+ description: "List Prisma Postgres databases for the resolved project",
177
+ examples: [
178
+ "prisma-cli database list",
179
+ "prisma-cli database list --branch feature/foo",
180
+ "prisma-cli database list --json"
181
+ ]
182
+ },
183
+ {
184
+ id: "database.show",
185
+ path: [
186
+ "prisma",
187
+ "database",
164
188
  "show"
165
189
  ],
166
- description: "Show the Platform branch matching your current Git branch",
167
- examples: ["prisma-cli branch show", "prisma-cli branch show --json"]
190
+ description: "Show database metadata without secret values",
191
+ examples: ["prisma-cli database show db_123", "prisma-cli database show acme-preview --branch preview --json"]
168
192
  },
169
193
  {
170
- id: "branch.use",
194
+ id: "database.create",
171
195
  path: [
172
196
  "prisma",
173
- "branch",
174
- "use"
197
+ "database",
198
+ "create"
199
+ ],
200
+ description: "Create a Prisma Postgres database and print its one-time connection URL",
201
+ examples: ["prisma-cli database create my-db", "prisma-cli database create my-db --branch feature/foo --region eu-central-1"]
202
+ },
203
+ {
204
+ id: "database.remove",
205
+ path: [
206
+ "prisma",
207
+ "database",
208
+ "remove"
175
209
  ],
176
- description: "Change the local default branch context.",
177
- examples: ["prisma-cli branch use", "prisma-cli branch use production"]
210
+ description: "Remove a database after exact id confirmation",
211
+ examples: ["prisma-cli database remove db_123 --confirm db_123"]
212
+ },
213
+ {
214
+ id: "database.connection",
215
+ path: [
216
+ "prisma",
217
+ "database",
218
+ "connection"
219
+ ],
220
+ description: "Manage one-time-view database connection strings",
221
+ examples: [
222
+ "prisma-cli database connection list db_123",
223
+ "prisma-cli database connection create db_123",
224
+ "prisma-cli database connection remove conn_123 --confirm conn_123"
225
+ ]
226
+ },
227
+ {
228
+ id: "database.connection.list",
229
+ path: [
230
+ "prisma",
231
+ "database",
232
+ "connection",
233
+ "list"
234
+ ],
235
+ description: "List database connection metadata without secret values",
236
+ examples: ["prisma-cli database connection list db_123", "prisma-cli database connection list acme-preview --branch preview --json"]
237
+ },
238
+ {
239
+ id: "database.connection.create",
240
+ path: [
241
+ "prisma",
242
+ "database",
243
+ "connection",
244
+ "create"
245
+ ],
246
+ description: "Create a database connection and print its one-time connection URL",
247
+ examples: ["prisma-cli database connection create db_123", "prisma-cli database connection create db_123 --name readonly"]
248
+ },
249
+ {
250
+ id: "database.connection.remove",
251
+ path: [
252
+ "prisma",
253
+ "database",
254
+ "connection",
255
+ "remove"
256
+ ],
257
+ description: "Remove a database connection after exact id confirmation",
258
+ examples: ["prisma-cli database connection remove conn_123 --confirm conn_123"]
178
259
  },
179
260
  {
180
261
  id: "app.build",
@@ -214,8 +295,12 @@ const DESCRIPTORS = [
214
295
  "prisma-cli app deploy --project proj_123",
215
296
  "prisma-cli app deploy --create-project my-app --yes",
216
297
  "prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
298
+ "prisma-cli app deploy --env .env",
299
+ "prisma-cli app deploy --db",
300
+ "prisma-cli app deploy --db --yes",
217
301
  "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
218
302
  "prisma-cli app deploy --branch feat-login --framework hono",
303
+ "prisma-cli app deploy --prod --yes",
219
304
  "pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all",
220
305
  "prisma-cli app deploy --framework bun --entry src/server.ts"
221
306
  ]
@@ -378,8 +463,9 @@ const DESCRIPTORS = [
378
463
  ],
379
464
  description: "Manage environment variables for the active project",
380
465
  examples: [
381
- "prisma-cli project env list --role production",
466
+ "prisma-cli project env list",
382
467
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
468
+ "prisma-cli project env add --file .env --role preview",
383
469
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
384
470
  "prisma-cli project env remove STRIPE_KEY --role preview"
385
471
  ]
@@ -396,7 +482,9 @@ const DESCRIPTORS = [
396
482
  examples: [
397
483
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
398
484
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role preview",
485
+ "prisma-cli project env add --file .env --role preview",
399
486
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
487
+ "prisma-cli project env add --file .env.local --branch feature/foo",
400
488
  "API_URL=https://api.example prisma-cli project env add API_URL --project proj_123 --role preview"
401
489
  ]
402
490
  },
@@ -412,6 +500,7 @@ const DESCRIPTORS = [
412
500
  examples: [
413
501
  "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role production",
414
502
  "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role preview",
503
+ "prisma-cli project env update --file .env --role production",
415
504
  "prisma-cli project env update DATABASE_URL=postgresql://branch --branch feature/foo"
416
505
  ]
417
506
  },
@@ -425,6 +514,7 @@ const DESCRIPTORS = [
425
514
  ],
426
515
  description: "List environment variable metadata for a scope (no values).",
427
516
  examples: [
517
+ "prisma-cli project env list",
428
518
  "prisma-cli project env list --role production",
429
519
  "prisma-cli project env list --role preview",
430
520
  "prisma-cli project env list --branch feature/foo"
@@ -1,41 +1,79 @@
1
- import { CliError, authRequiredError } from "./errors.js";
2
- import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
1
+ import { CliError, authRequiredError, commandCanceledError } from "./errors.js";
3
2
  import { getCommandDescriptor } from "./command-meta.js";
4
3
  import { resolveGlobalFlags } from "./global-flags.js";
5
4
  import { createCommandContext } from "./runtime.js";
5
+ import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.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
- function toCliError(error) {
10
+ function toCliError(error, runtime) {
11
+ if (isCancellationError(error) || runtime.signal.aborted) return commandCanceledError();
9
12
  if (error instanceof CliError) return error;
10
13
  if (error instanceof AuthError) return authRequiredError(["prisma-cli auth login"], { debug: error.message });
11
14
  return null;
12
15
  }
16
+ function isCancellationError(error) {
17
+ if (!(error instanceof Error)) return false;
18
+ return error.name === "AbortError" || error.name === "CancelledError";
19
+ }
13
20
  async function runCommand(runtime, commandName, options, handler, presenter) {
14
- const flags = resolveGlobalFlags(runtime.argv, options);
15
- const context = await createCommandContext(runtime, flags);
21
+ const context = await createCommandContext(runtime, resolveGlobalFlags(runtime.argv, options));
16
22
  const descriptor = getCommandDescriptor(commandName);
23
+ const startedAt = Date.now();
17
24
  try {
18
- const success = await handler(context);
19
- if (flags.json) {
20
- writeJsonSuccess(context.output, {
21
- ...success,
22
- result: presenter.renderJson ? presenter.renderJson(success.result) : success.result
23
- });
24
- return;
25
- }
26
- if (flags.quiet) return;
27
- writeHumanLines(context.output, presenter.renderHuman(context, descriptor, success.result));
25
+ await writeCommandSuccess(context, descriptor, await handler(context), presenter, Date.now() - startedAt);
28
26
  } catch (error) {
29
- const cliError = toCliError(error);
27
+ const cliError = toCliError(error, runtime);
30
28
  if (cliError) {
31
- if (flags.json) writeJsonError(context.output, commandName, cliError);
32
- else writeHumanError(context.output, context.ui, cliError, { trace: flags.trace });
29
+ writeCommandError(context, commandName, cliError);
33
30
  process.exitCode = cliError.exitCode;
34
31
  return;
35
32
  }
36
33
  throw error;
37
34
  }
38
35
  }
36
+ async function writeCommandSuccess(context, descriptor, success, presenter, durationMs) {
37
+ if (context.flags.json) {
38
+ writeJsonSuccess(context.output, {
39
+ ...success,
40
+ result: presenter.renderJson ? presenter.renderJson(success.result) : success.result
41
+ });
42
+ return;
43
+ }
44
+ const stdout = presenter.renderStdout?.(context, descriptor, success.result) ?? [];
45
+ if (context.flags.quiet) {
46
+ writeStdoutLines(context, stdout);
47
+ return;
48
+ }
49
+ const rendered = presenter.renderHuman(context, descriptor, success.result);
50
+ const diagnostics = await renderBestEffortCommandDiagnostics(context, {
51
+ enabled: context.flags.verbose && rendered.length > 0,
52
+ durationMs
53
+ });
54
+ const humanLines = [...rendered, ...diagnostics];
55
+ if (stdout.length > 0 && humanLines.length > 0) humanLines.push("");
56
+ writeHumanLines(context.output, humanLines);
57
+ writeStdoutLines(context, stdout);
58
+ }
59
+ function writeCommandError(context, commandName, cliError) {
60
+ if (context.flags.json) {
61
+ writeJsonError(context.output, commandName, cliError);
62
+ return;
63
+ }
64
+ writeHumanError(context.output, context.ui, cliError, { trace: context.flags.trace });
65
+ }
66
+ function writeStdoutLines(context, lines) {
67
+ if (lines.length > 0) context.output.stdout.write(`${lines.join("\n")}\n`);
68
+ }
69
+ async function renderBestEffortCommandDiagnostics(context, options) {
70
+ if (!options.enabled) return [];
71
+ try {
72
+ return renderCommandDiagnostics(context, await collectCommandDiagnostics(context, { durationMs: options.durationMs }));
73
+ } catch {
74
+ return [];
75
+ }
76
+ }
39
77
  async function runStreamingCommand(runtime, commandName, options, handler) {
40
78
  const flags = resolveGlobalFlags(runtime.argv, options);
41
79
  const context = await createCommandContext(runtime, flags);
@@ -51,7 +89,7 @@ async function runStreamingCommand(runtime, commandName, options, handler) {
51
89
  nextActions: []
52
90
  });
53
91
  } catch (error) {
54
- const cliError = toCliError(error);
92
+ const cliError = toCliError(error, runtime);
55
93
  if (cliError) {
56
94
  if (flags.json) writeJsonEvent(context.output, {
57
95
  type: "error",
@@ -0,0 +1,57 @@
1
+ import { renderVerboseBlock } from "./ui.js";
2
+ import { shortenHomePath } from "../lib/fs/home-path.js";
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
+ return shortenHomePath(value, env);
47
+ }
48
+ function formatDirtyState(dirty) {
49
+ if (dirty === null) return "unknown";
50
+ return dirty ? "yes" : "no";
51
+ }
52
+ function formatDuration(durationMs) {
53
+ if (durationMs < 1e3) return `${durationMs}ms`;
54
+ return `${(durationMs / 1e3).toFixed(1)}s`;
55
+ }
56
+ //#endregion
57
+ export { renderCommandDiagnostics };
@@ -56,6 +56,17 @@ function authRequiredError(nextSteps = ["prisma-cli auth login"], options = {})
56
56
  nextSteps
57
57
  });
58
58
  }
59
+ function commandCanceledError() {
60
+ return new CliError({
61
+ code: "COMMAND_CANCELED",
62
+ domain: "cli",
63
+ summary: "Command canceled",
64
+ why: null,
65
+ fix: null,
66
+ exitCode: 130,
67
+ humanLines: ["Command canceled [COMMAND_CANCELED]"]
68
+ });
69
+ }
59
70
  function workspaceRequiredError() {
60
71
  return usageError("Workspace required", "This command needs an active workspace, but the authenticated session does not have one.", "Run prisma-cli auth login and choose a workspace.", ["prisma-cli auth login"], "auth");
61
72
  }
@@ -71,4 +82,4 @@ function featureUnavailableError(summary, why, fix, nextSteps = [], domain = "cl
71
82
  });
72
83
  }
73
84
  //#endregion
74
- export { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError };
85
+ export { CliError, authRequiredError, commandCanceledError, featureUnavailableError, usageError, workspaceRequiredError };
@@ -1,6 +1,6 @@
1
- import { createShellUi, padDisplay, wrapText } from "./ui.js";
2
1
  import { formatDescriptorLabel, getDescriptorForCommand } from "./command-meta.js";
3
2
  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);
@@ -10,28 +10,38 @@ function renderHelp(command, runtime) {
10
10
  const visibleCommands = command.commands.filter((candidate) => candidate.name() !== "help" && !candidate.hidden);
11
11
  const visibleOptions = command.options.filter((candidate) => !candidate.hidden);
12
12
  if (visibleCommands.length > 0) lines.push(...renderCommandRows(rail, ui, visibleCommands));
13
- if (descriptor.longDescription) {
14
- lines.push(`${rail}`);
15
- const wrapped = wrapText(descriptor.longDescription, Math.max(ui.width - 3, 40));
16
- for (const line of wrapped) lines.push(`${rail} ${line}`);
17
- }
18
- if (visibleOptions.length > 0) {
19
- if (visibleCommands.length > 0) lines.push(`${rail}`);
20
- if (visibleCommands.length > 0 && visibleOptions.every((option) => COMPACT_GLOBAL_OPTION_FLAGS.includes(option.flags))) lines.push(`${rail} Global options:`);
21
- lines.push(...renderOptionRows(rail, ui, visibleOptions));
22
- }
23
- if (descriptor.examples && descriptor.examples.length > 0) {
24
- lines.push(`${rail}`);
25
- lines.push(`${rail} Examples:`);
26
- for (const example of descriptor.examples) lines.push(`${rail} $ ${example}`);
27
- }
28
- if (descriptor.docsPath) {
29
- lines.push(`${rail}`);
30
- lines.push(`${rail} ${ui.accent(padDisplay("Read more", 16))} ${ui.link(descriptor.docsPath)}`);
31
- }
13
+ lines.push(...renderLongDescription(rail, ui, descriptor.longDescription));
14
+ lines.push(...renderVisibleOptions(rail, ui, visibleCommands, visibleOptions));
15
+ lines.push(...renderExamples(rail, descriptor.examples));
16
+ lines.push(...renderDocsPath(rail, ui, descriptor.docsPath));
32
17
  lines.push("");
33
18
  return `${lines.join("\n")}`;
34
19
  }
20
+ function renderLongDescription(rail, ui, longDescription) {
21
+ if (!longDescription) return [];
22
+ return [`${rail}`, ...wrapText(longDescription, Math.max(ui.width - 3, 40)).map((line) => `${rail} ${line}`)];
23
+ }
24
+ function renderVisibleOptions(rail, ui, visibleCommands, visibleOptions) {
25
+ if (visibleOptions.length === 0) return [];
26
+ const lines = visibleCommands.length > 0 ? [`${rail}`] : [];
27
+ if (shouldLabelGlobalOptions(visibleCommands, visibleOptions)) lines.push(`${rail} Global options:`);
28
+ return [...lines, ...renderOptionRows(rail, ui, visibleOptions)];
29
+ }
30
+ function shouldLabelGlobalOptions(visibleCommands, visibleOptions) {
31
+ return visibleCommands.length > 0 && visibleOptions.every((option) => COMPACT_GLOBAL_OPTION_FLAGS.includes(option.flags));
32
+ }
33
+ function renderExamples(rail, examples) {
34
+ if (!examples || examples.length === 0) return [];
35
+ return [
36
+ `${rail}`,
37
+ `${rail} Examples:`,
38
+ ...examples.map((example) => `${rail} $ ${example}`)
39
+ ];
40
+ }
41
+ function renderDocsPath(rail, ui, docsPath) {
42
+ if (!docsPath) return [];
43
+ return [`${rail}`, `${rail} ${ui.accent(padDisplay("Read more", 16))} ${ui.link(docsPath)}`];
44
+ }
35
45
  function renderCommandRows(rail, ui, commands) {
36
46
  return renderAlignedRows(rail, ui, commands.map((command) => {
37
47
  const descriptor = getDescriptorForCommand(command);
@@ -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 };
@@ -1,7 +1,8 @@
1
- import { createShellUi } from "./ui.js";
2
1
  import { LocalStateStore } from "../adapters/local-state.js";
3
2
  import { MockApi } from "../adapters/mock-api.js";
3
+ import { createShellUi } from "./ui.js";
4
4
  import { renderHelp } from "./help.js";
5
+ import { findComputeConfigDir } from "@prisma/compute-sdk/config";
5
6
  import path from "node:path";
6
7
  //#region src/shell/runtime.ts
7
8
  const DEFAULT_STATE_DIR_NAME = path.join(".prisma", "cli");
@@ -20,15 +21,15 @@ function configureRuntimeCommand(command, runtime) {
20
21
  }
21
22
  async function createCommandContext(runtime, flags) {
22
23
  const fixturePath = runtime.fixturePath ?? runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
23
- const stateDir = resolveStateDir(runtime);
24
+ const stateDir = await resolveStateDir(runtime);
24
25
  let loadedApi;
25
- if (fixturePath) loadedApi = await MockApi.load(fixturePath);
26
+ if (fixturePath) loadedApi = await MockApi.load(fixturePath, runtime.signal);
26
27
  return {
27
28
  get api() {
28
29
  if (!loadedApi) throw new Error("context.api accessed in real mode. Set runtime.fixturePath or PRISMA_CLI_MOCK_FIXTURE_PATH to use fixture mode.");
29
30
  return loadedApi;
30
31
  },
31
- stateStore: new LocalStateStore(stateDir),
32
+ stateStore: new LocalStateStore(stateDir, runtime.signal),
32
33
  output: {
33
34
  stdout: runtime.stdout,
34
35
  stderr: runtime.stderr
@@ -38,8 +39,11 @@ async function createCommandContext(runtime, flags) {
38
39
  ui: createShellUi(runtime, flags)
39
40
  };
40
41
  }
41
- function resolveStateDir(runtime) {
42
- return runtime.stateDir ?? runtime.env.PRISMA_CLI_STATE_DIR ?? path.join(runtime.cwd, DEFAULT_STATE_DIR_NAME);
42
+ async function resolveStateDir(runtime) {
43
+ const explicitStateDir = runtime.stateDir ?? runtime.env.PRISMA_CLI_STATE_DIR;
44
+ if (explicitStateDir) return explicitStateDir;
45
+ const projectDir = await findComputeConfigDir(runtime.cwd, runtime.signal);
46
+ return path.join(projectDir ?? runtime.cwd, DEFAULT_STATE_DIR_NAME);
43
47
  }
44
48
  function canPrompt(context) {
45
49
  if (context.flags.json) return false;
@@ -48,4 +52,4 @@ function canPrompt(context) {
48
52
  return Boolean(context.runtime.stdin.isTTY && context.runtime.stderr.isTTY);
49
53
  }
50
54
  //#endregion
51
- export { canPrompt, configureRuntimeCommand, createCommandContext };
55
+ export { canPrompt, configureRuntimeCommand, createCommandContext, resolveStateDir };
package/dist/shell/ui.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { createColors } from "colorette";
1
2
  import stringWidth from "string-width";
2
3
  import stripAnsi from "strip-ansi";
3
4
  import wrapAnsi from "wrap-ansi";
4
- import { createColors } from "colorette";
5
5
  //#region src/shell/ui.ts
6
+ const URL_CREDENTIALS_PATTERN = /:\/\/[^:@/\s]+:[^@/\s]+@/g;
6
7
  const DEFAULT_WIDTH = 80;
7
8
  function createShellUi(runtime, flags) {
8
9
  const isTTY = Boolean(runtime.stderr.isTTY);
@@ -48,6 +49,20 @@ function renderNextSteps(steps) {
48
49
  ...steps.map((step) => `- ${step}`)
49
50
  ];
50
51
  }
52
+ function renderVerboseBlock(ui, rows, options = {}) {
53
+ if (!ui.verbose || rows.length === 0) return [];
54
+ const title = options.title ?? "Details";
55
+ const keyWidth = Math.max(...rows.map((row) => stringWidth(`${row.key}:`)));
56
+ const rail = ui.dim("│");
57
+ return [
58
+ "",
59
+ `${ui.dim(title)}:`,
60
+ ...rows.map((row) => `${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${formatVerboseValue(ui, row)}`)
61
+ ];
62
+ }
63
+ function formatColumns(columns, widths) {
64
+ return columns.map((value, index) => padDisplay(value, widths[index])).join(" ").trimEnd();
65
+ }
51
66
  function wrapText(text, width, indent = "") {
52
67
  return wrapAnsi(text, width, {
53
68
  hard: false,
@@ -59,7 +74,23 @@ function padDisplay(text, width) {
59
74
  return `${text}${" ".repeat(padding)}`;
60
75
  }
61
76
  function maskValue(value) {
62
- return value.replace(/([A-Za-z0-9._%+-]{1,})(?=@)/g, "****").replace(/:\/\/[^:@/\s]+:[^@/\s]+@/g, "://****:****@");
77
+ return maskEmailLocalParts(value).replace(URL_CREDENTIALS_PATTERN, "://****:****@");
78
+ }
79
+ function maskEmailLocalParts(value) {
80
+ let masked = "";
81
+ let segmentStart = 0;
82
+ for (let index = 0; index < value.length; index += 1) {
83
+ if (value[index] !== "@") continue;
84
+ let localStart = index;
85
+ while (localStart > segmentStart && isEmailLocalPartChar(value[localStart - 1])) localStart -= 1;
86
+ if (localStart === index) continue;
87
+ masked += `${value.slice(segmentStart, localStart)}****@`;
88
+ segmentStart = index + 1;
89
+ }
90
+ return masked + value.slice(segmentStart);
91
+ }
92
+ function isEmailLocalPartChar(char) {
93
+ return char >= "A" && char <= "Z" || char >= "a" && char <= "z" || char >= "0" && char <= "9" || char === "!" || char === "#" || char === "$" || char === "." || char === "&" || char === "'" || char === "*" || char === "%" || char === "+" || char === "-" || char === "/" || char === "=" || char === "?" || char === "^" || char === "_" || char === "`" || char === "{" || char === "|" || char === "}" || char === "~";
63
94
  }
64
95
  function resolveColorEnabled(runtime, flags, isTTY) {
65
96
  if (flags.color === true) return true;
@@ -73,5 +104,13 @@ function formatHeaderValue(ui, row) {
73
104
  if (row.tone === "link") return ui.link(value);
74
105
  return value;
75
106
  }
107
+ function formatVerboseValue(ui, row) {
108
+ const value = row.sensitive ? maskValue(row.value) : row.value;
109
+ if (row.tone === "dim") return ui.dim(value);
110
+ if (row.tone === "success") return ui.success(value);
111
+ if (row.tone === "warning") return ui.warning(value);
112
+ if (row.tone === "link") return ui.link(value);
113
+ return value;
114
+ }
76
115
  //#endregion
77
- export { createShellUi, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
116
+ export { createShellUi, formatColumns, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, renderVerboseBlock, wrapText };