@prisma/cli 3.0.0-dev.71.1 → 3.0.0-dev.74.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.
- package/dist/adapters/mock-api.js +75 -0
- package/dist/cli2.js +2 -0
- package/dist/commands/database/index.js +159 -0
- package/dist/controllers/app.js +16 -3
- package/dist/controllers/database.js +316 -0
- package/dist/controllers/project.js +17 -3
- package/dist/lib/app/branch-database-deploy.js +1 -0
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/project/local-pin.js +64 -14
- package/dist/lib/project/resolution.js +16 -2
- package/dist/presenters/database.js +223 -0
- package/dist/shell/command-meta.js +101 -0
- package/dist/shell/command-runner.js +2 -0
- package/package.json +2 -1
|
@@ -56,6 +56,81 @@ var MockApi = class MockApi {
|
|
|
56
56
|
getDeployment(deploymentId) {
|
|
57
57
|
return this.data.deployments.find((deployment) => deployment.id === deploymentId);
|
|
58
58
|
}
|
|
59
|
+
listDatabasesForProject(projectId, branchName) {
|
|
60
|
+
return (this.data.databases ?? []).filter((database) => database.projectId === projectId && (!branchName || database.branchName === branchName));
|
|
61
|
+
}
|
|
62
|
+
getDatabase(databaseId) {
|
|
63
|
+
return (this.data.databases ?? []).find((database) => database.id === databaseId);
|
|
64
|
+
}
|
|
65
|
+
createDatabase(input) {
|
|
66
|
+
this.data.databases ??= [];
|
|
67
|
+
this.data.databaseConnections ??= [];
|
|
68
|
+
const database = {
|
|
69
|
+
id: `db_${this.data.databases.length + 1e3}`,
|
|
70
|
+
projectId: input.projectId,
|
|
71
|
+
branchId: input.branchName ? this.getBranchForProject(input.projectId, input.branchName)?.id ?? null : null,
|
|
72
|
+
branchName: input.branchName ?? null,
|
|
73
|
+
name: input.name,
|
|
74
|
+
region: input.region ?? null,
|
|
75
|
+
status: "ready",
|
|
76
|
+
isDefault: false,
|
|
77
|
+
createdAt: "2026-06-09T00:00:00.000Z"
|
|
78
|
+
};
|
|
79
|
+
const connectionString = `postgresql://${database.id}.example.prisma.io/postgres`;
|
|
80
|
+
const connection = {
|
|
81
|
+
id: `conn_${this.data.databaseConnections.length + 1e3}`,
|
|
82
|
+
databaseId: database.id,
|
|
83
|
+
name: database.name,
|
|
84
|
+
createdAt: "2026-06-09T00:00:00.000Z",
|
|
85
|
+
connectionString
|
|
86
|
+
};
|
|
87
|
+
this.data.databases.push(database);
|
|
88
|
+
this.data.databaseConnections.push(connection);
|
|
89
|
+
return {
|
|
90
|
+
database,
|
|
91
|
+
connection,
|
|
92
|
+
connectionString
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
removeDatabase(databaseId) {
|
|
96
|
+
this.data.databases ??= [];
|
|
97
|
+
this.data.databaseConnections ??= [];
|
|
98
|
+
const database = this.getDatabase(databaseId);
|
|
99
|
+
if (!database) return;
|
|
100
|
+
this.data.databases = this.data.databases.filter((candidate) => candidate.id !== databaseId);
|
|
101
|
+
this.data.databaseConnections = this.data.databaseConnections.filter((connection) => connection.databaseId !== databaseId);
|
|
102
|
+
return database;
|
|
103
|
+
}
|
|
104
|
+
listDatabaseConnections(databaseId) {
|
|
105
|
+
return (this.data.databaseConnections ?? []).filter((connection) => connection.databaseId === databaseId);
|
|
106
|
+
}
|
|
107
|
+
getDatabaseConnection(connectionId) {
|
|
108
|
+
return (this.data.databaseConnections ?? []).find((connection) => connection.id === connectionId);
|
|
109
|
+
}
|
|
110
|
+
createDatabaseConnection(input) {
|
|
111
|
+
if (!this.getDatabase(input.databaseId)) return;
|
|
112
|
+
this.data.databaseConnections ??= [];
|
|
113
|
+
const connectionString = `postgresql://${input.databaseId}-${this.data.databaseConnections.length + 1}.example.prisma.io/postgres`;
|
|
114
|
+
const connection = {
|
|
115
|
+
id: `conn_${this.data.databaseConnections.length + 1e3}`,
|
|
116
|
+
databaseId: input.databaseId,
|
|
117
|
+
name: input.name,
|
|
118
|
+
createdAt: "2026-06-09T00:00:00.000Z",
|
|
119
|
+
connectionString
|
|
120
|
+
};
|
|
121
|
+
this.data.databaseConnections.push(connection);
|
|
122
|
+
return {
|
|
123
|
+
connection,
|
|
124
|
+
connectionString
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
removeDatabaseConnection(connectionId) {
|
|
128
|
+
this.data.databaseConnections ??= [];
|
|
129
|
+
const connection = this.getDatabaseConnection(connectionId);
|
|
130
|
+
if (!connection) return;
|
|
131
|
+
this.data.databaseConnections = this.data.databaseConnections.filter((candidate) => candidate.id !== connectionId);
|
|
132
|
+
return connection;
|
|
133
|
+
}
|
|
59
134
|
};
|
|
60
135
|
//#endregion
|
|
61
136
|
export { MockApi };
|
package/dist/cli2.js
CHANGED
|
@@ -8,6 +8,7 @@ import "./shell/prompt.js";
|
|
|
8
8
|
import { createAppCommand } from "./commands/app/index.js";
|
|
9
9
|
import { createAuthCommand } from "./commands/auth/index.js";
|
|
10
10
|
import { createBranchCommand } from "./commands/branch/index.js";
|
|
11
|
+
import { createDatabaseCommand } from "./commands/database/index.js";
|
|
11
12
|
import { createGitCommand } from "./commands/git/index.js";
|
|
12
13
|
import { createProjectCommand } from "./commands/project/index.js";
|
|
13
14
|
import { getCliName, getCliVersion } from "./lib/version.js";
|
|
@@ -49,6 +50,7 @@ function createProgram(runtime) {
|
|
|
49
50
|
program.addCommand(createProjectCommand(runtime));
|
|
50
51
|
program.addCommand(createGitCommand(runtime));
|
|
51
52
|
program.addCommand(createBranchCommand(runtime));
|
|
53
|
+
program.addCommand(createDatabaseCommand(runtime));
|
|
52
54
|
program.addCommand(createAppCommand(runtime));
|
|
53
55
|
return program;
|
|
54
56
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { attachCommandDescriptor } from "../../shell/command-meta.js";
|
|
2
|
+
import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags.js";
|
|
3
|
+
import { configureRuntimeCommand } from "../../shell/runtime.js";
|
|
4
|
+
import { runCommand } from "../../shell/command-runner.js";
|
|
5
|
+
import { runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseShow } from "../../controllers/database.js";
|
|
6
|
+
import { renderDatabaseConnectionCreate, renderDatabaseConnectionCreateStdout, renderDatabaseConnectionList, renderDatabaseConnectionRemove, renderDatabaseCreate, renderDatabaseCreateStdout, renderDatabaseList, renderDatabaseRemove, renderDatabaseShow, serializeDatabaseConnectionCreate, serializeDatabaseConnectionList, serializeDatabaseConnectionRemove, serializeDatabaseCreate, serializeDatabaseList, serializeDatabaseRemove, serializeDatabaseShow } from "../../presenters/database.js";
|
|
7
|
+
import { Command, Option } from "commander";
|
|
8
|
+
//#region src/commands/database/index.ts
|
|
9
|
+
function createDatabaseCommand(runtime) {
|
|
10
|
+
const database = attachCommandDescriptor(configureRuntimeCommand(new Command("database"), runtime), "database");
|
|
11
|
+
addCompactGlobalFlags(database);
|
|
12
|
+
database.addCommand(createDatabaseListCommand(runtime));
|
|
13
|
+
database.addCommand(createDatabaseShowCommand(runtime));
|
|
14
|
+
database.addCommand(createDatabaseCreateCommand(runtime));
|
|
15
|
+
database.addCommand(createDatabaseRemoveCommand(runtime));
|
|
16
|
+
database.addCommand(createDatabaseConnectionCommand(runtime));
|
|
17
|
+
return database;
|
|
18
|
+
}
|
|
19
|
+
function addProjectAndBranchOptions(command) {
|
|
20
|
+
return command.addOption(new Option("--project <id-or-name>", "Project id or name")).addOption(new Option("--branch <git-name>", "Branch git name"));
|
|
21
|
+
}
|
|
22
|
+
function createDatabaseListCommand(runtime) {
|
|
23
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "database.list");
|
|
24
|
+
addProjectAndBranchOptions(command);
|
|
25
|
+
addGlobalFlags(command);
|
|
26
|
+
command.action(async (options) => {
|
|
27
|
+
const projectRef = options.project;
|
|
28
|
+
const branchName = options.branch;
|
|
29
|
+
await runCommand(runtime, "database.list", options, (context) => runDatabaseList(context, {
|
|
30
|
+
projectRef,
|
|
31
|
+
branchName
|
|
32
|
+
}), {
|
|
33
|
+
renderHuman: (context, descriptor, result) => renderDatabaseList(context, descriptor, result),
|
|
34
|
+
renderJson: (result) => serializeDatabaseList(result)
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return command;
|
|
38
|
+
}
|
|
39
|
+
function createDatabaseShowCommand(runtime) {
|
|
40
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("show"), runtime), "database.show");
|
|
41
|
+
command.argument("<database>", "Database id or name");
|
|
42
|
+
addProjectAndBranchOptions(command);
|
|
43
|
+
addGlobalFlags(command);
|
|
44
|
+
command.action(async (databaseRef, options) => {
|
|
45
|
+
const projectRef = options.project;
|
|
46
|
+
const branchName = options.branch;
|
|
47
|
+
await runCommand(runtime, "database.show", options, (context) => runDatabaseShow(context, databaseRef, {
|
|
48
|
+
projectRef,
|
|
49
|
+
branchName
|
|
50
|
+
}), {
|
|
51
|
+
renderHuman: (context, descriptor, result) => renderDatabaseShow(context, descriptor, result),
|
|
52
|
+
renderJson: (result) => serializeDatabaseShow(result)
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return command;
|
|
56
|
+
}
|
|
57
|
+
function createDatabaseCreateCommand(runtime) {
|
|
58
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("create"), runtime), "database.create");
|
|
59
|
+
command.argument("<name>", "Database name").addOption(new Option("--region <region>", "Prisma Postgres region id"));
|
|
60
|
+
addProjectAndBranchOptions(command);
|
|
61
|
+
addGlobalFlags(command);
|
|
62
|
+
command.action(async (name, options) => {
|
|
63
|
+
const projectRef = options.project;
|
|
64
|
+
const branchName = options.branch;
|
|
65
|
+
const region = options.region;
|
|
66
|
+
await runCommand(runtime, "database.create", options, (context) => runDatabaseCreate(context, name, {
|
|
67
|
+
projectRef,
|
|
68
|
+
branchName,
|
|
69
|
+
region
|
|
70
|
+
}), {
|
|
71
|
+
renderStdout: (context, descriptor, result) => renderDatabaseCreateStdout(context, descriptor, result),
|
|
72
|
+
renderHuman: () => renderDatabaseCreate(),
|
|
73
|
+
renderJson: (result) => serializeDatabaseCreate(result)
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
return command;
|
|
77
|
+
}
|
|
78
|
+
function createDatabaseRemoveCommand(runtime) {
|
|
79
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("remove"), runtime), "database.remove");
|
|
80
|
+
command.argument("<database>", "Database id or name").addOption(new Option("--confirm <database-id>", "Exact database id required to remove"));
|
|
81
|
+
addProjectAndBranchOptions(command);
|
|
82
|
+
addGlobalFlags(command);
|
|
83
|
+
command.action(async (databaseRef, options) => {
|
|
84
|
+
const projectRef = options.project;
|
|
85
|
+
const branchName = options.branch;
|
|
86
|
+
const confirm = options.confirm;
|
|
87
|
+
await runCommand(runtime, "database.remove", options, (context) => runDatabaseRemove(context, databaseRef, {
|
|
88
|
+
projectRef,
|
|
89
|
+
branchName,
|
|
90
|
+
confirm
|
|
91
|
+
}), {
|
|
92
|
+
renderHuman: (context, descriptor, result) => renderDatabaseRemove(context, descriptor, result),
|
|
93
|
+
renderJson: (result) => serializeDatabaseRemove(result)
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
return command;
|
|
97
|
+
}
|
|
98
|
+
function createDatabaseConnectionCommand(runtime) {
|
|
99
|
+
const connection = attachCommandDescriptor(configureRuntimeCommand(new Command("connection"), runtime), "database.connection");
|
|
100
|
+
addCompactGlobalFlags(connection);
|
|
101
|
+
connection.addCommand(createDatabaseConnectionListCommand(runtime));
|
|
102
|
+
connection.addCommand(createDatabaseConnectionCreateCommand(runtime));
|
|
103
|
+
connection.addCommand(createDatabaseConnectionRemoveCommand(runtime));
|
|
104
|
+
return connection;
|
|
105
|
+
}
|
|
106
|
+
function createDatabaseConnectionListCommand(runtime) {
|
|
107
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "database.connection.list");
|
|
108
|
+
command.argument("<database>", "Database id or name");
|
|
109
|
+
addProjectAndBranchOptions(command);
|
|
110
|
+
addGlobalFlags(command);
|
|
111
|
+
command.action(async (databaseRef, options) => {
|
|
112
|
+
const projectRef = options.project;
|
|
113
|
+
const branchName = options.branch;
|
|
114
|
+
await runCommand(runtime, "database.connection.list", options, (context) => runDatabaseConnectionList(context, databaseRef, {
|
|
115
|
+
projectRef,
|
|
116
|
+
branchName
|
|
117
|
+
}), {
|
|
118
|
+
renderHuman: (context, descriptor, result) => renderDatabaseConnectionList(context, descriptor, result),
|
|
119
|
+
renderJson: (result) => serializeDatabaseConnectionList(result)
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
return command;
|
|
123
|
+
}
|
|
124
|
+
function createDatabaseConnectionCreateCommand(runtime) {
|
|
125
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("create"), runtime), "database.connection.create");
|
|
126
|
+
command.argument("<database>", "Database id or name").addOption(new Option("--name <name>", "Connection name"));
|
|
127
|
+
addProjectAndBranchOptions(command);
|
|
128
|
+
addGlobalFlags(command);
|
|
129
|
+
command.action(async (databaseRef, options) => {
|
|
130
|
+
const projectRef = options.project;
|
|
131
|
+
const branchName = options.branch;
|
|
132
|
+
const name = options.name;
|
|
133
|
+
await runCommand(runtime, "database.connection.create", options, (context) => runDatabaseConnectionCreate(context, databaseRef, {
|
|
134
|
+
projectRef,
|
|
135
|
+
branchName,
|
|
136
|
+
name
|
|
137
|
+
}), {
|
|
138
|
+
renderStdout: (context, descriptor, result) => renderDatabaseConnectionCreateStdout(context, descriptor, result),
|
|
139
|
+
renderHuman: () => renderDatabaseConnectionCreate(),
|
|
140
|
+
renderJson: (result) => serializeDatabaseConnectionCreate(result)
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
return command;
|
|
144
|
+
}
|
|
145
|
+
function createDatabaseConnectionRemoveCommand(runtime) {
|
|
146
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("remove"), runtime), "database.connection.remove");
|
|
147
|
+
command.argument("<connection>", "Connection id").addOption(new Option("--confirm <connection-id>", "Exact connection id required to remove"));
|
|
148
|
+
addGlobalFlags(command);
|
|
149
|
+
command.action(async (connectionRef, options) => {
|
|
150
|
+
const confirm = options.confirm;
|
|
151
|
+
await runCommand(runtime, "database.connection.remove", options, (context) => runDatabaseConnectionRemove(context, connectionRef, { confirm }), {
|
|
152
|
+
renderHuman: (context, descriptor, result) => renderDatabaseConnectionRemove(context, descriptor, result),
|
|
153
|
+
renderJson: (result) => serializeDatabaseConnectionRemove(result)
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
return command;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
export { createDatabaseCommand };
|
package/dist/controllers/app.js
CHANGED
|
@@ -31,6 +31,7 @@ import { listRealWorkspaceProjects } from "./project.js";
|
|
|
31
31
|
import { access, readFile } from "node:fs/promises";
|
|
32
32
|
import path from "node:path";
|
|
33
33
|
import open from "open";
|
|
34
|
+
import { Result, matchError } from "better-result";
|
|
34
35
|
//#region src/controllers/app.ts
|
|
35
36
|
const DEPLOY_FRAMEWORKS = [
|
|
36
37
|
"nextjs",
|
|
@@ -112,9 +113,9 @@ async function runAppDeploy(context, appName, options) {
|
|
|
112
113
|
createProjectName: options?.createProjectName,
|
|
113
114
|
envProjectId
|
|
114
115
|
});
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
|
|
117
|
+
if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
|
|
118
|
+
const localPin = localPinReadResult.value;
|
|
118
119
|
const branch = await resolveDeployBranch(context, options?.branchName);
|
|
119
120
|
if (options?.httpPort) parseDeployHttpPort(options.httpPort);
|
|
120
121
|
assertSupportedEntrypointForRequestedDeployShape({
|
|
@@ -1976,6 +1977,18 @@ function localResolutionPinStaleError() {
|
|
|
1976
1977
|
]
|
|
1977
1978
|
});
|
|
1978
1979
|
}
|
|
1980
|
+
function localPinReadErrorToDeployError(error) {
|
|
1981
|
+
return matchError(error, {
|
|
1982
|
+
LocalResolutionPinInvalidJsonError: () => localResolutionPinStaleError(),
|
|
1983
|
+
LocalResolutionPinInvalidShapeError: () => localResolutionPinStaleError(),
|
|
1984
|
+
LocalResolutionPinReadAbortedError: (error) => {
|
|
1985
|
+
throw error;
|
|
1986
|
+
},
|
|
1987
|
+
UnhandledException: (error) => {
|
|
1988
|
+
throw error;
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1979
1992
|
function readDeployEnvOverride(context, name) {
|
|
1980
1993
|
const value = context.runtime.env[name]?.trim();
|
|
1981
1994
|
return value ? value : void 0;
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
3
|
+
import { resolveProjectTarget } from "../lib/project/resolution.js";
|
|
4
|
+
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
5
|
+
import { listFixtureWorkspaceProjects, listRealWorkspaceProjects } from "./project.js";
|
|
6
|
+
import { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase } from "../lib/database/provider.js";
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
//#region src/controllers/database.ts
|
|
9
|
+
function isRealMode(context) {
|
|
10
|
+
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
11
|
+
}
|
|
12
|
+
async function runDatabaseList(context, flags) {
|
|
13
|
+
const { provider, target } = await requireDatabaseContext(context, flags, "database list");
|
|
14
|
+
const databases = sortDatabases(await provider.listDatabases({
|
|
15
|
+
projectId: target.project.id,
|
|
16
|
+
branchName: flags.branchName,
|
|
17
|
+
signal: context.runtime.signal
|
|
18
|
+
}));
|
|
19
|
+
return {
|
|
20
|
+
command: "database.list",
|
|
21
|
+
result: {
|
|
22
|
+
projectId: target.project.id,
|
|
23
|
+
projectName: target.project.name,
|
|
24
|
+
branchName: flags.branchName ?? null,
|
|
25
|
+
verboseContext: target,
|
|
26
|
+
databases
|
|
27
|
+
},
|
|
28
|
+
warnings: [],
|
|
29
|
+
nextSteps: []
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function runDatabaseShow(context, databaseRef, flags) {
|
|
33
|
+
const { provider, target } = await requireDatabaseContext(context, flags, "database show");
|
|
34
|
+
const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
|
|
35
|
+
const connections = await provider.listConnections(database.id, { signal: context.runtime.signal });
|
|
36
|
+
return {
|
|
37
|
+
command: "database.show",
|
|
38
|
+
result: {
|
|
39
|
+
projectId: target.project.id,
|
|
40
|
+
projectName: target.project.name,
|
|
41
|
+
verboseContext: target,
|
|
42
|
+
database,
|
|
43
|
+
connections
|
|
44
|
+
},
|
|
45
|
+
warnings: [],
|
|
46
|
+
nextSteps: []
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async function runDatabaseCreate(context, name, flags) {
|
|
50
|
+
const databaseName = name.trim();
|
|
51
|
+
if (!databaseName) throw usageError("Database name required", "Database create needs a non-empty name.", "Pass a database name.", ["prisma-cli database create <name>"], "database");
|
|
52
|
+
const { provider, target } = await requireDatabaseContext(context, flags, "database create");
|
|
53
|
+
const created = await provider.createDatabase({
|
|
54
|
+
projectId: target.project.id,
|
|
55
|
+
name: databaseName,
|
|
56
|
+
branchName: flags.branchName,
|
|
57
|
+
region: flags.region,
|
|
58
|
+
signal: context.runtime.signal
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
command: "database.create",
|
|
62
|
+
result: {
|
|
63
|
+
projectId: target.project.id,
|
|
64
|
+
projectName: target.project.name,
|
|
65
|
+
verboseContext: target,
|
|
66
|
+
database: ensureProjectId(created.database, target.project.id),
|
|
67
|
+
connection: created.connection,
|
|
68
|
+
connectionString: created.connectionString
|
|
69
|
+
},
|
|
70
|
+
warnings: [],
|
|
71
|
+
nextSteps: []
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function runDatabaseRemove(context, databaseRef, flags) {
|
|
75
|
+
const { provider, target } = await requireDatabaseContext(context, flags, "database remove");
|
|
76
|
+
const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
|
|
77
|
+
requireExactConfirmation({
|
|
78
|
+
resourceName: "database",
|
|
79
|
+
commandName: "database remove",
|
|
80
|
+
id: database.id,
|
|
81
|
+
confirm: flags.confirm
|
|
82
|
+
});
|
|
83
|
+
await provider.removeDatabase(database.id, { signal: context.runtime.signal });
|
|
84
|
+
return {
|
|
85
|
+
command: "database.remove",
|
|
86
|
+
result: {
|
|
87
|
+
projectId: target.project.id,
|
|
88
|
+
projectName: target.project.name,
|
|
89
|
+
verboseContext: target,
|
|
90
|
+
database
|
|
91
|
+
},
|
|
92
|
+
warnings: [],
|
|
93
|
+
nextSteps: []
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function runDatabaseConnectionList(context, databaseRef, flags) {
|
|
97
|
+
const { provider, target } = await requireDatabaseContext(context, flags, "database connection list");
|
|
98
|
+
const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
|
|
99
|
+
const connections = await provider.listConnections(database.id, { signal: context.runtime.signal });
|
|
100
|
+
return {
|
|
101
|
+
command: "database.connection.list",
|
|
102
|
+
result: {
|
|
103
|
+
projectId: target.project.id,
|
|
104
|
+
projectName: target.project.name,
|
|
105
|
+
verboseContext: target,
|
|
106
|
+
database,
|
|
107
|
+
connections
|
|
108
|
+
},
|
|
109
|
+
warnings: [],
|
|
110
|
+
nextSteps: []
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function runDatabaseConnectionCreate(context, databaseRef, flags) {
|
|
114
|
+
const { provider, target } = await requireDatabaseContext(context, flags, "database connection create");
|
|
115
|
+
const database = await resolveDatabase(provider, target, databaseRef, flags.branchName, context.runtime.signal);
|
|
116
|
+
const created = await provider.createConnection({
|
|
117
|
+
databaseId: database.id,
|
|
118
|
+
name: flags.name?.trim() || defaultConnectionName(),
|
|
119
|
+
signal: context.runtime.signal
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
command: "database.connection.create",
|
|
123
|
+
result: {
|
|
124
|
+
projectId: target.project.id,
|
|
125
|
+
projectName: target.project.name,
|
|
126
|
+
verboseContext: target,
|
|
127
|
+
database,
|
|
128
|
+
connection: created.connection,
|
|
129
|
+
connectionString: created.connectionString
|
|
130
|
+
},
|
|
131
|
+
warnings: [],
|
|
132
|
+
nextSteps: []
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function runDatabaseConnectionRemove(context, connectionRef, flags) {
|
|
136
|
+
const connectionId = connectionRef.trim();
|
|
137
|
+
if (!connectionId) throw usageError("Connection id required", "Database connection removal needs a connection id.", "Pass the connection id to remove.", ["prisma-cli database connection remove <connection-id> --confirm <connection-id>"], "database");
|
|
138
|
+
requireExactConfirmation({
|
|
139
|
+
resourceName: "database connection",
|
|
140
|
+
commandName: "database connection remove",
|
|
141
|
+
id: connectionId,
|
|
142
|
+
confirm: flags.confirm
|
|
143
|
+
});
|
|
144
|
+
await (await requireDatabaseProviderOnly(context)).removeConnection(connectionId, { signal: context.runtime.signal });
|
|
145
|
+
return {
|
|
146
|
+
command: "database.connection.remove",
|
|
147
|
+
result: { connection: { id: connectionId } },
|
|
148
|
+
warnings: [],
|
|
149
|
+
nextSteps: []
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async function requireDatabaseContext(context, flags, commandName) {
|
|
153
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
154
|
+
if (!workspace) throw workspaceRequiredError();
|
|
155
|
+
if (isRealMode(context)) {
|
|
156
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
157
|
+
if (!client) throw authRequiredError();
|
|
158
|
+
const target = await resolveProjectTarget({
|
|
159
|
+
context,
|
|
160
|
+
workspace,
|
|
161
|
+
explicitProject: flags.projectRef,
|
|
162
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
163
|
+
commandName
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
provider: createManagementDatabaseProvider(client),
|
|
167
|
+
target
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const target = await resolveProjectTarget({
|
|
171
|
+
context,
|
|
172
|
+
workspace,
|
|
173
|
+
explicitProject: flags.projectRef,
|
|
174
|
+
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
175
|
+
commandName
|
|
176
|
+
});
|
|
177
|
+
return {
|
|
178
|
+
provider: createFixtureDatabaseProvider(context),
|
|
179
|
+
target
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function requireDatabaseProviderOnly(context) {
|
|
183
|
+
await requireAuthenticatedAuthState(context);
|
|
184
|
+
if (isRealMode(context)) {
|
|
185
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
186
|
+
if (!client) throw authRequiredError();
|
|
187
|
+
return createManagementDatabaseProvider(client);
|
|
188
|
+
}
|
|
189
|
+
return createFixtureDatabaseProvider(context);
|
|
190
|
+
}
|
|
191
|
+
function createFixtureDatabaseProvider(context) {
|
|
192
|
+
return {
|
|
193
|
+
async listDatabases(options) {
|
|
194
|
+
return context.api.listDatabasesForProject(options.projectId, options.branchName).map((database) => normalizeDatabase(database, database.projectId));
|
|
195
|
+
},
|
|
196
|
+
async showDatabase(databaseId) {
|
|
197
|
+
const database = context.api.getDatabase(databaseId);
|
|
198
|
+
return database ? normalizeDatabase(database, database.projectId) : null;
|
|
199
|
+
},
|
|
200
|
+
async createDatabase(options) {
|
|
201
|
+
const created = context.api.createDatabase(options);
|
|
202
|
+
return {
|
|
203
|
+
database: normalizeDatabase(created.database, created.database.projectId),
|
|
204
|
+
connection: normalizeConnection(created.connection, created.connection.databaseId),
|
|
205
|
+
connectionString: created.connectionString
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
async removeDatabase(databaseId) {
|
|
209
|
+
if (!context.api.removeDatabase(databaseId)) throw databaseNotFoundError(databaseId);
|
|
210
|
+
},
|
|
211
|
+
async listConnections(databaseId) {
|
|
212
|
+
if (!context.api.getDatabase(databaseId)) throw databaseNotFoundError(databaseId);
|
|
213
|
+
return context.api.listDatabaseConnections(databaseId).map((connection) => normalizeConnection(connection, connection.databaseId));
|
|
214
|
+
},
|
|
215
|
+
async createConnection(options) {
|
|
216
|
+
const created = context.api.createDatabaseConnection(options);
|
|
217
|
+
if (!created) throw databaseNotFoundError(options.databaseId);
|
|
218
|
+
return {
|
|
219
|
+
connection: normalizeConnection(created.connection, created.connection.databaseId),
|
|
220
|
+
connectionString: created.connectionString
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
async removeConnection(connectionId) {
|
|
224
|
+
if (!context.api.removeDatabaseConnection(connectionId)) throw connectionNotFoundError(connectionId);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async function resolveDatabase(provider, target, databaseRef, branchName, signal) {
|
|
229
|
+
const ref = databaseRef.trim();
|
|
230
|
+
if (!ref) throw usageError("Database id or name required", "This command needs a database id or name.", "Pass a database id or name.", ["prisma-cli database list"], "database");
|
|
231
|
+
const matches = (await provider.listDatabases({
|
|
232
|
+
projectId: target.project.id,
|
|
233
|
+
branchName,
|
|
234
|
+
signal
|
|
235
|
+
})).filter((database) => database.id === ref || database.name === ref);
|
|
236
|
+
if (matches.length === 0) throw databaseNotFoundError(ref, target.project.name, branchName);
|
|
237
|
+
if (matches.length > 1) throw databaseAmbiguousError(ref, matches, branchName);
|
|
238
|
+
const selected = matches[0];
|
|
239
|
+
return ensureProjectId(await provider.showDatabase(selected.id, {
|
|
240
|
+
projectId: target.project.id,
|
|
241
|
+
signal
|
|
242
|
+
}) ?? selected, target.project.id);
|
|
243
|
+
}
|
|
244
|
+
function ensureProjectId(database, projectId) {
|
|
245
|
+
return database.projectId ? database : {
|
|
246
|
+
...database,
|
|
247
|
+
projectId
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function sortDatabases(databases) {
|
|
251
|
+
return databases.slice().sort((left, right) => {
|
|
252
|
+
const branchOrder = (left.branchName ?? "").localeCompare(right.branchName ?? "");
|
|
253
|
+
if (branchOrder !== 0) return branchOrder;
|
|
254
|
+
const nameOrder = left.name.localeCompare(right.name);
|
|
255
|
+
return nameOrder !== 0 ? nameOrder : left.id.localeCompare(right.id);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function requireExactConfirmation(options) {
|
|
259
|
+
if (options.confirm === options.id) return;
|
|
260
|
+
throw new CliError({
|
|
261
|
+
code: "CONFIRMATION_REQUIRED",
|
|
262
|
+
domain: "database",
|
|
263
|
+
summary: `Confirm ${options.resourceName} removal`,
|
|
264
|
+
why: `Removing this ${options.resourceName} is destructive and requires the exact id.`,
|
|
265
|
+
fix: `Rerun with --confirm ${options.id}.`,
|
|
266
|
+
exitCode: 2,
|
|
267
|
+
nextSteps: [`prisma-cli ${options.commandName} ${options.id} --confirm ${options.id}`],
|
|
268
|
+
meta: {
|
|
269
|
+
expectedConfirm: options.id,
|
|
270
|
+
receivedConfirm: options.confirm ?? null
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function defaultConnectionName() {
|
|
275
|
+
return `cli-${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 17)}-${randomBytes(2).toString("hex")}`;
|
|
276
|
+
}
|
|
277
|
+
function databaseNotFoundError(databaseRef, projectName, branchName) {
|
|
278
|
+
return new CliError({
|
|
279
|
+
code: "DATABASE_NOT_FOUND",
|
|
280
|
+
domain: "database",
|
|
281
|
+
summary: "Database not found",
|
|
282
|
+
why: `No database matched "${databaseRef}"${projectName ? ` in project "${projectName}"${branchName ? ` on branch "${branchName}"` : ""}` : ""}.`,
|
|
283
|
+
fix: "Pass a database id or name from prisma-cli database list.",
|
|
284
|
+
exitCode: 1,
|
|
285
|
+
nextSteps: ["prisma-cli database list"]
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function databaseAmbiguousError(databaseRef, matches, branchName) {
|
|
289
|
+
return new CliError({
|
|
290
|
+
code: "DATABASE_AMBIGUOUS",
|
|
291
|
+
domain: "database",
|
|
292
|
+
summary: "Database resolution is ambiguous",
|
|
293
|
+
why: branchName ? `Multiple databases matched "${databaseRef}" on branch "${branchName}".` : `Multiple databases matched "${databaseRef}".`,
|
|
294
|
+
fix: "Pass the database id, or pass --branch <git-name> to narrow the match.",
|
|
295
|
+
exitCode: 1,
|
|
296
|
+
nextSteps: ["prisma-cli database list"],
|
|
297
|
+
meta: { matches: matches.map((database) => ({
|
|
298
|
+
id: database.id,
|
|
299
|
+
name: database.name,
|
|
300
|
+
branchName: database.branchName
|
|
301
|
+
})) }
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function connectionNotFoundError(connectionId) {
|
|
305
|
+
return new CliError({
|
|
306
|
+
code: "DATABASE_CONNECTION_NOT_FOUND",
|
|
307
|
+
domain: "database",
|
|
308
|
+
summary: "Database connection not found",
|
|
309
|
+
why: `No database connection matched "${connectionId}".`,
|
|
310
|
+
fix: "Pass a connection id from prisma-cli database connection list <database>.",
|
|
311
|
+
exitCode: 1,
|
|
312
|
+
nextSteps: ["prisma-cli database connection list <database>"]
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
316
|
+
export { runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseShow };
|
|
@@ -13,6 +13,7 @@ import { requireAuthenticatedAuthState } from "./auth.js";
|
|
|
13
13
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
14
14
|
import { createProjectUseCases } from "../use-cases/project.js";
|
|
15
15
|
import open from "open";
|
|
16
|
+
import { matchError } from "better-result";
|
|
16
17
|
//#region src/controllers/project.ts
|
|
17
18
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
18
19
|
const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
@@ -20,11 +21,24 @@ function isRealMode(context) {
|
|
|
20
21
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
21
22
|
}
|
|
22
23
|
async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
|
|
23
|
-
const
|
|
24
|
+
const pinResult = await readLocalResolutionPin(cwd, signal);
|
|
25
|
+
if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
|
|
26
|
+
const pin = pinResult.value;
|
|
24
27
|
if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
25
|
-
if (pin.kind === "invalid") return { status: "invalid" };
|
|
26
28
|
return { status: "not-linked" };
|
|
27
29
|
}
|
|
30
|
+
function localPinReadErrorToInvalidLocalBinding(error) {
|
|
31
|
+
return matchError(error, {
|
|
32
|
+
LocalResolutionPinInvalidJsonError: () => ({ status: "invalid" }),
|
|
33
|
+
LocalResolutionPinInvalidShapeError: () => ({ status: "invalid" }),
|
|
34
|
+
LocalResolutionPinReadAbortedError: (error) => {
|
|
35
|
+
throw error;
|
|
36
|
+
},
|
|
37
|
+
UnhandledException: (error) => {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
28
42
|
async function runProjectList(context) {
|
|
29
43
|
const authState = await requireAuthenticatedAuthState(context);
|
|
30
44
|
const workspace = authState.workspace;
|
|
@@ -699,4 +713,4 @@ function repoConnectionFixForStatus(status) {
|
|
|
699
713
|
return "Re-run with --trace for the underlying API response details.";
|
|
700
714
|
}
|
|
701
715
|
//#endregion
|
|
702
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
716
|
+
export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
@@ -3,6 +3,7 @@ import { renderSummaryLine } from "../../shell/ui.js";
|
|
|
3
3
|
import { canPrompt } from "../../shell/runtime.js";
|
|
4
4
|
import { confirmPrompt } from "../../shell/prompt.js";
|
|
5
5
|
import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
6
|
+
import "../project/setup.js";
|
|
6
7
|
import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } from "./branch-database.js";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
//#region src/lib/app/branch-database-deploy.ts
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { CliError } from "../../shell/errors.js";
|
|
2
|
+
//#region src/lib/database/provider.ts
|
|
3
|
+
function createManagementDatabaseProvider(client) {
|
|
4
|
+
return {
|
|
5
|
+
async listDatabases(options) {
|
|
6
|
+
const databases = [];
|
|
7
|
+
let cursor;
|
|
8
|
+
while (true) {
|
|
9
|
+
const result = await client.GET("/v1/databases", {
|
|
10
|
+
params: { query: {
|
|
11
|
+
projectId: options.projectId,
|
|
12
|
+
branchGitName: options.branchName,
|
|
13
|
+
cursor
|
|
14
|
+
} },
|
|
15
|
+
signal: options.signal
|
|
16
|
+
});
|
|
17
|
+
if (result.error || !result.data) throw databaseApiError("Failed to list databases", result.response, result.error);
|
|
18
|
+
databases.push(...result.data.data);
|
|
19
|
+
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
20
|
+
cursor = result.data.pagination.nextCursor;
|
|
21
|
+
}
|
|
22
|
+
return databases.map((database) => normalizeDatabase(database, options.projectId));
|
|
23
|
+
},
|
|
24
|
+
async showDatabase(databaseId, options) {
|
|
25
|
+
const result = await client.GET("/v1/databases/{databaseId}", {
|
|
26
|
+
params: { path: { databaseId } },
|
|
27
|
+
signal: options?.signal
|
|
28
|
+
});
|
|
29
|
+
if (result.response?.status === 404) return null;
|
|
30
|
+
if (result.error || !result.data) throw databaseApiError("Failed to show database", result.response, result.error);
|
|
31
|
+
const database = result.data.data;
|
|
32
|
+
return normalizeDatabase(database, requireDatabaseProjectId(database, options?.projectId));
|
|
33
|
+
},
|
|
34
|
+
async createDatabase(options) {
|
|
35
|
+
const result = await client.POST("/v1/databases", {
|
|
36
|
+
body: {
|
|
37
|
+
projectId: options.projectId,
|
|
38
|
+
name: options.name,
|
|
39
|
+
source: { type: "empty" },
|
|
40
|
+
...options.branchName ? { branchGitName: options.branchName } : {},
|
|
41
|
+
...options.region ? { region: options.region } : {}
|
|
42
|
+
},
|
|
43
|
+
signal: options.signal
|
|
44
|
+
});
|
|
45
|
+
if (result.error || !result.data) throw databaseApiError("Failed to create database", result.response, result.error);
|
|
46
|
+
return normalizeCreatedDatabase(result.data.data, options.projectId);
|
|
47
|
+
},
|
|
48
|
+
async removeDatabase(databaseId, options) {
|
|
49
|
+
const result = await client.DELETE("/v1/databases/{databaseId}", {
|
|
50
|
+
params: { path: { databaseId } },
|
|
51
|
+
signal: options?.signal
|
|
52
|
+
});
|
|
53
|
+
if (result.error) throw databaseApiError("Failed to remove database", result.response, result.error);
|
|
54
|
+
},
|
|
55
|
+
async listConnections(databaseId, options) {
|
|
56
|
+
const result = await client.GET("/v1/databases/{databaseId}/connections", {
|
|
57
|
+
params: { path: { databaseId } },
|
|
58
|
+
signal: options?.signal
|
|
59
|
+
});
|
|
60
|
+
if (result.error || !result.data) throw databaseApiError("Failed to list database connections", result.response, result.error);
|
|
61
|
+
return result.data.data.map((connection) => normalizeConnection(connection, databaseId));
|
|
62
|
+
},
|
|
63
|
+
async createConnection(options) {
|
|
64
|
+
const result = await client.POST("/v1/databases/{databaseId}/connections", {
|
|
65
|
+
params: { path: { databaseId: options.databaseId } },
|
|
66
|
+
body: { name: options.name },
|
|
67
|
+
signal: options.signal
|
|
68
|
+
});
|
|
69
|
+
if (result.error || !result.data) throw databaseApiError("Failed to create database connection", result.response, result.error);
|
|
70
|
+
return normalizeCreatedConnection(result.data.data, options.databaseId);
|
|
71
|
+
},
|
|
72
|
+
async removeConnection(connectionId, options) {
|
|
73
|
+
const result = await client.DELETE("/v1/connections/{id}", {
|
|
74
|
+
params: { path: { id: connectionId } },
|
|
75
|
+
signal: options?.signal
|
|
76
|
+
});
|
|
77
|
+
if (result.error) throw databaseApiError("Failed to remove database connection", result.response, result.error);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function normalizeDatabase(database, fallbackProjectId) {
|
|
82
|
+
return {
|
|
83
|
+
id: database.id,
|
|
84
|
+
name: database.name,
|
|
85
|
+
projectId: database.projectId ?? fallbackProjectId,
|
|
86
|
+
branchId: database.branchId ?? database.branch?.id ?? null,
|
|
87
|
+
branchName: database.branchGitName ?? database.branchName ?? database.branch?.gitName ?? database.branch?.name ?? null,
|
|
88
|
+
region: normalizeRegion(database),
|
|
89
|
+
status: database.status ?? null,
|
|
90
|
+
isDefault: database.isDefault ?? null,
|
|
91
|
+
createdAt: database.createdAt ?? null
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function normalizeConnection(connection, fallbackDatabaseId) {
|
|
95
|
+
return {
|
|
96
|
+
id: connection.id,
|
|
97
|
+
name: connection.name ?? connection.id,
|
|
98
|
+
databaseId: connection.databaseId ?? fallbackDatabaseId,
|
|
99
|
+
createdAt: connection.createdAt ?? null
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function normalizeCreatedDatabase(database, fallbackProjectId) {
|
|
103
|
+
const rawConnection = database.connections?.[0];
|
|
104
|
+
if (!rawConnection) throw new CliError({
|
|
105
|
+
code: "DATABASE_CONNECTION_MISSING",
|
|
106
|
+
domain: "database",
|
|
107
|
+
summary: "Created database did not return a connection string",
|
|
108
|
+
why: "The Management API created the database but did not include the one-time connection payload.",
|
|
109
|
+
fix: "Create a connection explicitly with prisma-cli database connection create <database>.",
|
|
110
|
+
exitCode: 1,
|
|
111
|
+
nextSteps: [`prisma-cli database connection create ${database.id}`]
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
database: normalizeDatabase(database, fallbackProjectId),
|
|
115
|
+
...normalizeCreatedConnection(rawConnection, database.id)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function normalizeCreatedConnection(connection, fallbackDatabaseId) {
|
|
119
|
+
const connectionString = extractConnectionString(connection);
|
|
120
|
+
if (!connectionString) throw new CliError({
|
|
121
|
+
code: "DATABASE_CONNECTION_STRING_MISSING",
|
|
122
|
+
domain: "database",
|
|
123
|
+
summary: "Created connection did not return a connection string",
|
|
124
|
+
why: "Database connection strings are one-time-view secrets, but the Management API did not include one in this create response.",
|
|
125
|
+
fix: "Create another database connection and store the returned URL immediately.",
|
|
126
|
+
exitCode: 1,
|
|
127
|
+
nextSteps: [`prisma-cli database connection create ${fallbackDatabaseId}`]
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
connection: normalizeConnection(connection, fallbackDatabaseId),
|
|
131
|
+
connectionString
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function normalizeRegion(database) {
|
|
135
|
+
if (typeof database.region === "string") return database.region;
|
|
136
|
+
return database.region?.id ?? database.regionId ?? null;
|
|
137
|
+
}
|
|
138
|
+
function requireDatabaseProjectId(database, fallbackProjectId) {
|
|
139
|
+
const projectId = database.projectId ?? fallbackProjectId;
|
|
140
|
+
if (projectId) return projectId;
|
|
141
|
+
throw new CliError({
|
|
142
|
+
code: "DATABASE_API_ERROR",
|
|
143
|
+
domain: "database",
|
|
144
|
+
summary: "Database response did not include a project id",
|
|
145
|
+
why: "The Management API returned database metadata without project context.",
|
|
146
|
+
fix: "Re-run with --trace for the underlying API response details.",
|
|
147
|
+
exitCode: 1,
|
|
148
|
+
nextSteps: []
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function extractConnectionString(connection) {
|
|
152
|
+
return connection.endpoints?.pooled?.connectionString ?? connection.connectionString ?? connection.endpoints?.direct?.connectionString ?? connection.endpoints?.accelerate?.connectionString ?? null;
|
|
153
|
+
}
|
|
154
|
+
function databaseApiError(summary, response, error) {
|
|
155
|
+
const status = response?.status ?? 0;
|
|
156
|
+
return new CliError({
|
|
157
|
+
code: error?.error?.code ?? "DATABASE_API_ERROR",
|
|
158
|
+
domain: "database",
|
|
159
|
+
summary,
|
|
160
|
+
why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
|
|
161
|
+
fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
|
|
162
|
+
exitCode: 1,
|
|
163
|
+
nextSteps: []
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
export { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase };
|
|
@@ -1,25 +1,75 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { Result, TaggedError, UnhandledException } from "better-result";
|
|
3
4
|
//#region src/lib/project/local-pin.ts
|
|
4
5
|
const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
var LocalResolutionPinInvalidJsonError = class extends TaggedError("LocalResolutionPinInvalidJsonError")() {
|
|
7
|
+
constructor(cause) {
|
|
8
|
+
super({
|
|
9
|
+
message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} contains invalid JSON.`,
|
|
10
|
+
cause,
|
|
11
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var LocalResolutionPinInvalidShapeError = class extends TaggedError("LocalResolutionPinInvalidShapeError")() {
|
|
16
|
+
constructor() {
|
|
17
|
+
super({
|
|
18
|
+
message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} must contain workspaceId and projectId string fields only.`,
|
|
19
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var LocalResolutionPinReadAbortedError = class extends TaggedError("LocalResolutionPinReadAbortedError")() {
|
|
24
|
+
constructor(cause) {
|
|
25
|
+
super({
|
|
26
|
+
message: `Reading ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
|
|
27
|
+
cause,
|
|
28
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
11
29
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
async function readLocalResolutionPin(cwd, signal) {
|
|
33
|
+
return Result.gen(async function* () {
|
|
34
|
+
yield* ensureLocalResolutionPinReadNotAborted(signal);
|
|
35
|
+
const file = yield* Result.await(readLocalResolutionPinFile(cwd, signal));
|
|
36
|
+
if (file.kind === "missing") return Result.ok({ kind: "missing" });
|
|
37
|
+
const parsed = yield* parseLocalResolutionPin(file.raw);
|
|
38
|
+
if (!isLocalResolutionPin(parsed)) return Result.err(new LocalResolutionPinInvalidShapeError());
|
|
39
|
+
return Result.ok({
|
|
15
40
|
kind: "present",
|
|
16
41
|
pin: parsed
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function ensureLocalResolutionPinReadNotAborted(signal) {
|
|
46
|
+
return Result.try({
|
|
47
|
+
try: () => signal?.throwIfAborted(),
|
|
48
|
+
catch: (cause) => new LocalResolutionPinReadAbortedError(cause)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async function readLocalResolutionPinFile(cwd, signal) {
|
|
52
|
+
const readResult = await Result.tryPromise({
|
|
53
|
+
try: () => readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
signal
|
|
56
|
+
}),
|
|
57
|
+
catch: (cause) => signal?.aborted ? new LocalResolutionPinReadAbortedError(cause) : new UnhandledException({ cause })
|
|
58
|
+
});
|
|
59
|
+
if (readResult.isErr()) {
|
|
60
|
+
if (readResult.error instanceof UnhandledException && readResult.error.cause.code === "ENOENT") return Result.ok({ kind: "missing" });
|
|
61
|
+
return Result.err(readResult.error);
|
|
22
62
|
}
|
|
63
|
+
return Result.ok({
|
|
64
|
+
kind: "present",
|
|
65
|
+
raw: readResult.value
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function parseLocalResolutionPin(raw) {
|
|
69
|
+
return Result.try({
|
|
70
|
+
try: () => JSON.parse(raw),
|
|
71
|
+
catch: (cause) => cause instanceof SyntaxError ? new LocalResolutionPinInvalidJsonError(cause) : new UnhandledException({ cause })
|
|
72
|
+
});
|
|
23
73
|
}
|
|
24
74
|
async function writeLocalResolutionPin(cwd, pin, signal) {
|
|
25
75
|
const prismaDir = path.join(cwd, ".prisma");
|
|
@@ -3,6 +3,7 @@ import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
|
3
3
|
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { matchError } from "better-result";
|
|
6
7
|
//#region src/lib/project/resolution.ts
|
|
7
8
|
async function resolveProjectTarget(options) {
|
|
8
9
|
const localPin = await readImplicitLocalPin(options, { allowEnvProjectId: true });
|
|
@@ -224,7 +225,6 @@ async function resolveBoundProjectTarget(options, projects, settings) {
|
|
|
224
225
|
}
|
|
225
226
|
const localPin = settings.localPin;
|
|
226
227
|
if (!localPin) return null;
|
|
227
|
-
if (localPin.kind === "invalid") throw localStateStaleError();
|
|
228
228
|
if (localPin.kind === "present") {
|
|
229
229
|
if (localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
|
|
230
230
|
pinnedWorkspaceId: localPin.pin.workspaceId,
|
|
@@ -247,7 +247,9 @@ async function resolveBoundProjectTarget(options, projects, settings) {
|
|
|
247
247
|
}
|
|
248
248
|
async function readImplicitLocalPin(options, settings) {
|
|
249
249
|
if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return null;
|
|
250
|
-
const
|
|
250
|
+
const localPinResult = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
|
|
251
|
+
if (localPinResult.isErr()) throw localPinReadErrorToProjectError(localPinResult.error);
|
|
252
|
+
const localPin = localPinResult.value;
|
|
251
253
|
if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
|
|
252
254
|
pinnedWorkspaceId: localPin.pin.workspaceId,
|
|
253
255
|
pinnedProjectId: localPin.pin.projectId,
|
|
@@ -255,6 +257,18 @@ async function readImplicitLocalPin(options, settings) {
|
|
|
255
257
|
});
|
|
256
258
|
return localPin;
|
|
257
259
|
}
|
|
260
|
+
function localPinReadErrorToProjectError(error) {
|
|
261
|
+
return matchError(error, {
|
|
262
|
+
LocalResolutionPinInvalidJsonError: () => localStateStaleError(),
|
|
263
|
+
LocalResolutionPinInvalidShapeError: () => localStateStaleError(),
|
|
264
|
+
LocalResolutionPinReadAbortedError: (error) => {
|
|
265
|
+
throw error;
|
|
266
|
+
},
|
|
267
|
+
UnhandledException: (error) => {
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
258
272
|
function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
|
|
259
273
|
return {
|
|
260
274
|
workspace,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { formatColumns } from "../shell/ui.js";
|
|
2
|
+
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
3
|
+
import { renderMutate, renderShow, serializeList } from "../output/patterns.js";
|
|
4
|
+
import { renderResolvedProjectContextBlock, stripVerboseContext } from "./verbose-context.js";
|
|
5
|
+
//#region src/presenters/database.ts
|
|
6
|
+
function renderDatabaseList(context, descriptor, result) {
|
|
7
|
+
const ui = context.ui;
|
|
8
|
+
const lines = [`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing databases for the resolved project.")}`, ""];
|
|
9
|
+
const rail = ui.dim("│");
|
|
10
|
+
lines.push(`${rail} ${ui.accent("project:")} ${result.projectName}`);
|
|
11
|
+
if (result.branchName) lines.push(`${rail} ${ui.accent("branch:")} ${result.branchName}`);
|
|
12
|
+
lines.push(rail);
|
|
13
|
+
if (result.databases.length === 0) {
|
|
14
|
+
lines.push(`${rail} ${ui.dim("No databases found.")}`);
|
|
15
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
16
|
+
return lines;
|
|
17
|
+
}
|
|
18
|
+
const rows = result.databases.map((database) => [
|
|
19
|
+
database.name,
|
|
20
|
+
database.branchName ?? "unscoped",
|
|
21
|
+
database.region ?? "unknown",
|
|
22
|
+
formatStatus(database),
|
|
23
|
+
database.id
|
|
24
|
+
]);
|
|
25
|
+
const widths = [
|
|
26
|
+
Math.max(4, ...rows.map((row) => row[0].length)),
|
|
27
|
+
Math.max(6, ...rows.map((row) => row[1].length)),
|
|
28
|
+
Math.max(6, ...rows.map((row) => row[2].length)),
|
|
29
|
+
Math.max(6, ...rows.map((row) => row[3].length)),
|
|
30
|
+
Math.max(2, ...rows.map((row) => row[4].length))
|
|
31
|
+
];
|
|
32
|
+
lines.push(`${rail} ${ui.accent(formatColumns([
|
|
33
|
+
"Name",
|
|
34
|
+
"Branch",
|
|
35
|
+
"Region",
|
|
36
|
+
"Status",
|
|
37
|
+
"Id"
|
|
38
|
+
], widths))}`);
|
|
39
|
+
for (const row of rows) lines.push(`${rail} ${formatColumns(row, widths)}`);
|
|
40
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
43
|
+
function serializeDatabaseList(result) {
|
|
44
|
+
return {
|
|
45
|
+
...serializeList({
|
|
46
|
+
context: {
|
|
47
|
+
project: result.projectName,
|
|
48
|
+
...result.branchName ? { branch: result.branchName } : {}
|
|
49
|
+
},
|
|
50
|
+
items: result.databases.map((database) => ({
|
|
51
|
+
noun: "database",
|
|
52
|
+
label: database.name,
|
|
53
|
+
id: database.id,
|
|
54
|
+
status: database.isDefault ? "default" : null
|
|
55
|
+
}))
|
|
56
|
+
}),
|
|
57
|
+
projectId: result.projectId,
|
|
58
|
+
branchName: result.branchName,
|
|
59
|
+
databases: result.databases
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function renderDatabaseShow(context, descriptor, result) {
|
|
63
|
+
const lines = renderShow({
|
|
64
|
+
title: "Showing database metadata.",
|
|
65
|
+
descriptor,
|
|
66
|
+
fields: [
|
|
67
|
+
{
|
|
68
|
+
key: "project",
|
|
69
|
+
value: result.projectName
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "database",
|
|
73
|
+
value: result.database.name
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "id",
|
|
77
|
+
value: result.database.id,
|
|
78
|
+
tone: "dim"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: "branch",
|
|
82
|
+
value: result.database.branchName ?? "unscoped",
|
|
83
|
+
tone: result.database.branchName ? "default" : "dim"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "region",
|
|
87
|
+
value: result.database.region ?? "unknown",
|
|
88
|
+
tone: result.database.region ? "default" : "dim"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: "status",
|
|
92
|
+
value: formatStatus(result.database)
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: "connections",
|
|
96
|
+
value: String(result.connections.length)
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}, context.ui);
|
|
100
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
101
|
+
return lines;
|
|
102
|
+
}
|
|
103
|
+
function serializeDatabaseShow(result) {
|
|
104
|
+
return stripVerboseContext(result);
|
|
105
|
+
}
|
|
106
|
+
function renderDatabaseCreateStdout(_context, _descriptor, result) {
|
|
107
|
+
return [result.connectionString];
|
|
108
|
+
}
|
|
109
|
+
function renderDatabaseCreate() {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
function serializeDatabaseCreate(result) {
|
|
113
|
+
return stripVerboseContext(result);
|
|
114
|
+
}
|
|
115
|
+
function renderDatabaseRemove(context, descriptor, result) {
|
|
116
|
+
const lines = renderMutate({
|
|
117
|
+
title: "Removing database.",
|
|
118
|
+
descriptor,
|
|
119
|
+
context: [
|
|
120
|
+
{
|
|
121
|
+
key: "project",
|
|
122
|
+
value: result.projectName
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: "database",
|
|
126
|
+
value: result.database.name
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: "id",
|
|
130
|
+
value: result.database.id,
|
|
131
|
+
tone: "dim"
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
operationDescription: "Removing database",
|
|
135
|
+
operationCount: 1,
|
|
136
|
+
details: ["Database and its connection metadata were removed."]
|
|
137
|
+
}, context.ui);
|
|
138
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
139
|
+
return lines;
|
|
140
|
+
}
|
|
141
|
+
function serializeDatabaseRemove(result) {
|
|
142
|
+
return stripVerboseContext(result);
|
|
143
|
+
}
|
|
144
|
+
function renderDatabaseConnectionList(context, descriptor, result) {
|
|
145
|
+
const ui = context.ui;
|
|
146
|
+
const lines = [`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing database connection metadata.")}`, ""];
|
|
147
|
+
const rail = ui.dim("│");
|
|
148
|
+
lines.push(`${rail} ${ui.accent("database:")} ${result.database.name}`);
|
|
149
|
+
lines.push(rail);
|
|
150
|
+
if (result.connections.length === 0) {
|
|
151
|
+
lines.push(`${rail} ${ui.dim("No database connections found.")}`);
|
|
152
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
153
|
+
return lines;
|
|
154
|
+
}
|
|
155
|
+
const rows = result.connections.map((connection) => [
|
|
156
|
+
connection.name,
|
|
157
|
+
connection.id,
|
|
158
|
+
connection.createdAt ?? "unknown"
|
|
159
|
+
]);
|
|
160
|
+
const widths = [
|
|
161
|
+
Math.max(4, ...rows.map((row) => row[0].length)),
|
|
162
|
+
Math.max(2, ...rows.map((row) => row[1].length)),
|
|
163
|
+
Math.max(7, ...rows.map((row) => row[2].length))
|
|
164
|
+
];
|
|
165
|
+
lines.push(`${rail} ${ui.accent(formatColumns([
|
|
166
|
+
"Name",
|
|
167
|
+
"Id",
|
|
168
|
+
"Created"
|
|
169
|
+
], widths))}`);
|
|
170
|
+
for (const row of rows) lines.push(`${rail} ${formatColumns(row, widths)}`);
|
|
171
|
+
lines.push(...renderResolvedProjectContextBlock(context.ui, result.verboseContext));
|
|
172
|
+
return lines;
|
|
173
|
+
}
|
|
174
|
+
function serializeDatabaseConnectionList(result) {
|
|
175
|
+
return {
|
|
176
|
+
...serializeList({
|
|
177
|
+
context: {
|
|
178
|
+
project: result.projectName,
|
|
179
|
+
database: result.database.name
|
|
180
|
+
},
|
|
181
|
+
items: result.connections.map((connection) => ({
|
|
182
|
+
noun: "connection",
|
|
183
|
+
label: connection.name,
|
|
184
|
+
id: connection.id,
|
|
185
|
+
status: null
|
|
186
|
+
}))
|
|
187
|
+
}),
|
|
188
|
+
projectId: result.projectId,
|
|
189
|
+
database: result.database,
|
|
190
|
+
connections: result.connections
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function renderDatabaseConnectionCreateStdout(_context, _descriptor, result) {
|
|
194
|
+
return [result.connectionString];
|
|
195
|
+
}
|
|
196
|
+
function renderDatabaseConnectionCreate() {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
function serializeDatabaseConnectionCreate(result) {
|
|
200
|
+
return stripVerboseContext(result);
|
|
201
|
+
}
|
|
202
|
+
function renderDatabaseConnectionRemove(context, descriptor, result) {
|
|
203
|
+
return renderMutate({
|
|
204
|
+
title: "Removing database connection.",
|
|
205
|
+
descriptor,
|
|
206
|
+
context: [{
|
|
207
|
+
key: "connection",
|
|
208
|
+
value: result.connection.id,
|
|
209
|
+
tone: "dim"
|
|
210
|
+
}],
|
|
211
|
+
operationDescription: "Removing database connection",
|
|
212
|
+
operationCount: 1,
|
|
213
|
+
details: ["The connection metadata was removed. Existing one-time secrets were not shown."]
|
|
214
|
+
}, context.ui);
|
|
215
|
+
}
|
|
216
|
+
function serializeDatabaseConnectionRemove(result) {
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
function formatStatus(database) {
|
|
220
|
+
return database.status ?? (database.isDefault ? "default" : "unknown");
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
export { renderDatabaseConnectionCreate, renderDatabaseConnectionCreateStdout, renderDatabaseConnectionList, renderDatabaseConnectionRemove, renderDatabaseCreate, renderDatabaseCreateStdout, renderDatabaseList, renderDatabaseRemove, renderDatabaseShow, serializeDatabaseConnectionCreate, serializeDatabaseConnectionList, serializeDatabaseConnectionRemove, serializeDatabaseCreate, serializeDatabaseList, serializeDatabaseRemove, serializeDatabaseShow };
|
|
@@ -72,6 +72,16 @@ const DESCRIPTORS = [
|
|
|
72
72
|
description: "View your Platform branches",
|
|
73
73
|
examples: ["prisma-cli branch list"]
|
|
74
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
|
+
]
|
|
84
|
+
},
|
|
75
85
|
{
|
|
76
86
|
id: "git",
|
|
77
87
|
path: ["prisma", "git"],
|
|
@@ -156,6 +166,97 @@ const DESCRIPTORS = [
|
|
|
156
166
|
description: "List Platform branches for the resolved project",
|
|
157
167
|
examples: ["prisma-cli branch list", "prisma-cli branch list --json"]
|
|
158
168
|
},
|
|
169
|
+
{
|
|
170
|
+
id: "database.list",
|
|
171
|
+
path: [
|
|
172
|
+
"prisma",
|
|
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",
|
|
188
|
+
"show"
|
|
189
|
+
],
|
|
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"]
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: "database.create",
|
|
195
|
+
path: [
|
|
196
|
+
"prisma",
|
|
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"
|
|
209
|
+
],
|
|
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"]
|
|
259
|
+
},
|
|
159
260
|
{
|
|
160
261
|
id: "app.build",
|
|
161
262
|
path: [
|
|
@@ -31,6 +31,8 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
|
|
|
31
31
|
});
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
const stdout = presenter.renderStdout?.(context, descriptor, success.result) ?? [];
|
|
35
|
+
if (stdout.length > 0) context.output.stdout.write(`${stdout.join("\n")}\n`);
|
|
34
36
|
if (flags.quiet) return;
|
|
35
37
|
const rendered = presenter.renderHuman(context, descriptor, success.result);
|
|
36
38
|
const diagnostics = await renderBestEffortCommandDiagnostics(context, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/cli",
|
|
3
|
-
"version": "3.0.0-dev.
|
|
3
|
+
"version": "3.0.0-dev.74.1",
|
|
4
4
|
"description": "Command-line interface for the Prisma Developer Platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@prisma/compute-sdk": "^0.22.0",
|
|
40
40
|
"@prisma/credentials-store": "^7.8.0",
|
|
41
41
|
"@prisma/management-api-sdk": "^1.37.0",
|
|
42
|
+
"better-result": "^2.9.2",
|
|
42
43
|
"c12": "4.0.0-beta.5",
|
|
43
44
|
"colorette": "^2.0.20",
|
|
44
45
|
"commander": "^14.0.3",
|