@@ -113,7 +113,7 @@ export class TemplateService {
113113 // 3. Build the workflow graph from the template, overriding the name
114114 // Templates may lack node positions (stripped during publish to reduce size)
115115 // so we add default positions in a grid layout before schema validation.
116- const graphData : Record < string , unknown > = {
116+ let graphData : Record < string , unknown > = {
117117 ...template . graph ,
118118 name : params . workflowName ,
119119 } ;
@@ -127,6 +127,14 @@ export class TemplateService {
127127 } ) ;
128128 }
129129
130+ if ( params . secretMappings && template . requiredSecrets && template . requiredSecrets . length > 0 ) {
131+ graphData = this . applySecretMappings (
132+ graphData ,
133+ params . secretMappings ,
134+ template . requiredSecrets ,
135+ ) ;
136+ }
137+
130138 // Parse through the WorkflowGraphSchema to ensure it conforms to the
131139 // expected shape (adds defaults for viewport, config, etc.)
132140 const workflowGraph = WorkflowGraphSchema . parse ( graphData ) ;
@@ -166,4 +174,73 @@ export class TemplateService {
166174 async getSubmissions ( userId : string ) {
167175 return await this . templatesRepository . findSubmissionsByUser ( userId ) ;
168176 }
177+
178+ private applySecretMappings (
179+ graph : Record < string , unknown > ,
180+ secretMappings : Record < string , string > ,
181+ requiredSecrets : { name : string ; type : string ; description ?: string ; placeholder ?: string } [ ] ,
182+ ) : Record < string , unknown > {
183+ if ( ! secretMappings || Object . keys ( secretMappings ) . length === 0 ) {
184+ return graph ;
185+ }
186+
187+ const json = JSON . stringify ( graph ) ;
188+
189+ if ( requiredSecrets . length === 1 && secretMappings [ requiredSecrets [ 0 ] . name ] ) {
190+ const replaced = json . replace (
191+ / \{ \{ S E C R E T _ P L A C E H O L D E R \} \} / g,
192+ secretMappings [ requiredSecrets [ 0 ] . name ] ,
193+ ) ;
194+ return JSON . parse ( replaced ) ;
195+ }
196+
197+ const result = JSON . parse ( json ) ;
198+ this . traverseAndApplySecrets ( result , secretMappings ) ;
199+ return result ;
200+ }
201+
202+ private traverseAndApplySecrets ( obj : unknown , secretMappings : Record < string , string > ) : void {
203+ if ( typeof obj !== 'object' || obj === null ) return ;
204+
205+ if ( Array . isArray ( obj ) ) {
206+ for ( const item of obj ) {
207+ this . traverseAndApplySecrets ( item , secretMappings ) ;
208+ }
209+ return ;
210+ }
211+
212+ const record = obj as Record < string , unknown > ;
213+
214+ const secretKeys = [ 'secretId' , 'secret_name' , 'secretName' , 'secretRef' , 'secret_ref' ] ;
215+ for ( const key of secretKeys ) {
216+ if ( record [ key ] === '{{SECRET_PLACEHOLDER}}' ) {
217+ const possibleNameKeys = [ 'label' , 'name' , 'key' , 'displayName' ] ;
218+ for ( const nameKey of possibleNameKeys ) {
219+ const nameValue = record [ nameKey ] ;
220+ if ( typeof nameValue === 'string' && secretMappings [ nameValue ] ) {
221+ record [ key ] = secretMappings [ nameValue ] ;
222+ break ;
223+ }
224+ }
225+
226+ if ( record [ key ] === '{{SECRET_PLACEHOLDER}}' ) {
227+ const firstAvailable = Object . values ( secretMappings ) [ 0 ] ;
228+ if ( firstAvailable ) {
229+ record [ key ] = firstAvailable ;
230+ }
231+ }
232+ }
233+ }
234+
235+ for ( const [ key , value ] of Object . entries ( record ) ) {
236+ if ( typeof value === 'string' && value . includes ( '{{SECRET_PLACEHOLDER}}' ) ) {
237+ const firstAvailable = Object . values ( secretMappings ) [ 0 ] ;
238+ if ( firstAvailable ) {
239+ record [ key ] = value . replace ( / \{ \{ S E C R E T _ P L A C E H O L D E R \} \} / g, firstAvailable ) ;
240+ }
241+ } else if ( typeof value === 'object' && value !== null ) {
242+ this . traverseAndApplySecrets ( value , secretMappings ) ;
243+ }
244+ }
245+ }
169246}
0 commit comments