@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.
- 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 +5 -3
- 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/app/build.js +2 -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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { formatPrismaCliCommand } from "../../shell/cli-command.js";
|
|
2
|
+
import { CliError } from "../../shell/errors.js";
|
|
3
|
+
//#region src/lib/project/provider.ts
|
|
4
|
+
function createManagementProjectProvider(client) {
|
|
5
|
+
return {
|
|
6
|
+
async renameProject(options) {
|
|
7
|
+
const result = await client.PATCH("/v1/projects/{id}", {
|
|
8
|
+
params: { path: { id: options.projectId } },
|
|
9
|
+
body: { name: options.name },
|
|
10
|
+
signal: options.signal
|
|
11
|
+
});
|
|
12
|
+
const status = result.response?.status ?? 0;
|
|
13
|
+
if (status === 400 || status === 422) throw projectRenameFailedError(options.name, result.error);
|
|
14
|
+
if (result.error || !result.data) throw projectApiError("Failed to rename project", result.response, result.error);
|
|
15
|
+
const project = result.data.data;
|
|
16
|
+
return {
|
|
17
|
+
id: project.id,
|
|
18
|
+
name: project.name,
|
|
19
|
+
...project.url ? { url: project.url } : {}
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
async removeProject(options) {
|
|
23
|
+
const result = await client.DELETE("/v1/projects/{id}", {
|
|
24
|
+
params: { path: { id: options.projectId } },
|
|
25
|
+
signal: options.signal
|
|
26
|
+
});
|
|
27
|
+
if (result.response?.status === 400) throw projectRemoveBlockedError(options.projectId, result.error);
|
|
28
|
+
if (result.error) throw projectApiError("Failed to remove project", result.response, result.error);
|
|
29
|
+
},
|
|
30
|
+
async transferProject(options) {
|
|
31
|
+
const result = await client.POST("/v1/projects/{id}/transfer", {
|
|
32
|
+
params: { path: { id: options.projectId } },
|
|
33
|
+
body: { recipientAccessToken: options.recipientAccessToken },
|
|
34
|
+
signal: options.signal
|
|
35
|
+
});
|
|
36
|
+
if (result.response?.status === 400) throw projectTransferRejectedError(options.projectId, result.error);
|
|
37
|
+
if (result.error) throw projectApiError("Failed to transfer project", result.response, result.error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function projectRenameFailedError(name, error) {
|
|
42
|
+
return new CliError({
|
|
43
|
+
code: "PROJECT_RENAME_FAILED",
|
|
44
|
+
domain: "project",
|
|
45
|
+
summary: "Project rename failed",
|
|
46
|
+
why: error?.error?.message ?? `The platform rejected the name "${name}".`,
|
|
47
|
+
fix: error?.error?.hint ?? "Pass a different project name and retry the rename.",
|
|
48
|
+
exitCode: 1,
|
|
49
|
+
nextSteps: []
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function projectRemoveBlockedError(projectId, error) {
|
|
53
|
+
return new CliError({
|
|
54
|
+
code: "PROJECT_REMOVE_BLOCKED",
|
|
55
|
+
domain: "project",
|
|
56
|
+
summary: "Project cannot be removed yet",
|
|
57
|
+
why: error?.error?.message ?? `Project "${projectId}" still has active deployments.`,
|
|
58
|
+
fix: "Remove the project's apps first, then retry the removal.",
|
|
59
|
+
exitCode: 1,
|
|
60
|
+
nextSteps: [formatPrismaCliCommand([
|
|
61
|
+
"app",
|
|
62
|
+
"remove",
|
|
63
|
+
"--app",
|
|
64
|
+
"<name>"
|
|
65
|
+
])]
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function projectTransferRejectedError(projectId, error) {
|
|
69
|
+
return new CliError({
|
|
70
|
+
code: "PROJECT_TRANSFER_REJECTED",
|
|
71
|
+
domain: "project",
|
|
72
|
+
summary: "Project transfer was rejected",
|
|
73
|
+
why: error?.error?.message ?? `The platform rejected the transfer of project "${projectId}", for example because the recipient token is invalid or expired.`,
|
|
74
|
+
fix: "Check the recipient workspace session or token and retry the transfer.",
|
|
75
|
+
exitCode: 1,
|
|
76
|
+
nextSteps: []
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function projectApiError(summary, response, error) {
|
|
80
|
+
const status = response?.status ?? 0;
|
|
81
|
+
return new CliError({
|
|
82
|
+
code: error?.error?.code ?? "PROJECT_API_ERROR",
|
|
83
|
+
domain: "project",
|
|
84
|
+
summary,
|
|
85
|
+
why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
|
|
86
|
+
fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
|
|
87
|
+
exitCode: 1,
|
|
88
|
+
nextSteps: []
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { createManagementProjectProvider, projectRemoveBlockedError, projectRenameFailedError, projectTransferRejectedError };
|
package/dist/presenters/app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolvePrismaCliPackageCommandSync } from "../lib/agent/cli-command.js";
|
|
1
2
|
import { renderVerboseBlock } from "../shell/ui.js";
|
|
2
3
|
import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
|
|
3
4
|
import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
|
|
@@ -38,17 +39,31 @@ function renderAppDeploy(context, descriptor, result, options) {
|
|
|
38
39
|
],
|
|
39
40
|
...renderBranchDatabaseDeploySummary(context, result),
|
|
40
41
|
"",
|
|
41
|
-
...renderDeployOutputRows(context.ui, [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
...renderDeployOutputRows(context.ui, [
|
|
43
|
+
...result.promoted ? [] : [{
|
|
44
|
+
label: "Promote",
|
|
45
|
+
value: `prisma-cli app promote ${result.deployment.id}`
|
|
46
|
+
}],
|
|
47
|
+
{
|
|
48
|
+
label: "Logs",
|
|
49
|
+
value: logsCommand
|
|
50
|
+
},
|
|
51
|
+
...deployUsedComputeConfig(result) ? [] : [{
|
|
52
|
+
label: "Config",
|
|
53
|
+
value: resolvePrismaCliPackageCommandSync(context.runtime.cwd, ["init"])
|
|
54
|
+
}]
|
|
55
|
+
]),
|
|
48
56
|
...renderDeployResolvedContextBlock(context, result),
|
|
49
57
|
...renderDeploySettingsBlock(context, result)
|
|
50
58
|
];
|
|
51
59
|
}
|
|
60
|
+
function deployUsedComputeConfig(result) {
|
|
61
|
+
return result.deploySettings.config.path !== null || [
|
|
62
|
+
result.deploySettings.framework.source,
|
|
63
|
+
result.deploySettings.buildCommand.source,
|
|
64
|
+
result.deploySettings.outputDirectory.source
|
|
65
|
+
].some((source) => source?.includes("prisma.compute"));
|
|
66
|
+
}
|
|
52
67
|
function isAppDeployAllResult(result) {
|
|
53
68
|
return "deployments" in result;
|
|
54
69
|
}
|
|
@@ -236,6 +236,180 @@ function renderDatabaseConnectionRemove(context, descriptor, result) {
|
|
|
236
236
|
function serializeDatabaseConnectionRemove(result) {
|
|
237
237
|
return { connection: result.connection };
|
|
238
238
|
}
|
|
239
|
+
function renderDatabaseUsage(context, descriptor, result) {
|
|
240
|
+
const lines = renderShow({
|
|
241
|
+
title: "Showing database usage metrics.",
|
|
242
|
+
descriptor,
|
|
243
|
+
fields: [
|
|
244
|
+
{
|
|
245
|
+
key: "project",
|
|
246
|
+
value: result.projectName
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
key: "database",
|
|
250
|
+
value: result.database.name
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
key: "id",
|
|
254
|
+
value: result.database.id,
|
|
255
|
+
tone: "dim"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
key: "period",
|
|
259
|
+
value: `${formatUsageTimestamp(result.period.start)} to ${formatUsageTimestamp(result.period.end)}`
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: "operations",
|
|
263
|
+
value: `${result.metrics.operations.used} ${result.metrics.operations.unit}`
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
key: "storage",
|
|
267
|
+
value: `${result.metrics.storage.used} ${result.metrics.storage.unit}`
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
key: "generated",
|
|
271
|
+
value: formatUsageTimestamp(result.generatedAt),
|
|
272
|
+
tone: "dim"
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
}, context.ui);
|
|
276
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
277
|
+
return lines;
|
|
278
|
+
}
|
|
279
|
+
function serializeDatabaseUsage(result) {
|
|
280
|
+
return stripVerboseContext(result);
|
|
281
|
+
}
|
|
282
|
+
function renderDatabaseBackupList(context, descriptor, result) {
|
|
283
|
+
const ui = context.ui;
|
|
284
|
+
const lines = [`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing platform-created database backups.")}`, ""];
|
|
285
|
+
const rail = ui.dim("│");
|
|
286
|
+
lines.push(`${rail} ${ui.accent("database:")} ${result.database.name}`);
|
|
287
|
+
if (result.retentionDays !== null) lines.push(`${rail} ${ui.accent("retention:")} ${result.retentionDays} days`);
|
|
288
|
+
lines.push(rail);
|
|
289
|
+
if (result.backups.length === 0) {
|
|
290
|
+
lines.push(`${rail} ${ui.dim("No backups found.")}`);
|
|
291
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
292
|
+
return lines;
|
|
293
|
+
}
|
|
294
|
+
const rows = result.backups.map((backup) => [
|
|
295
|
+
backup.id,
|
|
296
|
+
backup.backupType,
|
|
297
|
+
backup.status,
|
|
298
|
+
formatBackupSize(backup.size),
|
|
299
|
+
backup.createdAt
|
|
300
|
+
]);
|
|
301
|
+
const widths = [
|
|
302
|
+
Math.max(2, ...rows.map((row) => row[0].length)),
|
|
303
|
+
Math.max(4, ...rows.map((row) => row[1].length)),
|
|
304
|
+
Math.max(6, ...rows.map((row) => row[2].length)),
|
|
305
|
+
Math.max(4, ...rows.map((row) => row[3].length)),
|
|
306
|
+
Math.max(7, ...rows.map((row) => row[4].length))
|
|
307
|
+
];
|
|
308
|
+
lines.push(`${rail} ${ui.accent(formatColumns([
|
|
309
|
+
"Id",
|
|
310
|
+
"Type",
|
|
311
|
+
"Status",
|
|
312
|
+
"Size",
|
|
313
|
+
"Created"
|
|
314
|
+
], widths))}`);
|
|
315
|
+
for (const row of rows) lines.push(`${rail} ${formatColumns(row, widths)}`);
|
|
316
|
+
if (result.hasMore) {
|
|
317
|
+
lines.push(rail);
|
|
318
|
+
lines.push(`${rail} ${ui.dim("More backups exist; raise --limit to see them.")}`);
|
|
319
|
+
}
|
|
320
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
321
|
+
return lines;
|
|
322
|
+
}
|
|
323
|
+
function serializeDatabaseBackupList(result) {
|
|
324
|
+
return {
|
|
325
|
+
...serializeList({
|
|
326
|
+
context: {
|
|
327
|
+
project: result.projectName,
|
|
328
|
+
database: result.database.name
|
|
329
|
+
},
|
|
330
|
+
items: result.backups.map((backup) => ({
|
|
331
|
+
noun: "backup",
|
|
332
|
+
label: backup.id,
|
|
333
|
+
id: backup.id,
|
|
334
|
+
status: null
|
|
335
|
+
}))
|
|
336
|
+
}),
|
|
337
|
+
projectId: result.projectId,
|
|
338
|
+
database: result.database,
|
|
339
|
+
backups: result.backups,
|
|
340
|
+
retentionDays: result.retentionDays,
|
|
341
|
+
hasMore: result.hasMore
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function renderDatabaseRestore(context, descriptor, result) {
|
|
345
|
+
const lines = renderMutate({
|
|
346
|
+
title: "Restoring database from backup.",
|
|
347
|
+
descriptor,
|
|
348
|
+
context: [
|
|
349
|
+
{
|
|
350
|
+
key: "project",
|
|
351
|
+
value: result.projectName
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
key: "database",
|
|
355
|
+
value: result.database.name
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
key: "id",
|
|
359
|
+
value: result.database.id,
|
|
360
|
+
tone: "dim"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
key: "backup",
|
|
364
|
+
value: result.source.backupId
|
|
365
|
+
},
|
|
366
|
+
...result.source.databaseId !== result.database.id ? [{
|
|
367
|
+
key: "source",
|
|
368
|
+
value: result.source.databaseId
|
|
369
|
+
}] : []
|
|
370
|
+
],
|
|
371
|
+
operationDescription: "Restoring database",
|
|
372
|
+
operationCount: 1,
|
|
373
|
+
details: [`The restore is running; the database status is "${result.database.status ?? "recovering"}" until it completes.`, "Connections and credentials are preserved."]
|
|
374
|
+
}, context.ui);
|
|
375
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
376
|
+
return lines;
|
|
377
|
+
}
|
|
378
|
+
function serializeDatabaseRestore(result) {
|
|
379
|
+
return stripVerboseContext(result);
|
|
380
|
+
}
|
|
381
|
+
function renderDatabaseConnectionRotateStdout(_context, _descriptor, result) {
|
|
382
|
+
return [result.connectionString];
|
|
383
|
+
}
|
|
384
|
+
function renderDatabaseConnectionRotate(context, _descriptor, result) {
|
|
385
|
+
const ui = context.ui;
|
|
386
|
+
const lines = [
|
|
387
|
+
"Rotating connection...",
|
|
388
|
+
renderSummaryLine(ui, "success", `Rotated credentials for ${result.database ? `"${result.database.name}"` : `connection ${result.connection.id}`}. The previous credentials no longer work.`),
|
|
389
|
+
" The connection URL below is shown once, so save it now."
|
|
390
|
+
];
|
|
391
|
+
if (ui.verbose) {
|
|
392
|
+
lines.push("");
|
|
393
|
+
lines.push(...renderDatabaseConnectionRotateVerboseRows(context, result));
|
|
394
|
+
}
|
|
395
|
+
return lines;
|
|
396
|
+
}
|
|
397
|
+
function serializeDatabaseConnectionRotate(result) {
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
function renderDatabaseConnectionRotateVerboseRows(context, result) {
|
|
401
|
+
return renderMetadataRows([...result.database ? [["database", formatResourceWithId(context, result.database.name, result.database.id)]] : [], ["connection", formatResourceWithId(context, result.connection.name, result.connection.id)]]);
|
|
402
|
+
}
|
|
403
|
+
function formatBackupSize(size) {
|
|
404
|
+
if (size === null) return "unknown";
|
|
405
|
+
if (size < 1024) return `${size} B`;
|
|
406
|
+
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KiB`;
|
|
407
|
+
if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MiB`;
|
|
408
|
+
return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GiB`;
|
|
409
|
+
}
|
|
410
|
+
function formatUsageTimestamp(value) {
|
|
411
|
+
return value || "unknown";
|
|
412
|
+
}
|
|
239
413
|
function formatStatus(database) {
|
|
240
414
|
return database.status ?? (database.isDefault ? "default" : "unknown");
|
|
241
415
|
}
|
|
@@ -271,4 +445,4 @@ function renderMetadataRows(rows) {
|
|
|
271
445
|
return rows.map(([key, value]) => ` ${formatColumns([key, value], widths)}`);
|
|
272
446
|
}
|
|
273
447
|
//#endregion
|
|
274
|
-
export { renderDatabaseConnectionCreate, renderDatabaseConnectionCreateStdout, renderDatabaseConnectionList, renderDatabaseConnectionRemove, renderDatabaseCreate, renderDatabaseCreateStdout, renderDatabaseList, renderDatabaseRemove, renderDatabaseShow, serializeDatabaseConnectionCreate, serializeDatabaseConnectionList, serializeDatabaseConnectionRemove, serializeDatabaseCreate, serializeDatabaseList, serializeDatabaseRemove, serializeDatabaseShow };
|
|
448
|
+
export { renderDatabaseBackupList, renderDatabaseConnectionCreate, renderDatabaseConnectionCreateStdout, renderDatabaseConnectionList, renderDatabaseConnectionRemove, renderDatabaseConnectionRotate, renderDatabaseConnectionRotateStdout, renderDatabaseCreate, renderDatabaseCreateStdout, renderDatabaseList, renderDatabaseRemove, renderDatabaseRestore, renderDatabaseShow, renderDatabaseUsage, serializeDatabaseBackupList, serializeDatabaseConnectionCreate, serializeDatabaseConnectionList, serializeDatabaseConnectionRemove, serializeDatabaseConnectionRotate, serializeDatabaseCreate, serializeDatabaseList, serializeDatabaseRemove, serializeDatabaseRestore, serializeDatabaseShow, serializeDatabaseUsage };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command.js";
|
|
2
|
+
import { renderNextSteps, renderSummaryLine } from "../shell/ui.js";
|
|
3
|
+
//#region src/presenters/init.ts
|
|
4
|
+
function renderInit(context, _descriptor, result) {
|
|
5
|
+
const ui = context.ui;
|
|
6
|
+
const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
7
|
+
const lines = [renderSummaryLine(ui, "success", `Wrote ${result.configPath}`)];
|
|
8
|
+
if (result.types.status === "installed") lines.push(renderSummaryLine(ui, "success", `Installed ${result.types.package} (config types)`));
|
|
9
|
+
else if ((result.types.status === "skipped" || result.types.status === "declined") && result.types.installCommand) lines.push(` ${ui.dim(`For editor types: ${result.types.installCommand}`)}`);
|
|
10
|
+
switch (result.link.status) {
|
|
11
|
+
case "linked":
|
|
12
|
+
lines.push(renderSummaryLine(ui, "success", `Linked "${result.directory}" to Project "${result.link.project?.name ?? ""}"`));
|
|
13
|
+
break;
|
|
14
|
+
case "already-linked": break;
|
|
15
|
+
case "skipped":
|
|
16
|
+
case "declined":
|
|
17
|
+
lines.push(` ${ui.dim(`Not linked to a Project yet; link with ${formatCommand(["project", "link"])}.`)}`);
|
|
18
|
+
break;
|
|
19
|
+
case "failed": break;
|
|
20
|
+
}
|
|
21
|
+
const linked = result.link.status === "linked" || result.link.status === "already-linked";
|
|
22
|
+
lines.push(...renderNextSteps([formatCommand(["app", "deploy"]), ...linked ? [] : [formatCommand(["project", "link"])]]));
|
|
23
|
+
return lines;
|
|
24
|
+
}
|
|
25
|
+
function serializeInit(result) {
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { renderInit, serializeInit };
|
|
@@ -86,6 +86,95 @@ function renderProjectSetup(context, _descriptor, result) {
|
|
|
86
86
|
function serializeProjectSetup(result) {
|
|
87
87
|
return result;
|
|
88
88
|
}
|
|
89
|
+
function renderProjectRename(context, descriptor, result) {
|
|
90
|
+
return renderMutate({
|
|
91
|
+
title: "Renaming project.",
|
|
92
|
+
descriptor,
|
|
93
|
+
context: [
|
|
94
|
+
{
|
|
95
|
+
key: "workspace",
|
|
96
|
+
value: result.workspace.name
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: "project",
|
|
100
|
+
value: result.previousName
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
key: "id",
|
|
104
|
+
value: result.project.id,
|
|
105
|
+
tone: "dim"
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
operationDescription: "Renaming project",
|
|
109
|
+
operationCount: 1,
|
|
110
|
+
details: [`The project is now named "${result.project.name}". Directory bindings pin the project id, so they stay valid.`]
|
|
111
|
+
}, context.ui);
|
|
112
|
+
}
|
|
113
|
+
function serializeProjectRename(result) {
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
function renderProjectRemove(context, descriptor, result) {
|
|
117
|
+
return renderMutate({
|
|
118
|
+
title: "Removing project.",
|
|
119
|
+
descriptor,
|
|
120
|
+
context: [
|
|
121
|
+
{
|
|
122
|
+
key: "workspace",
|
|
123
|
+
value: result.workspace.name
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: "project",
|
|
127
|
+
value: result.project.name
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
key: "id",
|
|
131
|
+
value: result.project.id,
|
|
132
|
+
tone: "dim"
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
operationDescription: "Removing project",
|
|
136
|
+
operationCount: 1,
|
|
137
|
+
details: ["The project, its databases, and its apps were removed.", ...result.localPin.cleared ? ["This directory's local project binding was cleared."] : []]
|
|
138
|
+
}, context.ui);
|
|
139
|
+
}
|
|
140
|
+
function serializeProjectRemove(result) {
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
function renderProjectTransfer(context, descriptor, result) {
|
|
144
|
+
return renderMutate({
|
|
145
|
+
title: "Transferring project.",
|
|
146
|
+
descriptor,
|
|
147
|
+
context: [
|
|
148
|
+
{
|
|
149
|
+
key: "workspace",
|
|
150
|
+
value: result.workspace.name
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: "project",
|
|
154
|
+
value: result.project.name
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
key: "id",
|
|
158
|
+
value: result.project.id,
|
|
159
|
+
tone: "dim"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "recipient",
|
|
163
|
+
value: result.recipient.workspaceName ?? result.recipient.workspaceId ?? "workspace of the provided recipient token"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
operationDescription: "Transferring project",
|
|
167
|
+
operationCount: 1,
|
|
168
|
+
details: [
|
|
169
|
+
"The project now belongs to the recipient workspace; this workspace no longer has access.",
|
|
170
|
+
...result.localPin.action === "rewritten" ? ["This directory's local project binding now points at the recipient workspace."] : [],
|
|
171
|
+
...result.localPin.action === "cleared" ? ["This directory's local project binding was cleared."] : []
|
|
172
|
+
]
|
|
173
|
+
}, context.ui);
|
|
174
|
+
}
|
|
175
|
+
function serializeProjectTransfer(result) {
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
89
178
|
function renderGitConnect(context, descriptor, result) {
|
|
90
179
|
const connection = result.repositoryConnection;
|
|
91
180
|
return renderMutate({
|
|
@@ -171,4 +260,4 @@ function formatGitConnectionDetail(status) {
|
|
|
171
260
|
}
|
|
172
261
|
}
|
|
173
262
|
//#endregion
|
|
174
|
-
export { renderGitConnect, renderGitDisconnect, renderProjectList, renderProjectSetup, renderProjectShow, serializeProjectList, serializeProjectSetup, serializeProjectShow };
|
|
263
|
+
export { renderGitConnect, renderGitDisconnect, renderProjectList, renderProjectRemove, renderProjectRename, renderProjectSetup, renderProjectShow, renderProjectTransfer, serializeProjectList, serializeProjectRemove, serializeProjectRename, serializeProjectSetup, serializeProjectShow, serializeProjectTransfer };
|
|
@@ -20,6 +20,22 @@ const DESCRIPTORS = [
|
|
|
20
20
|
description: "Show CLI build and environment",
|
|
21
21
|
examples: ["prisma-cli version", "prisma-cli version --json"]
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
id: "init",
|
|
25
|
+
path: ["prisma", "init"],
|
|
26
|
+
description: "Write a committed prisma.compute.ts for this app",
|
|
27
|
+
examples: (runtime) => agentCommandExamples(runtime, [
|
|
28
|
+
["init"],
|
|
29
|
+
[
|
|
30
|
+
"init",
|
|
31
|
+
"--framework",
|
|
32
|
+
"hono",
|
|
33
|
+
"--entry",
|
|
34
|
+
"src/index.ts"
|
|
35
|
+
],
|
|
36
|
+
["init", "--no-link"]
|
|
37
|
+
])
|
|
38
|
+
},
|
|
23
39
|
{
|
|
24
40
|
id: "agent",
|
|
25
41
|
path: ["prisma", "agent"],
|
|
@@ -271,6 +287,36 @@ const DESCRIPTORS = [
|
|
|
271
287
|
"prisma-cli project link \"Acme Dashboard\" --json"
|
|
272
288
|
]
|
|
273
289
|
},
|
|
290
|
+
{
|
|
291
|
+
id: "project.rename",
|
|
292
|
+
path: [
|
|
293
|
+
"prisma",
|
|
294
|
+
"project",
|
|
295
|
+
"rename"
|
|
296
|
+
],
|
|
297
|
+
description: "Rename the resolved Project",
|
|
298
|
+
examples: ["prisma-cli project rename \"Acme Dashboard v2\"", "prisma-cli project rename billing-api --project proj_123"]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "project.remove",
|
|
302
|
+
path: [
|
|
303
|
+
"prisma",
|
|
304
|
+
"project",
|
|
305
|
+
"remove"
|
|
306
|
+
],
|
|
307
|
+
description: "Remove a Project permanently after exact id confirmation",
|
|
308
|
+
examples: ["prisma-cli project remove proj_123 --confirm proj_123"]
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "project.transfer",
|
|
312
|
+
path: [
|
|
313
|
+
"prisma",
|
|
314
|
+
"project",
|
|
315
|
+
"transfer"
|
|
316
|
+
],
|
|
317
|
+
description: "Transfer a Project to another workspace after exact id confirmation",
|
|
318
|
+
examples: ["prisma-cli project transfer proj_123 --to-workspace \"Prisma Labs\" --confirm proj_123", "prisma-cli project transfer proj_123 --recipient-token <token> --confirm proj_123"]
|
|
319
|
+
},
|
|
274
320
|
{
|
|
275
321
|
id: "git.connect",
|
|
276
322
|
path: [
|
|
@@ -339,6 +385,26 @@ const DESCRIPTORS = [
|
|
|
339
385
|
description: "Create a Prisma Postgres database and print its one-time connection URL",
|
|
340
386
|
examples: ["prisma-cli database create my-db", "prisma-cli database create my-db --branch feature/foo --region eu-central-1"]
|
|
341
387
|
},
|
|
388
|
+
{
|
|
389
|
+
id: "database.usage",
|
|
390
|
+
path: [
|
|
391
|
+
"prisma",
|
|
392
|
+
"database",
|
|
393
|
+
"usage"
|
|
394
|
+
],
|
|
395
|
+
description: "Show usage metrics for a database",
|
|
396
|
+
examples: ["prisma-cli database usage db_123", "prisma-cli database usage acme-production --from 2026-06-01 --to 2026-06-30"]
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "database.restore",
|
|
400
|
+
path: [
|
|
401
|
+
"prisma",
|
|
402
|
+
"database",
|
|
403
|
+
"restore"
|
|
404
|
+
],
|
|
405
|
+
description: "Restore a database from a backup after exact id confirmation",
|
|
406
|
+
examples: ["prisma-cli database restore db_123 --backup bkp_456 --confirm db_123"]
|
|
407
|
+
},
|
|
342
408
|
{
|
|
343
409
|
id: "database.remove",
|
|
344
410
|
path: [
|
|
@@ -349,6 +415,27 @@ const DESCRIPTORS = [
|
|
|
349
415
|
description: "Remove a database after exact id confirmation",
|
|
350
416
|
examples: ["prisma-cli database remove db_123 --confirm db_123"]
|
|
351
417
|
},
|
|
418
|
+
{
|
|
419
|
+
id: "database.backup",
|
|
420
|
+
path: [
|
|
421
|
+
"prisma",
|
|
422
|
+
"database",
|
|
423
|
+
"backup"
|
|
424
|
+
],
|
|
425
|
+
description: "Inspect platform-created database backups",
|
|
426
|
+
examples: ["prisma-cli database backup list db_123"]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: "database.backup.list",
|
|
430
|
+
path: [
|
|
431
|
+
"prisma",
|
|
432
|
+
"database",
|
|
433
|
+
"backup",
|
|
434
|
+
"list"
|
|
435
|
+
],
|
|
436
|
+
description: "List backups for a database",
|
|
437
|
+
examples: ["prisma-cli database backup list db_123", "prisma-cli database backup list acme-production --limit 50"]
|
|
438
|
+
},
|
|
352
439
|
{
|
|
353
440
|
id: "database.connection",
|
|
354
441
|
path: [
|
|
@@ -385,6 +472,17 @@ const DESCRIPTORS = [
|
|
|
385
472
|
description: "Create a database connection and print its one-time connection URL",
|
|
386
473
|
examples: ["prisma-cli database connection create db_123", "prisma-cli database connection create db_123 --name readonly"]
|
|
387
474
|
},
|
|
475
|
+
{
|
|
476
|
+
id: "database.connection.rotate",
|
|
477
|
+
path: [
|
|
478
|
+
"prisma",
|
|
479
|
+
"database",
|
|
480
|
+
"connection",
|
|
481
|
+
"rotate"
|
|
482
|
+
],
|
|
483
|
+
description: "Rotate connection credentials and print the new one-time connection URL",
|
|
484
|
+
examples: ["prisma-cli database connection rotate conn_123 --confirm conn_123"]
|
|
485
|
+
},
|
|
388
486
|
{
|
|
389
487
|
id: "database.connection.remove",
|
|
390
488
|
path: [
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CliError, authConfigInvalidError, authRequiredError, commandCanceledError } from "./errors.js";
|
|
2
2
|
import { getCommandDescriptor } from "./command-meta.js";
|
|
3
|
+
import { renderSummaryLine } from "./ui.js";
|
|
3
4
|
import { resolveGlobalFlags } from "./global-flags.js";
|
|
4
5
|
import { createCommandContext } from "./runtime.js";
|
|
5
6
|
import { collectCommandDiagnostics } from "../lib/diagnostics.js";
|
|
@@ -48,11 +49,16 @@ async function writeCommandSuccess(context, descriptor, success, presenter, dura
|
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
const rendered = presenter.renderHuman(context, descriptor, success.result);
|
|
52
|
+
const warningLines = success.warnings.map((warning) => renderSummaryLine(context.ui, "warning", warning));
|
|
51
53
|
const diagnostics = await renderBestEffortCommandDiagnostics(context, {
|
|
52
54
|
enabled: context.flags.verbose && rendered.length > 0,
|
|
53
55
|
durationMs
|
|
54
56
|
});
|
|
55
|
-
const humanLines = [
|
|
57
|
+
const humanLines = [
|
|
58
|
+
...rendered,
|
|
59
|
+
...warningLines,
|
|
60
|
+
...diagnostics
|
|
61
|
+
];
|
|
56
62
|
if (stdout.length > 0 && humanLines.length > 0) humanLines.push("");
|
|
57
63
|
writeHumanLines(context.output, humanLines);
|
|
58
64
|
writeStdoutLines(context, stdout);
|
package/dist/shell/errors.js
CHANGED
|
@@ -44,6 +44,12 @@ function usageError(summary, why, fix, nextSteps = [], domain = "cli") {
|
|
|
44
44
|
nextSteps
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
+
function isUsageError(error, summary) {
|
|
48
|
+
return isErrorRecord(error) && error.code === "USAGE_ERROR" && (summary === void 0 || error.summary === summary);
|
|
49
|
+
}
|
|
50
|
+
function isErrorRecord(error) {
|
|
51
|
+
return typeof error === "object" && error !== null;
|
|
52
|
+
}
|
|
47
53
|
function authRequiredError(nextSteps = ["prisma-cli auth login"], options = {}) {
|
|
48
54
|
return new CliError({
|
|
49
55
|
code: "AUTH_REQUIRED",
|
|
@@ -131,4 +137,4 @@ function featureUnavailableError(summary, why, fix, nextSteps = [], domain = "cl
|
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
//#endregion
|
|
134
|
-
export { CliError, authConfigInvalidError, authRequiredError, commandCanceledError, featureUnavailableError, usageError, workspaceAmbiguousError, workspaceNotAuthenticatedError, workspaceRequiredError, workspaceSwitchUnavailableError };
|
|
140
|
+
export { CliError, authConfigInvalidError, authRequiredError, commandCanceledError, featureUnavailableError, isUsageError, usageError, workspaceAmbiguousError, workspaceNotAuthenticatedError, workspaceRequiredError, workspaceSwitchUnavailableError };
|
package/dist/shell/prompt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { usageError } from "./errors.js";
|
|
1
|
+
import { isUsageError, usageError } from "./errors.js";
|
|
2
2
|
import { confirm, isCancel, select, text } from "@clack/prompts";
|
|
3
3
|
//#region src/shell/prompt.ts
|
|
4
4
|
const PROMPT_CANCELED_SUMMARY = "Interactive prompt canceled";
|
|
@@ -40,6 +40,9 @@ async function confirmPrompt(options) {
|
|
|
40
40
|
if (isCancel(response)) throw usageError(PROMPT_CANCELED_SUMMARY, "The command was canceled before a confirmation was made.", "Re-run the command and choose an option to continue.");
|
|
41
41
|
return response;
|
|
42
42
|
}
|
|
43
|
+
function isPromptCancelError(error) {
|
|
44
|
+
return isUsageError(error, PROMPT_CANCELED_SUMMARY);
|
|
45
|
+
}
|
|
43
46
|
function disposePromptState(_input) {}
|
|
44
47
|
//#endregion
|
|
45
|
-
export { confirmPrompt, disposePromptState, selectPrompt, textPrompt };
|
|
48
|
+
export { confirmPrompt, disposePromptState, isPromptCancelError, selectPrompt, textPrompt };
|