| … | … | @@ -0,0 +1,48 @@ |
| 1 | #!/usr/bin/env node |
| 2 | // Extract a bare-union-bundle flat .bundle file into a readable source tree. |
| 3 | // Format: <decimal-len>\n <json-manifest> <binary-tail> |
| 4 | // manifest.files = { "<path>": { offset, length, mode } } indexed into tail. |
| 5 | 'use strict' |
| 6 | const fs = require('fs') |
| 7 | const path = require('path') |
| 8 | |
| 9 | const bundlePath = process.argv[2] |
| 10 | const outDir = process.argv[3] |
| 11 | if (!bundlePath || !outDir) { console.error('usage: extract-bundle.cjs <bundle> <outdir>'); process.exit(1) } |
| 12 | |
| 13 | const buf = fs.readFileSync(bundlePath) |
| 14 | const nl = buf.indexOf(10) |
| 15 | // String-aware brace counter to find the end of the JSON manifest. |
| 16 | let i = nl + 1 |
| 17 | let depth = 0, inStr = false, esc = false, start = -1 |
| 18 | for (; i < buf.length; i++) { |
| 19 | const c = buf[i] |
| 20 | if (start === -1) { if (c === 0x7b) { start = i; depth = 1 } continue } |
| 21 | if (inStr) { |
| 22 | if (esc) esc = false |
| 23 | else if (c === 0x5c) esc = true |
| 24 | else if (c === 0x22) inStr = false |
| 25 | } else { |
| 26 | if (c === 0x22) inStr = true |
| 27 | else if (c === 0x7b) depth++ |
| 28 | else if (c === 0x7d) { depth--; if (depth === 0) { i++; break } } |
| 29 | } |
| 30 | } |
| 31 | const manifest = JSON.parse(buf.slice(start, i).toString('utf8')) |
| 32 | const tailStart = i |
| 33 | const files = manifest.files || {} |
| 34 | const paths = Object.keys(files) |
| 35 | console.log('manifest keys:', Object.keys(manifest).join(',')) |
| 36 | console.log('files:', paths.length, '| tailStart:', tailStart, '| sample:', paths[0], JSON.stringify(files[paths[0]])) |
| 37 | |
| 38 | let written = 0 |
| 39 | for (const p of paths) { |
| 40 | const f = files[p] |
| 41 | if (typeof f.offset !== 'number' || typeof f.length !== 'number') continue |
| 42 | const data = buf.slice(tailStart + f.offset, tailStart + f.offset + f.length) |
| 43 | const dest = path.join(outDir, p) |
| 44 | fs.mkdirSync(path.dirname(dest), { recursive: true }) |
| 45 | fs.writeFileSync(dest, data) |
| 46 | written++ |
| 47 | } |
| 48 | console.log('wrote', written, 'files to', outDir) |