-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathbin.js
executable file
·173 lines (161 loc) · 5.8 KB
/
bin.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env node
import fs from 'fs'
import sade from 'sade'
import Conf from 'conf'
import { CID } from 'multiformats/cid'
import { Block } from 'multiformats/block'
import { Readable } from 'stream'
import { pipeline } from 'stream/promises'
import { CarWriter } from '@ipld/car'
import { TimeoutController } from 'timeout-abort-controller'
import { getLibp2p, fromNetwork } from './p2p.js'
import archy from 'archy'
const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url)).toString())
const TIMEOUT = 10_000
const config = new Conf({
projectName: 'dagula',
projectVersion: pkg.version,
configFileMode: 0o600
})
const cli = sade('dagula')
cli
.version(pkg.version)
.example('get bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy')
cli.command('peer set <addr>')
.describe('Save a peer multiaddr to use for all requests.')
.action(addr => {
config.set('peer', addr)
console.log('🧛 Peer multiaddr saved')
})
cli.command('block get <cid>')
.describe('Fetch a block from the peer.')
.option('-p, --peer', 'Address of peer to fetch data from.')
.option('-t, --timeout', 'Timeout in milliseconds.', TIMEOUT)
.action(async (cid, { peer, timeout }) => {
const controller = new TimeoutController(timeout)
const libp2p = await getLibp2p()
const dagula = await fromNetwork(libp2p, { peer })
try {
const block = await dagula.getBlock(cid, { signal: controller.signal })
process.stdout.write(block.bytes)
} finally {
controller.clear()
await libp2p.stop()
}
})
cli.command('get <cid>')
.describe('Fetch a DAG from the peer. Outputs a CAR file.')
.option('-p, --peer', 'Address of peer to fetch data from.')
.option('-r, --order', 'Specify returned order of blocks in the DAG, "dfs" or "unk"', 'dfs')
.option('-s, --dag-scope', 'Specify the set of blocks of the targeted DAG to get, "all", "entity" or "block"', 'all')
.option('-t, --timeout', 'Timeout in milliseconds.', TIMEOUT)
.action(async (cidPath, { peer, order, 'dag-scope': dagScope, timeout }) => {
const [cidStr] = cidPath.replace(/^\/ipfs\//, '').split('/')
const cid = CID.parse(cidStr)
const controller = new TimeoutController(timeout)
const libp2p = await getLibp2p()
const dagula = await fromNetwork(libp2p, { peer })
const { writer, out } = CarWriter.create(cid)
try {
let error
;(async () => {
try {
for await (const block of dagula.getPath(cidPath, { signal: controller.signal, order, dagScope })) {
controller.reset()
await writer.put(block)
}
} catch (err) {
error = err
} finally {
await writer.close()
}
})()
await pipeline(Readable.from(out), process.stdout)
if (error) throw error
} finally {
controller.clear()
await libp2p.stop()
}
})
cli.command('unixfs get <path>')
.describe('Fetch a UnixFS file from the peer.')
.option('-p, --peer', 'Address of peer to fetch data from.')
.option('-t, --timeout', 'Timeout in milliseconds.', TIMEOUT)
.action(async (path, { peer, timeout }) => {
const controller = new TimeoutController(timeout)
const libp2p = await getLibp2p()
const dagula = await fromNetwork(libp2p, { peer })
try {
const entry = await dagula.getUnixfs(path, { signal: controller.signal })
if (entry.type === 'directory') throw new Error(`${path} is a directory`)
await pipeline(
Readable.from((async function * () {
for await (const chunk of entry.content()) {
controller.reset()
yield chunk
}
})()),
process.stdout
)
} finally {
controller.clear()
await libp2p.stop()
}
})
cli.command('ls <cid>')
.describe('Fetch a DAG from the peer and log the CIDs as blocks arrive')
.option('-p, --peer', 'Address of peer to fetch data from.')
.option('-t, --timeout', 'Timeout in milliseconds.', TIMEOUT)
.action(async (cid, { peer, timeout }) => {
cid = CID.parse(cid)
const controller = new TimeoutController(timeout)
const libp2p = await getLibp2p()
const dagula = await fromNetwork(libp2p, { peer })
try {
for await (const block of dagula.get(cid, { signal: controller.signal })) {
controller.reset()
console.log(block.cid.toString())
}
} finally {
controller.clear()
await libp2p.stop()
}
})
cli.command('tree <cid>')
.describe('Fetch a DAG from the peer then print the CIDs as a tree')
.option('-p, --peer', 'Address of peer to fetch data from.')
.option('-t, --timeout', 'Timeout in milliseconds.', TIMEOUT)
.action(async (cid, { peer, timeout }) => {
cid = CID.parse(cid)
const controller = new TimeoutController(timeout)
const libp2p = await getLibp2p()
const dagula = await fromNetwork(libp2p, { peer })
// build up the tree, starting with the root
/** @type {archy.Data} */
const root = { label: cid.toString(), nodes: [] }
// used to find nodes in the tree
const allNodes = new Map([[root.label, root]])
try {
for await (const block of dagula.get(cid, { signal: controller.signal })) {
controller.reset()
const node = allNodes.get(block.cid.toString())
if (!node) throw new Error('missing node in tree')
if (block instanceof Block) {
for (const [, linkCid] of block.links()) {
let target = allNodes.get(linkCid.toString())
if (!target) {
target = { label: linkCid.toString(), nodes: [] }
allNodes.set(target.label, target)
}
node.nodes = node.nodes || []
node.nodes.push(target)
}
}
}
console.log(archy(root))
} finally {
controller.clear()
await libp2p.stop()
}
})
cli.parse(process.argv)