OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-deploy commits / 56e3c90

Initial hyper-deploy — P2P release distribution module
Content-addressed file distribution over Hyperswarm + Protomux. Proven at 1.30 GB / 5.9 MB/s (tabertron → stortron, 6,872 files). Features: - DeployServer: index directory, announce on DHT, serve to peers - DeployClient: connect, diff manifest (file-level dedup), pull what's new - Backpressure via Protomux ondrain - Hash verification on received files (opt-out with verify: false) - Connection timeout, error handling, session tracking - Path traversal protection on client - Web UI: status page with SSE live updates - CLI: serve, pull, info, --dry-run, --no-verify Protocol v1: typed JSON messages (manifest, requests, file-start, file-end, transfer-complete, error) + raw data channel for file chunks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
56e3c909d4637a5ec88869c352968c731e51fb7f
by plankatron <fleeky@gmail.com> · 2026-04-09T19:49:57.000Z

Changed files (10) +4357 -0

.gitignore +1 -0
……@@ -0,0 +1 @@
1node_modules/
bin/hyper-deploy.js +288 -0
……@@ -0,0 +1,288 @@
1#!/usr/bin/env bare
2/**
3 * hyper-deploy CLI — P2P release distribution
4 *
5 * Commands:
6 * serve <dir> Serve a directory to peers
7 * pull <topic> [dest] Pull files from a server
8 * info <topic> Show remote manifest without pulling
9 * help Show usage
10 * version Show version
11 */
12
13await import('bare-node-runtime/global')
14
15import fs from 'bare-fs'
16import path from 'bare-path'
17import { DeployServer, DeployClient } from '../index.js'
18import { startWeb } from '../lib/web.js'
19import { diffManifest } from '../lib/scanner.js'
20
21const VERSION = '0.1.0'
22
23function fmt (b) {
24 if (b < 1024) return b + ' B'
25 if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'
26 if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB'
27 return (b / 1073741824).toFixed(2) + ' GB'
28}
29
30function parseFlags (args) {
31 const flags = {}
32 const positional = []
33 for (let i = 0; i < args.length; i++) {
34 if (args[i].startsWith('--')) {
35 const key = args[i].slice(2)
36 if (key === 'no-web' || key === 'no-verify' || key === 'dry-run' || key === 'verify') {
37 flags[key] = true
38 } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
39 flags[key] = args[++i]
40 } else {
41 flags[key] = true
42 }
43 } else {
44 positional.push(args[i])
45 }
46 }
47 return { flags, positional }
48}
49
50const args = process.argv.slice(2)
51const command = args[0]
52
53if (!command || command === 'help' || command === '--help') {
54 console.log(`hyper-deploy v${VERSION} — P2P release distribution
55
56Commands:
57 serve <dir> Index and serve a directory to peers
58 pull <topic> [dest] Pull files from a server
59 info <topic> Show remote manifest without pulling
60 version Show version
61
62Serve options:
63 --topic <hex> Use a specific topic (default: random)
64 --web-port <port> Web UI port (default: 9090)
65 --no-web Disable web UI
66
67Pull options:
68 --timeout <ms> Connection timeout (default: 30000)
69 --no-verify Skip hash verification on received files
70 --dry-run Show what would transfer without doing it
71
72Examples:
73 hyper-deploy serve ./build/release
74 hyper-deploy pull abc123...def456 ./my-app
75 hyper-deploy pull abc123...def456 ./my-app --dry-run
76 hyper-deploy info abc123...def456
77`)
78 process.exit(0)
79}
80
81if (command === 'version' || command === '--version') {
82 console.log('hyper-deploy v' + VERSION)
83 process.exit(0)
84}
85
86// ─── SERVE ───────────────────────────────────────────────────
87
88if (command === 'serve') {
89 const { flags, positional } = parseFlags(args.slice(1))
90 const dir = positional[0]
91
92 if (!dir) {
93 console.error('Usage: hyper-deploy serve <dir>')
94 process.exit(1)
95 }
96
97 if (!fs.existsSync(dir)) {
98 console.error('Directory not found: ' + dir)
99 process.exit(1)
100 }
101
102 const topic = flags.topic ? Buffer.from(flags.topic, 'hex') : undefined
103
104 const server = new DeployServer(dir, { topic })
105
106 server.on('index-progress', (i, total) => {
107 if (i % 2000 === 0) console.log(` indexing ${i}/${total}...`)
108 })
109
110 server.on('indexed', (count, size) => {
111 console.log(`Indexed: ${count} files, ${fmt(size)}`)
112 })
113
114 server.on('ready', () => {
115 console.log(`\nServing on topic:`)
116 console.log(` ${server.topicHex}`)
117 console.log(`\nPull with:`)
118 console.log(` hyper-deploy pull ${server.topicHex} <dest-dir>`)
119 console.log(`\nWaiting for peers...`)
120 })
121
122 server.on('peer', ({ key, state }) => {
123 if (state === 'connected') console.log(`\nPeer ${key}: connected`)
124 else if (state === 'transferring') console.log(` Peer ${key}: transferring...`)
125 else if (state === 'done') console.log(` Peer ${key}: done`)
126 else if (state === 'disconnected') console.log(` Peer ${key}: disconnected`)
127 })
128
129 server.on('progress', (p) => {
130 if (p.filesServed && p.filesServed % 500 === 0) {
131 console.log(` ${p.filesServed} files, ${fmt(p.bytesServed)}`)
132 }
133 })
134
135 server.on('error', (err) => {
136 console.error('Error:', err.message)
137 })
138
139 await server.ready()
140
141 if (!flags['no-web']) {
142 const webPort = parseInt(flags['web-port']) || 9090
143 const { port } = startWeb(server, { port: webPort })
144 console.log(`\nWeb UI: http://localhost:${port}`)
145 }
146}
147
148// ─── PULL ────────────────────────────────────────────────────
149
150else if (command === 'pull') {
151 const { flags, positional } = parseFlags(args.slice(1))
152 const topicHex = positional[0]
153 const dest = positional[1] || './deploy-output'
154
155 if (!topicHex || topicHex.length !== 64) {
156 console.error('Usage: hyper-deploy pull <64-char-topic-hex> [dest-dir]')
157 process.exit(1)
158 }
159
160 const timeout = parseInt(flags.timeout) || 30000
161 const verify = !flags['no-verify']
162 const dryRun = !!flags['dry-run']
163
164 console.log(`Pulling to: ${path.resolve(dest)}`)
165 console.log(`Connecting to: ${topicHex.slice(0, 16)}...`)
166 if (dryRun) console.log('(dry run — no files will be transferred)')
167
168 const startTime = Date.now()
169 const client = new DeployClient(topicHex, dest, { timeout, verify })
170
171 client.on('connected', (key) => {
172 console.log(`Connected: ${key} (${Date.now() - startTime}ms)`)
173 })
174
175 client.on('manifest', (count) => {
176 console.log(`Manifest: ${count} files`)
177 })
178
179 client.on('diff', (needed, skipped) => {
180 console.log(`Need: ${needed}, already have: ${skipped}`)
181
182 if (dryRun) {
183 console.log('\n(dry run complete — would transfer ' + needed + ' files)')
184 client.close().then(() => process.exit(0))
185 }
186 })
187
188 client.on('up-to-date', () => {
189 console.log('Already up to date!')
190 })
191
192 client.on('progress', (p) => {
193 const total = p.filesTransferred + p.filesSkipped
194 if (p.filesTransferred % 500 === 0 || total === p.totalFiles) {
195 const elapsed = (Date.now() - startTime) / 1000
196 const pct = (total / p.totalFiles * 100).toFixed(0)
197 console.log(` ${pct}% — ${p.filesTransferred} transferred, ${fmt(p.bytesReceived)}, ${elapsed.toFixed(0)}s`)
198 }
199 })
200
201 client.on('error', (err) => {
202 console.error('Error:', err.message)
203 })
204
205 try {
206 await client.ready()
207
208 if (dryRun) {
209 // Wait for diff event to fire then exit (handled in diff listener above)
210 await new Promise(r => setTimeout(r, 60000))
211 } else {
212 const result = await client.pull()
213 const speed = result.bytesReceived > 0 ? (result.bytesReceived / result.elapsed * 1000 / 1024 / 1024).toFixed(1) : '0'
214 console.log(`\nComplete!`)
215 console.log(` Files: ${result.filesTransferred} transferred, ${result.filesSkipped} skipped`)
216 console.log(` Data: ${fmt(result.bytesReceived)}`)
217 console.log(` Time: ${(result.elapsed / 1000).toFixed(1)}s (${speed} MB/s)`)
218 if (result.errors.length > 0) {
219 console.log(` Errors: ${result.errors.length}`)
220 for (const e of result.errors) console.log(` ${e.path}: ${e.message}`)
221 }
222 }
223 } catch (err) {
224 console.error('\nFailed:', err.message)
225 process.exit(1)
226 }
227
228 // process.exit after close — libudx has a double-free bug in io_uring
229 // teardown that crashes when swarm.destroy() runs after real network transfers.
230 await client.close()
231 process.exit(0)
232}
233
234// ─── INFO ────────────────────────────────────────────────────
235
236else if (command === 'info') {
237 const topicHex = args[1]
238
239 if (!topicHex || topicHex.length !== 64) {
240 console.error('Usage: hyper-deploy info <64-char-topic-hex>')
241 process.exit(1)
242 }
243
244 console.log(`Connecting to: ${topicHex.slice(0, 16)}...`)
245
246 const client = new DeployClient(topicHex, '/tmp/hyper-deploy-info-dummy', { timeout: 15000 })
247
248 client.on('connected', (key) => {
249 console.log(`Connected: ${key}`)
250 })
251
252 client.on('manifest', (count) => {
253 // We have the manifest — print summary and exit
254 })
255
256 client.on('diff', (needed, skipped) => {
257 console.log(`\nRemote has ${needed + skipped} files`)
258 console.log(` Would transfer: ${needed}`)
259 console.log(` Already local: ${skipped}`)
260
261 client.close().then(() => process.exit(0))
262 })
263
264 client.on('up-to-date', () => {
265 console.log('Already up to date!')
266 client.close().then(() => process.exit(0))
267 })
268
269 try {
270 await client.ready()
271 await client.pull()
272 } catch (err) {
273 if (err.message.includes('closed during pull')) {
274 // Expected — we close after getting the manifest
275 } else {
276 console.error('Failed:', err.message)
277 process.exit(1)
278 }
279 }
280}
281
282// ─── UNKNOWN ─────────────────────────────────────────────────
283
284else {
285 console.error('Unknown command: ' + command)
286 console.error('Run: hyper-deploy help')
287 process.exit(1)
288}
index.js +493 -0
……@@ -0,0 +1,493 @@
1/**
2 * hyper-deploy — P2P release distribution
3 *
4 * Serves build artifacts from a directory to peers over Hyperswarm.
5 * File-level dedup: client compares content hashes, only pulls what changed.
6 * Backpressure via Protomux ondrain. Hash verification on received files.
7 *
8 * Usage:
9 * const server = new DeployServer(dir)
10 * await server.ready()
11 *
12 * const client = new DeployClient(topic, destDir)
13 * await client.ready()
14 * const result = await client.pull()
15 */
16
17import Hyperswarm from 'hyperswarm'
18import crypto from 'hypercore-crypto'
19import fs from 'bare-fs'
20import path from 'bare-path'
21import ReadyResource from 'ready-resource'
22import safetyCatch from 'safety-catch'
23
24import { buildManifest, diffManifest, readChunk, hashFile } from './lib/scanner.js'
25import { attachProtocol, PROTOCOL_VERSION, CHUNK_SIZE } from './lib/protocol.js'
26
27// ─── DeployServer ────────────────────────────────────────────
28
29export class DeployServer extends ReadyResource {
30 constructor (dir, opts) {
31 super()
32 opts = opts || {}
33
34 this.dir = path.resolve(dir)
35 this.topic = opts.topic || crypto.randomBytes(32)
36 this.swarm = opts.swarm || null
37 this._ownSwarm = !this.swarm
38 this.manifest = null
39 this._manifestHash = null
40 this._discovery = null
41 this._sessions = new Set()
42
43 this.stats = { totalConnections: 0, filesServed: 0, bytesServed: 0 }
44
45 this.ready().catch(safetyCatch)
46 }
47
48 async _open () {
49 if (!fs.existsSync(this.dir)) {
50 throw new Error('Directory not found: ' + this.dir)
51 }
52
53 await this._scan()
54
55 if (!this.swarm) this.swarm = new Hyperswarm()
56 this.swarm.on('connection', (conn) => this._onConnection(conn))
57
58 this._discovery = this.swarm.join(this.topic, { server: true, client: false })
59 await this._discovery.flushed()
60
61 this.emit('ready')
62 }
63
64 async _close () {
65 // Cancel active sessions
66 for (const session of this._sessions) {
67 session.destroyed = true
68 }
69 this._sessions.clear()
70
71 if (this._discovery) {
72 await this._discovery.destroy()
73 this._discovery = null
74 }
75 if (this._ownSwarm && this.swarm) {
76 await this.swarm.destroy()
77 this.swarm = null
78 }
79 }
80
81 get topicHex () {
82 return this.topic.toString('hex')
83 }
84
85 get connections () {
86 return this._sessions.size
87 }
88
89 async rescan () {
90 await this._scan()
91 return { files: this.manifest.files.length, totalSize: this.manifest.totalSize }
92 }
93
94 async _scan () {
95 this.manifest = buildManifest(this.dir, {
96 onProgress: (i, total) => {
97 if (i % 1000 === 0) this.emit('index-progress', i, total)
98 }
99 })
100
101 // Compute manifest hash for integrity verification
102 const manifestJson = JSON.stringify(this.manifest.files.map(f => f.path + ':' + f.hash))
103 this._manifestHash = crypto.data(Buffer.from(manifestJson)).toString('hex')
104
105 this.emit('indexed', this.manifest.files.length, this.manifest.totalSize)
106 }
107
108 _onConnection (conn) {
109 const peerKey = conn.remotePublicKey
110 ? conn.remotePublicKey.toString('hex').slice(0, 12)
111 : 'unknown'
112
113 this.stats.totalConnections++
114
115 const proto = attachProtocol(conn)
116 if (!proto) {
117 this.emit('error', new Error('Failed to attach protocol for peer ' + peerKey))
118 return
119 }
120
121 const session = { peerKey, destroyed: false, filesServed: 0, bytesServed: 0 }
122 this._sessions.add(session)
123 this.emit('peer', { key: peerKey, state: 'connected' })
124
125 conn.on('close', () => {
126 this._sessions.delete(session)
127 this.emit('peer', { key: peerKey, state: 'disconnected' })
128 })
129
130 conn.on('error', (err) => {
131 this._sessions.delete(session)
132 this.emit('error', err)
133 })
134
135 // Send manifest with version and integrity hash
136 proto.sendMeta({
137 type: 'manifest',
138 version: PROTOCOL_VERSION,
139 files: this.manifest.files,
140 manifestHash: this._manifestHash
141 }).catch(safetyCatch)
142
143 // Handle client messages
144 proto.metaMsg.onmessage = async (raw) => {
145 if (session.destroyed) return
146
147 let msg
148 try { msg = proto.parseMeta(raw) } catch { return }
149
150 if (msg.type === 'up-to-date') {
151 this.emit('peer', { key: peerKey, state: 'done' })
152 return
153 }
154
155 if (msg.type === 'requests') {
156 this.emit('peer', { key: peerKey, state: 'transferring' })
157
158 const requested = new Set(msg.paths)
159 this.emit('progress', {
160 peer: peerKey,
161 requested: requested.size,
162 skipped: this.manifest.files.length - requested.size
163 })
164
165 for (const f of this.manifest.files) {
166 if (session.destroyed) break
167 if (!requested.has(f.path)) continue
168
169 const fullPath = path.join(this.dir, f.path)
170 let stat
171 try { stat = fs.statSync(fullPath) } catch {
172 await proto.sendMeta({ type: 'error', message: 'File not found', path: f.path })
173 continue
174 }
175
176 // File start
177 await proto.sendMeta({ type: 'file-start', path: f.path, size: stat.size, hash: f.hash })
178
179 // Stream data from disk
180 let fd
181 try {
182 fd = fs.openSync(fullPath, 'r')
183 for (let off = 0; off < stat.size; off += CHUNK_SIZE) {
184 if (session.destroyed) break
185 const len = Math.min(CHUNK_SIZE, stat.size - off)
186 const buf = Buffer.alloc(len)
187 fs.readSync(fd, buf, 0, len, off)
188 await proto.sendData(buf)
189 session.bytesServed += len
190 this.stats.bytesServed += len
191 }
192 } finally {
193 if (fd !== undefined) try { fs.closeSync(fd) } catch {}
194 }
195
196 // File end
197 await proto.sendMeta({ type: 'file-end', path: f.path })
198
199 session.filesServed++
200 this.stats.filesServed++
201 this.emit('progress', {
202 peer: peerKey,
203 file: f.path,
204 filesServed: session.filesServed,
205 bytesServed: session.bytesServed
206 })
207 }
208
209 await proto.sendMeta({
210 type: 'transfer-complete',
211 filesServed: session.filesServed,
212 bytesServed: session.bytesServed
213 })
214
215 this.emit('peer', { key: peerKey, state: 'done' })
216 }
217
218 if (msg.type === 'done') {
219 this._sessions.delete(session)
220 }
221 }
222 }
223}
224
225// ─── DeployClient ────────────────────────────────────────────
226
227export class DeployClient extends ReadyResource {
228 constructor (topic, destDir, opts) {
229 super()
230 opts = opts || {}
231
232 this.topic = typeof topic === 'string' ? Buffer.from(topic, 'hex') : topic
233 this.destDir = path.resolve(destDir)
234 this.swarm = opts.swarm || null
235 this._ownSwarm = !this.swarm
236 this._timeout = opts.timeout !== undefined ? opts.timeout : 30000
237 this._verify = opts.verify !== false
238 this.connected = false
239
240 this.stats = {
241 filesTransferred: 0,
242 filesSkipped: 0,
243 bytesReceived: 0,
244 totalFiles: 0,
245 elapsed: 0,
246 errors: []
247 }
248
249 this._pullResolve = null
250 this._pullReject = null
251 this._startTime = null
252 this._currentFd = null
253 this._currentPath = null
254 this._currentHash = null
255 this._connectTimer = null
256
257 this.ready().catch(safetyCatch)
258 }
259
260 async _open () {
261 fs.mkdirSync(this.destDir, { recursive: true })
262
263 if (!this.swarm) this.swarm = new Hyperswarm()
264 this.swarm.on('connection', (conn) => this._onConnection(conn))
265
266 this.swarm.join(this.topic, { server: false, client: true })
267 await this.swarm.flush()
268 }
269
270 async _close () {
271 // Close any open file descriptor
272 if (this._currentFd !== null) {
273 try { fs.closeSync(this._currentFd) } catch {}
274 this._currentFd = null
275 }
276
277 if (this._connectTimer) {
278 clearTimeout(this._connectTimer)
279 this._connectTimer = null
280 }
281
282 if (this._pullReject) {
283 this._pullReject(new Error('Client closed during pull'))
284 this._pullReject = null
285 this._pullResolve = null
286 }
287
288 if (this._ownSwarm && this.swarm) {
289 await this.swarm.destroy()
290 this.swarm = null
291 }
292 }
293
294 /**
295 * Pull files from the server. Resolves when complete.
296 * @returns {Promise<{ filesTransferred, filesSkipped, bytesReceived, totalFiles, elapsed, errors }>}
297 */
298 pull () {
299 if (this._pullResolve) return Promise.reject(new Error('Pull already in progress'))
300
301 this._startTime = Date.now()
302
303 return new Promise((resolve, reject) => {
304 this._pullResolve = resolve
305 this._pullReject = reject
306
307 // Connection timeout
308 if (this._timeout > 0 && !this.connected) {
309 this._connectTimer = setTimeout(() => {
310 if (!this.connected && this._pullReject) {
311 this._pullReject(new Error('Connection timeout after ' + this._timeout + 'ms'))
312 this._pullReject = null
313 this._pullResolve = null
314 }
315 }, this._timeout)
316 }
317 })
318 }
319
320 _finish () {
321 this.stats.elapsed = Date.now() - this._startTime
322 if (this._pullResolve) {
323 const resolve = this._pullResolve
324 this._pullResolve = null
325 this._pullReject = null
326 resolve({ ...this.stats })
327 }
328 this.emit('complete', { ...this.stats })
329 }
330
331 _fail (err) {
332 this.stats.elapsed = Date.now() - (this._startTime || Date.now())
333 this.emit('error', err)
334 if (this._pullReject) {
335 const reject = this._pullReject
336 this._pullReject = null
337 this._pullResolve = null
338 reject(err)
339 }
340 }
341
342 _onConnection (conn) {
343 if (this._connectTimer) {
344 clearTimeout(this._connectTimer)
345 this._connectTimer = null
346 }
347
348 this.connected = true
349 const peerKey = conn.remotePublicKey
350 ? conn.remotePublicKey.toString('hex').slice(0, 12)
351 : 'unknown'
352 this.emit('connected', peerKey)
353
354 conn.on('close', () => {
355 this.connected = false
356 // If we were mid-transfer, that's an error
357 if (this._pullReject && this.stats.filesTransferred + this.stats.filesSkipped < this.stats.totalFiles) {
358 this._fail(new Error('Connection lost during transfer'))
359 }
360 })
361
362 conn.on('error', (err) => {
363 this.connected = false
364 this._fail(err)
365 })
366
367 const proto = attachProtocol(conn)
368 if (!proto) {
369 this._fail(new Error('Failed to attach protocol'))
370 return
371 }
372
373 proto.metaMsg.onmessage = (raw) => {
374 let msg
375 try { msg = proto.parseMeta(raw) } catch (err) {
376 this._fail(new Error('Invalid protocol message'))
377 return
378 }
379
380 if (msg.type === 'manifest') {
381 this._handleManifest(msg, proto)
382 } else if (msg.type === 'file-start') {
383 this._handleFileStart(msg)
384 } else if (msg.type === 'file-end') {
385 this._handleFileEnd(msg)
386 } else if (msg.type === 'transfer-complete') {
387 this._handleTransferComplete(msg, proto)
388 } else if (msg.type === 'error') {
389 this.stats.errors.push({ path: msg.path, message: msg.message })
390 this.emit('error', new Error(msg.message))
391 }
392 }
393
394 proto.dataMsg.onmessage = (chunk) => {
395 if (this._currentFd !== null) {
396 try {
397 fs.writeSync(this._currentFd, chunk, 0, chunk.length, this._currentWritten)
398 this._currentWritten += chunk.length
399 this.stats.bytesReceived += chunk.length
400 } catch (err) {
401 this._fail(new Error('Write failed: ' + err.message))
402 }
403 }
404 }
405 }
406
407 _handleManifest (msg, proto) {
408 // Version check
409 if (msg.version && msg.version !== PROTOCOL_VERSION) {
410 this._fail(new Error('Protocol version mismatch: expected ' + PROTOCOL_VERSION + ', got ' + msg.version))
411 return
412 }
413
414 this.stats.totalFiles = msg.files.length
415 this.emit('manifest', msg.files.length)
416
417 // Diff against local files
418 const { needed, skipped } = diffManifest(msg.files, this.destDir)
419 this.stats.filesSkipped = skipped
420 this.emit('diff', needed.length, skipped)
421
422 if (needed.length === 0) {
423 proto.sendMeta({ type: 'up-to-date' }).catch(safetyCatch)
424 this.emit('up-to-date')
425 this._finish()
426 return
427 }
428
429 // Pipeline all requests
430 proto.sendMeta({ type: 'requests', paths: needed }).catch(safetyCatch)
431 }
432
433 _handleFileStart (msg) {
434 const targetPath = path.resolve(path.join(this.destDir, msg.path))
435
436 // Reject path traversal (e.g. ../../.bashrc)
437 if (!targetPath.startsWith(this.destDir + path.sep) && targetPath !== this.destDir) {
438 this.stats.errors.push({ path: msg.path, message: 'Path traversal rejected' })
439 this.emit('error', new Error('Path traversal rejected: ' + msg.path))
440 return
441 }
442
443 fs.mkdirSync(path.dirname(targetPath), { recursive: true })
444
445 this._currentPath = msg.path
446 this._currentHash = msg.hash
447 this._currentFd = fs.openSync(targetPath, 'w')
448 this._currentWritten = 0
449 if (msg.size > 0) fs.ftruncateSync(this._currentFd, msg.size)
450 }
451
452 _handleFileEnd (msg) {
453 if (this._currentFd !== null) {
454 fs.closeSync(this._currentFd)
455 this._currentFd = null
456 }
457
458 // Verify hash if enabled
459 if (this._verify && this._currentHash) {
460 const targetPath = path.join(this.destDir, msg.path)
461 try {
462 const actual = hashFile(targetPath)
463 if (actual !== this._currentHash) {
464 const err = 'Hash mismatch for ' + msg.path
465 this.stats.errors.push({ path: msg.path, message: err })
466 this.emit('error', new Error(err))
467 // Delete corrupted file
468 try { fs.unlinkSync(targetPath) } catch {}
469 return
470 }
471 } catch (err) {
472 this.stats.errors.push({ path: msg.path, message: err.message })
473 }
474 }
475
476 this.stats.filesTransferred++
477 this._currentPath = null
478 this._currentHash = null
479
480 this.emit('progress', {
481 file: msg.path,
482 filesTransferred: this.stats.filesTransferred,
483 filesSkipped: this.stats.filesSkipped,
484 bytesReceived: this.stats.bytesReceived,
485 totalFiles: this.stats.totalFiles
486 })
487 }
488
489 _handleTransferComplete (msg, proto) {
490 proto.sendMeta({ type: 'done' }).catch(safetyCatch)
491 this._finish()
492 }
493}
lib/protocol.js +95 -0
……@@ -0,0 +1,95 @@
1/**
2 * Deploy protocol — Protomux channel for release distribution
3 *
4 * Two message types on one channel:
5 * meta (JSON): typed messages for control flow
6 * data (raw): file content chunks
7 *
8 * Message types:
9 * manifest — server → client: file list with hashes
10 * requests — client → server: which files to send
11 * up-to-date — client → server: nothing needed
12 * file-start — server → client: begin file transfer
13 * file-end — server → client: file transfer complete
14 * transfer-complete — server → client: all files sent
15 * done — client → server: acknowledged complete
16 * error — either direction: something went wrong
17 */
18
19import Protomux from 'protomux'
20import c from 'compact-encoding'
21
22export const PROTOCOL_NAME = 'hyper-deploy'
23export const PROTOCOL_VERSION = 1
24export const CHUNK_SIZE = 65536
25
26/**
27 * Attach the deploy protocol to a Protomux-capable stream.
28 *
29 * @param {Stream} stream — SecretStream or Hyperswarm connection
30 * @returns {{ ch, metaMsg, dataMsg, mux, send, sendMeta, sendData } | null}
31 */
32export function attachProtocol (stream) {
33 const mux = Protomux.from(stream)
34 if (!mux) return null
35
36 let drainResolve = null
37
38 const ch = mux.createChannel({
39 protocol: PROTOCOL_NAME,
40 ondrain () {
41 if (drainResolve) {
42 const r = drainResolve
43 drainResolve = null
44 r()
45 }
46 }
47 })
48
49 if (!ch) return null
50
51 const metaMsg = ch.addMessage({ encoding: c.raw })
52 const dataMsg = ch.addMessage({ encoding: c.raw })
53
54 ch.open()
55
56 async function waitDrain () {
57 await new Promise(r => { drainResolve = r })
58 }
59
60 return {
61 ch,
62 metaMsg,
63 dataMsg,
64 mux,
65
66 /**
67 * Send a typed JSON message on the meta channel with backpressure.
68 * @param {object} obj — must have a `type` field
69 */
70 async sendMeta (obj) {
71 if (!metaMsg.send(Buffer.from(JSON.stringify(obj)))) {
72 await waitDrain()
73 }
74 },
75
76 /**
77 * Send raw data on the data channel with backpressure.
78 * @param {Buffer} buf
79 */
80 async sendData (buf) {
81 if (!dataMsg.send(buf)) {
82 await waitDrain()
83 }
84 },
85
86 /**
87 * Parse a received meta message.
88 * @param {Buffer} raw
89 * @returns {object}
90 */
91 parseMeta (raw) {
92 return JSON.parse(raw.toString())
93 }
94 }
95}
lib/scanner.js +108 -0
……@@ -0,0 +1,108 @@
1/**
2 * Directory scanner — walk, stat, hash files
3 *
4 * Returns a manifest: array of { path, size, hash } for all files
5 * under a directory. Used by the server to build the file list
6 * and by the client to determine which files it already has.
7 */
8
9import fs from 'bare-fs'
10import path from 'bare-path'
11import crypto from 'hypercore-crypto'
12
13/**
14 * Walk a directory recursively, return file metadata.
15 * @param {string} dir — absolute path to scan
16 * @param {string} [prefix] — internal, for recursion
17 * @returns {Array<{ path: string, size: number }>}
18 */
19export function walkDir (dir, prefix) {
20 prefix = prefix || ''
21 const results = []
22 try {
23 for (const entry of fs.readdirSync(dir)) {
24 const full = path.join(dir, entry)
25 const rel = prefix ? prefix + '/' + entry : entry
26 try {
27 const stat = fs.statSync(full)
28 if (stat.isDirectory()) results.push(...walkDir(full, rel))
29 else if (stat.isFile()) results.push({ path: rel, size: stat.size })
30 } catch {}
31 }
32 } catch {}
33 return results
34}
35
36/**
37 * Hash a file's contents with blake2b-256.
38 * @param {string} filePath — absolute path
39 * @returns {string} hex hash
40 */
41export function hashFile (filePath) {
42 return crypto.data(fs.readFileSync(filePath)).toString('hex')
43}
44
45/**
46 * Build a full manifest: walk + hash every file.
47 * @param {string} dir — absolute path to scan
48 * @param {{ onProgress?: (i: number, total: number) => void }} [opts]
49 * @returns {{ files: Array<{ path: string, size: number, hash: string }>, totalSize: number }}
50 */
51export function buildManifest (dir, opts) {
52 const onProgress = opts && opts.onProgress
53 const files = walkDir(dir)
54 let totalSize = 0
55
56 for (let i = 0; i < files.length; i++) {
57 const f = files[i]
58 f.hash = hashFile(path.join(dir, f.path))
59 totalSize += f.size
60 if (onProgress) onProgress(i + 1, files.length)
61 }
62
63 return { files, totalSize }
64}
65
66/**
67 * Diff a remote manifest against local files.
68 * Returns which files the client needs to request.
69 * @param {Array<{ path: string, size: number, hash: string }>} manifest — server's file list
70 * @param {string} localDir — local directory to compare against
71 * @returns {{ needed: string[], skipped: number }}
72 */
73export function diffManifest (manifest, localDir) {
74 const needed = []
75 let skipped = 0
76
77 for (const f of manifest) {
78 const localPath = path.join(localDir, f.path)
79 try {
80 const stat = fs.statSync(localPath)
81 if (stat.size === f.size) {
82 const localHash = hashFile(localPath)
83 if (localHash === f.hash) {
84 skipped++
85 continue
86 }
87 }
88 } catch {}
89 needed.push(f.path)
90 }
91
92 return { needed, skipped }
93}
94
95/**
96 * Read a chunk from a file without loading the whole thing.
97 * @param {string} filePath
98 * @param {number} offset
99 * @param {number} length
100 * @returns {Buffer}
101 */
102export function readChunk (filePath, offset, length) {
103 const fd = fs.openSync(filePath, 'r')
104 const buf = Buffer.alloc(length)
105 fs.readSync(fd, buf, 0, length, offset)
106 fs.closeSync(fd)
107 return buf
108}
lib/web.js +318 -0
……@@ -0,0 +1,318 @@
1/**
2 * Web UI for hyper-deploy server status
3 *
4 * Serves a single-page status dashboard:
5 * GET / — HTML page with live updates via SSE
6 * GET /api/status — JSON status (files, peers, transfers)
7 * GET /events — Server-Sent Events for live updates
8 */
9
10import http from 'bare-http1'
11
12function fmt (b) {
13 if (b < 1024) return b + ' B'
14 if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'
15 if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB'
16 return (b / 1073741824).toFixed(2) + ' GB'
17}
18
19/**
20 * Start the web UI server.
21 * @param {DeployServer} deployServer — the running deploy server instance
22 * @param {{ port?: number }} [opts]
23 * @returns {{ server, port }}
24 */
25export function startWeb (deployServer, opts) {
26 const port = (opts && opts.port) || 9090
27 const sseClients = new Set()
28
29 function broadcast (event, data) {
30 const msg = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
31 for (const res of sseClients) {
32 try { res.write(msg) } catch { sseClients.delete(res) }
33 }
34 }
35
36 // Wire deploy server events to SSE broadcasts
37 deployServer.on('peer', (info) => broadcast('peer', info))
38 deployServer.on('progress', (info) => {
39 broadcast('progress', {
40 ...info,
41 totalFilesServed: deployServer.stats.filesServed,
42 totalBytesServed: deployServer.stats.bytesServed
43 })
44 })
45 deployServer.on('error', (err) => broadcast('error', { message: err.message }))
46
47 function getStatus () {
48 return {
49 dir: deployServer.dir,
50 topic: deployServer.topicHex,
51 files: deployServer.manifest ? deployServer.manifest.files.length : 0,
52 totalSize: deployServer.manifest ? deployServer.manifest.totalSize : 0,
53 totalSizeFmt: deployServer.manifest ? fmt(deployServer.manifest.totalSize) : '0',
54 connections: deployServer.connections,
55 stats: {
56 totalConnections: deployServer.stats.totalConnections,
57 filesServed: deployServer.stats.filesServed,
58 bytesServed: deployServer.stats.bytesServed,
59 bytesServedFmt: fmt(deployServer.stats.bytesServed)
60 }
61 }
62 }
63
64 const server = http.createServer(async (req, res) => {
65 const url = req.url.split('?')[0]
66
67 if (url === '/api/status') {
68 res.writeHead(200, { 'Content-Type': 'application/json' })
69 res.end(JSON.stringify(getStatus()))
70 return
71 }
72
73 if (url === '/api/rescan' && req.method === 'POST') {
74 try {
75 const result = await deployServer.rescan()
76 res.writeHead(200, { 'Content-Type': 'application/json' })
77 res.end(JSON.stringify({ ok: true, ...result, ...getStatus() }))
78 } catch (err) {
79 res.writeHead(500, { 'Content-Type': 'application/json' })
80 res.end(JSON.stringify({ ok: false, error: err.message }))
81 }
82 return
83 }
84
85 if (url === '/events') {
86 res.writeHead(200, {
87 'Content-Type': 'text/event-stream',
88 'Cache-Control': 'no-cache',
89 Connection: 'keep-alive'
90 })
91 res.write(`data: ${JSON.stringify(getStatus())}\n\n`)
92 sseClients.add(res)
93 req.on('close', () => sseClients.delete(res))
94 return
95 }
96
97 // Serve HTML dashboard
98 res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
99 res.end(renderPage())
100 })
101
102 server.listen(port, () => {})
103
104 return { server, port }
105}
106
107function renderPage () {
108 return `<!DOCTYPE html>
109<html lang="en">
110<head>
111<meta charset="UTF-8">
112<meta name="viewport" content="width=device-width, initial-scale=1.0">
113<title>hyper-deploy</title>
114<style>
115 * { margin: 0; padding: 0; box-sizing: border-box; }
116 body {
117 font-family: -apple-system, system-ui, sans-serif;
118 background: #0d1117;
119 color: #c9d1d9;
120 padding: 2rem;
121 max-width: 720px;
122 margin: 0 auto;
123 }
124 h1 { color: #58a6ff; font-size: 1.4rem; margin-bottom: 1.5rem; }
125 .card {
126 background: #161b22;
127 border: 1px solid #30363d;
128 border-radius: 6px;
129 padding: 1rem 1.2rem;
130 margin-bottom: 1rem;
131 }
132 .card h2 {
133 font-size: 0.75rem;
134 text-transform: uppercase;
135 letter-spacing: 0.05em;
136 color: #8b949e;
137 margin-bottom: 0.6rem;
138 }
139 .topic {
140 font-family: monospace;
141 font-size: 0.8rem;
142 word-break: break-all;
143 color: #79c0ff;
144 cursor: pointer;
145 padding: 0.5rem;
146 background: #0d1117;
147 border-radius: 4px;
148 position: relative;
149 }
150 .topic:hover { background: #1a2332; }
151 .topic .copied {
152 position: absolute;
153 right: 0.5rem;
154 top: 0.5rem;
155 color: #3fb950;
156 font-size: 0.7rem;
157 display: none;
158 }
159 .stat-row {
160 display: flex;
161 justify-content: space-between;
162 padding: 0.3rem 0;
163 border-bottom: 1px solid #21262d;
164 }
165 .stat-row:last-child { border-bottom: none; }
166 .stat-label { color: #8b949e; }
167 .stat-value { color: #e6edf3; font-family: monospace; }
168 .peers {
169 min-height: 2rem;
170 }
171 .peer {
172 display: inline-block;
173 background: #1f6feb22;
174 border: 1px solid #1f6feb44;
175 color: #58a6ff;
176 padding: 0.2rem 0.5rem;
177 border-radius: 3px;
178 font-family: monospace;
179 font-size: 0.75rem;
180 margin: 0.2rem;
181 }
182 .peer.done { border-color: #3fb95044; color: #3fb950; }
183 .progress-bar {
184 height: 4px;
185 background: #21262d;
186 border-radius: 2px;
187 margin-top: 0.5rem;
188 overflow: hidden;
189 }
190 .progress-fill {
191 height: 100%;
192 background: #1f6feb;
193 border-radius: 2px;
194 transition: width 0.3s;
195 width: 0%;
196 }
197 .empty { color: #484f58; font-style: italic; font-size: 0.85rem; }
198 .cmd {
199 font-family: monospace;
200 font-size: 0.8rem;
201 color: #c9d1d9;
202 background: #0d1117;
203 padding: 0.5rem;
204 border-radius: 4px;
205 margin-top: 0.4rem;
206 }
207 .rescan-btn {
208 background: #21262d;
209 color: #c9d1d9;
210 border: 1px solid #30363d;
211 padding: 0.4rem 0.8rem;
212 border-radius: 4px;
213 cursor: pointer;
214 font-size: 0.8rem;
215 float: right;
216 margin-top: -0.2rem;
217 }
218 .rescan-btn:hover { background: #30363d; }
219</style>
220</head>
221<body>
222<h1>hyper-deploy</h1>
223
224<div class="card">
225 <h2>Topic <button class="rescan-btn" onclick="rescan()">Rescan</button></h2>
226 <div class="topic" onclick="copyTopic(this)" id="topic">loading...</div>
227 <div class="cmd" id="pull-cmd">loading...</div>
228</div>
229
230<div class="card">
231 <h2>Release</h2>
232 <div class="stat-row"><span class="stat-label">Directory</span><span class="stat-value" id="dir">-</span></div>
233 <div class="stat-row"><span class="stat-label">Files</span><span class="stat-value" id="files">-</span></div>
234 <div class="stat-row"><span class="stat-label">Total size</span><span class="stat-value" id="size">-</span></div>
235</div>
236
237<div class="card">
238 <h2>Transfer Stats</h2>
239 <div class="stat-row"><span class="stat-label">Connections</span><span class="stat-value" id="connections">0</span></div>
240 <div class="stat-row"><span class="stat-label">Files served</span><span class="stat-value" id="served">0</span></div>
241 <div class="stat-row"><span class="stat-label">Data served</span><span class="stat-value" id="bytes">0 B</span></div>
242 <div class="progress-bar"><div class="progress-fill" id="progress"></div></div>
243</div>
244
245<div class="card">
246 <h2>Peers</h2>
247 <div class="peers" id="peers"><span class="empty">No peers connected</span></div>
248</div>
249
250<script>
251const $ = id => document.getElementById(id)
252const peers = new Map()
253let totalFiles = 0
254
255function update (data) {
256 if (data.topic) {
257 $('topic').textContent = data.topic
258 $('pull-cmd').textContent = 'hyper-deploy pull ' + data.topic + ' <dest>'
259 }
260 if (data.dir) $('dir').textContent = data.dir
261 if (data.files) { $('files').textContent = data.files; totalFiles = data.files }
262 if (data.totalSizeFmt) $('size').textContent = data.totalSizeFmt
263 if (data.stats) {
264 $('connections').textContent = data.stats.connections
265 $('served').textContent = data.stats.filesServed
266 $('bytes').textContent = data.stats.bytesServedFmt
267 if (totalFiles > 0) {
268 $('progress').style.width = Math.min(100, data.stats.filesServed / totalFiles * 100) + '%'
269 }
270 }
271}
272
273function addPeer (key, type) {
274 peers.set(key, type)
275 renderPeers()
276}
277
278function renderPeers () {
279 if (peers.size === 0) {
280 $('peers').innerHTML = '<span class="empty">No peers connected</span>'
281 return
282 }
283 $('peers').innerHTML = Array.from(peers.entries())
284 .map(([k, t]) => '<span class="peer' + (t === 'done' ? ' done' : '') + '">' + k + '</span>')
285 .join('')
286}
287
288function copyTopic (el) {
289 navigator.clipboard.writeText(el.textContent).then(() => {
290 const copied = el.querySelector('.copied')
291 if (copied) { copied.style.display = 'inline'; setTimeout(() => copied.style.display = 'none', 1500) }
292 })
293}
294
295function rescan () {
296 fetch('/api/rescan', { method: 'POST' }).then(r => r.json()).then(update)
297}
298
299// SSE for live updates
300const es = new EventSource('/events')
301es.onmessage = (e) => update(JSON.parse(e.data))
302es.addEventListener('peer', (e) => {
303 const d = JSON.parse(e.data)
304 addPeer(d.key, d.type)
305})
306es.addEventListener('progress', (e) => {
307 const d = JSON.parse(e.data)
308 $('served').textContent = d.filesServed
309 if (totalFiles > 0) $('progress').style.width = Math.min(100, d.filesServed / totalFiles * 100) + '%'
310})
311es.addEventListener('complete', (e) => {
312 const d = JSON.parse(e.data)
313 $('progress').style.width = '100%'
314})
315</script>
316</body>
317</html>`
318}
package-lock.json +2079 -0
……@@ -0,0 +1,2079 @@
1{
2 "name": "hyper-deploy",
3 "version": "0.1.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "hyper-deploy",
9 "version": "0.1.0",
10 "dependencies": {
11 "@hyperswarm/secret-stream": "^6.0.0",
12 "bare-fs": "^4.0.0",
13 "bare-http1": "^2.0.0",
14 "bare-node-runtime": "^1.2.0",
15 "bare-os": "^3.0.0",
16 "bare-path": "^3.0.0",
17 "compact-encoding": "^2.0.0",
18 "hypercore-crypto": "^3.0.0",
19 "hyperswarm": "^4.0.0",
20 "protomux": "^3.0.0",
21 "ready-resource": "^1.0.0",
22 "safety-catch": "^1.0.3"
23 },
24 "bin": {
25 "hyper-deploy": "bin/hyper-deploy.js"
26 },
27 "devDependencies": {
28 "brittle": "^3.0.0",
29 "corestore": "^7.0.0",
30 "hyper-overlay": "file:../hyper-overlay"
31 }
32 },
33 "../hyper-overlay": {
34 "version": "0.1.0",
35 "dev": true,
36 "license": "AGPL-3.0",
37 "dependencies": {
38 "@hyperswarm/testnet": "^3.1.4",
39 "b4a": "^1.0.0",
40 "bare-fs": "^4.6.0",
41 "bare-http1": "^4.5.5",
42 "bare-native": "^0.1.2",
43 "bare-os": "^3.8.7",
44 "bare-path": "^3.0.0",
45 "bare-pipe": "^4.1.5",
46 "bare-process": "^4.4.1",
47 "bare-subprocess": "^5.2.3",
48 "bare-zlib": "^1.3.3",
49 "blind-pairing": "^2.3.1",
50 "cellery": "^1.4.5",
51 "cellery-html": "^1.1.10",
52 "compact-encoding": "^2.0.0",
53 "corestore": "^7.0.0",
54 "hyperbee": "^2.0.0",
55 "hypercore-crypto": "^3.0.0",
56 "hyperswarm": "^4.0.0",
57 "localwatch": "^1.4.3",
58 "paparam": "^1.10.0",
59 "protomux": "^3.0.0",
60 "quickbit-universal": "^2.0.0",
61 "ready-resource": "^1.0.0",
62 "streamx": "^2.0.0"
63 },
64 "bin": {
65 "hyper-overlay": "bin/hyper-overlay.js",
66 "hyper-overlay-clean": "bin/hyper-overlay-clean.js",
67 "hyper-overlay-smudge": "bin/hyper-overlay-smudge.js",
68 "overlay": "bin/overlay.js"
69 },
70 "devDependencies": {
71 "brittle": "^3.0.0"
72 },
73 "optionalDependencies": {
74 "fuse-native": "^2.2.6"
75 }
76 },
77 "node_modules/@hyperswarm/secret-stream": {
78 "version": "6.9.1",
79 "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz",
80 "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==",
81 "license": "Apache-2.0",
82 "dependencies": {
83 "b4a": "^1.1.0",
84 "hypercore-crypto": "^3.3.1",
85 "noise-curve-ed": "^2.0.1",
86 "noise-handshake": "^4.0.0",
87 "sodium-secretstream": "^1.1.0",
88 "sodium-universal": "^5.0.0",
89 "streamx": "^2.14.0",
90 "timeout-refresh": "^2.0.0",
91 "unslab": "^1.3.0"
92 }
93 },
94 "node_modules/@jridgewell/resolve-uri": {
95 "version": "3.1.2",
96 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
97 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
98 "dev": true,
99 "license": "MIT",
100 "engines": {
101 "node": ">=6.0.0"
102 }
103 },
104 "node_modules/@jridgewell/sourcemap-codec": {
105 "version": "1.5.5",
106 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
107 "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
108 "dev": true,
109 "license": "MIT"
110 },
111 "node_modules/@jridgewell/trace-mapping": {
112 "version": "0.3.31",
113 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
114 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
115 "dev": true,
116 "license": "MIT",
117 "dependencies": {
118 "@jridgewell/resolve-uri": "^3.1.0",
119 "@jridgewell/sourcemap-codec": "^1.4.14"
120 }
121 },
122 "node_modules/@types/istanbul-lib-coverage": {
123 "version": "2.0.6",
124 "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
125 "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
126 "dev": true,
127 "license": "MIT"
128 },
129 "node_modules/adaptive-timeout": {
130 "version": "1.0.1",
131 "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz",
132 "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==",
133 "license": "Apache-2.0",
134 "dependencies": {
135 "xache": "^1.2.1"
136 }
137 },
138 "node_modules/b4a": {
139 "version": "1.8.0",
140 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
141 "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
142 "license": "Apache-2.0",
143 "peerDependencies": {
144 "react-native-b4a": "*"
145 },
146 "peerDependenciesMeta": {
147 "react-native-b4a": {
148 "optional": true
149 }
150 }
151 },
152 "node_modules/bare-abort": {
153 "version": "2.0.13",
154 "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz",
155 "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==",
156 "license": "Apache-2.0"
157 },
158 "node_modules/bare-abort-controller": {
159 "version": "1.1.1",
160 "resolved": "https://registry.npmjs.org/bare-abort-controller/-/bare-abort-controller-1.1.1.tgz",
161 "integrity": "sha512-A/3w+PHaGnjlrRcmBeuCk8v23SP35db1X6a5GOkTVSloVmMEWdVhhnlrJWzIvnLJh79ljhAFS1lvLRev09LCGg==",
162 "license": "Apache-2.0",
163 "peer": true,
164 "dependencies": {
165 "bare-events": "^2.7.0"
166 }
167 },
168 "node_modules/bare-addon-resolve": {
169 "version": "1.10.0",
170 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz",
171 "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==",
172 "license": "Apache-2.0",
173 "dependencies": {
174 "bare-module-resolve": "^1.10.0",
175 "bare-semver": "^1.0.0"
176 },
177 "peerDependencies": {
178 "bare-url": "*"
179 },
180 "peerDependenciesMeta": {
181 "bare-url": {
182 "optional": true
183 }
184 }
185 },
186 "node_modules/bare-ansi-escapes": {
187 "version": "2.2.3",
188 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
189 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
190 "license": "Apache-2.0",
191 "dependencies": {
192 "bare-stream": "^2.6.5"
193 },
194 "peerDependencies": {
195 "bare-buffer": "*"
196 },
197 "peerDependenciesMeta": {
198 "bare-buffer": {
199 "optional": true
200 }
201 }
202 },
203 "node_modules/bare-assert": {
204 "version": "1.2.0",
205 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
206 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
207 "license": "Apache-2.0",
208 "dependencies": {
209 "bare-inspect": "^3.1.2"
210 }
211 },
212 "node_modules/bare-async-hooks": {
213 "version": "0.0.0",
214 "resolved": "https://registry.npmjs.org/bare-async-hooks/-/bare-async-hooks-0.0.0.tgz",
215 "integrity": "sha512-xNfGwUobaomCGMGAqohAekS3uMCj+4tvI4AoOaJnO7NfpN+dvFdkC5xkeQtmZzs2vxf2TR5J6i5FDd1ImCZERw==",
216 "license": "Apache-2.0"
217 },
218 "node_modules/bare-buffer": {
219 "version": "3.6.0",
220 "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.0.tgz",
221 "integrity": "sha512-/maRWEQ2eBkVNMbNFVsq1pHXJYVj4Y3AixwruB24eKZDs5Gtu0fixzvjYmBIuTsBMtVH5Yb27pQO9BhFa+IlIQ==",
222 "license": "Apache-2.0",
223 "engines": {
224 "bare": ">=1.20.0"
225 }
226 },
227 "node_modules/bare-bundle": {
228 "version": "1.10.0",
229 "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz",
230 "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==",
231 "license": "Apache-2.0",
232 "peerDependencies": {
233 "bare-buffer": "*",
234 "bare-url": "*"
235 },
236 "peerDependenciesMeta": {
237 "bare-buffer": {
238 "optional": true
239 },
240 "bare-url": {
241 "optional": true
242 }
243 }
244 },
245 "node_modules/bare-channel": {
246 "version": "5.2.3",
247 "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.2.3.tgz",
248 "integrity": "sha512-2cRErqS4fzzr3ZUSd5W67ka70dZKJ1VWrCsvxPVC8vr3rWvd4aaCG5iCDas9+MFVaUIWvfw04+NH4+FHQDddMQ==",
249 "license": "Apache-2.0",
250 "dependencies": {
251 "bare-events": "^2.0.0",
252 "bare-stream": "^2.7.0",
253 "bare-structured-clone": "^1.4.0"
254 },
255 "engines": {
256 "bare": ">=1.7.0"
257 }
258 },
259 "node_modules/bare-console": {
260 "version": "6.1.0",
261 "resolved": "https://registry.npmjs.org/bare-console/-/bare-console-6.1.0.tgz",
262 "integrity": "sha512-BziGTEIyVh0zn78842mmpOLGTEFE7/S4A5Tb0d2iIu7bryVdoiF0przraYMetJckHxm0P5ITTZrYfRuYRr9n/Q==",
263 "license": "Apache-2.0",
264 "dependencies": {
265 "bare-hrtime": "^2.0.0",
266 "bare-logger": "^2.0.0",
267 "bare-system-logger": "^1.0.2"
268 }
269 },
270 "node_modules/bare-cov": {
271 "version": "1.2.1",
272 "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz",
273 "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==",
274 "dev": true,
275 "license": "Apache-2.0",
276 "dependencies": {
277 "bare-fs": "^4.1.2",
278 "bare-inspector": "^4.0.1",
279 "bare-path": "^3.0.0",
280 "bare-process": "^4.2.1",
281 "bare-url": "^2.1.5",
282 "bare-v8-to-istanbul": "^1.0.2",
283 "picomatch": "^4.0.2",
284 "which-runtime": "^1.2.1"
285 }
286 },
287 "node_modules/bare-crypto": {
288 "version": "1.13.4",
289 "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.4.tgz",
290 "integrity": "sha512-JiCZ5l2YOG1y8J7yy1BCAKTCZrPnHLb7pDRIdurBTOn5oIwBQDIv8iH5Pl2V85vzjl1NZXRfNY4HZLsE942jJA==",
291 "license": "Apache-2.0",
292 "dependencies": {
293 "bare-assert": "^1.2.0",
294 "bare-stream": "^2.6.3"
295 },
296 "peerDependencies": {
297 "bare-buffer": "*"
298 },
299 "peerDependenciesMeta": {
300 "bare-buffer": {
301 "optional": true
302 }
303 }
304 },
305 "node_modules/bare-debug-log": {
306 "version": "2.0.0",
307 "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz",
308 "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==",
309 "license": "Apache-2.0",
310 "dependencies": {
311 "bare-os": "^3.0.1"
312 }
313 },
314 "node_modules/bare-dgram": {
315 "version": "1.0.1",
316 "resolved": "https://registry.npmjs.org/bare-dgram/-/bare-dgram-1.0.1.tgz",
317 "integrity": "sha512-EdsyRErrkWgN8fENdrDdXFEE9HAuJ/m6ehXz13fVj9JhdCaLWIA+L8o5aYNRLt66x08RlyG2vbrRAZoxGfcdlg==",
318 "license": "Apache-2.0",
319 "dependencies": {
320 "bare-events": "^2.5.0",
321 "udx-native": "^1.11.2"
322 }
323 },
324 "node_modules/bare-diagnostics-channel": {
325 "version": "1.1.0",
326 "resolved": "https://registry.npmjs.org/bare-diagnostics-channel/-/bare-diagnostics-channel-1.1.0.tgz",
327 "integrity": "sha512-Reu+EQo+eLpB+a5p8UykFEdXndFaRaSgKV38uAMh/qhE2eTeJcdwAwo74hKYyeN8GD3DIFqC9ZlM4bnDc03CIg==",
328 "license": "Apache-2.0"
329 },
330 "node_modules/bare-dns": {
331 "version": "2.1.4",
332 "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz",
333 "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==",
334 "license": "Apache-2.0",
335 "engines": {
336 "bare": ">=1.7.0"
337 }
338 },
339 "node_modules/bare-encoding": {
340 "version": "1.0.3",
341 "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz",
342 "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==",
343 "license": "Apache-2.0",
344 "peerDependencies": {
345 "bare-buffer": "*"
346 },
347 "peerDependenciesMeta": {
348 "bare-buffer": {
349 "optional": true
350 }
351 }
352 },
353 "node_modules/bare-env": {
354 "version": "3.0.0",
355 "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz",
356 "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==",
357 "license": "Apache-2.0",
358 "dependencies": {
359 "bare-os": "^3.0.1"
360 }
361 },
362 "node_modules/bare-events": {
363 "version": "2.8.2",
364 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
365 "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
366 "license": "Apache-2.0",
367 "peerDependencies": {
368 "bare-abort-controller": "*"
369 },
370 "peerDependenciesMeta": {
371 "bare-abort-controller": {
372 "optional": true
373 }
374 }
375 },
376 "node_modules/bare-fetch": {
377 "version": "2.8.2",
378 "resolved": "https://registry.npmjs.org/bare-fetch/-/bare-fetch-2.8.2.tgz",
379 "integrity": "sha512-N0gZH8iuuC36Ygd4Kjfeo90aOKZ19cycbuZWVpm9W+f5v2ks/2qvgVIKp9hRDZSo5imqO097ynJcn4w24F8c/A==",
380 "license": "Apache-2.0",
381 "dependencies": {
382 "bare-form-data": "^1.2.0",
383 "bare-http1": "^4.5.2",
384 "bare-https": "^2.0.0",
385 "bare-stream": "^2.9.1",
386 "bare-url": "^2.4.0",
387 "bare-zlib": "^1.3.0"
388 },
389 "peerDependencies": {
390 "bare-abort-controller": "*",
391 "bare-buffer": "*"
392 },
393 "peerDependenciesMeta": {
394 "bare-abort-controller": {
395 "optional": true
396 },
397 "bare-buffer": {
398 "optional": true
399 }
400 }
401 },
402 "node_modules/bare-fetch/node_modules/bare-http1": {
403 "version": "4.5.5",
404 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz",
405 "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==",
406 "license": "Apache-2.0",
407 "dependencies": {
408 "bare-events": "^2.6.0",
409 "bare-http-parser": "^1.1.1",
410 "bare-stream": "^2.10.0",
411 "bare-tcp": "^2.2.0"
412 },
413 "peerDependencies": {
414 "bare-buffer": "*",
415 "bare-url": "*"
416 },
417 "peerDependenciesMeta": {
418 "bare-buffer": {
419 "optional": true
420 },
421 "bare-url": {
422 "optional": true
423 }
424 }
425 },
426 "node_modules/bare-form-data": {
427 "version": "1.2.1",
428 "resolved": "https://registry.npmjs.org/bare-form-data/-/bare-form-data-1.2.1.tgz",
429 "integrity": "sha512-DFZhp5Tn0S7TjII/xhgLl2BNU6f7l5QbIW/YH7TjzQVnxLa8SaMSbl77jpy0ZR1oUnAo0a7NHJ9QAOU6PPr0Jg==",
430 "license": "Apache-2.0",
431 "dependencies": {
432 "bare-buffer": "^3.6.0",
433 "bare-stream": "^2.6.5"
434 }
435 },
436 "node_modules/bare-format": {
437 "version": "1.0.2",
438 "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz",
439 "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==",
440 "license": "Apache-2.0",
441 "dependencies": {
442 "bare-inspect": "^3.0.0"
443 }
444 },
445 "node_modules/bare-fs": {
446 "version": "4.6.0",
447 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.6.0.tgz",
448 "integrity": "sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA==",
449 "license": "Apache-2.0",
450 "dependencies": {
451 "bare-events": "^2.5.4",
452 "bare-path": "^3.0.0",
453 "bare-stream": "^2.6.4",
454 "bare-url": "^2.2.2",
455 "fast-fifo": "^1.3.2"
456 },
457 "engines": {
458 "bare": ">=1.16.0"
459 },
460 "peerDependencies": {
461 "bare-buffer": "*"
462 },
463 "peerDependenciesMeta": {
464 "bare-buffer": {
465 "optional": true
466 }
467 }
468 },
469 "node_modules/bare-hrtime": {
470 "version": "2.1.1",
471 "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz",
472 "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==",
473 "license": "Apache-2.0"
474 },
475 "node_modules/bare-http-parser": {
476 "version": "1.1.3",
477 "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.3.tgz",
478 "integrity": "sha512-+dhVvQi6brHq14L/XHNRQ+TLuVE76VjRmMt61wVEtS+Od8xUslfMHWJN/ZjIIt3RtTG6vPuA+x9cOh7KrkBJsA==",
479 "license": "Apache-2.0"
480 },
481 "node_modules/bare-http1": {
482 "version": "2.0.5",
483 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-2.0.5.tgz",
484 "integrity": "sha512-MCaGha2DceFRGAOjrHV6zNYLa71kFR6KHpoc8K2T6ukeL2j7yjbsdFziQIF2mkMjz7RF75sfu1+rtgHEeIBenw==",
485 "license": "Apache-2.0",
486 "dependencies": {
487 "bare-events": "^2.0.0",
488 "streamx": "^2.13.0"
489 }
490 },
491 "node_modules/bare-https": {
492 "version": "2.1.3",
493 "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.3.tgz",
494 "integrity": "sha512-0TI/mJXQGYXmG7UUyWEG+KCJusayIAQLywUjFAskDoKuxfqVnGF+M/mTMrEV8J64DaIdU+5x761FvgmT7M68tA==",
495 "license": "Apache-2.0",
496 "dependencies": {
497 "bare-http1": "^4.4.0",
498 "bare-tcp": "^2.2.0",
499 "bare-tls": "^2.0.0"
500 }
501 },
502 "node_modules/bare-https/node_modules/bare-http1": {
503 "version": "4.5.5",
504 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz",
505 "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==",
506 "license": "Apache-2.0",
507 "dependencies": {
508 "bare-events": "^2.6.0",
509 "bare-http-parser": "^1.1.1",
510 "bare-stream": "^2.10.0",
511 "bare-tcp": "^2.2.0"
512 },
513 "peerDependencies": {
514 "bare-buffer": "*",
515 "bare-url": "*"
516 },
517 "peerDependenciesMeta": {
518 "bare-buffer": {
519 "optional": true
520 },
521 "bare-url": {
522 "optional": true
523 }
524 }
525 },
526 "node_modules/bare-inspect": {
527 "version": "3.1.4",
528 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz",
529 "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==",
530 "license": "Apache-2.0",
531 "dependencies": {
532 "bare-ansi-escapes": "^2.1.0",
533 "bare-type": "^1.0.0"
534 },
535 "engines": {
536 "bare": ">=1.18.0"
537 }
538 },
539 "node_modules/bare-inspector": {
540 "version": "4.0.3",
541 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz",
542 "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==",
543 "dev": true,
544 "license": "Apache-2.0",
545 "dependencies": {
546 "bare-events": "^2.1.0",
547 "bare-http1": "^4.0.0",
548 "bare-stream": "^2.0.0",
549 "bare-url": "^2.0.0",
550 "bare-ws": "^2.0.0"
551 },
552 "engines": {
553 "bare": ">=1.2.0"
554 },
555 "peerDependencies": {
556 "bare-tcp": "*"
557 },
558 "peerDependenciesMeta": {
559 "bare-tcp": {
560 "optional": true
561 }
562 }
563 },
564 "node_modules/bare-inspector/node_modules/bare-http1": {
565 "version": "4.5.5",
566 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz",
567 "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==",
568 "dev": true,
569 "license": "Apache-2.0",
570 "dependencies": {
571 "bare-events": "^2.6.0",
572 "bare-http-parser": "^1.1.1",
573 "bare-stream": "^2.10.0",
574 "bare-tcp": "^2.2.0"
575 },
576 "peerDependencies": {
577 "bare-buffer": "*",
578 "bare-url": "*"
579 },
580 "peerDependenciesMeta": {
581 "bare-buffer": {
582 "optional": true
583 },
584 "bare-url": {
585 "optional": true
586 }
587 }
588 },
589 "node_modules/bare-logger": {
590 "version": "2.0.3",
591 "resolved": "https://registry.npmjs.org/bare-logger/-/bare-logger-2.0.3.tgz",
592 "integrity": "sha512-U6K7NxHdeAHxEgHFDV8zXvgjgDZKQO6LlCrxQvUvlrZEVTnoezSImMWizi85rbZpVsbeI1ZWcLCRGnoIf5EV8Q==",
593 "license": "Apache-2.0",
594 "dependencies": {
595 "bare-format": "^1.0.0"
596 }
597 },
598 "node_modules/bare-module": {
599 "version": "6.1.3",
600 "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz",
601 "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==",
602 "license": "Apache-2.0",
603 "dependencies": {
604 "bare-bundle": "^1.3.0",
605 "bare-module-lexer": "^1.0.0",
606 "bare-module-resolve": "^1.8.0",
607 "bare-path": "^3.0.0",
608 "bare-url": "^2.0.1"
609 },
610 "engines": {
611 "bare": ">=1.23.0"
612 },
613 "peerDependencies": {
614 "bare-buffer": "*"
615 },
616 "peerDependenciesMeta": {
617 "bare-buffer": {
618 "optional": true
619 }
620 }
621 },
622 "node_modules/bare-module-lexer": {
623 "version": "1.4.7",
624 "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz",
625 "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==",
626 "license": "Apache-2.0",
627 "dependencies": {
628 "require-addon": "^1.0.2"
629 },
630 "peerDependencies": {
631 "bare-buffer": "*"
632 },
633 "peerDependenciesMeta": {
634 "bare-buffer": {
635 "optional": true
636 }
637 }
638 },
639 "node_modules/bare-module-resolve": {
640 "version": "1.12.1",
641 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz",
642 "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==",
643 "license": "Apache-2.0",
644 "dependencies": {
645 "bare-semver": "^1.0.0"
646 },
647 "peerDependencies": {
648 "bare-url": "*"
649 },
650 "peerDependenciesMeta": {
651 "bare-url": {
652 "optional": true
653 }
654 }
655 },
656 "node_modules/bare-module-traverse": {
657 "version": "2.0.1",
658 "resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.0.1.tgz",
659 "integrity": "sha512-1au+Og5p97T9b6Y7xmHZ7KtpW8vEYtz2jC2whmm+YJp46EaHfk26j91MmQhufdkR/8sdK1Q5p+P9A/Y5GrJg7Q==",
660 "license": "Apache-2.0",
661 "dependencies": {
662 "bare-addon-resolve": "^1.5.0",
663 "bare-module-lexer": "^1.4.0",
664 "bare-module-resolve": "^1.7.0"
665 },
666 "peerDependencies": {
667 "bare-buffer": "*",
668 "bare-url": "*"
669 },
670 "peerDependenciesMeta": {
671 "bare-buffer": {
672 "optional": true
673 },
674 "bare-url": {
675 "optional": true
676 }
677 }
678 },
679 "node_modules/bare-net": {
680 "version": "2.3.1",
681 "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.1.tgz",
682 "integrity": "sha512-MypSqDKpDU2Xt7FIfazn5yGvRnV09gFcIPHGWstW0gxuzA4tucTcwJSZeos97C4F89vtU5oGwXDN/HrGN6Y4Jw==",
683 "license": "Apache-2.0",
684 "dependencies": {
685 "bare-events": "^2.2.2",
686 "bare-pipe": "^4.0.0",
687 "bare-stream": "^2.0.0",
688 "bare-tcp": "^2.0.0"
689 }
690 },
691 "node_modules/bare-node-runtime": {
692 "version": "1.2.0",
693 "resolved": "https://registry.npmjs.org/bare-node-runtime/-/bare-node-runtime-1.2.0.tgz",
694 "integrity": "sha512-oCpkcKBLDuzhtga0Q/63Ds3Gj03I0jIf+VX4mDhSClpoBfz/wnDtl2d1NKQDAVNCoP4I617GrJkS3vwNO4dd7w==",
695 "license": "Apache-2.0",
696 "dependencies": {
697 "bare-abort-controller": "^1.0.0",
698 "bare-assert": "^1.1.0",
699 "bare-async-hooks": "^0.0.0",
700 "bare-buffer": "^3.3.1",
701 "bare-console": "^6.0.1",
702 "bare-crypto": "^1.11.2",
703 "bare-dgram": "^1.0.1",
704 "bare-diagnostics-channel": "^1.1.0",
705 "bare-dns": "^2.1.4",
706 "bare-events": "^2.7.0",
707 "bare-fetch": "^2.5.0",
708 "bare-fs": "^4.2.3",
709 "bare-http1": "^4.0.4",
710 "bare-https": "^2.0.0",
711 "bare-inspector": "^5.0.0",
712 "bare-module": "^6.1.2",
713 "bare-net": "^2.0.2",
714 "bare-os": "^3.6.2",
715 "bare-path": "^3.0.0",
716 "bare-performance": "^1.1.0",
717 "bare-process": "^4.2.1",
718 "bare-punycode": "^0.0.0",
719 "bare-querystring": "^1.0.0",
720 "bare-readline": "^1.1.0",
721 "bare-repl": "^6.0.1",
722 "bare-stream": "^2.7.0",
723 "bare-string-decoder": "^1.0.0",
724 "bare-subprocess": "^5.1.1",
725 "bare-timers": "^3.0.3",
726 "bare-tls": "^2.1.3",
727 "bare-tty": "^5.0.3",
728 "bare-url": "^2.2.2",
729 "bare-utils": "^1.5.1",
730 "bare-v8": "^1.0.1",
731 "bare-vm": "^1.0.0",
732 "bare-worker": "^4.0.0",
733 "bare-ws": "^2.1.0",
734 "bare-zlib": "^1.3.1"
735 }
736 },
737 "node_modules/bare-node-runtime/node_modules/bare-http1": {
738 "version": "4.5.5",
739 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz",
740 "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==",
741 "license": "Apache-2.0",
742 "dependencies": {
743 "bare-events": "^2.6.0",
744 "bare-http-parser": "^1.1.1",
745 "bare-stream": "^2.10.0",
746 "bare-tcp": "^2.2.0"
747 },
748 "peerDependencies": {
749 "bare-buffer": "*",
750 "bare-url": "*"
751 },
752 "peerDependenciesMeta": {
753 "bare-buffer": {
754 "optional": true
755 },
756 "bare-url": {
757 "optional": true
758 }
759 }
760 },
761 "node_modules/bare-node-runtime/node_modules/bare-inspector": {
762 "version": "5.0.0",
763 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-5.0.0.tgz",
764 "integrity": "sha512-blObRFPBK6yn0/wZbrRgkGcOuLUQUYVvkKbsEp5p3bWJB+uNrAh+qUqOijQHS6mZjGvyGCE86VERp3EiUR2TyA==",
765 "license": "Apache-2.0",
766 "dependencies": {
767 "bare-events": "^2.1.0",
768 "bare-http1": "^4.0.0",
769 "bare-stream": "^2.0.0",
770 "bare-url": "^2.0.0",
771 "bare-ws": "^2.0.0"
772 },
773 "engines": {
774 "bare": ">=1.2.0"
775 },
776 "peerDependencies": {
777 "bare-tcp": "*"
778 },
779 "peerDependenciesMeta": {
780 "bare-tcp": {
781 "optional": true
782 }
783 }
784 },
785 "node_modules/bare-os": {
786 "version": "3.8.7",
787 "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz",
788 "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==",
789 "license": "Apache-2.0",
790 "engines": {
791 "bare": ">=1.14.0"
792 }
793 },
794 "node_modules/bare-path": {
795 "version": "3.0.0",
796 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
797 "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
798 "license": "Apache-2.0",
799 "dependencies": {
800 "bare-os": "^3.0.1"
801 }
802 },
803 "node_modules/bare-performance": {
804 "version": "1.3.0",
805 "resolved": "https://registry.npmjs.org/bare-performance/-/bare-performance-1.3.0.tgz",
806 "integrity": "sha512-ITlHSQwsnJSPjpagTtPo043LRkFgcrl9SgPsVUBXZvTeGWuNLUfMRwmwAS+35i8qnIEinySoLxwhVoJNZAIF2g==",
807 "license": "Apache-2.0"
808 },
809 "node_modules/bare-pipe": {
810 "version": "4.1.5",
811 "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.5.tgz",
812 "integrity": "sha512-6OfxaG8JSkRh3Gc4hzHRsxNt+yu2PpN7lrv1V+T78GdknWQkVGwiEvu4m+1nbfk8cMVQ0TGxRvQ90XA4rhnTuw==",
813 "license": "Apache-2.0",
814 "dependencies": {
815 "bare-events": "^2.0.0",
816 "bare-stream": "^2.0.0"
817 },
818 "engines": {
819 "bare": ">=1.16.0"
820 }
821 },
822 "node_modules/bare-process": {
823 "version": "4.4.1",
824 "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.4.1.tgz",
825 "integrity": "sha512-JfcTtymq6akqM2bdyTsP4A4GYJceBzb91k/ECmFps75uFf6uO33SM1bOZsRAqyRXExabsQJLn5S41NBl8HTM4A==",
826 "license": "Apache-2.0",
827 "dependencies": {
828 "bare-abort": "^2.0.13",
829 "bare-env": "^3.0.0",
830 "bare-events": "^2.3.1",
831 "bare-hrtime": "^2.0.0",
832 "bare-os": "^3.7.1",
833 "bare-signals": "^4.0.0",
834 "bare-stdio": "^1.0.1"
835 }
836 },
837 "node_modules/bare-punycode": {
838 "version": "0.0.0",
839 "resolved": "https://registry.npmjs.org/bare-punycode/-/bare-punycode-0.0.0.tgz",
840 "integrity": "sha512-PC2Y6mGLytZPJCB9M7CvO5Zb6uWYVUZ+5t3L6vePFuFM6tdC6SsQ+sIsuf0Sa6LHBoWURX7I9yMMbPXMv7TIdQ==",
841 "license": "Apache-2.0",
842 "dependencies": {
843 "punycode": "^2.3.1"
844 }
845 },
846 "node_modules/bare-querystring": {
847 "version": "1.0.0",
848 "resolved": "https://registry.npmjs.org/bare-querystring/-/bare-querystring-1.0.0.tgz",
849 "integrity": "sha512-Kbqdjd4f1MxMtutXzraEKIXqXRfq9uWLwzUT5bLUh9TCiPznY9l6vHc4BUPNIa13DyH1VAlPy3iOxHzhqV2quQ==",
850 "license": "Apache-2.0"
851 },
852 "node_modules/bare-readline": {
853 "version": "1.3.1",
854 "resolved": "https://registry.npmjs.org/bare-readline/-/bare-readline-1.3.1.tgz",
855 "integrity": "sha512-QtSU4ZfgQcDI6AQssjDFqTiRe4rCiciMn+Yqx6siMJZBIftAIFrNSOK0sa5SBrmNv/a7CD9Dm0onUDCRyn5Fdg==",
856 "license": "Apache-2.0",
857 "dependencies": {
858 "bare-ansi-escapes": "^2.0.0",
859 "bare-stream": "^2.0.0"
860 }
861 },
862 "node_modules/bare-realm": {
863 "version": "2.0.1",
864 "resolved": "https://registry.npmjs.org/bare-realm/-/bare-realm-2.0.1.tgz",
865 "integrity": "sha512-kQbYU6AAQu4XBTQesuz2+PpjBx7MJzf5Qw3nSLzQCzbVglx7Ml85MtWEjUe1AeslXqrd+nFPHMEbL55ouISscA==",
866 "license": "Apache-2.0",
867 "engines": {
868 "bare": ">=1.5.0"
869 }
870 },
871 "node_modules/bare-repl": {
872 "version": "6.0.2",
873 "resolved": "https://registry.npmjs.org/bare-repl/-/bare-repl-6.0.2.tgz",
874 "integrity": "sha512-w2R+5LOrMl9UH/gcFjvdIpsB+SgyLr8cW4uJo5bAEzRbRpVA+ZTm8pMHYdj0uqIUBClP/6fkyW0MnB1XkxakmQ==",
875 "license": "Apache-2.0",
876 "dependencies": {
877 "bare-inspect": "^3.0.0",
878 "bare-module": "^6.0.1",
879 "bare-os": "^3.0.1",
880 "bare-path": "^3.0.0",
881 "bare-pipe": "^4.0.0",
882 "bare-readline": "^1.0.0",
883 "bare-stream": "^2.0.0",
884 "bare-tty": "^5.0.0"
885 }
886 },
887 "node_modules/bare-semver": {
888 "version": "1.0.2",
889 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz",
890 "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==",
891 "license": "Apache-2.0"
892 },
893 "node_modules/bare-signals": {
894 "version": "4.2.0",
895 "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz",
896 "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==",
897 "license": "Apache-2.0",
898 "dependencies": {
899 "bare-events": "^2.5.3",
900 "bare-os": "^3.3.1"
901 },
902 "engines": {
903 "bare": ">=1.7.0"
904 }
905 },
906 "node_modules/bare-stdio": {
907 "version": "1.0.2",
908 "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz",
909 "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==",
910 "license": "Apache-2.0",
911 "dependencies": {
912 "bare-fs": "^4.5.2",
913 "bare-pipe": "^4.1.5",
914 "bare-tty": "^5.0.3"
915 }
916 },
917 "node_modules/bare-stream": {
918 "version": "2.12.0",
919 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.12.0.tgz",
920 "integrity": "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==",
921 "license": "Apache-2.0",
922 "dependencies": {
923 "streamx": "^2.25.0",
924 "teex": "^1.0.1"
925 },
926 "peerDependencies": {
927 "bare-abort-controller": "*",
928 "bare-buffer": "*",
929 "bare-events": "*"
930 },
931 "peerDependenciesMeta": {
932 "bare-abort-controller": {
933 "optional": true
934 },
935 "bare-buffer": {
936 "optional": true
937 },
938 "bare-events": {
939 "optional": true
940 }
941 }
942 },
943 "node_modules/bare-string-decoder": {
944 "version": "1.0.0",
945 "resolved": "https://registry.npmjs.org/bare-string-decoder/-/bare-string-decoder-1.0.0.tgz",
946 "integrity": "sha512-FjFvfHo88U7borNQSj9ijP2JTb1asqY6K28OZrix4dF9iZf5D2wi1+99CgLlHT3HtYqdwxRhDAiKu8rVstt5rQ==",
947 "license": "Apache-2.0",
948 "dependencies": {
949 "text-decoder": "^1.2.3"
950 },
951 "peerDependencies": {
952 "bare-buffer": "*"
953 },
954 "peerDependenciesMeta": {
955 "bare-buffer": {
956 "optional": true
957 }
958 }
959 },
960 "node_modules/bare-structured-clone": {
961 "version": "1.5.3",
962 "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.5.3.tgz",
963 "integrity": "sha512-vC/YqGsp67ZeFnpyAskwaEIXtNscnCwFVKlSk0Oh2X3AqWT6H+7DC9vVI080FohUJeuUuB9UNx4UFdDcyQgZaw==",
964 "license": "Apache-2.0",
965 "dependencies": {
966 "bare-type": "^1.1.0",
967 "compact-encoding": "^2.15.0"
968 },
969 "engines": {
970 "bare": ">=1.2.0"
971 },
972 "peerDependencies": {
973 "bare-buffer": "*",
974 "bare-url": "*"
975 },
976 "peerDependenciesMeta": {
977 "bare-buffer": {
978 "optional": true
979 },
980 "bare-url": {
981 "optional": true
982 }
983 }
984 },
985 "node_modules/bare-stylize": {
986 "version": "0.0.1",
987 "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz",
988 "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==",
989 "license": "Apache-2.0",
990 "dependencies": {
991 "bare-ansi-escapes": "^2.2.3",
992 "bare-process": "^4.2.1"
993 }
994 },
995 "node_modules/bare-subprocess": {
996 "version": "5.2.3",
997 "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
998 "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
999 "license": "Apache-2.0",
1000 "dependencies": {
1001 "bare-env": "^3.0.0",
1002 "bare-events": "^2.5.4",
1003 "bare-os": "^3.0.1",
1004 "bare-pipe": "^4.0.0",
1005 "bare-url": "^2.2.2"
1006 },
1007 "engines": {
1008 "bare": ">=1.7.0"
1009 },
1010 "peerDependencies": {
1011 "bare-buffer": "*"
1012 },
1013 "peerDependenciesMeta": {
1014 "bare-buffer": {
1015 "optional": true
1016 }
1017 }
1018 },
1019 "node_modules/bare-system-logger": {
1020 "version": "1.0.3",
1021 "resolved": "https://registry.npmjs.org/bare-system-logger/-/bare-system-logger-1.0.3.tgz",
1022 "integrity": "sha512-HXTXZyhIIS4ZDYun4J9zKNQZDfLgEkkK8wy9nrTFJE5xq8APsOhDxuEsxP0YcNwC23DW1jaNpqD51zDeXPzIdg==",
1023 "license": "Apache-2.0",
1024 "dependencies": {
1025 "bare-logger": "^2.0.0"
1026 }
1027 },
1028 "node_modules/bare-tcp": {
1029 "version": "2.2.7",
1030 "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.7.tgz",
1031 "integrity": "sha512-rjpqNQ2cOCkNo3NeYA/W4GTK3DRkl8sDHO3uos+AEswUjLC8XXMQF8WrJCSjlIowCbS6NUVxKE92X5RGXjyefg==",
1032 "license": "Apache-2.0",
1033 "dependencies": {
1034 "bare-dns": "^2.0.4",
1035 "bare-events": "^2.5.4",
1036 "bare-stream": "^2.6.4"
1037 },
1038 "engines": {
1039 "bare": ">=1.16.0"
1040 }
1041 },
1042 "node_modules/bare-thread": {
1043 "version": "1.2.0",
1044 "resolved": "https://registry.npmjs.org/bare-thread/-/bare-thread-1.2.0.tgz",
1045 "integrity": "sha512-evYpeDqaTROp2JIdVgWrIBc5rVCV64tFMwZdeL13ahYt/lno2RLvTbYrcf1p6NA2p1SxybbSbGQ2F9/gHudrTg==",
1046 "license": "Apache-2.0",
1047 "dependencies": {
1048 "bare-bundle": "^1.9.0",
1049 "bare-module-resolve": "^1.11.2",
1050 "bare-module-traverse": "^2.0.0"
1051 }
1052 },
1053 "node_modules/bare-timers": {
1054 "version": "3.2.1",
1055 "resolved": "https://registry.npmjs.org/bare-timers/-/bare-timers-3.2.1.tgz",
1056 "integrity": "sha512-O+9e6Jol/BoYsQUzAFXG0gWUW1GWryFMblGcNBfi3smPrD3rJIKQwpw2FJARl2j/1VHne4a8YaHPK1cxDKfiYQ==",
1057 "license": "Apache-2.0",
1058 "engines": {
1059 "bare": ">=1.7.0"
1060 },
1061 "peerDependencies": {
1062 "bare-abort-controller": "*"
1063 },
1064 "peerDependenciesMeta": {
1065 "bare-abort-controller": {
1066 "optional": true
1067 }
1068 }
1069 },
1070 "node_modules/bare-tls": {
1071 "version": "2.2.1",
1072 "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.2.1.tgz",
1073 "integrity": "sha512-hZ+ZqwrUO4dyH7/6WYkYWjgAFNJKjzwEYJiDaMnMs+eRleBDjQ3CvNZawpkw0Ar9jnM9NZK6+f6GqjkZ2FLGmQ==",
1074 "license": "Apache-2.0",
1075 "dependencies": {
1076 "bare-net": "^2.0.1",
1077 "bare-stream": "^2.6.4"
1078 },
1079 "engines": {
1080 "bare": ">=1.7.0"
1081 }
1082 },
1083 "node_modules/bare-tty": {
1084 "version": "5.1.0",
1085 "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.0.tgz",
1086 "integrity": "sha512-EZLvW4A+XiJgI3TW+e1pMME9PIJsfEXe/DA/WSKzIkq/v7Yarpv/rvG6Z5pGnpo4V/Bd+qopwnCLSR71hMMBYA==",
1087 "license": "Apache-2.0",
1088 "dependencies": {
1089 "bare-events": "^2.2.0",
1090 "bare-signals": "^4.0.0",
1091 "bare-stream": "^2.0.0"
1092 },
1093 "engines": {
1094 "bare": ">=1.16.0"
1095 }
1096 },
1097 "node_modules/bare-type": {
1098 "version": "1.1.0",
1099 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz",
1100 "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==",
1101 "license": "Apache-2.0",
1102 "engines": {
1103 "bare": ">=1.2.0"
1104 }
1105 },
1106 "node_modules/bare-url": {
1107 "version": "2.4.0",
1108 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
1109 "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
1110 "license": "Apache-2.0",
1111 "peer": true,
1112 "dependencies": {
1113 "bare-path": "^3.0.0"
1114 }
1115 },
1116 "node_modules/bare-utils": {
1117 "version": "1.6.0",
1118 "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz",
1119 "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==",
1120 "license": "Apache-2.0",
1121 "dependencies": {
1122 "bare-debug-log": "^2.0.0",
1123 "bare-encoding": "^1.0.0",
1124 "bare-format": "^1.0.0",
1125 "bare-inspect": "^3.0.0",
1126 "bare-stylize": "^0.0.1",
1127 "bare-type": "^1.0.6"
1128 }
1129 },
1130 "node_modules/bare-v8": {
1131 "version": "1.0.1",
1132 "resolved": "https://registry.npmjs.org/bare-v8/-/bare-v8-1.0.1.tgz",
1133 "integrity": "sha512-/cR5ZvFWQRdtTZ4tx0j7TKvTWce8UnnLqm88fwHtJmfM7HODIBVjQGDT7KkDLeD2d/eHP2pzB71Y8/QyiMMKrQ==",
1134 "license": "Apache-2.0"
1135 },
1136 "node_modules/bare-v8-to-istanbul": {
1137 "version": "1.0.2",
1138 "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz",
1139 "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==",
1140 "dev": true,
1141 "license": "Apache-2.0",
1142 "dependencies": {
1143 "bare-assert": "^1.0.2",
1144 "bare-fs": "^4.1.2",
1145 "bare-module": "^6.1.2",
1146 "bare-path": "^3.0.0",
1147 "bare-process": "^4.2.0",
1148 "bare-url": "^2.1.5",
1149 "bare-utils": "^1.2.0",
1150 "v8-to-istanbul": "^9.3.0",
1151 "which-runtime": "^1.2.1"
1152 }
1153 },
1154 "node_modules/bare-vm": {
1155 "version": "1.0.1",
1156 "resolved": "https://registry.npmjs.org/bare-vm/-/bare-vm-1.0.1.tgz",
1157 "integrity": "sha512-yLnbRvKt3AhRTmtfTIrYfdTHqGEfIJc+Fgb2DcHejE0HJ+p5adGxxPMvd3893Z7iXVYnalxukNARn4oJSZELHQ==",
1158 "license": "Apache-2.0",
1159 "dependencies": {
1160 "bare-realm": "^2.0.0"
1161 }
1162 },
1163 "node_modules/bare-worker": {
1164 "version": "4.1.6",
1165 "resolved": "https://registry.npmjs.org/bare-worker/-/bare-worker-4.1.6.tgz",
1166 "integrity": "sha512-yvsektF7xNqlAyjJV+YYeBx+sGZ8Fhbo5Cjc/lpIdkM8Gd/e3MlaxTFg8NknkyvJ4oDJXqvp38Q+ZOn5Gyr+nQ==",
1167 "license": "Apache-2.0",
1168 "dependencies": {
1169 "bare-channel": "^5.1.5",
1170 "bare-events": "^2.2.1",
1171 "bare-module": "^6.0.1",
1172 "bare-thread": "^1.1.3"
1173 }
1174 },
1175 "node_modules/bare-ws": {
1176 "version": "2.1.0",
1177 "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.1.0.tgz",
1178 "integrity": "sha512-2gEWlPK9iyBchACdIY6oQXgmDz3KLrChdwrPgmU3IVXOFTnxTqSUT27WE/+izd4QojHj/SsqDkQiD2HDvuTdAA==",
1179 "license": "Apache-2.0",
1180 "dependencies": {
1181 "bare-crypto": "^1.2.0",
1182 "bare-events": "^2.3.1",
1183 "bare-http1": "^4.0.0",
1184 "bare-https": "^2.0.0",
1185 "bare-stream": "^2.1.2"
1186 },
1187 "peerDependencies": {
1188 "bare-buffer": "*",
1189 "bare-url": "*"
1190 },
1191 "peerDependenciesMeta": {
1192 "bare-buffer": {
1193 "optional": true
1194 },
1195 "bare-url": {
1196 "optional": true
1197 }
1198 }
1199 },
1200 "node_modules/bare-ws/node_modules/bare-http1": {
1201 "version": "4.5.5",
1202 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz",
1203 "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==",
1204 "license": "Apache-2.0",
1205 "dependencies": {
1206 "bare-events": "^2.6.0",
1207 "bare-http-parser": "^1.1.1",
1208 "bare-stream": "^2.10.0",
1209 "bare-tcp": "^2.2.0"
1210 },
1211 "peerDependencies": {
1212 "bare-buffer": "*",
1213 "bare-url": "*"
1214 },
1215 "peerDependenciesMeta": {
1216 "bare-buffer": {
1217 "optional": true
1218 },
1219 "bare-url": {
1220 "optional": true
1221 }
1222 }
1223 },
1224 "node_modules/bare-zlib": {
1225 "version": "1.3.3",
1226 "resolved": "https://registry.npmjs.org/bare-zlib/-/bare-zlib-1.3.3.tgz",
1227 "integrity": "sha512-rXNczo+SQg6cn20olmh/mUiGeJK9maipFH/zI/QwYgwhEmOns1R7fl1GV5apNO+aAp4x2d4uUa7HLhO4mhOnBQ==",
1228 "license": "Apache-2.0",
1229 "dependencies": {
1230 "bare-stream": "^2.0.0"
1231 }
1232 },
1233 "node_modules/big-sparse-array": {
1234 "version": "1.0.3",
1235 "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz",
1236 "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==",
1237 "dev": true,
1238 "license": "MIT"
1239 },
1240 "node_modules/bits-to-bytes": {
1241 "version": "1.3.0",
1242 "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz",
1243 "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==",
1244 "license": "ISC",
1245 "dependencies": {
1246 "b4a": "^1.5.0"
1247 }
1248 },
1249 "node_modules/blind-relay": {
1250 "version": "1.4.0",
1251 "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.4.0.tgz",
1252 "integrity": "sha512-6xt7fDfCs6eGmNNym6I9N42jmjcMQn2qwwOVnkP9ZnrkXFk6c4/tdO1xqRmDEzKzV8gigd+DVdCUG/RUYnen7Q==",
1253 "license": "Apache-2.0",
1254 "dependencies": {
1255 "b4a": "^1.6.4",
1256 "bare-events": "^2.2.0",
1257 "bits-to-bytes": "^1.3.0",
1258 "compact-encoding": "^2.12.0",
1259 "compact-encoding-bitfield": "^1.0.0",
1260 "protomux": "^3.5.1",
1261 "sodium-universal": "^5.0.0",
1262 "streamx": "^2.15.1"
1263 }
1264 },
1265 "node_modules/bogon": {
1266 "version": "1.2.0",
1267 "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.2.0.tgz",
1268 "integrity": "sha512-FqOBr/1VMzCOsoJd+fzNUMarUYki2+TKt07A2+xaulsNx4r53iJ7MV5k0jbqg7W2U0CsLqxCZOrFibdG6h6HSg==",
1269 "license": "MIT",
1270 "dependencies": {
1271 "compact-encoding": "^2.11.0",
1272 "compact-encoding-net": "^1.2.0"
1273 }
1274 },
1275 "node_modules/brittle": {
1276 "version": "3.19.1",
1277 "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz",
1278 "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==",
1279 "dev": true,
1280 "license": "Apache-2.0",
1281 "dependencies": {
1282 "b4a": "^1.6.0",
1283 "bare-assert": "^1.0.2",
1284 "bare-cov": "^1.1.0",
1285 "bare-fs": "^4.1.6",
1286 "bare-os": "^3.6.1",
1287 "bare-path": "^3.0.0",
1288 "bare-process": "^4.2.1",
1289 "bare-subprocess": "^5.0.0",
1290 "bare-url": "^2.1.6",
1291 "error-stack-parser": "^2.1.4",
1292 "globbie": "^1.0.2",
1293 "paparam": "^1.6.2",
1294 "same-object": "^1.0.2",
1295 "test-tmp": "^1.4.0",
1296 "tmatch": "^5.0.0"
1297 },
1298 "bin": {
1299 "brittle": "bin/node.js",
1300 "brittle-bare": "bin/bare.js",
1301 "brittle-node": "bin/node.js",
1302 "brittle-pear": "bin/pear.js"
1303 }
1304 },
1305 "node_modules/compact-encoding": {
1306 "version": "2.19.2",
1307 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.2.tgz",
1308 "integrity": "sha512-/YjhHQE/5L4F7l5Bht69dRbP9RV6zoJPeowi8bMKQxNKe3Nh6hOY8pBGoVE9fz5GaWfEd8fWJ2aU9sB4KZuMYg==",
1309 "license": "Apache-2.0",
1310 "dependencies": {
1311 "b4a": "^1.3.0"
1312 }
1313 },
1314 "node_modules/compact-encoding-bitfield": {
1315 "version": "1.0.0",
1316 "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.0.0.tgz",
1317 "integrity": "sha512-3nMVKUg+PF72UHfainmCL8uKvyWfxsjqOtUY+HiMPGLPCTjnwzoKfFAMo1Ad7nwTPdjBqtGK5b3BOFTFW4EBTg==",
1318 "license": "ISC",
1319 "dependencies": {
1320 "compact-encoding": "^2.4.1"
1321 }
1322 },
1323 "node_modules/compact-encoding-net": {
1324 "version": "1.2.0",
1325 "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.2.0.tgz",
1326 "integrity": "sha512-LVXpNpF7PGQeHRVVLGgYWzuVoYAaDZvKUsUxRioGfkotzvOh4AzoQF1HBH3zMNaSnx7gJXuUr3hkjnijaH/Eng==",
1327 "license": "ISC",
1328 "dependencies": {
1329 "compact-encoding": "^2.4.1"
1330 }
1331 },
1332 "node_modules/convert-source-map": {
1333 "version": "2.0.0",
1334 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1335 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1336 "dev": true,
1337 "license": "MIT"
1338 },
1339 "node_modules/corestore": {
1340 "version": "7.9.2",
1341 "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.9.2.tgz",
1342 "integrity": "sha512-dyIktVSVLu0skZ1UM4yODbNbZ46dBpTHypzwlwET1btVuS3YIf66Zz8Tx8h3Xa8nbLO2USGqrYtvPYzSRj2wCA==",
1343 "dev": true,
1344 "license": "MIT",
1345 "dependencies": {
1346 "b4a": "^1.6.7",
1347 "hypercore": "^11.19.0",
1348 "hypercore-crypto": "^3.4.2",
1349 "hypercore-errors": "^1.4.0",
1350 "hypercore-id-encoding": "^1.3.0",
1351 "ready-resource": "^1.1.1",
1352 "sodium-universal": "^5.0.1",
1353 "which-runtime": "^1.2.1"
1354 }
1355 },
1356 "node_modules/device-file": {
1357 "version": "2.3.1",
1358 "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz",
1359 "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==",
1360 "dev": true,
1361 "license": "Apache-2.0",
1362 "dependencies": {
1363 "b4a": "^1.6.7",
1364 "bare-fs": "^4.0.1",
1365 "bare-path": "^3.0.0",
1366 "fd-lock": "^2.1.0",
1367 "fs-native-extensions": "^1.4.0",
1368 "ready-resource": "^1.2.0"
1369 }
1370 },
1371 "node_modules/dht-rpc": {
1372 "version": "6.26.3",
1373 "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.26.3.tgz",
1374 "integrity": "sha512-KuLfRv/hecUHipQcTXHpVv4/N4Jhpww5sLdsrn3Edm5oHwzK9SgNV34hNt7a2aVZCOcG5SfP4AvcQ7pI+y9YNg==",
1375 "license": "MIT",
1376 "dependencies": {
1377 "adaptive-timeout": "^1.0.1",
1378 "b4a": "^1.6.1",
1379 "bare-events": "^2.2.0",
1380 "compact-encoding": "^2.11.0",
1381 "compact-encoding-net": "^1.2.0",
1382 "fast-fifo": "^1.1.0",
1383 "kademlia-routing-table": "^1.0.1",
1384 "nat-sampler": "^1.0.1",
1385 "sodium-universal": "^5.0.0",
1386 "streamx": "^2.13.2",
1387 "time-ordered-set": "^2.0.0",
1388 "udx-native": "^1.5.3"
1389 }
1390 },
1391 "node_modules/error-stack-parser": {
1392 "version": "2.1.4",
1393 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
1394 "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
1395 "dev": true,
1396 "license": "MIT",
1397 "dependencies": {
1398 "stackframe": "^1.3.4"
1399 }
1400 },
1401 "node_modules/events-universal": {
1402 "version": "1.0.1",
1403 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
1404 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
1405 "license": "Apache-2.0",
1406 "dependencies": {
1407 "bare-events": "^2.7.0"
1408 }
1409 },
1410 "node_modules/fast-fifo": {
1411 "version": "1.3.2",
1412 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
1413 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
1414 "license": "MIT"
1415 },
1416 "node_modules/fd-lock": {
1417 "version": "2.1.1",
1418 "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.1.1.tgz",
1419 "integrity": "sha512-H3VkcWFl39Rk0xBokDcUyBcVs6VrYTUo/DhDWMzJOF99wXWDAvmvq/GlLTS2NTehsznZhp3fXVyrtB2ldDXhwg==",
1420 "dev": true,
1421 "license": "Apache-2.0",
1422 "dependencies": {
1423 "bare-fs": "^4.5.0",
1424 "fs-native-extensions": "^1.4.4",
1425 "ready-resource": "^1.2.0",
1426 "resource-on-exit": "^1.0.0"
1427 }
1428 },
1429 "node_modules/flat-tree": {
1430 "version": "1.13.0",
1431 "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz",
1432 "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==",
1433 "dev": true,
1434 "license": "MIT"
1435 },
1436 "node_modules/fs-native-extensions": {
1437 "version": "1.4.5",
1438 "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz",
1439 "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==",
1440 "dev": true,
1441 "license": "Apache-2.0",
1442 "dependencies": {
1443 "require-addon": "^1.1.0",
1444 "which-runtime": "^1.2.0"
1445 }
1446 },
1447 "node_modules/generate-object-property": {
1448 "version": "2.0.0",
1449 "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz",
1450 "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==",
1451 "dev": true,
1452 "license": "MIT",
1453 "dependencies": {
1454 "is-property": "^1.0.0"
1455 }
1456 },
1457 "node_modules/generate-string": {
1458 "version": "1.0.1",
1459 "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz",
1460 "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==",
1461 "dev": true,
1462 "license": "MIT"
1463 },
1464 "node_modules/globbie": {
1465 "version": "1.0.3",
1466 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz",
1467 "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==",
1468 "dev": true,
1469 "license": "Apache-2.0",
1470 "dependencies": {
1471 "bare-fs": "^4.1.2",
1472 "bare-path": "^3.0.0",
1473 "bare-process": "^4.2.1",
1474 "picomatch": "^4.0.2"
1475 }
1476 },
1477 "node_modules/hyper-overlay": {
1478 "resolved": "../hyper-overlay",
1479 "link": true
1480 },
1481 "node_modules/hypercore": {
1482 "version": "11.28.1",
1483 "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.28.1.tgz",
1484 "integrity": "sha512-d8jqUilG7Gri8wT+sRdkFTYRPMHGyvBz/NCYHf5rX7+LoivRNRxQm5nWChT7caiUKbcsDak6h7IiEQ5wbD1wCg==",
1485 "dev": true,
1486 "license": "MIT",
1487 "dependencies": {
1488 "@hyperswarm/secret-stream": "^6.0.0",
1489 "b4a": "^1.1.0",
1490 "bare-events": "^2.2.0",
1491 "big-sparse-array": "^1.0.3",
1492 "compact-encoding": "^2.11.0",
1493 "fast-fifo": "^1.3.0",
1494 "flat-tree": "^1.9.0",
1495 "hypercore-crypto": "^3.2.1",
1496 "hypercore-errors": "^1.5.0",
1497 "hypercore-id-encoding": "^1.2.0",
1498 "hypercore-storage": "^2.8.0",
1499 "is-options": "^1.0.1",
1500 "nanoassert": "^2.0.0",
1501 "protomux": "^3.5.0",
1502 "quickbit-universal": "^2.2.0",
1503 "random-array-iterator": "^1.0.0",
1504 "safety-catch": "^1.0.1",
1505 "sodium-universal": "^5.0.1",
1506 "streamx": "^2.12.4",
1507 "unslab": "^1.3.0",
1508 "z32": "^1.0.0"
1509 }
1510 },
1511 "node_modules/hypercore-crypto": {
1512 "version": "3.6.1",
1513 "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz",
1514 "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==",
1515 "license": "MIT",
1516 "dependencies": {
1517 "b4a": "^1.6.6",
1518 "compact-encoding": "^2.15.0",
1519 "sodium-universal": "^5.0.0"
1520 }
1521 },
1522 "node_modules/hypercore-errors": {
1523 "version": "1.5.0",
1524 "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz",
1525 "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==",
1526 "dev": true,
1527 "license": "Apache-2.0",
1528 "dependencies": {
1529 "hypercore-id-encoding": "^1.3.0"
1530 }
1531 },
1532 "node_modules/hypercore-id-encoding": {
1533 "version": "1.3.0",
1534 "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz",
1535 "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==",
1536 "license": "Apache-2.0",
1537 "dependencies": {
1538 "b4a": "^1.5.3",
1539 "z32": "^1.0.0"
1540 }
1541 },
1542 "node_modules/hypercore-storage": {
1543 "version": "2.8.0",
1544 "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-2.8.0.tgz",
1545 "integrity": "sha512-s35HllFMYvrkSHvkIIklVFmCI/bRVNO7H3pi4mKvWl0XLyhGGGM+NtJ7fpsh82oPRCmL9XtMRO+pT6LiMAO8MA==",
1546 "dev": true,
1547 "license": "Apache-2.0",
1548 "dependencies": {
1549 "b4a": "^1.6.7",
1550 "bare-fs": "^4.0.1",
1551 "bare-path": "^3.0.0",
1552 "compact-encoding": "^2.16.0",
1553 "device-file": "^2.1.2",
1554 "flat-tree": "^1.12.1",
1555 "hypercore-crypto": "^3.4.2",
1556 "hyperschema": "^1.7.0",
1557 "index-encoder": "^3.3.2",
1558 "resolve-reject-promise": "^1.0.0",
1559 "rocksdb-native": "^3.11.0",
1560 "scope-lock": "^1.2.4",
1561 "streamx": "^2.21.1"
1562 }
1563 },
1564 "node_modules/hyperdht": {
1565 "version": "6.29.6",
1566 "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.6.tgz",
1567 "integrity": "sha512-bMMtw02fhiALdsTVLNZ/VFmMZuEY8kxb0/Rprl6oYF4/TCS653i3FDjtJeRbqtnAzwAj6dVZ/EwhoNOSCFGaYQ==",
1568 "license": "MIT",
1569 "dependencies": {
1570 "@hyperswarm/secret-stream": "^6.6.2",
1571 "b4a": "^1.3.1",
1572 "bare-events": "^2.2.0",
1573 "blind-relay": "^1.3.0",
1574 "bogon": "^1.0.0",
1575 "compact-encoding": "^2.4.1",
1576 "dht-rpc": "^6.15.1",
1577 "hypercore-crypto": "^3.3.0",
1578 "hypercore-id-encoding": "^1.2.0",
1579 "noise-curve-ed": "^2.0.0",
1580 "noise-handshake": "^4.0.0",
1581 "record-cache": "^1.1.1",
1582 "safety-catch": "^1.0.1",
1583 "signal-promise": "^1.0.3",
1584 "sodium-universal": "^5.0.1",
1585 "streamx": "^2.16.1",
1586 "unslab": "^1.3.0",
1587 "xache": "^1.1.0"
1588 },
1589 "bin": {
1590 "hyperdht": "bin.js"
1591 }
1592 },
1593 "node_modules/hyperschema": {
1594 "version": "1.20.1",
1595 "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.20.1.tgz",
1596 "integrity": "sha512-7gnvaTNUs8FqdrU3NNE6nzhOneGyDlUlpROu9YFlHA61fE8ppKKf2gLZjZjsx/enNt+VFN4ITxy8ijypfyWBLw==",
1597 "dev": true,
1598 "license": "Apache-2.0",
1599 "dependencies": {
1600 "bare-fs": "^4.0.1",
1601 "compact-encoding": "^2.19.0",
1602 "generate-object-property": "^2.0.0",
1603 "generate-string": "^1.0.1"
1604 }
1605 },
1606 "node_modules/hyperswarm": {
1607 "version": "4.17.0",
1608 "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz",
1609 "integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==",
1610 "license": "MIT",
1611 "dependencies": {
1612 "b4a": "^1.3.1",
1613 "bare-events": "^2.2.0",
1614 "hyperdht": "^6.21.0",
1615 "safety-catch": "^1.0.2",
1616 "shuffled-priority-queue": "^2.1.0",
1617 "streamx": "^2.22.1",
1618 "unslab": "^1.3.0"
1619 }
1620 },
1621 "node_modules/index-encoder": {
1622 "version": "3.5.0",
1623 "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz",
1624 "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==",
1625 "dev": true,
1626 "license": "Apache-2.0",
1627 "dependencies": {
1628 "b4a": "^1.6.4"
1629 }
1630 },
1631 "node_modules/is-options": {
1632 "version": "1.0.2",
1633 "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz",
1634 "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==",
1635 "dev": true,
1636 "license": "MIT",
1637 "dependencies": {
1638 "b4a": "^1.1.1"
1639 }
1640 },
1641 "node_modules/is-property": {
1642 "version": "1.0.2",
1643 "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
1644 "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
1645 "dev": true,
1646 "license": "MIT"
1647 },
1648 "node_modules/kademlia-routing-table": {
1649 "version": "1.0.6",
1650 "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz",
1651 "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==",
1652 "license": "MIT",
1653 "dependencies": {
1654 "bare-events": "^2.2.0"
1655 }
1656 },
1657 "node_modules/nanoassert": {
1658 "version": "2.0.0",
1659 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
1660 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
1661 "license": "ISC"
1662 },
1663 "node_modules/nat-sampler": {
1664 "version": "1.0.1",
1665 "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz",
1666 "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==",
1667 "license": "MIT"
1668 },
1669 "node_modules/noise-curve-ed": {
1670 "version": "2.1.0",
1671 "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz",
1672 "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==",
1673 "license": "ISC",
1674 "dependencies": {
1675 "b4a": "^1.1.0",
1676 "nanoassert": "^2.0.0",
1677 "sodium-universal": "^5.0.0"
1678 }
1679 },
1680 "node_modules/noise-handshake": {
1681 "version": "4.2.0",
1682 "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz",
1683 "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==",
1684 "license": "Apache-2.0",
1685 "dependencies": {
1686 "b4a": "^1.1.0",
1687 "nanoassert": "^2.0.0",
1688 "sodium-universal": "^5.0.0"
1689 }
1690 },
1691 "node_modules/paparam": {
1692 "version": "1.10.1",
1693 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz",
1694 "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==",
1695 "dev": true,
1696 "license": "Apache-2.0"
1697 },
1698 "node_modules/picomatch": {
1699 "version": "4.0.4",
1700 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
1701 "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
1702 "dev": true,
1703 "license": "MIT",
1704 "engines": {
1705 "node": ">=12"
1706 },
1707 "funding": {
1708 "url": "https://github.com/sponsors/jonschlinkert"
1709 }
1710 },
1711 "node_modules/protomux": {
1712 "version": "3.10.1",
1713 "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz",
1714 "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==",
1715 "license": "MIT",
1716 "dependencies": {
1717 "b4a": "^1.3.1",
1718 "compact-encoding": "^2.5.1",
1719 "queue-tick": "^1.0.0",
1720 "safety-catch": "^1.0.1",
1721 "unslab": "^1.3.0"
1722 }
1723 },
1724 "node_modules/punycode": {
1725 "version": "2.3.1",
1726 "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1727 "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1728 "license": "MIT",
1729 "engines": {
1730 "node": ">=6"
1731 }
1732 },
1733 "node_modules/queue-tick": {
1734 "version": "1.0.1",
1735 "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
1736 "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
1737 "license": "MIT"
1738 },
1739 "node_modules/quickbit-native": {
1740 "version": "2.4.8",
1741 "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz",
1742 "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==",
1743 "dev": true,
1744 "license": "Apache-2.0",
1745 "optional": true,
1746 "dependencies": {
1747 "require-addon": "^1.1.0"
1748 }
1749 },
1750 "node_modules/quickbit-universal": {
1751 "version": "2.2.0",
1752 "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz",
1753 "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==",
1754 "dev": true,
1755 "license": "ISC",
1756 "dependencies": {
1757 "b4a": "^1.6.0",
1758 "simdle-universal": "^1.1.0"
1759 },
1760 "optionalDependencies": {
1761 "quickbit-native": "^2.2.0"
1762 }
1763 },
1764 "node_modules/random-array-iterator": {
1765 "version": "1.0.0",
1766 "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz",
1767 "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==",
1768 "dev": true,
1769 "license": "MIT"
1770 },
1771 "node_modules/ready-resource": {
1772 "version": "1.2.0",
1773 "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz",
1774 "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==",
1775 "license": "MIT",
1776 "dependencies": {
1777 "bare-events": "^2.2.0"
1778 }
1779 },
1780 "node_modules/record-cache": {
1781 "version": "1.2.0",
1782 "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz",
1783 "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==",
1784 "license": "MIT",
1785 "dependencies": {
1786 "b4a": "^1.3.1"
1787 }
1788 },
1789 "node_modules/refcounter": {
1790 "version": "1.0.0",
1791 "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz",
1792 "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==",
1793 "dev": true,
1794 "license": "Apache-2.0"
1795 },
1796 "node_modules/require-addon": {
1797 "version": "1.2.0",
1798 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
1799 "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==",
1800 "license": "Apache-2.0",
1801 "dependencies": {
1802 "bare-addon-resolve": "^1.3.0"
1803 },
1804 "engines": {
1805 "bare": ">=1.10.0"
1806 }
1807 },
1808 "node_modules/resolve-reject-promise": {
1809 "version": "1.1.0",
1810 "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz",
1811 "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==",
1812 "dev": true,
1813 "license": "MIT"
1814 },
1815 "node_modules/resource-on-exit": {
1816 "version": "1.0.0",
1817 "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz",
1818 "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==",
1819 "dev": true,
1820 "license": "Apache-2.0"
1821 },
1822 "node_modules/rocksdb-native": {
1823 "version": "3.15.0",
1824 "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.15.0.tgz",
1825 "integrity": "sha512-czYx0hOzKAy+HYmJROZm7thvJ1tHFWHNkGI3nomumXWzrJghJdrWo4MIhehGX4OTff0lnOCoB2zu6ekzIKY0rQ==",
1826 "dev": true,
1827 "license": "Apache-2.0",
1828 "dependencies": {
1829 "compact-encoding": "^2.15.0",
1830 "ready-resource": "^1.0.0",
1831 "refcounter": "^1.0.0",
1832 "require-addon": "^1.0.2",
1833 "resolve-reject-promise": "^1.1.0",
1834 "signal-promise": "^1.0.3",
1835 "streamx": "^2.16.1"
1836 },
1837 "engines": {
1838 "bare": ">=1.16.0"
1839 }
1840 },
1841 "node_modules/safety-catch": {
1842 "version": "1.0.3",
1843 "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz",
1844 "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==",
1845 "license": "MIT"
1846 },
1847 "node_modules/same-object": {
1848 "version": "1.0.2",
1849 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
1850 "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==",
1851 "dev": true,
1852 "license": "MIT"
1853 },
1854 "node_modules/scope-lock": {
1855 "version": "1.2.4",
1856 "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz",
1857 "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==",
1858 "dev": true,
1859 "license": "Apache-2.0"
1860 },
1861 "node_modules/shuffled-priority-queue": {
1862 "version": "2.1.0",
1863 "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz",
1864 "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==",
1865 "license": "MIT",
1866 "dependencies": {
1867 "unordered-set": "^2.0.1"
1868 }
1869 },
1870 "node_modules/signal-promise": {
1871 "version": "1.0.3",
1872 "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz",
1873 "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==",
1874 "license": "MIT"
1875 },
1876 "node_modules/simdle-native": {
1877 "version": "1.3.9",
1878 "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz",
1879 "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==",
1880 "dev": true,
1881 "license": "Apache-2.0",
1882 "optional": true,
1883 "dependencies": {
1884 "b4a": "^1.6.0",
1885 "require-addon": "^1.1.0"
1886 }
1887 },
1888 "node_modules/simdle-universal": {
1889 "version": "1.1.2",
1890 "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz",
1891 "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==",
1892 "dev": true,
1893 "license": "ISC",
1894 "dependencies": {
1895 "b4a": "^1.6.0"
1896 },
1897 "optionalDependencies": {
1898 "simdle-native": "^1.1.1"
1899 }
1900 },
1901 "node_modules/sodium-native": {
1902 "version": "5.1.0",
1903 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
1904 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
1905 "license": "MIT",
1906 "dependencies": {
1907 "bare-assert": "^1.2.0",
1908 "require-addon": "^1.1.0",
1909 "which-runtime": "^1.2.1"
1910 },
1911 "engines": {
1912 "bare": ">=1.16.0"
1913 }
1914 },
1915 "node_modules/sodium-secretstream": {
1916 "version": "1.2.0",
1917 "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz",
1918 "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==",
1919 "license": "MIT",
1920 "dependencies": {
1921 "b4a": "^1.1.1",
1922 "sodium-universal": "^5.0.0"
1923 }
1924 },
1925 "node_modules/sodium-universal": {
1926 "version": "5.0.1",
1927 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
1928 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
1929 "license": "MIT",
1930 "dependencies": {
1931 "sodium-native": "^5.0.1"
1932 },
1933 "peerDependencies": {
1934 "sodium-javascript": "~0.8.0"
1935 },
1936 "peerDependenciesMeta": {
1937 "sodium-javascript": {
1938 "optional": true
1939 }
1940 }
1941 },
1942 "node_modules/stackframe": {
1943 "version": "1.3.4",
1944 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
1945 "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
1946 "dev": true,
1947 "license": "MIT"
1948 },
1949 "node_modules/streamx": {
1950 "version": "2.25.0",
1951 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz",
1952 "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==",
1953 "license": "MIT",
1954 "dependencies": {
1955 "events-universal": "^1.0.0",
1956 "fast-fifo": "^1.3.2",
1957 "text-decoder": "^1.1.0"
1958 }
1959 },
1960 "node_modules/teex": {
1961 "version": "1.0.1",
1962 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
1963 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
1964 "license": "MIT",
1965 "dependencies": {
1966 "streamx": "^2.12.5"
1967 }
1968 },
1969 "node_modules/test-tmp": {
1970 "version": "1.4.0",
1971 "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz",
1972 "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==",
1973 "dev": true,
1974 "license": "MIT",
1975 "dependencies": {
1976 "bare-fs": "^4.0.1",
1977 "bare-os": "^3.3.0",
1978 "bare-path": "^3.0.0"
1979 }
1980 },
1981 "node_modules/text-decoder": {
1982 "version": "1.2.7",
1983 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
1984 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
1985 "license": "Apache-2.0",
1986 "dependencies": {
1987 "b4a": "^1.6.4"
1988 }
1989 },
1990 "node_modules/time-ordered-set": {
1991 "version": "2.0.1",
1992 "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz",
1993 "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==",
1994 "license": "MIT"
1995 },
1996 "node_modules/timeout-refresh": {
1997 "version": "2.0.1",
1998 "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz",
1999 "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==",
2000 "license": "MIT"
2001 },
2002 "node_modules/tmatch": {
2003 "version": "5.0.0",
2004 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
2005 "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==",
2006 "dev": true,
2007 "license": "ISC",
2008 "engines": {
2009 "node": ">=8"
2010 }
2011 },
2012 "node_modules/udx-native": {
2013 "version": "1.19.2",
2014 "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.19.2.tgz",
2015 "integrity": "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g==",
2016 "license": "Apache-2.0",
2017 "dependencies": {
2018 "b4a": "^1.5.0",
2019 "bare-events": "^2.2.0",
2020 "require-addon": "^1.1.0",
2021 "streamx": "^2.22.0"
2022 },
2023 "engines": {
2024 "bare": ">=1.17.4"
2025 }
2026 },
2027 "node_modules/unordered-set": {
2028 "version": "2.0.1",
2029 "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz",
2030 "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==",
2031 "license": "MIT"
2032 },
2033 "node_modules/unslab": {
2034 "version": "1.3.0",
2035 "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz",
2036 "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==",
2037 "license": "Apache-2.0",
2038 "dependencies": {
2039 "b4a": "^1.6.6"
2040 }
2041 },
2042 "node_modules/v8-to-istanbul": {
2043 "version": "9.3.0",
2044 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
2045 "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
2046 "dev": true,
2047 "license": "ISC",
2048 "dependencies": {
2049 "@jridgewell/trace-mapping": "^0.3.12",
2050 "@types/istanbul-lib-coverage": "^2.0.1",
2051 "convert-source-map": "^2.0.0"
2052 },
2053 "engines": {
2054 "node": ">=10.12.0"
2055 }
2056 },
2057 "node_modules/which-runtime": {
2058 "version": "1.3.2",
2059 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz",
2060 "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==",
2061 "license": "Apache-2.0"
2062 },
2063 "node_modules/xache": {
2064 "version": "1.2.1",
2065 "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz",
2066 "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==",
2067 "license": "MIT"
2068 },
2069 "node_modules/z32": {
2070 "version": "1.1.0",
2071 "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz",
2072 "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==",
2073 "license": "MIT",
2074 "dependencies": {
2075 "b4a": "^1.5.3"
2076 }
2077 }
2078 }
2079}
package.json +32 -0
……@@ -0,0 +1,32 @@
1{
2 "name": "hyper-deploy",
3 "version": "0.1.0",
4 "description": "P2P release distribution — serve build artifacts to peers over Hyperswarm",
5 "type": "module",
6 "main": "index.js",
7 "bin": {
8 "hyper-deploy": "./bin/hyper-deploy.js"
9 },
10 "scripts": {
11 "test": "brittle test/*.test.js"
12 },
13 "dependencies": {
14 "@hyperswarm/secret-stream": "^6.0.0",
15 "bare-fs": "^4.0.0",
16 "bare-http1": "^2.0.0",
17 "bare-node-runtime": "^1.2.0",
18 "bare-os": "^3.0.0",
19 "bare-path": "^3.0.0",
20 "compact-encoding": "^2.0.0",
21 "hypercore-crypto": "^3.0.0",
22 "hyperswarm": "^4.0.0",
23 "protomux": "^3.0.0",
24 "ready-resource": "^1.0.0",
25 "safety-catch": "^1.0.3"
26 },
27 "devDependencies": {
28 "brittle": "^3.0.0",
29 "corestore": "^7.0.0",
30 "hyper-overlay": "file:../hyper-overlay"
31 }
32}
test/e2e-release-cross-machine.js +373 -0
……@@ -0,0 +1,373 @@
1#!/usr/bin/env bare
2/**
3 * hyper-deploy Phase 3: Cross-machine release distribution
4 *
5 * Server (tabertron): indexes a release directory, serves chunks over Hyperswarm
6 * Client (stortron): connects, pulls all files, verifies hashes
7 *
8 * Usage:
9 * Server: bare test/e2e-release-cross-machine.js server <dir>
10 * Client: bare test/e2e-release-cross-machine.js client <topic> <dest-dir>
11 */
12
13await import('bare-node-runtime/global')
14
15import Hyperswarm from 'hyperswarm'
16import Corestore from 'corestore'
17import Protomux from 'protomux'
18import c from 'compact-encoding'
19import crypto from 'hypercore-crypto'
20import fs from 'bare-fs'
21import path from 'bare-path'
22import os from 'bare-os'
23
24import { FileIndex } from 'hyper-overlay/file-index'
25import { TransferManager } from 'hyper-overlay/transfer'
26import { hashChunk } from 'hyper-overlay/chunker'
27
28const PROTOCOL = 'hyper-deploy-release'
29
30const args = process.argv.slice(2)
31const mode = args[0]
32
33if (!mode || !['server', 'client'].includes(mode)) {
34 console.log('Usage:')
35 console.log(' Server: bare test/e2e-release-cross-machine.js server <release-dir>')
36 console.log(' Client: bare test/e2e-release-cross-machine.js client <topic-hex> <dest-dir>')
37 process.exit(1)
38}
39
40// --- Helpers ---
41
42function walkDir (dir, prefix) {
43 prefix = prefix || ''
44 const results = []
45 for (const entry of fs.readdirSync(dir)) {
46 const full = path.join(dir, entry)
47 const rel = prefix ? prefix + '/' + entry : entry
48 try {
49 const stat = fs.statSync(full)
50 if (stat.isDirectory()) {
51 results.push(...walkDir(full, rel))
52 } else if (stat.isFile()) {
53 results.push({ path: rel, size: stat.size })
54 }
55 } catch {}
56 }
57 return results
58}
59
60function hashFile (filePath) {
61 const data = fs.readFileSync(filePath)
62 return crypto.data(data).toString('hex')
63}
64
65function formatBytes (bytes) {
66 if (bytes < 1024) return bytes + ' B'
67 if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'
68 if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB'
69 return (bytes / 1073741824).toFixed(2) + ' GB'
70}
71
72// ============================================================
73// SERVER
74// ============================================================
75
76async function runServer () {
77 const releaseDir = args[1]
78 if (!releaseDir) {
79 console.error('Usage: server <release-dir>')
80 process.exit(1)
81 }
82
83 const absDir = path.resolve(releaseDir)
84 if (!fs.existsSync(absDir)) {
85 console.error('Directory not found:', absDir)
86 process.exit(1)
87 }
88
89 console.log('[server] Scanning:', absDir)
90 const files = walkDir(absDir)
91 let totalSize = 0
92 for (const f of files) totalSize += f.size
93 console.log(`[server] ${files.length} files, ${formatBytes(totalSize)}`)
94
95 const storePath = path.join(os.tmpdir(), `deploy-srv-${Date.now()}`)
96 const store = new Corestore(storePath)
97 const fileIndex = new FileIndex(store)
98 const transfer = new TransferManager(fileIndex)
99 await fileIndex.ready()
100
101 // Index all files (metadata only — no data stored)
102 console.log('[server] Indexing...')
103 const indexStart = Date.now()
104 for (let i = 0; i < files.length; i++) {
105 const f = files[i]
106 const fullPath = path.join(absDir, f.path)
107 const contentHash = crypto.data(fs.readFileSync(fullPath)).toString('hex')
108 await fileIndex.putFile(f.path, {
109 contentHash,
110 size: f.size,
111 mtime: fs.statSync(fullPath).mtimeMs
112 })
113 if ((i + 1) % 1000 === 0) console.log(` indexed ${i + 1}/${files.length}`)
114 }
115 console.log(`[server] Indexed in ${((Date.now() - indexStart) / 1000).toFixed(1)}s`)
116
117 const swarm = new Hyperswarm()
118 const topic = crypto.randomBytes(32)
119
120 console.log(`[server] Topic: ${topic.toString('hex')}`)
121 console.log()
122 console.log('Run on client:')
123 console.log(` bare test/e2e-release-cross-machine.js client ${topic.toString('hex')} /tmp/deploy-test-output`)
124 console.log()
125
126 swarm.on('connection', (conn) => {
127 const peerKey = conn.remotePublicKey.toString('hex').slice(0, 12)
128 console.log(`[server] Peer connected: ${peerKey}`)
129
130 store.replicate(conn)
131 const mux = Protomux.from(conn)
132
133 let drainResolve = null
134
135 const ch = mux.createChannel({
136 protocol: PROTOCOL,
137 id: Buffer.from('release'),
138 ondrain () {
139 // Protomux calls this when the underlying stream drains
140 if (drainResolve) {
141 const resolve = drainResolve
142 drainResolve = null
143 resolve()
144 }
145 }
146 })
147 if (!ch) { console.error('[server] channel null'); return }
148
149 const fileListMsg = ch.addMessage({ encoding: c.raw })
150 const fileReqMsg = ch.addMessage({ encoding: c.raw })
151 const chunkInfoMsg = ch.addMessage({ encoding: c.raw })
152 const chunkDataMsg = ch.addMessage({ encoding: c.raw })
153 const controlMsg = ch.addMessage({ encoding: c.raw })
154
155 let bytesSent = 0
156 let filesSent = 0
157 let chunksSent = 0
158 let drainWaits = 0
159
160 // Handle file requests from client
161 fileReqMsg.onmessage = async (data) => {
162 const req = JSON.parse(data.toString())
163 const filePath = path.join(absDir, req.path)
164
165 const prepared = await transfer.prepareFile(filePath, req.path)
166 if (!prepared) {
167 controlMsg.send(Buffer.from(JSON.stringify({ type: 'error', path: req.path, msg: 'prepare failed' })))
168 return
169 }
170
171 // Send chunk info
172 chunkInfoMsg.send(Buffer.from(JSON.stringify({
173 path: req.path,
174 size: prepared.size,
175 chunks: prepared.chunks
176 })))
177
178 // Send chunks with proper backpressure via ondrain callback
179 const haveSet = new Set(req.have || [])
180
181 for (let i = 0; i < prepared.chunks.length; i++) {
182 if (haveSet.has(i)) continue
183
184 const bytes = transfer.readChunk(filePath, prepared.chunks[i].offset, prepared.chunks[i].length)
185 if (!bytes) continue
186
187 const header = Buffer.alloc(4)
188 header.writeUInt32LE(i)
189 const drained = chunkDataMsg.send(Buffer.concat([header, bytes]))
190 bytesSent += bytes.length
191 chunksSent++
192
193 if (!drained) {
194 // Wait for ondrain callback before continuing
195 drainWaits++
196 await new Promise(resolve => { drainResolve = resolve })
197 }
198 }
199
200 filesSent++
201 if (filesSent % 100 === 0 || filesSent === 1) {
202 console.log(`[server] sent ${filesSent} files, ${chunksSent} chunks, ${formatBytes(bytesSent)}, ${drainWaits} drain waits`)
203 }
204 }
205
206 // Handle control messages
207 controlMsg.onmessage = (data) => {
208 const msg = JSON.parse(data.toString())
209 if (msg.type === 'done') {
210 console.log(`[server] Client done. Sent ${filesSent} files, ${formatBytes(bytesSent)}`)
211 }
212 }
213
214 ch.open()
215
216 // Send file list
217 const fileList = files.map(f => ({ path: f.path, size: f.size }))
218 fileListMsg.send(Buffer.from(JSON.stringify(fileList)))
219 console.log(`[server] Sent file list (${files.length} files)`)
220 })
221
222 const discovery = swarm.join(topic, { server: true, client: false })
223 await discovery.flushed()
224 console.log('[server] Announced on DHT, waiting for client...')
225}
226
227// ============================================================
228// CLIENT
229// ============================================================
230
231async function runClient () {
232 const topicHex = args[1]
233 const destDir = args[2] || '/tmp/deploy-test-output'
234
235 if (!topicHex || topicHex.length !== 64) {
236 console.error('Usage: client <64-char-topic-hex> [dest-dir]')
237 process.exit(1)
238 }
239
240 const topic = Buffer.from(topicHex, 'hex')
241 fs.mkdirSync(destDir, { recursive: true })
242
243 console.log(`[client] Dest: ${destDir}`)
244 console.log(`[client] Connecting to topic: ${topicHex.slice(0, 16)}...`)
245
246 const storePath = path.join(os.tmpdir(), `deploy-cli-${Date.now()}`)
247 const store = new Corestore(storePath)
248 const fileIndex = new FileIndex(store)
249 const transfer = new TransferManager(fileIndex)
250 await fileIndex.ready()
251
252 const swarm = new Hyperswarm()
253 const startTime = Date.now()
254
255 let connectTimeout = null
256
257 swarm.on('connection', (conn) => {
258 if (connectTimeout) { clearTimeout(connectTimeout); connectTimeout = null }
259 const connectTime = Date.now() - startTime
260 const peerKey = conn.remotePublicKey.toString('hex').slice(0, 12)
261 console.log(`[client] Connected to server: ${peerKey} (${connectTime}ms)`)
262
263 store.replicate(conn)
264 const mux = Protomux.from(conn)
265
266 const ch = mux.createChannel({ protocol: PROTOCOL, id: Buffer.from('release') })
267 if (!ch) { console.error('[client] channel null'); return }
268
269 const fileListMsg = ch.addMessage({ encoding: c.raw })
270 const fileReqMsg = ch.addMessage({ encoding: c.raw })
271 const chunkInfoMsg = ch.addMessage({ encoding: c.raw })
272 const chunkDataMsg = ch.addMessage({ encoding: c.raw })
273 const controlMsg = ch.addMessage({ encoding: c.raw })
274
275 let fileList = []
276 let currentFile = null
277 let currentChunks = null
278 let filesCompleted = 0
279 let bytesReceived = 0
280 let chunksReceived = 0
281 let fileIdx = 0
282
283 function requestNextFile () {
284 if (fileIdx >= fileList.length) {
285 // All done
286 const elapsed = Date.now() - startTime
287 console.log()
288 console.log(`[client] === COMPLETE ===`)
289 console.log(`[client] Files: ${filesCompleted}/${fileList.length}`)
290 console.log(`[client] Bytes: ${formatBytes(bytesReceived)}`)
291 console.log(`[client] Chunks: ${chunksReceived}`)
292 console.log(`[client] Time: ${(elapsed / 1000).toFixed(1)}s`)
293 console.log(`[client] Speed: ${(bytesReceived / elapsed * 1000 / 1024 / 1024).toFixed(1)} MB/s`)
294
295 controlMsg.send(Buffer.from(JSON.stringify({ type: 'done' })))
296
297 setTimeout(() => {
298 swarm.destroy().then(() => store.close()).then(() => process.exit(0))
299 }, 2000)
300 return
301 }
302
303 const file = fileList[fileIdx]
304 fileReqMsg.send(Buffer.from(JSON.stringify({ path: file.path })))
305 fileIdx++
306 }
307
308 // Receive file list
309 fileListMsg.onmessage = (data) => {
310 fileList = JSON.parse(data.toString())
311 console.log(`[client] Received file list: ${fileList.length} files`)
312 requestNextFile()
313 }
314
315 // Receive chunk info for a file
316 chunkInfoMsg.onmessage = (data) => {
317 const info = JSON.parse(data.toString())
318 currentFile = info.path
319 currentChunks = info.chunks
320
321 const targetPath = path.join(destDir, currentFile)
322 transfer.startReceive(targetPath, { size: info.size, chunks: currentChunks })
323 }
324
325 // Receive chunk data
326 chunkDataMsg.onmessage = (data) => {
327 const index = data.readUInt32LE(0)
328 const chunkData = data.subarray(4)
329
330 const targetPath = path.join(destDir, currentFile)
331 const result = transfer.writeChunk(targetPath, index, chunkData)
332
333 if (!result.ok) {
334 console.error(`[client] writeChunk error: ${result.error}`)
335 return
336 }
337
338 chunksReceived++
339 bytesReceived += chunkData.length
340
341 if (transfer.isComplete(targetPath)) {
342 const fin = transfer.finalize(targetPath)
343 if (!fin.ok) {
344 console.error(`[client] finalize error: ${fin.error}`)
345 return
346 }
347 filesCompleted++
348
349 if (filesCompleted % 500 === 0 || filesCompleted === fileList.length) {
350 const elapsed = Date.now() - startTime
351 const pct = (filesCompleted / fileList.length * 100).toFixed(0)
352 console.log(`[client] ${pct}% — ${filesCompleted} files, ${formatBytes(bytesReceived)}, ${(elapsed / 1000).toFixed(0)}s`)
353 }
354
355 requestNextFile()
356 }
357 }
358
359 ch.open()
360 })
361
362 swarm.join(topic, { server: false, client: true })
363 await swarm.flush()
364 console.log('[client] Joined topic, searching for server...')
365
366 // No timeout — let the transfer run as long as it needs.
367 // Connection drops are detected by stream error/close events.
368}
369
370// ============================================================
371
372if (mode === 'server') runServer().catch(err => { console.error(err); process.exit(1) })
373else runClient().catch(err => { console.error(err); process.exit(1) })
test/e2e-release-sync.test.js +570 -0
……@@ -0,0 +1,570 @@
1/**
2 * hyper-deploy Phase 1: Local release distribution test
3 *
4 * Proves hyper-overlay's primitives can distribute build artifacts
5 * between two peers over Protomux. Uses SecretStream pairs (no network).
6 *
7 * This is a consumer of hyper-overlay — no modifications to hyper-overlay.
8 * The orchestration logic lives here in hyper-deploy.
9 */
10
11import test from 'brittle'
12import Corestore from 'corestore'
13import Protomux from 'protomux'
14import c from 'compact-encoding'
15import SecretStream from '@hyperswarm/secret-stream'
16import fs from 'bare-fs'
17import path from 'bare-path'
18import os from 'bare-os'
19import crypto from 'hypercore-crypto'
20
21// hyper-overlay as dependency (not modified)
22import { FileIndex } from 'hyper-overlay/file-index'
23import { TransferManager } from 'hyper-overlay/transfer'
24import { chunk, hashChunk } from 'hyper-overlay/chunker'
25
26const tmpdir = os.tmpdir()
27const PARTIAL_SUFFIX = '.overlay-partial'
28
29// --- Helpers ---
30
31function makePair () {
32 const a = new SecretStream(true)
33 const b = new SecretStream(false)
34 a.rawStream.pipe(b.rawStream).pipe(a.rawStream)
35 return [a, b]
36}
37
38function mktemp (prefix) {
39 const dir = path.join(tmpdir, `hyper-deploy-${prefix}-${Date.now()}`)
40 fs.mkdirSync(dir, { recursive: true })
41 return dir
42}
43
44/**
45 * Walk a directory recursively, return relative paths
46 */
47function walkDir (dir, prefix) {
48 prefix = prefix || ''
49 const results = []
50 for (const entry of fs.readdirSync(dir)) {
51 const full = path.join(dir, entry)
52 const rel = prefix ? prefix + '/' + entry : entry
53 const stat = fs.statSync(full)
54 if (stat.isDirectory()) {
55 results.push(...walkDir(full, rel))
56 } else {
57 results.push({ path: rel, size: stat.size, mtime: stat.mtimeMs })
58 }
59 }
60 return results
61}
62
63/**
64 * Compute content hash for a file
65 */
66function hashFile (filePath) {
67 const data = fs.readFileSync(filePath)
68 return crypto.data(data).toString('hex')
69}
70
71/**
72 * Index a directory: walk, hash, store in FileIndex + prepare chunk maps
73 */
74async function indexDirectory (dir, fileIndex, transferManager) {
75 const files = walkDir(dir)
76 let totalSize = 0
77
78 for (const file of files) {
79 const fullPath = path.join(dir, file.path)
80 const contentHash = hashFile(fullPath)
81
82 await fileIndex.putFile(file.path, {
83 contentHash,
84 size: file.size,
85 mtime: file.mtime
86 })
87
88 totalSize += file.size
89 }
90
91 return { fileCount: files.length, totalSize }
92}
93
94/**
95 * Release distribution over a single Protomux channel.
96 *
97 * Protocol (custom, minimal):
98 * Message 0 (fileList): server → client, JSON array of { path, contentHash, size }
99 * Message 1 (fileReq): client → server, JSON { path }
100 * Message 2 (chunkInfo): server → client, JSON { path, tier, chunks: [{hash, offset, length}] }
101 * Message 3 (chunkData): server → client, raw bytes prefixed with path+index header
102 * Message 4 (control): bidirectional, JSON { type, ... }
103 */
104const DEPLOY_PROTOCOL = 'hyper-deploy'
105
106// === Tests ===
107
108test('release distribution: small files', async (t) => {
109 t.timeout(30000)
110
111 // Create test release directory
112 const srcDir = mktemp('src')
113 for (let i = 0; i < 10; i++) {
114 fs.writeFileSync(path.join(srcDir, `file_${i}.txt`), `content for file ${i}\n`)
115 }
116
117 const dstDir = mktemp('dst')
118
119 // Setup stores
120 const storeA = new Corestore(mktemp('store-a'))
121 const storeB = new Corestore(mktemp('store-b'))
122
123 const fileIndexA = new FileIndex(storeA)
124 const fileIndexB = new FileIndex(storeB)
125 const transferA = new TransferManager(fileIndexA)
126 const transferB = new TransferManager(fileIndexB)
127
128 await fileIndexA.ready()
129 await fileIndexB.ready()
130
131 // Index source directory
132 const { fileCount, totalSize } = await indexDirectory(srcDir, fileIndexA, transferA)
133 t.is(fileCount, 10, 'indexed 10 files')
134
135 // Connect via SecretStream
136 const [sa, sb] = makePair()
137 storeA.replicate(sa)
138 storeB.replicate(sb)
139
140 const muxA = Protomux.from(sa)
141 const muxB = Protomux.from(sb)
142
143 // Transfer all files through Protomux
144 const transferDone = new Promise(async (resolve, reject) => {
145 const chA = muxA.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('test') })
146 const chB = muxB.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('test') })
147
148 const fileListMsg = chA.addMessage({ encoding: c.raw })
149 const fileReqMsg = chA.addMessage({ encoding: c.raw })
150 const chunkInfoMsg = chA.addMessage({ encoding: c.raw })
151 const chunkDataMsg = chA.addMessage({ encoding: c.raw })
152
153 const fileListMsgB = chB.addMessage({ encoding: c.raw })
154 const fileReqMsgB = chB.addMessage({ encoding: c.raw })
155 const chunkInfoMsgB = chB.addMessage({ encoding: c.raw })
156 const chunkDataMsgB = chB.addMessage({ encoding: c.raw })
157
158 let filesCompleted = 0
159 let bytesTransferred = 0
160 let pendingFiles = []
161
162 // CLIENT (B): receive file list, request each file
163 fileListMsgB.onmessage = (data) => {
164 const files = JSON.parse(data.toString())
165 pendingFiles = files
166 // Request first file
167 if (files.length > 0) {
168 fileReqMsgB.send(Buffer.from(JSON.stringify({ path: files[0].path })))
169 }
170 }
171
172 // CLIENT (B): receive chunk info, then receive chunks
173 let currentChunks = null
174 let currentPath = null
175
176 chunkInfoMsgB.onmessage = (data) => {
177 const info = JSON.parse(data.toString())
178 currentPath = info.path
179 currentChunks = info.chunks
180
181 const targetPath = path.join(dstDir, currentPath)
182 transferB.startReceive(targetPath, { size: info.size, chunks: currentChunks })
183 }
184
185 // CLIENT (B): receive chunk data
186 chunkDataMsgB.onmessage = (data) => {
187 // Header: 4 bytes index (uint32 LE) + rest is chunk data
188 const index = data.readUInt32LE(0)
189 const chunkData = data.subarray(4)
190
191 const targetPath = path.join(dstDir, currentPath)
192 const result = transferB.writeChunk(targetPath, index, chunkData)
193 if (!result.ok) {
194 reject(new Error(`writeChunk failed: ${result.error}`))
195 return
196 }
197
198 bytesTransferred += chunkData.length
199
200 if (transferB.isComplete(targetPath)) {
201 const fin = transferB.finalize(targetPath)
202 if (!fin.ok) {
203 reject(new Error(`finalize failed: ${fin.error}`))
204 return
205 }
206 filesCompleted++
207
208 // Request next file
209 if (filesCompleted < pendingFiles.length) {
210 fileReqMsgB.send(Buffer.from(JSON.stringify({ path: pendingFiles[filesCompleted].path })))
211 } else {
212 resolve({ filesCompleted, bytesTransferred })
213 }
214 }
215 }
216
217 // SERVER (A): handle file requests
218 fileReqMsg.onmessage = async (data) => {
219 const req = JSON.parse(data.toString())
220 const filePath = path.join(srcDir, req.path)
221
222 const prepared = await transferA.prepareFile(filePath, req.path)
223 if (!prepared) {
224 reject(new Error(`prepareFile failed for ${req.path}`))
225 return
226 }
227
228 // Send chunk info
229 chunkInfoMsg.send(Buffer.from(JSON.stringify({
230 path: req.path,
231 tier: prepared.tier,
232 size: prepared.size,
233 chunks: prepared.chunks
234 })))
235
236 // Send all chunks
237 for (let i = 0; i < prepared.chunks.length; i++) {
238 const chunkBytes = transferA.readChunk(filePath, prepared.chunks[i].offset, prepared.chunks[i].length)
239 if (!chunkBytes) {
240 reject(new Error(`readChunk failed for ${req.path} index ${i}`))
241 return
242 }
243
244 // Header: 4 bytes index + chunk data
245 const header = Buffer.alloc(4)
246 header.writeUInt32LE(i)
247 chunkDataMsg.send(Buffer.concat([header, chunkBytes]))
248 }
249 }
250
251 chA.open()
252 chB.open()
253
254 // Server sends file list
255 const files = await fileIndexA.listFiles()
256 const fileList = files.map(f => ({ path: f.path, contentHash: f.contentHash, size: f.size }))
257 fileListMsg.send(Buffer.from(JSON.stringify(fileList)))
258 })
259
260 const result = await transferDone
261
262 t.is(result.filesCompleted, 10, 'all 10 files transferred')
263 t.ok(result.bytesTransferred > 0, `transferred ${result.bytesTransferred} bytes`)
264
265 // Verify each file matches
266 for (let i = 0; i < 10; i++) {
267 const srcHash = hashFile(path.join(srcDir, `file_${i}.txt`))
268 const dstHash = hashFile(path.join(dstDir, `file_${i}.txt`))
269 t.is(srcHash, dstHash, `file_${i}.txt hash matches`)
270 }
271
272 t.teardown(async () => {
273 sa.destroy()
274 sb.destroy()
275 await storeA.close()
276 await storeB.close()
277 })
278})
279
280test('release distribution: large binary (100MB)', async (t) => {
281 t.timeout(60000)
282
283 const srcDir = mktemp('lg-src')
284 const dstDir = mktemp('lg-dst')
285
286 // Create 100MB file with random-ish data
287 const bigBuf = Buffer.alloc(100 * 1024 * 1024)
288 for (let i = 0; i < bigBuf.length; i += 4096) {
289 bigBuf.writeUInt32LE(i, i) // non-zero pattern so it actually chunks
290 }
291 fs.writeFileSync(path.join(srcDir, 'large.bin'), bigBuf)
292
293 const storeA = new Corestore(mktemp('lg-store-a'))
294 const storeB = new Corestore(mktemp('lg-store-b'))
295 const fileIndexA = new FileIndex(storeA)
296 const fileIndexB = new FileIndex(storeB)
297 const transferA = new TransferManager(fileIndexA)
298 const transferB = new TransferManager(fileIndexB)
299 await fileIndexA.ready()
300 await fileIndexB.ready()
301
302 const { fileCount } = await indexDirectory(srcDir, fileIndexA, transferA)
303 t.is(fileCount, 1)
304
305 const [sa, sb] = makePair()
306 storeA.replicate(sa)
307 storeB.replicate(sb)
308 const muxA = Protomux.from(sa)
309 const muxB = Protomux.from(sb)
310
311 const startTime = Date.now()
312
313 // Simpler single-file transfer
314 const transferDone = new Promise(async (resolve, reject) => {
315 const chA = muxA.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('lg') })
316 const chB = muxB.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('lg') })
317
318 const infoMsg = chA.addMessage({ encoding: c.raw })
319 const dataMsg = chA.addMessage({ encoding: c.raw })
320 const infoMsgB = chB.addMessage({ encoding: c.raw })
321 const dataMsgB = chB.addMessage({ encoding: c.raw })
322
323 let chunks = null
324 let totalChunks = 0
325 let receivedChunks = 0
326 let bytesRx = 0
327
328 infoMsgB.onmessage = (data) => {
329 const info = JSON.parse(data.toString())
330 chunks = info.chunks
331 totalChunks = chunks.length
332 const targetPath = path.join(dstDir, 'large.bin')
333 transferB.startReceive(targetPath, { size: info.size, chunks })
334 }
335
336 dataMsgB.onmessage = (data) => {
337 const index = data.readUInt32LE(0)
338 const chunkData = data.subarray(4)
339 const targetPath = path.join(dstDir, 'large.bin')
340
341 const result = transferB.writeChunk(targetPath, index, chunkData)
342 if (!result.ok) { reject(new Error(result.error)); return }
343
344 receivedChunks++
345 bytesRx += chunkData.length
346
347 if (transferB.isComplete(targetPath)) {
348 transferB.finalize(targetPath)
349 resolve({ totalChunks, bytesRx })
350 }
351 }
352
353 chA.open()
354 chB.open()
355
356 // Server prepares and sends
357 const filePath = path.join(srcDir, 'large.bin')
358 const prepared = await transferA.prepareFile(filePath, 'large.bin')
359
360 infoMsg.send(Buffer.from(JSON.stringify({
361 size: prepared.size,
362 chunks: prepared.chunks
363 })))
364
365 for (let i = 0; i < prepared.chunks.length; i++) {
366 const bytes = transferA.readChunk(filePath, prepared.chunks[i].offset, prepared.chunks[i].length)
367 const header = Buffer.alloc(4)
368 header.writeUInt32LE(i)
369 dataMsg.send(Buffer.concat([header, bytes]))
370 }
371 })
372
373 const result = await transferDone
374 const elapsed = Date.now() - startTime
375
376 t.ok(result.totalChunks > 0, `${result.totalChunks} chunks`)
377 t.is(result.bytesRx, 100 * 1024 * 1024, '100MB received')
378
379 // Verify hash
380 const srcHash = hashFile(path.join(srcDir, 'large.bin'))
381 const dstHash = hashFile(path.join(dstDir, 'large.bin'))
382 t.is(srcHash, dstHash, 'large binary hash matches')
383
384 t.comment(`100MB in ${elapsed}ms (${result.totalChunks} chunks, ${(result.bytesRx / elapsed * 1000 / 1024 / 1024).toFixed(0)} MB/s)`)
385
386 t.teardown(async () => {
387 sa.destroy()
388 sb.destroy()
389 await storeA.close()
390 await storeB.close()
391 })
392})
393
394test('release distribution: resumable after disconnect', async (t) => {
395 t.timeout(60000)
396
397 const srcDir = mktemp('res-src')
398 const dstDir = mktemp('res-dst')
399
400 // Create 50MB file — enough chunks to split meaningfully
401 const bigBuf = Buffer.alloc(50 * 1024 * 1024)
402 for (let i = 0; i < bigBuf.length; i += 4096) {
403 bigBuf.writeUInt32LE(i, i)
404 }
405 fs.writeFileSync(path.join(srcDir, 'resumable.bin'), bigBuf)
406
407 // Shared stores (persist across reconnects)
408 const storeA = new Corestore(mktemp('res-store-a'))
409 const storeB = new Corestore(mktemp('res-store-b'))
410 const fileIndexA = new FileIndex(storeA)
411 const fileIndexB = new FileIndex(storeB)
412 const transferA = new TransferManager(fileIndexA)
413
414 await fileIndexA.ready()
415 await fileIndexB.ready()
416 await indexDirectory(srcDir, fileIndexA, transferA)
417
418 // Prepare file once (get chunk info)
419 const filePath = path.join(srcDir, 'resumable.bin')
420 const prepared = await transferA.prepareFile(filePath, 'resumable.bin')
421 const totalChunks = prepared.chunks.length
422 const cutoff = Math.floor(totalChunks * 0.5)
423
424 t.comment(`total chunks: ${totalChunks}, will disconnect after ${cutoff}`)
425
426 // --- FIRST ATTEMPT: transfer ~50%, then disconnect ---
427 const targetPath = path.join(dstDir, 'resumable.bin')
428 const partialPath = path.join(dstDir, '.resumable.bin' + PARTIAL_SUFFIX)
429 let firstAttemptChunks = 0
430
431 {
432 const [sa, sb] = makePair()
433 storeA.replicate(sa)
434 storeB.replicate(sb)
435
436 const transferB1 = new TransferManager(fileIndexB)
437
438 await new Promise((resolve) => {
439 const muxA = Protomux.from(sa)
440 const muxB = Protomux.from(sb)
441 const chA = muxA.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('res1') })
442 const chB = muxB.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('res1') })
443
444 const infoMsg = chA.addMessage({ encoding: c.raw })
445 const dataMsg = chA.addMessage({ encoding: c.raw })
446 const infoMsgB = chB.addMessage({ encoding: c.raw })
447 const dataMsgB = chB.addMessage({ encoding: c.raw })
448
449 infoMsgB.onmessage = (data) => {
450 const info = JSON.parse(data.toString())
451 transferB1.startReceive(targetPath, { size: info.size, chunks: info.chunks })
452 }
453
454 dataMsgB.onmessage = (data) => {
455 const index = data.readUInt32LE(0)
456 const chunkData = data.subarray(4)
457 transferB1.writeChunk(targetPath, index, chunkData)
458 firstAttemptChunks++
459
460 if (firstAttemptChunks >= cutoff) {
461 sa.destroy()
462 sb.destroy()
463 resolve()
464 }
465 }
466
467 chA.open()
468 chB.open()
469
470 infoMsg.send(Buffer.from(JSON.stringify({ size: prepared.size, chunks: prepared.chunks })))
471
472 for (let i = 0; i < prepared.chunks.length; i++) {
473 const bytes = transferA.readChunk(filePath, prepared.chunks[i].offset, prepared.chunks[i].length)
474 const header = Buffer.alloc(4)
475 header.writeUInt32LE(i)
476 dataMsg.send(Buffer.concat([header, bytes]))
477 }
478 })
479
480 t.ok(firstAttemptChunks >= cutoff, `first attempt: ${firstAttemptChunks}/${totalChunks} chunks`)
481 t.ok(fs.existsSync(partialPath), 'partial file exists after disconnect')
482 }
483
484 // --- SECOND ATTEMPT: scan partial, send only missing ---
485 {
486 const [sa2, sb2] = makePair()
487 storeA.replicate(sa2)
488 storeB.replicate(sb2)
489
490 const transferB2 = new TransferManager(fileIndexB)
491
492 // Scan partial to find which chunks are already good
493 const haveSet = new Set()
494 const partialData = fs.readFileSync(partialPath)
495 for (let i = 0; i < prepared.chunks.length; i++) {
496 const ch = prepared.chunks[i]
497 const slice = partialData.subarray(ch.offset, ch.offset + ch.length)
498 if (slice.length === ch.length && hashChunk(slice) === ch.hash) {
499 haveSet.add(i)
500 }
501 }
502
503 const needed = []
504 for (let i = 0; i < prepared.chunks.length; i++) {
505 if (!haveSet.has(i)) needed.push(i)
506 }
507
508 t.comment(`resume: have ${haveSet.size}/${totalChunks}, need ${needed.length}`)
509 t.ok(needed.length < totalChunks, 'should need fewer chunks than total')
510
511 // Resume transfer — DON'T call startReceive (it truncates the partial).
512 // Instead, manually populate the _active state pointing at existing partial.
513 const state = {
514 partialPath,
515 targetPath,
516 chunks: prepared.chunks,
517 received: new Set(haveSet),
518 total: prepared.chunks.length,
519 size: prepared.size
520 }
521 transferB2._active.set(targetPath, state)
522
523 let secondAttemptChunks = 0
524
525 await new Promise((resolve, reject) => {
526 const muxA2 = Protomux.from(sa2)
527 const muxB2 = Protomux.from(sb2)
528 const chA = muxA2.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('res2') })
529 const chB = muxB2.createChannel({ protocol: DEPLOY_PROTOCOL, id: Buffer.from('res2') })
530
531 const dataMsg = chA.addMessage({ encoding: c.raw })
532 const dataMsgB = chB.addMessage({ encoding: c.raw })
533
534 dataMsgB.onmessage = (data) => {
535 const index = data.readUInt32LE(0)
536 const chunkData = data.subarray(4)
537 const result = transferB2.writeChunk(targetPath, index, chunkData)
538 if (!result.ok) { reject(new Error(result.error)); return }
539 secondAttemptChunks++
540 if (transferB2.isComplete(targetPath)) {
541 transferB2.finalize(targetPath)
542 resolve()
543 }
544 }
545
546 chA.open()
547 chB.open()
548
549 for (const i of needed) {
550 const bytes = transferA.readChunk(filePath, prepared.chunks[i].offset, prepared.chunks[i].length)
551 const header = Buffer.alloc(4)
552 header.writeUInt32LE(i)
553 dataMsg.send(Buffer.concat([header, bytes]))
554 }
555 })
556
557 t.is(secondAttemptChunks, needed.length, `resumed: only ${secondAttemptChunks} chunks sent (not ${totalChunks})`)
558
559 const srcHash = hashFile(path.join(srcDir, 'resumable.bin'))
560 const dstHash = hashFile(path.join(dstDir, 'resumable.bin'))
561 t.is(srcHash, dstHash, 'resumed file hash matches original')
562
563 t.teardown(async () => {
564 sa2.destroy()
565 sb2.destroy()
566 await storeA.close()
567 await storeB.close()
568 })
569 }
570})