@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
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
|
+
import { createAppProvider } from "../lib/app/app-provider.js";
|
|
2
3
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
3
4
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
|
-
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
|
-
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
6
5
|
import { readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
7
|
+
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
|
+
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
9
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
9
10
|
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
10
|
-
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
11
11
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
12
12
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
13
13
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
14
14
|
import { createProjectUseCases } from "../use-cases/project.js";
|
|
15
|
+
import { matchError } from "better-result";
|
|
15
16
|
import open from "open";
|
|
16
17
|
//#region src/controllers/project.ts
|
|
17
18
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
@@ -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;
|
|
@@ -90,7 +104,7 @@ async function runProjectCreate(context, projectName) {
|
|
|
90
104
|
if (!isRealMode(context)) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
|
|
91
105
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
92
106
|
if (!client) throw authRequiredError();
|
|
93
|
-
const provider =
|
|
107
|
+
const provider = createAppProvider(client);
|
|
94
108
|
const name = projectName.trim();
|
|
95
109
|
const created = await provider.createProject({
|
|
96
110
|
name,
|
|
@@ -102,12 +116,14 @@ async function runProjectCreate(context, projectName) {
|
|
|
102
116
|
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
103
117
|
});
|
|
104
118
|
});
|
|
119
|
+
const bindResult = await bindProjectToDirectory(context, workspace, {
|
|
120
|
+
id: created.id,
|
|
121
|
+
name: created.name
|
|
122
|
+
}, "created");
|
|
123
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
105
124
|
return {
|
|
106
125
|
command: "project.create",
|
|
107
|
-
result:
|
|
108
|
-
id: created.id,
|
|
109
|
-
name: created.name
|
|
110
|
-
}, "created"),
|
|
126
|
+
result: bindResult.value,
|
|
111
127
|
warnings: [],
|
|
112
128
|
nextSteps: ["prisma-cli app deploy"]
|
|
113
129
|
};
|
|
@@ -120,11 +136,11 @@ async function runProjectLink(context, projectRef) {
|
|
|
120
136
|
if (isRealMode(context)) {
|
|
121
137
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
122
138
|
if (!client) throw authRequiredError();
|
|
123
|
-
provider =
|
|
139
|
+
provider = createAppProvider(client);
|
|
124
140
|
projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
|
|
125
141
|
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
126
142
|
let result;
|
|
127
|
-
if (projectRef?.trim()) result = await
|
|
143
|
+
if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
128
144
|
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
129
145
|
else throw await projectLinkTargetRequiredError(context, projects);
|
|
130
146
|
return {
|
|
@@ -148,7 +164,12 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
148
164
|
nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
|
|
149
165
|
}
|
|
150
166
|
});
|
|
151
|
-
return
|
|
167
|
+
return requireProjectDirectoryBinding(context, workspace, setup.project, setup.action);
|
|
168
|
+
}
|
|
169
|
+
async function requireProjectDirectoryBinding(context, workspace, project, action) {
|
|
170
|
+
const bindResult = await bindProjectToDirectory(context, workspace, project, action);
|
|
171
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
172
|
+
return bindResult.value;
|
|
152
173
|
}
|
|
153
174
|
async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
|
|
154
175
|
const created = await provider.createProject({
|
|
@@ -309,42 +330,50 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
309
330
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
310
331
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
311
332
|
if (!client) throw authRequiredError();
|
|
312
|
-
|
|
333
|
+
const result = await inspectProjectBinding({
|
|
313
334
|
context,
|
|
314
335
|
workspace,
|
|
315
336
|
explicitProject,
|
|
316
337
|
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
317
338
|
commandName: "project show"
|
|
318
339
|
});
|
|
340
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
341
|
+
return result.value;
|
|
319
342
|
}
|
|
320
343
|
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
321
344
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
322
345
|
if (!client) throw authRequiredError();
|
|
323
|
-
|
|
346
|
+
const result = await resolveProjectTarget({
|
|
324
347
|
context,
|
|
325
348
|
workspace,
|
|
326
349
|
explicitProject,
|
|
327
350
|
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
328
351
|
commandName
|
|
329
352
|
});
|
|
353
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
354
|
+
return result.value;
|
|
330
355
|
}
|
|
331
356
|
async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
|
|
332
|
-
|
|
357
|
+
const result = await inspectProjectBinding({
|
|
333
358
|
context,
|
|
334
359
|
workspace,
|
|
335
360
|
explicitProject,
|
|
336
361
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
337
362
|
commandName: "project show"
|
|
338
363
|
});
|
|
364
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
365
|
+
return result.value;
|
|
339
366
|
}
|
|
340
367
|
async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
|
|
341
|
-
|
|
368
|
+
const result = await resolveProjectTarget({
|
|
342
369
|
context,
|
|
343
370
|
workspace,
|
|
344
371
|
explicitProject,
|
|
345
372
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
346
373
|
commandName
|
|
347
374
|
});
|
|
375
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
376
|
+
return result.value;
|
|
348
377
|
}
|
|
349
378
|
async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
350
379
|
const { data } = await client.GET("/v1/projects", { signal });
|
|
@@ -699,4 +728,4 @@ function repoConnectionFixForStatus(status) {
|
|
|
699
728
|
return "Re-run with --trace for the underlying API response details.";
|
|
700
729
|
}
|
|
701
730
|
//#endregion
|
|
702
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
731
|
+
export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import { createBranchDatabase, createEnvironmentVariable, deleteBranchDatabase, deleteEnvironmentVariable, listEnvironmentVariables, updateEnvironmentVariable } from "./branch-database-api.js";
|
|
2
|
+
import { AppBuildStrategy } from "./build.js";
|
|
1
3
|
import { envVarNames } from "./env-vars.js";
|
|
2
|
-
import { PreviewBuildStrategy } from "./preview-build.js";
|
|
3
|
-
import { createBranchDatabase, createEnvironmentVariable, deleteBranchDatabase, deleteEnvironmentVariable, listEnvironmentVariables, updateEnvironmentVariable } from "./preview-branch-database.js";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { ApiError, CancelledError, ComputeClient, streamLogs } from "@prisma/compute-sdk";
|
|
6
|
-
//#region src/lib/app/
|
|
7
|
-
var
|
|
6
|
+
//#region src/lib/app/app-provider.ts
|
|
7
|
+
var DomainApiError = class extends Error {
|
|
8
8
|
status;
|
|
9
9
|
code;
|
|
10
10
|
hint;
|
|
11
11
|
constructor(options) {
|
|
12
12
|
super(`${options.summary}: ${options.message}${options.hint ? ` ${options.hint}` : ""}`);
|
|
13
|
-
this.name = "
|
|
13
|
+
this.name = "DomainApiError";
|
|
14
14
|
this.status = options.status;
|
|
15
15
|
this.code = options.code ?? null;
|
|
16
16
|
this.hint = options.hint ?? null;
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
-
function
|
|
19
|
+
function createAppProvider(client, options) {
|
|
20
20
|
const sdk = new ComputeClient(client);
|
|
21
21
|
return {
|
|
22
22
|
async createProject(options) {
|
|
@@ -161,7 +161,7 @@ function createPreviewAppProvider(client, options) {
|
|
|
161
161
|
region: options.region
|
|
162
162
|
};
|
|
163
163
|
const deployResult = await sdk.deploy({
|
|
164
|
-
strategy: new
|
|
164
|
+
strategy: new AppBuildStrategy({
|
|
165
165
|
appPath: path.resolve(options.cwd),
|
|
166
166
|
entrypoint: options.entrypoint,
|
|
167
167
|
buildType: options.buildType,
|
|
@@ -491,7 +491,7 @@ function apiCallError(summary, response, error) {
|
|
|
491
491
|
return /* @__PURE__ */ new Error(`${summary}: ${message}${hint}`);
|
|
492
492
|
}
|
|
493
493
|
function domainApiCallError(summary, response, error) {
|
|
494
|
-
return new
|
|
494
|
+
return new DomainApiError({
|
|
495
495
|
summary,
|
|
496
496
|
status: response.status,
|
|
497
497
|
code: error.error?.code ?? null,
|
|
@@ -509,32 +509,36 @@ async function findAppForDeployment(sdk, deploymentId, signal) {
|
|
|
509
509
|
});
|
|
510
510
|
if (servicesResult.isErr()) throw new Error(servicesResult.error.message);
|
|
511
511
|
for (const service of servicesResult.value) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
signal
|
|
515
|
-
});
|
|
516
|
-
if (detailResult.isErr()) throw new Error(detailResult.error.message);
|
|
517
|
-
const app = {
|
|
518
|
-
id: detailResult.value.id,
|
|
519
|
-
name: detailResult.value.name,
|
|
520
|
-
region: detailResult.value.region ?? null,
|
|
521
|
-
liveDeploymentId: detailResult.value.latestVersionId ?? null,
|
|
522
|
-
liveUrl: toAbsoluteUrl(detailResult.value.serviceEndpointDomain ?? null)
|
|
523
|
-
};
|
|
524
|
-
if (app.liveDeploymentId === deploymentId) return app;
|
|
525
|
-
const versionsResult = await sdk.listVersions({
|
|
526
|
-
serviceId: service.id,
|
|
527
|
-
signal
|
|
528
|
-
});
|
|
529
|
-
if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
|
|
530
|
-
if (versionsResult.value.some((version) => version.id === deploymentId)) return app;
|
|
512
|
+
const app = await findServiceAppForDeployment(sdk, service.id, deploymentId, signal);
|
|
513
|
+
if (app) return app;
|
|
531
514
|
}
|
|
532
515
|
}
|
|
533
516
|
return null;
|
|
534
517
|
}
|
|
518
|
+
async function findServiceAppForDeployment(sdk, serviceId, deploymentId, signal) {
|
|
519
|
+
const detailResult = await sdk.showService({
|
|
520
|
+
serviceId,
|
|
521
|
+
signal
|
|
522
|
+
});
|
|
523
|
+
if (detailResult.isErr()) throw new Error(detailResult.error.message);
|
|
524
|
+
const app = {
|
|
525
|
+
id: detailResult.value.id,
|
|
526
|
+
name: detailResult.value.name,
|
|
527
|
+
region: detailResult.value.region ?? null,
|
|
528
|
+
liveDeploymentId: detailResult.value.latestVersionId ?? null,
|
|
529
|
+
liveUrl: toAbsoluteUrl(detailResult.value.serviceEndpointDomain ?? null)
|
|
530
|
+
};
|
|
531
|
+
if (app.liveDeploymentId === deploymentId) return app;
|
|
532
|
+
const versionsResult = await sdk.listVersions({
|
|
533
|
+
serviceId,
|
|
534
|
+
signal
|
|
535
|
+
});
|
|
536
|
+
if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
|
|
537
|
+
return versionsResult.value.some((version) => version.id === deploymentId) ? app : null;
|
|
538
|
+
}
|
|
535
539
|
function toAbsoluteUrl(url) {
|
|
536
540
|
if (!url) return null;
|
|
537
541
|
return url.startsWith("https://") || url.startsWith("http://") ? url : `https://${url}`;
|
|
538
542
|
}
|
|
539
543
|
//#endregion
|
|
540
|
-
export {
|
|
544
|
+
export { DomainApiError, createAppProvider };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CliError, usageError } from "../../shell/errors.js";
|
|
2
|
+
import { confirmPrompt } from "../../shell/prompt.js";
|
|
2
3
|
import { renderSummaryLine } from "../../shell/ui.js";
|
|
3
4
|
import { canPrompt } from "../../shell/runtime.js";
|
|
4
|
-
import { confirmPrompt } from "../../shell/prompt.js";
|
|
5
5
|
import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
6
|
-
import
|
|
6
|
+
import "../project/setup.js";
|
|
7
|
+
import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal } from "./branch-database.js";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
//#region src/lib/app/branch-database-deploy.ts
|
|
9
10
|
async function maybeSetupBranchDatabase(context, provider, projectId, branch, options) {
|
|
@@ -25,13 +26,12 @@ async function maybeSetupBranchDatabase(context, provider, projectId, branch, op
|
|
|
25
26
|
result: options.db === true ? {
|
|
26
27
|
status: "skipped",
|
|
27
28
|
reason: existingDatabaseEnvReason(branch),
|
|
28
|
-
envVars: targetEnvVars
|
|
29
|
-
schema: null
|
|
29
|
+
envVars: targetEnvVars
|
|
30
30
|
} : void 0,
|
|
31
31
|
warnings: warning ? [warning] : []
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
const localSignal = await inspectBranchDatabaseSignal(
|
|
34
|
+
const localSignal = await inspectBranchDatabaseSignal(options.projectDir, context.runtime.signal);
|
|
35
35
|
if (localSignal.unsupportedSchema) {
|
|
36
36
|
if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch, context);
|
|
37
37
|
return emptyBranchDatabaseSetupOutcome();
|
|
@@ -55,9 +55,9 @@ async function maybeSetupBranchDatabase(context, provider, projectId, branch, op
|
|
|
55
55
|
initialValue: false
|
|
56
56
|
})) return emptyBranchDatabaseSetupOutcome();
|
|
57
57
|
} else if (!canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
|
|
58
|
-
return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
|
|
58
|
+
return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState, options.projectDir);
|
|
59
59
|
}
|
|
60
|
-
async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
|
|
60
|
+
async function setupBranchDatabase(context, provider, projectId, branch, signal, envState, projectDir) {
|
|
61
61
|
emitBranchDatabaseProgress(context, "pending", "Creating database");
|
|
62
62
|
const database = await provider.createBranchDatabase({
|
|
63
63
|
projectId,
|
|
@@ -69,27 +69,11 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
|
|
|
69
69
|
});
|
|
70
70
|
emitBranchDatabaseProgress(context, "success", "Created database");
|
|
71
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, context.runtime.cwd);
|
|
84
|
-
});
|
|
85
|
-
emitBranchDatabaseProgress(context, "success", "Applied database schema");
|
|
86
|
-
} else skippedSchemaWarning = "No supported Prisma schema source was found. Database env vars were created, but schema setup was skipped.";
|
|
87
72
|
const envVars = await upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState);
|
|
88
|
-
emitBranchDatabaseProgress(context, "success", `Added ${envScopeLabel(branch)} env var${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
73
|
+
emitBranchDatabaseProgress(context, "success", `Added ${envScopeLabel(branch)} env var${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")} — shared by every app on ${envScopeLabel(branch) === "production" ? "production" : `branch "${branch.name}"`}`);
|
|
74
|
+
const schemaCommand = signal.schema ? `DATABASE_URL=<url> npx ${formatSchemaSetupCommand(signal.schema.command)} (detected ${path.relative(projectDir, signal.schema.path) || signal.schema.path})` : "your own migration tooling";
|
|
75
|
+
const schemaSuggestion = `The new database is empty. Get a connection URL with \`prisma-cli database connection create ${database.id}\`, then apply your schema with ${schemaCommand}.`;
|
|
76
|
+
emitBranchDatabaseWarning(context, schemaSuggestion);
|
|
93
77
|
return {
|
|
94
78
|
result: {
|
|
95
79
|
status: "created",
|
|
@@ -97,14 +81,9 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
|
|
|
97
81
|
id: database.id,
|
|
98
82
|
name: database.name
|
|
99
83
|
},
|
|
100
|
-
envVars
|
|
101
|
-
schema: schemaSetup ? {
|
|
102
|
-
command: schemaSetup.command,
|
|
103
|
-
source: schemaSetup.source,
|
|
104
|
-
path: schemaSetup.schemaPath
|
|
105
|
-
} : null
|
|
84
|
+
envVars
|
|
106
85
|
},
|
|
107
|
-
warnings
|
|
86
|
+
warnings: [schemaSuggestion]
|
|
108
87
|
};
|
|
109
88
|
} catch (error) {
|
|
110
89
|
throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch, error);
|
|
@@ -306,24 +285,6 @@ function branchDatabaseCleanupFailedError(setupError, cleanupError, database, br
|
|
|
306
285
|
nextSteps: []
|
|
307
286
|
});
|
|
308
287
|
}
|
|
309
|
-
function schemaSetupFailedError(error, schema, branch, cwd) {
|
|
310
|
-
return new CliError({
|
|
311
|
-
code: "SCHEMA_SETUP_FAILED",
|
|
312
|
-
domain: "app",
|
|
313
|
-
summary: "Database schema setup failed",
|
|
314
|
-
why: error instanceof Error ? error.message : String(error),
|
|
315
|
-
fix: "Fix the Prisma schema or migrations, then rerun deploy with --db.",
|
|
316
|
-
debug: formatDebugDetails(error),
|
|
317
|
-
meta: {
|
|
318
|
-
branch: branch.name,
|
|
319
|
-
schemaPath: schema.path,
|
|
320
|
-
source: schema.kind,
|
|
321
|
-
command: schema.command
|
|
322
|
-
},
|
|
323
|
-
exitCode: 1,
|
|
324
|
-
nextSteps: [...formatSchemaSetupNextSteps(schema, cwd), formatAppDeployWithDbNextStep(branch)]
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
288
|
function unsupportedBranchDatabaseSchemaError(schema, branch, context) {
|
|
328
289
|
return usageError("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, or switch the Prisma schema source to PostgreSQL before using --db.", [formatProjectEnvAddNextStep(branch), `prisma-cli app deploy --branch ${formatCommandArgument(branch.name)}`], "app");
|
|
329
290
|
}
|
|
@@ -336,14 +297,6 @@ function formatProjectEnvListNextStep(branch) {
|
|
|
336
297
|
function formatProjectEnvAddNextStep(branch) {
|
|
337
298
|
return branch.kind === "production" ? "prisma-cli project env add DATABASE_URL=<value> --role production" : `prisma-cli project env add DATABASE_URL=<value> --branch ${formatCommandArgument(branch.name)}`;
|
|
338
299
|
}
|
|
339
|
-
function formatSchemaSetupNextSteps(schema, cwd) {
|
|
340
|
-
const sourcePath = path.relative(cwd, schema.path) || defaultSchemaSourcePath(schema);
|
|
341
|
-
switch (schema.command) {
|
|
342
|
-
case "migrate-deploy": return [`npx --no-install prisma migrate deploy --schema ${formatCommandArgument(sourcePath)}`];
|
|
343
|
-
case "db-push": return [`npx --no-install prisma db push --schema ${formatCommandArgument(sourcePath)}`];
|
|
344
|
-
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>`];
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
300
|
function defaultSchemaSourcePath(schema) {
|
|
348
301
|
return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
|
|
349
302
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { access, readFile, readdir, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
3
|
//#region src/lib/app/branch-database.ts
|
|
5
4
|
const SKIPPED_DIRECTORIES = new Set([
|
|
6
5
|
".git",
|
|
@@ -67,23 +66,6 @@ function hasBranchDatabaseSignal(signal) {
|
|
|
67
66
|
if (signal.unsupportedSchema) return false;
|
|
68
67
|
return Boolean(signal.schema || signal.databaseUrlReferences.length > 0);
|
|
69
68
|
}
|
|
70
|
-
async function runBranchDatabaseSchemaSetup(options) {
|
|
71
|
-
const schemaPath = path.relative(options.context.runtime.cwd, options.schema.path) || defaultSchemaSourcePath(options.schema);
|
|
72
|
-
const commands = buildSchemaSetupCommands(options.schema, schemaPath, options.databaseUrl);
|
|
73
|
-
for (const command of commands) await runPrismaCommand({
|
|
74
|
-
context: options.context,
|
|
75
|
-
...command,
|
|
76
|
-
env: {
|
|
77
|
-
DATABASE_URL: options.databaseUrl,
|
|
78
|
-
...options.directUrl ? { DIRECT_URL: options.directUrl } : {}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
return {
|
|
82
|
-
command: options.schema.command,
|
|
83
|
-
source: options.schema.kind,
|
|
84
|
-
schemaPath
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
69
|
async function scanDirectory(cwd, directory, depth, state, signal) {
|
|
88
70
|
signal.throwIfAborted();
|
|
89
71
|
if (depth > MAX_SCAN_DEPTH || state.filesVisited >= MAX_SCAN_FILES) return;
|
|
@@ -229,88 +211,5 @@ async function readTextFileIfSmall(filePath, signal) {
|
|
|
229
211
|
signal
|
|
230
212
|
});
|
|
231
213
|
}
|
|
232
|
-
function buildSchemaSetupCommands(schema, schemaPath, databaseUrl) {
|
|
233
|
-
if (schema.command === "migrate-deploy") return [{
|
|
234
|
-
args: [
|
|
235
|
-
"--no-install",
|
|
236
|
-
"prisma",
|
|
237
|
-
"migrate",
|
|
238
|
-
"deploy",
|
|
239
|
-
"--schema",
|
|
240
|
-
schemaPath
|
|
241
|
-
],
|
|
242
|
-
displayCommand: "npx --no-install prisma migrate deploy"
|
|
243
|
-
}];
|
|
244
|
-
if (schema.command === "db-push") return [{
|
|
245
|
-
args: [
|
|
246
|
-
"--no-install",
|
|
247
|
-
"prisma",
|
|
248
|
-
"db",
|
|
249
|
-
"push",
|
|
250
|
-
"--schema",
|
|
251
|
-
schemaPath
|
|
252
|
-
],
|
|
253
|
-
displayCommand: "npx --no-install prisma db push"
|
|
254
|
-
}];
|
|
255
|
-
return [{
|
|
256
|
-
args: [
|
|
257
|
-
"--no-install",
|
|
258
|
-
"prisma-next",
|
|
259
|
-
"contract",
|
|
260
|
-
"emit",
|
|
261
|
-
"--config",
|
|
262
|
-
schemaPath
|
|
263
|
-
],
|
|
264
|
-
displayCommand: "npx --no-install prisma-next contract emit"
|
|
265
|
-
}, {
|
|
266
|
-
args: [
|
|
267
|
-
"--no-install",
|
|
268
|
-
"prisma-next",
|
|
269
|
-
"db",
|
|
270
|
-
"init",
|
|
271
|
-
"--config",
|
|
272
|
-
schemaPath,
|
|
273
|
-
"--db",
|
|
274
|
-
databaseUrl
|
|
275
|
-
],
|
|
276
|
-
displayCommand: "npx --no-install prisma-next db init"
|
|
277
|
-
}];
|
|
278
|
-
}
|
|
279
|
-
function defaultSchemaSourcePath(schema) {
|
|
280
|
-
return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
|
|
281
|
-
}
|
|
282
|
-
async function runPrismaCommand(options) {
|
|
283
|
-
const shouldPipeOutput = !options.context.flags.json && !options.context.flags.quiet;
|
|
284
|
-
const child = spawn("npx", options.args, {
|
|
285
|
-
cwd: options.context.runtime.cwd,
|
|
286
|
-
env: {
|
|
287
|
-
...options.context.runtime.env,
|
|
288
|
-
...options.env
|
|
289
|
-
},
|
|
290
|
-
signal: options.context.runtime.signal,
|
|
291
|
-
stdio: shouldPipeOutput ? [
|
|
292
|
-
"ignore",
|
|
293
|
-
"pipe",
|
|
294
|
-
"pipe"
|
|
295
|
-
] : [
|
|
296
|
-
"ignore",
|
|
297
|
-
"ignore",
|
|
298
|
-
"ignore"
|
|
299
|
-
]
|
|
300
|
-
});
|
|
301
|
-
if (shouldPipeOutput) {
|
|
302
|
-
child.stdout?.pipe(options.context.output.stderr, { end: false });
|
|
303
|
-
child.stderr?.pipe(options.context.output.stderr, { end: false });
|
|
304
|
-
}
|
|
305
|
-
const exit = await new Promise((resolve, reject) => {
|
|
306
|
-
child.once("error", reject);
|
|
307
|
-
child.once("close", (code, signal) => resolve({
|
|
308
|
-
code,
|
|
309
|
-
signal
|
|
310
|
-
}));
|
|
311
|
-
});
|
|
312
|
-
if (exit.signal) throw new Error(`${options.displayCommand} was terminated by ${exit.signal}.`);
|
|
313
|
-
if (exit.code !== 0) throw new Error(`${options.displayCommand} exited with code ${exit.code ?? 1}.`);
|
|
314
|
-
}
|
|
315
214
|
//#endregion
|
|
316
|
-
export { hasBranchDatabaseSignal, inspectBranchDatabaseSignal
|
|
215
|
+
export { hasBranchDatabaseSignal, inspectBranchDatabaseSignal };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveBuildSettings, resolveConfiguredBuildSettings } from "@prisma/compute-sdk";
|
|
4
|
+
//#region src/lib/app/build-settings.ts
|
|
5
|
+
/** Legacy build-settings file: no longer read or written, only detected for migration. */
|
|
6
|
+
const PRISMA_APP_CONFIG_FILENAME = "prisma.app.json";
|
|
7
|
+
/**
|
|
8
|
+
* Detects a leftover `prisma.app.json`. The file is no longer used: one that
|
|
9
|
+
* matches the effective settings is reported for deletion, one with custom
|
|
10
|
+
* values must be migrated to the compute config so builds never silently
|
|
11
|
+
* change.
|
|
12
|
+
*/
|
|
13
|
+
async function detectLegacyBuildSettings(options) {
|
|
14
|
+
const configPath = path.join(options.appPath, PRISMA_APP_CONFIG_FILENAME);
|
|
15
|
+
let content;
|
|
16
|
+
try {
|
|
17
|
+
options.signal?.throwIfAborted();
|
|
18
|
+
content = await readFile(configPath, {
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
signal: options.signal
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (options.signal?.aborted) throw error;
|
|
24
|
+
return { kind: "absent" };
|
|
25
|
+
}
|
|
26
|
+
let legacy;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(content);
|
|
29
|
+
const buildCommand = parsed.buildCommand === null || typeof parsed.buildCommand === "string" ? typeof parsed.buildCommand === "string" ? parsed.buildCommand.trim() || null : null : void 0;
|
|
30
|
+
const outputDirectory = typeof parsed.outputDirectory === "string" ? normalizeRelativePath(parsed.outputDirectory) : void 0;
|
|
31
|
+
if (buildCommand === void 0 || !outputDirectory) return {
|
|
32
|
+
kind: "invalid",
|
|
33
|
+
configPath
|
|
34
|
+
};
|
|
35
|
+
legacy = {
|
|
36
|
+
buildCommand,
|
|
37
|
+
outputDirectory
|
|
38
|
+
};
|
|
39
|
+
} catch {
|
|
40
|
+
return {
|
|
41
|
+
kind: "invalid",
|
|
42
|
+
configPath
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return legacy.buildCommand === options.effective.buildCommand && legacy.outputDirectory === options.effective.outputDirectory ? {
|
|
46
|
+
kind: "matching",
|
|
47
|
+
configPath
|
|
48
|
+
} : {
|
|
49
|
+
kind: "custom",
|
|
50
|
+
configPath,
|
|
51
|
+
...legacy
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Resolves build settings purely from framework inference; nothing is read or written. */
|
|
55
|
+
async function resolveInferredAppBuildSettings(options) {
|
|
56
|
+
return {
|
|
57
|
+
status: "inferred",
|
|
58
|
+
configPath: null,
|
|
59
|
+
relativeConfigPath: null,
|
|
60
|
+
settings: await resolveBuildSettings(options)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolves build settings when the compute config owns them: configured
|
|
65
|
+
* fields win, omitted fields fall back to framework defaults.
|
|
66
|
+
*/
|
|
67
|
+
async function resolveConfiguredAppBuildSettings(options) {
|
|
68
|
+
const configFilename = path.basename(options.configPath);
|
|
69
|
+
const settings = await resolveConfiguredBuildSettings({
|
|
70
|
+
appPath: options.appPath,
|
|
71
|
+
buildType: options.buildType,
|
|
72
|
+
configured: options.configured,
|
|
73
|
+
source: `set by ${configFilename}`,
|
|
74
|
+
signal: options.signal
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
status: "config",
|
|
78
|
+
configPath: options.configPath,
|
|
79
|
+
relativeConfigPath: configFilename,
|
|
80
|
+
settings
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function normalizeRelativePath(value) {
|
|
84
|
+
const raw = value.trim().replace(/\\/g, "/");
|
|
85
|
+
if (raw.length === 0 || raw.split("/").includes("..")) return;
|
|
86
|
+
if (/^[A-Za-z]:/.test(raw)) return;
|
|
87
|
+
const normalized = path.posix.normalize(raw);
|
|
88
|
+
const segments = normalized.split("/");
|
|
89
|
+
if (path.win32.isAbsolute(value) || path.posix.isAbsolute(normalized) || segments.includes("..")) return;
|
|
90
|
+
return normalized === "." ? "." : normalized;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredAppBuildSettings, resolveInferredAppBuildSettings };
|