| … | … | @@ -6,6 +6,8 @@ |
| 6 | 6 | * Backpressure via Protomux ondrain. Hash verification on received files. |
| 7 | 7 | * |
| 8 | 8 | * Usage: |
| 9 | * const { DeployServer, DeployClient } = require('hyper-deploy') | |
| 10 | * | |
| 9 | 11 | * const server = new DeployServer(dir) |
| 10 | 12 | * await server.ready() |
| 11 | 13 | * |
| … | … | @@ -14,228 +16,221 @@ |
| 14 | 16 | * const result = await client.pull() |
| 15 | 17 | */ |
| 16 | 18 | |
| 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' | |
| 19 | const Hyperswarm = require('hyperswarm'); | |
| 20 | const crypto = require('hypercore-crypto'); | |
| 21 | const fs = require('fs'); | |
| 22 | const path = require('path'); | |
| 23 | const ReadyResource = require('ready-resource'); | |
| 24 | const safetyCatch = require('safety-catch'); | |
| 23 | 25 | |
| 24 | import { buildManifest, diffManifest, readChunk, hashFile } from './lib/scanner.js' | |
| 25 | import { attachProtocol, PROTOCOL_VERSION, CHUNK_SIZE } from './lib/protocol.js' | |
| 26 | const { buildManifest, diffManifest, readChunk, hashFile } = require('./lib/scanner'); | |
| 27 | const { attachProtocol, PROTOCOL_VERSION, CHUNK_SIZE } = require('./lib/protocol'); | |
| 26 | 28 | |
| 27 | 29 | // ─── DeployServer ──────────────────────────────────────────── |
| 28 | 30 | |
| 29 | export class DeployServer extends ReadyResource { | |
| 31 | class DeployServer extends ReadyResource { | |
| 30 | 32 | constructor (dir, opts) { |
| 31 | super() | |
| 32 | opts = opts || {} | |
| 33 | super(); | |
| 34 | opts = opts || {}; | |
| 33 | 35 | |
| 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() | |
| 36 | this.dir = path.resolve(dir); | |
| 37 | this.topic = opts.topic || crypto.randomBytes(32); | |
| 38 | this.swarm = opts.swarm || null; | |
| 39 | this._ownSwarm = !this.swarm; | |
| 40 | this.manifest = null; | |
| 41 | this._manifestHash = null; | |
| 42 | this._discovery = null; | |
| 43 | this._sessions = new Set(); | |
| 42 | 44 | |
| 43 | this.stats = { totalConnections: 0, filesServed: 0, bytesServed: 0 } | |
| 45 | this.stats = { totalConnections: 0, filesServed: 0, bytesServed: 0 }; | |
| 44 | 46 | |
| 45 | this.ready().catch(safetyCatch) | |
| 47 | this.ready().catch(safetyCatch); | |
| 46 | 48 | } |
| 47 | 49 | |
| 48 | 50 | async _open () { |
| 49 | 51 | if (!fs.existsSync(this.dir)) { |
| 50 | throw new Error('Directory not found: ' + this.dir) | |
| 52 | throw new Error('Directory not found: ' + this.dir); | |
| 51 | 53 | } |
| 52 | 54 | |
| 53 | await this._scan() | |
| 55 | await this._scan(); | |
| 54 | 56 | |
| 55 | if (!this.swarm) this.swarm = new Hyperswarm() | |
| 56 | this.swarm.on('connection', (conn) => this._onConnection(conn)) | |
| 57 | if (!this.swarm) this.swarm = new Hyperswarm(); | |
| 58 | this.swarm.on('connection', (conn) => this._onConnection(conn)); | |
| 57 | 59 | |
| 58 | this._discovery = this.swarm.join(this.topic, { server: true, client: false }) | |
| 59 | await this._discovery.flushed() | |
| 60 | this._discovery = this.swarm.join(this.topic, { server: true, client: false }); | |
| 61 | await this._discovery.flushed(); | |
| 60 | 62 | |
| 61 | this.emit('ready') | |
| 63 | this.emit('ready'); | |
| 62 | 64 | } |
| 63 | 65 | |
| 64 | 66 | async _close () { |
| 65 | // Cancel active sessions | |
| 66 | 67 | for (const session of this._sessions) { |
| 67 | session.destroyed = true | |
| 68 | session.destroyed = true; | |
| 68 | 69 | } |
| 69 | this._sessions.clear() | |
| 70 | this._sessions.clear(); | |
| 70 | 71 | |
| 71 | 72 | if (this._discovery) { |
| 72 | await this._discovery.destroy() | |
| 73 | this._discovery = null | |
| 73 | await this._discovery.destroy(); | |
| 74 | this._discovery = null; | |
| 74 | 75 | } |
| 75 | 76 | if (this._ownSwarm && this.swarm) { |
| 76 | await this.swarm.destroy() | |
| 77 | this.swarm = null | |
| 77 | await this.swarm.destroy(); | |
| 78 | this.swarm = null; | |
| 78 | 79 | } |
| 79 | 80 | } |
| 80 | 81 | |
| 81 | 82 | get topicHex () { |
| 82 | return this.topic.toString('hex') | |
| 83 | return this.topic.toString('hex'); | |
| 83 | 84 | } |
| 84 | 85 | |
| 85 | 86 | get connections () { |
| 86 | return this._sessions.size | |
| 87 | return this._sessions.size; | |
| 87 | 88 | } |
| 88 | 89 | |
| 89 | 90 | async rescan () { |
| 90 | await this._scan() | |
| 91 | return { files: this.manifest.files.length, totalSize: this.manifest.totalSize } | |
| 91 | await this._scan(); | |
| 92 | return { files: this.manifest.files.length, totalSize: this.manifest.totalSize }; | |
| 92 | 93 | } |
| 93 | 94 | |
| 94 | 95 | async _scan () { |
| 95 | 96 | this.manifest = buildManifest(this.dir, { |
| 96 | 97 | onProgress: (i, total) => { |
| 97 | if (i % 1000 === 0) this.emit('index-progress', i, total) | |
| 98 | if (i % 1000 === 0) this.emit('index-progress', i, total); | |
| 98 | 99 | } |
| 99 | }) | |
| 100 | }); | |
| 100 | 101 | |
| 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') | |
| 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 | 104 | |
| 105 | this.emit('indexed', this.manifest.files.length, this.manifest.totalSize) | |
| 105 | this.emit('indexed', this.manifest.files.length, this.manifest.totalSize); | |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | 108 | _onConnection (conn) { |
| 109 | 109 | const peerKey = conn.remotePublicKey |
| 110 | 110 | ? conn.remotePublicKey.toString('hex').slice(0, 12) |
| 111 | : 'unknown' | |
| 111 | : 'unknown'; | |
| 112 | 112 | |
| 113 | this.stats.totalConnections++ | |
| 113 | this.stats.totalConnections++; | |
| 114 | 114 | |
| 115 | const proto = attachProtocol(conn) | |
| 115 | const proto = attachProtocol(conn); | |
| 116 | 116 | if (!proto) { |
| 117 | this.emit('error', new Error('Failed to attach protocol for peer ' + peerKey)) | |
| 118 | return | |
| 117 | this.emit('error', new Error('Failed to attach protocol for peer ' + peerKey)); | |
| 118 | return; | |
| 119 | 119 | } |
| 120 | 120 | |
| 121 | const session = { peerKey, destroyed: false, filesServed: 0, bytesServed: 0 } | |
| 122 | this._sessions.add(session) | |
| 123 | this.emit('peer', { key: peerKey, state: 'connected' }) | |
| 121 | const session = { peerKey, destroyed: false, filesServed: 0, bytesServed: 0 }; | |
| 122 | this._sessions.add(session); | |
| 123 | this.emit('peer', { key: peerKey, state: 'connected' }); | |
| 124 | 124 | |
| 125 | 125 | conn.on('close', () => { |
| 126 | this._sessions.delete(session) | |
| 127 | this.emit('peer', { key: peerKey, state: 'disconnected' }) | |
| 128 | }) | |
| 126 | this._sessions.delete(session); | |
| 127 | this.emit('peer', { key: peerKey, state: 'disconnected' }); | |
| 128 | }); | |
| 129 | 129 | |
| 130 | 130 | conn.on('error', (err) => { |
| 131 | this._sessions.delete(session) | |
| 132 | this.emit('error', err) | |
| 133 | }) | |
| 131 | this._sessions.delete(session); | |
| 132 | this.emit('error', err); | |
| 133 | }); | |
| 134 | 134 | |
| 135 | // Send manifest with version and integrity hash | |
| 136 | 135 | proto.sendMeta({ |
| 137 | 136 | type: 'manifest', |
| 138 | 137 | version: PROTOCOL_VERSION, |
| 139 | 138 | files: this.manifest.files, |
| 140 | 139 | manifestHash: this._manifestHash |
| 141 | }).catch(safetyCatch) | |
| 140 | }).catch(safetyCatch); | |
| 142 | 141 | |
| 143 | // Handle client messages | |
| 144 | 142 | proto.metaMsg.onmessage = async (raw) => { |
| 145 | if (session.destroyed) return | |
| 143 | if (session.destroyed) return; | |
| 146 | 144 | |
| 147 | let msg | |
| 148 | try { msg = proto.parseMeta(raw) } catch { return } | |
| 145 | let msg; | |
| 146 | try { msg = proto.parseMeta(raw); } catch { return; } | |
| 149 | 147 | |
| 150 | 148 | if (msg.type === 'up-to-date') { |
| 151 | this.emit('peer', { key: peerKey, state: 'done' }) | |
| 152 | return | |
| 149 | this.emit('peer', { key: peerKey, state: 'done' }); | |
| 150 | return; | |
| 153 | 151 | } |
| 154 | 152 | |
| 155 | 153 | if (msg.type === 'requests') { |
| 156 | this.emit('peer', { key: peerKey, state: 'transferring' }) | |
| 154 | this.emit('peer', { key: peerKey, state: 'transferring' }); | |
| 157 | 155 | |
| 158 | const requested = new Set(msg.paths) | |
| 156 | const requested = new Set(msg.paths); | |
| 159 | 157 | this.emit('progress', { |
| 160 | 158 | peer: peerKey, |
| 161 | 159 | requested: requested.size, |
| 162 | 160 | skipped: this.manifest.files.length - requested.size |
| 163 | }) | |
| 161 | }); | |
| 164 | 162 | |
| 165 | 163 | 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 | |
| 164 | if (session.destroyed) break; | |
| 165 | if (!requested.has(f.path)) continue; | |
| 166 | ||
| 167 | const fullPath = path.join(this.dir, f.path); | |
| 168 | let stat; | |
| 169 | try { stat = fs.statSync(fullPath); } catch { | |
| 170 | await proto.sendMeta({ type: 'error', message: 'File not found', path: f.path }); | |
| 171 | continue; | |
| 174 | 172 | } |
| 175 | 173 | |
| 176 | // File start | |
| 177 | await proto.sendMeta({ type: 'file-start', path: f.path, size: stat.size, hash: f.hash }) | |
| 174 | await proto.sendMeta({ type: 'file-start', path: f.path, size: stat.size, hash: f.hash }); | |
| 178 | 175 | |
| 179 | // Stream data from disk | |
| 180 | let fd | |
| 176 | let fd; | |
| 181 | 177 | try { |
| 182 | fd = fs.openSync(fullPath, 'r') | |
| 178 | fd = fs.openSync(fullPath, 'r'); | |
| 183 | 179 | 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 | |
| 180 | if (session.destroyed) break; | |
| 181 | const len = Math.min(CHUNK_SIZE, stat.size - off); | |
| 182 | const buf = Buffer.alloc(len); | |
| 183 | fs.readSync(fd, buf, 0, len, off); | |
| 184 | await proto.sendData(buf); | |
| 185 | session.bytesServed += len; | |
| 186 | this.stats.bytesServed += len; | |
| 191 | 187 | } |
| 192 | 188 | } finally { |
| 193 | if (fd !== undefined) try { fs.closeSync(fd) } catch {} | |
| 189 | if (fd !== undefined) try { fs.closeSync(fd); } catch {} | |
| 194 | 190 | } |
| 195 | 191 | |
| 196 | // File end | |
| 197 | await proto.sendMeta({ type: 'file-end', path: f.path }) | |
| 192 | await proto.sendMeta({ type: 'file-end', path: f.path }); | |
| 198 | 193 | |
| 199 | session.filesServed++ | |
| 200 | this.stats.filesServed++ | |
| 194 | session.filesServed++; | |
| 195 | this.stats.filesServed++; | |
| 201 | 196 | this.emit('progress', { |
| 202 | 197 | peer: peerKey, |
| 203 | 198 | file: f.path, |
| 204 | 199 | filesServed: session.filesServed, |
| 205 | 200 | bytesServed: session.bytesServed |
| 206 | }) | |
| 201 | }); | |
| 207 | 202 | } |
| 208 | 203 | |
| 209 | 204 | await proto.sendMeta({ |
| 210 | 205 | type: 'transfer-complete', |
| 211 | 206 | filesServed: session.filesServed, |
| 212 | 207 | bytesServed: session.bytesServed |
| 213 | }) | |
| 208 | }); | |
| 214 | 209 | |
| 215 | this.emit('peer', { key: peerKey, state: 'done' }) | |
| 210 | this.emit('peer', { key: peerKey, state: 'done' }); | |
| 216 | 211 | } |
| 217 | 212 | |
| 218 | 213 | if (msg.type === 'done') { |
| 219 | this._sessions.delete(session) | |
| 214 | this._sessions.delete(session); | |
| 220 | 215 | } |
| 221 | } | |
| 216 | }; | |
| 222 | 217 | } |
| 223 | 218 | } |
| 224 | 219 | |
| 225 | 220 | // ─── DeployClient ──────────────────────────────────────────── |
| 226 | 221 | |
| 227 | export class DeployClient extends ReadyResource { | |
| 222 | class DeployClient extends ReadyResource { | |
| 228 | 223 | constructor (topic, destDir, opts) { |
| 229 | super() | |
| 230 | opts = opts || {} | |
| 224 | super(); | |
| 225 | opts = opts || {}; | |
| 231 | 226 | |
| 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 | |
| 227 | this.topic = typeof topic === 'string' ? Buffer.from(topic, 'hex') : topic; | |
| 228 | this.destDir = path.resolve(destDir); | |
| 229 | this.swarm = opts.swarm || null; | |
| 230 | this._ownSwarm = !this.swarm; | |
| 231 | this._timeout = opts.timeout !== undefined ? opts.timeout : 30000; | |
| 232 | this._verify = opts.verify !== false; | |
| 233 | this.connected = false; | |
| 239 | 234 | |
| 240 | 235 | this.stats = { |
| 241 | 236 | filesTransferred: 0, |
| … | … | @@ -244,238 +239,222 @@ export class DeployClient extends ReadyResource { |
| 244 | 239 | totalFiles: 0, |
| 245 | 240 | elapsed: 0, |
| 246 | 241 | 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) | |
| 242 | }; | |
| 243 | ||
| 244 | this._pullResolve = null; | |
| 245 | this._pullReject = null; | |
| 246 | this._startTime = null; | |
| 247 | this._currentFd = null; | |
| 248 | this._currentPath = null; | |
| 249 | this._currentHash = null; | |
| 250 | this._currentWritten = 0; | |
| 251 | this._connectTimer = null; | |
| 252 | ||
| 253 | this.ready().catch(safetyCatch); | |
| 258 | 254 | } |
| 259 | 255 | |
| 260 | 256 | async _open () { |
| 261 | fs.mkdirSync(this.destDir, { recursive: true }) | |
| 257 | fs.mkdirSync(this.destDir, { recursive: true }); | |
| 262 | 258 | |
| 263 | if (!this.swarm) this.swarm = new Hyperswarm() | |
| 264 | this.swarm.on('connection', (conn) => this._onConnection(conn)) | |
| 259 | if (!this.swarm) this.swarm = new Hyperswarm(); | |
| 260 | this.swarm.on('connection', (conn) => this._onConnection(conn)); | |
| 265 | 261 | |
| 266 | this.swarm.join(this.topic, { server: false, client: true }) | |
| 267 | await this.swarm.flush() | |
| 262 | this.swarm.join(this.topic, { server: false, client: true }); | |
| 263 | await this.swarm.flush(); | |
| 268 | 264 | } |
| 269 | 265 | |
| 270 | 266 | async _close () { |
| 271 | // Close any open file descriptor | |
| 272 | 267 | if (this._currentFd !== null) { |
| 273 | try { fs.closeSync(this._currentFd) } catch {} | |
| 274 | this._currentFd = null | |
| 268 | try { fs.closeSync(this._currentFd); } catch {} | |
| 269 | this._currentFd = null; | |
| 275 | 270 | } |
| 276 | 271 | |
| 277 | 272 | if (this._connectTimer) { |
| 278 | clearTimeout(this._connectTimer) | |
| 279 | this._connectTimer = null | |
| 273 | clearTimeout(this._connectTimer); | |
| 274 | this._connectTimer = null; | |
| 280 | 275 | } |
| 281 | 276 | |
| 282 | 277 | if (this._pullReject) { |
| 283 | this._pullReject(new Error('Client closed during pull')) | |
| 284 | this._pullReject = null | |
| 285 | this._pullResolve = null | |
| 278 | this._pullReject(new Error('Client closed during pull')); | |
| 279 | this._pullReject = null; | |
| 280 | this._pullResolve = null; | |
| 286 | 281 | } |
| 287 | 282 | |
| 288 | 283 | if (this._ownSwarm && this.swarm) { |
| 289 | await this.swarm.destroy() | |
| 290 | this.swarm = null | |
| 284 | await this.swarm.destroy(); | |
| 285 | this.swarm = null; | |
| 291 | 286 | } |
| 292 | 287 | } |
| 293 | 288 | |
| 294 | /** | |
| 295 | * Pull files from the server. Resolves when complete. | |
| 296 | * @returns {Promise<{ filesTransferred, filesSkipped, bytesReceived, totalFiles, elapsed, errors }>} | |
| 297 | */ | |
| 298 | 289 | pull () { |
| 299 | if (this._pullResolve) return Promise.reject(new Error('Pull already in progress')) | |
| 290 | if (this._pullResolve) return Promise.reject(new Error('Pull already in progress')); | |
| 300 | 291 | |
| 301 | this._startTime = Date.now() | |
| 292 | this._startTime = Date.now(); | |
| 302 | 293 | |
| 303 | 294 | return new Promise((resolve, reject) => { |
| 304 | this._pullResolve = resolve | |
| 305 | this._pullReject = reject | |
| 295 | this._pullResolve = resolve; | |
| 296 | this._pullReject = reject; | |
| 306 | 297 | |
| 307 | // Connection timeout | |
| 308 | 298 | if (this._timeout > 0 && !this.connected) { |
| 309 | 299 | this._connectTimer = setTimeout(() => { |
| 310 | 300 | if (!this.connected && this._pullReject) { |
| 311 | this._pullReject(new Error('Connection timeout after ' + this._timeout + 'ms')) | |
| 312 | this._pullReject = null | |
| 313 | this._pullResolve = null | |
| 301 | this._pullReject(new Error('Connection timeout after ' + this._timeout + 'ms')); | |
| 302 | this._pullReject = null; | |
| 303 | this._pullResolve = null; | |
| 314 | 304 | } |
| 315 | }, this._timeout) | |
| 305 | }, this._timeout); | |
| 316 | 306 | } |
| 317 | }) | |
| 307 | }); | |
| 318 | 308 | } |
| 319 | 309 | |
| 320 | 310 | _finish () { |
| 321 | this.stats.elapsed = Date.now() - this._startTime | |
| 311 | this.stats.elapsed = Date.now() - this._startTime; | |
| 322 | 312 | if (this._pullResolve) { |
| 323 | const resolve = this._pullResolve | |
| 324 | this._pullResolve = null | |
| 325 | this._pullReject = null | |
| 326 | resolve({ ...this.stats }) | |
| 313 | const resolve = this._pullResolve; | |
| 314 | this._pullResolve = null; | |
| 315 | this._pullReject = null; | |
| 316 | resolve({ ...this.stats }); | |
| 327 | 317 | } |
| 328 | this.emit('complete', { ...this.stats }) | |
| 318 | this.emit('complete', { ...this.stats }); | |
| 329 | 319 | } |
| 330 | 320 | |
| 331 | 321 | _fail (err) { |
| 332 | this.stats.elapsed = Date.now() - (this._startTime || Date.now()) | |
| 333 | this.emit('error', err) | |
| 322 | this.stats.elapsed = Date.now() - (this._startTime || Date.now()); | |
| 323 | this.emit('error', err); | |
| 334 | 324 | if (this._pullReject) { |
| 335 | const reject = this._pullReject | |
| 336 | this._pullReject = null | |
| 337 | this._pullResolve = null | |
| 338 | reject(err) | |
| 325 | const reject = this._pullReject; | |
| 326 | this._pullReject = null; | |
| 327 | this._pullResolve = null; | |
| 328 | reject(err); | |
| 339 | 329 | } |
| 340 | 330 | } |
| 341 | 331 | |
| 342 | 332 | _onConnection (conn) { |
| 343 | 333 | if (this._connectTimer) { |
| 344 | clearTimeout(this._connectTimer) | |
| 345 | this._connectTimer = null | |
| 334 | clearTimeout(this._connectTimer); | |
| 335 | this._connectTimer = null; | |
| 346 | 336 | } |
| 347 | 337 | |
| 348 | this.connected = true | |
| 338 | this.connected = true; | |
| 349 | 339 | const peerKey = conn.remotePublicKey |
| 350 | 340 | ? conn.remotePublicKey.toString('hex').slice(0, 12) |
| 351 | : 'unknown' | |
| 352 | this.emit('connected', peerKey) | |
| 341 | : 'unknown'; | |
| 342 | this.emit('connected', peerKey); | |
| 353 | 343 | |
| 354 | 344 | conn.on('close', () => { |
| 355 | this.connected = false | |
| 356 | // If we were mid-transfer, that's an error | |
| 345 | this.connected = false; | |
| 357 | 346 | if (this._pullReject && this.stats.filesTransferred + this.stats.filesSkipped < this.stats.totalFiles) { |
| 358 | this._fail(new Error('Connection lost during transfer')) | |
| 347 | this._fail(new Error('Connection lost during transfer')); | |
| 359 | 348 | } |
| 360 | }) | |
| 349 | }); | |
| 361 | 350 | |
| 362 | 351 | conn.on('error', (err) => { |
| 363 | this.connected = false | |
| 364 | this._fail(err) | |
| 365 | }) | |
| 352 | this.connected = false; | |
| 353 | this._fail(err); | |
| 354 | }); | |
| 366 | 355 | |
| 367 | const proto = attachProtocol(conn) | |
| 356 | const proto = attachProtocol(conn); | |
| 368 | 357 | if (!proto) { |
| 369 | this._fail(new Error('Failed to attach protocol')) | |
| 370 | return | |
| 358 | this._fail(new Error('Failed to attach protocol')); | |
| 359 | return; | |
| 371 | 360 | } |
| 372 | 361 | |
| 373 | 362 | 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 | |
| 363 | let msg; | |
| 364 | try { msg = proto.parseMeta(raw); } catch { | |
| 365 | this._fail(new Error('Invalid protocol message')); | |
| 366 | return; | |
| 378 | 367 | } |
| 379 | 368 | |
| 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)) | |
| 369 | if (msg.type === 'manifest') this._handleManifest(msg, proto); | |
| 370 | else if (msg.type === 'file-start') this._handleFileStart(msg); | |
| 371 | else if (msg.type === 'file-end') this._handleFileEnd(msg); | |
| 372 | else if (msg.type === 'transfer-complete') this._handleTransferComplete(msg, proto); | |
| 373 | else if (msg.type === 'error') { | |
| 374 | this.stats.errors.push({ path: msg.path, message: msg.message }); | |
| 375 | this.emit('error', new Error(msg.message)); | |
| 391 | 376 | } |
| 392 | } | |
| 377 | }; | |
| 393 | 378 | |
| 394 | 379 | proto.dataMsg.onmessage = (chunk) => { |
| 395 | 380 | if (this._currentFd !== null) { |
| 396 | 381 | try { |
| 397 | fs.writeSync(this._currentFd, chunk, 0, chunk.length, this._currentWritten) | |
| 398 | this._currentWritten += chunk.length | |
| 399 | this.stats.bytesReceived += chunk.length | |
| 382 | fs.writeSync(this._currentFd, chunk, 0, chunk.length, this._currentWritten); | |
| 383 | this._currentWritten += chunk.length; | |
| 384 | this.stats.bytesReceived += chunk.length; | |
| 400 | 385 | } catch (err) { |
| 401 | this._fail(new Error('Write failed: ' + err.message)) | |
| 386 | this._fail(new Error('Write failed: ' + err.message)); | |
| 402 | 387 | } |
| 403 | 388 | } |
| 404 | } | |
| 389 | }; | |
| 405 | 390 | } |
| 406 | 391 | |
| 407 | 392 | _handleManifest (msg, proto) { |
| 408 | // Version check | |
| 409 | 393 | if (msg.version && msg.version !== PROTOCOL_VERSION) { |
| 410 | this._fail(new Error('Protocol version mismatch: expected ' + PROTOCOL_VERSION + ', got ' + msg.version)) | |
| 411 | return | |
| 394 | this._fail(new Error('Protocol version mismatch: expected ' + PROTOCOL_VERSION + ', got ' + msg.version)); | |
| 395 | return; | |
| 412 | 396 | } |
| 413 | 397 | |
| 414 | this.stats.totalFiles = msg.files.length | |
| 415 | this.emit('manifest', msg.files.length) | |
| 398 | this.stats.totalFiles = msg.files.length; | |
| 399 | this.emit('manifest', msg.files.length); | |
| 416 | 400 | |
| 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) | |
| 401 | const { needed, skipped } = diffManifest(msg.files, this.destDir); | |
| 402 | this.stats.filesSkipped = skipped; | |
| 403 | this.emit('diff', needed.length, skipped); | |
| 421 | 404 | |
| 422 | 405 | if (needed.length === 0) { |
| 423 | proto.sendMeta({ type: 'up-to-date' }).catch(safetyCatch) | |
| 424 | this.emit('up-to-date') | |
| 425 | this._finish() | |
| 426 | return | |
| 406 | proto.sendMeta({ type: 'up-to-date' }).catch(safetyCatch); | |
| 407 | this.emit('up-to-date'); | |
| 408 | this._finish(); | |
| 409 | return; | |
| 427 | 410 | } |
| 428 | 411 | |
| 429 | // Pipeline all requests | |
| 430 | proto.sendMeta({ type: 'requests', paths: needed }).catch(safetyCatch) | |
| 412 | proto.sendMeta({ type: 'requests', paths: needed }).catch(safetyCatch); | |
| 431 | 413 | } |
| 432 | 414 | |
| 433 | 415 | _handleFileStart (msg) { |
| 434 | const targetPath = path.resolve(path.join(this.destDir, msg.path)) | |
| 416 | const targetPath = path.resolve(path.join(this.destDir, msg.path)); | |
| 435 | 417 | |
| 436 | // Reject path traversal (e.g. ../../.bashrc) | |
| 437 | 418 | 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 | |
| 419 | this.stats.errors.push({ path: msg.path, message: 'Path traversal rejected' }); | |
| 420 | this.emit('error', new Error('Path traversal rejected: ' + msg.path)); | |
| 421 | return; | |
| 441 | 422 | } |
| 442 | 423 | |
| 443 | fs.mkdirSync(path.dirname(targetPath), { recursive: true }) | |
| 424 | fs.mkdirSync(path.dirname(targetPath), { recursive: true }); | |
| 444 | 425 | |
| 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) | |
| 426 | this._currentPath = msg.path; | |
| 427 | this._currentHash = msg.hash; | |
| 428 | this._currentFd = fs.openSync(targetPath, 'w'); | |
| 429 | this._currentWritten = 0; | |
| 430 | if (msg.size > 0) fs.ftruncateSync(this._currentFd, msg.size); | |
| 450 | 431 | } |
| 451 | 432 | |
| 452 | 433 | _handleFileEnd (msg) { |
| 453 | 434 | if (this._currentFd !== null) { |
| 454 | fs.closeSync(this._currentFd) | |
| 455 | this._currentFd = null | |
| 435 | fs.closeSync(this._currentFd); | |
| 436 | this._currentFd = null; | |
| 456 | 437 | } |
| 457 | 438 | |
| 458 | // Verify hash if enabled | |
| 459 | 439 | if (this._verify && this._currentHash) { |
| 460 | const targetPath = path.join(this.destDir, msg.path) | |
| 440 | const targetPath = path.join(this.destDir, msg.path); | |
| 461 | 441 | try { |
| 462 | const actual = hashFile(targetPath) | |
| 442 | const actual = hashFile(targetPath); | |
| 463 | 443 | 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 | |
| 444 | const err = 'Hash mismatch for ' + msg.path; | |
| 445 | this.stats.errors.push({ path: msg.path, message: err }); | |
| 446 | this.emit('error', new Error(err)); | |
| 447 | try { fs.unlinkSync(targetPath); } catch {} | |
| 448 | return; | |
| 470 | 449 | } |
| 471 | 450 | } catch (err) { |
| 472 | this.stats.errors.push({ path: msg.path, message: err.message }) | |
| 451 | this.stats.errors.push({ path: msg.path, message: err.message }); | |
| 473 | 452 | } |
| 474 | 453 | } |
| 475 | 454 | |
| 476 | this.stats.filesTransferred++ | |
| 477 | this._currentPath = null | |
| 478 | this._currentHash = null | |
| 455 | this.stats.filesTransferred++; | |
| 456 | this._currentPath = null; | |
| 457 | this._currentHash = null; | |
| 479 | 458 | |
| 480 | 459 | this.emit('progress', { |
| 481 | 460 | file: msg.path, |
| … | … | @@ -483,11 +462,13 @@ export class DeployClient extends ReadyResource { |
| 483 | 462 | filesSkipped: this.stats.filesSkipped, |
| 484 | 463 | bytesReceived: this.stats.bytesReceived, |
| 485 | 464 | totalFiles: this.stats.totalFiles |
| 486 | }) | |
| 465 | }); | |
| 487 | 466 | } |
| 488 | 467 | |
| 489 | 468 | _handleTransferComplete (msg, proto) { |
| 490 | proto.sendMeta({ type: 'done' }).catch(safetyCatch) | |
| 491 | this._finish() | |
| 469 | proto.sendMeta({ type: 'done' }).catch(safetyCatch); | |
| 470 | this._finish(); | |
| 492 | 471 | } |
| 493 | 472 | } |
| 473 | ||
| 474 | module.exports = { DeployServer, DeployClient }; |
| … | … | @@ -1,60 +1,36 @@ |
| 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 | */ | |
| 1 | const Protomux = require('protomux'); | |
| 2 | const c = require('compact-encoding'); | |
| 18 | 3 | |
| 19 | import Protomux from 'protomux' | |
| 20 | import c from 'compact-encoding' | |
| 4 | const PROTOCOL_NAME = 'hyper-deploy'; | |
| 5 | const PROTOCOL_VERSION = 1; | |
| 6 | const CHUNK_SIZE = 65536; | |
| 21 | 7 | |
| 22 | export const PROTOCOL_NAME = 'hyper-deploy' | |
| 23 | export const PROTOCOL_VERSION = 1 | |
| 24 | export const CHUNK_SIZE = 65536 | |
| 8 | function attachProtocol (stream) { | |
| 9 | const mux = Protomux.from(stream); | |
| 10 | if (!mux) return null; | |
| 25 | 11 | |
| 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 | |
| 12 | let drainResolve = null; | |
| 37 | 13 | |
| 38 | 14 | const ch = mux.createChannel({ |
| 39 | 15 | protocol: PROTOCOL_NAME, |
| 40 | 16 | ondrain () { |
| 41 | 17 | if (drainResolve) { |
| 42 | const r = drainResolve | |
| 43 | drainResolve = null | |
| 44 | r() | |
| 18 | const r = drainResolve; | |
| 19 | drainResolve = null; | |
| 20 | r(); | |
| 45 | 21 | } |
| 46 | 22 | } |
| 47 | }) | |
| 23 | }); | |
| 48 | 24 | |
| 49 | if (!ch) return null | |
| 25 | if (!ch) return null; | |
| 50 | 26 | |
| 51 | const metaMsg = ch.addMessage({ encoding: c.raw }) | |
| 52 | const dataMsg = ch.addMessage({ encoding: c.raw }) | |
| 27 | const metaMsg = ch.addMessage({ encoding: c.raw }); | |
| 28 | const dataMsg = ch.addMessage({ encoding: c.raw }); | |
| 53 | 29 | |
| 54 | ch.open() | |
| 30 | ch.open(); | |
| 55 | 31 | |
| 56 | 32 | async function waitDrain () { |
| 57 | await new Promise(r => { drainResolve = r }) | |
| 33 | await new Promise(r => { drainResolve = r; }); | |
| 58 | 34 | } |
| 59 | 35 | |
| 60 | 36 | return { |
| … | … | @@ -63,33 +39,22 @@ export function attachProtocol (stream) { |
| 63 | 39 | dataMsg, |
| 64 | 40 | mux, |
| 65 | 41 | |
| 66 | /** | |
| 67 | * Send a typed JSON message on the meta channel with backpressure. | |
| 68 | * @param {object} obj — must have a `type` field | |
| 69 | */ | |
| 70 | 42 | async sendMeta (obj) { |
| 71 | 43 | if (!metaMsg.send(Buffer.from(JSON.stringify(obj)))) { |
| 72 | await waitDrain() | |
| 44 | await waitDrain(); | |
| 73 | 45 | } |
| 74 | 46 | }, |
| 75 | 47 | |
| 76 | /** | |
| 77 | * Send raw data on the data channel with backpressure. | |
| 78 | * @param {Buffer} buf | |
| 79 | */ | |
| 80 | 48 | async sendData (buf) { |
| 81 | 49 | if (!dataMsg.send(buf)) { |
| 82 | await waitDrain() | |
| 50 | await waitDrain(); | |
| 83 | 51 | } |
| 84 | 52 | }, |
| 85 | 53 | |
| 86 | /** | |
| 87 | * Parse a received meta message. | |
| 88 | * @param {Buffer} raw | |
| 89 | * @returns {object} | |
| 90 | */ | |
| 91 | 54 | parseMeta (raw) { |
| 92 | return JSON.parse(raw.toString()) | |
| 55 | return JSON.parse(raw.toString()); | |
| 93 | 56 | } |
| 94 | } | |
| 57 | }; | |
| 95 | 58 | } |
| 59 | ||
| 60 | module.exports = { attachProtocol, PROTOCOL_NAME, PROTOCOL_VERSION, CHUNK_SIZE }; |
| … | … | @@ -1,108 +1,71 @@ |
| 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 | */ | |
| 1 | const fs = require('fs'); | |
| 2 | const path = require('path'); | |
| 3 | const crypto = require('hypercore-crypto'); | |
| 8 | 4 | |
| 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 = [] | |
| 5 | function walkDir (dir, prefix) { | |
| 6 | prefix = prefix || ''; | |
| 7 | const results = []; | |
| 22 | 8 | try { |
| 23 | 9 | for (const entry of fs.readdirSync(dir)) { |
| 24 | const full = path.join(dir, entry) | |
| 25 | const rel = prefix ? prefix + '/' + entry : entry | |
| 10 | const full = path.join(dir, entry); | |
| 11 | const rel = prefix ? prefix + '/' + entry : entry; | |
| 26 | 12 | 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 }) | |
| 13 | const stat = fs.statSync(full); | |
| 14 | if (stat.isDirectory()) results.push(...walkDir(full, rel)); | |
| 15 | else if (stat.isFile()) results.push({ path: rel, size: stat.size }); | |
| 30 | 16 | } catch {} |
| 31 | 17 | } |
| 32 | 18 | } catch {} |
| 33 | return results | |
| 19 | return results; | |
| 34 | 20 | } |
| 35 | 21 | |
| 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') | |
| 22 | function hashFile (filePath) { | |
| 23 | return crypto.data(fs.readFileSync(filePath)).toString('hex'); | |
| 43 | 24 | } |
| 44 | 25 | |
| 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 | |
| 26 | function buildManifest (dir, opts) { | |
| 27 | const onProgress = opts && opts.onProgress; | |
| 28 | const files = walkDir(dir); | |
| 29 | let totalSize = 0; | |
| 55 | 30 | |
| 56 | 31 | 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) | |
| 32 | const f = files[i]; | |
| 33 | f.hash = hashFile(path.join(dir, f.path)); | |
| 34 | totalSize += f.size; | |
| 35 | if (onProgress) onProgress(i + 1, files.length); | |
| 61 | 36 | } |
| 62 | 37 | |
| 63 | return { files, totalSize } | |
| 38 | return { files, totalSize }; | |
| 64 | 39 | } |
| 65 | 40 | |
| 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 | |
| 41 | function diffManifest (manifest, localDir) { | |
| 42 | const needed = []; | |
| 43 | let skipped = 0; | |
| 76 | 44 | |
| 77 | 45 | for (const f of manifest) { |
| 78 | const localPath = path.join(localDir, f.path) | |
| 46 | const localPath = path.join(localDir, f.path); | |
| 79 | 47 | try { |
| 80 | const stat = fs.statSync(localPath) | |
| 48 | const stat = fs.statSync(localPath); | |
| 81 | 49 | if (stat.size === f.size) { |
| 82 | const localHash = hashFile(localPath) | |
| 50 | const localHash = hashFile(localPath); | |
| 83 | 51 | if (localHash === f.hash) { |
| 84 | skipped++ | |
| 85 | continue | |
| 52 | skipped++; | |
| 53 | continue; | |
| 86 | 54 | } |
| 87 | 55 | } |
| 88 | 56 | } catch {} |
| 89 | needed.push(f.path) | |
| 57 | needed.push(f.path); | |
| 90 | 58 | } |
| 91 | 59 | |
| 92 | return { needed, skipped } | |
| 60 | return { needed, skipped }; | |
| 93 | 61 | } |
| 94 | 62 | |
| 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 | |
| 63 | function readChunk (filePath, offset, length) { | |
| 64 | const fd = fs.openSync(filePath, 'r'); | |
| 65 | const buf = Buffer.alloc(length); | |
| 66 | fs.readSync(fd, buf, 0, length, offset); | |
| 67 | fs.closeSync(fd); | |
| 68 | return buf; | |
| 108 | 69 | } |
| 70 | ||
| 71 | module.exports = { walkDir, hashFile, buildManifest, diffManifest, readChunk }; |
| … | … | @@ -1,39 +1,29 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "hyper-deploy", |
| 3 | "version": "0.1.0", | |
| 3 | "version": "0.2.0", | |
| 4 | 4 | "lockfileVersion": 3, |
| 5 | 5 | "requires": true, |
| 6 | 6 | "packages": { |
| 7 | 7 | "": { |
| 8 | 8 | "name": "hyper-deploy", |
| 9 | "version": "0.1.0", | |
| 9 | "version": "0.2.0", | |
| 10 | 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 | 11 | "compact-encoding": "^2.0.0", |
| 18 | 12 | "hypercore-crypto": "^3.0.0", |
| 19 | 13 | "hyperswarm": "^4.0.0", |
| 20 | 14 | "protomux": "^3.0.0", |
| 21 | 15 | "ready-resource": "^1.0.0", |
| 22 | "safety-catch": "^1.0.3", | |
| 23 | "udx-native": "^1.20.3" | |
| 24 | }, | |
| 25 | "bin": { | |
| 26 | "hyper-deploy": "bin/hyper-deploy.js" | |
| 16 | "safety-catch": "^1.0.0" | |
| 27 | 17 | }, |
| 28 | 18 | "devDependencies": { |
| 19 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 29 | 20 | "brittle": "^3.0.0", |
| 30 | "corestore": "^7.0.0", | |
| 31 | "hyper-overlay": "file:../hyper-overlay" | |
| 21 | "corestore": "^7.0.0" | |
| 32 | 22 | } |
| 33 | 23 | }, |
| 34 | 24 | "../hyper-overlay": { |
| 35 | 25 | "version": "0.1.0", |
| 36 | "dev": true, | |
| 26 | "extraneous": true, | |
| 37 | 27 | "license": "AGPL-3.0", |
| 38 | 28 | "dependencies": { |
| 39 | 29 | "@hyperswarm/testnet": "^3.1.4", |
| … | … | @@ -154,18 +144,9 @@ |
| 154 | 144 | "version": "2.0.13", |
| 155 | 145 | "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz", |
| 156 | 146 | "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==", |
| 147 | "dev": true, | |
| 157 | 148 | "license": "Apache-2.0" |
| 158 | 149 | }, |
| 159 | "node_modules/bare-abort-controller": { | |
| 160 | "version": "1.1.1", | |
| 161 | "resolved": "https://registry.npmjs.org/bare-abort-controller/-/bare-abort-controller-1.1.1.tgz", | |
| 162 | "integrity": "sha512-A/3w+PHaGnjlrRcmBeuCk8v23SP35db1X6a5GOkTVSloVmMEWdVhhnlrJWzIvnLJh79ljhAFS1lvLRev09LCGg==", | |
| 163 | "license": "Apache-2.0", | |
| 164 | "peer": true, | |
| 165 | "dependencies": { | |
| 166 | "bare-events": "^2.7.0" | |
| 167 | } | |
| 168 | }, | |
| 169 | 150 | "node_modules/bare-addon-resolve": { |
| 170 | 151 | "version": "1.10.0", |
| 171 | 152 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", |
| … | … | @@ -210,25 +191,11 @@ |
| 210 | 191 | "bare-inspect": "^3.1.2" |
| 211 | 192 | } |
| 212 | 193 | }, |
| 213 | "node_modules/bare-async-hooks": { | |
| 214 | "version": "0.0.0", | |
| 215 | "resolved": "https://registry.npmjs.org/bare-async-hooks/-/bare-async-hooks-0.0.0.tgz", | |
| 216 | "integrity": "sha512-xNfGwUobaomCGMGAqohAekS3uMCj+4tvI4AoOaJnO7NfpN+dvFdkC5xkeQtmZzs2vxf2TR5J6i5FDd1ImCZERw==", | |
| 217 | "license": "Apache-2.0" | |
| 218 | }, | |
| 219 | "node_modules/bare-buffer": { | |
| 220 | "version": "3.6.0", | |
| 221 | "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.0.tgz", | |
| 222 | "integrity": "sha512-/maRWEQ2eBkVNMbNFVsq1pHXJYVj4Y3AixwruB24eKZDs5Gtu0fixzvjYmBIuTsBMtVH5Yb27pQO9BhFa+IlIQ==", | |
| 223 | "license": "Apache-2.0", | |
| 224 | "engines": { | |
| 225 | "bare": ">=1.20.0" | |
| 226 | } | |
| 227 | }, | |
| 228 | 194 | "node_modules/bare-bundle": { |
| 229 | 195 | "version": "1.10.0", |
| 230 | 196 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", |
| 231 | 197 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", |
| 198 | "dev": true, | |
| 232 | 199 | "license": "Apache-2.0", |
| 233 | 200 | "peerDependencies": { |
| 234 | 201 | "bare-buffer": "*", |
| … | … | @@ -243,31 +210,6 @@ |
| 243 | 210 | } |
| 244 | 211 | } |
| 245 | 212 | }, |
| 246 | "node_modules/bare-channel": { | |
| 247 | "version": "5.2.3", | |
| 248 | "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.2.3.tgz", | |
| 249 | "integrity": "sha512-2cRErqS4fzzr3ZUSd5W67ka70dZKJ1VWrCsvxPVC8vr3rWvd4aaCG5iCDas9+MFVaUIWvfw04+NH4+FHQDddMQ==", | |
| 250 | "license": "Apache-2.0", | |
| 251 | "dependencies": { | |
| 252 | "bare-events": "^2.0.0", | |
| 253 | "bare-stream": "^2.7.0", | |
| 254 | "bare-structured-clone": "^1.4.0" | |
| 255 | }, | |
| 256 | "engines": { | |
| 257 | "bare": ">=1.7.0" | |
| 258 | } | |
| 259 | }, | |
| 260 | "node_modules/bare-console": { | |
| 261 | "version": "6.1.0", | |
| 262 | "resolved": "https://registry.npmjs.org/bare-console/-/bare-console-6.1.0.tgz", | |
| 263 | "integrity": "sha512-BziGTEIyVh0zn78842mmpOLGTEFE7/S4A5Tb0d2iIu7bryVdoiF0przraYMetJckHxm0P5ITTZrYfRuYRr9n/Q==", | |
| 264 | "license": "Apache-2.0", | |
| 265 | "dependencies": { | |
| 266 | "bare-hrtime": "^2.0.0", | |
| 267 | "bare-logger": "^2.0.0", | |
| 268 | "bare-system-logger": "^1.0.2" | |
| 269 | } | |
| 270 | }, | |
| 271 | 213 | "node_modules/bare-cov": { |
| 272 | 214 | "version": "1.2.1", |
| 273 | 215 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", |
| … | … | @@ -289,6 +231,7 @@ |
| 289 | 231 | "version": "1.13.4", |
| 290 | 232 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.4.tgz", |
| 291 | 233 | "integrity": "sha512-JiCZ5l2YOG1y8J7yy1BCAKTCZrPnHLb7pDRIdurBTOn5oIwBQDIv8iH5Pl2V85vzjl1NZXRfNY4HZLsE942jJA==", |
| 234 | "dev": true, | |
| 292 | 235 | "license": "Apache-2.0", |
| 293 | 236 | "dependencies": { |
| 294 | 237 | "bare-assert": "^1.2.0", |
| … | … | @@ -307,31 +250,17 @@ |
| 307 | 250 | "version": "2.0.0", |
| 308 | 251 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", |
| 309 | 252 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", |
| 253 | "dev": true, | |
| 310 | 254 | "license": "Apache-2.0", |
| 311 | 255 | "dependencies": { |
| 312 | 256 | "bare-os": "^3.0.1" |
| 313 | 257 | } |
| 314 | 258 | }, |
| 315 | "node_modules/bare-dgram": { | |
| 316 | "version": "1.0.1", | |
| 317 | "resolved": "https://registry.npmjs.org/bare-dgram/-/bare-dgram-1.0.1.tgz", | |
| 318 | "integrity": "sha512-EdsyRErrkWgN8fENdrDdXFEE9HAuJ/m6ehXz13fVj9JhdCaLWIA+L8o5aYNRLt66x08RlyG2vbrRAZoxGfcdlg==", | |
| 319 | "license": "Apache-2.0", | |
| 320 | "dependencies": { | |
| 321 | "bare-events": "^2.5.0", | |
| 322 | "udx-native": "^1.11.2" | |
| 323 | } | |
| 324 | }, | |
| 325 | "node_modules/bare-diagnostics-channel": { | |
| 326 | "version": "1.1.0", | |
| 327 | "resolved": "https://registry.npmjs.org/bare-diagnostics-channel/-/bare-diagnostics-channel-1.1.0.tgz", | |
| 328 | "integrity": "sha512-Reu+EQo+eLpB+a5p8UykFEdXndFaRaSgKV38uAMh/qhE2eTeJcdwAwo74hKYyeN8GD3DIFqC9ZlM4bnDc03CIg==", | |
| 329 | "license": "Apache-2.0" | |
| 330 | }, | |
| 331 | 259 | "node_modules/bare-dns": { |
| 332 | 260 | "version": "2.1.4", |
| 333 | 261 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", |
| 334 | 262 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", |
| 263 | "dev": true, | |
| 335 | 264 | "license": "Apache-2.0", |
| 336 | 265 | "engines": { |
| 337 | 266 | "bare": ">=1.7.0" |
| … | … | @@ -341,6 +270,7 @@ |
| 341 | 270 | "version": "1.0.3", |
| 342 | 271 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", |
| 343 | 272 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", |
| 273 | "dev": true, | |
| 344 | 274 | "license": "Apache-2.0", |
| 345 | 275 | "peerDependencies": { |
| 346 | 276 | "bare-buffer": "*" |
| … | … | @@ -355,6 +285,7 @@ |
| 355 | 285 | "version": "3.0.0", |
| 356 | 286 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", |
| 357 | 287 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", |
| 288 | "dev": true, | |
| 358 | 289 | "license": "Apache-2.0", |
| 359 | 290 | "dependencies": { |
| 360 | 291 | "bare-os": "^3.0.1" |
| … | … | @@ -374,70 +305,11 @@ |
| 374 | 305 | } |
| 375 | 306 | } |
| 376 | 307 | }, |
| 377 | "node_modules/bare-fetch": { | |
| 378 | "version": "2.8.2", | |
| 379 | "resolved": "https://registry.npmjs.org/bare-fetch/-/bare-fetch-2.8.2.tgz", | |
| 380 | "integrity": "sha512-N0gZH8iuuC36Ygd4Kjfeo90aOKZ19cycbuZWVpm9W+f5v2ks/2qvgVIKp9hRDZSo5imqO097ynJcn4w24F8c/A==", | |
| 381 | "license": "Apache-2.0", | |
| 382 | "dependencies": { | |
| 383 | "bare-form-data": "^1.2.0", | |
| 384 | "bare-http1": "^4.5.2", | |
| 385 | "bare-https": "^2.0.0", | |
| 386 | "bare-stream": "^2.9.1", | |
| 387 | "bare-url": "^2.4.0", | |
| 388 | "bare-zlib": "^1.3.0" | |
| 389 | }, | |
| 390 | "peerDependencies": { | |
| 391 | "bare-abort-controller": "*", | |
| 392 | "bare-buffer": "*" | |
| 393 | }, | |
| 394 | "peerDependenciesMeta": { | |
| 395 | "bare-abort-controller": { | |
| 396 | "optional": true | |
| 397 | }, | |
| 398 | "bare-buffer": { | |
| 399 | "optional": true | |
| 400 | } | |
| 401 | } | |
| 402 | }, | |
| 403 | "node_modules/bare-fetch/node_modules/bare-http1": { | |
| 404 | "version": "4.5.5", | |
| 405 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz", | |
| 406 | "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==", | |
| 407 | "license": "Apache-2.0", | |
| 408 | "dependencies": { | |
| 409 | "bare-events": "^2.6.0", | |
| 410 | "bare-http-parser": "^1.1.1", | |
| 411 | "bare-stream": "^2.10.0", | |
| 412 | "bare-tcp": "^2.2.0" | |
| 413 | }, | |
| 414 | "peerDependencies": { | |
| 415 | "bare-buffer": "*", | |
| 416 | "bare-url": "*" | |
| 417 | }, | |
| 418 | "peerDependenciesMeta": { | |
| 419 | "bare-buffer": { | |
| 420 | "optional": true | |
| 421 | }, | |
| 422 | "bare-url": { | |
| 423 | "optional": true | |
| 424 | } | |
| 425 | } | |
| 426 | }, | |
| 427 | "node_modules/bare-form-data": { | |
| 428 | "version": "1.2.1", | |
| 429 | "resolved": "https://registry.npmjs.org/bare-form-data/-/bare-form-data-1.2.1.tgz", | |
| 430 | "integrity": "sha512-DFZhp5Tn0S7TjII/xhgLl2BNU6f7l5QbIW/YH7TjzQVnxLa8SaMSbl77jpy0ZR1oUnAo0a7NHJ9QAOU6PPr0Jg==", | |
| 431 | "license": "Apache-2.0", | |
| 432 | "dependencies": { | |
| 433 | "bare-buffer": "^3.6.0", | |
| 434 | "bare-stream": "^2.6.5" | |
| 435 | } | |
| 436 | }, | |
| 437 | 308 | "node_modules/bare-format": { |
| 438 | 309 | "version": "1.0.2", |
| 439 | 310 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz", |
| 440 | 311 | "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==", |
| 312 | "dev": true, | |
| 441 | 313 | "license": "Apache-2.0", |
| 442 | 314 | "dependencies": { |
| 443 | 315 | "bare-inspect": "^3.0.0" |
| … | … | @@ -447,6 +319,7 @@ |
| 447 | 319 | "version": "4.6.0", |
| 448 | 320 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.6.0.tgz", |
| 449 | 321 | "integrity": "sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA==", |
| 322 | "dev": true, | |
| 450 | 323 | "license": "Apache-2.0", |
| 451 | 324 | "dependencies": { |
| 452 | 325 | "bare-events": "^2.5.4", |
| … | … | @@ -471,28 +344,21 @@ |
| 471 | 344 | "version": "2.1.1", |
| 472 | 345 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", |
| 473 | 346 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", |
| 347 | "dev": true, | |
| 474 | 348 | "license": "Apache-2.0" |
| 475 | 349 | }, |
| 476 | 350 | "node_modules/bare-http-parser": { |
| 477 | 351 | "version": "1.1.3", |
| 478 | 352 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.3.tgz", |
| 479 | 353 | "integrity": "sha512-+dhVvQi6brHq14L/XHNRQ+TLuVE76VjRmMt61wVEtS+Od8xUslfMHWJN/ZjIIt3RtTG6vPuA+x9cOh7KrkBJsA==", |
| 354 | "dev": true, | |
| 480 | 355 | "license": "Apache-2.0" |
| 481 | 356 | }, |
| 482 | "node_modules/bare-http1": { | |
| 483 | "version": "2.0.5", | |
| 484 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-2.0.5.tgz", | |
| 485 | "integrity": "sha512-MCaGha2DceFRGAOjrHV6zNYLa71kFR6KHpoc8K2T6ukeL2j7yjbsdFziQIF2mkMjz7RF75sfu1+rtgHEeIBenw==", | |
| 486 | "license": "Apache-2.0", | |
| 487 | "dependencies": { | |
| 488 | "bare-events": "^2.0.0", | |
| 489 | "streamx": "^2.13.0" | |
| 490 | } | |
| 491 | }, | |
| 492 | 357 | "node_modules/bare-https": { |
| 493 | 358 | "version": "2.1.3", |
| 494 | 359 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.3.tgz", |
| 495 | 360 | "integrity": "sha512-0TI/mJXQGYXmG7UUyWEG+KCJusayIAQLywUjFAskDoKuxfqVnGF+M/mTMrEV8J64DaIdU+5x761FvgmT7M68tA==", |
| 361 | "dev": true, | |
| 496 | 362 | "license": "Apache-2.0", |
| 497 | 363 | "dependencies": { |
| 498 | 364 | "bare-http1": "^4.4.0", |
| … | … | @@ -504,6 +370,7 @@ |
| 504 | 370 | "version": "4.5.5", |
| 505 | 371 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz", |
| 506 | 372 | "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==", |
| 373 | "dev": true, | |
| 507 | 374 | "license": "Apache-2.0", |
| 508 | 375 | "dependencies": { |
| 509 | 376 | "bare-events": "^2.6.0", |
| … | … | @@ -587,19 +454,11 @@ |
| 587 | 454 | } |
| 588 | 455 | } |
| 589 | 456 | }, |
| 590 | "node_modules/bare-logger": { | |
| 591 | "version": "2.0.3", | |
| 592 | "resolved": "https://registry.npmjs.org/bare-logger/-/bare-logger-2.0.3.tgz", | |
| 593 | "integrity": "sha512-U6K7NxHdeAHxEgHFDV8zXvgjgDZKQO6LlCrxQvUvlrZEVTnoezSImMWizi85rbZpVsbeI1ZWcLCRGnoIf5EV8Q==", | |
| 594 | "license": "Apache-2.0", | |
| 595 | "dependencies": { | |
| 596 | "bare-format": "^1.0.0" | |
| 597 | } | |
| 598 | }, | |
| 599 | 457 | "node_modules/bare-module": { |
| 600 | 458 | "version": "6.1.3", |
| 601 | 459 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", |
| 602 | 460 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", |
| 461 | "dev": true, | |
| 603 | 462 | "license": "Apache-2.0", |
| 604 | 463 | "dependencies": { |
| 605 | 464 | "bare-bundle": "^1.3.0", |
| … | … | @@ -624,6 +483,7 @@ |
| 624 | 483 | "version": "1.4.7", |
| 625 | 484 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", |
| 626 | 485 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", |
| 486 | "dev": true, | |
| 627 | 487 | "license": "Apache-2.0", |
| 628 | 488 | "dependencies": { |
| 629 | 489 | "require-addon": "^1.0.2" |
| … | … | @@ -654,33 +514,11 @@ |
| 654 | 514 | } |
| 655 | 515 | } |
| 656 | 516 | }, |
| 657 | "node_modules/bare-module-traverse": { | |
| 658 | "version": "2.0.1", | |
| 659 | "resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.0.1.tgz", | |
| 660 | "integrity": "sha512-1au+Og5p97T9b6Y7xmHZ7KtpW8vEYtz2jC2whmm+YJp46EaHfk26j91MmQhufdkR/8sdK1Q5p+P9A/Y5GrJg7Q==", | |
| 661 | "license": "Apache-2.0", | |
| 662 | "dependencies": { | |
| 663 | "bare-addon-resolve": "^1.5.0", | |
| 664 | "bare-module-lexer": "^1.4.0", | |
| 665 | "bare-module-resolve": "^1.7.0" | |
| 666 | }, | |
| 667 | "peerDependencies": { | |
| 668 | "bare-buffer": "*", | |
| 669 | "bare-url": "*" | |
| 670 | }, | |
| 671 | "peerDependenciesMeta": { | |
| 672 | "bare-buffer": { | |
| 673 | "optional": true | |
| 674 | }, | |
| 675 | "bare-url": { | |
| 676 | "optional": true | |
| 677 | } | |
| 678 | } | |
| 679 | }, | |
| 680 | 517 | "node_modules/bare-net": { |
| 681 | 518 | "version": "2.3.1", |
| 682 | 519 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.1.tgz", |
| 683 | 520 | "integrity": "sha512-MypSqDKpDU2Xt7FIfazn5yGvRnV09gFcIPHGWstW0gxuzA4tucTcwJSZeos97C4F89vtU5oGwXDN/HrGN6Y4Jw==", |
| 521 | "dev": true, | |
| 684 | 522 | "license": "Apache-2.0", |
| 685 | 523 | "dependencies": { |
| 686 | 524 | "bare-events": "^2.2.2", |
| … | … | @@ -689,104 +527,11 @@ |
| 689 | 527 | "bare-tcp": "^2.0.0" |
| 690 | 528 | } |
| 691 | 529 | }, |
| 692 | "node_modules/bare-node-runtime": { | |
| 693 | "version": "1.2.0", | |
| 694 | "resolved": "https://registry.npmjs.org/bare-node-runtime/-/bare-node-runtime-1.2.0.tgz", | |
| 695 | "integrity": "sha512-oCpkcKBLDuzhtga0Q/63Ds3Gj03I0jIf+VX4mDhSClpoBfz/wnDtl2d1NKQDAVNCoP4I617GrJkS3vwNO4dd7w==", | |
| 696 | "license": "Apache-2.0", | |
| 697 | "dependencies": { | |
| 698 | "bare-abort-controller": "^1.0.0", | |
| 699 | "bare-assert": "^1.1.0", | |
| 700 | "bare-async-hooks": "^0.0.0", | |
| 701 | "bare-buffer": "^3.3.1", | |
| 702 | "bare-console": "^6.0.1", | |
| 703 | "bare-crypto": "^1.11.2", | |
| 704 | "bare-dgram": "^1.0.1", | |
| 705 | "bare-diagnostics-channel": "^1.1.0", | |
| 706 | "bare-dns": "^2.1.4", | |
| 707 | "bare-events": "^2.7.0", | |
| 708 | "bare-fetch": "^2.5.0", | |
| 709 | "bare-fs": "^4.2.3", | |
| 710 | "bare-http1": "^4.0.4", | |
| 711 | "bare-https": "^2.0.0", | |
| 712 | "bare-inspector": "^5.0.0", | |
| 713 | "bare-module": "^6.1.2", | |
| 714 | "bare-net": "^2.0.2", | |
| 715 | "bare-os": "^3.6.2", | |
| 716 | "bare-path": "^3.0.0", | |
| 717 | "bare-performance": "^1.1.0", | |
| 718 | "bare-process": "^4.2.1", | |
| 719 | "bare-punycode": "^0.0.0", | |
| 720 | "bare-querystring": "^1.0.0", | |
| 721 | "bare-readline": "^1.1.0", | |
| 722 | "bare-repl": "^6.0.1", | |
| 723 | "bare-stream": "^2.7.0", | |
| 724 | "bare-string-decoder": "^1.0.0", | |
| 725 | "bare-subprocess": "^5.1.1", | |
| 726 | "bare-timers": "^3.0.3", | |
| 727 | "bare-tls": "^2.1.3", | |
| 728 | "bare-tty": "^5.0.3", | |
| 729 | "bare-url": "^2.2.2", | |
| 730 | "bare-utils": "^1.5.1", | |
| 731 | "bare-v8": "^1.0.1", | |
| 732 | "bare-vm": "^1.0.0", | |
| 733 | "bare-worker": "^4.0.0", | |
| 734 | "bare-ws": "^2.1.0", | |
| 735 | "bare-zlib": "^1.3.1" | |
| 736 | } | |
| 737 | }, | |
| 738 | "node_modules/bare-node-runtime/node_modules/bare-http1": { | |
| 739 | "version": "4.5.5", | |
| 740 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz", | |
| 741 | "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==", | |
| 742 | "license": "Apache-2.0", | |
| 743 | "dependencies": { | |
| 744 | "bare-events": "^2.6.0", | |
| 745 | "bare-http-parser": "^1.1.1", | |
| 746 | "bare-stream": "^2.10.0", | |
| 747 | "bare-tcp": "^2.2.0" | |
| 748 | }, | |
| 749 | "peerDependencies": { | |
| 750 | "bare-buffer": "*", | |
| 751 | "bare-url": "*" | |
| 752 | }, | |
| 753 | "peerDependenciesMeta": { | |
| 754 | "bare-buffer": { | |
| 755 | "optional": true | |
| 756 | }, | |
| 757 | "bare-url": { | |
| 758 | "optional": true | |
| 759 | } | |
| 760 | } | |
| 761 | }, | |
| 762 | "node_modules/bare-node-runtime/node_modules/bare-inspector": { | |
| 763 | "version": "5.0.0", | |
| 764 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-5.0.0.tgz", | |
| 765 | "integrity": "sha512-blObRFPBK6yn0/wZbrRgkGcOuLUQUYVvkKbsEp5p3bWJB+uNrAh+qUqOijQHS6mZjGvyGCE86VERp3EiUR2TyA==", | |
| 766 | "license": "Apache-2.0", | |
| 767 | "dependencies": { | |
| 768 | "bare-events": "^2.1.0", | |
| 769 | "bare-http1": "^4.0.0", | |
| 770 | "bare-stream": "^2.0.0", | |
| 771 | "bare-url": "^2.0.0", | |
| 772 | "bare-ws": "^2.0.0" | |
| 773 | }, | |
| 774 | "engines": { | |
| 775 | "bare": ">=1.2.0" | |
| 776 | }, | |
| 777 | "peerDependencies": { | |
| 778 | "bare-tcp": "*" | |
| 779 | }, | |
| 780 | "peerDependenciesMeta": { | |
| 781 | "bare-tcp": { | |
| 782 | "optional": true | |
| 783 | } | |
| 784 | } | |
| 785 | }, | |
| 786 | 530 | "node_modules/bare-os": { |
| 787 | 531 | "version": "3.8.7", |
| 788 | 532 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", |
| 789 | 533 | "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", |
| 534 | "devOptional": true, | |
| 790 | 535 | "license": "Apache-2.0", |
| 791 | 536 | "engines": { |
| 792 | 537 | "bare": ">=1.14.0" |
| … | … | @@ -796,21 +541,17 @@ |
| 796 | 541 | "version": "3.0.0", |
| 797 | 542 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", |
| 798 | 543 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", |
| 544 | "devOptional": true, | |
| 799 | 545 | "license": "Apache-2.0", |
| 800 | 546 | "dependencies": { |
| 801 | 547 | "bare-os": "^3.0.1" |
| 802 | 548 | } |
| 803 | 549 | }, |
| 804 | "node_modules/bare-performance": { | |
| 805 | "version": "1.3.0", | |
| 806 | "resolved": "https://registry.npmjs.org/bare-performance/-/bare-performance-1.3.0.tgz", | |
| 807 | "integrity": "sha512-ITlHSQwsnJSPjpagTtPo043LRkFgcrl9SgPsVUBXZvTeGWuNLUfMRwmwAS+35i8qnIEinySoLxwhVoJNZAIF2g==", | |
| 808 | "license": "Apache-2.0" | |
| 809 | }, | |
| 810 | 550 | "node_modules/bare-pipe": { |
| 811 | 551 | "version": "4.1.5", |
| 812 | 552 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.5.tgz", |
| 813 | 553 | "integrity": "sha512-6OfxaG8JSkRh3Gc4hzHRsxNt+yu2PpN7lrv1V+T78GdknWQkVGwiEvu4m+1nbfk8cMVQ0TGxRvQ90XA4rhnTuw==", |
| 554 | "dev": true, | |
| 814 | 555 | "license": "Apache-2.0", |
| 815 | 556 | "dependencies": { |
| 816 | 557 | "bare-events": "^2.0.0", |
| … | … | @@ -824,6 +565,7 @@ |
| 824 | 565 | "version": "4.4.1", |
| 825 | 566 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.4.1.tgz", |
| 826 | 567 | "integrity": "sha512-JfcTtymq6akqM2bdyTsP4A4GYJceBzb91k/ECmFps75uFf6uO33SM1bOZsRAqyRXExabsQJLn5S41NBl8HTM4A==", |
| 568 | "dev": true, | |
| 827 | 569 | "license": "Apache-2.0", |
| 828 | 570 | "dependencies": { |
| 829 | 571 | "bare-abort": "^2.0.13", |
| … | … | @@ -835,56 +577,6 @@ |
| 835 | 577 | "bare-stdio": "^1.0.1" |
| 836 | 578 | } |
| 837 | 579 | }, |
| 838 | "node_modules/bare-punycode": { | |
| 839 | "version": "0.0.0", | |
| 840 | "resolved": "https://registry.npmjs.org/bare-punycode/-/bare-punycode-0.0.0.tgz", | |
| 841 | "integrity": "sha512-PC2Y6mGLytZPJCB9M7CvO5Zb6uWYVUZ+5t3L6vePFuFM6tdC6SsQ+sIsuf0Sa6LHBoWURX7I9yMMbPXMv7TIdQ==", | |
| 842 | "license": "Apache-2.0", | |
| 843 | "dependencies": { | |
| 844 | "punycode": "^2.3.1" | |
| 845 | } | |
| 846 | }, | |
| 847 | "node_modules/bare-querystring": { | |
| 848 | "version": "1.0.0", | |
| 849 | "resolved": "https://registry.npmjs.org/bare-querystring/-/bare-querystring-1.0.0.tgz", | |
| 850 | "integrity": "sha512-Kbqdjd4f1MxMtutXzraEKIXqXRfq9uWLwzUT5bLUh9TCiPznY9l6vHc4BUPNIa13DyH1VAlPy3iOxHzhqV2quQ==", | |
| 851 | "license": "Apache-2.0" | |
| 852 | }, | |
| 853 | "node_modules/bare-readline": { | |
| 854 | "version": "1.3.1", | |
| 855 | "resolved": "https://registry.npmjs.org/bare-readline/-/bare-readline-1.3.1.tgz", | |
| 856 | "integrity": "sha512-QtSU4ZfgQcDI6AQssjDFqTiRe4rCiciMn+Yqx6siMJZBIftAIFrNSOK0sa5SBrmNv/a7CD9Dm0onUDCRyn5Fdg==", | |
| 857 | "license": "Apache-2.0", | |
| 858 | "dependencies": { | |
| 859 | "bare-ansi-escapes": "^2.0.0", | |
| 860 | "bare-stream": "^2.0.0" | |
| 861 | } | |
| 862 | }, | |
| 863 | "node_modules/bare-realm": { | |
| 864 | "version": "2.0.1", | |
| 865 | "resolved": "https://registry.npmjs.org/bare-realm/-/bare-realm-2.0.1.tgz", | |
| 866 | "integrity": "sha512-kQbYU6AAQu4XBTQesuz2+PpjBx7MJzf5Qw3nSLzQCzbVglx7Ml85MtWEjUe1AeslXqrd+nFPHMEbL55ouISscA==", | |
| 867 | "license": "Apache-2.0", | |
| 868 | "engines": { | |
| 869 | "bare": ">=1.5.0" | |
| 870 | } | |
| 871 | }, | |
| 872 | "node_modules/bare-repl": { | |
| 873 | "version": "6.0.2", | |
| 874 | "resolved": "https://registry.npmjs.org/bare-repl/-/bare-repl-6.0.2.tgz", | |
| 875 | "integrity": "sha512-w2R+5LOrMl9UH/gcFjvdIpsB+SgyLr8cW4uJo5bAEzRbRpVA+ZTm8pMHYdj0uqIUBClP/6fkyW0MnB1XkxakmQ==", | |
| 876 | "license": "Apache-2.0", | |
| 877 | "dependencies": { | |
| 878 | "bare-inspect": "^3.0.0", | |
| 879 | "bare-module": "^6.0.1", | |
| 880 | "bare-os": "^3.0.1", | |
| 881 | "bare-path": "^3.0.0", | |
| 882 | "bare-pipe": "^4.0.0", | |
| 883 | "bare-readline": "^1.0.0", | |
| 884 | "bare-stream": "^2.0.0", | |
| 885 | "bare-tty": "^5.0.0" | |
| 886 | } | |
| 887 | }, | |
| 888 | 580 | "node_modules/bare-semver": { |
| 889 | 581 | "version": "1.0.2", |
| 890 | 582 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", |
| … | … | @@ -895,6 +587,7 @@ |
| 895 | 587 | "version": "4.2.0", |
| 896 | 588 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", |
| 897 | 589 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", |
| 590 | "dev": true, | |
| 898 | 591 | "license": "Apache-2.0", |
| 899 | 592 | "dependencies": { |
| 900 | 593 | "bare-events": "^2.5.3", |
| … | … | @@ -908,6 +601,7 @@ |
| 908 | 601 | "version": "1.0.2", |
| 909 | 602 | "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz", |
| 910 | 603 | "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==", |
| 604 | "dev": true, | |
| 911 | 605 | "license": "Apache-2.0", |
| 912 | 606 | "dependencies": { |
| 913 | 607 | "bare-fs": "^4.5.2", |
| … | … | @@ -941,52 +635,11 @@ |
| 941 | 635 | } |
| 942 | 636 | } |
| 943 | 637 | }, |
| 944 | "node_modules/bare-string-decoder": { | |
| 945 | "version": "1.0.0", | |
| 946 | "resolved": "https://registry.npmjs.org/bare-string-decoder/-/bare-string-decoder-1.0.0.tgz", | |
| 947 | "integrity": "sha512-FjFvfHo88U7borNQSj9ijP2JTb1asqY6K28OZrix4dF9iZf5D2wi1+99CgLlHT3HtYqdwxRhDAiKu8rVstt5rQ==", | |
| 948 | "license": "Apache-2.0", | |
| 949 | "dependencies": { | |
| 950 | "text-decoder": "^1.2.3" | |
| 951 | }, | |
| 952 | "peerDependencies": { | |
| 953 | "bare-buffer": "*" | |
| 954 | }, | |
| 955 | "peerDependenciesMeta": { | |
| 956 | "bare-buffer": { | |
| 957 | "optional": true | |
| 958 | } | |
| 959 | } | |
| 960 | }, | |
| 961 | "node_modules/bare-structured-clone": { | |
| 962 | "version": "1.5.3", | |
| 963 | "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.5.3.tgz", | |
| 964 | "integrity": "sha512-vC/YqGsp67ZeFnpyAskwaEIXtNscnCwFVKlSk0Oh2X3AqWT6H+7DC9vVI080FohUJeuUuB9UNx4UFdDcyQgZaw==", | |
| 965 | "license": "Apache-2.0", | |
| 966 | "dependencies": { | |
| 967 | "bare-type": "^1.1.0", | |
| 968 | "compact-encoding": "^2.15.0" | |
| 969 | }, | |
| 970 | "engines": { | |
| 971 | "bare": ">=1.2.0" | |
| 972 | }, | |
| 973 | "peerDependencies": { | |
| 974 | "bare-buffer": "*", | |
| 975 | "bare-url": "*" | |
| 976 | }, | |
| 977 | "peerDependenciesMeta": { | |
| 978 | "bare-buffer": { | |
| 979 | "optional": true | |
| 980 | }, | |
| 981 | "bare-url": { | |
| 982 | "optional": true | |
| 983 | } | |
| 984 | } | |
| 985 | }, | |
| 986 | 638 | "node_modules/bare-stylize": { |
| 987 | 639 | "version": "0.0.1", |
| 988 | 640 | "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz", |
| 989 | 641 | "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==", |
| 642 | "dev": true, | |
| 990 | 643 | "license": "Apache-2.0", |
| 991 | 644 | "dependencies": { |
| 992 | 645 | "bare-ansi-escapes": "^2.2.3", |
| … | … | @@ -997,6 +650,7 @@ |
| 997 | 650 | "version": "5.2.3", |
| 998 | 651 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz", |
| 999 | 652 | "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==", |
| 653 | "dev": true, | |
| 1000 | 654 | "license": "Apache-2.0", |
| 1001 | 655 | "dependencies": { |
| 1002 | 656 | "bare-env": "^3.0.0", |
| … | … | @@ -1017,19 +671,11 @@ |
| 1017 | 671 | } |
| 1018 | 672 | } |
| 1019 | 673 | }, |
| 1020 | "node_modules/bare-system-logger": { | |
| 1021 | "version": "1.0.3", | |
| 1022 | "resolved": "https://registry.npmjs.org/bare-system-logger/-/bare-system-logger-1.0.3.tgz", | |
| 1023 | "integrity": "sha512-HXTXZyhIIS4ZDYun4J9zKNQZDfLgEkkK8wy9nrTFJE5xq8APsOhDxuEsxP0YcNwC23DW1jaNpqD51zDeXPzIdg==", | |
| 1024 | "license": "Apache-2.0", | |
| 1025 | "dependencies": { | |
| 1026 | "bare-logger": "^2.0.0" | |
| 1027 | } | |
| 1028 | }, | |
| 1029 | 674 | "node_modules/bare-tcp": { |
| 1030 | 675 | "version": "2.2.7", |
| 1031 | 676 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.7.tgz", |
| 1032 | 677 | "integrity": "sha512-rjpqNQ2cOCkNo3NeYA/W4GTK3DRkl8sDHO3uos+AEswUjLC8XXMQF8WrJCSjlIowCbS6NUVxKE92X5RGXjyefg==", |
| 678 | "dev": true, | |
| 1033 | 679 | "license": "Apache-2.0", |
| 1034 | 680 | "dependencies": { |
| 1035 | 681 | "bare-dns": "^2.0.4", |
| … | … | @@ -1040,38 +686,11 @@ |
| 1040 | 686 | "bare": ">=1.16.0" |
| 1041 | 687 | } |
| 1042 | 688 | }, |
| 1043 | "node_modules/bare-thread": { | |
| 1044 | "version": "1.2.0", | |
| 1045 | "resolved": "https://registry.npmjs.org/bare-thread/-/bare-thread-1.2.0.tgz", | |
| 1046 | "integrity": "sha512-evYpeDqaTROp2JIdVgWrIBc5rVCV64tFMwZdeL13ahYt/lno2RLvTbYrcf1p6NA2p1SxybbSbGQ2F9/gHudrTg==", | |
| 1047 | "license": "Apache-2.0", | |
| 1048 | "dependencies": { | |
| 1049 | "bare-bundle": "^1.9.0", | |
| 1050 | "bare-module-resolve": "^1.11.2", | |
| 1051 | "bare-module-traverse": "^2.0.0" | |
| 1052 | } | |
| 1053 | }, | |
| 1054 | "node_modules/bare-timers": { | |
| 1055 | "version": "3.2.1", | |
| 1056 | "resolved": "https://registry.npmjs.org/bare-timers/-/bare-timers-3.2.1.tgz", | |
| 1057 | "integrity": "sha512-O+9e6Jol/BoYsQUzAFXG0gWUW1GWryFMblGcNBfi3smPrD3rJIKQwpw2FJARl2j/1VHne4a8YaHPK1cxDKfiYQ==", | |
| 1058 | "license": "Apache-2.0", | |
| 1059 | "engines": { | |
| 1060 | "bare": ">=1.7.0" | |
| 1061 | }, | |
| 1062 | "peerDependencies": { | |
| 1063 | "bare-abort-controller": "*" | |
| 1064 | }, | |
| 1065 | "peerDependenciesMeta": { | |
| 1066 | "bare-abort-controller": { | |
| 1067 | "optional": true | |
| 1068 | } | |
| 1069 | } | |
| 1070 | }, | |
| 1071 | 689 | "node_modules/bare-tls": { |
| 1072 | 690 | "version": "2.2.1", |
| 1073 | 691 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.2.1.tgz", |
| 1074 | 692 | "integrity": "sha512-hZ+ZqwrUO4dyH7/6WYkYWjgAFNJKjzwEYJiDaMnMs+eRleBDjQ3CvNZawpkw0Ar9jnM9NZK6+f6GqjkZ2FLGmQ==", |
| 693 | "dev": true, | |
| 1075 | 694 | "license": "Apache-2.0", |
| 1076 | 695 | "dependencies": { |
| 1077 | 696 | "bare-net": "^2.0.1", |
| … | … | @@ -1085,6 +704,7 @@ |
| 1085 | 704 | "version": "5.1.0", |
| 1086 | 705 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.0.tgz", |
| 1087 | 706 | "integrity": "sha512-EZLvW4A+XiJgI3TW+e1pMME9PIJsfEXe/DA/WSKzIkq/v7Yarpv/rvG6Z5pGnpo4V/Bd+qopwnCLSR71hMMBYA==", |
| 707 | "dev": true, | |
| 1088 | 708 | "license": "Apache-2.0", |
| 1089 | 709 | "dependencies": { |
| 1090 | 710 | "bare-events": "^2.2.0", |
| … | … | @@ -1108,6 +728,7 @@ |
| 1108 | 728 | "version": "2.4.0", |
| 1109 | 729 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", |
| 1110 | 730 | "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", |
| 731 | "devOptional": true, | |
| 1111 | 732 | "license": "Apache-2.0", |
| 1112 | 733 | "peer": true, |
| 1113 | 734 | "dependencies": { |
| … | … | @@ -1118,6 +739,7 @@ |
| 1118 | 739 | "version": "1.6.0", |
| 1119 | 740 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz", |
| 1120 | 741 | "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==", |
| 742 | "dev": true, | |
| 1121 | 743 | "license": "Apache-2.0", |
| 1122 | 744 | "dependencies": { |
| 1123 | 745 | "bare-debug-log": "^2.0.0", |
| … | … | @@ -1128,12 +750,6 @@ |
| 1128 | 750 | "bare-type": "^1.0.6" |
| 1129 | 751 | } |
| 1130 | 752 | }, |
| 1131 | "node_modules/bare-v8": { | |
| 1132 | "version": "1.0.1", | |
| 1133 | "resolved": "https://registry.npmjs.org/bare-v8/-/bare-v8-1.0.1.tgz", | |
| 1134 | "integrity": "sha512-/cR5ZvFWQRdtTZ4tx0j7TKvTWce8UnnLqm88fwHtJmfM7HODIBVjQGDT7KkDLeD2d/eHP2pzB71Y8/QyiMMKrQ==", | |
| 1135 | "license": "Apache-2.0" | |
| 1136 | }, | |
| 1137 | 753 | "node_modules/bare-v8-to-istanbul": { |
| 1138 | 754 | "version": "1.0.2", |
| 1139 | 755 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", |
| … | … | @@ -1152,31 +768,11 @@ |
| 1152 | 768 | "which-runtime": "^1.2.1" |
| 1153 | 769 | } |
| 1154 | 770 | }, |
| 1155 | "node_modules/bare-vm": { | |
| 1156 | "version": "1.0.1", | |
| 1157 | "resolved": "https://registry.npmjs.org/bare-vm/-/bare-vm-1.0.1.tgz", | |
| 1158 | "integrity": "sha512-yLnbRvKt3AhRTmtfTIrYfdTHqGEfIJc+Fgb2DcHejE0HJ+p5adGxxPMvd3893Z7iXVYnalxukNARn4oJSZELHQ==", | |
| 1159 | "license": "Apache-2.0", | |
| 1160 | "dependencies": { | |
| 1161 | "bare-realm": "^2.0.0" | |
| 1162 | } | |
| 1163 | }, | |
| 1164 | "node_modules/bare-worker": { | |
| 1165 | "version": "4.1.6", | |
| 1166 | "resolved": "https://registry.npmjs.org/bare-worker/-/bare-worker-4.1.6.tgz", | |
| 1167 | "integrity": "sha512-yvsektF7xNqlAyjJV+YYeBx+sGZ8Fhbo5Cjc/lpIdkM8Gd/e3MlaxTFg8NknkyvJ4oDJXqvp38Q+ZOn5Gyr+nQ==", | |
| 1168 | "license": "Apache-2.0", | |
| 1169 | "dependencies": { | |
| 1170 | "bare-channel": "^5.1.5", | |
| 1171 | "bare-events": "^2.2.1", | |
| 1172 | "bare-module": "^6.0.1", | |
| 1173 | "bare-thread": "^1.1.3" | |
| 1174 | } | |
| 1175 | }, | |
| 1176 | 771 | "node_modules/bare-ws": { |
| 1177 | 772 | "version": "2.1.0", |
| 1178 | 773 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.1.0.tgz", |
| 1179 | 774 | "integrity": "sha512-2gEWlPK9iyBchACdIY6oQXgmDz3KLrChdwrPgmU3IVXOFTnxTqSUT27WE/+izd4QojHj/SsqDkQiD2HDvuTdAA==", |
| 775 | "dev": true, | |
| 1180 | 776 | "license": "Apache-2.0", |
| 1181 | 777 | "dependencies": { |
| 1182 | 778 | "bare-crypto": "^1.2.0", |
| … | … | @@ -1202,6 +798,7 @@ |
| 1202 | 798 | "version": "4.5.5", |
| 1203 | 799 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz", |
| 1204 | 800 | "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==", |
| 801 | "dev": true, | |
| 1205 | 802 | "license": "Apache-2.0", |
| 1206 | 803 | "dependencies": { |
| 1207 | 804 | "bare-events": "^2.6.0", |
| … | … | @@ -1222,15 +819,6 @@ |
| 1222 | 819 | } |
| 1223 | 820 | } |
| 1224 | 821 | }, |
| 1225 | "node_modules/bare-zlib": { | |
| 1226 | "version": "1.3.3", | |
| 1227 | "resolved": "https://registry.npmjs.org/bare-zlib/-/bare-zlib-1.3.3.tgz", | |
| 1228 | "integrity": "sha512-rXNczo+SQg6cn20olmh/mUiGeJK9maipFH/zI/QwYgwhEmOns1R7fl1GV5apNO+aAp4x2d4uUa7HLhO4mhOnBQ==", | |
| 1229 | "license": "Apache-2.0", | |
| 1230 | "dependencies": { | |
| 1231 | "bare-stream": "^2.0.0" | |
| 1232 | } | |
| 1233 | }, | |
| 1234 | 822 | "node_modules/big-sparse-array": { |
| 1235 | 823 | "version": "1.0.3", |
| 1236 | 824 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", |
| … | … | @@ -1475,10 +1063,6 @@ |
| 1475 | 1063 | "picomatch": "^4.0.2" |
| 1476 | 1064 | } |
| 1477 | 1065 | }, |
| 1478 | "node_modules/hyper-overlay": { | |
| 1479 | "resolved": "../hyper-overlay", | |
| 1480 | "link": true | |
| 1481 | }, | |
| 1482 | 1066 | "node_modules/hypercore": { |
| 1483 | 1067 | "version": "11.28.1", |
| 1484 | 1068 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.28.1.tgz", |
| … | … | @@ -1722,15 +1306,6 @@ |
| 1722 | 1306 | "unslab": "^1.3.0" |
| 1723 | 1307 | } |
| 1724 | 1308 | }, |
| 1725 | "node_modules/punycode": { | |
| 1726 | "version": "2.3.1", | |
| 1727 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", | |
| 1728 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", | |
| 1729 | "license": "MIT", | |
| 1730 | "engines": { | |
| 1731 | "node": ">=6" | |
| 1732 | } | |
| 1733 | }, | |
| 1734 | 1309 | "node_modules/queue-tick": { |
| 1735 | 1310 | "version": "1.0.1", |
| 1736 | 1311 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", |
| … | … | @@ -1,33 +1,27 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "hyper-deploy", |
| 3 | "version": "0.1.0", | |
| 3 | "version": "0.2.0", | |
| 4 | 4 | "description": "P2P release distribution — serve build artifacts to peers over Hyperswarm", |
| 5 | "type": "module", | |
| 6 | 5 | "main": "index.js", |
| 7 | "bin": { | |
| 8 | "hyper-deploy": "./bin/hyper-deploy.js" | |
| 6 | "imports": { | |
| 7 | "fs": { "bare": "bare-fs", "default": "fs" }, | |
| 8 | "path": { "bare": "bare-path", "default": "path" }, | |
| 9 | "os": { "bare": "bare-os", "default": "os" } | |
| 9 | 10 | }, |
| 10 | 11 | "scripts": { |
| 11 | 12 | "test": "brittle test/*.test.js" |
| 12 | 13 | }, |
| 13 | 14 | "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 | 15 | "compact-encoding": "^2.0.0", |
| 21 | 16 | "hypercore-crypto": "^3.0.0", |
| 22 | 17 | "hyperswarm": "^4.0.0", |
| 23 | 18 | "protomux": "^3.0.0", |
| 24 | 19 | "ready-resource": "^1.0.0", |
| 25 | "safety-catch": "^1.0.3", | |
| 26 | "udx-native": "^1.20.3" | |
| 20 | "safety-catch": "^1.0.0" | |
| 27 | 21 | }, |
| 28 | 22 | "devDependencies": { |
| 23 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 29 | 24 | "brittle": "^3.0.0", |
| 30 | "corestore": "^7.0.0", | |
| 31 | "hyper-overlay": "file:../hyper-overlay" | |
| 25 | "corestore": "^7.0.0" | |
| 32 | 26 | } |
| 33 | 27 | } |
| … | … | @@ -0,0 +1,200 @@ |
| 1 | const test = require('brittle'); | |
| 2 | const fs = require('fs'); | |
| 3 | const path = require('path'); | |
| 4 | const os = require('os'); | |
| 5 | const crypto = require('hypercore-crypto'); | |
| 6 | const SecretStream = require('@hyperswarm/secret-stream'); | |
| 7 | const { DeployServer, DeployClient } = require('../'); | |
| 8 | const { buildManifest, diffManifest, hashFile } = require('../lib/scanner'); | |
| 9 | ||
| 10 | const tmpdir = os.tmpdir(); | |
| 11 | ||
| 12 | function mktemp (prefix) { | |
| 13 | const dir = path.join(tmpdir, 'hyper-deploy-test-' + prefix + '-' + Date.now()); | |
| 14 | fs.mkdirSync(dir, { recursive: true }); | |
| 15 | return dir; | |
| 16 | } | |
| 17 | ||
| 18 | // ── Scanner ───────────────────────────────────────────────── | |
| 19 | ||
| 20 | test('buildManifest scans directory', async (t) => { | |
| 21 | const dir = mktemp('scan'); | |
| 22 | fs.writeFileSync(path.join(dir, 'a.txt'), 'hello'); | |
| 23 | fs.writeFileSync(path.join(dir, 'b.txt'), 'world'); | |
| 24 | fs.mkdirSync(path.join(dir, 'sub')); | |
| 25 | fs.writeFileSync(path.join(dir, 'sub', 'c.txt'), 'nested'); | |
| 26 | ||
| 27 | const manifest = buildManifest(dir); | |
| 28 | t.is(manifest.files.length, 3, '3 files'); | |
| 29 | t.ok(manifest.totalSize > 0, 'has size'); | |
| 30 | t.ok(manifest.files[0].hash, 'has hash'); | |
| 31 | t.ok(manifest.files[0].path, 'has path'); | |
| 32 | ||
| 33 | fs.rmSync(dir, { recursive: true }); | |
| 34 | }); | |
| 35 | ||
| 36 | test('diffManifest detects changes', async (t) => { | |
| 37 | const dir = mktemp('diff'); | |
| 38 | fs.writeFileSync(path.join(dir, 'same.txt'), 'unchanged'); | |
| 39 | fs.writeFileSync(path.join(dir, 'changed.txt'), 'original'); | |
| 40 | ||
| 41 | const manifest = buildManifest(dir); | |
| 42 | ||
| 43 | // Modify one file | |
| 44 | fs.writeFileSync(path.join(dir, 'changed.txt'), 'modified'); | |
| 45 | ||
| 46 | const { needed, skipped } = diffManifest(manifest.files, dir); | |
| 47 | t.is(skipped, 1, '1 unchanged'); | |
| 48 | t.is(needed.length, 1, '1 changed'); | |
| 49 | t.is(needed[0], 'changed.txt'); | |
| 50 | ||
| 51 | fs.rmSync(dir, { recursive: true }); | |
| 52 | }); | |
| 53 | ||
| 54 | test('diffManifest handles missing files', async (t) => { | |
| 55 | const manifest = { files: [{ path: 'nope.txt', size: 5, hash: 'abc' }] }; | |
| 56 | const { needed, skipped } = diffManifest(manifest.files, '/nonexistent'); | |
| 57 | t.is(needed.length, 1); | |
| 58 | t.is(skipped, 0); | |
| 59 | }); | |
| 60 | ||
| 61 | // ── DeployServer ──────────────────────────────────────────── | |
| 62 | ||
| 63 | test('DeployServer lifecycle', async (t) => { | |
| 64 | const dir = mktemp('srv'); | |
| 65 | fs.writeFileSync(path.join(dir, 'test.txt'), 'hello'); | |
| 66 | ||
| 67 | const server = new DeployServer(dir); | |
| 68 | await server.ready(); | |
| 69 | ||
| 70 | t.ok(server.manifest, 'has manifest'); | |
| 71 | t.is(server.manifest.files.length, 1); | |
| 72 | t.ok(server.topicHex, 'has topic'); | |
| 73 | t.is(server.connections, 0); | |
| 74 | ||
| 75 | await server.close(); | |
| 76 | fs.rmSync(dir, { recursive: true }); | |
| 77 | }); | |
| 78 | ||
| 79 | test('DeployServer rescan', async (t) => { | |
| 80 | const dir = mktemp('rescan'); | |
| 81 | fs.writeFileSync(path.join(dir, 'a.txt'), 'first'); | |
| 82 | ||
| 83 | const server = new DeployServer(dir); | |
| 84 | await server.ready(); | |
| 85 | t.is(server.manifest.files.length, 1); | |
| 86 | ||
| 87 | fs.writeFileSync(path.join(dir, 'b.txt'), 'second'); | |
| 88 | await server.rescan(); | |
| 89 | t.is(server.manifest.files.length, 2); | |
| 90 | ||
| 91 | await server.close(); | |
| 92 | fs.rmSync(dir, { recursive: true }); | |
| 93 | }); | |
| 94 | ||
| 95 | test('DeployServer rejects missing directory', async (t) => { | |
| 96 | const server = new DeployServer('/nonexistent/path/xyz'); | |
| 97 | try { | |
| 98 | await server.ready(); | |
| 99 | t.fail('should have thrown'); | |
| 100 | } catch (err) { | |
| 101 | t.ok(err.message.includes('not found'), err.message); | |
| 102 | } | |
| 103 | }); | |
| 104 | ||
| 105 | // ── DeployClient ──────────────────────────────────────────── | |
| 106 | ||
| 107 | test('DeployClient timeout on no connection', async (t) => { | |
| 108 | const client = new DeployClient(crypto.randomBytes(32), mktemp('timeout'), { | |
| 109 | timeout: 500 | |
| 110 | }); | |
| 111 | await client.ready(); | |
| 112 | ||
| 113 | try { | |
| 114 | await client.pull(); | |
| 115 | t.fail('should have timed out'); | |
| 116 | } catch (err) { | |
| 117 | t.ok(err.message.includes('timeout'), err.message); | |
| 118 | } | |
| 119 | ||
| 120 | await client.close(); | |
| 121 | }); | |
| 122 | ||
| 123 | // ── Integration ───────────────────────────────────────────── | |
| 124 | ||
| 125 | test('DeployServer + DeployClient full transfer', async (t) => { | |
| 126 | t.timeout(15000); | |
| 127 | ||
| 128 | const srcDir = mktemp('int-src'); | |
| 129 | for (let i = 0; i < 5; i++) { | |
| 130 | fs.writeFileSync(path.join(srcDir, 'file_' + i + '.txt'), 'content ' + i); | |
| 131 | } | |
| 132 | ||
| 133 | const dstDir = mktemp('int-dst'); | |
| 134 | ||
| 135 | // Separate swarms — same process can't self-connect on one swarm | |
| 136 | const Hyperswarm = require('hyperswarm'); | |
| 137 | const topic = crypto.randomBytes(32); | |
| 138 | const swarmA = new Hyperswarm(); | |
| 139 | const swarmB = new Hyperswarm(); | |
| 140 | ||
| 141 | const server = new DeployServer(srcDir, { topic, swarm: swarmA }); | |
| 142 | const client = new DeployClient(topic, dstDir, { swarm: swarmB, timeout: 10000 }); | |
| 143 | ||
| 144 | await server.ready(); | |
| 145 | await client.ready(); | |
| 146 | ||
| 147 | const result = await client.pull(); | |
| 148 | ||
| 149 | t.is(result.filesTransferred, 5, '5 files transferred'); | |
| 150 | t.is(result.filesSkipped, 0, 'none skipped'); | |
| 151 | t.ok(result.bytesReceived > 0, 'received bytes'); | |
| 152 | t.is(result.errors.length, 0, 'no errors'); | |
| 153 | ||
| 154 | for (let i = 0; i < 5; i++) { | |
| 155 | const srcHash = hashFile(path.join(srcDir, 'file_' + i + '.txt')); | |
| 156 | const dstHash = hashFile(path.join(dstDir, 'file_' + i + '.txt')); | |
| 157 | t.is(srcHash, dstHash, 'file_' + i + '.txt matches'); | |
| 158 | } | |
| 159 | ||
| 160 | await client.close(); | |
| 161 | await server.close(); | |
| 162 | await swarmA.destroy(); | |
| 163 | await swarmB.destroy(); | |
| 164 | fs.rmSync(srcDir, { recursive: true }); | |
| 165 | fs.rmSync(dstDir, { recursive: true }); | |
| 166 | }); | |
| 167 | ||
| 168 | test('DeployClient file-level dedup', async (t) => { | |
| 169 | t.timeout(15000); | |
| 170 | ||
| 171 | const srcDir = mktemp('dup-src'); | |
| 172 | fs.writeFileSync(path.join(srcDir, 'a.txt'), 'unchanged'); | |
| 173 | fs.writeFileSync(path.join(srcDir, 'b.txt'), 'will change'); | |
| 174 | ||
| 175 | const dstDir = mktemp('dup-dst'); | |
| 176 | fs.writeFileSync(path.join(dstDir, 'a.txt'), 'unchanged'); | |
| 177 | ||
| 178 | const Hyperswarm = require('hyperswarm'); | |
| 179 | const topic = crypto.randomBytes(32); | |
| 180 | const swarmA = new Hyperswarm(); | |
| 181 | const swarmB = new Hyperswarm(); | |
| 182 | ||
| 183 | const server = new DeployServer(srcDir, { topic, swarm: swarmA }); | |
| 184 | const client = new DeployClient(topic, dstDir, { swarm: swarmB, timeout: 10000 }); | |
| 185 | ||
| 186 | await server.ready(); | |
| 187 | await client.ready(); | |
| 188 | ||
| 189 | const result = await client.pull(); | |
| 190 | ||
| 191 | t.is(result.filesTransferred, 1, '1 transferred'); | |
| 192 | t.is(result.filesSkipped, 1, '1 skipped'); | |
| 193 | ||
| 194 | await client.close(); | |
| 195 | await server.close(); | |
| 196 | await swarmA.destroy(); | |
| 197 | await swarmB.destroy(); | |
| 198 | fs.rmSync(srcDir, { recursive: true }); | |
| 199 | fs.rmSync(dstDir, { recursive: true }); | |
| 200 | }); |