@prisma/cli 3.0.0-dev.135.1 → 3.0.0-dev.137.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/controllers/app.js +14 -47
- package/dist/controllers/database.js +9 -3
- package/dist/lib/database/provider.js +85 -21
- package/package.json +3 -3
package/dist/controllers/app.js
CHANGED
|
@@ -32,8 +32,9 @@ import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
|
32
32
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
33
33
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
34
34
|
import path from "node:path";
|
|
35
|
-
import { access
|
|
35
|
+
import { access } from "node:fs/promises";
|
|
36
36
|
import { COMPUTE_REGIONS, ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, frameworkByKey, frameworkFromAlias, isConfigBackedBuildType } from "@prisma/compute-sdk/config";
|
|
37
|
+
import { detectComputeAppFromDirectory } from "@prisma/compute-sdk/config/directory";
|
|
37
38
|
import { Result, matchError } from "better-result";
|
|
38
39
|
import open from "open";
|
|
39
40
|
//#region src/controllers/app.ts
|
|
@@ -1990,55 +1991,21 @@ async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint, signa
|
|
|
1990
1991
|
}
|
|
1991
1992
|
}
|
|
1992
1993
|
async function detectDeployFramework(cwd, signal) {
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
buildType: framework.buildType,
|
|
2002
|
-
displayName: framework.displayName,
|
|
2003
|
-
annotation
|
|
2004
|
-
};
|
|
2005
|
-
}
|
|
2006
|
-
return null;
|
|
2007
|
-
}
|
|
2008
|
-
async function detectFrameworkConfigFile(cwd, framework, signal) {
|
|
2009
|
-
for (const candidate of framework.detectConfigFiles) {
|
|
2010
|
-
const filePath = path.join(cwd, candidate);
|
|
2011
|
-
signal.throwIfAborted();
|
|
2012
|
-
try {
|
|
2013
|
-
const content = await readFile(filePath, {
|
|
2014
|
-
encoding: "utf8",
|
|
2015
|
-
signal
|
|
2016
|
-
});
|
|
2017
|
-
return {
|
|
2018
|
-
exists: true,
|
|
2019
|
-
standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),
|
|
2020
|
-
path: filePath
|
|
2021
|
-
};
|
|
2022
|
-
} catch (error) {
|
|
2023
|
-
if (signal.aborted) throw error;
|
|
2024
|
-
if (error.code !== "ENOENT") throw error;
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
1994
|
+
const detected = await detectComputeAppFromDirectory({
|
|
1995
|
+
appPath: cwd,
|
|
1996
|
+
signal
|
|
1997
|
+
});
|
|
1998
|
+
if (!detected) return null;
|
|
1999
|
+
let annotation = "detected from package.json";
|
|
2000
|
+
if (detected.configFile?.standaloneOutput) annotation = "standalone output detected";
|
|
2001
|
+
else if (detected.configFile) annotation = `detected from ${path.basename(detected.configFile.path)}`;
|
|
2027
2002
|
return {
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2003
|
+
key: detected.framework,
|
|
2004
|
+
buildType: detected.buildType,
|
|
2005
|
+
displayName: detected.frameworkName,
|
|
2006
|
+
annotation
|
|
2031
2007
|
};
|
|
2032
2008
|
}
|
|
2033
|
-
function hasPackageDependency(packageJson, dependencyName) {
|
|
2034
|
-
return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
|
|
2035
|
-
}
|
|
2036
|
-
function hasAnyPackageDependency(packageJson, dependencyNames) {
|
|
2037
|
-
return dependencyNames.some((dependencyName) => hasPackageDependency(packageJson, dependencyName));
|
|
2038
|
-
}
|
|
2039
|
-
function hasDependency(dependencies, dependencyName) {
|
|
2040
|
-
return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
|
|
2041
|
-
}
|
|
2042
2009
|
function frameworkFromUserFacingValue(value, annotation) {
|
|
2043
2010
|
const framework = frameworkFromAlias(value);
|
|
2044
2011
|
if (!framework) throw frameworkNotDetectedError(void 0, value);
|
|
@@ -356,7 +356,10 @@ async function requireDatabaseContext(context, flags, commandName) {
|
|
|
356
356
|
});
|
|
357
357
|
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
358
358
|
return {
|
|
359
|
-
provider: createManagementDatabaseProvider(client, {
|
|
359
|
+
provider: createManagementDatabaseProvider(client, {
|
|
360
|
+
formatCommand: resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd),
|
|
361
|
+
workspaceId: workspace.id
|
|
362
|
+
}),
|
|
360
363
|
target: targetResult.value
|
|
361
364
|
};
|
|
362
365
|
}
|
|
@@ -374,11 +377,14 @@ async function requireDatabaseContext(context, flags, commandName) {
|
|
|
374
377
|
};
|
|
375
378
|
}
|
|
376
379
|
async function requireDatabaseProviderOnly(context) {
|
|
377
|
-
await requireAuthenticatedAuthState(context);
|
|
380
|
+
const authState = await requireAuthenticatedAuthState(context);
|
|
378
381
|
if (isRealMode(context)) {
|
|
379
382
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
380
383
|
if (!client) throw authRequiredError();
|
|
381
|
-
return createManagementDatabaseProvider(client, {
|
|
384
|
+
return createManagementDatabaseProvider(client, {
|
|
385
|
+
formatCommand: resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd),
|
|
386
|
+
workspaceId: authState.workspace?.id
|
|
387
|
+
});
|
|
382
388
|
}
|
|
383
389
|
return createFixtureDatabaseProvider(context);
|
|
384
390
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { formatPrismaCliCommand } from "../../shell/cli-command.js";
|
|
2
2
|
import { CliError } from "../../shell/errors.js";
|
|
3
3
|
//#region src/lib/database/provider.ts
|
|
4
|
+
const SUBSCRIPTION_LOOKUP_TIMEOUT_MS = 3e3;
|
|
4
5
|
function createManagementDatabaseProvider(client, options) {
|
|
5
6
|
const formatCommand = options?.formatCommand ?? ((args) => formatPrismaCliCommand(args));
|
|
7
|
+
const toDatabaseApiError = (summary, response, error, signal) => databaseApiError({
|
|
8
|
+
client,
|
|
9
|
+
workspaceId: options?.workspaceId,
|
|
10
|
+
summary,
|
|
11
|
+
response,
|
|
12
|
+
error,
|
|
13
|
+
signal
|
|
14
|
+
});
|
|
6
15
|
return {
|
|
7
16
|
async listDatabases(options) {
|
|
8
17
|
const databases = [];
|
|
@@ -16,7 +25,7 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
16
25
|
} },
|
|
17
26
|
signal: options.signal
|
|
18
27
|
});
|
|
19
|
-
if (result.error || !result.data) throw
|
|
28
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to list databases", result.response, result.error, options.signal);
|
|
20
29
|
databases.push(...result.data.data);
|
|
21
30
|
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
22
31
|
cursor = result.data.pagination.nextCursor;
|
|
@@ -28,8 +37,8 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
28
37
|
params: { path: { databaseId } },
|
|
29
38
|
signal: options?.signal
|
|
30
39
|
});
|
|
31
|
-
if (result.response?.status === 404) return null;
|
|
32
|
-
if (result.error || !result.data) throw
|
|
40
|
+
if (result.response?.status === 404 && !isPlanLimitApiError(result.error)) return null;
|
|
41
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to show database", result.response, result.error, options?.signal);
|
|
33
42
|
const database = result.data.data;
|
|
34
43
|
return normalizeDatabase(database, requireDatabaseProjectId(database, options?.projectId));
|
|
35
44
|
},
|
|
@@ -44,7 +53,7 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
44
53
|
},
|
|
45
54
|
signal: options.signal
|
|
46
55
|
});
|
|
47
|
-
if (result.error || !result.data) throw
|
|
56
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to create database", result.response, result.error, options.signal);
|
|
48
57
|
return normalizeCreatedDatabase(result.data.data, options.projectId);
|
|
49
58
|
},
|
|
50
59
|
async removeDatabase(databaseId, options) {
|
|
@@ -52,14 +61,14 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
52
61
|
params: { path: { databaseId } },
|
|
53
62
|
signal: options?.signal
|
|
54
63
|
});
|
|
55
|
-
if (result.error) throw
|
|
64
|
+
if (result.error) throw await toDatabaseApiError("Failed to remove database", result.response, result.error, options?.signal);
|
|
56
65
|
},
|
|
57
66
|
async listConnections(databaseId, options) {
|
|
58
67
|
const result = await client.GET("/v1/databases/{databaseId}/connections", {
|
|
59
68
|
params: { path: { databaseId } },
|
|
60
69
|
signal: options?.signal
|
|
61
70
|
});
|
|
62
|
-
if (result.error || !result.data) throw
|
|
71
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to list database connections", result.response, result.error, options?.signal);
|
|
63
72
|
return result.data.data.map((connection) => normalizeConnection(connection, databaseId));
|
|
64
73
|
},
|
|
65
74
|
async createConnection(options) {
|
|
@@ -68,7 +77,7 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
68
77
|
body: { name: options.name },
|
|
69
78
|
signal: options.signal
|
|
70
79
|
});
|
|
71
|
-
if (result.error || !result.data) throw
|
|
80
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to create database connection", result.response, result.error, options.signal);
|
|
72
81
|
return normalizeCreatedConnection(result.data.data, options.databaseId);
|
|
73
82
|
},
|
|
74
83
|
async removeConnection(connectionId, options) {
|
|
@@ -76,7 +85,7 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
76
85
|
params: { path: { id: connectionId } },
|
|
77
86
|
signal: options?.signal
|
|
78
87
|
});
|
|
79
|
-
if (result.error) throw
|
|
88
|
+
if (result.error) throw await toDatabaseApiError("Failed to remove database connection", result.response, result.error, options?.signal);
|
|
80
89
|
},
|
|
81
90
|
async getUsage(databaseId, options) {
|
|
82
91
|
const result = await client.GET("/v1/databases/{databaseId}/usage", {
|
|
@@ -89,7 +98,7 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
89
98
|
},
|
|
90
99
|
signal: options?.signal
|
|
91
100
|
});
|
|
92
|
-
if (result.error || !result.data) throw
|
|
101
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to fetch database usage", result.response, result.error, options?.signal);
|
|
93
102
|
return normalizeUsage(result.data);
|
|
94
103
|
},
|
|
95
104
|
async listBackups(databaseId, options) {
|
|
@@ -100,8 +109,8 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
100
109
|
},
|
|
101
110
|
signal: options?.signal
|
|
102
111
|
});
|
|
103
|
-
if (result.response?.status === 422) throw backupsUnsupportedError(databaseId, result.error);
|
|
104
|
-
if (result.error || !result.data) throw
|
|
112
|
+
if (result.response?.status === 422 && !isPlanLimitApiError(result.error)) throw backupsUnsupportedError(databaseId, result.error);
|
|
113
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to list database backups", result.response, result.error, options?.signal);
|
|
105
114
|
return normalizeBackupList(result.data);
|
|
106
115
|
},
|
|
107
116
|
async restoreDatabase(options) {
|
|
@@ -114,9 +123,9 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
114
123
|
} },
|
|
115
124
|
signal: options.signal
|
|
116
125
|
});
|
|
117
|
-
if (result.response?.status === 409) throw restoreConflictError(options.targetDatabaseId, result.error, formatCommand);
|
|
118
|
-
if (result.response?.status === 404) throw restoreBackupNotFoundError(options, result.error, formatCommand);
|
|
119
|
-
if (result.error || !result.data) throw
|
|
126
|
+
if (result.response?.status === 409 && !isPlanLimitApiError(result.error)) throw restoreConflictError(options.targetDatabaseId, result.error, formatCommand);
|
|
127
|
+
if (result.response?.status === 404 && !isPlanLimitApiError(result.error)) throw restoreBackupNotFoundError(options, result.error, formatCommand);
|
|
128
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to restore database", result.response, result.error, options.signal);
|
|
120
129
|
return normalizeDatabase(result.data.data, options.projectId);
|
|
121
130
|
},
|
|
122
131
|
async rotateConnection(connectionId, options) {
|
|
@@ -124,7 +133,7 @@ function createManagementDatabaseProvider(client, options) {
|
|
|
124
133
|
params: { path: { id: connectionId } },
|
|
125
134
|
signal: options?.signal
|
|
126
135
|
});
|
|
127
|
-
if (result.error || !result.data) throw
|
|
136
|
+
if (result.error || !result.data) throw await toDatabaseApiError("Failed to rotate database connection", result.response, result.error, options?.signal);
|
|
128
137
|
return normalizeRotatedConnection(result.data.data);
|
|
129
138
|
}
|
|
130
139
|
};
|
|
@@ -298,17 +307,72 @@ function restoreConflictError(targetDatabaseId, error, formatCommand) {
|
|
|
298
307
|
])]
|
|
299
308
|
});
|
|
300
309
|
}
|
|
301
|
-
function databaseApiError(
|
|
302
|
-
|
|
310
|
+
async function databaseApiError(options) {
|
|
311
|
+
if (isPlanLimitApiError(options.error)) {
|
|
312
|
+
const subscription = options.workspaceId ? await readWorkspaceSubscription(options.client, options.workspaceId, options.signal) : null;
|
|
313
|
+
const workspaceLine = options.workspaceId ? `Workspace: ${options.workspaceId}` : "Workspace: unavailable";
|
|
314
|
+
const planName = subscription?.planName || null;
|
|
315
|
+
const usageBlocked = subscription?.usageBlocked ?? null;
|
|
316
|
+
const upgradeUrl = subscription?.upgradeUrl || null;
|
|
317
|
+
const recoveryLines = [...planName ? [`Current plan: ${planName}`] : [], upgradeUrl ? `Upgrade: ${upgradeUrl}` : "Upgrade: Open Prisma Console and upgrade the affected workspace plan."];
|
|
318
|
+
return new CliError({
|
|
319
|
+
code: "PLAN_LIMIT_REACHED",
|
|
320
|
+
domain: "database",
|
|
321
|
+
summary: "Workspace plan limit reached",
|
|
322
|
+
why: "Database operations are blocked because this workspace has used the operations included in its plan. This is a workspace plan limit, not a Prisma outage.",
|
|
323
|
+
fix: upgradeUrl ? `Upgrade the workspace plan at ${upgradeUrl}.` : "Open Prisma Console and upgrade the affected workspace plan.",
|
|
324
|
+
meta: {
|
|
325
|
+
workspaceId: options.workspaceId ?? null,
|
|
326
|
+
blockedFeature: null,
|
|
327
|
+
planName,
|
|
328
|
+
usageBlocked,
|
|
329
|
+
upgradeUrl
|
|
330
|
+
},
|
|
331
|
+
exitCode: 1,
|
|
332
|
+
nextSteps: [],
|
|
333
|
+
humanLines: [
|
|
334
|
+
"Workspace plan limit reached [PLAN_LIMIT_REACHED]",
|
|
335
|
+
"",
|
|
336
|
+
"Database operations are blocked because this workspace has used the operations included in its plan. This is a workspace plan limit, not a Prisma outage.",
|
|
337
|
+
"",
|
|
338
|
+
workspaceLine,
|
|
339
|
+
...recoveryLines
|
|
340
|
+
]
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const status = options.response?.status ?? 0;
|
|
303
344
|
return new CliError({
|
|
304
|
-
code: error?.error?.code ?? "DATABASE_API_ERROR",
|
|
345
|
+
code: options.error?.error?.code ?? "DATABASE_API_ERROR",
|
|
305
346
|
domain: "database",
|
|
306
|
-
summary,
|
|
307
|
-
why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
|
|
308
|
-
fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
|
|
347
|
+
summary: options.summary,
|
|
348
|
+
why: options.error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
|
|
349
|
+
fix: options.error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
|
|
309
350
|
exitCode: 1,
|
|
310
351
|
nextSteps: []
|
|
311
352
|
});
|
|
312
353
|
}
|
|
354
|
+
function isPlanLimitApiError(error) {
|
|
355
|
+
return error?.error?.code === "planLimitReached";
|
|
356
|
+
}
|
|
357
|
+
async function readWorkspaceSubscription(client, workspaceId, signal) {
|
|
358
|
+
signal?.throwIfAborted();
|
|
359
|
+
const timeoutController = new AbortController();
|
|
360
|
+
const timeout = setTimeout(() => timeoutController.abort(), SUBSCRIPTION_LOOKUP_TIMEOUT_MS);
|
|
361
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
|
|
362
|
+
try {
|
|
363
|
+
const result = await client.GET("/v1/workspaces/{id}/subscription", {
|
|
364
|
+
params: { path: { id: workspaceId } },
|
|
365
|
+
signal: requestSignal
|
|
366
|
+
});
|
|
367
|
+
signal?.throwIfAborted();
|
|
368
|
+
if (result.error) return null;
|
|
369
|
+
return result.data?.data ?? null;
|
|
370
|
+
} catch {
|
|
371
|
+
signal?.throwIfAborted();
|
|
372
|
+
return null;
|
|
373
|
+
} finally {
|
|
374
|
+
clearTimeout(timeout);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
313
377
|
//#endregion
|
|
314
378
|
export { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase };
|
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.137.1",
|
|
4
4
|
"description": "Command-line interface for the Prisma Developer Platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@clack/prompts": "^1.5.0",
|
|
47
|
-
"@prisma/compute-sdk": "0.
|
|
47
|
+
"@prisma/compute-sdk": "0.39.0",
|
|
48
48
|
"@prisma/credentials-store": "^7.8.0",
|
|
49
|
-
"@prisma/management-api-sdk": "^1.
|
|
49
|
+
"@prisma/management-api-sdk": "^1.55.0",
|
|
50
50
|
"better-result": "^2.9.2",
|
|
51
51
|
"colorette": "^2.0.20",
|
|
52
52
|
"commander": "^14.0.3",
|