This repository has been archived by the owner on Apr 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
cli.js
executable file
·133 lines (117 loc) · 4.74 KB
/
cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env node
import sade from 'sade'
import archy from 'archy'
import colors from 'colors'
import { extract } from './index.js'
import * as raw from 'multiformats/codecs/raw'
import * as dagPb from '@ipld/dag-pb'
import * as dagCbor from '@ipld/dag-cbor'
import * as dagJson from '@ipld/dag-json'
import { CarBlockIterator, CarWriter, CarIndexedReader } from '@ipld/car'
import { CID } from 'multiformats/cid'
import { decode as blockDecode } from 'multiformats/block'
import { sha256 } from 'multiformats/hashes/sha2'
import { identity } from 'multiformats/hashes/identity'
import { blake2b256 } from '@multiformats/blake2/blake2b'
import { createReadStream, createWriteStream } from 'fs'
import { Readable } from 'stream'
import { exporter } from 'ipfs-unixfs-exporter'
export const Decoders = {
[raw.code]: raw,
[dagPb.code]: dagPb,
[dagCbor.code]: dagCbor,
[dagJson.code]: dagJson
}
export const Hashers = {
[identity.code]: identity,
[sha256.code]: sha256,
[blake2b256.code]: blake2b256
}
const cli = sade('ipfs-car-tools')
cli.command('extract <path> <car>')
.describe('Extract IPFS path blocks from a CAR')
.example('ipfs-car-tools extract bafybeig5uisjbc25pkjwtyq5goocmwr7lz5ln63llrtw4d5s2y7m7nhyeu/path/to/image.png my.car > image.png.car')
.option('-o, --output', 'Output path for CAR')
.action(async (path, car, options) => {
const reader = await CarIndexedReader.fromFile(car)
const blocks = extract(reader, path)
const { writer, out } = CarWriter.create(getRootCidFromPath(path))
const dest = options.output ? createWriteStream(options.output) : process.stdout
Readable.from(out).pipe(dest)
for await (const block of blocks) {
await writer.put(block)
}
await writer.close()
})
cli.command('tree <car>')
.describe('Print a tree with CIDs in a CAR')
.action(async (car) => {
const reader = await CarIndexedReader.fromFile(car)
const roots = await reader.getRoots()
const archyRoot = { label: `${colors.green(roots[0].toString())}`, nodes: [] }
// used to find nodes in the tree
const allNodes = new Map([[roots[0].toString(), archyRoot]])
for await (const block of reader.blocks()) {
const decoder = Decoders[block.cid.code]
if (!decoder) throw new Error(`Missing decoder: 0x${block.cid.code.toString(16)}`)
const hasher = Hashers[block.cid.multihash.code]
if (!hasher) throw new Error(`Missing hasher: 0x${block.cid.multihash.code.toString(16)}`)
const multiformatsBlock = await blockDecode({ bytes: block.bytes, codec: decoder, hasher })
let node = allNodes.get(block.cid.toString())
if (!node) {
const hasCid = await reader.has(block.cid)
const label = hasCid ? `${colors.green(block.cid.toString())}` : `${colors.red(block.cid.toString())}`
const missingNode = { label, nodes: [] }
allNodes.set(block.cid.toString(), missingNode)
node = missingNode
}
for (const [_, linkCid] of multiformatsBlock.links()) {
let target = allNodes.get(linkCid.toString())
if (!target) {
const hasCid = await reader.has(linkCid)
const label = hasCid ? `${colors.green(linkCid.toString())}` : `${colors.red(linkCid.toString())}`
target = { label, nodes: [] }
allNodes.set(linkCid.toString(), target)
}
// @ts-ignore
node.nodes.push(target)
}
}
console.log(archy(archyRoot))
})
cli.command('export <path> <car>')
.describe('Export a UnixFS file from a CAR')
.example('ipfs-car-tools export bafybeig5uisjbc25pkjwtyq5goocmwr7lz5ln63llrtw4d5s2y7m7nhyeu/path/to/image.png image.png.car > image.png')
.option('-o, --output', 'Output path for file')
.action(async (path, car, options) => {
const blocks = (await CarBlockIterator.fromIterable(createReadStream(car)))[Symbol.asyncIterator]()
const blockstore = {
async get (key) {
const { done, value } = await blocks.next()
if (done) throw new Error('unexpected EOF')
if (value.cid.toString() !== key.toString()) {
throw new Error(`CID mismatch, expected: ${key}, received: ${value.cid}`)
}
return value.bytes
}
}
const entry = await exporter(path, blockstore)
if (entry.type === 'directory') throw new Error(`${path} is a directory`)
const dest = options.output ? createWriteStream(options.output) : process.stdout
Readable.from(entry.content()).pipe(dest)
})
cli.parse(process.argv)
function getRootCidFromPath (path) {
if (path.startsWith('/')) {
path = path.slice(1)
}
if (path.endsWith('/')) {
path = path.slice(0, -1)
}
const parts = path.split('/')
const rootCidStr = parts.shift()
if (!rootCidStr) {
throw new Error(`no root cid found in path`)
}
return CID.parse(rootCidStr)
}