@@ -213,6 +213,37 @@ type RawKeyRef = {
213213 userId : string
214214}
215215
216+ type EnvLookup = {
217+ wsEnvVars : Record < string , string >
218+ personalEnvCache : Map < string , Record < string , string > >
219+ }
220+
221+ async function resolveKey (
222+ ref : RawKeyRef ,
223+ context : string ,
224+ env : EnvLookup
225+ ) : Promise < { key : string | null ; envVarFailed : boolean } > {
226+ if ( ! isEnvVarReference ( ref . rawValue ) ) return { key : ref . rawValue , envVarFailed : false }
227+
228+ const varName = extractEnvVarName ( ref . rawValue )
229+ if ( ! varName ) return { key : null , envVarFailed : true }
230+
231+ const personalVars = env . personalEnvCache . get ( ref . userId )
232+ const encryptedValue = env . wsEnvVars [ varName ] ?? personalVars ?. [ varName ]
233+ if ( ! encryptedValue ) {
234+ console . warn ( ` [WARN] Env var "${ varName } " not found (${ context } )` )
235+ return { key : null , envVarFailed : true }
236+ }
237+
238+ try {
239+ const decrypted = await decryptSecret ( encryptedValue )
240+ return { key : decrypted , envVarFailed : false }
241+ } catch ( error ) {
242+ console . warn ( ` [WARN] Failed to decrypt env var "${ varName } " (${ context } ): ${ error } ` )
243+ return { key : null , envVarFailed : true }
244+ }
245+ }
246+
216247// ---------- Main ----------
217248async function run ( ) {
218249 console . log ( `Mode: ${ DRY_RUN ? 'DRY RUN (audit + preview)' : 'LIVE' } ` )
@@ -232,72 +263,69 @@ async function run() {
232263 }
233264
234265 try {
235- // 1. Find all blocks that match our mapped types or contain nested tools
266+ // 1. Get distinct workspace IDs that have matching blocks
236267 const mappedBlockTypes = Object . keys ( BLOCK_TYPE_TO_PROVIDER )
237268 const agentTypes = Object . keys ( TOOL_INPUT_SUBBLOCK_IDS )
238269 const allBlockTypes = [ ...new Set ( [ ...mappedBlockTypes , ...agentTypes ] ) ]
239270
240- const rows = await db
241- . select ( {
242- blockId : workflowBlocks . id ,
243- blockName : workflowBlocks . name ,
244- blockType : workflowBlocks . type ,
245- subBlocks : workflowBlocks . subBlocks ,
246- workflowId : workflow . id ,
247- workflowName : workflow . name ,
248- userId : workflow . userId ,
249- workspaceId : workflow . workspaceId ,
250- } )
271+ const workspaceIdRows = await db
272+ . selectDistinct ( { workspaceId : workflow . workspaceId } )
251273 . from ( workflowBlocks )
252274 . innerJoin ( workflow , eq ( workflowBlocks . workflowId , workflow . id ) )
253275 . where (
254- sql `${ workflowBlocks . type } IN (${ sql . join (
276+ sql `${ workflow . workspaceId } IS NOT NULL AND ${ workflowBlocks . type } IN (${ sql . join (
255277 allBlockTypes . map ( ( t ) => sql `${ t } ` ) ,
256278 sql `, `
257279 ) } )`
258280 )
259281
260- // Group rows by workspace
261- const workspaceRows = new Map < string , typeof rows > ( )
262- let skippedNoWorkspace = 0
263- for ( const row of rows ) {
264- if ( ! row . workspaceId ) {
265- skippedNoWorkspace ++
266- continue
267- }
268- if ( ! workspaceRows . has ( row . workspaceId ) ) workspaceRows . set ( row . workspaceId , [ ] )
269- workspaceRows . get ( row . workspaceId ) ! . push ( row )
270- }
271-
272- console . log ( `Found ${ rows . length } candidate blocks across ${ workspaceRows . size } workspaces` )
273- if ( skippedNoWorkspace > 0 ) console . log ( `Skipped ${ skippedNoWorkspace } blocks with no workspace` )
274- console . log ( )
282+ const workspaceIds = workspaceIdRows
283+ . map ( ( r ) => r . workspaceId )
284+ . filter ( ( id ) : id is string => id !== null )
285+
286+ console . log ( `Found ${ workspaceIds . length } workspaces with candidate blocks\n` )
287+
288+ // 2. Process one workspace at a time
289+ for ( const workspaceId of workspaceIds ) {
290+ const blocks = await db
291+ . select ( {
292+ blockId : workflowBlocks . id ,
293+ blockName : workflowBlocks . name ,
294+ blockType : workflowBlocks . type ,
295+ subBlocks : workflowBlocks . subBlocks ,
296+ workflowId : workflow . id ,
297+ workflowName : workflow . name ,
298+ userId : workflow . userId ,
299+ } )
300+ . from ( workflowBlocks )
301+ . innerJoin ( workflow , eq ( workflowBlocks . workflowId , workflow . id ) )
302+ . where (
303+ sql `${ workflow . workspaceId } = ${ workspaceId } AND ${ workflowBlocks . type } IN (${ sql . join (
304+ allBlockTypes . map ( ( t ) => sql `${ t } ` ) ,
305+ sql `, `
306+ ) } )`
307+ )
275308
276- // 2. Iterate per workspace
277- for ( const [ workspaceId , blocks ] of workspaceRows ) {
278309 console . log ( `[Workspace ${ workspaceId } ] ${ blocks . length } blocks` )
279310
280311 // 2a. Extract all raw key references grouped by provider
281312 const providerKeys = new Map < string , RawKeyRef [ ] > ( )
282313
283- function addRef ( providerId : string , ref : RawKeyRef ) {
284- if ( ! providerKeys . has ( providerId ) ) providerKeys . set ( providerId , [ ] )
285- providerKeys . get ( providerId ) ! . push ( ref )
286- }
287-
288314 for ( const block of blocks ) {
289315 const subBlocks = block . subBlocks as Record < string , { value ?: any } >
290316
291317 const providerId = BLOCK_TYPE_TO_PROVIDER [ block . blockType ]
292318 if ( providerId ) {
293319 const val = subBlocks ?. apiKey ?. value
294320 if ( typeof val === 'string' && val . trim ( ) ) {
295- addRef ( providerId , {
321+ const refs = providerKeys . get ( providerId ) ?? [ ]
322+ refs . push ( {
296323 rawValue : val ,
297324 blockName : block . blockName ,
298325 workflowName : block . workflowName ,
299326 userId : block . userId ,
300327 } )
328+ providerKeys . set ( providerId , refs )
301329 }
302330 }
303331
@@ -310,12 +338,14 @@ async function run() {
310338 if ( ! toolType || ! toolApiKey || ! toolApiKey . trim ( ) ) continue
311339 const toolProviderId = BLOCK_TYPE_TO_PROVIDER [ toolType ]
312340 if ( ! toolProviderId ) continue
313- addRef ( toolProviderId , {
341+ const refs = providerKeys . get ( toolProviderId ) ?? [ ]
342+ refs . push ( {
314343 rawValue : toolApiKey ,
315344 blockName : `${ block . blockName } > tool "${ tool . title || toolType } "` ,
316345 workflowName : block . workflowName ,
317346 userId : block . userId ,
318347 } )
348+ providerKeys . set ( toolProviderId , refs )
319349 }
320350 }
321351 }
@@ -361,34 +391,7 @@ async function run() {
361391 }
362392 }
363393
364- async function resolveKey (
365- ref : RawKeyRef ,
366- context : string
367- ) : Promise < string | null > {
368- if ( ! isEnvVarReference ( ref . rawValue ) ) return ref . rawValue
369-
370- const varName = extractEnvVarName ( ref . rawValue )
371- if ( ! varName ) {
372- stats . envVarFailures ++
373- return null
374- }
375-
376- const personalVars = personalEnvCache . get ( ref . userId )
377- const encryptedValue = wsEnvVars [ varName ] ?? personalVars ?. [ varName ]
378- if ( ! encryptedValue ) {
379- console . warn ( ` [WARN] Env var "${ varName } " not found (${ context } )` )
380- stats . envVarFailures ++
381- return null
382- }
383-
384- try {
385- return await decryptSecret ( encryptedValue )
386- } catch ( error ) {
387- console . warn ( ` [WARN] Failed to decrypt env var "${ varName } " (${ context } ): ${ error } ` )
388- stats . envVarFailures ++
389- return null
390- }
391- }
394+ const envLookup : EnvLookup = { wsEnvVars, personalEnvCache }
392395
393396 // 2c. For each provider, detect conflicts then resolve and insert
394397 stats . workspacesProcessed ++
@@ -397,7 +400,9 @@ async function run() {
397400 // Resolve all keys for this provider to check for conflicts
398401 const resolved : { ref : RawKeyRef ; key : string } [ ] = [ ]
399402 for ( const ref of refs ) {
400- const key = await resolveKey ( ref , `"${ ref . blockName } " in "${ ref . workflowName } "` )
403+ const context = `"${ ref . blockName } " in "${ ref . workflowName } "`
404+ const { key, envVarFailed } = await resolveKey ( ref , context , envLookup )
405+ if ( envVarFailed ) stats . envVarFailures ++
401406 if ( key ?. trim ( ) ) resolved . push ( { ref, key } )
402407 }
403408
0 commit comments