@@ -25,13 +25,43 @@ export async function secrets(opts: { branch?: string; output?: string; print?:
2525 info ( ' tip: `insta run -- <cmd>` injects these per-run with nothing written to disk' )
2626}
2727
28- export async function secretsList ( opts : { branch ?: string } ) : Promise < void > {
28+ // Shape of GET /secrets/tree: the whole binding picture — project-wide secrets, then per-branch
29+ // service groupings plus any branch-level (unbound) secrets.
30+ type Tree = {
31+ projectWide : string [ ]
32+ branches : { name : string ; isDefault : boolean ; services : { type : string ; name : string ; secrets : string [ ] } [ ] ; unbound : string [ ] } [ ]
33+ }
34+
35+ // Render one branch's service-grouped secrets, then its unbound (branch-level) secrets.
36+ function renderBranch ( b : Tree [ 'branches' ] [ number ] ) : void {
37+ for ( const s of b . services ) if ( s . secrets . length ) { info ( ` ${ s . type } /${ s . name } ` ) ; for ( const n of s . secrets ) info ( ` ${ n } ` ) }
38+ if ( b . unbound . length ) { info ( ' (branch-level)' ) ; for ( const n of b . unbound ) info ( ` ${ n } ` ) }
39+ }
40+
41+ // Show the full binding tree: project-wide, then every branch grouped by service.
42+ export async function secretsTree ( opts : { json ?: boolean } ) : Promise < void > {
43+ const api = await ApiClient . load ( )
44+ const p = await requireProject ( )
45+ const res = await api . rawRequest ( 'GET' , `/projects/${ p . projectId } /secrets/tree` )
46+ if ( handleApproval ( res ) ) return
47+ const tree : Tree = res . body
48+ if ( opts . json ) return printJson ( tree )
49+ if ( tree . projectWide . length ) { info ( '(project-wide)' ) ; for ( const n of tree . projectWide ) info ( ` ${ n } ` ) }
50+ for ( const b of tree . branches ) { info ( `${ b . name } ${ b . isDefault ? ' *' : '' } ` ) ; renderBranch ( b ) }
51+ }
52+
53+ // List secret names for the current (or given) branch, grouped by service.
54+ export async function secretsList ( opts : { branch ?: string ; json ?: boolean } ) : Promise < void > {
2955 const api = await ApiClient . load ( )
3056 const p = await requireProject ( )
3157 const branch = opts . branch ?? p . branch
32- const res = await api . rawRequest ( 'GET' , `/projects/${ p . projectId } /secrets${ q ( branch ) } ` )
58+ const res = await api . rawRequest ( 'GET' , `/projects/${ p . projectId } /secrets/tree ` )
3359 if ( handleApproval ( res ) ) return
34- for ( const name of Object . keys ( res . body . secrets ) ) info ( name )
60+ const tree : Tree = res . body
61+ const b = tree . branches . find ( ( x ) => x . name === branch )
62+ if ( opts . json ) return printJson ( { projectWide : tree . projectWide , branch : b } )
63+ if ( tree . projectWide . length ) { info ( '(project-wide)' ) ; for ( const n of tree . projectWide ) info ( ` ${ n } ` ) }
64+ if ( b ) { info ( `${ b . name } ` ) ; renderBranch ( b ) }
3565}
3666
3767async function readStdin ( ) : Promise < string > {
@@ -40,17 +70,19 @@ async function readStdin(): Promise<string> {
4070 return data . trim ( )
4171}
4272
43- // Set a user secret. Project-wide by default; --branch scopes it to one branch. Value comes from
44- // the argument, or stdin when omitted (keeps secret values out of shell history).
45- export async function secretsSet ( name : string , value : string | undefined , opts : { branch ?: string } ) : Promise < void > {
73+ // Set a user secret. Project-wide by default; --branch scopes it to one branch. --service binds
74+ // it to a branch service instead, which implies the current branch (binding requires one). Value
75+ // comes from the argument, or stdin when omitted (keeps secret values out of shell history).
76+ export async function secretsSet ( name : string , value : string | undefined , opts : { branch ?: string ; service ?: string } ) : Promise < void > {
4677 const api = await ApiClient . load ( )
4778 const p = await requireProject ( )
4879 const v = value ?? ( await readStdin ( ) )
4980 if ( ! v ) die ( 'value is required (pass as an argument or on stdin)' )
50- const payload : Record < string , string > = opts . branch ? { value : v , branch : opts . branch } : { value : v }
81+ const branch = opts . service ? ( opts . branch ?? p . branch ) : opts . branch
82+ const payload : Record < string , string > = { value : v , ...( branch ? { branch } : { } ) , ...( opts . service ? { service : opts . service } : { } ) }
5183 const res = await api . rawRequest ( 'PUT' , `/projects/${ p . projectId } /secrets/${ encodeURIComponent ( name ) } ` , payload )
5284 if ( handleApproval ( res ) ) return
53- info ( `set ${ name } ( ${ opts . branch ? `branch ${ opts . branch } ` : 'project-wide' } )` )
85+ info ( `set ${ name } ${ opts . service ? ` → ${ opts . service } ` : '' } ( ${ branch ? `branch ${ branch } ` : 'project-wide' } )` )
5486}
5587
5688export async function secretsUnset ( name : string , opts : { branch ?: string } ) : Promise < void > {
0 commit comments