@prisma/cli 3.0.0-dev.56.1 → 3.0.0-dev.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,7 @@ Useful next commands:
57
57
  npx prisma-cli app logs
58
58
  npx prisma-cli app open
59
59
  npx prisma-cli project env add DATABASE_URL=postgresql://example --role preview
60
+ npx prisma-cli project env list
60
61
  npx prisma-cli project env list --role preview
61
62
  ```
62
63
 
@@ -1,16 +1,11 @@
1
1
  import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
2
  import { requireComputeAuth } from "../lib/auth/guard.js";
3
3
  import { resolveProjectTarget } from "../lib/project/resolution.js";
4
+ import { readLocalGitBranch } from "../lib/git/local-branch.js";
4
5
  import { requireAuthenticatedAuthState } from "./auth.js";
5
6
  import { listRealWorkspaceProjects } from "./project.js";
6
7
  import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
7
8
  //#region src/controllers/app-env.ts
8
- function defaultRoleScope() {
9
- return {
10
- kind: "role",
11
- role: "production"
12
- };
13
- }
14
9
  async function runEnvAdd(context, rawAssignment, flags) {
15
10
  const { key, value } = parseKeyValuePositional(rawAssignment, "add", context.runtime.env);
16
11
  const scope = resolveEnvScope(flags, {
@@ -108,25 +103,30 @@ async function runEnvUpdate(context, rawAssignment, flags) {
108
103
  };
109
104
  }
110
105
  async function runEnvList(context, flags) {
111
- const scope = resolveEnvScope(flags, {
106
+ const explicit = resolveEnvScope(flags, {
112
107
  requireExplicit: false,
113
108
  command: "list"
114
- }) ?? defaultRoleScope();
109
+ });
115
110
  const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
116
- const resolved = await resolveScopeToApi(client, projectId, scope, {
117
- createBranchIfMissing: false,
111
+ const resolved = await resolveListScopeToApi(client, projectId, explicit, {
112
+ cwd: context.runtime.cwd,
118
113
  signal: context.runtime.signal
119
114
  });
120
- const variables = await listVariables(client, projectId, resolved, context.runtime.signal);
115
+ const variables = resolved.kind === "scoped" ? await listVariables(client, projectId, {
116
+ scope: resolved.addScope,
117
+ descriptor: resolved.descriptor,
118
+ apiTarget: resolved.apiTarget
119
+ }, context.runtime.signal) : await listOverviewVariables(client, projectId, context.runtime.signal);
121
120
  return {
122
121
  command: "project.env.list",
123
122
  result: {
124
123
  projectId,
125
124
  scope: resolved.descriptor,
125
+ target: resolved.target,
126
126
  variables: variables.map((row) => toMetadata(row, resolved.descriptor))
127
127
  },
128
128
  warnings: [],
129
- nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(scope)}`] : []
129
+ nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(resolved.addScope)}`] : []
130
130
  };
131
131
  }
132
132
  async function runEnvRemove(context, key, flags) {
@@ -196,12 +196,12 @@ async function resolveScopeToApi(client, projectId, scope, options) {
196
196
  }
197
197
  };
198
198
  const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName, options.signal) : await resolveExistingBranch(client, projectId, scope.branchName, options.signal);
199
- if (branch.isDefault) throw new CliError({
199
+ if (branch.role === "production") throw new CliError({
200
200
  code: "ENV_BRANCH_SCOPE_IS_PRODUCTION",
201
201
  domain: "app",
202
- summary: `Branch "${scope.branchName}" is the default branch`,
202
+ summary: `Branch "${scope.branchName}" is the production branch`,
203
203
  why: "Production variables are project-level only; branch overrides apply to preview branches.",
204
- fix: "Use --role production for the default branch.",
204
+ fix: "Use --role production for the production branch.",
205
205
  exitCode: 1,
206
206
  nextSteps: ["prisma-cli project env list --role production"]
207
207
  });
@@ -218,6 +218,123 @@ async function resolveScopeToApi(client, projectId, scope, options) {
218
218
  }
219
219
  };
220
220
  }
221
+ async function resolveListScopeToApi(client, projectId, explicit, options) {
222
+ if (explicit) {
223
+ const resolved = await resolveScopeToApi(client, projectId, explicit, {
224
+ createBranchIfMissing: false,
225
+ signal: options.signal
226
+ });
227
+ return {
228
+ kind: "scoped",
229
+ descriptor: resolved.descriptor,
230
+ target: targetFromExplicitScope(resolved.descriptor),
231
+ apiTarget: resolved.apiTarget,
232
+ addScope: resolved.scope
233
+ };
234
+ }
235
+ const gitBranch = await readLocalGitBranch(options.cwd, options.signal);
236
+ if (gitBranch) {
237
+ const branch = (await listBranchesByName(client, projectId, gitBranch, options.signal))[0];
238
+ if (!branch) return {
239
+ kind: "scoped",
240
+ descriptor: {
241
+ kind: "role",
242
+ role: "preview"
243
+ },
244
+ target: {
245
+ source: "local-git",
246
+ branchName: gitBranch,
247
+ branchExists: false,
248
+ envMap: "preview"
249
+ },
250
+ apiTarget: {
251
+ class: "preview",
252
+ branchId: null
253
+ },
254
+ addScope: {
255
+ kind: "branch",
256
+ branchName: gitBranch
257
+ }
258
+ };
259
+ if (branch.role === "production") return {
260
+ kind: "scoped",
261
+ descriptor: {
262
+ kind: "role",
263
+ role: "production"
264
+ },
265
+ target: {
266
+ source: "local-git",
267
+ branchName: branch.gitName,
268
+ branchId: branch.id,
269
+ branchRole: branch.role,
270
+ branchExists: true,
271
+ envMap: "production"
272
+ },
273
+ apiTarget: {
274
+ class: "production",
275
+ branchId: null
276
+ },
277
+ addScope: {
278
+ kind: "role",
279
+ role: "production"
280
+ }
281
+ };
282
+ return {
283
+ kind: "scoped",
284
+ descriptor: {
285
+ kind: "branch",
286
+ branchName: branch.gitName,
287
+ branchId: branch.id
288
+ },
289
+ target: {
290
+ source: "local-git",
291
+ branchName: branch.gitName,
292
+ branchId: branch.id,
293
+ branchRole: branch.role,
294
+ branchExists: true,
295
+ envMap: "preview"
296
+ },
297
+ apiTarget: {
298
+ class: "preview",
299
+ branchId: branch.id
300
+ },
301
+ addScope: {
302
+ kind: "branch",
303
+ branchName: branch.gitName
304
+ }
305
+ };
306
+ }
307
+ return {
308
+ kind: "overview",
309
+ descriptor: { kind: "overview" },
310
+ target: {
311
+ source: "overview",
312
+ envMap: "overview"
313
+ },
314
+ addScope: {
315
+ kind: "role",
316
+ role: "preview"
317
+ }
318
+ };
319
+ }
320
+ function targetFromExplicitScope(scope) {
321
+ if (scope.kind === "branch") return {
322
+ source: "explicit",
323
+ branchName: scope.branchName,
324
+ branchId: scope.branchId,
325
+ branchRole: "preview",
326
+ branchExists: true,
327
+ envMap: "preview"
328
+ };
329
+ if (scope.kind === "role") return {
330
+ source: "explicit",
331
+ envMap: scope.role
332
+ };
333
+ return {
334
+ source: "overview",
335
+ envMap: "overview"
336
+ };
337
+ }
221
338
  function formatScopeFlag(scope) {
222
339
  if (scope.kind === "role") return `--role ${scope.role}`;
223
340
  return `--branch ${scope.branchName}`;
@@ -306,25 +423,38 @@ async function findVariableByNaturalKey(client, projectId, key, resolved, signal
306
423
  return data.data.filter((row) => rowMatchesExactScope(row, resolved))[0] ?? null;
307
424
  }
308
425
  async function listVariables(client, projectId, resolved, signal) {
426
+ return materializeEffectiveRows(await collectEnvironmentVariables(client, projectId, signal, {
427
+ className: resolved.apiTarget.class,
428
+ filter: (row) => rowMatchesScope(row, resolved)
429
+ }), resolved);
430
+ }
431
+ async function listOverviewVariables(client, projectId, signal) {
432
+ return (await collectEnvironmentVariables(client, projectId, signal, { filter: (row) => row.branchId === null && (row.class === "production" || row.class === "preview") })).sort((left, right) => {
433
+ const roleOrder = roleSortOrder(left.class) - roleSortOrder(right.class);
434
+ return roleOrder !== 0 ? roleOrder : left.key.localeCompare(right.key);
435
+ });
436
+ }
437
+ async function collectEnvironmentVariables(client, projectId, signal, options) {
309
438
  const collected = [];
310
439
  let cursor;
311
440
  while (true) {
312
- const query = {
313
- projectId,
314
- class: resolved.apiTarget.class
315
- };
441
+ const query = { projectId };
442
+ if (options.className !== void 0) query.class = options.className;
316
443
  if (cursor !== void 0) query.cursor = cursor;
317
444
  const result = await client.GET("/v1/environment-variables", {
318
445
  params: { query },
319
446
  signal
320
447
  });
321
448
  if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
322
- const page = result.data.data.filter((row) => rowMatchesScope(row, resolved));
449
+ const page = result.data.data.filter(options.filter);
323
450
  collected.push(...page);
324
451
  if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
325
452
  cursor = result.data.pagination.nextCursor;
326
453
  }
327
- return materializeEffectiveRows(collected, resolved);
454
+ return collected;
455
+ }
456
+ function roleSortOrder(role) {
457
+ return role === "production" ? 0 : 1;
328
458
  }
329
459
  function rowMatchesScope(row, resolved) {
330
460
  if (row.class !== resolved.apiTarget.class) return false;
@@ -357,6 +487,7 @@ function toMetadata(row, requestedScope) {
357
487
  }
358
488
  function formatDescriptorLabel(scope) {
359
489
  if (scope.kind === "role") return scope.role ?? "unknown";
490
+ if (scope.kind === "overview") return "overview";
360
491
  return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
361
492
  }
362
493
  function apiCallError(summary, response, error) {
@@ -16,6 +16,7 @@ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../l
16
16
  import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
17
17
  import { bindProjectToDirectory, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
18
18
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
19
+ import { readLocalGitBranch } from "../lib/git/local-branch.js";
19
20
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
20
21
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
21
22
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
@@ -1481,42 +1482,6 @@ async function resolveDeployBranch(context, explicitBranchName) {
1481
1482
  annotation: "default"
1482
1483
  };
1483
1484
  }
1484
- async function readLocalGitBranch(cwd, signal) {
1485
- const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
1486
- if (!headPath) return null;
1487
- try {
1488
- const head = (await readFile(headPath, {
1489
- encoding: "utf8",
1490
- signal
1491
- })).trim();
1492
- if (head.startsWith("ref: refs/heads/")) return head.slice(16);
1493
- } catch (error) {
1494
- if (signal.aborted) throw error;
1495
- return null;
1496
- }
1497
- return null;
1498
- }
1499
- async function resolveGitHeadPath(gitPath, signal) {
1500
- signal.throwIfAborted();
1501
- try {
1502
- const raw = await readFile(gitPath, {
1503
- encoding: "utf8",
1504
- signal
1505
- });
1506
- if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
1507
- } catch (error) {
1508
- if (signal.aborted) throw error;
1509
- }
1510
- signal.throwIfAborted();
1511
- try {
1512
- await access(path.join(gitPath, "HEAD"));
1513
- signal.throwIfAborted();
1514
- return path.join(gitPath, "HEAD");
1515
- } catch (error) {
1516
- if (signal.aborted) throw error;
1517
- return null;
1518
- }
1519
- }
1520
1485
  async function resolveDeployFramework(context, options) {
1521
1486
  if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
1522
1487
  if (options.entrypoint) return {
@@ -0,0 +1,41 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ //#region src/lib/git/local-branch.ts
4
+ async function readLocalGitBranch(cwd, signal) {
5
+ const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
6
+ if (!headPath) return null;
7
+ try {
8
+ const head = (await readFile(headPath, {
9
+ encoding: "utf8",
10
+ signal
11
+ })).trim();
12
+ if (head.startsWith("ref: refs/heads/")) return head.slice(16);
13
+ } catch (error) {
14
+ if (signal.aborted) throw error;
15
+ return null;
16
+ }
17
+ return null;
18
+ }
19
+ async function resolveGitHeadPath(gitPath, signal) {
20
+ signal.throwIfAborted();
21
+ try {
22
+ const raw = await readFile(gitPath, {
23
+ encoding: "utf8",
24
+ signal
25
+ });
26
+ if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
27
+ } catch (error) {
28
+ if (signal.aborted) throw error;
29
+ }
30
+ signal.throwIfAborted();
31
+ try {
32
+ await access(path.join(gitPath, "HEAD"));
33
+ signal.throwIfAborted();
34
+ return path.join(gitPath, "HEAD");
35
+ } catch (error) {
36
+ if (signal.aborted) throw error;
37
+ return null;
38
+ }
39
+ }
40
+ //#endregion
41
+ export { readLocalGitBranch };
@@ -2,8 +2,18 @@ import { renderList, renderShow, serializeList } from "../output/patterns.js";
2
2
  //#region src/presenters/app-env.ts
3
3
  function scopeLabel(scope) {
4
4
  if (scope.kind === "role") return scope.role ?? "unknown";
5
+ if (scope.kind === "overview") return "overview";
5
6
  return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
6
7
  }
8
+ function listTargetLabel(result) {
9
+ const target = result.target;
10
+ if (target.source === "overview") return "overview";
11
+ if (target.branchName) {
12
+ const suffix = target.branchExists === false ? " (not created yet)" : "";
13
+ return `branch:${target.branchName} -> ${target.envMap}${suffix}`;
14
+ }
15
+ return scopeLabel(result.scope);
16
+ }
7
17
  function renderEnvAdd(context, descriptor, result) {
8
18
  return renderShow({
9
19
  title: "Setting a new environment variable.",
@@ -75,8 +85,8 @@ function renderEnvList(context, descriptor, result) {
75
85
  title: "Listing environment variables for the selected scope.",
76
86
  descriptor,
77
87
  parentContext: {
78
- key: "scope",
79
- value: scopeLabel(result.scope)
88
+ key: "target",
89
+ value: listTargetLabel(result)
80
90
  },
81
91
  items: result.variables.map((variable) => ({
82
92
  noun: "variable",
@@ -91,8 +101,9 @@ function serializeEnvList(result) {
91
101
  return {
92
102
  projectId: result.projectId,
93
103
  scope: result.scope,
104
+ target: result.target,
94
105
  ...serializeList({
95
- context: { scope: scopeLabel(result.scope) },
106
+ context: { target: listTargetLabel(result) },
96
107
  items: result.variables.map((variable) => ({
97
108
  noun: "variable",
98
109
  label: `${variable.key} (${variable.source})`,
@@ -379,7 +379,7 @@ const DESCRIPTORS = [
379
379
  ],
380
380
  description: "Manage environment variables for the active project",
381
381
  examples: [
382
- "prisma-cli project env list --role production",
382
+ "prisma-cli project env list",
383
383
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
384
384
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
385
385
  "prisma-cli project env remove STRIPE_KEY --role preview"
@@ -426,6 +426,7 @@ const DESCRIPTORS = [
426
426
  ],
427
427
  description: "List environment variable metadata for a scope (no values).",
428
428
  examples: [
429
+ "prisma-cli project env list",
429
430
  "prisma-cli project env list --role production",
430
431
  "prisma-cli project env list --role preview",
431
432
  "prisma-cli project env list --branch feature/foo"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "3.0.0-dev.56.1",
3
+ "version": "3.0.0-dev.57.1",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {