( jsonContent: string, regenerateIdsFlag = true )
| 492 | * Handles both new format (with version/exportedAt/state) and legacy format (blocks/edges at root). |
| 493 | */ |
| 494 | export function parseWorkflowJson( |
| 495 | jsonContent: string, |
| 496 | regenerateIdsFlag = true |
| 497 | ): { |
| 498 | data: WorkflowState | null |
| 499 | errors: string[] |
| 500 | } { |
| 501 | const errors: string[] = [] |
| 502 | |
| 503 | try { |
| 504 | // Parse JSON content |
| 505 | let data: any |
| 506 | try { |
| 507 | data = JSON.parse(jsonContent) |
| 508 | } catch (parseError) { |
| 509 | errors.push(`Invalid JSON: ${getErrorMessage(parseError, 'Parse error')}`) |
| 510 | return { data: null, errors } |
| 511 | } |
| 512 | |
| 513 | // Validate top-level structure |
| 514 | if (!data || typeof data !== 'object') { |
| 515 | errors.push('Invalid JSON: Root must be an object') |
| 516 | return { data: null, errors } |
| 517 | } |
| 518 | |
| 519 | data = unwrapWorkflowExportEnvelope(data) |
| 520 | |
| 521 | // Handle new export format (version/exportedAt/state) or old format (blocks/edges at root) |
| 522 | let workflowData: any |
| 523 | if (isRecordLike(data.state)) { |
| 524 | // Export/API envelope format with workflow state nested under `state` |
| 525 | logger.info('Parsing workflow JSON with version', { |
| 526 | version: data.version, |
| 527 | exportedAt: data.exportedAt, |
| 528 | }) |
| 529 | workflowData = data.state |
| 530 | } else { |
| 531 | // Old format - blocks/edges at root level |
| 532 | logger.info('Parsing legacy workflow JSON format') |
| 533 | workflowData = data |
| 534 | } |
| 535 | |
| 536 | // Validate required fields |
| 537 | if (!workflowData.blocks || typeof workflowData.blocks !== 'object') { |
| 538 | errors.push('Missing or invalid field: blocks') |
| 539 | return { data: null, errors } |
| 540 | } |
| 541 | |
| 542 | if (!Array.isArray(workflowData.edges)) { |
| 543 | errors.push('Missing or invalid field: edges (must be an array)') |
| 544 | return { data: null, errors } |
| 545 | } |
| 546 | |
| 547 | // Validate blocks have required fields |
| 548 | Object.entries(workflowData.blocks).forEach(([blockId, block]: [string, any]) => { |
| 549 | if (!block || typeof block !== 'object') { |
| 550 | errors.push(`Invalid block ${blockId}: must be an object`) |
| 551 | return |
no test coverage detected