@prisma/cli 3.0.0-dev.97.1 → 8.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +3 -14
  2. package/dist/adapters/local-state.js +14 -3
  3. package/dist/{lib/auth → auth}/client.js +11 -3
  4. package/dist/auth/errors.js +50 -0
  5. package/dist/{lib/auth → auth}/guard.js +7 -6
  6. package/dist/{lib/auth → auth}/login.js +147 -6
  7. package/dist/{lib/auth/auth-ops.js → auth/operations.js} +63 -13
  8. package/dist/auth/recipient.js +42 -0
  9. package/dist/{adapters → auth}/token-storage.js +28 -7
  10. package/dist/auth/workspaces.js +171 -0
  11. package/dist/cli-command.js +14 -0
  12. package/dist/cli-name.js +12 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/cli2.js +25 -8
  15. package/dist/command-arguments.js +12 -0
  16. package/dist/commands/agent/index.js +60 -0
  17. package/dist/commands/app/index.js +11 -6
  18. package/dist/commands/auth/index.js +2 -3
  19. package/dist/commands/bucket/index.js +123 -0
  20. package/dist/commands/build/index.js +29 -0
  21. package/dist/commands/database/index.js +92 -2
  22. package/dist/commands/feedback/index.js +20 -0
  23. package/dist/commands/git/index.js +1 -1
  24. package/dist/commands/init/index.js +33 -0
  25. package/dist/commands/project/index.js +54 -5
  26. package/dist/controllers/agent-setup.js +52 -0
  27. package/dist/controllers/agent.js +228 -0
  28. package/dist/controllers/app-env-api.js +3 -2
  29. package/dist/controllers/app-env-file.js +1 -1
  30. package/dist/controllers/app-env.js +5 -5
  31. package/dist/controllers/app.js +423 -358
  32. package/dist/controllers/auth.js +62 -254
  33. package/dist/controllers/branch.js +5 -16
  34. package/dist/controllers/bucket.js +184 -0
  35. package/dist/controllers/build.js +88 -0
  36. package/dist/controllers/database.js +230 -85
  37. package/dist/controllers/feedback.js +90 -0
  38. package/dist/controllers/init.js +814 -0
  39. package/dist/controllers/project.js +345 -184
  40. package/dist/controllers/select-prompt-port.js +1 -0
  41. package/dist/{shell/errors.js → errors.js} +8 -40
  42. package/dist/lib/agent/cli-command.js +20 -0
  43. package/dist/lib/agent/constants.js +12 -0
  44. package/dist/lib/agent/package-manager.js +99 -0
  45. package/dist/lib/agent/setup-status.js +83 -0
  46. package/dist/lib/app/app-provider.js +168 -95
  47. package/dist/lib/app/branch-database-deploy.js +70 -42
  48. package/dist/lib/app/branch-database.js +39 -20
  49. package/dist/lib/app/build-settings.js +8 -3
  50. package/dist/lib/app/build.js +16 -14
  51. package/dist/lib/app/bun-project.js +1 -1
  52. package/dist/lib/app/compute-config.js +29 -31
  53. package/dist/lib/app/deploy-plan.js +1 -0
  54. package/dist/lib/app/deploy-progress.js +7 -7
  55. package/dist/lib/app/env-config.js +1 -1
  56. package/dist/lib/app/env-file.js +2 -2
  57. package/dist/lib/app/env-vars.js +1 -1
  58. package/dist/lib/app/local-dev.js +1 -1
  59. package/dist/lib/app/production-deploy-gate.js +3 -2
  60. package/dist/lib/bucket/provider.js +139 -0
  61. package/dist/lib/database/provider.js +235 -17
  62. package/dist/lib/diagnostics.js +2 -1
  63. package/dist/lib/feedback.js +15 -0
  64. package/dist/lib/git/local-branch.js +1 -1
  65. package/dist/lib/project/interactive-setup.js +6 -5
  66. package/dist/lib/project/local-pin.js +1 -1
  67. package/dist/lib/project/provider.js +93 -0
  68. package/dist/lib/project/resolution.js +11 -8
  69. package/dist/lib/project/setup.js +9 -6
  70. package/dist/lib/version.js +3 -2
  71. package/dist/lib/workspace-id.js +18 -0
  72. package/dist/payload-B88Qvzxk-CrtTXSOe.js +34 -0
  73. package/dist/presenters/agent.js +74 -0
  74. package/dist/presenters/app.js +33 -7
  75. package/dist/presenters/auth.js +3 -2
  76. package/dist/presenters/bucket.js +174 -0
  77. package/dist/presenters/database.js +193 -5
  78. package/dist/presenters/feedback.js +26 -0
  79. package/dist/presenters/init.js +30 -0
  80. package/dist/presenters/project.js +98 -4
  81. package/dist/shell/cli-command.js +2 -0
  82. package/dist/shell/command-meta.js +303 -4
  83. package/dist/shell/command-runner.js +13 -6
  84. package/dist/shell/help.js +6 -5
  85. package/dist/shell/output.js +65 -3
  86. package/dist/shell/prompt.js +12 -5
  87. package/dist/shell/runtime.js +4 -21
  88. package/dist/state-dir.js +12 -0
  89. package/dist/{shell/update-check.js → update-check.js} +6 -5
  90. package/dist/v8/cli.js +14699 -0
  91. package/dist/v8/sender.js +192 -0
  92. package/package.json +21 -13
  93. package/dist/adapters/mock-api.js +0 -136
  94. package/dist/lib/app/app-interaction.js +0 -5
  95. package/dist/shell/command-arguments.js +0 -6
  96. package/dist/use-cases/auth.js +0 -145
  97. package/dist/use-cases/branch.js +0 -47
  98. package/dist/use-cases/create-cli-gateways.js +0 -68
  99. package/dist/use-cases/project.js +0 -30
@@ -1,14 +1,12 @@
1
- import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
1
+ import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command.js";
2
+ import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../errors.js";
3
+ import { authenticatedManagementApiClient } from "../auth/guard.js";
2
4
  import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
3
- import { requireComputeAuth } from "../lib/auth/guard.js";
4
5
  import { requireAuthenticatedAuthState } from "./auth.js";
5
- import { listFixtureWorkspaceProjects, listRealWorkspaceProjects } from "./project.js";
6
- import { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase } from "../lib/database/provider.js";
6
+ import { listRealWorkspaceProjects } from "./project.js";
7
+ import { createManagementDatabaseProvider } from "../lib/database/provider.js";
7
8
  import { randomBytes } from "node:crypto";
8
9
  //#region src/controllers/database.ts
9
- function isRealMode(context) {
10
- return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
11
- }
12
10
  async function runDatabaseList(context, flags) {
13
11
  const { provider, target } = await requireDatabaseContext(context, flags, "database list");
14
12
  const databases = sortDatabases(await provider.listDatabases({
@@ -149,83 +147,228 @@ async function runDatabaseConnectionRemove(context, connectionRef, flags) {
149
147
  nextSteps: []
150
148
  };
151
149
  }
150
+ async function runDatabaseUsage(context, databaseRef, flags) {
151
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
152
+ const from = parseUsageDate(flags.from, "--from", "start", formatCommand);
153
+ const to = parseUsageDate(flags.to, "--to", "end", formatCommand);
154
+ if (from && to && Date.parse(from) > Date.parse(to)) throw usageError("Invalid usage period", "--from must not be later than --to.", "Pass a --from date that is on or before the --to date.", [formatCommand([
155
+ "database",
156
+ "usage",
157
+ "<database>",
158
+ "--from",
159
+ "2026-06-01",
160
+ "--to",
161
+ "2026-06-30"
162
+ ])], "database");
163
+ const { provider, target } = await requireDatabaseContext(context, flags, "database usage");
164
+ const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
165
+ const usage = await provider.getUsage(database.id, {
166
+ from,
167
+ to,
168
+ signal: context.runtime.signal
169
+ });
170
+ return {
171
+ command: "database.usage",
172
+ result: {
173
+ projectId: target.project.id,
174
+ projectName: target.project.name,
175
+ verboseContext: target,
176
+ database,
177
+ period: usage.period,
178
+ metrics: usage.metrics,
179
+ generatedAt: usage.generatedAt
180
+ },
181
+ warnings: [],
182
+ nextSteps: []
183
+ };
184
+ }
185
+ async function runDatabaseBackupList(context, databaseRef, flags) {
186
+ const limit = parseBackupLimit(flags.limit, resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd));
187
+ const { provider, target } = await requireDatabaseContext(context, flags, "database backup list");
188
+ const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
189
+ const backups = await provider.listBackups(database.id, {
190
+ limit,
191
+ signal: context.runtime.signal
192
+ });
193
+ return {
194
+ command: "database.backup.list",
195
+ result: {
196
+ projectId: target.project.id,
197
+ projectName: target.project.name,
198
+ verboseContext: target,
199
+ database,
200
+ backups: backups.backups,
201
+ retentionDays: backups.retentionDays,
202
+ hasMore: backups.hasMore
203
+ },
204
+ warnings: [],
205
+ nextSteps: []
206
+ };
207
+ }
208
+ async function runDatabaseRestore(context, databaseRef, flags) {
209
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
210
+ const backupId = flags.backupId?.trim();
211
+ if (!backupId) throw usageError("Backup id required", "Database restore needs the backup to restore from.", `Pass --backup <backup-id> from ${formatCommand([
212
+ "database",
213
+ "backup",
214
+ "list",
215
+ "<database>"
216
+ ])}.`, [formatCommand([
217
+ "database",
218
+ "backup",
219
+ "list",
220
+ "<database>"
221
+ ])], "database");
222
+ const { provider, target } = await requireDatabaseContext(context, flags, "database restore");
223
+ const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
224
+ const sourceDatabase = flags.sourceDatabaseRef ? await resolveDatabase(provider, target, flags.sourceDatabaseRef, flags.branchName, context.runtime.signal) : database;
225
+ const sourceDatabaseArg = sourceDatabase.id === database.id ? "" : ` --source-database ${sourceDatabase.id}`;
226
+ requireExactConfirmation({
227
+ resourceName: "database",
228
+ commandName: "database restore",
229
+ id: database.id,
230
+ confirm: flags.confirm,
231
+ summary: "Confirm database restore",
232
+ why: "Restoring immediately and irreversibly overwrites all data in the target database, so it requires the exact target database id.",
233
+ nextStep: `${formatCommand([
234
+ "database",
235
+ "restore",
236
+ database.id,
237
+ "--backup",
238
+ backupId
239
+ ])}${sourceDatabaseArg} --confirm ${database.id}`
240
+ });
241
+ const restored = await provider.restoreDatabase({
242
+ targetDatabaseId: database.id,
243
+ sourceDatabaseId: sourceDatabase.id,
244
+ backupId,
245
+ projectId: target.project.id,
246
+ signal: context.runtime.signal
247
+ });
248
+ return {
249
+ command: "database.restore",
250
+ result: {
251
+ projectId: target.project.id,
252
+ projectName: target.project.name,
253
+ verboseContext: target,
254
+ database: ensureProjectId(restored, target.project.id),
255
+ source: {
256
+ databaseId: sourceDatabase.id,
257
+ backupId
258
+ }
259
+ },
260
+ warnings: [],
261
+ nextSteps: [formatCommand([
262
+ "database",
263
+ "show",
264
+ database.id
265
+ ])]
266
+ };
267
+ }
268
+ async function runDatabaseConnectionRotate(context, connectionRef, flags) {
269
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
270
+ const connectionId = connectionRef.trim();
271
+ if (!connectionId) throw usageError("Connection id required", "Database connection rotation needs a connection id.", "Pass the connection id to rotate.", [formatCommand([
272
+ "database",
273
+ "connection",
274
+ "rotate",
275
+ "<connection-id>",
276
+ "--confirm",
277
+ "<connection-id>"
278
+ ])], "database");
279
+ requireExactConfirmation({
280
+ resourceName: "database connection",
281
+ commandName: "database connection rotate",
282
+ id: connectionId,
283
+ confirm: flags.confirm,
284
+ summary: "Confirm database connection rotation",
285
+ why: "Rotating revokes the previous credentials and breaks clients still using them, so it requires the exact connection id.",
286
+ nextStep: formatCommand([
287
+ "database",
288
+ "connection",
289
+ "rotate",
290
+ connectionId,
291
+ "--confirm",
292
+ connectionId
293
+ ])
294
+ });
295
+ const rotated = await (await requireDatabaseProviderOnly(context)).rotateConnection(connectionId, { signal: context.runtime.signal });
296
+ return {
297
+ command: "database.connection.rotate",
298
+ result: {
299
+ connection: rotated.connection,
300
+ database: rotated.database,
301
+ connectionString: rotated.connectionString
302
+ },
303
+ warnings: [],
304
+ nextSteps: []
305
+ };
306
+ }
307
+ const USAGE_DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
308
+ const USAGE_DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
309
+ function parseUsageDate(value, flagName, dayBoundary, formatCommand) {
310
+ if (value === void 0) return;
311
+ const trimmed = value.trim();
312
+ if (USAGE_DATE_ONLY_PATTERN.test(trimmed) && isValidCalendarDate(trimmed)) return dayBoundary === "start" ? `${trimmed}T00:00:00.000Z` : `${trimmed}T23:59:59.999Z`;
313
+ if (USAGE_DATETIME_PATTERN.test(trimmed) && !Number.isNaN(Date.parse(trimmed)) && isValidCalendarDate(trimmed.slice(0, 10))) return trimmed;
314
+ throw usageError("Invalid usage period", `${flagName} must be an ISO date such as 2026-06-01 or an ISO datetime such as 2026-06-01T12:00:00Z.`, `Pass an ISO date or datetime to ${flagName}.`, [formatCommand([
315
+ "database",
316
+ "usage",
317
+ "<database>",
318
+ "--from",
319
+ "2026-06-01",
320
+ "--to",
321
+ "2026-06-30"
322
+ ])], "database");
323
+ }
324
+ function isValidCalendarDate(datePart) {
325
+ const timestamp = Date.parse(`${datePart}T00:00:00.000Z`);
326
+ return !Number.isNaN(timestamp) && new Date(timestamp).toISOString().startsWith(datePart);
327
+ }
328
+ function parseBackupLimit(value, formatCommand) {
329
+ if (value === void 0) return;
330
+ const limit = Number(value.trim());
331
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw usageError("Invalid backup limit", "--limit must be an integer between 1 and 100.", "Pass a --limit between 1 and 100.", [formatCommand([
332
+ "database",
333
+ "backup",
334
+ "list",
335
+ "<database>",
336
+ "--limit",
337
+ "50"
338
+ ])], "database");
339
+ return limit;
340
+ }
152
341
  async function requireDatabaseContext(context, flags, commandName) {
153
342
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
154
343
  if (!workspace) throw workspaceRequiredError();
155
- if (isRealMode(context)) {
156
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
157
- if (!client) throw authRequiredError();
344
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
345
+ if (!client) throw authRequiredError();
346
+ {
158
347
  const targetResult = await resolveProjectTarget({
159
348
  context,
160
349
  workspace,
161
350
  explicitProject: flags.projectRef,
162
- listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
351
+ listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
163
352
  commandName
164
353
  });
165
354
  if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
166
355
  return {
167
- provider: createManagementDatabaseProvider(client),
356
+ provider: createManagementDatabaseProvider(client, {
357
+ formatCommand: resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd),
358
+ workspaceId: workspace.id
359
+ }),
168
360
  target: targetResult.value
169
361
  };
170
362
  }
171
- const targetResult = await resolveProjectTarget({
172
- context,
173
- workspace,
174
- explicitProject: flags.projectRef,
175
- listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
176
- commandName
177
- });
178
- if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
179
- return {
180
- provider: createFixtureDatabaseProvider(context),
181
- target: targetResult.value
182
- };
183
363
  }
184
364
  async function requireDatabaseProviderOnly(context) {
185
- await requireAuthenticatedAuthState(context);
186
- if (isRealMode(context)) {
187
- const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
188
- if (!client) throw authRequiredError();
189
- return createManagementDatabaseProvider(client);
190
- }
191
- return createFixtureDatabaseProvider(context);
192
- }
193
- function createFixtureDatabaseProvider(context) {
194
- return {
195
- async listDatabases(options) {
196
- return context.api.listDatabasesForProject(options.projectId, options.branchName).map((database) => normalizeDatabase(database, database.projectId));
197
- },
198
- async showDatabase(databaseId) {
199
- const database = context.api.getDatabase(databaseId);
200
- return database ? normalizeDatabase(database, database.projectId) : null;
201
- },
202
- async createDatabase(options) {
203
- const created = context.api.createDatabase(options);
204
- return {
205
- database: normalizeDatabase(created.database, created.database.projectId),
206
- connection: normalizeConnection(created.connection, created.connection.databaseId),
207
- connectionString: created.connectionString
208
- };
209
- },
210
- async removeDatabase(databaseId) {
211
- if (!context.api.removeDatabase(databaseId)) throw databaseNotFoundError(databaseId);
212
- },
213
- async listConnections(databaseId) {
214
- if (!context.api.getDatabase(databaseId)) throw databaseNotFoundError(databaseId);
215
- return context.api.listDatabaseConnections(databaseId).map((connection) => normalizeConnection(connection, connection.databaseId));
216
- },
217
- async createConnection(options) {
218
- const created = context.api.createDatabaseConnection(options);
219
- if (!created) throw databaseNotFoundError(options.databaseId);
220
- return {
221
- connection: normalizeConnection(created.connection, created.connection.databaseId),
222
- connectionString: created.connectionString
223
- };
224
- },
225
- async removeConnection(connectionId) {
226
- if (!context.api.removeDatabaseConnection(connectionId)) throw connectionNotFoundError(connectionId);
227
- }
228
- };
365
+ const authState = await requireAuthenticatedAuthState(context);
366
+ const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
367
+ if (!client) throw authRequiredError();
368
+ return createManagementDatabaseProvider(client, {
369
+ formatCommand: resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd),
370
+ workspaceId: authState.workspace?.id
371
+ });
229
372
  }
230
373
  async function resolveDatabase(provider, target, databaseRef, branchName, signal) {
231
374
  const ref = databaseRef.trim();
@@ -238,10 +381,23 @@ async function resolveDatabase(provider, target, databaseRef, branchName, signal
238
381
  if (matches.length === 0) throw databaseNotFoundError(ref, target.project.name, branchName);
239
382
  if (matches.length > 1) throw databaseAmbiguousError(ref, matches, branchName);
240
383
  const selected = matches[0];
241
- return ensureProjectId(await provider.showDatabase(selected.id, {
384
+ const shown = await provider.showDatabase(selected.id, {
242
385
  projectId: target.project.id,
243
386
  signal
244
- }) ?? selected, target.project.id);
387
+ });
388
+ if (shown === null) throw databaseRemovedDuringResolutionError(selected, target.project.name);
389
+ return ensureProjectId(shown, target.project.id);
390
+ }
391
+ function databaseRemovedDuringResolutionError(database, projectName) {
392
+ return new CliError({
393
+ code: "DATABASE_NOT_FOUND",
394
+ domain: "database",
395
+ summary: "Database not found",
396
+ why: `"${database.name}" (${database.id}) was listed for project "${projectName}", but reading it returned 404. It was most likely removed while this command was running.`,
397
+ fix: "Re-run the command, or list the project's databases to see what is there now.",
398
+ exitCode: 1,
399
+ nextSteps: ["prisma-cli database list"]
400
+ });
245
401
  }
246
402
  function ensureProjectId(database, projectId) {
247
403
  return database.projectId ? database : {
@@ -262,11 +418,11 @@ function requireExactConfirmation(options) {
262
418
  throw new CliError({
263
419
  code: "CONFIRMATION_REQUIRED",
264
420
  domain: "database",
265
- summary: `Confirm ${options.resourceName} removal`,
266
- why: `Removing this ${options.resourceName} is destructive and requires the exact id.`,
421
+ summary: options.summary ?? `Confirm ${options.resourceName} removal`,
422
+ why: options.why ?? `Removing this ${options.resourceName} is destructive and requires the exact id.`,
267
423
  fix: `Rerun with --confirm ${options.id}.`,
268
424
  exitCode: 2,
269
- nextSteps: [`prisma-cli ${options.commandName} ${options.id} --confirm ${options.id}`],
425
+ nextSteps: [options.nextStep ?? `prisma-cli ${options.commandName} ${options.id} --confirm ${options.id}`],
270
426
  meta: {
271
427
  expectedConfirm: options.id,
272
428
  receivedConfirm: options.confirm ?? null
@@ -303,16 +459,5 @@ function databaseAmbiguousError(databaseRef, matches, branchName) {
303
459
  })) }
304
460
  });
305
461
  }
306
- function connectionNotFoundError(connectionId) {
307
- return new CliError({
308
- code: "DATABASE_CONNECTION_NOT_FOUND",
309
- domain: "database",
310
- summary: "Database connection not found",
311
- why: `No database connection matched "${connectionId}".`,
312
- fix: "Pass a connection id from prisma-cli database connection list <database>.",
313
- exitCode: 1,
314
- nextSteps: ["prisma-cli database connection list <database>"]
315
- });
316
- }
317
462
  //#endregion
318
- export { runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseShow };
463
+ export { runDatabaseBackupList, runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseConnectionRotate, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseRestore, runDatabaseShow, runDatabaseUsage };
@@ -0,0 +1,90 @@
1
+ import { CliError, usageError } from "../errors.js";
2
+ import { FEEDBACK_TIMEOUT_MS, unreachableDetail, unreadableBodyDetail } from "../lib/feedback.js";
3
+ import { getCliVersion } from "../lib/version.js";
4
+ //#region src/controllers/feedback.ts
5
+ const DEFAULT_FEEDBACK_ENDPOINT = "https://hiieirp2pwqnjvq9axzyg6d0.fra.prisma.build/feedback";
6
+ const MAX_MESSAGE_LENGTH = 4e3;
7
+ const MAX_EMAIL_LENGTH = 320;
8
+ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
9
+ async function runFeedback(context, messageArg, flags) {
10
+ const message = messageArg.trim();
11
+ if (!message) throw usageError("Feedback message required", "The message argument is empty.", "Pass a non-empty message.", ["prisma-cli feedback \"the deploy flow is great\""]);
12
+ if (message.length > MAX_MESSAGE_LENGTH) throw usageError("Feedback message too long", `The message is ${message.length} characters; the limit is ${MAX_MESSAGE_LENGTH}.`, "Shorten the message.");
13
+ const email = flags.email?.trim();
14
+ if (email !== void 0 && (email.length > MAX_EMAIL_LENGTH || !EMAIL_PATTERN.test(email))) throw usageError("Invalid email", `"${flags.email}" is not a valid email address of at most ${MAX_EMAIL_LENGTH} characters.`, "Pass a valid address with --email, or drop the flag to stay anonymous.", ["prisma-cli feedback \"please add X\" --email you@example.com"]);
15
+ const feedbackContext = {
16
+ cliVersion: getCliVersion(),
17
+ nodeVersion: process.version,
18
+ platform: process.platform,
19
+ arch: process.arch
20
+ };
21
+ return {
22
+ command: "feedback",
23
+ result: {
24
+ id: await postFeedback(context, context.runtime.env.PRISMA_CLI_FEEDBACK_URL || DEFAULT_FEEDBACK_ENDPOINT, {
25
+ message,
26
+ ...email ? { email } : {},
27
+ meta: { ...feedbackContext }
28
+ }),
29
+ email: email ?? null,
30
+ context: feedbackContext
31
+ },
32
+ warnings: [],
33
+ nextSteps: []
34
+ };
35
+ }
36
+ async function postFeedback(context, endpoint, body) {
37
+ const response = await sendFeedback(context, endpoint, body);
38
+ if (!response.ok) throw feedbackSendFailed(`The feedback service responded with HTTP ${response.status}${await readServiceError(context, response)}.`);
39
+ return readSubmissionId(context, response);
40
+ }
41
+ async function sendFeedback(context, endpoint, body) {
42
+ try {
43
+ return await fetch(endpoint, {
44
+ method: "POST",
45
+ headers: {
46
+ "content-type": "application/json",
47
+ "user-agent": `prisma-cli/${getCliVersion()}`
48
+ },
49
+ body: JSON.stringify(body),
50
+ signal: AbortSignal.any([context.runtime.signal, AbortSignal.timeout(FEEDBACK_TIMEOUT_MS)])
51
+ });
52
+ } catch (error) {
53
+ if (context.runtime.signal.aborted) throw error;
54
+ throw feedbackSendFailed(unreachableDetail(error));
55
+ }
56
+ }
57
+ async function readSubmissionId(context, response) {
58
+ let payload;
59
+ try {
60
+ payload = await response.json();
61
+ } catch (error) {
62
+ if (context.runtime.signal.aborted) throw error;
63
+ if (!(error instanceof SyntaxError)) throw feedbackSendFailed(unreadableBodyDetail(error));
64
+ payload = null;
65
+ }
66
+ return typeof payload?.id === "string" ? payload.id : null;
67
+ }
68
+ async function readServiceError(context, response) {
69
+ let payload;
70
+ try {
71
+ payload = await response.json();
72
+ } catch (error) {
73
+ if (context.runtime.signal.aborted) throw error;
74
+ return "";
75
+ }
76
+ return typeof payload?.error?.message === "string" ? ` (${payload.error.message})` : "";
77
+ }
78
+ function feedbackSendFailed(detail) {
79
+ return new CliError({
80
+ code: "FEEDBACK_SEND_FAILED",
81
+ domain: "cli",
82
+ summary: "Feedback could not be delivered",
83
+ why: detail,
84
+ fix: "Check your network and rerun the command.",
85
+ exitCode: 1,
86
+ nextSteps: []
87
+ });
88
+ }
89
+ //#endregion
90
+ export { runFeedback };