(cell: JupyterCell, index: number, idGenerator: () => string)
| 201 | } |
| 202 | |
| 203 | function convertCellToBlock(cell: JupyterCell, index: number, idGenerator: () => string): DeepnoteBlock { |
| 204 | let source = Array.isArray(cell.source) ? cell.source.join('') : cell.source |
| 205 | |
| 206 | // Check if this cell has Deepnote metadata (from a previous conversion) |
| 207 | const cellId = cell.metadata?.cell_id as string | undefined |
| 208 | const deepnoteCellType = cell.metadata?.deepnote_cell_type as string | undefined |
| 209 | const sortingKey = cell.metadata?.deepnote_sorting_key as string | undefined |
| 210 | |
| 211 | // Restore snapshot fields from metadata |
| 212 | const contentHash = cell.metadata?.deepnote_content_hash as string | undefined |
| 213 | const executionStartedAt = cell.metadata?.deepnote_execution_started_at as string | undefined |
| 214 | const executionFinishedAt = cell.metadata?.deepnote_execution_finished_at as string | undefined |
| 215 | |
| 216 | // Determine blockGroup: prefer metadata, fall back to top-level field, then generate |
| 217 | // Cloud-exported notebooks may have block_group at top level |
| 218 | const blockGroup = cell.metadata?.deepnote_block_group ?? cell.block_group ?? idGenerator() |
| 219 | |
| 220 | // Restore original content from metadata if available (for lossless roundtrip) |
| 221 | // For plain code blocks, only use deepnote_source if it matches the current source |
| 222 | // (this respects user edits while preserving roundtrip for unchanged content) |
| 223 | // For non-code blocks (sql, text-cell-h2, etc.), always use deepnote_source since |
| 224 | // transformations are applied during export (e.g., "Data Cleaning" → "## Data Cleaning") |
| 225 | const deepnoteSource = cell.metadata?.deepnote_source as string | undefined |
| 226 | const isPlainCodeBlock = cell.cell_type === 'code' && (!deepnoteCellType || deepnoteCellType === 'code') |
| 227 | if (deepnoteSource !== undefined) { |
| 228 | if (isPlainCodeBlock) { |
| 229 | // Plain code blocks: only use deepnote_source if content is unchanged |
| 230 | if (deepnoteSource === source) { |
| 231 | source = deepnoteSource |
| 232 | } |
| 233 | // Otherwise, user has edited - keep the actual source |
| 234 | } else { |
| 235 | // Non-code blocks: always use deepnote_source (content is transformed during export) |
| 236 | source = deepnoteSource |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | const blockType = (deepnoteCellType ?? (cell.cell_type === 'code' ? 'code' : 'markdown')) as DeepnoteBlock['type'] |
| 241 | |
| 242 | // Extract original metadata (exclude Deepnote-specific fields) |
| 243 | const originalMetadata = { ...cell.metadata } |
| 244 | delete originalMetadata.cell_id |
| 245 | delete originalMetadata.deepnote_cell_type |
| 246 | delete originalMetadata.deepnote_block_group |
| 247 | delete originalMetadata.deepnote_sorting_key |
| 248 | delete originalMetadata.deepnote_source |
| 249 | delete originalMetadata.deepnote_content_hash |
| 250 | delete originalMetadata.deepnote_execution_started_at |
| 251 | delete originalMetadata.deepnote_execution_finished_at |
| 252 | // Also remove top-level block_group from metadata to avoid duplication |
| 253 | delete (cell as { block_group?: unknown }).block_group |
| 254 | |
| 255 | // Build block object - order doesn't matter here since we sort alphabetically after parsing |
| 256 | // Only include executionCount and outputs when they have values |
| 257 | const executionCount = cell.execution_count ?? undefined |
| 258 | const hasExecutionCount = executionCount !== undefined |
| 259 | const hasOutputs = cell.cell_type === 'code' && cell.outputs !== undefined |
| 260 |
no test coverage detected