@prisma/cli 3.0.0-beta.21 → 3.0.0-beta.23

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,3 +1,4 @@
1
+ import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command.js";
1
2
  import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
3
  import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
3
4
  import { requireComputeAuth } from "../lib/auth/guard.js";
@@ -149,6 +150,197 @@ async function runDatabaseConnectionRemove(context, connectionRef, flags) {
149
150
  nextSteps: []
150
151
  };
151
152
  }
153
+ async function runDatabaseUsage(context, databaseRef, flags) {
154
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
155
+ const from = parseUsageDate(flags.from, "--from", "start", formatCommand);
156
+ const to = parseUsageDate(flags.to, "--to", "end", formatCommand);
157
+ 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([
158
+ "database",
159
+ "usage",
160
+ "<database>",
161
+ "--from",
162
+ "2026-06-01",
163
+ "--to",
164
+ "2026-06-30"
165
+ ])], "database");
166
+ const { provider, target } = await requireDatabaseContext(context, flags, "database usage");
167
+ const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
168
+ const usage = await provider.getUsage(database.id, {
169
+ from,
170
+ to,
171
+ signal: context.runtime.signal
172
+ });
173
+ return {
174
+ command: "database.usage",
175
+ result: {
176
+ projectId: target.project.id,
177
+ projectName: target.project.name,
178
+ verboseContext: target,
179
+ database,
180
+ period: usage.period,
181
+ metrics: usage.metrics,
182
+ generatedAt: usage.generatedAt
183
+ },
184
+ warnings: [],
185
+ nextSteps: []
186
+ };
187
+ }
188
+ async function runDatabaseBackupList(context, databaseRef, flags) {
189
+ const limit = parseBackupLimit(flags.limit, resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd));
190
+ const { provider, target } = await requireDatabaseContext(context, flags, "database backup list");
191
+ const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
192
+ const backups = await provider.listBackups(database.id, {
193
+ limit,
194
+ signal: context.runtime.signal
195
+ });
196
+ return {
197
+ command: "database.backup.list",
198
+ result: {
199
+ projectId: target.project.id,
200
+ projectName: target.project.name,
201
+ verboseContext: target,
202
+ database,
203
+ backups: backups.backups,
204
+ retentionDays: backups.retentionDays,
205
+ hasMore: backups.hasMore
206
+ },
207
+ warnings: [],
208
+ nextSteps: []
209
+ };
210
+ }
211
+ async function runDatabaseRestore(context, databaseRef, flags) {
212
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
213
+ const backupId = flags.backupId?.trim();
214
+ if (!backupId) throw usageError("Backup id required", "Database restore needs the backup to restore from.", `Pass --backup <backup-id> from ${formatCommand([
215
+ "database",
216
+ "backup",
217
+ "list",
218
+ "<database>"
219
+ ])}.`, [formatCommand([
220
+ "database",
221
+ "backup",
222
+ "list",
223
+ "<database>"
224
+ ])], "database");
225
+ const { provider, target } = await requireDatabaseContext(context, flags, "database restore");
226
+ const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
227
+ const sourceDatabase = flags.sourceDatabaseRef ? await resolveDatabase(provider, target, flags.sourceDatabaseRef, flags.branchName, context.runtime.signal) : database;
228
+ const sourceDatabaseArg = sourceDatabase.id === database.id ? "" : ` --source-database ${sourceDatabase.id}`;
229
+ requireExactConfirmation({
230
+ resourceName: "database",
231
+ commandName: "database restore",
232
+ id: database.id,
233
+ confirm: flags.confirm,
234
+ summary: "Confirm database restore",
235
+ why: "Restoring immediately and irreversibly overwrites all data in the target database, so it requires the exact target database id.",
236
+ nextStep: `${formatCommand([
237
+ "database",
238
+ "restore",
239
+ database.id,
240
+ "--backup",
241
+ backupId
242
+ ])}${sourceDatabaseArg} --confirm ${database.id}`
243
+ });
244
+ const restored = await provider.restoreDatabase({
245
+ targetDatabaseId: database.id,
246
+ sourceDatabaseId: sourceDatabase.id,
247
+ backupId,
248
+ projectId: target.project.id,
249
+ signal: context.runtime.signal
250
+ });
251
+ return {
252
+ command: "database.restore",
253
+ result: {
254
+ projectId: target.project.id,
255
+ projectName: target.project.name,
256
+ verboseContext: target,
257
+ database: ensureProjectId(restored, target.project.id),
258
+ source: {
259
+ databaseId: sourceDatabase.id,
260
+ backupId
261
+ }
262
+ },
263
+ warnings: [],
264
+ nextSteps: [formatCommand([
265
+ "database",
266
+ "show",
267
+ database.id
268
+ ])]
269
+ };
270
+ }
271
+ async function runDatabaseConnectionRotate(context, connectionRef, flags) {
272
+ const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
273
+ const connectionId = connectionRef.trim();
274
+ if (!connectionId) throw usageError("Connection id required", "Database connection rotation needs a connection id.", "Pass the connection id to rotate.", [formatCommand([
275
+ "database",
276
+ "connection",
277
+ "rotate",
278
+ "<connection-id>",
279
+ "--confirm",
280
+ "<connection-id>"
281
+ ])], "database");
282
+ requireExactConfirmation({
283
+ resourceName: "database connection",
284
+ commandName: "database connection rotate",
285
+ id: connectionId,
286
+ confirm: flags.confirm,
287
+ summary: "Confirm database connection rotation",
288
+ why: "Rotating revokes the previous credentials and breaks clients still using them, so it requires the exact connection id.",
289
+ nextStep: formatCommand([
290
+ "database",
291
+ "connection",
292
+ "rotate",
293
+ connectionId,
294
+ "--confirm",
295
+ connectionId
296
+ ])
297
+ });
298
+ const rotated = await (await requireDatabaseProviderOnly(context)).rotateConnection(connectionId, { signal: context.runtime.signal });
299
+ return {
300
+ command: "database.connection.rotate",
301
+ result: {
302
+ connection: rotated.connection,
303
+ database: rotated.database,
304
+ connectionString: rotated.connectionString
305
+ },
306
+ warnings: [],
307
+ nextSteps: []
308
+ };
309
+ }
310
+ const USAGE_DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
311
+ const USAGE_DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
312
+ function parseUsageDate(value, flagName, dayBoundary, formatCommand) {
313
+ if (value === void 0) return;
314
+ const trimmed = value.trim();
315
+ if (USAGE_DATE_ONLY_PATTERN.test(trimmed) && isValidCalendarDate(trimmed)) return dayBoundary === "start" ? `${trimmed}T00:00:00.000Z` : `${trimmed}T23:59:59.999Z`;
316
+ if (USAGE_DATETIME_PATTERN.test(trimmed) && !Number.isNaN(Date.parse(trimmed)) && isValidCalendarDate(trimmed.slice(0, 10))) return trimmed;
317
+ 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([
318
+ "database",
319
+ "usage",
320
+ "<database>",
321
+ "--from",
322
+ "2026-06-01",
323
+ "--to",
324
+ "2026-06-30"
325
+ ])], "database");
326
+ }
327
+ function isValidCalendarDate(datePart) {
328
+ const timestamp = Date.parse(`${datePart}T00:00:00.000Z`);
329
+ return !Number.isNaN(timestamp) && new Date(timestamp).toISOString().startsWith(datePart);
330
+ }
331
+ function parseBackupLimit(value, formatCommand) {
332
+ if (value === void 0) return;
333
+ const limit = Number(value.trim());
334
+ 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([
335
+ "database",
336
+ "backup",
337
+ "list",
338
+ "<database>",
339
+ "--limit",
340
+ "50"
341
+ ])], "database");
342
+ return limit;
343
+ }
152
344
  async function requireDatabaseContext(context, flags, commandName) {
153
345
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
154
346
  if (!workspace) throw workspaceRequiredError();
@@ -164,7 +356,7 @@ async function requireDatabaseContext(context, flags, commandName) {
164
356
  });
165
357
  if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
166
358
  return {
167
- provider: createManagementDatabaseProvider(client),
359
+ provider: createManagementDatabaseProvider(client, { formatCommand: resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd) }),
168
360
  target: targetResult.value
169
361
  };
170
362
  }
@@ -186,7 +378,7 @@ async function requireDatabaseProviderOnly(context) {
186
378
  if (isRealMode(context)) {
187
379
  const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
188
380
  if (!client) throw authRequiredError();
189
- return createManagementDatabaseProvider(client);
381
+ return createManagementDatabaseProvider(client, { formatCommand: resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd) });
190
382
  }
191
383
  return createFixtureDatabaseProvider(context);
192
384
  }
@@ -224,6 +416,40 @@ function createFixtureDatabaseProvider(context) {
224
416
  },
225
417
  async removeConnection(connectionId) {
226
418
  if (!context.api.removeDatabaseConnection(connectionId)) throw connectionNotFoundError(connectionId);
419
+ },
420
+ async getUsage(databaseId, options) {
421
+ if (!context.api.getDatabase(databaseId)) throw databaseNotFoundError(databaseId);
422
+ return context.api.getDatabaseUsage(databaseId, {
423
+ from: options?.from,
424
+ to: options?.to
425
+ });
426
+ },
427
+ async listBackups(databaseId, options) {
428
+ if (!context.api.getDatabase(databaseId)) throw databaseNotFoundError(databaseId);
429
+ return context.api.listDatabaseBackups(databaseId, options?.limit);
430
+ },
431
+ async restoreDatabase(options) {
432
+ const restored = context.api.restoreDatabase({
433
+ targetDatabaseId: options.targetDatabaseId,
434
+ sourceDatabaseId: options.sourceDatabaseId,
435
+ backupId: options.backupId
436
+ });
437
+ if (restored.outcome === "target-not-found") throw databaseNotFoundError(options.targetDatabaseId);
438
+ if (restored.outcome === "backup-not-found") throw backupNotFoundError(options.backupId, options.sourceDatabaseId, resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd));
439
+ return normalizeDatabase(restored.database, options.projectId);
440
+ },
441
+ async rotateConnection(connectionId) {
442
+ const rotated = context.api.rotateDatabaseConnection(connectionId);
443
+ if (!rotated) throw connectionNotFoundError(connectionId);
444
+ const database = context.api.getDatabase(rotated.connection.databaseId);
445
+ return {
446
+ connection: normalizeConnection(rotated.connection, rotated.connection.databaseId),
447
+ database: database ? {
448
+ id: database.id,
449
+ name: database.name
450
+ } : null,
451
+ connectionString: rotated.connectionString
452
+ };
227
453
  }
228
454
  };
229
455
  }
@@ -262,11 +488,11 @@ function requireExactConfirmation(options) {
262
488
  throw new CliError({
263
489
  code: "CONFIRMATION_REQUIRED",
264
490
  domain: "database",
265
- summary: `Confirm ${options.resourceName} removal`,
266
- why: `Removing this ${options.resourceName} is destructive and requires the exact id.`,
491
+ summary: options.summary ?? `Confirm ${options.resourceName} removal`,
492
+ why: options.why ?? `Removing this ${options.resourceName} is destructive and requires the exact id.`,
267
493
  fix: `Rerun with --confirm ${options.id}.`,
268
494
  exitCode: 2,
269
- nextSteps: [`prisma-cli ${options.commandName} ${options.id} --confirm ${options.id}`],
495
+ nextSteps: [options.nextStep ?? `prisma-cli ${options.commandName} ${options.id} --confirm ${options.id}`],
270
496
  meta: {
271
497
  expectedConfirm: options.id,
272
498
  receivedConfirm: options.confirm ?? null
@@ -303,6 +529,23 @@ function databaseAmbiguousError(databaseRef, matches, branchName) {
303
529
  })) }
304
530
  });
305
531
  }
532
+ function backupNotFoundError(backupId, sourceDatabaseId, formatCommand) {
533
+ const listCommand = formatCommand([
534
+ "database",
535
+ "backup",
536
+ "list",
537
+ sourceDatabaseId
538
+ ]);
539
+ return new CliError({
540
+ code: "DATABASE_BACKUP_NOT_FOUND",
541
+ domain: "database",
542
+ summary: "Database backup not found",
543
+ why: `No backup matched "${backupId}" for database "${sourceDatabaseId}".`,
544
+ fix: `Pass a backup id from ${listCommand}.`,
545
+ exitCode: 1,
546
+ nextSteps: [listCommand]
547
+ });
548
+ }
306
549
  function connectionNotFoundError(connectionId) {
307
550
  return new CliError({
308
551
  code: "DATABASE_CONNECTION_NOT_FOUND",
@@ -315,4 +558,4 @@ function connectionNotFoundError(connectionId) {
315
558
  });
316
559
  }
317
560
  //#endregion
318
- export { runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseShow };
561
+ export { runDatabaseBackupList, runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseConnectionRotate, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseRestore, runDatabaseShow, runDatabaseUsage };