@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.
Files changed (56) hide show
  1. package/README.md +0 -13
  2. package/dist/adapters/mock-api.js +75 -0
  3. package/dist/adapters/token-storage.js +311 -33
  4. package/dist/cli.js +7 -7
  5. package/dist/cli2.js +5 -3
  6. package/dist/commands/app/index.js +65 -52
  7. package/dist/commands/auth/index.js +55 -2
  8. package/dist/commands/database/index.js +159 -0
  9. package/dist/controllers/app-env.js +10 -7
  10. package/dist/controllers/app.js +550 -208
  11. package/dist/controllers/auth.js +211 -2
  12. package/dist/controllers/branch.js +5 -6
  13. package/dist/controllers/database.js +318 -0
  14. package/dist/controllers/project.js +49 -20
  15. package/dist/lib/app/app-interaction.js +5 -0
  16. package/dist/lib/app/{preview-provider.js → app-provider.js} +32 -28
  17. package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
  18. package/dist/lib/app/branch-database-deploy.js +13 -60
  19. package/dist/lib/app/branch-database.js +1 -102
  20. package/dist/lib/app/build-settings.js +93 -0
  21. package/dist/lib/app/build.js +82 -0
  22. package/dist/lib/app/bun-project.js +2 -3
  23. package/dist/lib/app/compute-config.js +147 -0
  24. package/dist/lib/app/deploy-plan.js +58 -0
  25. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +5 -5
  26. package/dist/lib/app/local-dev.js +3 -60
  27. package/dist/lib/app/production-deploy-gate.js +3 -3
  28. package/dist/lib/app/read-branch.js +30 -0
  29. package/dist/lib/auth/auth-ops.js +10 -4
  30. package/dist/lib/auth/guard.js +4 -1
  31. package/dist/lib/auth/login.js +32 -25
  32. package/dist/lib/database/provider.js +167 -0
  33. package/dist/lib/diagnostics.js +1 -1
  34. package/dist/lib/fs/home-path.js +24 -0
  35. package/dist/lib/git/local-branch.js +15 -3
  36. package/dist/lib/project/local-pin.js +170 -40
  37. package/dist/lib/project/resolution.js +166 -61
  38. package/dist/lib/project/setup.js +65 -19
  39. package/dist/output/patterns.js +1 -1
  40. package/dist/presenters/app.js +44 -39
  41. package/dist/presenters/auth.js +98 -1
  42. package/dist/presenters/branch.js +1 -1
  43. package/dist/presenters/database.js +274 -0
  44. package/dist/presenters/project.js +3 -9
  45. package/dist/shell/command-meta.js +153 -2
  46. package/dist/shell/command-runner.js +39 -21
  47. package/dist/shell/diagnostics-output.js +2 -8
  48. package/dist/shell/errors.js +50 -1
  49. package/dist/shell/help.js +30 -20
  50. package/dist/shell/runtime.js +8 -4
  51. package/dist/shell/ui.js +19 -2
  52. package/dist/use-cases/auth.js +68 -1
  53. package/package.json +18 -3
  54. package/dist/lib/app/preview-build-settings.js +0 -385
  55. package/dist/lib/app/preview-build.js +0 -475
  56. 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 { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
8
- import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
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 pin = await readLocalResolutionPin(cwd, signal);
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 = createPreviewAppProvider(client);
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: await bindProjectToDirectory(context, workspace, {
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 = createPreviewAppProvider(client);
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 bindProjectToDirectory(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
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 bindProjectToDirectory(context, workspace, setup.project, setup.action);
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
- return inspectProjectBinding({
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
- return resolveProjectTarget({
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
- return inspectProjectBinding({
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
- return resolveProjectTarget({
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 };
@@ -0,0 +1,5 @@
1
+ import "../../shell/prompt.js";
2
+ //#region src/lib/app/app-interaction.ts
3
+ const DEFAULT_REGION = "eu-central-1";
4
+ //#endregion
5
+ export { DEFAULT_REGION };
@@ -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/preview-provider.ts
7
- var PreviewDomainApiError = class extends Error {
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 = "PreviewDomainApiError";
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 createPreviewAppProvider(client, options) {
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 PreviewBuildStrategy({
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 PreviewDomainApiError({
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 detailResult = await sdk.showService({
513
- serviceId: service.id,
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 { PreviewDomainApiError, createPreviewAppProvider };
544
+ export { DomainApiError, createAppProvider };
@@ -1,4 +1,4 @@
1
- //#region src/lib/app/preview-branch-database.ts
1
+ //#region src/lib/app/branch-database-api.ts
2
2
  async function createBranchDatabase(client, options) {
3
3
  const result = await client.POST("/v1/databases", {
4
4
  body: {
@@ -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 { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } from "./branch-database.js";
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(context.runtime.cwd, context.runtime.signal);
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
- if (skippedSchemaWarning) {
90
- emitBranchDatabaseWarning(context, skippedSchemaWarning);
91
- warnings.push(skippedSchemaWarning);
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, runBranchDatabaseSchemaSetup };
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 };