@prisma/cli 3.0.0-beta.8 → 3.0.0-dev.101.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/README.md +0 -13
- package/dist/adapters/mock-api.js +75 -0
- package/dist/adapters/token-storage.js +311 -33
- package/dist/cli.js +7 -7
- package/dist/cli2.js +5 -3
- package/dist/commands/app/index.js +65 -52
- package/dist/commands/auth/index.js +55 -2
- package/dist/commands/database/index.js +159 -0
- package/dist/controllers/app-env.js +10 -7
- package/dist/controllers/app.js +550 -208
- package/dist/controllers/auth.js +211 -2
- package/dist/controllers/branch.js +5 -6
- package/dist/controllers/database.js +318 -0
- package/dist/controllers/project.js +49 -20
- package/dist/lib/app/app-interaction.js +5 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +32 -28
- package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
- package/dist/lib/app/branch-database-deploy.js +13 -60
- package/dist/lib/app/branch-database.js +1 -102
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +82 -0
- package/dist/lib/app/bun-project.js +2 -3
- package/dist/lib/app/compute-config.js +147 -0
- package/dist/lib/app/deploy-plan.js +58 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +5 -5
- package/dist/lib/app/local-dev.js +3 -60
- package/dist/lib/app/production-deploy-gate.js +3 -3
- package/dist/lib/app/read-branch.js +30 -0
- package/dist/lib/auth/auth-ops.js +10 -4
- package/dist/lib/auth/guard.js +4 -1
- package/dist/lib/auth/login.js +32 -25
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/diagnostics.js +1 -1
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +15 -3
- package/dist/lib/project/local-pin.js +170 -40
- package/dist/lib/project/resolution.js +166 -61
- package/dist/lib/project/setup.js +65 -19
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/app.js +44 -39
- package/dist/presenters/auth.js +98 -1
- package/dist/presenters/branch.js +1 -1
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +3 -9
- package/dist/shell/command-meta.js +153 -2
- package/dist/shell/command-runner.js +39 -21
- package/dist/shell/diagnostics-output.js +2 -8
- package/dist/shell/errors.js +50 -1
- package/dist/shell/help.js +30 -20
- package/dist/shell/runtime.js +8 -4
- package/dist/shell/ui.js +19 -2
- package/dist/use-cases/auth.js +68 -1
- package/package.json +18 -3
- package/dist/lib/app/preview-build-settings.js +0 -385
- package/dist/lib/app/preview-build.js +0 -475
- package/dist/lib/app/preview-interaction.js +0 -5
package/dist/controllers/auth.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CLIENT_ID, SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
|
|
2
|
+
import { FileTokenStorage, WorkspaceSelectionError } from "../adapters/token-storage.js";
|
|
3
|
+
import { authRequiredError, usageError, workspaceAmbiguousError, workspaceNotAuthenticatedError, workspaceSwitchUnavailableError } from "../shell/errors.js";
|
|
2
4
|
import { canPrompt } from "../shell/runtime.js";
|
|
3
5
|
import { performLogin, performLogout, readAuthState } from "../lib/auth/auth-ops.js";
|
|
4
6
|
import { createAuthUseCases } from "../use-cases/auth.js";
|
|
5
7
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
6
8
|
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
9
|
+
import { createManagementApiSdk } from "@prisma/management-api-sdk";
|
|
7
10
|
//#region src/controllers/auth.ts
|
|
8
11
|
function isRealMode(context) {
|
|
9
12
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
@@ -30,6 +33,35 @@ async function runAuthWhoAmI(context) {
|
|
|
30
33
|
else result = await createAuthUseCases(createCliUseCaseGateways(context)).whoami();
|
|
31
34
|
return createAuthSuccess("auth.whoami", result, result.authenticated ? [] : ["prisma-cli auth login"]);
|
|
32
35
|
}
|
|
36
|
+
async function runAuthWorkspaceList(context) {
|
|
37
|
+
const result = isRealMode(context) ? await listRealAuthWorkspaces(context) : await createAuthUseCases(createCliUseCaseGateways(context)).listWorkspaces();
|
|
38
|
+
return {
|
|
39
|
+
command: "auth.workspace.list",
|
|
40
|
+
result,
|
|
41
|
+
warnings: [],
|
|
42
|
+
nextSteps: result.workspaces.length === 0 ? ["prisma-cli auth login"] : []
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function runAuthWorkspaceUse(context, workspaceRef) {
|
|
46
|
+
const trimmedWorkspaceRef = workspaceRef?.trim();
|
|
47
|
+
const selectedWorkspaceRef = trimmedWorkspaceRef ? trimmedWorkspaceRef : await selectWorkspaceSession(context);
|
|
48
|
+
return {
|
|
49
|
+
command: "auth.workspace.use",
|
|
50
|
+
result: isRealMode(context) ? await useRealAuthWorkspace(context, selectedWorkspaceRef) : await createAuthUseCases(createCliUseCaseGateways(context)).useWorkspace(selectedWorkspaceRef),
|
|
51
|
+
warnings: [],
|
|
52
|
+
nextSteps: ["prisma-cli auth whoami", "prisma-cli project list"]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function runAuthWorkspaceLogout(context, workspaceRef) {
|
|
56
|
+
if (!workspaceRef?.trim()) throw usageError("Workspace required", "auth workspace logout needs a workspace id or cached workspace name.", "Pass a workspace from prisma-cli auth workspace list.", ["prisma-cli auth workspace list"], "auth");
|
|
57
|
+
const result = isRealMode(context) ? await logoutRealAuthWorkspace(context, workspaceRef) : await createAuthUseCases(createCliUseCaseGateways(context)).logoutWorkspace(workspaceRef);
|
|
58
|
+
return {
|
|
59
|
+
command: "auth.workspace.logout",
|
|
60
|
+
result,
|
|
61
|
+
warnings: [],
|
|
62
|
+
nextSteps: result.activeWorkspace ? ["prisma-cli auth workspace list"] : ["prisma-cli auth workspace list", "prisma-cli auth workspace use <id>"]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
33
65
|
async function requireAuthenticatedAuthState(context) {
|
|
34
66
|
if (isRealMode(context)) {
|
|
35
67
|
const current = await readAuthState(context.runtime.env, context.runtime.signal);
|
|
@@ -44,6 +76,183 @@ async function requireAuthenticatedAuthState(context) {
|
|
|
44
76
|
if (!canPrompt(context)) throw authRequiredError();
|
|
45
77
|
return loginWithSelectionFlow(context, useCases, {});
|
|
46
78
|
}
|
|
79
|
+
async function listRealAuthWorkspaces(context) {
|
|
80
|
+
const rawServiceToken = context.runtime.env[SERVICE_TOKEN_ENV_VAR];
|
|
81
|
+
const storage = new FileTokenStorage(context.runtime.env, context.runtime.signal);
|
|
82
|
+
const localWorkspaces = await hydrateLocalAuthWorkspaces(context, storage, await storage.listWorkspaces());
|
|
83
|
+
if (rawServiceToken !== void 0) {
|
|
84
|
+
const authState = await readAuthState(context.runtime.env, context.runtime.signal);
|
|
85
|
+
return {
|
|
86
|
+
authSource: authState.authenticated ? "service_token" : "none",
|
|
87
|
+
activeWorkspace: authState.workspace,
|
|
88
|
+
workspaces: [...authState.workspace ? [{
|
|
89
|
+
...authState.workspace,
|
|
90
|
+
credentialWorkspaceId: null,
|
|
91
|
+
active: true,
|
|
92
|
+
source: "service_token",
|
|
93
|
+
switchable: false,
|
|
94
|
+
lastSeenAt: null
|
|
95
|
+
}] : [], ...localWorkspaces.map((workspace) => ({
|
|
96
|
+
...toAuthWorkspace(workspace),
|
|
97
|
+
credentialWorkspaceId: workspace.credentialWorkspaceId,
|
|
98
|
+
active: false,
|
|
99
|
+
source: "oauth",
|
|
100
|
+
switchable: false,
|
|
101
|
+
lastSeenAt: workspace.lastSeenAt
|
|
102
|
+
}))]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const active = localWorkspaces.find((workspace) => workspace.active) ?? null;
|
|
106
|
+
return {
|
|
107
|
+
authSource: localWorkspaces.length > 0 ? "oauth" : "none",
|
|
108
|
+
activeWorkspace: active ? toAuthWorkspace(active) : null,
|
|
109
|
+
workspaces: localWorkspaces.map((workspace) => ({
|
|
110
|
+
...toAuthWorkspace(workspace),
|
|
111
|
+
credentialWorkspaceId: workspace.credentialWorkspaceId,
|
|
112
|
+
active: workspace.active,
|
|
113
|
+
source: "oauth",
|
|
114
|
+
switchable: true,
|
|
115
|
+
lastSeenAt: workspace.lastSeenAt
|
|
116
|
+
}))
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function useRealAuthWorkspace(context, workspaceRef) {
|
|
120
|
+
if (context.runtime.env["PRISMA_SERVICE_TOKEN"] !== void 0) throw workspaceSwitchUnavailableError();
|
|
121
|
+
const storage = new FileTokenStorage(context.runtime.env, context.runtime.signal);
|
|
122
|
+
await hydrateLocalAuthWorkspaces(context, storage, await storage.listWorkspaces());
|
|
123
|
+
try {
|
|
124
|
+
const result = await storage.useWorkspace(workspaceRef);
|
|
125
|
+
return {
|
|
126
|
+
previousWorkspace: result.previous ? toAuthWorkspace(result.previous) : null,
|
|
127
|
+
workspace: toAuthWorkspace(result.selected)
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (error instanceof WorkspaceSelectionError) {
|
|
131
|
+
if (error.reason === "ambiguous") throw workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
|
|
132
|
+
id: match.id,
|
|
133
|
+
name: match.name,
|
|
134
|
+
credentialWorkspaceId: match.credentialWorkspaceId
|
|
135
|
+
})));
|
|
136
|
+
throw workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function logoutRealAuthWorkspace(context, workspaceRef) {
|
|
142
|
+
const storage = new FileTokenStorage(context.runtime.env, context.runtime.signal);
|
|
143
|
+
await hydrateLocalAuthWorkspaces(context, storage, await storage.listWorkspaces());
|
|
144
|
+
try {
|
|
145
|
+
const result = await storage.logoutWorkspace(workspaceRef);
|
|
146
|
+
return {
|
|
147
|
+
workspace: toAuthWorkspace(result.workspace),
|
|
148
|
+
wasActive: result.wasActive,
|
|
149
|
+
activeWorkspace: result.activeWorkspace ? toAuthWorkspace(result.activeWorkspace) : null
|
|
150
|
+
};
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error instanceof WorkspaceSelectionError) {
|
|
153
|
+
if (error.reason === "ambiguous") throw workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
|
|
154
|
+
id: match.id,
|
|
155
|
+
name: match.name,
|
|
156
|
+
credentialWorkspaceId: match.credentialWorkspaceId
|
|
157
|
+
})));
|
|
158
|
+
throw workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function selectWorkspaceSession(context) {
|
|
164
|
+
const realMode = isRealMode(context);
|
|
165
|
+
if (realMode && context.runtime.env["PRISMA_SERVICE_TOKEN"] !== void 0) throw workspaceSwitchUnavailableError();
|
|
166
|
+
const workspaces = (realMode ? await listRealAuthWorkspaces(context) : await createAuthUseCases(createCliUseCaseGateways(context)).listWorkspaces()).workspaces.filter((workspace) => workspace.switchable);
|
|
167
|
+
if (workspaces.length === 0) throw usageError("No authenticated workspaces", "There are no local OAuth workspace sessions to select.", "Run prisma-cli auth login and authorize a workspace.", ["prisma-cli auth login"], "auth");
|
|
168
|
+
if (workspaces.length === 1) return workspaces[0].id;
|
|
169
|
+
if (!canPrompt(context)) throw usageError("Interactive workspace selection unavailable", "auth workspace use needs an interactive terminal when no workspace is provided and more than one workspace is available.", "Run prisma-cli auth workspace use <id-or-name> with a workspace from prisma-cli auth workspace list.", ["prisma-cli auth workspace list"], "auth");
|
|
170
|
+
return (await createSelectPromptPort(context).select({
|
|
171
|
+
message: "Select a workspace",
|
|
172
|
+
choices: workspaces.map((workspace) => ({
|
|
173
|
+
label: `${workspace.name} (${workspace.id})${workspace.active ? " active" : ""}`,
|
|
174
|
+
value: workspace
|
|
175
|
+
}))
|
|
176
|
+
})).id;
|
|
177
|
+
}
|
|
178
|
+
async function hydrateLocalAuthWorkspaces(context, storage, workspaces) {
|
|
179
|
+
const candidates = workspaces.filter(needsWorkspaceMetadataHydration);
|
|
180
|
+
if (candidates.length === 0) return workspaces;
|
|
181
|
+
const tokensByCredentialWorkspaceId = new Map((await storage.listWorkspaceTokens()).map((tokens) => [tokens.workspaceId, tokens]));
|
|
182
|
+
let nextWorkspaces = workspaces;
|
|
183
|
+
for (const workspace of candidates) {
|
|
184
|
+
const tokens = tokensByCredentialWorkspaceId.get(workspace.credentialWorkspaceId);
|
|
185
|
+
if (!tokens) continue;
|
|
186
|
+
const resolved = await resolveOAuthWorkspaceMetadata(context, tokens);
|
|
187
|
+
if (!resolved) continue;
|
|
188
|
+
await rememberResolvedWorkspaceMetadata(context, storage, tokens, resolved);
|
|
189
|
+
nextWorkspaces = nextWorkspaces.map((candidate) => candidate.credentialWorkspaceId === workspace.credentialWorkspaceId ? {
|
|
190
|
+
...candidate,
|
|
191
|
+
id: resolved.id,
|
|
192
|
+
name: resolved.name,
|
|
193
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
194
|
+
} : candidate);
|
|
195
|
+
}
|
|
196
|
+
return nextWorkspaces;
|
|
197
|
+
}
|
|
198
|
+
async function rememberResolvedWorkspaceMetadata(context, storage, tokens, resolved) {
|
|
199
|
+
try {
|
|
200
|
+
await storage.rememberWorkspace(tokens.workspaceId, resolved);
|
|
201
|
+
} catch {
|
|
202
|
+
context.runtime.signal?.throwIfAborted();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function needsWorkspaceMetadataHydration(workspace) {
|
|
206
|
+
return workspace.id === workspace.credentialWorkspaceId || workspace.name === "Unknown workspace" || workspace.name === workspace.credentialWorkspaceId;
|
|
207
|
+
}
|
|
208
|
+
async function resolveOAuthWorkspaceMetadata(context, tokens) {
|
|
209
|
+
const sdk = createManagementApiSdk({
|
|
210
|
+
clientId: CLIENT_ID,
|
|
211
|
+
redirectUri: "http://localhost:0/auth/callback",
|
|
212
|
+
tokenStorage: createSingleWorkspaceTokenStorage(new FileTokenStorage(context.runtime.env, context.runtime.signal, { activateOnSetTokens: false }), tokens),
|
|
213
|
+
apiBaseUrl: getApiBaseUrl(context.runtime.env)
|
|
214
|
+
});
|
|
215
|
+
try {
|
|
216
|
+
const { data } = await sdk.client.GET("/v1/workspaces/{id}", {
|
|
217
|
+
params: { path: { id: tokens.workspaceId } },
|
|
218
|
+
signal: context.runtime.signal
|
|
219
|
+
});
|
|
220
|
+
const id = stringOrNull(data?.data?.id) ?? tokens.workspaceId;
|
|
221
|
+
const name = stringOrNull(data?.data?.name) ?? id;
|
|
222
|
+
if (id === tokens.workspaceId && name === tokens.workspaceId) return null;
|
|
223
|
+
return {
|
|
224
|
+
id,
|
|
225
|
+
name
|
|
226
|
+
};
|
|
227
|
+
} catch {
|
|
228
|
+
context.runtime.signal?.throwIfAborted();
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function createSingleWorkspaceTokenStorage(storage, initialTokens) {
|
|
233
|
+
let currentTokens = initialTokens;
|
|
234
|
+
return {
|
|
235
|
+
getTokens: async () => currentTokens,
|
|
236
|
+
setTokens: async (tokens) => {
|
|
237
|
+
currentTokens = tokens;
|
|
238
|
+
await storage.setTokens(tokens);
|
|
239
|
+
},
|
|
240
|
+
clearTokens: async () => {
|
|
241
|
+
const tokens = currentTokens;
|
|
242
|
+
currentTokens = null;
|
|
243
|
+
if (tokens) await storage.clearTokensIfCurrent(tokens);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function stringOrNull(value) {
|
|
248
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
249
|
+
}
|
|
250
|
+
function toAuthWorkspace(workspace) {
|
|
251
|
+
return {
|
|
252
|
+
id: workspace.id,
|
|
253
|
+
name: workspace.name
|
|
254
|
+
};
|
|
255
|
+
}
|
|
47
256
|
async function loginWithSelectionFlow(context, useCases, options) {
|
|
48
257
|
const selection = await resolveLoginSelection(useCases, canPrompt(context) ? createSelectPromptPort(context) : null, options);
|
|
49
258
|
return useCases.login(selection);
|
|
@@ -104,4 +313,4 @@ function createAuthSuccess(command, result, nextSteps) {
|
|
|
104
313
|
};
|
|
105
314
|
}
|
|
106
315
|
//#endregion
|
|
107
|
-
export { requireAuthenticatedAuthState, runAuthLogin, runAuthLogout, runAuthWhoAmI };
|
|
316
|
+
export { requireAuthenticatedAuthState, runAuthLogin, runAuthLogout, runAuthWhoAmI, runAuthWorkspaceList, runAuthWorkspaceLogout, runAuthWorkspaceUse };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { CliError, authRequiredError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
|
+
import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
2
3
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
3
|
-
import { resolveProjectTarget } from "../lib/project/resolution.js";
|
|
4
4
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
5
|
-
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
6
5
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
7
6
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
8
7
|
import { createBranchUseCases } from "../use-cases/branch.js";
|
|
@@ -30,13 +29,13 @@ async function listRealBranches(context) {
|
|
|
30
29
|
if (!client) throw authRequiredError(["prisma-cli auth login"]);
|
|
31
30
|
const workspace = authState.workspace;
|
|
32
31
|
if (!workspace) throw workspaceRequiredError();
|
|
33
|
-
const
|
|
32
|
+
const targetResult = await resolveProjectTarget({
|
|
34
33
|
context,
|
|
35
34
|
workspace,
|
|
36
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal)
|
|
37
|
-
prompt: createSelectPromptPort(context),
|
|
38
|
-
remember: true
|
|
35
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal)
|
|
39
36
|
});
|
|
37
|
+
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
38
|
+
const target = targetResult.value;
|
|
40
39
|
const branches = await listBranches(client, target.project.id, context.runtime.signal);
|
|
41
40
|
return {
|
|
42
41
|
projectId: target.project.id,
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
|
+
import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
3
|
+
import { requireComputeAuth } from "../lib/auth/guard.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 targetResult = await resolveProjectTarget({
|
|
159
|
+
context,
|
|
160
|
+
workspace,
|
|
161
|
+
explicitProject: flags.projectRef,
|
|
162
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
163
|
+
commandName
|
|
164
|
+
});
|
|
165
|
+
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
166
|
+
return {
|
|
167
|
+
provider: createManagementDatabaseProvider(client),
|
|
168
|
+
target: targetResult.value
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const targetResult = await resolveProjectTarget({
|
|
172
|
+
context,
|
|
173
|
+
workspace,
|
|
174
|
+
explicitProject: flags.projectRef,
|
|
175
|
+
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
176
|
+
commandName
|
|
177
|
+
});
|
|
178
|
+
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
179
|
+
return {
|
|
180
|
+
provider: createFixtureDatabaseProvider(context),
|
|
181
|
+
target: targetResult.value
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function requireDatabaseProviderOnly(context) {
|
|
185
|
+
await requireAuthenticatedAuthState(context);
|
|
186
|
+
if (isRealMode(context)) {
|
|
187
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
188
|
+
if (!client) throw authRequiredError();
|
|
189
|
+
return createManagementDatabaseProvider(client);
|
|
190
|
+
}
|
|
191
|
+
return createFixtureDatabaseProvider(context);
|
|
192
|
+
}
|
|
193
|
+
function createFixtureDatabaseProvider(context) {
|
|
194
|
+
return {
|
|
195
|
+
async listDatabases(options) {
|
|
196
|
+
return context.api.listDatabasesForProject(options.projectId, options.branchName).map((database) => normalizeDatabase(database, database.projectId));
|
|
197
|
+
},
|
|
198
|
+
async showDatabase(databaseId) {
|
|
199
|
+
const database = context.api.getDatabase(databaseId);
|
|
200
|
+
return database ? normalizeDatabase(database, database.projectId) : null;
|
|
201
|
+
},
|
|
202
|
+
async createDatabase(options) {
|
|
203
|
+
const created = context.api.createDatabase(options);
|
|
204
|
+
return {
|
|
205
|
+
database: normalizeDatabase(created.database, created.database.projectId),
|
|
206
|
+
connection: normalizeConnection(created.connection, created.connection.databaseId),
|
|
207
|
+
connectionString: created.connectionString
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
async removeDatabase(databaseId) {
|
|
211
|
+
if (!context.api.removeDatabase(databaseId)) throw databaseNotFoundError(databaseId);
|
|
212
|
+
},
|
|
213
|
+
async listConnections(databaseId) {
|
|
214
|
+
if (!context.api.getDatabase(databaseId)) throw databaseNotFoundError(databaseId);
|
|
215
|
+
return context.api.listDatabaseConnections(databaseId).map((connection) => normalizeConnection(connection, connection.databaseId));
|
|
216
|
+
},
|
|
217
|
+
async createConnection(options) {
|
|
218
|
+
const created = context.api.createDatabaseConnection(options);
|
|
219
|
+
if (!created) throw databaseNotFoundError(options.databaseId);
|
|
220
|
+
return {
|
|
221
|
+
connection: normalizeConnection(created.connection, created.connection.databaseId),
|
|
222
|
+
connectionString: created.connectionString
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
async removeConnection(connectionId) {
|
|
226
|
+
if (!context.api.removeDatabaseConnection(connectionId)) throw connectionNotFoundError(connectionId);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async function resolveDatabase(provider, target, databaseRef, branchName, signal) {
|
|
231
|
+
const ref = databaseRef.trim();
|
|
232
|
+
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");
|
|
233
|
+
const matches = (await provider.listDatabases({
|
|
234
|
+
projectId: target.project.id,
|
|
235
|
+
branchName,
|
|
236
|
+
signal
|
|
237
|
+
})).filter((database) => database.id === ref || database.name === ref);
|
|
238
|
+
if (matches.length === 0) throw databaseNotFoundError(ref, target.project.name, branchName);
|
|
239
|
+
if (matches.length > 1) throw databaseAmbiguousError(ref, matches, branchName);
|
|
240
|
+
const selected = matches[0];
|
|
241
|
+
return ensureProjectId(await provider.showDatabase(selected.id, {
|
|
242
|
+
projectId: target.project.id,
|
|
243
|
+
signal
|
|
244
|
+
}) ?? selected, target.project.id);
|
|
245
|
+
}
|
|
246
|
+
function ensureProjectId(database, projectId) {
|
|
247
|
+
return database.projectId ? database : {
|
|
248
|
+
...database,
|
|
249
|
+
projectId
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function sortDatabases(databases) {
|
|
253
|
+
return databases.slice().sort((left, right) => {
|
|
254
|
+
const branchOrder = (left.branchName ?? "").localeCompare(right.branchName ?? "");
|
|
255
|
+
if (branchOrder !== 0) return branchOrder;
|
|
256
|
+
const nameOrder = left.name.localeCompare(right.name);
|
|
257
|
+
return nameOrder !== 0 ? nameOrder : left.id.localeCompare(right.id);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
function requireExactConfirmation(options) {
|
|
261
|
+
if (options.confirm === options.id) return;
|
|
262
|
+
throw new CliError({
|
|
263
|
+
code: "CONFIRMATION_REQUIRED",
|
|
264
|
+
domain: "database",
|
|
265
|
+
summary: `Confirm ${options.resourceName} removal`,
|
|
266
|
+
why: `Removing this ${options.resourceName} is destructive and requires the exact id.`,
|
|
267
|
+
fix: `Rerun with --confirm ${options.id}.`,
|
|
268
|
+
exitCode: 2,
|
|
269
|
+
nextSteps: [`prisma-cli ${options.commandName} ${options.id} --confirm ${options.id}`],
|
|
270
|
+
meta: {
|
|
271
|
+
expectedConfirm: options.id,
|
|
272
|
+
receivedConfirm: options.confirm ?? null
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function defaultConnectionName() {
|
|
277
|
+
return `cli-${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 17)}-${randomBytes(2).toString("hex")}`;
|
|
278
|
+
}
|
|
279
|
+
function databaseNotFoundError(databaseRef, projectName, branchName) {
|
|
280
|
+
return new CliError({
|
|
281
|
+
code: "DATABASE_NOT_FOUND",
|
|
282
|
+
domain: "database",
|
|
283
|
+
summary: "Database not found",
|
|
284
|
+
why: `No database matched "${databaseRef}"${projectName ? ` in project "${projectName}"${branchName ? ` on branch "${branchName}"` : ""}` : ""}.`,
|
|
285
|
+
fix: "Pass a database id or name from prisma-cli database list.",
|
|
286
|
+
exitCode: 1,
|
|
287
|
+
nextSteps: ["prisma-cli database list"]
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function databaseAmbiguousError(databaseRef, matches, branchName) {
|
|
291
|
+
return new CliError({
|
|
292
|
+
code: "DATABASE_AMBIGUOUS",
|
|
293
|
+
domain: "database",
|
|
294
|
+
summary: "Database resolution is ambiguous",
|
|
295
|
+
why: branchName ? `Multiple databases matched "${databaseRef}" on branch "${branchName}".` : `Multiple databases matched "${databaseRef}".`,
|
|
296
|
+
fix: "Pass the database id, or pass --branch <git-name> to narrow the match.",
|
|
297
|
+
exitCode: 1,
|
|
298
|
+
nextSteps: ["prisma-cli database list"],
|
|
299
|
+
meta: { matches: matches.map((database) => ({
|
|
300
|
+
id: database.id,
|
|
301
|
+
name: database.name,
|
|
302
|
+
branchName: database.branchName
|
|
303
|
+
})) }
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function connectionNotFoundError(connectionId) {
|
|
307
|
+
return new CliError({
|
|
308
|
+
code: "DATABASE_CONNECTION_NOT_FOUND",
|
|
309
|
+
domain: "database",
|
|
310
|
+
summary: "Database connection not found",
|
|
311
|
+
why: `No database connection matched "${connectionId}".`,
|
|
312
|
+
fix: "Pass a connection id from prisma-cli database connection list <database>.",
|
|
313
|
+
exitCode: 1,
|
|
314
|
+
nextSteps: ["prisma-cli database connection list <database>"]
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
export { runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseShow };
|