@prisma/cli 3.0.0-beta.7 → 3.0.0-beta.9

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.
@@ -3,71 +3,72 @@ import { renderSummaryLine } from "../../shell/ui.js";
3
3
  import { canPrompt } from "../../shell/runtime.js";
4
4
  import { confirmPrompt } from "../../shell/prompt.js";
5
5
  import { formatCommandArgument } from "../../shell/command-arguments.js";
6
+ import "../project/setup.js";
6
7
  import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } 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) {
10
11
  if (options.db === false) return emptyBranchDatabaseSetupOutcome();
11
- if (hasInlineDatabaseEnvVars(options.inlineEnvVars)) {
12
- if (options.db === true) throw usageError("Branch database setup cannot be combined with inline database env vars", "The deploy command received --db and an inline DATABASE_URL or DIRECT_URL value.", "Remove the inline --env database value to let --db create a branch override, or remove --db to deploy with the provided value.", ["prisma-cli app deploy --db", "prisma-cli app deploy --env DATABASE_URL=postgresql://example"], "app");
12
+ if (hasProvidedDatabaseEnvVars(options.providedEnvVars)) {
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");
13
14
  return emptyBranchDatabaseSetupOutcome();
14
15
  }
15
- if (branch.kind === "production") {
16
- if (options.db === true) throw usageError("Branch database setup is only available for preview branches", "Production database wiring is a durable environment decision and is not created implicitly by app deploy.", "Use project env commands to manage production DATABASE_URL, or deploy a preview branch with --db.", ["prisma-cli project env add DATABASE_URL=<value> --role production", "prisma-cli app deploy --branch feature/db --db"], "app");
16
+ if (branch.kind === "production" && !options.firstProductionDeploy) {
17
+ if (options.db === true) throw productionDatabaseSetupAfterFirstDeployError();
17
18
  return emptyBranchDatabaseSetupOutcome();
18
19
  }
19
- const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
20
- const envState = await inspectBranchDatabaseEnv(provider, projectId, branch.id, context.runtime.signal);
21
- const branchEnvVars = [envState.branchDatabaseUrl, envState.branchDirectUrl].filter((variable) => Boolean(variable)).map((variable) => variable.key).sort();
22
- if (envState.branchDatabaseUrl) {
23
- const warning = options.db === true ? `Branch "${branch.name}" already has DATABASE_URL. Leaving branch database env vars unchanged.` : null;
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
24
  if (warning) emitBranchDatabaseWarning(context, warning);
25
25
  return {
26
26
  result: options.db === true ? {
27
27
  status: "skipped",
28
- reason: "branch-env-exists",
29
- envVars: branchEnvVars,
28
+ reason: existingDatabaseEnvReason(branch),
29
+ envVars: targetEnvVars,
30
30
  schema: null
31
31
  } : void 0,
32
32
  warnings: warning ? [warning] : []
33
33
  };
34
34
  }
35
+ const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
35
36
  if (localSignal.unsupportedSchema) {
36
- if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch.name, context);
37
+ if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch, context);
37
38
  return emptyBranchDatabaseSetupOutcome();
38
39
  }
39
- const hasSignal = hasBranchDatabaseSignal(localSignal) || Boolean(envState.previewDatabaseUrl);
40
+ const hasSignal = hasBranchDatabaseSignal(localSignal) || Boolean(envState.inheritedPreviewDatabaseUrl);
40
41
  if (options.db !== true) {
41
42
  if (!hasSignal) return emptyBranchDatabaseSetupOutcome();
42
43
  if (!canPrompt(context) || context.flags.yes) {
43
- const warning = "This app appears to use DATABASE_URL. Run prisma-cli app deploy --db to create an isolated database for this preview branch.";
44
+ const warning = databasePromptSuppressedWarning(branch);
44
45
  emitBranchDatabaseWarning(context, warning);
45
46
  return {
46
47
  result: void 0,
47
48
  warnings: [warning]
48
49
  };
49
50
  }
50
- maybeRenderBranchDatabaseSignal(context, branch.name, localSignal, envState);
51
+ maybeRenderBranchDatabaseSignal(context, branch, localSignal, envState);
51
52
  if (!await confirmPrompt({
52
53
  input: context.runtime.stdin,
53
54
  output: context.output.stderr,
54
- message: `Create an isolated database for branch "${branch.name}"?`,
55
+ message: databasePromptMessage(branch),
55
56
  initialValue: false
56
57
  })) return emptyBranchDatabaseSetupOutcome();
57
- }
58
+ } else if (!canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
58
59
  return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
59
60
  }
60
61
  async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
61
- emitBranchDatabaseProgress(context, "pending", "Creating branch database");
62
+ emitBranchDatabaseProgress(context, "pending", "Creating database");
62
63
  const database = await provider.createBranchDatabase({
63
64
  projectId,
64
65
  branchId: branch.id,
65
66
  branchName: branch.name,
66
67
  signal: context.runtime.signal
67
68
  }).catch((error) => {
68
- throw branchDatabaseSetupFailedError("Failed to create branch database", error, branch.name);
69
+ throw branchDatabaseSetupFailedError("Failed to create database", error, branch);
69
70
  });
70
- emitBranchDatabaseProgress(context, "success", "Created branch database");
71
+ emitBranchDatabaseProgress(context, "success", "Created database");
71
72
  try {
72
73
  let schemaSetup = null;
73
74
  const warnings = [];
@@ -80,12 +81,12 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
80
81
  databaseUrl: database.databaseUrl,
81
82
  directUrl: database.directUrl
82
83
  }).catch((error) => {
83
- throw schemaSetupFailedError(error, signal.schema, branch.name, context.runtime.cwd);
84
+ throw schemaSetupFailedError(error, signal.schema, branch, context.runtime.cwd);
84
85
  });
85
86
  emitBranchDatabaseProgress(context, "success", "Applied database schema");
86
- } else skippedSchemaWarning = "No supported Prisma schema source was found. Branch database env vars were created, but schema setup was skipped.";
87
+ } else skippedSchemaWarning = "No supported Prisma schema source was found. Database env vars were created, but schema setup was skipped.";
87
88
  const envVars = await upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState);
88
- emitBranchDatabaseProgress(context, "success", `Added branch env override${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
89
+ emitBranchDatabaseProgress(context, "success", `Added ${envScopeLabel(branch)} env var${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
89
90
  if (skippedSchemaWarning) {
90
91
  emitBranchDatabaseWarning(context, skippedSchemaWarning);
91
92
  warnings.push(skippedSchemaWarning);
@@ -107,37 +108,36 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
107
108
  warnings
108
109
  };
109
110
  } catch (error) {
110
- throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch.name, error);
111
+ throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch, error);
111
112
  }
112
113
  }
113
114
  async function upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState) {
115
+ const scope = envScopeForBranch(branch);
114
116
  const written = [];
115
117
  await upsertBranchDatabaseEnvVar(context, provider, {
116
118
  projectId,
117
- branchId: branch.id,
118
- className: "preview",
119
+ ...scope,
119
120
  key: "DATABASE_URL",
120
121
  value: database.databaseUrl,
121
- existing: envState.branchDatabaseUrl,
122
- branchName: branch.name
122
+ existing: envState.targetDatabaseUrl,
123
+ branch
123
124
  });
124
125
  written.push("DATABASE_URL");
125
126
  if (database.directUrl) {
126
127
  await upsertBranchDatabaseEnvVar(context, provider, {
127
128
  projectId,
128
- branchId: branch.id,
129
- className: "preview",
129
+ ...scope,
130
130
  key: "DIRECT_URL",
131
131
  value: database.directUrl,
132
- existing: envState.branchDirectUrl,
133
- branchName: branch.name
132
+ existing: envState.targetDirectUrl,
133
+ branch
134
134
  });
135
135
  written.push("DIRECT_URL");
136
- } else if (envState.branchDirectUrl) await provider.deleteEnvironmentVariable({
137
- envVarId: envState.branchDirectUrl.id,
136
+ } else if (branch.kind === "preview" && envState.targetDirectUrl) await provider.deleteEnvironmentVariable({
137
+ envVarId: envState.targetDirectUrl.id,
138
138
  signal: context.runtime.signal
139
139
  }).catch((error) => {
140
- throw branchDatabaseSetupFailedError("Failed to remove stale DIRECT_URL", error, branch.name);
140
+ throw branchDatabaseSetupFailedError("Failed to remove stale DIRECT_URL", error, branch);
141
141
  });
142
142
  return written;
143
143
  }
@@ -148,53 +148,88 @@ async function upsertBranchDatabaseEnvVar(context, provider, options) {
148
148
  value: options.value,
149
149
  signal: context.runtime.signal
150
150
  }).catch((error) => {
151
- throw branchDatabaseSetupFailedError(`Failed to update ${options.key}`, error, options.branchName);
151
+ throw branchDatabaseSetupFailedError(`Failed to update ${options.key}`, error, options.branch);
152
152
  });
153
153
  return;
154
154
  }
155
155
  await provider.createEnvironmentVariable({
156
156
  projectId: options.projectId,
157
- branchId: options.branchId,
158
157
  className: options.className,
159
158
  key: options.key,
160
159
  value: options.value,
160
+ ...options.branchId ? { branchId: options.branchId } : {},
161
161
  signal: context.runtime.signal
162
162
  }).catch((error) => {
163
- throw branchDatabaseSetupFailedError(`Failed to write ${options.key}`, error, options.branchName);
163
+ throw branchDatabaseSetupFailedError(`Failed to write ${options.key}`, error, options.branch);
164
164
  });
165
165
  }
166
- async function inspectBranchDatabaseEnv(provider, projectId, branchId, signal) {
166
+ async function inspectBranchDatabaseEnv(provider, projectId, branch, signal) {
167
+ const scope = envScopeForBranch(branch);
167
168
  const [databaseUrlRows, directUrlRows] = await Promise.all([provider.listEnvironmentVariables({
168
169
  projectId,
169
- className: "preview",
170
+ className: scope.className,
170
171
  key: "DATABASE_URL",
171
172
  signal
172
173
  }), provider.listEnvironmentVariables({
173
174
  projectId,
174
- className: "preview",
175
+ className: scope.className,
175
176
  key: "DIRECT_URL",
176
177
  signal
177
178
  })]);
179
+ const targetBranchId = branch.kind === "preview" ? branch.id : null;
178
180
  return {
179
- branchDatabaseUrl: findEnvVar(databaseUrlRows, { branchId }),
180
- branchDirectUrl: findEnvVar(directUrlRows, { branchId }),
181
- previewDatabaseUrl: findEnvVar(databaseUrlRows, { branchId: null })
181
+ targetDatabaseUrl: findEnvVar(databaseUrlRows, { branchId: targetBranchId }),
182
+ targetDirectUrl: findEnvVar(directUrlRows, { branchId: targetBranchId }),
183
+ inheritedPreviewDatabaseUrl: branch.kind === "preview" ? findEnvVar(databaseUrlRows, { branchId: null }) : null
182
184
  };
183
185
  }
184
186
  function findEnvVar(rows, options) {
185
187
  return rows.find((row) => row.branchId === options.branchId) ?? null;
186
188
  }
187
- function hasInlineDatabaseEnvVars(envVars) {
189
+ function hasProvidedDatabaseEnvVars(envVars) {
188
190
  return Boolean(envVars && ("DATABASE_URL" in envVars || "DIRECT_URL" in envVars));
189
191
  }
190
- function maybeRenderBranchDatabaseSignal(context, branchName, signal, envState) {
192
+ function envScopeForBranch(branch) {
193
+ return branch.kind === "production" ? { className: "production" } : {
194
+ className: "preview",
195
+ branchId: branch.id
196
+ };
197
+ }
198
+ function envScopeLabel(branch) {
199
+ return branch.kind === "production" ? "production" : "branch";
200
+ }
201
+ function getTargetDatabaseEnvVarKeys(envState) {
202
+ return [envState.targetDatabaseUrl, envState.targetDirectUrl].filter((variable) => Boolean(variable)).map((variable) => variable.key).sort();
203
+ }
204
+ function hasExistingDatabaseEnvForTarget(branch, envState) {
205
+ if (branch.kind === "production") return Boolean(envState.targetDatabaseUrl || envState.targetDirectUrl);
206
+ return Boolean(envState.targetDatabaseUrl);
207
+ }
208
+ function existingDatabaseEnvReason(branch) {
209
+ return branch.kind === "production" ? "production-env-exists" : "branch-env-exists";
210
+ }
211
+ function existingDatabaseEnvWarning(branch, envVars) {
212
+ if (branch.kind === "production") return `Production already has ${envVars.join(" and ")}. Treating it as BYO database configuration and leaving env vars unchanged.`;
213
+ return `Branch "${branch.name}" already has DATABASE_URL. Leaving branch database env vars unchanged.`;
214
+ }
215
+ function databasePromptSuppressedWarning(branch) {
216
+ if (branch.kind === "production") return "This app appears to use DATABASE_URL. Run prisma-cli app deploy --db --yes to create and wire a Prisma Postgres database for this first production deploy.";
217
+ return "This app appears to use DATABASE_URL. Run prisma-cli app deploy --db to create an isolated database for this preview branch.";
218
+ }
219
+ function databasePromptMessage(branch) {
220
+ return branch.kind === "production" ? "Create a Prisma Postgres database for production?" : `Create an isolated database for branch "${branch.name}"?`;
221
+ }
222
+ function maybeRenderBranchDatabaseSignal(context, branch, signal, envState) {
191
223
  if (context.flags.json || context.flags.quiet) return;
192
224
  const rows = [
193
225
  signal.schema ? ` Schema ${path.relative(context.runtime.cwd, signal.schema.path) || defaultSchemaSourcePath(signal.schema)}` : null,
194
226
  signal.databaseUrlReferences.length > 0 ? ` Code ${signal.databaseUrlReferences.slice(0, 3).join(", ")}` : null,
195
- envState.previewDatabaseUrl ? " Env preview DATABASE_URL is inherited by this branch" : null
227
+ envState.inheritedPreviewDatabaseUrl ? " Env preview DATABASE_URL is inherited by this branch" : null
196
228
  ].filter((row) => Boolean(row));
197
- context.output.stderr.write(`Database signal found for branch "${branchName}"\n${rows.join("\n")}\n\n`);
229
+ context.output.stderr.write(`Database signal found for ${databaseTargetLabel(branch)}\n${rows.join("\n")}\n\n`);
230
+ }
231
+ function databaseTargetLabel(branch) {
232
+ return branch.kind === "production" ? `production branch "${branch.name}"` : `branch "${branch.name}"`;
198
233
  }
199
234
  function emitBranchDatabaseProgress(context, status, message) {
200
235
  if (context.flags.json || context.flags.quiet) return;
@@ -211,6 +246,12 @@ function emptyBranchDatabaseSetupOutcome() {
211
246
  warnings: []
212
247
  };
213
248
  }
249
+ function productionDatabaseSetupAfterFirstDeployError() {
250
+ return usageError("Database setup is only available during the first production deploy", "The selected production app already has a live deployment.", "Use project env commands to manage production DATABASE_URL, or deploy a preview branch with --db.", ["prisma-cli project env add DATABASE_URL=<value> --role production", "prisma-cli app deploy --branch feature/db --db"], "app");
251
+ }
252
+ function nonInteractiveDatabaseSetupRequiresYesError(branch) {
253
+ return usageError("Database setup requires --yes in non-interactive mode", "The deploy command received --db, but prompts are not available and --yes was not passed.", "Pass --yes together with --db to confirm non-interactive database creation.", [branch.kind === "production" ? "prisma-cli app deploy --prod --db --yes" : `prisma-cli app deploy --branch ${formatCommandArgument(branch.name)} --db --yes`], "app");
254
+ }
214
255
  function formatSchemaSetupCommand(command) {
215
256
  switch (command) {
216
257
  case "migrate-deploy": return "prisma migrate deploy";
@@ -218,46 +259,46 @@ function formatSchemaSetupCommand(command) {
218
259
  case "prisma-next-db-init": return "prisma-next db init";
219
260
  }
220
261
  }
221
- function branchDatabaseSetupFailedError(summary, error, branchName) {
262
+ function branchDatabaseSetupFailedError(summary, error, branch) {
222
263
  return new CliError({
223
264
  code: "BRANCH_DATABASE_SETUP_FAILED",
224
265
  domain: "app",
225
266
  summary,
226
267
  why: error instanceof Error ? error.message : String(error),
227
- fix: "Retry the command, or create the branch database and env vars manually with project env commands.",
268
+ fix: "Retry the command, or create the database and env vars manually with project env commands.",
228
269
  debug: formatDebugDetails(error),
229
- meta: { branch: branchName },
270
+ meta: { branch: branch.name },
230
271
  exitCode: 1,
231
- nextSteps: [`prisma-cli app deploy --branch ${formatCommandArgument(branchName)} --db`, `prisma-cli project env list --branch ${formatCommandArgument(branchName)}`]
272
+ nextSteps: [formatAppDeployWithDbNextStep(branch), formatProjectEnvListNextStep(branch)]
232
273
  });
233
274
  }
234
- async function cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branchName, error) {
235
- const setupError = error instanceof CliError ? error : branchDatabaseSetupFailedError("Branch database setup failed", error, branchName);
236
- emitBranchDatabaseProgress(context, "pending", "Removing branch database after setup failed");
275
+ async function cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch, error) {
276
+ const setupError = error instanceof CliError ? error : branchDatabaseSetupFailedError("Database setup failed", error, branch);
277
+ emitBranchDatabaseProgress(context, "pending", "Removing database after setup failed");
237
278
  try {
238
279
  await provider.deleteBranchDatabase({
239
280
  databaseId: database.id,
240
281
  signal: context.runtime.signal
241
282
  });
242
- emitBranchDatabaseProgress(context, "success", "Removed branch database after setup failed");
283
+ emitBranchDatabaseProgress(context, "success", "Removed database after setup failed");
243
284
  } catch (cleanupError) {
244
- return branchDatabaseCleanupFailedError(setupError, cleanupError, database, branchName);
285
+ return branchDatabaseCleanupFailedError(setupError, cleanupError, database, branch);
245
286
  }
246
287
  return setupError;
247
288
  }
248
- function branchDatabaseCleanupFailedError(setupError, cleanupError, database, branchName) {
289
+ function branchDatabaseCleanupFailedError(setupError, cleanupError, database, branch) {
249
290
  const cleanupWhy = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
250
- const setupWhy = setupError.why ?? "Branch database setup failed.";
291
+ const setupWhy = setupError.why ?? "Database setup failed.";
251
292
  return new CliError({
252
293
  code: setupError.code,
253
294
  domain: setupError.domain,
254
295
  summary: setupError.summary,
255
296
  why: `${setupWhy} Prisma could not delete the created database "${database.name}" (${database.id}): ${cleanupWhy}`,
256
- fix: "Delete the created branch database from Console or contact Prisma support, then rerun deploy with --db.",
297
+ fix: "Delete the created database from Console or contact Prisma support, then rerun deploy with --db.",
257
298
  debug: formatCombinedDebugDetails(setupError, cleanupError),
258
299
  meta: {
259
300
  ...setupError.meta,
260
- branch: branchName,
301
+ branch: branch.name,
261
302
  databaseId: database.id,
262
303
  databaseName: database.name,
263
304
  cleanupFailed: true
@@ -266,7 +307,7 @@ function branchDatabaseCleanupFailedError(setupError, cleanupError, database, br
266
307
  nextSteps: []
267
308
  });
268
309
  }
269
- function schemaSetupFailedError(error, schema, branchName, cwd) {
310
+ function schemaSetupFailedError(error, schema, branch, cwd) {
270
311
  return new CliError({
271
312
  code: "SCHEMA_SETUP_FAILED",
272
313
  domain: "app",
@@ -275,17 +316,26 @@ function schemaSetupFailedError(error, schema, branchName, cwd) {
275
316
  fix: "Fix the Prisma schema or migrations, then rerun deploy with --db.",
276
317
  debug: formatDebugDetails(error),
277
318
  meta: {
278
- branch: branchName,
319
+ branch: branch.name,
279
320
  schemaPath: schema.path,
280
321
  source: schema.kind,
281
322
  command: schema.command
282
323
  },
283
324
  exitCode: 1,
284
- nextSteps: [...formatSchemaSetupNextSteps(schema, cwd), `prisma-cli app deploy --branch ${formatCommandArgument(branchName)} --db`]
325
+ nextSteps: [...formatSchemaSetupNextSteps(schema, cwd), formatAppDeployWithDbNextStep(branch)]
285
326
  });
286
327
  }
287
- function unsupportedBranchDatabaseSchemaError(schema, branchName, context) {
288
- return usageError("Branch database setup is not available for this Prisma schema", `${path.relative(context.runtime.cwd, schema.path) || defaultUnsupportedSchemaSourcePath(schema)} targets ${formatUnsupportedSchemaTarget(schema.target)}, but --db creates Prisma Postgres databases.`, "Use project env commands to provide a database URL for this branch, or switch the Prisma schema source to PostgreSQL before using --db.", [`prisma-cli project env add DATABASE_URL=<value> --branch ${formatCommandArgument(branchName)}`, `prisma-cli app deploy --branch ${formatCommandArgument(branchName)}`], "app");
328
+ function unsupportedBranchDatabaseSchemaError(schema, branch, context) {
329
+ 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
+ }
331
+ function formatAppDeployWithDbNextStep(branch) {
332
+ return `prisma-cli app deploy --branch ${formatCommandArgument(branch.name)} --db`;
333
+ }
334
+ function formatProjectEnvListNextStep(branch) {
335
+ return branch.kind === "production" ? "prisma-cli project env list --role production" : `prisma-cli project env list --branch ${formatCommandArgument(branch.name)}`;
336
+ }
337
+ function formatProjectEnvAddNextStep(branch) {
338
+ 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)}`;
289
339
  }
290
340
  function formatSchemaSetupNextSteps(schema, cwd) {
291
341
  const sourcePath = path.relative(cwd, schema.path) || defaultSchemaSourcePath(schema);
@@ -11,7 +11,7 @@ async function readEnvFileAssignments(cwd, filePath, command) {
11
11
  try {
12
12
  contents = await readFile(resolvedPath, "utf8");
13
13
  } catch (error) {
14
- throw usageError(`Failed to read env file "${filePath}"`, error instanceof Error ? error.message : "The file could not be read.", "Pass a readable dotenv file path.", [`prisma-cli project env ${command} --file .env --role preview`], "app");
14
+ throw usageError(`Failed to read env file "${filePath}"`, error instanceof Error ? error.message : "The file could not be read.", "Pass a readable dotenv file path.", [command === "deploy" ? "prisma-cli app deploy --env .env" : `prisma-cli project env ${command} --file .env --role preview`], "app");
15
15
  }
16
16
  return parseEnvFileContents(contents, filePath, command);
17
17
  }
@@ -63,7 +63,7 @@ function extractParsedKeys(contents) {
63
63
  }
64
64
  function validateEnvFileKey(key, line, filePath, command) {
65
65
  try {
66
- validateKey(key, command);
66
+ validateKey(key, command === "deploy" ? "add" : command);
67
67
  } catch (error) {
68
68
  const reason = error instanceof Error && error.message.length > 0 ? error.message : "Invalid environment variable key.";
69
69
  throw usageError(`Invalid environment variable "${key}" in "${filePath}"`, `Line ${line}: ${reason}`, "Use a valid env-var key and retry the import.", [], "app");
@@ -1,4 +1,6 @@
1
1
  import { usageError } from "../../shell/errors.js";
2
+ import { validateKey } from "./env-config.js";
3
+ import { readEnvFileAssignments } from "./env-file.js";
2
4
  //#region src/lib/app/env-vars.ts
3
5
  function parseEnvAssignments(assignments, options) {
4
6
  const values = assignments ?? [];
@@ -10,15 +12,39 @@ function parseEnvAssignments(assignments, options) {
10
12
  if (separatorIndex === -1) throw usageError("Environment variable assignment must use NAME=VALUE", "A provided --env flag is missing the = separator.", `Pass repeated --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
11
13
  const name = assignment.slice(0, separatorIndex);
12
14
  if (name.length === 0) throw usageError("Environment variable name is required", "A provided --env flag has an empty variable name.", `Pass repeated --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
15
+ validateEnvAssignmentName(name, options.commandName);
13
16
  if (seen.has(name)) throw usageError(`Environment variable "${name}" was provided more than once`, "Each environment variable name may be set only once per command invocation.", `Remove the duplicate "${name}" assignment and rerun prisma-cli app ${options.commandName}.`, [`prisma-cli app ${options.commandName} --env ${name}=value`], "app");
17
+ const value = assignment.slice(separatorIndex + 1);
18
+ if (value.length === 0) throw usageError(`Environment variable "${name}" has an empty value`, `A provided --env flag defines ${name} with no value.`, "Pass a non-empty value, or omit the key from the deploy command.", [`prisma-cli app ${options.commandName} --env ${name}=value`], "app");
14
19
  seen.add(name);
15
- parsed[name] = assignment.slice(separatorIndex + 1);
20
+ parsed[name] = value;
16
21
  }
17
22
  return parsed;
18
23
  }
24
+ async function parseEnvInputs(cwd, inputs, options) {
25
+ const values = inputs ?? [];
26
+ const expandedAssignments = [];
27
+ for (const value of values) {
28
+ if (value.includes("=")) {
29
+ expandedAssignments.push(value);
30
+ continue;
31
+ }
32
+ const fileAssignments = await readEnvFileAssignments(cwd, value, options.commandName);
33
+ expandedAssignments.push(...fileAssignments.map((assignment) => `${assignment.key}=${assignment.value}`));
34
+ }
35
+ return parseEnvAssignments(expandedAssignments, options);
36
+ }
37
+ function validateEnvAssignmentName(name, commandName) {
38
+ try {
39
+ validateKey(name, "add");
40
+ } catch (error) {
41
+ const reason = error instanceof Error && error.message.length > 0 ? error.message : "Invalid environment variable name.";
42
+ throw usageError(`Invalid environment variable "${name}"`, reason, "Use a valid env-var name and retry the deploy.", [`prisma-cli app ${commandName} --env DATABASE_URL=postgresql://example`], "app");
43
+ }
44
+ }
19
45
  function envVarNames(envVars) {
20
46
  if (!envVars) return [];
21
47
  return Object.entries(envVars).filter(([, value]) => value !== null).map(([name]) => name).sort((left, right) => left.localeCompare(right));
22
48
  }
23
49
  //#endregion
24
- export { envVarNames, parseEnvAssignments };
50
+ export { envVarNames, parseEnvInputs };