@prisma/cli 3.0.0-beta.21 → 3.0.0-beta.22
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.
- package/dist/adapters/mock-api.js +96 -0
- package/dist/adapters/token-storage.js +23 -0
- package/dist/cli2.js +2 -0
- package/dist/commands/database/index.js +92 -2
- package/dist/commands/init/index.js +32 -0
- package/dist/commands/project/index.js +51 -3
- package/dist/controllers/app.js +1 -1
- package/dist/controllers/database.js +249 -6
- package/dist/controllers/init.js +491 -0
- package/dist/controllers/project.js +326 -3
- package/dist/lib/agent/cli-command.js +4 -1
- package/dist/lib/agent/package-manager.js +1 -1
- package/dist/lib/auth/recipient.js +42 -0
- package/dist/lib/database/provider.js +148 -1
- package/dist/lib/project/interactive-setup.js +4 -4
- package/dist/lib/project/provider.js +92 -0
- package/dist/presenters/app.js +22 -7
- package/dist/presenters/database.js +175 -1
- package/dist/presenters/init.js +29 -0
- package/dist/presenters/project.js +90 -1
- package/dist/shell/command-meta.js +98 -0
- package/dist/shell/command-runner.js +7 -1
- package/dist/shell/errors.js +7 -1
- package/dist/shell/prompt.js +5 -2
- package/package.json +1 -1
|
@@ -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 };
|