(file: SnapshotHashInput)
| 20 | * @returns Hash string in format 'sha256:{hex}' |
| 21 | */ |
| 22 | export function computeSnapshotHash(file: SnapshotHashInput): string { |
| 23 | const parts: string[] = [] |
| 24 | |
| 25 | // Version |
| 26 | parts.push(`version:${file.version}`) |
| 27 | |
| 28 | // Environment hash |
| 29 | if (file.environment?.hash) { |
| 30 | parts.push(`env:${file.environment.hash}`) |
| 31 | } |
| 32 | |
| 33 | // Integrations (sorted for determinism) |
| 34 | const integrations = file.project.integrations ?? [] |
| 35 | const sortedIntegrations = [...integrations].sort((a, b) => a.id.localeCompare(b.id)) |
| 36 | for (const integration of sortedIntegrations) { |
| 37 | parts.push(`integration:${integration.id}:${integration.type}`) |
| 38 | } |
| 39 | |
| 40 | // All block content hashes (preserve notebook and block order) |
| 41 | for (const notebook of file.project.notebooks) { |
| 42 | for (const block of notebook.blocks) { |
| 43 | if (block.contentHash) { |
| 44 | parts.push(`block:${block.id}:${block.contentHash}`) |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | const combined = parts.join('\n') |
| 50 | const hash = createHash('sha256').update(combined, 'utf-8').digest('hex') |
| 51 | return `sha256:${hash}` |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Adds content hashes to all blocks in a DeepnoteFile that don't already have them. |
no outgoing calls
no test coverage detected