@prisma/cli 3.0.0-beta.4 → 3.0.0-beta.6
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 +1 -0
- package/dist/adapters/local-state.js +1 -1
- package/dist/cli2.js +2 -3
- package/dist/commands/app/index.js +21 -11
- package/dist/commands/env.js +8 -4
- package/dist/controllers/app-env-api.js +54 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +71 -82
- package/dist/controllers/app.js +55 -4
- package/dist/controllers/branch.js +5 -0
- package/dist/lib/app/branch-database-deploy.js +323 -0
- package/dist/lib/app/branch-database.js +316 -0
- package/dist/lib/app/env-config.js +1 -1
- package/dist/lib/app/env-file.js +82 -0
- package/dist/lib/app/preview-branch-database.js +102 -0
- package/dist/lib/app/preview-provider.js +19 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/resolution.js +49 -4
- package/dist/presenters/app-env.js +137 -13
- package/dist/presenters/app.js +165 -20
- package/dist/presenters/branch.js +10 -3
- package/dist/presenters/project.js +41 -17
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/command-meta.js +6 -0
- package/dist/shell/command-runner.js +17 -1
- package/dist/shell/diagnostics-output.js +63 -0
- package/dist/shell/output.js +10 -1
- package/dist/shell/runtime.js +1 -1
- package/dist/shell/ui.js +20 -1
- package/package.json +4 -3
package/dist/controllers/app.js
CHANGED
|
@@ -7,7 +7,7 @@ import { canPrompt } from "../shell/runtime.js";
|
|
|
7
7
|
import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
|
|
8
8
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
9
9
|
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
10
|
-
import { parseEnvAssignments } from "../lib/app/env-vars.js";
|
|
10
|
+
import { envVarNames, parseEnvAssignments } from "../lib/app/env-vars.js";
|
|
11
11
|
import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
|
|
12
12
|
import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
|
|
13
13
|
import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
|
|
@@ -19,6 +19,7 @@ import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js
|
|
|
19
19
|
import { readLocalGitBranch } from "../lib/git/local-branch.js";
|
|
20
20
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
21
21
|
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
22
|
+
import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
|
|
22
23
|
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
|
|
23
24
|
import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
24
25
|
import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
|
|
@@ -170,6 +171,10 @@ async function runAppDeploy(context, appName, options) {
|
|
|
170
171
|
assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
|
|
171
172
|
const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint, context.runtime.signal);
|
|
172
173
|
const portMapping = parseDeployPortMapping(String(runtime.port));
|
|
174
|
+
const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
|
|
175
|
+
db: options?.db,
|
|
176
|
+
inlineEnvVars: envVars
|
|
177
|
+
});
|
|
173
178
|
const progressState = createPreviewDeployProgressState();
|
|
174
179
|
const deployStartedAt = Date.now();
|
|
175
180
|
const deployResult = await provider.deployApp({
|
|
@@ -200,17 +205,30 @@ async function runAppDeploy(context, appName, options) {
|
|
|
200
205
|
result: {
|
|
201
206
|
workspace: target.workspace,
|
|
202
207
|
project: target.project,
|
|
203
|
-
branch: target.branch,
|
|
208
|
+
branch: toResultBranch(target.branch),
|
|
204
209
|
resolution: target.resolution,
|
|
210
|
+
branchDatabase: branchDatabaseSetup.result,
|
|
205
211
|
app: {
|
|
206
212
|
id: deployResult.app.id,
|
|
207
213
|
name: deployResult.app.name
|
|
208
214
|
},
|
|
209
215
|
deployment: deployResult.deployment,
|
|
216
|
+
deploySettings: {
|
|
217
|
+
framework: {
|
|
218
|
+
key: framework.key,
|
|
219
|
+
buildType,
|
|
220
|
+
name: framework.displayName,
|
|
221
|
+
source: framework.annotation
|
|
222
|
+
},
|
|
223
|
+
entrypoint: entrypoint ?? null,
|
|
224
|
+
httpPort: runtime.port,
|
|
225
|
+
region: selectedApp.region ?? null,
|
|
226
|
+
envVars: envVarNames(envVars)
|
|
227
|
+
},
|
|
210
228
|
durationMs: deployDurationMs,
|
|
211
229
|
localPin: localPinResult
|
|
212
230
|
},
|
|
213
|
-
warnings:
|
|
231
|
+
warnings: branchDatabaseSetup.warnings,
|
|
214
232
|
nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
|
|
215
233
|
};
|
|
216
234
|
}
|
|
@@ -222,6 +240,7 @@ async function runAppListDeploys(context, appName, projectRef) {
|
|
|
222
240
|
command: "app.list-deploys",
|
|
223
241
|
result: {
|
|
224
242
|
projectId,
|
|
243
|
+
verboseContext: toAppVerboseContext(target),
|
|
225
244
|
app: null,
|
|
226
245
|
deployments: []
|
|
227
246
|
},
|
|
@@ -241,6 +260,7 @@ async function runAppListDeploys(context, appName, projectRef) {
|
|
|
241
260
|
command: "app.list-deploys",
|
|
242
261
|
result: {
|
|
243
262
|
projectId,
|
|
263
|
+
verboseContext: toAppVerboseContext(target),
|
|
244
264
|
app: {
|
|
245
265
|
id: deploymentsResult.app.id,
|
|
246
266
|
name: deploymentsResult.app.name
|
|
@@ -259,6 +279,7 @@ async function runAppShow(context, appName, projectRef) {
|
|
|
259
279
|
command: "app.show",
|
|
260
280
|
result: {
|
|
261
281
|
projectId,
|
|
282
|
+
verboseContext: toAppVerboseContext(target),
|
|
262
283
|
app: null,
|
|
263
284
|
liveDeployment: null,
|
|
264
285
|
liveUrl: null,
|
|
@@ -281,6 +302,7 @@ async function runAppShow(context, appName, projectRef) {
|
|
|
281
302
|
command: "app.show",
|
|
282
303
|
result: {
|
|
283
304
|
projectId,
|
|
305
|
+
verboseContext: toAppVerboseContext(target),
|
|
284
306
|
app: {
|
|
285
307
|
id: deploymentsResult.app.id,
|
|
286
308
|
name: deploymentsResult.app.name
|
|
@@ -354,6 +376,7 @@ async function runAppOpen(context, appName, projectRef) {
|
|
|
354
376
|
command: "app.open",
|
|
355
377
|
result: {
|
|
356
378
|
projectId,
|
|
379
|
+
verboseContext: toAppVerboseContext(target),
|
|
357
380
|
app: {
|
|
358
381
|
id: deploymentsResult.app.id,
|
|
359
382
|
name: deploymentsResult.app.name
|
|
@@ -644,6 +667,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
|
|
|
644
667
|
command: "app.promote",
|
|
645
668
|
result: {
|
|
646
669
|
projectId,
|
|
670
|
+
verboseContext: toAppVerboseContext(target),
|
|
647
671
|
app: {
|
|
648
672
|
id: deploymentsResult.app.id,
|
|
649
673
|
name: deploymentsResult.app.name
|
|
@@ -686,6 +710,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
|
|
|
686
710
|
command: "app.rollback",
|
|
687
711
|
result: {
|
|
688
712
|
projectId,
|
|
713
|
+
verboseContext: toAppVerboseContext(target),
|
|
689
714
|
app: {
|
|
690
715
|
id: deploymentsResult.app.id,
|
|
691
716
|
name: deploymentsResult.app.name
|
|
@@ -714,6 +739,7 @@ async function runAppRemove(context, appName, projectRef) {
|
|
|
714
739
|
command: "app.remove",
|
|
715
740
|
result: {
|
|
716
741
|
projectId,
|
|
742
|
+
verboseContext: toAppVerboseContext(target),
|
|
717
743
|
app: {
|
|
718
744
|
id: removedApp.id,
|
|
719
745
|
name: removedApp.name
|
|
@@ -757,7 +783,7 @@ async function resolveAppDomainTarget(context, options, commandName = "app domai
|
|
|
757
783
|
resultTarget: {
|
|
758
784
|
workspace: target.workspace,
|
|
759
785
|
project: target.project,
|
|
760
|
-
branch: target.branch,
|
|
786
|
+
branch: toResultBranch(target.branch),
|
|
761
787
|
app: {
|
|
762
788
|
id: selectedApp.id,
|
|
763
789
|
name: selectedApp.name
|
|
@@ -1320,6 +1346,7 @@ async function resolveProjectContext(context, client, explicitProject, options)
|
|
|
1320
1346
|
return {
|
|
1321
1347
|
...resolved,
|
|
1322
1348
|
branch: {
|
|
1349
|
+
id: null,
|
|
1323
1350
|
name: branch.name,
|
|
1324
1351
|
kind: toBranchKind(branch.name)
|
|
1325
1352
|
}
|
|
@@ -1446,6 +1473,7 @@ async function withRemoteDeployBranch(provider, target, branch, signal) {
|
|
|
1446
1473
|
return {
|
|
1447
1474
|
...target,
|
|
1448
1475
|
branch: {
|
|
1476
|
+
id: remoteBranch.id,
|
|
1449
1477
|
name: remoteBranch.name,
|
|
1450
1478
|
kind: remoteBranch.role
|
|
1451
1479
|
}
|
|
@@ -1454,6 +1482,29 @@ async function withRemoteDeployBranch(provider, target, branch, signal) {
|
|
|
1454
1482
|
function toBranchKind(name) {
|
|
1455
1483
|
return name === "production" || name === "main" ? "production" : "preview";
|
|
1456
1484
|
}
|
|
1485
|
+
function toResultBranch(branch) {
|
|
1486
|
+
return {
|
|
1487
|
+
id: branch.id,
|
|
1488
|
+
name: branch.name,
|
|
1489
|
+
kind: branch.kind
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function toAppVerboseContext(target) {
|
|
1493
|
+
return {
|
|
1494
|
+
workspace: target.workspace,
|
|
1495
|
+
project: target.project,
|
|
1496
|
+
branch: target.branch,
|
|
1497
|
+
resolution: target.resolution
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
function toBranchDatabaseDeployBranch(branch) {
|
|
1501
|
+
if (!branch.id) throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);
|
|
1502
|
+
return {
|
|
1503
|
+
id: branch.id,
|
|
1504
|
+
name: branch.name,
|
|
1505
|
+
kind: branch.kind
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1457
1508
|
function assertExclusiveDeployProjectInputs(options) {
|
|
1458
1509
|
const provided = [
|
|
1459
1510
|
options.projectRef ? "--project" : null,
|
|
@@ -41,6 +41,11 @@ async function listRealBranches(context) {
|
|
|
41
41
|
return {
|
|
42
42
|
projectId: target.project.id,
|
|
43
43
|
projectName: target.project.name,
|
|
44
|
+
verboseContext: {
|
|
45
|
+
workspace,
|
|
46
|
+
project: target.project,
|
|
47
|
+
resolution: target.resolution
|
|
48
|
+
},
|
|
44
49
|
branches: sortBranches(branches.map(toBranchSummary))
|
|
45
50
|
};
|
|
46
51
|
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { CliError, usageError } from "../../shell/errors.js";
|
|
2
|
+
import { renderSummaryLine } from "../../shell/ui.js";
|
|
3
|
+
import { canPrompt } from "../../shell/runtime.js";
|
|
4
|
+
import { confirmPrompt } from "../../shell/prompt.js";
|
|
5
|
+
import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
6
|
+
import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } from "./branch-database.js";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
//#region src/lib/app/branch-database-deploy.ts
|
|
9
|
+
async function maybeSetupBranchDatabase(context, provider, projectId, branch, options) {
|
|
10
|
+
if (options.db === false) return emptyBranchDatabaseSetupOutcome();
|
|
11
|
+
if (hasInlineDatabaseEnvVars(options.inlineEnvVars)) {
|
|
12
|
+
if (options.db === true) throw usageError("Branch database setup cannot be combined with inline database env vars", "The deploy command received --db and an inline DATABASE_URL or DIRECT_URL value.", "Remove the inline --env database value to let --db create a branch override, or remove --db to deploy with the provided value.", ["prisma-cli app deploy --db", "prisma-cli app deploy --env DATABASE_URL=postgresql://example"], "app");
|
|
13
|
+
return emptyBranchDatabaseSetupOutcome();
|
|
14
|
+
}
|
|
15
|
+
if (branch.kind === "production") {
|
|
16
|
+
if (options.db === true) throw usageError("Branch database setup is only available for preview branches", "Production database wiring is a durable environment decision and is not created implicitly by app deploy.", "Use project env commands to manage production DATABASE_URL, or deploy a preview branch with --db.", ["prisma-cli project env add DATABASE_URL=<value> --role production", "prisma-cli app deploy --branch feature/db --db"], "app");
|
|
17
|
+
return emptyBranchDatabaseSetupOutcome();
|
|
18
|
+
}
|
|
19
|
+
const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
|
|
20
|
+
const envState = await inspectBranchDatabaseEnv(provider, projectId, branch.id, context.runtime.signal);
|
|
21
|
+
const branchEnvVars = [envState.branchDatabaseUrl, envState.branchDirectUrl].filter((variable) => Boolean(variable)).map((variable) => variable.key).sort();
|
|
22
|
+
if (envState.branchDatabaseUrl) {
|
|
23
|
+
const warning = options.db === true ? `Branch "${branch.name}" already has DATABASE_URL. Leaving branch database env vars unchanged.` : null;
|
|
24
|
+
if (warning) emitBranchDatabaseWarning(context, warning);
|
|
25
|
+
return {
|
|
26
|
+
result: options.db === true ? {
|
|
27
|
+
status: "skipped",
|
|
28
|
+
reason: "branch-env-exists",
|
|
29
|
+
envVars: branchEnvVars,
|
|
30
|
+
schema: null
|
|
31
|
+
} : void 0,
|
|
32
|
+
warnings: warning ? [warning] : []
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (localSignal.unsupportedSchema) {
|
|
36
|
+
if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch.name, context);
|
|
37
|
+
return emptyBranchDatabaseSetupOutcome();
|
|
38
|
+
}
|
|
39
|
+
const hasSignal = hasBranchDatabaseSignal(localSignal) || Boolean(envState.previewDatabaseUrl);
|
|
40
|
+
if (options.db !== true) {
|
|
41
|
+
if (!hasSignal) return emptyBranchDatabaseSetupOutcome();
|
|
42
|
+
if (!canPrompt(context) || context.flags.yes) {
|
|
43
|
+
const warning = "This app appears to use DATABASE_URL. Run prisma-cli app deploy --db to create an isolated database for this preview branch.";
|
|
44
|
+
emitBranchDatabaseWarning(context, warning);
|
|
45
|
+
return {
|
|
46
|
+
result: void 0,
|
|
47
|
+
warnings: [warning]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
maybeRenderBranchDatabaseSignal(context, branch.name, localSignal, envState);
|
|
51
|
+
if (!await confirmPrompt({
|
|
52
|
+
input: context.runtime.stdin,
|
|
53
|
+
output: context.output.stderr,
|
|
54
|
+
message: `Create an isolated database for branch "${branch.name}"?`,
|
|
55
|
+
initialValue: false
|
|
56
|
+
})) return emptyBranchDatabaseSetupOutcome();
|
|
57
|
+
}
|
|
58
|
+
return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
|
|
59
|
+
}
|
|
60
|
+
async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
|
|
61
|
+
emitBranchDatabaseProgress(context, "pending", "Creating branch database");
|
|
62
|
+
const database = await provider.createBranchDatabase({
|
|
63
|
+
projectId,
|
|
64
|
+
branchId: branch.id,
|
|
65
|
+
branchName: branch.name,
|
|
66
|
+
signal: context.runtime.signal
|
|
67
|
+
}).catch((error) => {
|
|
68
|
+
throw branchDatabaseSetupFailedError("Failed to create branch database", error, branch.name);
|
|
69
|
+
});
|
|
70
|
+
emitBranchDatabaseProgress(context, "success", "Created branch database");
|
|
71
|
+
try {
|
|
72
|
+
let schemaSetup = null;
|
|
73
|
+
const warnings = [];
|
|
74
|
+
let skippedSchemaWarning = null;
|
|
75
|
+
if (signal.schema) {
|
|
76
|
+
emitBranchDatabaseProgress(context, "pending", `Applying database schema with ${formatSchemaSetupCommand(signal.schema.command)}`);
|
|
77
|
+
schemaSetup = await runBranchDatabaseSchemaSetup({
|
|
78
|
+
context,
|
|
79
|
+
schema: signal.schema,
|
|
80
|
+
databaseUrl: database.databaseUrl,
|
|
81
|
+
directUrl: database.directUrl
|
|
82
|
+
}).catch((error) => {
|
|
83
|
+
throw schemaSetupFailedError(error, signal.schema, branch.name, context.runtime.cwd);
|
|
84
|
+
});
|
|
85
|
+
emitBranchDatabaseProgress(context, "success", "Applied database schema");
|
|
86
|
+
} else skippedSchemaWarning = "No supported Prisma schema source was found. Branch database env vars were created, but schema setup was skipped.";
|
|
87
|
+
const envVars = await upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState);
|
|
88
|
+
emitBranchDatabaseProgress(context, "success", `Added branch env override${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
|
|
89
|
+
if (skippedSchemaWarning) {
|
|
90
|
+
emitBranchDatabaseWarning(context, skippedSchemaWarning);
|
|
91
|
+
warnings.push(skippedSchemaWarning);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
result: {
|
|
95
|
+
status: "created",
|
|
96
|
+
database: {
|
|
97
|
+
id: database.id,
|
|
98
|
+
name: database.name
|
|
99
|
+
},
|
|
100
|
+
envVars,
|
|
101
|
+
schema: schemaSetup ? {
|
|
102
|
+
command: schemaSetup.command,
|
|
103
|
+
source: schemaSetup.source,
|
|
104
|
+
path: schemaSetup.schemaPath
|
|
105
|
+
} : null
|
|
106
|
+
},
|
|
107
|
+
warnings
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch.name, error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState) {
|
|
114
|
+
const written = [];
|
|
115
|
+
await upsertBranchDatabaseEnvVar(context, provider, {
|
|
116
|
+
projectId,
|
|
117
|
+
branchId: branch.id,
|
|
118
|
+
className: "preview",
|
|
119
|
+
key: "DATABASE_URL",
|
|
120
|
+
value: database.databaseUrl,
|
|
121
|
+
existing: envState.branchDatabaseUrl,
|
|
122
|
+
branchName: branch.name
|
|
123
|
+
});
|
|
124
|
+
written.push("DATABASE_URL");
|
|
125
|
+
if (database.directUrl) {
|
|
126
|
+
await upsertBranchDatabaseEnvVar(context, provider, {
|
|
127
|
+
projectId,
|
|
128
|
+
branchId: branch.id,
|
|
129
|
+
className: "preview",
|
|
130
|
+
key: "DIRECT_URL",
|
|
131
|
+
value: database.directUrl,
|
|
132
|
+
existing: envState.branchDirectUrl,
|
|
133
|
+
branchName: branch.name
|
|
134
|
+
});
|
|
135
|
+
written.push("DIRECT_URL");
|
|
136
|
+
} else if (envState.branchDirectUrl) await provider.deleteEnvironmentVariable({
|
|
137
|
+
envVarId: envState.branchDirectUrl.id,
|
|
138
|
+
signal: context.runtime.signal
|
|
139
|
+
}).catch((error) => {
|
|
140
|
+
throw branchDatabaseSetupFailedError("Failed to remove stale DIRECT_URL", error, branch.name);
|
|
141
|
+
});
|
|
142
|
+
return written;
|
|
143
|
+
}
|
|
144
|
+
async function upsertBranchDatabaseEnvVar(context, provider, options) {
|
|
145
|
+
if (options.existing) {
|
|
146
|
+
await provider.updateEnvironmentVariable({
|
|
147
|
+
envVarId: options.existing.id,
|
|
148
|
+
value: options.value,
|
|
149
|
+
signal: context.runtime.signal
|
|
150
|
+
}).catch((error) => {
|
|
151
|
+
throw branchDatabaseSetupFailedError(`Failed to update ${options.key}`, error, options.branchName);
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
await provider.createEnvironmentVariable({
|
|
156
|
+
projectId: options.projectId,
|
|
157
|
+
branchId: options.branchId,
|
|
158
|
+
className: options.className,
|
|
159
|
+
key: options.key,
|
|
160
|
+
value: options.value,
|
|
161
|
+
signal: context.runtime.signal
|
|
162
|
+
}).catch((error) => {
|
|
163
|
+
throw branchDatabaseSetupFailedError(`Failed to write ${options.key}`, error, options.branchName);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function inspectBranchDatabaseEnv(provider, projectId, branchId, signal) {
|
|
167
|
+
const [databaseUrlRows, directUrlRows] = await Promise.all([provider.listEnvironmentVariables({
|
|
168
|
+
projectId,
|
|
169
|
+
className: "preview",
|
|
170
|
+
key: "DATABASE_URL",
|
|
171
|
+
signal
|
|
172
|
+
}), provider.listEnvironmentVariables({
|
|
173
|
+
projectId,
|
|
174
|
+
className: "preview",
|
|
175
|
+
key: "DIRECT_URL",
|
|
176
|
+
signal
|
|
177
|
+
})]);
|
|
178
|
+
return {
|
|
179
|
+
branchDatabaseUrl: findEnvVar(databaseUrlRows, { branchId }),
|
|
180
|
+
branchDirectUrl: findEnvVar(directUrlRows, { branchId }),
|
|
181
|
+
previewDatabaseUrl: findEnvVar(databaseUrlRows, { branchId: null })
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function findEnvVar(rows, options) {
|
|
185
|
+
return rows.find((row) => row.branchId === options.branchId) ?? null;
|
|
186
|
+
}
|
|
187
|
+
function hasInlineDatabaseEnvVars(envVars) {
|
|
188
|
+
return Boolean(envVars && ("DATABASE_URL" in envVars || "DIRECT_URL" in envVars));
|
|
189
|
+
}
|
|
190
|
+
function maybeRenderBranchDatabaseSignal(context, branchName, signal, envState) {
|
|
191
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
192
|
+
const rows = [
|
|
193
|
+
signal.schema ? ` Schema ${path.relative(context.runtime.cwd, signal.schema.path) || defaultSchemaSourcePath(signal.schema)}` : null,
|
|
194
|
+
signal.databaseUrlReferences.length > 0 ? ` Code ${signal.databaseUrlReferences.slice(0, 3).join(", ")}` : null,
|
|
195
|
+
envState.previewDatabaseUrl ? " Env preview DATABASE_URL is inherited by this branch" : null
|
|
196
|
+
].filter((row) => Boolean(row));
|
|
197
|
+
context.output.stderr.write(`Database signal found for branch "${branchName}"\n${rows.join("\n")}\n\n`);
|
|
198
|
+
}
|
|
199
|
+
function emitBranchDatabaseProgress(context, status, message) {
|
|
200
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
201
|
+
const line = status === "pending" ? `${context.ui.warning("◇")} ${message}...` : renderSummaryLine(context.ui, "success", message);
|
|
202
|
+
context.output.stderr.write(`${line}\n`);
|
|
203
|
+
}
|
|
204
|
+
function emitBranchDatabaseWarning(context, warning) {
|
|
205
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
206
|
+
context.output.stderr.write(`${renderSummaryLine(context.ui, "warning", warning)}\n`);
|
|
207
|
+
}
|
|
208
|
+
function emptyBranchDatabaseSetupOutcome() {
|
|
209
|
+
return {
|
|
210
|
+
result: void 0,
|
|
211
|
+
warnings: []
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function formatSchemaSetupCommand(command) {
|
|
215
|
+
switch (command) {
|
|
216
|
+
case "migrate-deploy": return "prisma migrate deploy";
|
|
217
|
+
case "db-push": return "prisma db push";
|
|
218
|
+
case "prisma-next-db-init": return "prisma-next db init";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function branchDatabaseSetupFailedError(summary, error, branchName) {
|
|
222
|
+
return new CliError({
|
|
223
|
+
code: "BRANCH_DATABASE_SETUP_FAILED",
|
|
224
|
+
domain: "app",
|
|
225
|
+
summary,
|
|
226
|
+
why: error instanceof Error ? error.message : String(error),
|
|
227
|
+
fix: "Retry the command, or create the branch database and env vars manually with project env commands.",
|
|
228
|
+
debug: formatDebugDetails(error),
|
|
229
|
+
meta: { branch: branchName },
|
|
230
|
+
exitCode: 1,
|
|
231
|
+
nextSteps: [`prisma-cli app deploy --branch ${formatCommandArgument(branchName)} --db`, `prisma-cli project env list --branch ${formatCommandArgument(branchName)}`]
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
async function cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branchName, error) {
|
|
235
|
+
const setupError = error instanceof CliError ? error : branchDatabaseSetupFailedError("Branch database setup failed", error, branchName);
|
|
236
|
+
emitBranchDatabaseProgress(context, "pending", "Removing branch database after setup failed");
|
|
237
|
+
try {
|
|
238
|
+
await provider.deleteBranchDatabase({
|
|
239
|
+
databaseId: database.id,
|
|
240
|
+
signal: context.runtime.signal
|
|
241
|
+
});
|
|
242
|
+
emitBranchDatabaseProgress(context, "success", "Removed branch database after setup failed");
|
|
243
|
+
} catch (cleanupError) {
|
|
244
|
+
return branchDatabaseCleanupFailedError(setupError, cleanupError, database, branchName);
|
|
245
|
+
}
|
|
246
|
+
return setupError;
|
|
247
|
+
}
|
|
248
|
+
function branchDatabaseCleanupFailedError(setupError, cleanupError, database, branchName) {
|
|
249
|
+
const cleanupWhy = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
250
|
+
const setupWhy = setupError.why ?? "Branch database setup failed.";
|
|
251
|
+
return new CliError({
|
|
252
|
+
code: setupError.code,
|
|
253
|
+
domain: setupError.domain,
|
|
254
|
+
summary: setupError.summary,
|
|
255
|
+
why: `${setupWhy} Prisma could not delete the created database "${database.name}" (${database.id}): ${cleanupWhy}`,
|
|
256
|
+
fix: "Delete the created branch database from Console or contact Prisma support, then rerun deploy with --db.",
|
|
257
|
+
debug: formatCombinedDebugDetails(setupError, cleanupError),
|
|
258
|
+
meta: {
|
|
259
|
+
...setupError.meta,
|
|
260
|
+
branch: branchName,
|
|
261
|
+
databaseId: database.id,
|
|
262
|
+
databaseName: database.name,
|
|
263
|
+
cleanupFailed: true
|
|
264
|
+
},
|
|
265
|
+
exitCode: setupError.exitCode,
|
|
266
|
+
nextSteps: []
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
function schemaSetupFailedError(error, schema, branchName, cwd) {
|
|
270
|
+
return new CliError({
|
|
271
|
+
code: "SCHEMA_SETUP_FAILED",
|
|
272
|
+
domain: "app",
|
|
273
|
+
summary: "Database schema setup failed",
|
|
274
|
+
why: error instanceof Error ? error.message : String(error),
|
|
275
|
+
fix: "Fix the Prisma schema or migrations, then rerun deploy with --db.",
|
|
276
|
+
debug: formatDebugDetails(error),
|
|
277
|
+
meta: {
|
|
278
|
+
branch: branchName,
|
|
279
|
+
schemaPath: schema.path,
|
|
280
|
+
source: schema.kind,
|
|
281
|
+
command: schema.command
|
|
282
|
+
},
|
|
283
|
+
exitCode: 1,
|
|
284
|
+
nextSteps: [...formatSchemaSetupNextSteps(schema, cwd), `prisma-cli app deploy --branch ${formatCommandArgument(branchName)} --db`]
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
function unsupportedBranchDatabaseSchemaError(schema, branchName, context) {
|
|
288
|
+
return usageError("Branch database setup is not available for this Prisma schema", `${path.relative(context.runtime.cwd, schema.path) || defaultUnsupportedSchemaSourcePath(schema)} targets ${formatUnsupportedSchemaTarget(schema.target)}, but --db creates Prisma Postgres databases.`, "Use project env commands to provide a database URL for this branch, or switch the Prisma schema source to PostgreSQL before using --db.", [`prisma-cli project env add DATABASE_URL=<value> --branch ${formatCommandArgument(branchName)}`, `prisma-cli app deploy --branch ${formatCommandArgument(branchName)}`], "app");
|
|
289
|
+
}
|
|
290
|
+
function formatSchemaSetupNextSteps(schema, cwd) {
|
|
291
|
+
const sourcePath = path.relative(cwd, schema.path) || defaultSchemaSourcePath(schema);
|
|
292
|
+
switch (schema.command) {
|
|
293
|
+
case "migrate-deploy": return [`npx --no-install prisma migrate deploy --schema ${formatCommandArgument(sourcePath)}`];
|
|
294
|
+
case "db-push": return [`npx --no-install prisma db push --schema ${formatCommandArgument(sourcePath)}`];
|
|
295
|
+
case "prisma-next-db-init": return [`npx --no-install prisma-next contract emit --config ${formatCommandArgument(sourcePath)}`, `npx --no-install prisma-next db init --config ${formatCommandArgument(sourcePath)} --db <DATABASE_URL>`];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function defaultSchemaSourcePath(schema) {
|
|
299
|
+
return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
|
|
300
|
+
}
|
|
301
|
+
function defaultUnsupportedSchemaSourcePath(schema) {
|
|
302
|
+
return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
|
|
303
|
+
}
|
|
304
|
+
function formatUnsupportedSchemaTarget(target) {
|
|
305
|
+
switch (target) {
|
|
306
|
+
case "cockroachdb": return "CockroachDB";
|
|
307
|
+
case "mongodb": return "MongoDB";
|
|
308
|
+
case "mysql": return "MySQL";
|
|
309
|
+
case "sqlite": return "SQLite";
|
|
310
|
+
case "sqlserver": return "SQL Server";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function formatDebugDetails(error) {
|
|
314
|
+
if (error instanceof Error) return error.stack ?? error.message;
|
|
315
|
+
return typeof error === "string" ? error : null;
|
|
316
|
+
}
|
|
317
|
+
function formatCombinedDebugDetails(setupError, cleanupError) {
|
|
318
|
+
const setupDebug = setupError.debug ?? setupError.stack ?? setupError.message;
|
|
319
|
+
const cleanupDebug = formatDebugDetails(cleanupError);
|
|
320
|
+
return [setupDebug ? `Setup error:\n${setupDebug}` : null, cleanupDebug ? `Cleanup error:\n${cleanupDebug}` : null].filter((line) => Boolean(line)).join("\n\n") || null;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
export { maybeSetupBranchDatabase };
|