diff --git a/apps/sim/lib/workflows/operations/import-export.ts b/apps/sim/lib/workflows/operations/import-export.ts index fb741f7c4..bf211bdcd 100644 --- a/apps/sim/lib/workflows/operations/import-export.ts +++ b/apps/sim/lib/workflows/operations/import-export.ts @@ -583,7 +583,10 @@ export function parseWorkflowJson( loops: workflowData.loops || {}, parallels: workflowData.parallels || {}, metadata: workflowData.metadata, - variables: Array.isArray(workflowData.variables) ? workflowData.variables : undefined, + variables: + workflowData.variables && typeof workflowData.variables === 'object' + ? workflowData.variables + : undefined, } if (regenerateIdsFlag) { diff --git a/apps/sim/lib/workflows/persistence/duplicate.ts b/apps/sim/lib/workflows/persistence/duplicate.ts index 817f58c9c..329d077e9 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.ts @@ -33,6 +33,44 @@ interface DuplicateWorkflowResult { subflowsCount: number } +/** + * Remaps old variable IDs to new variable IDs inside block subBlocks. + * Specifically targets `variables-input` subblocks whose value is an array + * of variable assignments containing a `variableId` field. + */ +function remapVariableIdsInSubBlocks( + subBlocks: Record, + varIdMap: Map +): Record { + const updated: Record = {} + + for (const [key, subBlock] of Object.entries(subBlocks)) { + if ( + subBlock && + typeof subBlock === 'object' && + subBlock.type === 'variables-input' && + Array.isArray(subBlock.value) + ) { + updated[key] = { + ...subBlock, + value: subBlock.value.map((assignment: any) => { + if (assignment && typeof assignment === 'object' && assignment.variableId) { + const newVarId = varIdMap.get(assignment.variableId) + if (newVarId) { + return { ...assignment, variableId: newVarId } + } + } + return assignment + }), + } + } else { + updated[key] = subBlock + } + } + + return updated +} + /** * Duplicate a workflow with all its blocks, edges, and subflows * This is a shared helper used by both the workflow duplicate API and folder duplicate API @@ -104,6 +142,9 @@ export async function duplicateWorkflow( .where(and(eq(workflow.workspaceId, targetWorkspaceId), folderCondition)) const sortOrder = (minResult?.minOrder ?? 1) - 1 + // Mapping from old variable IDs to new variable IDs (populated during variable duplication) + const varIdMapping = new Map() + // Create the new workflow first (required for foreign key constraints) await tx.insert(workflow).values({ id: newWorkflowId, @@ -123,8 +164,9 @@ export async function duplicateWorkflow( variables: (() => { const sourceVars = (source.variables as Record) || {} const remapped: Record = {} - for (const [, variable] of Object.entries(sourceVars) as [string, Variable][]) { + for (const [oldVarId, variable] of Object.entries(sourceVars) as [string, Variable][]) { const newVarId = crypto.randomUUID() + varIdMapping.set(oldVarId, newVarId) remapped[newVarId] = { ...variable, id: newVarId, @@ -181,6 +223,20 @@ export async function duplicateWorkflow( } } + // Update variable references in subBlocks (e.g. variables-input assignments) + let updatedSubBlocks = block.subBlocks + if ( + varIdMapping.size > 0 && + block.subBlocks && + typeof block.subBlocks === 'object' && + !Array.isArray(block.subBlocks) + ) { + updatedSubBlocks = remapVariableIdsInSubBlocks( + block.subBlocks as Record, + varIdMapping + ) + } + return { ...block, id: newBlockId, @@ -188,6 +244,7 @@ export async function duplicateWorkflow( parentId: newParentId, extent: newExtent, data: updatedData, + subBlocks: updatedSubBlocks, locked: false, // Duplicated blocks should always be unlocked createdAt: now, updatedAt: now, diff --git a/apps/sim/lib/workflows/sanitization/json-sanitizer.ts b/apps/sim/lib/workflows/sanitization/json-sanitizer.ts index 0e452ccd1..7afec494c 100644 --- a/apps/sim/lib/workflows/sanitization/json-sanitizer.ts +++ b/apps/sim/lib/workflows/sanitization/json-sanitizer.ts @@ -57,12 +57,15 @@ export interface ExportWorkflowState { sortOrder?: number exportedAt?: string } - variables?: Array<{ - id: string - name: string - type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'plain' - value: unknown - }> + variables?: Record< + string, + { + id: string + name: string + type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'plain' + value: unknown + } + > } }