| … | … | @@ -0,0 +1 @@ |
| 1 | node_modules/ |
| … | … | @@ -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 | ||
| 13 | await import('bare-node-runtime/global') | |
| 14 | ||
| 15 | import fs from 'bare-fs' | |
| 16 | import path from 'bare-path' | |
| 17 | import { DeployServer, DeployClient } from '../index.js' | |
| 18 | import { startWeb } from '../lib/web.js' | |
| 19 | import { diffManifest } from '../lib/scanner.js' | |
| 20 | ||
| 21 | const VERSION = '0.1.0' | |
| 22 | ||
| 23 | function 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 | ||
| 30 | function 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 | ||
| 50 | const args = process.argv.slice(2) | |
| 51 | const command = args[0] | |
| 52 | ||
| 53 | if (!command || command === 'help' || command === '--help') { | |
| 54 | console.log(`hyper-deploy v${VERSION} — P2P release distribution | |
| 55 | ||
| 56 | Commands: | |
| 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 | ||
| 62 | Serve 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 | ||
| 67 | Pull 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 | ||
| 72 | Examples: | |
| 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 | ||
| 81 | if (command === 'version' || command === '--version') { | |
| 82 | console.log('hyper-deploy v' + VERSION) | |
| 83 | process.exit(0) | |
| 84 | } | |
| 85 | ||
| 86 | // ─── SERVE ─────────────────────────────────────────────────── | |
| 87 | ||
| 88 | if (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 | ||
| 150 | else 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 | ||
| 236 | else 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 | ||
| 284 | else { | |
| 285 | console.error('Unknown command: ' + command) | |
| 286 | console.error('Run: hyper-deploy help') | |
| 287 | process.exit(1) | |
| 288 | } |
| … | … | @@ -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 | ||
| 17 | import Hyperswarm from 'hyperswarm' | |
| 18 | import crypto from 'hypercore-crypto' | |
| 19 | import fs from 'bare-fs' | |
| 20 | import path from 'bare-path' | |
| 21 | import ReadyResource from 'ready-resource' | |
| 22 | import safetyCatch from 'safety-catch' | |
| 23 | ||
| 24 | import { buildManifest, diffManifest, readChunk, hashFile } from './lib/scanner.js' | |
| 25 | import { attachProtocol, PROTOCOL_VERSION, CHUNK_SIZE } from './lib/protocol.js' | |
| 26 | ||
| 27 | // ─── DeployServer ──────────────────────────────────────────── | |
| 28 | ||
| 29 | export 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 | ||
| 227 | export 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 | } |
| … | … | @@ -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 | ||
| 19 | import Protomux from 'protomux' | |
| 20 | import c from 'compact-encoding' | |
| 21 | ||
| 22 | export const PROTOCOL_NAME = 'hyper-deploy' | |
| 23 | export const PROTOCOL_VERSION = 1 | |
| 24 | export 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 | */ | |
| 32 | export 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 | } |
| … | … | @@ -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 | ||
| 9 | import fs from 'bare-fs' | |
| 10 | import path from 'bare-path' | |
| 11 | import 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 | */ | |
| 19 | export 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 | */ | |
| 41 | export 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 | */ | |
| 51 | export 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 | */ | |
| 73 | export 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 | */ | |
| 102 | export 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 | } |
| … | … | @@ -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 | ||
| 10 | import http from 'bare-http1' | |
| 11 | ||
| 12 | function 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 | */ | |
| 25 | export 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 | ||
| 107 | function 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> | |
| 251 | const $ = id => document.getElementById(id) | |
| 252 | const peers = new Map() | |
| 253 | let totalFiles = 0 | |
| 254 | ||
| 255 | function 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 | ||
| 273 | function addPeer (key, type) { | |
| 274 | peers.set(key, type) | |
| 275 | renderPeers() | |
| 276 | } | |
| 277 | ||
| 278 | function 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 | ||
| 288 | function 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 | ||
| 295 | function rescan () { | |
| 296 | fetch('/api/rescan', { method: 'POST' }).then(r => r.json()).then(update) | |
| 297 | } | |
| 298 | ||
| 299 | // SSE for live updates | |
| 300 | const es = new EventSource('/events') | |
| 301 | es.onmessage = (e) => update(JSON.parse(e.data)) | |
| 302 | es.addEventListener('peer', (e) => { | |
| 303 | const d = JSON.parse(e.data) | |
| 304 | addPeer(d.key, d.type) | |
| 305 | }) | |
| 306 | es.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 | }) | |
| 311 | es.addEventListener('complete', (e) => { | |
| 312 | const d = JSON.parse(e.data) | |
| 313 | $('progress').style.width = '100%' | |
| 314 | }) | |
| 315 | </script> | |
| 316 | </body> | |
| 317 | </html>` | |
| 318 | } |
| … | … | @@ -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 | } |
| … | … | @@ -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 | } |
| … | … | @@ -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 | ||
| 13 | await import('bare-node-runtime/global') | |
| 14 | ||
| 15 | import Hyperswarm from 'hyperswarm' | |
| 16 | import Corestore from 'corestore' | |
| 17 | import Protomux from 'protomux' | |
| 18 | import c from 'compact-encoding' | |
| 19 | import crypto from 'hypercore-crypto' | |
| 20 | import fs from 'bare-fs' | |
| 21 | import path from 'bare-path' | |
| 22 | import os from 'bare-os' | |
| 23 | ||
| 24 | import { FileIndex } from 'hyper-overlay/file-index' | |
| 25 | import { TransferManager } from 'hyper-overlay/transfer' | |
| 26 | import { hashChunk } from 'hyper-overlay/chunker' | |
| 27 | ||
| 28 | const PROTOCOL = 'hyper-deploy-release' | |
| 29 | ||
| 30 | const args = process.argv.slice(2) | |
| 31 | const mode = args[0] | |
| 32 | ||
| 33 | if (!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 | ||
| 42 | function 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 | ||
| 60 | function hashFile (filePath) { | |
| 61 | const data = fs.readFileSync(filePath) | |
| 62 | return crypto.data(data).toString('hex') | |
| 63 | } | |
| 64 | ||
| 65 | function 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 | ||
| 76 | async 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 | ||
| 231 | async 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 | ||
| 372 | if (mode === 'server') runServer().catch(err => { console.error(err); process.exit(1) }) | |
| 373 | else runClient().catch(err => { console.error(err); process.exit(1) }) |
| … | … | @@ -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 | ||
| 11 | import test from 'brittle' | |
| 12 | import Corestore from 'corestore' | |
| 13 | import Protomux from 'protomux' | |
| 14 | import c from 'compact-encoding' | |
| 15 | import SecretStream from '@hyperswarm/secret-stream' | |
| 16 | import fs from 'bare-fs' | |
| 17 | import path from 'bare-path' | |
| 18 | import os from 'bare-os' | |
| 19 | import crypto from 'hypercore-crypto' | |
| 20 | ||
| 21 | // hyper-overlay as dependency (not modified) | |
| 22 | import { FileIndex } from 'hyper-overlay/file-index' | |
| 23 | import { TransferManager } from 'hyper-overlay/transfer' | |
| 24 | import { chunk, hashChunk } from 'hyper-overlay/chunker' | |
| 25 | ||
| 26 | const tmpdir = os.tmpdir() | |
| 27 | const PARTIAL_SUFFIX = '.overlay-partial' | |
| 28 | ||
| 29 | // --- Helpers --- | |
| 30 | ||
| 31 | function 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 | ||
| 38 | function 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 | */ | |
| 47 | function 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 | */ | |
| 66 | function 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 | */ | |
| 74 | async 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 | */ | |
| 104 | const DEPLOY_PROTOCOL = 'hyper-deploy' | |
| 105 | ||
| 106 | // === Tests === | |
| 107 | ||
| 108 | test('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 | ||
| 280 | test('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 | ||
| 394 | test('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 | }) |