@@ -8,12 +8,14 @@ import {
88 type EnvVarRole ,
99} from "../lib/app/env-config" ;
1010import { requireComputeAuth } from "../lib/auth/guard" ;
11+ import { readLocalGitBranch } from "../lib/git/local-branch" ;
1112import { authRequiredError , CliError , usageError , workspaceRequiredError } from "../shell/errors" ;
1213import type { CommandSuccess } from "../shell/output" ;
1314import type { CommandContext } from "../shell/runtime" ;
1415import { resolveProjectTarget } from "../lib/project/resolution" ;
1516import type {
1617 EnvAddResult ,
18+ EnvListTarget ,
1719 EnvListResult ,
1820 EnvRmResult ,
1921 EnvScopeDescriptor ,
@@ -29,6 +31,21 @@ interface ResolvedScope {
2931 apiTarget : { class : EnvVarRole ; branchId : string | null } ;
3032}
3133
34+ type ResolvedListScope =
35+ | {
36+ kind : "scoped" ;
37+ descriptor : EnvScopeDescriptor ;
38+ target : EnvListTarget ;
39+ apiTarget : { class : EnvVarRole ; branchId : string | null } ;
40+ addScope : EnvScope ;
41+ }
42+ | {
43+ kind : "overview" ;
44+ descriptor : { kind : "overview" } ;
45+ target : EnvListTarget ;
46+ addScope : EnvScope ;
47+ } ;
48+
3249interface EnvCommandFlags {
3350 roleName ?: string ;
3451 branchName ?: string ;
@@ -47,13 +64,10 @@ interface RawEnvironmentVariable {
4764interface RawBranchRecord {
4865 id : string ;
4966 gitName : string ;
67+ role : EnvVarRole ;
5068 isDefault : boolean ;
5169}
5270
53- function defaultRoleScope ( ) : EnvScope {
54- return { kind : "role" , role : "production" } ;
55- }
56-
5771export async function runEnvAdd (
5872 context : CommandContext ,
5973 rawAssignment : string | undefined ,
@@ -204,25 +218,31 @@ export async function runEnvList(
204218 flags : EnvCommandFlags ,
205219) : Promise < CommandSuccess < EnvListResult > > {
206220 const explicit = resolveEnvScope ( flags , { requireExplicit : false , command : "list" } ) ;
207- const scope = explicit ?? defaultRoleScope ( ) ;
208221
209222 const { client, projectId } = await requireClientAndProject ( context , flags . projectRef , "project env list" ) ;
210- const resolved = await resolveScopeToApi ( client , projectId , scope , {
211- createBranchIfMissing : false ,
223+ const resolved = await resolveListScopeToApi ( client , projectId , explicit , {
224+ cwd : context . runtime . cwd ,
212225 signal : context . runtime . signal ,
213226 } ) ;
214- const variables = await listVariables ( client , projectId , resolved , context . runtime . signal ) ;
227+ const variables = resolved . kind === "scoped"
228+ ? await listVariables ( client , projectId , {
229+ scope : resolved . addScope ,
230+ descriptor : resolved . descriptor ,
231+ apiTarget : resolved . apiTarget ,
232+ } , context . runtime . signal )
233+ : await listOverviewVariables ( client , projectId , context . runtime . signal ) ;
215234
216235 return {
217236 command : "project.env.list" ,
218237 result : {
219238 projectId,
220239 scope : resolved . descriptor ,
240+ target : resolved . target ,
221241 variables : variables . map ( ( row ) => toMetadata ( row , resolved . descriptor ) ) ,
222242 } ,
223243 warnings : [ ] ,
224244 nextSteps : variables . length === 0
225- ? [ `prisma-cli project env add KEY=value ${ formatScopeFlag ( scope ) } ` ]
245+ ? [ `prisma-cli project env add KEY=value ${ formatScopeFlag ( resolved . addScope ) } ` ]
226246 : [ ] ,
227247 } ;
228248}
@@ -339,13 +359,13 @@ async function resolveScopeToApi(
339359 ? await resolveOrCreateBranch ( client , projectId , scope . branchName , options . signal )
340360 : await resolveExistingBranch ( client , projectId , scope . branchName , options . signal ) ;
341361
342- if ( branch . isDefault ) {
362+ if ( branch . role === "production" ) {
343363 throw new CliError ( {
344364 code : "ENV_BRANCH_SCOPE_IS_PRODUCTION" ,
345365 domain : "app" ,
346- summary : `Branch "${ scope . branchName } " is the default branch` ,
366+ summary : `Branch "${ scope . branchName } " is the production branch` ,
347367 why : "Production variables are project-level only; branch overrides apply to preview branches." ,
348- fix : "Use --role production for the default branch." ,
368+ fix : "Use --role production for the production branch." ,
349369 exitCode : 1 ,
350370 nextSteps : [ "prisma-cli project env list --role production" ] ,
351371 } ) ;
@@ -362,6 +382,117 @@ async function resolveScopeToApi(
362382 } ;
363383}
364384
385+ async function resolveListScopeToApi (
386+ client : ManagementApiClient ,
387+ projectId : string ,
388+ explicit : EnvScope | undefined ,
389+ options : { cwd : string ; signal : AbortSignal } ,
390+ ) : Promise < ResolvedListScope > {
391+ if ( explicit ) {
392+ const resolved = await resolveScopeToApi ( client , projectId , explicit , {
393+ createBranchIfMissing : false ,
394+ signal : options . signal ,
395+ } ) ;
396+ return {
397+ kind : "scoped" ,
398+ descriptor : resolved . descriptor ,
399+ target : targetFromExplicitScope ( resolved . descriptor ) ,
400+ apiTarget : resolved . apiTarget ,
401+ addScope : resolved . scope ,
402+ } ;
403+ }
404+
405+ const gitBranch = await readLocalGitBranch ( options . cwd , options . signal ) ;
406+ if ( gitBranch ) {
407+ const branch = ( await listBranchesByName ( client , projectId , gitBranch , options . signal ) ) [ 0 ] ;
408+ if ( ! branch ) {
409+ return {
410+ kind : "scoped" ,
411+ descriptor : { kind : "role" , role : "preview" } ,
412+ target : {
413+ source : "local-git" ,
414+ branchName : gitBranch ,
415+ branchExists : false ,
416+ envMap : "preview" ,
417+ } ,
418+ apiTarget : { class : "preview" , branchId : null } ,
419+ addScope : { kind : "branch" , branchName : gitBranch } ,
420+ } ;
421+ }
422+
423+ if ( branch . role === "production" ) {
424+ return {
425+ kind : "scoped" ,
426+ descriptor : { kind : "role" , role : "production" } ,
427+ target : {
428+ source : "local-git" ,
429+ branchName : branch . gitName ,
430+ branchId : branch . id ,
431+ branchRole : branch . role ,
432+ branchExists : true ,
433+ envMap : "production" ,
434+ } ,
435+ apiTarget : { class : "production" , branchId : null } ,
436+ addScope : { kind : "role" , role : "production" } ,
437+ } ;
438+ }
439+
440+ return {
441+ kind : "scoped" ,
442+ descriptor : {
443+ kind : "branch" ,
444+ branchName : branch . gitName ,
445+ branchId : branch . id ,
446+ } ,
447+ target : {
448+ source : "local-git" ,
449+ branchName : branch . gitName ,
450+ branchId : branch . id ,
451+ branchRole : branch . role ,
452+ branchExists : true ,
453+ envMap : "preview" ,
454+ } ,
455+ apiTarget : { class : "preview" , branchId : branch . id } ,
456+ addScope : { kind : "branch" , branchName : branch . gitName } ,
457+ } ;
458+ }
459+
460+ return {
461+ kind : "overview" ,
462+ descriptor : { kind : "overview" } ,
463+ target : {
464+ source : "overview" ,
465+ envMap : "overview" ,
466+ } ,
467+ addScope : { kind : "role" , role : "preview" } ,
468+ } ;
469+ }
470+
471+ function targetFromExplicitScope ( scope : EnvScopeDescriptor ) : EnvListTarget {
472+ if ( scope . kind === "branch" ) {
473+ return {
474+ source : "explicit" ,
475+ branchName : scope . branchName ,
476+ branchId : scope . branchId ,
477+ branchRole : "preview" ,
478+ branchExists : true ,
479+ envMap : "preview" ,
480+ } ;
481+ }
482+
483+ if ( scope . kind === "role" ) {
484+ return {
485+ source : "explicit" ,
486+ envMap : scope . role ,
487+ } ;
488+ }
489+
490+ return {
491+ source : "overview" ,
492+ envMap : "overview" ,
493+ } ;
494+ }
495+
365496function formatScopeFlag ( scope : EnvScope ) : string {
366497 if ( scope . kind === "role" ) {
367498 return `--role ${ scope . role } ` ;
@@ -529,16 +660,49 @@ async function listVariables(
529660 projectId : string ,
530661 resolved : ResolvedScope ,
531662 signal : AbortSignal ,
663+ ) : Promise < RawEnvironmentVariable [ ] > {
664+ const collected = await collectEnvironmentVariables ( client , projectId , signal , {
665+ className : resolved . apiTarget . class ,
666+ filter : ( row ) => rowMatchesScope ( row , resolved ) ,
667+ } ) ;
668+
669+ return materializeEffectiveRows ( collected , resolved ) ;
670+ }
671+
672+ async function listOverviewVariables (
673+ client : ManagementApiClient ,
674+ projectId : string ,
675+ signal : AbortSignal ,
676+ ) : Promise < RawEnvironmentVariable [ ] > {
677+ const collected = await collectEnvironmentVariables ( client , projectId , signal , {
678+ filter : ( row ) =>
679+ row . branchId === null && ( row . class === "production" || row . class === "preview" ) ,
680+ } ) ;
681+
682+ return collected . sort ( ( left , right ) => {
683+ const roleOrder = roleSortOrder ( left . class ) - roleSortOrder ( right . class ) ;
684+ return roleOrder !== 0 ? roleOrder : left . key . localeCompare ( right . key ) ;
685+ } ) ;
686+ }
687+
688+ async function collectEnvironmentVariables (
689+ client : ManagementApiClient ,
690+ projectId : string ,
691+ signal : AbortSignal ,
692+ options : {
693+ className ?: EnvVarRole ;
694+ filter ( row : RawEnvironmentVariable ) : boolean ;
695+ } ,
532696) : Promise < RawEnvironmentVariable [ ] > {
533697 const collected : RawEnvironmentVariable [ ] = [ ] ;
534698 let cursor : string | undefined ;
535699
536700 // eslint-disable-next-line no-constant-condition
537701 while ( true ) {
538- const query : Record < string , string | undefined > = {
539- projectId ,
540- class : resolved . apiTarget . class ,
541- } ;
702+ const query : Record < string , string | undefined > = { projectId } ;
703+ if ( options . className !== undefined ) {
704+ query . class = options . className ;
705+ }
542706 if ( cursor !== undefined ) {
543707 query . cursor = cursor ;
544708 }
@@ -555,9 +719,7 @@ async function listVariables(
555719 ) ;
556720 }
557721
558- const page = ( result . data . data as RawEnvironmentVariable [ ] ) . filter ( ( row ) =>
559- rowMatchesScope ( row , resolved ) ,
560- ) ;
722+ const page = ( result . data . data as RawEnvironmentVariable [ ] ) . filter ( options . filter ) ;
561723 collected . push ( ...page ) ;
562724
563725 if ( ! result . data . pagination . hasMore || ! result . data . pagination . nextCursor ) {
@@ -566,7 +728,11 @@ async function listVariables(
566728 cursor = result . data . pagination . nextCursor ;
567729 }
568730
569- return materializeEffectiveRows ( collected , resolved ) ;
731+ return collected ;
732+ }
733+
734+ function roleSortOrder ( role : EnvVarRole ) : number {
735+ return role === "production" ? 0 : 1 ;
570736}
571737
572738function rowMatchesScope (
@@ -638,6 +804,9 @@ function formatDescriptorLabel(scope: EnvScopeDescriptor): string {
638804 if ( scope . kind === "role" ) {
639805 return scope . role ?? "unknown" ;
640806 }
807+ if ( scope . kind === "overview" ) {
808+ return "overview" ;
809+ }
641810 return `branch:${ scope . branchName ?? scope . branchId ?? "unknown" } ` ;
642811}
643812
0 commit comments