@prisma/cli 3.0.0-beta.10 → 3.0.0-beta.12

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 (43) hide show
  1. package/dist/adapters/token-storage.js +2 -2
  2. package/dist/cli.js +7 -7
  3. package/dist/cli2.js +3 -3
  4. package/dist/commands/app/index.js +65 -52
  5. package/dist/controllers/app-env.js +6 -5
  6. package/dist/controllers/app.js +505 -184
  7. package/dist/controllers/branch.js +1 -1
  8. package/dist/controllers/database.js +1 -1
  9. package/dist/controllers/project.js +6 -6
  10. package/dist/lib/app/app-interaction.js +5 -0
  11. package/dist/lib/app/{preview-provider.js → app-provider.js} +32 -28
  12. package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
  13. package/dist/lib/app/branch-database-deploy.js +12 -60
  14. package/dist/lib/app/branch-database.js +1 -102
  15. package/dist/lib/app/build-settings.js +93 -0
  16. package/dist/lib/app/build.js +80 -0
  17. package/dist/lib/app/bun-project.js +2 -3
  18. package/dist/lib/app/compute-config.js +147 -0
  19. package/dist/lib/app/deploy-plan.js +58 -0
  20. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +5 -5
  21. package/dist/lib/app/local-dev.js +3 -60
  22. package/dist/lib/app/production-deploy-gate.js +3 -3
  23. package/dist/lib/app/read-branch.js +30 -0
  24. package/dist/lib/auth/login.js +31 -24
  25. package/dist/lib/diagnostics.js +1 -1
  26. package/dist/lib/fs/home-path.js +24 -0
  27. package/dist/lib/git/local-branch.js +15 -3
  28. package/dist/lib/project/resolution.js +2 -2
  29. package/dist/lib/project/setup.js +10 -7
  30. package/dist/output/patterns.js +1 -1
  31. package/dist/presenters/app.js +44 -39
  32. package/dist/presenters/branch.js +1 -1
  33. package/dist/presenters/database.js +2 -2
  34. package/dist/presenters/project.js +3 -9
  35. package/dist/shell/command-runner.js +37 -27
  36. package/dist/shell/diagnostics-output.js +2 -8
  37. package/dist/shell/help.js +30 -20
  38. package/dist/shell/runtime.js +8 -4
  39. package/dist/shell/ui.js +19 -2
  40. package/package.json +17 -3
  41. package/dist/lib/app/preview-build-settings.js +0 -385
  42. package/dist/lib/app/preview-build.js +0 -475
  43. package/dist/lib/app/preview-interaction.js +0 -5
@@ -1,6 +1,6 @@
1
1
  import { CliError, authRequiredError, workspaceRequiredError } from "../shell/errors.js";
2
- import { requireComputeAuth } from "../lib/auth/guard.js";
3
2
  import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
3
+ import { requireComputeAuth } from "../lib/auth/guard.js";
4
4
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
5
5
  import { requireAuthenticatedAuthState } from "./auth.js";
6
6
  import { listRealWorkspaceProjects } from "./project.js";
@@ -1,6 +1,6 @@
1
1
  import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
- import { requireComputeAuth } from "../lib/auth/guard.js";
3
2
  import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
3
+ import { requireComputeAuth } from "../lib/auth/guard.js";
4
4
  import { requireAuthenticatedAuthState } from "./auth.js";
5
5
  import { listFixtureWorkspaceProjects, listRealWorkspaceProjects } from "./project.js";
6
6
  import { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase } from "../lib/database/provider.js";
@@ -1,19 +1,19 @@
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";
6
+ import { formatCommandArgument } from "../shell/command-arguments.js";
7
7
  import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
8
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 open from "open";
16
15
  import { matchError } from "better-result";
16
+ import open from "open";
17
17
  //#region src/controllers/project.ts
18
18
  const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
19
19
  const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
@@ -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,
@@ -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,10 +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
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) {
@@ -26,13 +26,12 @@ async function maybeSetupBranchDatabase(context, provider, projectId, branch, op
26
26
  result: options.db === true ? {
27
27
  status: "skipped",
28
28
  reason: existingDatabaseEnvReason(branch),
29
- envVars: targetEnvVars,
30
- schema: null
29
+ envVars: targetEnvVars
31
30
  } : void 0,
32
31
  warnings: warning ? [warning] : []
33
32
  };
34
33
  }
35
- const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
34
+ const localSignal = await inspectBranchDatabaseSignal(options.projectDir, context.runtime.signal);
36
35
  if (localSignal.unsupportedSchema) {
37
36
  if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch, context);
38
37
  return emptyBranchDatabaseSetupOutcome();
@@ -56,9 +55,9 @@ async function maybeSetupBranchDatabase(context, provider, projectId, branch, op
56
55
  initialValue: false
57
56
  })) return emptyBranchDatabaseSetupOutcome();
58
57
  } else if (!canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
59
- return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
58
+ return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState, options.projectDir);
60
59
  }
61
- async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
60
+ async function setupBranchDatabase(context, provider, projectId, branch, signal, envState, projectDir) {
62
61
  emitBranchDatabaseProgress(context, "pending", "Creating database");
63
62
  const database = await provider.createBranchDatabase({
64
63
  projectId,
@@ -70,27 +69,11 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
70
69
  });
71
70
  emitBranchDatabaseProgress(context, "success", "Created database");
72
71
  try {
73
- let schemaSetup = null;
74
- const warnings = [];
75
- let skippedSchemaWarning = null;
76
- if (signal.schema) {
77
- emitBranchDatabaseProgress(context, "pending", `Applying database schema with ${formatSchemaSetupCommand(signal.schema.command)}`);
78
- schemaSetup = await runBranchDatabaseSchemaSetup({
79
- context,
80
- schema: signal.schema,
81
- databaseUrl: database.databaseUrl,
82
- directUrl: database.directUrl
83
- }).catch((error) => {
84
- throw schemaSetupFailedError(error, signal.schema, branch, context.runtime.cwd);
85
- });
86
- emitBranchDatabaseProgress(context, "success", "Applied database schema");
87
- } else skippedSchemaWarning = "No supported Prisma schema source was found. Database env vars were created, but schema setup was skipped.";
88
72
  const envVars = await upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState);
89
- emitBranchDatabaseProgress(context, "success", `Added ${envScopeLabel(branch)} env var${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
90
- if (skippedSchemaWarning) {
91
- emitBranchDatabaseWarning(context, skippedSchemaWarning);
92
- warnings.push(skippedSchemaWarning);
93
- }
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);
94
77
  return {
95
78
  result: {
96
79
  status: "created",
@@ -98,14 +81,9 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
98
81
  id: database.id,
99
82
  name: database.name
100
83
  },
101
- envVars,
102
- schema: schemaSetup ? {
103
- command: schemaSetup.command,
104
- source: schemaSetup.source,
105
- path: schemaSetup.schemaPath
106
- } : null
84
+ envVars
107
85
  },
108
- warnings
86
+ warnings: [schemaSuggestion]
109
87
  };
110
88
  } catch (error) {
111
89
  throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch, error);
@@ -307,24 +285,6 @@ function branchDatabaseCleanupFailedError(setupError, cleanupError, database, br
307
285
  nextSteps: []
308
286
  });
309
287
  }
310
- function schemaSetupFailedError(error, schema, branch, cwd) {
311
- return new CliError({
312
- code: "SCHEMA_SETUP_FAILED",
313
- domain: "app",
314
- summary: "Database schema setup failed",
315
- why: error instanceof Error ? error.message : String(error),
316
- fix: "Fix the Prisma schema or migrations, then rerun deploy with --db.",
317
- debug: formatDebugDetails(error),
318
- meta: {
319
- branch: branch.name,
320
- schemaPath: schema.path,
321
- source: schema.kind,
322
- command: schema.command
323
- },
324
- exitCode: 1,
325
- nextSteps: [...formatSchemaSetupNextSteps(schema, cwd), formatAppDeployWithDbNextStep(branch)]
326
- });
327
- }
328
288
  function unsupportedBranchDatabaseSchemaError(schema, branch, context) {
329
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");
330
290
  }
@@ -337,14 +297,6 @@ function formatProjectEnvListNextStep(branch) {
337
297
  function formatProjectEnvAddNextStep(branch) {
338
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)}`;
339
299
  }
340
- function formatSchemaSetupNextSteps(schema, cwd) {
341
- const sourcePath = path.relative(cwd, schema.path) || defaultSchemaSourcePath(schema);
342
- switch (schema.command) {
343
- case "migrate-deploy": return [`npx --no-install prisma migrate deploy --schema ${formatCommandArgument(sourcePath)}`];
344
- case "db-push": return [`npx --no-install prisma db push --schema ${formatCommandArgument(sourcePath)}`];
345
- 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>`];
346
- }
347
- }
348
300
  function defaultSchemaSourcePath(schema) {
349
301
  return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
350
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 };
@@ -0,0 +1,80 @@
1
+ import "./build-settings.js";
2
+ import "node:fs/promises";
3
+ import "node:path";
4
+ import { normalizeArtifactSymlinks, resolveBuildStrategy } from "@prisma/compute-sdk";
5
+ //#region src/lib/app/build.ts
6
+ const APP_BUILD_TYPES = [
7
+ "auto",
8
+ "bun",
9
+ "nextjs",
10
+ "nuxt",
11
+ "astro",
12
+ "nestjs",
13
+ "tanstack-start"
14
+ ];
15
+ const RESOLVED_APP_BUILD_TYPES = APP_BUILD_TYPES.filter((buildType) => buildType !== "auto");
16
+ var AppBuildStrategy = class {
17
+ #appPath;
18
+ #entrypoint;
19
+ #buildType;
20
+ #signal;
21
+ #buildSettings;
22
+ constructor(options) {
23
+ this.#appPath = options.appPath;
24
+ this.#entrypoint = options.entrypoint;
25
+ this.#buildType = options.buildType ?? "auto";
26
+ this.#signal = options.signal;
27
+ this.#buildSettings = options.buildSettings;
28
+ }
29
+ async canBuild(signal = this.#signal) {
30
+ const { strategy } = await resolveAppBuildStrategy({
31
+ appPath: this.#appPath,
32
+ entrypoint: this.#entrypoint,
33
+ buildType: this.#buildType,
34
+ signal,
35
+ buildSettings: this.#buildSettings
36
+ });
37
+ return strategy.canBuild(signal);
38
+ }
39
+ async execute(signal = this.#signal) {
40
+ const { artifact } = await executeAppBuild({
41
+ appPath: this.#appPath,
42
+ entrypoint: this.#entrypoint,
43
+ buildType: this.#buildType,
44
+ signal,
45
+ buildSettings: this.#buildSettings
46
+ });
47
+ return artifact;
48
+ }
49
+ };
50
+ async function executeAppBuild(options) {
51
+ const { strategy, buildType } = await resolveAppBuildStrategy({
52
+ appPath: options.appPath,
53
+ entrypoint: options.entrypoint,
54
+ buildType: options.buildType ?? "auto",
55
+ signal: options.signal,
56
+ buildSettings: options.buildSettings
57
+ });
58
+ const artifact = await strategy.execute(options.signal);
59
+ try {
60
+ await normalizeArtifactSymlinks(artifact.directory, options.appPath, options.signal);
61
+ return {
62
+ artifact,
63
+ buildType
64
+ };
65
+ } catch (error) {
66
+ await artifact.cleanup?.().catch(() => void 0);
67
+ throw error;
68
+ }
69
+ }
70
+ async function resolveAppBuildStrategy(options) {
71
+ return resolveBuildStrategy({
72
+ appPath: options.appPath,
73
+ buildType: options.buildType,
74
+ entrypoint: options.entrypoint,
75
+ buildSettings: options.buildSettings,
76
+ signal: options.signal
77
+ });
78
+ }
79
+ //#endregion
80
+ export { APP_BUILD_TYPES, AppBuildStrategy, RESOLVED_APP_BUILD_TYPES, executeAppBuild };
@@ -21,13 +21,12 @@ async function readBunPackageJson(appPath, signal) {
21
21
  }
22
22
  }
23
23
  function readBunPackageEntrypoint(packageJson) {
24
- if (typeof packageJson?.main === "string") return packageJson.main;
25
- if (typeof packageJson?.module === "string") return packageJson.module;
24
+ return typeof packageJson?.main === "string" ? packageJson.main : void 0;
26
25
  }
27
26
  async function resolveBunEntrypoint(appPath, explicitEntrypoint, signal) {
28
27
  const packageJson = await readBunPackageJson(appPath, signal);
29
28
  const candidate = explicitEntrypoint ?? readBunPackageEntrypoint(packageJson);
30
- if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main or module.");
29
+ if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main.");
31
30
  if (path.isAbsolute(candidate)) throw new Error("Entrypoint must be a relative path.");
32
31
  const normalized = path.normalize(candidate);
33
32
  if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized.includes(`${path.sep}..${path.sep}`)) throw new Error("Entrypoint must not escape the app directory.");