@prisma/cli 3.0.0-dev.87.1 → 3.0.0-dev.90.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.
@@ -1,11 +1,11 @@
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
5
  import { readLocalResolutionPin } from "../lib/project/local-pin.js";
5
6
  import { formatCommandArgument } from "../shell/command-arguments.js";
6
7
  import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
7
8
  import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
8
- import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
9
9
  import { requireComputeAuth } from "../lib/auth/guard.js";
10
10
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
11
11
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
@@ -104,7 +104,7 @@ async function runProjectCreate(context, projectName) {
104
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");
105
105
  const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
106
106
  if (!client) throw authRequiredError();
107
- const provider = createPreviewAppProvider(client);
107
+ const provider = createAppProvider(client);
108
108
  const name = projectName.trim();
109
109
  const created = await provider.createProject({
110
110
  name,
@@ -136,7 +136,7 @@ async function runProjectLink(context, projectRef) {
136
136
  if (isRealMode(context)) {
137
137
  const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
138
138
  if (!client) throw authRequiredError();
139
- provider = createPreviewAppProvider(client);
139
+ provider = createAppProvider(client);
140
140
  projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
141
141
  } else projects = listFixtureWorkspaceProjects(context, workspace);
142
142
  let result;
@@ -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,
@@ -541,4 +541,4 @@ function toAbsoluteUrl(url) {
541
541
  return url.startsWith("https://") || url.startsWith("http://") ? url : `https://${url}`;
542
542
  }
543
543
  //#endregion
544
- 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: {
@@ -4,27 +4,11 @@ import { renderSummaryLine } from "../../shell/ui.js";
4
4
  import { canPrompt } from "../../shell/runtime.js";
5
5
  import { formatCommandArgument } from "../../shell/command-arguments.js";
6
6
  import "../project/setup.js";
7
- import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } from "./branch-database.js";
7
+ import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal } from "./branch-database.js";
8
8
  import path from "node:path";
9
9
  //#region src/lib/app/branch-database-deploy.ts
10
10
  async function maybeSetupBranchDatabase(context, provider, projectId, branch, options) {
11
11
  if (options.db === false) return emptyBranchDatabaseSetupOutcome();
12
- const preflight = branchDatabasePreflight(branch, options);
13
- if (preflight) return preflight;
14
- const envState = await inspectBranchDatabaseEnv(provider, projectId, branch, context.runtime.signal);
15
- const existingEnvOutcome = existingBranchDatabaseEnvOutcome(context, branch, getTargetDatabaseEnvVarKeys(envState), envState, options.db);
16
- if (existingEnvOutcome) return existingEnvOutcome;
17
- const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
18
- if (localSignal.unsupportedSchema) {
19
- if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch, context);
20
- return emptyBranchDatabaseSetupOutcome();
21
- }
22
- const promptOutcome = await branchDatabasePromptOutcome(context, branch, localSignal, envState, options.db);
23
- if (promptOutcome) return promptOutcome;
24
- if (options.db === true && !canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
25
- return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
26
- }
27
- function branchDatabasePreflight(branch, options) {
28
12
  if (hasProvidedDatabaseEnvVars(options.providedEnvVars)) {
29
13
  if (options.db === true) throw usageError("Database setup cannot be combined with provided database env vars", "The deploy command received --db and a DATABASE_URL or DIRECT_URL value from --env.", "Remove the --env database value to let --db create and wire a database, or remove --db to deploy with the provided value.", ["prisma-cli app deploy --db", "prisma-cli app deploy --env DATABASE_URL=postgresql://example"], "app");
30
14
  return emptyBranchDatabaseSetupOutcome();
@@ -33,42 +17,47 @@ function branchDatabasePreflight(branch, options) {
33
17
  if (options.db === true) throw productionDatabaseSetupAfterFirstDeployError();
34
18
  return emptyBranchDatabaseSetupOutcome();
35
19
  }
36
- return null;
37
- }
38
- function existingBranchDatabaseEnvOutcome(context, branch, targetEnvVars, envState, requested) {
39
- if (!hasExistingDatabaseEnvForTarget(branch, envState)) return null;
40
- const warning = requested === true ? existingDatabaseEnvWarning(branch, targetEnvVars) : null;
41
- if (warning) emitBranchDatabaseWarning(context, warning);
42
- return {
43
- result: requested === true ? {
44
- status: "skipped",
45
- reason: existingDatabaseEnvReason(branch),
46
- envVars: targetEnvVars,
47
- schema: null
48
- } : void 0,
49
- warnings: warning ? [warning] : []
50
- };
51
- }
52
- async function branchDatabasePromptOutcome(context, branch, localSignal, envState, requested) {
53
- if (requested === true) return null;
54
- if (!(hasBranchDatabaseSignal(localSignal) || Boolean(envState.inheritedPreviewDatabaseUrl))) return emptyBranchDatabaseSetupOutcome();
55
- if (!canPrompt(context) || context.flags.yes) {
56
- const warning = databasePromptSuppressedWarning(branch);
57
- emitBranchDatabaseWarning(context, warning);
20
+ const envState = await inspectBranchDatabaseEnv(provider, projectId, branch, context.runtime.signal);
21
+ const targetEnvVars = getTargetDatabaseEnvVarKeys(envState);
22
+ if (hasExistingDatabaseEnvForTarget(branch, envState)) {
23
+ const warning = options.db === true ? existingDatabaseEnvWarning(branch, targetEnvVars) : null;
24
+ if (warning) emitBranchDatabaseWarning(context, warning);
58
25
  return {
59
- result: void 0,
60
- warnings: [warning]
26
+ result: options.db === true ? {
27
+ status: "skipped",
28
+ reason: existingDatabaseEnvReason(branch),
29
+ envVars: targetEnvVars
30
+ } : void 0,
31
+ warnings: warning ? [warning] : []
61
32
  };
62
33
  }
63
- maybeRenderBranchDatabaseSignal(context, branch, localSignal, envState);
64
- return await confirmPrompt({
65
- input: context.runtime.stdin,
66
- output: context.output.stderr,
67
- message: databasePromptMessage(branch),
68
- initialValue: false
69
- }) ? null : emptyBranchDatabaseSetupOutcome();
70
- }
71
- async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
34
+ const localSignal = await inspectBranchDatabaseSignal(options.projectDir, context.runtime.signal);
35
+ if (localSignal.unsupportedSchema) {
36
+ if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch, context);
37
+ return emptyBranchDatabaseSetupOutcome();
38
+ }
39
+ const hasSignal = hasBranchDatabaseSignal(localSignal) || Boolean(envState.inheritedPreviewDatabaseUrl);
40
+ if (options.db !== true) {
41
+ if (!hasSignal) return emptyBranchDatabaseSetupOutcome();
42
+ if (!canPrompt(context) || context.flags.yes) {
43
+ const warning = databasePromptSuppressedWarning(branch);
44
+ emitBranchDatabaseWarning(context, warning);
45
+ return {
46
+ result: void 0,
47
+ warnings: [warning]
48
+ };
49
+ }
50
+ maybeRenderBranchDatabaseSignal(context, branch, localSignal, envState);
51
+ if (!await confirmPrompt({
52
+ input: context.runtime.stdin,
53
+ output: context.output.stderr,
54
+ message: databasePromptMessage(branch),
55
+ initialValue: false
56
+ })) return emptyBranchDatabaseSetupOutcome();
57
+ } else if (!canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
58
+ return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState, options.projectDir);
59
+ }
60
+ async function setupBranchDatabase(context, provider, projectId, branch, signal, envState, projectDir) {
72
61
  emitBranchDatabaseProgress(context, "pending", "Creating database");
73
62
  const database = await provider.createBranchDatabase({
74
63
  projectId,
@@ -80,28 +69,11 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
80
69
  });
81
70
  emitBranchDatabaseProgress(context, "success", "Created database");
82
71
  try {
83
- let schemaSetup = null;
84
- const warnings = [];
85
- let skippedSchemaWarning = null;
86
- const schema = signal.schema;
87
- if (schema) {
88
- emitBranchDatabaseProgress(context, "pending", `Applying database schema with ${formatSchemaSetupCommand(schema.command)}`);
89
- schemaSetup = await runBranchDatabaseSchemaSetup({
90
- context,
91
- schema,
92
- databaseUrl: database.databaseUrl,
93
- directUrl: database.directUrl
94
- }).catch((error) => {
95
- throw schemaSetupFailedError(error, schema, branch, context.runtime.cwd);
96
- });
97
- emitBranchDatabaseProgress(context, "success", "Applied database schema");
98
- } else skippedSchemaWarning = "No supported Prisma schema source was found. Database env vars were created, but schema setup was skipped.";
99
72
  const envVars = await upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState);
100
- emitBranchDatabaseProgress(context, "success", `Added ${envScopeLabel(branch)} env var${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
101
- if (skippedSchemaWarning) {
102
- emitBranchDatabaseWarning(context, skippedSchemaWarning);
103
- warnings.push(skippedSchemaWarning);
104
- }
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);
105
77
  return {
106
78
  result: {
107
79
  status: "created",
@@ -109,14 +81,9 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
109
81
  id: database.id,
110
82
  name: database.name
111
83
  },
112
- envVars,
113
- schema: schemaSetup ? {
114
- command: schemaSetup.command,
115
- source: schemaSetup.source,
116
- path: schemaSetup.schemaPath
117
- } : null
84
+ envVars
118
85
  },
119
- warnings
86
+ warnings: [schemaSuggestion]
120
87
  };
121
88
  } catch (error) {
122
89
  throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch, error);
@@ -318,24 +285,6 @@ function branchDatabaseCleanupFailedError(setupError, cleanupError, database, br
318
285
  nextSteps: []
319
286
  });
320
287
  }
321
- function schemaSetupFailedError(error, schema, branch, cwd) {
322
- return new CliError({
323
- code: "SCHEMA_SETUP_FAILED",
324
- domain: "app",
325
- summary: "Database schema setup failed",
326
- why: error instanceof Error ? error.message : String(error),
327
- fix: "Fix the Prisma schema or migrations, then rerun deploy with --db.",
328
- debug: formatDebugDetails(error),
329
- meta: {
330
- branch: branch.name,
331
- schemaPath: schema.path,
332
- source: schema.kind,
333
- command: schema.command
334
- },
335
- exitCode: 1,
336
- nextSteps: [...formatSchemaSetupNextSteps(schema, cwd), formatAppDeployWithDbNextStep(branch)]
337
- });
338
- }
339
288
  function unsupportedBranchDatabaseSchemaError(schema, branch, context) {
340
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");
341
290
  }
@@ -348,14 +297,6 @@ function formatProjectEnvListNextStep(branch) {
348
297
  function formatProjectEnvAddNextStep(branch) {
349
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)}`;
350
299
  }
351
- function formatSchemaSetupNextSteps(schema, cwd) {
352
- const sourcePath = path.relative(cwd, schema.path) || defaultSchemaSourcePath(schema);
353
- switch (schema.command) {
354
- case "migrate-deploy": return [`npx --no-install prisma migrate deploy --schema ${formatCommandArgument(sourcePath)}`];
355
- case "db-push": return [`npx --no-install prisma db push --schema ${formatCommandArgument(sourcePath)}`];
356
- 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>`];
357
- }
358
- }
359
300
  function defaultSchemaSourcePath(schema) {
360
301
  return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
361
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,61 +66,31 @@ 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 prisma = await resolvePrismaInvocation(options.context.runtime.cwd);
73
- const commands = buildSchemaSetupCommands(options.schema, schemaPath, options.databaseUrl, prisma);
74
- for (const command of commands) await runPrismaCommand({
75
- context: options.context,
76
- ...command,
77
- env: {
78
- DATABASE_URL: options.databaseUrl,
79
- ...options.directUrl ? { DIRECT_URL: options.directUrl } : {}
80
- }
81
- });
82
- return {
83
- command: options.schema.command,
84
- source: options.schema.kind,
85
- schemaPath
86
- };
87
- }
88
69
  async function scanDirectory(cwd, directory, depth, state, signal) {
89
70
  signal.throwIfAborted();
90
71
  if (depth > MAX_SCAN_DEPTH || state.filesVisited >= MAX_SCAN_FILES) return;
91
- const entries = await readDirectoryEntries(directory);
92
- if (!entries) return;
93
- entries.sort((left, right) => left.name.localeCompare(right.name));
94
- for (const entry of entries) {
95
- signal.throwIfAborted();
96
- if (state.filesVisited >= MAX_SCAN_FILES) return;
97
- await scanDirectoryEntry(cwd, directory, entry, depth, state, signal);
98
- }
99
- }
100
- async function readDirectoryEntries(directory) {
72
+ let entries;
101
73
  try {
102
- return await readdir(directory, { withFileTypes: true });
74
+ entries = await readdir(directory, { withFileTypes: true });
103
75
  } catch (error) {
104
- if (error.code === "ENOENT") return null;
76
+ if (error.code === "ENOENT") return;
105
77
  throw error;
106
78
  }
107
- }
108
- async function scanDirectoryEntry(cwd, directory, entry, depth, state, signal) {
109
- const entryPath = path.join(directory, entry.name);
110
- if (entry.isDirectory()) {
111
- if (!SKIPPED_DIRECTORIES.has(entry.name)) await scanDirectory(cwd, entryPath, depth + 1, state, signal);
112
- return;
79
+ entries.sort((left, right) => left.name.localeCompare(right.name));
80
+ for (const entry of entries) {
81
+ signal.throwIfAborted();
82
+ if (state.filesVisited >= MAX_SCAN_FILES) return;
83
+ const entryPath = path.join(directory, entry.name);
84
+ if (entry.isDirectory()) {
85
+ if (!SKIPPED_DIRECTORIES.has(entry.name)) await scanDirectory(cwd, entryPath, depth + 1, state, signal);
86
+ continue;
87
+ }
88
+ if (!entry.isFile()) continue;
89
+ state.filesVisited += 1;
90
+ if (entry.name === "schema.prisma") state.schemaCandidates.push(entryPath);
91
+ if (isPrismaNextConfigFile(entry.name)) state.prismaNextConfigCandidates.push(entryPath);
92
+ if (state.databaseUrlReferences.length < MAX_DATABASE_URL_REFERENCE_FILES && shouldScanForDatabaseUrl(entry.name) && await fileContainsDatabaseUrl(entryPath, signal)) state.databaseUrlReferences.push(path.relative(cwd, entryPath) || entry.name);
113
93
  }
114
- if (!entry.isFile()) return;
115
- state.filesVisited += 1;
116
- collectBranchDatabaseCandidate(entryPath, entry.name, state);
117
- if (await shouldRecordDatabaseUrlReference(entryPath, entry.name, state, signal)) state.databaseUrlReferences.push(path.relative(cwd, entryPath) || entry.name);
118
- }
119
- function collectBranchDatabaseCandidate(entryPath, entryName, state) {
120
- if (entryName === "schema.prisma") state.schemaCandidates.push(entryPath);
121
- if (isPrismaNextConfigFile(entryName)) state.prismaNextConfigCandidates.push(entryPath);
122
- }
123
- async function shouldRecordDatabaseUrlReference(entryPath, entryName, state, signal) {
124
- return state.databaseUrlReferences.length < MAX_DATABASE_URL_REFERENCE_FILES && shouldScanForDatabaseUrl(entryName) && await fileContainsDatabaseUrl(entryPath, signal);
125
94
  }
126
95
  async function selectPrismaOrmSchema(cwd, candidates, signal) {
127
96
  const sorted = sortByPreferredRelativePath(cwd, candidates, "schema.prisma");
@@ -155,15 +124,12 @@ async function selectPrismaOrmSchema(cwd, candidates, signal) {
155
124
  };
156
125
  }
157
126
  function selectPrismaNextConfig(cwd, candidates, mode) {
158
- const matches = candidates.filter(mode === "supported" ? isSupportedPrismaNextConfig : isUnsupportedPrismaNextConfig);
127
+ const matches = candidates.filter((candidate) => {
128
+ const isSupported = candidate.target === "postgresql" || candidate.target === "unknown";
129
+ return mode === "supported" ? isSupported : !isSupported;
130
+ });
159
131
  return sortByPreferredRelativePath(cwd, matches.map((candidate) => candidate.path), "prisma-next.config.ts").map((candidatePath) => matches.find((candidate) => candidate.path === candidatePath)).find((candidate) => Boolean(candidate)) ?? null;
160
132
  }
161
- function isSupportedPrismaNextConfig(candidate) {
162
- return candidate.target === "postgresql" || candidate.target === "unknown";
163
- }
164
- function isUnsupportedPrismaNextConfig(candidate) {
165
- return !isSupportedPrismaNextConfig(candidate);
166
- }
167
133
  function sortByPreferredRelativePath(cwd, candidates, preferredRootFile) {
168
134
  return candidates.map((candidate) => ({
169
135
  absolute: candidate,
@@ -245,133 +211,5 @@ async function readTextFileIfSmall(filePath, signal) {
245
211
  signal
246
212
  });
247
213
  }
248
- const FALLBACK_PRISMA_CLI_VERSION = "6.19.3";
249
- /**
250
- * Picks how `prisma` CLI commands are invoked for schema setup. Projects
251
- * with the CLI installed run their own binary (version-exact). Projects
252
- * without it fall back to a versioned `npx prisma@<x>` pinned to the
253
- * installed `@prisma/client` — never bare `npx prisma`, which resolves to
254
- * latest and can be a major version ahead of the project's schema.
255
- */
256
- async function resolvePrismaInvocation(cwd) {
257
- if (await localPrismaBinExists(cwd)) return {
258
- argsPrefix: ["--no-install", "prisma"],
259
- displayPrefix: "npx --no-install prisma"
260
- };
261
- const pinned = await readInstalledPrismaClientVersion(cwd) ?? FALLBACK_PRISMA_CLI_VERSION;
262
- return {
263
- argsPrefix: ["--yes", `prisma@${pinned}`],
264
- displayPrefix: `npx prisma@${pinned}`
265
- };
266
- }
267
- /** npm/pnpm name the local CLI shim `prisma` on POSIX and `prisma.cmd`/`prisma.ps1` on Windows. */
268
- async function localPrismaBinExists(cwd) {
269
- const binDir = path.join(cwd, "node_modules", ".bin");
270
- return (await Promise.all([
271
- "prisma",
272
- "prisma.cmd",
273
- "prisma.ps1"
274
- ].map((name) => fileExists(path.join(binDir, name))))).some(Boolean);
275
- }
276
- async function fileExists(filePath) {
277
- try {
278
- await access(filePath);
279
- return true;
280
- } catch {
281
- return false;
282
- }
283
- }
284
- async function readInstalledPrismaClientVersion(cwd) {
285
- try {
286
- const raw = await readFile(path.join(cwd, "node_modules", "@prisma", "client", "package.json"), { encoding: "utf8" });
287
- const parsed = JSON.parse(raw);
288
- if (typeof parsed !== "object" || parsed === null) return null;
289
- const version = parsed.version;
290
- return typeof version === "string" && version.length > 0 ? version : null;
291
- } catch {
292
- return null;
293
- }
294
- }
295
- function buildSchemaSetupCommands(schema, schemaPath, databaseUrl, prisma) {
296
- if (schema.command === "migrate-deploy") return [{
297
- args: [
298
- ...prisma.argsPrefix,
299
- "migrate",
300
- "deploy",
301
- "--schema",
302
- schemaPath
303
- ],
304
- displayCommand: `${prisma.displayPrefix} migrate deploy`
305
- }];
306
- if (schema.command === "db-push") return [{
307
- args: [
308
- ...prisma.argsPrefix,
309
- "db",
310
- "push",
311
- "--schema",
312
- schemaPath
313
- ],
314
- displayCommand: `${prisma.displayPrefix} db push`
315
- }];
316
- return [{
317
- args: [
318
- "--no-install",
319
- "prisma-next",
320
- "contract",
321
- "emit",
322
- "--config",
323
- schemaPath
324
- ],
325
- displayCommand: "npx --no-install prisma-next contract emit"
326
- }, {
327
- args: [
328
- "--no-install",
329
- "prisma-next",
330
- "db",
331
- "init",
332
- "--config",
333
- schemaPath,
334
- "--db",
335
- databaseUrl
336
- ],
337
- displayCommand: "npx --no-install prisma-next db init"
338
- }];
339
- }
340
- function defaultSchemaSourcePath(schema) {
341
- return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
342
- }
343
- async function runPrismaCommand(options) {
344
- const shouldPipeOutput = !options.context.flags.json && !options.context.flags.quiet;
345
- const child = spawn("npx", options.args, {
346
- cwd: options.context.runtime.cwd,
347
- env: {
348
- ...options.context.runtime.env,
349
- ...options.env
350
- },
351
- signal: options.context.runtime.signal,
352
- stdio: shouldPipeOutput ? [
353
- "ignore",
354
- "pipe",
355
- "pipe"
356
- ] : [
357
- "ignore",
358
- "ignore",
359
- "ignore"
360
- ]
361
- });
362
- if (shouldPipeOutput) {
363
- child.stdout?.pipe(options.context.output.stderr, { end: false });
364
- child.stderr?.pipe(options.context.output.stderr, { end: false });
365
- }
366
- const exit = await new Promise((resolve, reject) => {
367
- child.once("error", reject);
368
- child.once("close", (code, signal) => resolve({
369
- code,
370
- signal
371
- }));
372
- });
373
- if (exit.signal) throw new Error(`${options.displayCommand} was terminated by ${exit.signal}.`);
374
- if (exit.code !== 0) throw new Error(`${options.displayCommand} exited with code ${exit.code ?? 1}.`);
375
- }
376
214
  //#endregion
377
- 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 };