git ssb

3+

cel / ssb-npm-registry



Tree: 5de540f93f944e46ff47bdb266e7d1432d6f9293

Files: 5de540f93f944e46ff47bdb266e7d1432d6f9293 / index.js

22521 bytesRaw
1var http = require('http')
2var os = require('os')
3var path = require('path')
4var fs = require('fs')
5var crypto = require('crypto')
6var pkg = require('./package')
7var semver = require('semver')
8var toPull = require('stream-to-pull-stream')
9var pull = require('pull-stream')
10var tar = require('tar-stream')
11var zlib = require('zlib')
12var hash = require('pull-hash')
13
14function escapeHTML(str) {
15 return String(str)
16 .replace(/</g, '&lt;')
17 .replace(/>/g, '&gt;')
18}
19
20function onceify(fn, self) {
21 var cbs = [], err, data
22 return function (cb) {
23 if (fn) {
24 cbs.push(cb)
25 fn.call(self, function (_err, _data) {
26 err = _err, data = _data
27 var _cbs = cbs
28 cbs = null
29 while (_cbs.length) _cbs.shift()(err, data)
30 })
31 fn = null
32 } else if (cbs) {
33 cbs.push(cb)
34 } else {
35 cb(err, data)
36 }
37 }
38}
39
40function once(cb) {
41 var done
42 return function (err, result) {
43 if (done) {
44 if (err) console.trace(err)
45 } else {
46 done = true
47 cb(err, result)
48 }
49 }
50}
51
52function pkgLockToRegistryPkgs(pkgLock, wsPort) {
53 // convert a package-lock.json file into data for serving as an npm registry
54 var hasNonBlobUrl = false
55 var blobUrlRegex = new RegExp('^http://localhost:' + wsPort + '/blobs/get/&')
56 var pkgs = {}
57 var queue = [pkgLock, pkgLock.name]
58 while (queue.length) {
59 var dep = queue.shift(), name = queue.shift()
60 if (name) {
61 var pkg = pkgs[name] || (pkgs[name] = {
62 _id: name,
63 name: name,
64 versions: {}
65 })
66 if (dep.version && dep.integrity && dep.resolved) {
67 if (!hasNonBlobUrl && !blobUrlRegex.test(dep.resolved)) hasNonBlobUrl = true
68 pkg.versions[dep.version] = {
69 name: name,
70 version: dep.version,
71 dist: {
72 integrity: dep.integrity,
73 tarball: dep.resolved
74 }
75 }
76 }
77 }
78 if (dep.dependencies) for (var depName in dep.dependencies) {
79 queue.push(dep.dependencies[depName], depName)
80 }
81 }
82 pkgs._hasNonBlobUrl = hasNonBlobUrl
83 return pkgs
84}
85
86function npmLogin(registryAddress, cb) {
87 var tokenLine = registryAddress.replace(/^http:/, '') + ':_authToken=1'
88 var filename = path.join(os.homedir(), '.npmrc')
89 fs.readFile(filename, 'utf8', function (err, data) {
90 if (err && err.code === 'ENOENT') data = ''
91 else if (err) return cb(new Error(err.stack))
92 var lines = data ? data.split('\n') : []
93 if (lines.indexOf(tokenLine) > -1) return cb()
94 var trailingNewline = (lines.length === 0 || lines[lines.length-1] === '')
95 var line = trailingNewline ? tokenLine + '\n' : '\n' + tokenLine
96 fs.appendFile(filename, line, cb)
97 })
98}
99
100function formatHost(host) {
101 return /^[^\[]:.*:.*:/.test(host) ? '[' + host + ']' : host
102}
103
104exports.name = 'npm-registry'
105exports.version = '1.0.0'
106exports.manifest = {
107 getAddress: 'async'
108}
109exports.init = function (sbot, config) {
110 var conf = config.npm || {}
111 var port = conf.port || 8043
112 var host = conf.host || null
113 var autoAuth = conf.autoAuth !== false
114
115 var server = http.createServer(exports.respond(sbot, config))
116 var getAddress = onceify(function (cb) {
117 server.on('error', cb)
118 server.listen(port, host, function () {
119 server.removeListener('error', cb)
120 var regHost = formatHost(host || 'localhost')
121 var regPort = this.address().port
122 var regUrl = 'http://' + regHost + ':' + regPort + '/'
123 if (autoAuth) npmLogin(regUrl, next)
124 else next()
125 function next(err) {
126 cb(err, regUrl)
127 }
128 })
129 sbot.on('close', function () {
130 server.close()
131 })
132 })
133
134 getAddress(function (err, addr) {
135 if (err) return console.error(err)
136 console.log('[npm-registry] Listening on ' + addr)
137 })
138
139 return {
140 getAddress: getAddress
141 }
142}
143
144exports.respond = function (sbot, config) {
145 var reg = new SsbNpmRegistryServer(sbot, config)
146 return function (req, res) {
147 new Req(reg, req, res).serve()
148 }
149}
150
151function publishMsg(sbot, value, cb) {
152 var gotExpectedPrevious = false
153 sbot.publish(value, function next(err, msg) {
154 if (err && /^expected previous:/.test(err.message)) {
155 // retry once on this error
156 if (gotExpectedPrevious) return cb(err)
157 gotExpectedPrevious = true
158 return sbot.publish(value, next)
159 }
160 cb(err, msg)
161 })
162}
163
164function publishMentions(sbot, mentions, cb) {
165 // console.error("publishing %s mentions", mentions.length)
166 if (mentions.length === 0) return cb(new Error('Empty mentions list'))
167 publishMsg(sbot, {
168 type: 'npm-packages',
169 mentions: mentions,
170 }, cb)
171}
172
173exports.publishPkgMentions = function (sbot, mentions, cb) {
174 // try to fit the mentions into as few messages as possible,
175 // while fitting under the message size limit.
176 var msgs = []
177 ;(function next(i, chunks) {
178 if (i >= mentions.length) return cb(null, msgs)
179 var chunkLen = Math.ceil(mentions.length / chunks)
180 publishMentions(sbot, mentions.slice(i, i + chunkLen), function (err, msg) {
181 if (err && /must not be large/.test(err.message)) return next(i, chunks + 1)
182 if (err && msgs.length) return onPartialPublish(err)
183 if (err) return cb(err)
184 msgs.push(msg)
185 next(i + chunkLen, chunks)
186 })
187 })(0, 1)
188 function onPartialPublish(err) {
189 var remaining = mentions.length - i
190 return cb(new Error('Published messages ' +
191 msgs.map(function (msg) { return msg.key }).join(', ') + ' ' +
192 'but failed to publish remaining ' + remaining + ': ' + (err.stack || err)))
193 }
194}
195
196function SsbNpmRegistryServer(sbot, config) {
197 this.sbot = sbot
198 this.config = config
199 this.npmConfig = config.npm || {}
200 this.host = this.npmConfig.host || 'localhost'
201 this.links2 = sbot.links2
202 if (!this.links2) throw new Error('missing ssb-links2 scuttlebot plugin')
203 this.wsPort = config.ws && Number(config.ws.port) || '8989'
204 this.blobsPrefix = 'http://' + (config.host || 'localhost') + ':'
205 + this.wsPort + '/blobs/get/'
206 this.getBootstrapInfo = onceify(this.getBootstrapInfo, this)
207}
208
209SsbNpmRegistryServer.prototype = Object.create(http.Server.prototype)
210SsbNpmRegistryServer.prototype.constructor = SsbNpmRegistryServer
211
212SsbNpmRegistryServer.prototype.pushBlobs = function (ids, cb) {
213 var self = this
214 if (!self.sbot.blobs.push) return cb(new Error('missing blobs.push'))
215 ;(function next(i) {
216 if (i >= ids.length) return cb()
217 self.sbot.blobs.push(ids[i], function (err) {
218 if (err) return cb(err)
219 next(i+1)
220 })
221 })(0)
222}
223
224SsbNpmRegistryServer.prototype.getBlob = function (id, cb) {
225 var blobs = this.sbot.blobs
226 blobs.size(id, function (err, size) {
227 if (typeof size === 'number') cb(null, blobs.get(id))
228 else blobs.want(id, function (err, got) {
229 if (err) cb(err)
230 else if (!got) cb('missing blob ' + id)
231 else cb(null, blobs.get(id))
232 })
233 })
234}
235
236SsbNpmRegistryServer.prototype.blobDist = function (id) {
237 var m = /^&([^.]+)\.([a-z0-9]+)$/.exec(id)
238 if (!m) throw new Error('bad blob id: ' + id)
239 return {
240 integrity: m[2] + '-' + m[1],
241 tarball: 'http://localhost:' + this.wsPort + '/blobs/get/' + id
242 }
243}
244
245SsbNpmRegistryServer.prototype.getMentions = function (name) {
246 return this.links2.read({
247 query: [
248 {$filter: {rel: ['mentions', name, {$gt: true}]}},
249 {$filter: {dest: {$prefix: '&'}}},
250 {$map: {
251 name: ['rel', 1],
252 size: ['rel', 2],
253 link: 'dest',
254 author: 'source',
255 ts: 'ts'
256 }}
257 ]
258 })
259}
260
261SsbNpmRegistryServer.prototype.getLocalPrebuildsLinks = function (cb) {
262 var self = this
263 var prebuildsDir = path.join(os.homedir(), '.npm', '_prebuilds')
264 var ids = {}
265 var nameRegex = new RegExp('^http-' + self.host.replace(/\./g, '.') + '-(?:[0-9]+)-prebuild-(.*)$')
266 fs.readdir(prebuildsDir, function (err, filenames) {
267 if (err) return cb(new Error(err.stack || err))
268 ;(function next(i) {
269 if (i >= filenames.length) return cb(null, ids)
270 var m = nameRegex.exec(filenames[i])
271 if (!m) return next(i+1)
272 var name = m[1]
273 fs.readFile(path.join(prebuildsDir, filenames[i]), function (err, data) {
274 if (err) return cb(new Error(err.stack || err))
275 self.sbot.blobs.add(function (err, id) {
276 if (err) return cb(new Error(err.stack || err))
277 ids[name] = id
278 next(i+1)
279 })(pull.once(data))
280 })
281 })(0)
282 })
283}
284
285SsbNpmRegistryServer.prototype.getBootstrapInfo = function (cb) {
286 var self = this
287 if (!self.sbot.bootstrap) return cb(new Error('missing sbot bootstrap plugin'))
288
289 self.sbot.bootstrap.getPackageLock(function (err, sbotPkgLock) {
290 if (err) return cb(new Error(err.stack || err))
291 var pkgs = pkgLockToRegistryPkgs(sbotPkgLock, self.wsPort)
292 if (pkgs._hasNonBlobUrl) {
293 console.error('[npm-registry] Warning: package-lock.json has non-blob URLs. Bootstrap installation may not be fully peer-to-peer.')
294 }
295
296 if (!sbotPkgLock.name) console.trace('missing pkg lock name')
297 if (!sbotPkgLock.version) console.trace('missing pkg lock version')
298
299 var waiting = 2
300
301 self.sbot.blobs.add(function (err, id) {
302 if (err) return next(new Error(err.stack || err))
303 var pkg = pkgs[sbotPkgLock.name] || (pkgs[sbotPkgLock.name] = {})
304 var versions = pkg.versions || (pkg.versions = {})
305 pkg.versions[sbotPkgLock.version] = {
306 name: sbotPkgLock.name,
307 version: sbotPkgLock.version,
308 dist: self.blobDist(id)
309 }
310 var distTags = pkg['dist-tags'] || (pkg['dist-tags'] = {})
311 distTags.latest = sbotPkgLock.version
312 next()
313 })(self.sbot.bootstrap.pack())
314
315 var prebuilds
316 self.getLocalPrebuildsLinks(function (err, _prebuilds) {
317 if (err) return next(err)
318 prebuilds = _prebuilds
319 next()
320 })
321
322 function next(err) {
323 if (err) return waiting = 0, cb(err)
324 if (--waiting) return
325 fs.readFile(path.join(__dirname, 'bootstrap.js'), {
326 encoding: 'utf8'
327 }, function (err, bootstrapScript) {
328 if (err) return cb(err)
329 var script = bootstrapScript + '\n' +
330 'exports.pkgs = ' + JSON.stringify(pkgs, 0, 2) + '\n' +
331 'exports.prebuilds = ' + JSON.stringify(prebuilds, 0, 2)
332
333 self.sbot.blobs.add(function (err, id) {
334 if (err) return cb(new Error(err.stack || err))
335 var m = /^&([^.]+)\.([a-z0-9]+)$/.exec(id)
336 if (!m) return cb(new Error('bad blob id: ' + id))
337 cb(null, {
338 name: sbotPkgLock.name,
339 blob: id,
340 hashType: m[2],
341 hashBuf: Buffer.from(m[1], 'base64'),
342 })
343 })(pull.once(script))
344 })
345 }
346 })
347}
348
349function Req(server, req, res) {
350 this.server = server
351 this.req = req
352 this.res = res
353 this.blobsToPush = []
354
355 var ua = this.req.headers['user-agent']
356 this.isNpm3 = /\bnpm\/3/.test(ua)
357 this.isYarn = /\byarn\//.test(ua)
358}
359
360Req.prototype.serve = function () {
361 console.log(this.req.method, this.req.url, this.req.socket.remoteAddress.replace(/^::ffff:/, ''))
362 var pathname = this.req.url.replace(/\?.*/, '')
363 var m
364 if (pathname === '/') return this.serveHome()
365 if (pathname === '/bootstrap') return this.serveBootstrap()
366 if (pathname === '/-/whoami') return this.serveWhoami()
367 if (pathname === '/-/ping') return this.respond(200, true)
368 if (pathname === '/-/user/org.couchdb.user:1') return this.serveUser1()
369 if ((m = /^\/-\/prebuild\/(.*)$/.exec(pathname))) return this.servePrebuild(m[1])
370 if (!/^\/-\//.test(pathname)) return this.servePkg(pathname.substr(1))
371 return this.respond(404)
372}
373
374Req.prototype.respond = function (status, message) {
375 this.res.writeHead(status, {'content-type': 'application/json'})
376 this.res.end(message && JSON.stringify(message, 0, 2))
377}
378
379Req.prototype.respondError = function (status, message) {
380 this.respond(status, {error: message})
381}
382
383var bootstrapName = 'ssb-npm-bootstrap'
384
385Req.prototype.serveHome = function () {
386 var self = this
387 self.res.writeHead(200, {'content-type': 'text/html'})
388 var port = 8044
389 self.res.end('<!doctype html><html><head><meta charset=utf-8>' +
390 '<title>' + escapeHTML(pkg.name) + '</title></head><body>' +
391 '<h1>' + escapeHTML(pkg.name) + '</h1>\n' +
392 '<p><a href="/bootstrap">Bootstrap</a></p>\n' +
393 '</body></html>')
394}
395
396Req.prototype.serveBootstrap = function () {
397 var self = this
398 self.server.getBootstrapInfo(function (err, info) {
399 if (err) return this.respondError(err.stack || err)
400 var pkgNameText = info.name
401 var pkgTmpText = '/tmp/' + bootstrapName + '.js'
402 var host = String(self.req.headers.host).replace(/:[0-9]*$/, '') || self.req.socket.localAddress
403 var httpHost = /^[^\[]:.*:.*:/.test(host) ? '[' + host + ']' : host
404 var blobsHostname = httpHost + ':' + self.server.wsPort
405 var tarballLink = 'http://' + blobsHostname + '/blobs/get/' + info.blob
406 var pkgHashText = info.hashBuf.toString('hex')
407 var hashCmd = info.hashType + 'sum'
408
409 var script =
410 'wget \'' + tarballLink + '\' -O ' + pkgTmpText + ' &&\n' +
411 'echo ' + pkgHashText + ' ' + pkgTmpText + ' | ' + hashCmd + ' -c &&\n' +
412 'node ' + pkgTmpText + ' --blobs-remote ' + blobsHostname + ' -- ' +
413 'npm install -g ' + info.name + ' &&\n' +
414 'sbot server'
415
416 self.res.writeHead(200, {'content-type': 'text/plain'})
417 self.res.end(script)
418 })
419}
420
421Req.prototype.serveWhoami = function () {
422 var self = this
423 self.server.sbot.whoami(function (err, feed) {
424 if (err) return self.respondError(err.stack || err)
425 self.respond(200, {username: feed.id})
426 })
427}
428
429Req.prototype.serveUser1 = function () {
430 this.respond(this.req.method === 'PUT' ? 201 : 200, {token: '1'})
431}
432
433function decodeName(name) {
434 var parts = name.replace(/\.tgz$/, '').split(':')
435 return {
436 name: parts[1],
437 version: parts[2],
438 distTag: parts[3],
439 }
440}
441
442Req.prototype.servePkg = function (pathname) {
443 var self = this
444 var parts = pathname.split('/')
445 var pkgName = parts.shift()
446 if (parts[0] === '-rev') return this.respondError(501, 'Unpublish is not supported')
447 var spec = parts.shift()
448 if (spec) try { spec = decodeURIComponent(spec) } finally {}
449 if (parts.length > 0) return this.respondError(404)
450 if (self.req.method === 'PUT') return self.publishPkg(pkgName)
451 var obj = {
452 _id: pkgName,
453 name: pkgName,
454 'dist-tags': {},
455 versions: {}
456 }
457 pull(
458 self.server.getMentions({$prefix: 'npm:' + pkgName + ':'}),
459 pull.drain(function (mention) {
460 var data = decodeName(mention.name)
461 if (!data.version) return
462 if (data.distTag) obj['dist-tags'][data.distTag] = data.version
463 obj.versions[data.version] = {
464 author: {
465 url: mention.author
466 },
467 name: pkgName,
468 version: data.version,
469 dist: self.server.blobDist(mention.link)
470 }
471 }, function (err) {
472 if (err) return self.respondError(500, err.stack || err)
473 if (spec) resolveSpec()
474 else if (self.isNpm3 || self.isYarn) resolveAll()
475 else self.respond(200, obj)
476 })
477 )
478 function resolveSpec() {
479 var version = obj['dist-tags'][spec]
480 || semver.maxSatisfying(Object.keys(obj.versions), spec)
481 obj = obj.versions[version]
482 if (!obj) return self.respondError(404, 'version not found: ' + spec)
483 self.populatePackageJson(obj, function (err, pkg) {
484 if (err) return self.respondError(500, err.stack || err)
485 self.respond(200, pkg || obj)
486 })
487 }
488 function resolveAll() {
489 // return self.respond(200, obj)
490 var waiting = 0
491 for (var version in obj.versions) (function (version) {
492 waiting++
493 self.populatePackageJson(obj.versions[version], function (err, pkg) {
494 if (err && waiting <= 0) return console.trace(err)
495 if (err) return waiting = 0, self.respondError(500, err.stack || err)
496 if (pkg) obj.versions[version] = pkg
497 if (!--waiting) self.respond(200, obj)
498 })
499 }(version))
500 }
501}
502
503Req.prototype.servePrebuild = function (name) {
504 var self = this
505 var getMention = self.server.getMentions('prebuild:' + name)
506 var blobsByAuthor = {/* <author>: BlobId */}
507 getMention(null, function next(err, link) {
508 if (err === true) return done()
509 if (err) return self.respondError(500, err.stack || err)
510 blobsByAuthor[link.author] = link.link
511 getMention(null, next)
512 })
513 function done() {
514 var authorsByLink = {/* <BlobId>: [FeedId...] */}
515 var blobId
516 for (var feed in blobsByAuthor) {
517 var blob = blobId = blobsByAuthor[feed]
518 var feeds = authorsByLink[blob] || (authorsByLink[blob] = [])
519 feeds.push(feed)
520 }
521 switch (Object.keys(authorsByLink).length) {
522 case 0:
523 return self.respondError(404, 'Not Found')
524 case 1:
525 self.res.writeHead(303, {Location: self.server.blobsPrefix + blobId})
526 return self.res.end()
527 default:
528 return self.respond(300, {choices: authorsByLink})
529 }
530 }
531}
532
533var localhosts = {
534 '::1': true,
535 '127.0.0.1': true,
536 '::ffff:127.0.0.1': true,
537}
538
539Req.prototype.publishPkg = function (pkgName) {
540 var self = this
541 var remoteAddress = self.req.socket.remoteAddress
542 if (!(remoteAddress in localhosts)) {
543 return self.respondError(403, 'You may not publish as this user.')
544 }
545
546 var chunks = []
547 self.req.on('data', function (data) {
548 chunks.push(data)
549 })
550 self.req.on('end', function () {
551 var data
552 try {
553 data = JSON.parse(Buffer.concat(chunks))
554 } catch(e) {
555 return self.respondError(400, e.stack)
556 }
557 return self.publishPkg2(pkgName, data || {})
558 })
559}
560
561Req.prototype.publishPkg2 = function (name, data) {
562 var self = this
563 if (data.users) console.trace('[npm-registry] users property is not supported')
564 var attachments = data._attachments || {}
565 var links = {/* <name>-<version>.tgz: {link: <BlobId>, size: number} */}
566 var waiting = 0
567 Object.keys(attachments).forEach(function (filename) {
568 waiting++
569 var tarball = new Buffer(attachments[filename].data, 'base64')
570 var length = attachments[filename].length
571 if (length && length !== tarball.length) return self.respondError(400,
572 'Length mismatch for attachment \'' + filename + '\'')
573 self.server.sbot.blobs.add(function (err, id) {
574 if (err) return self.respondError(500,
575 'Adding attachment \'' + filename + '\' as blob failed')
576 self.blobsToPush.push(id)
577 links[filename] = {link: id, size: tarball.length}
578 if (!--waiting) next()
579 })(pull.once(tarball))
580 })
581 function next() {
582 try {
583 self.publishPkg3(name, data, links)
584 } catch(e) {
585 self.respondError(500, e.stack || e)
586 }
587 }
588}
589
590Req.prototype.publishPkg3 = function (name, data, links) {
591 var self = this
592 var versions = data.versions || {}
593 var linksByVersion = {/* <version>: link */}
594
595 // associate tarball blobs with versions
596 for (var version in versions) {
597 var pkg = versions[version]
598 if (!pkg) return self.respondError(400, 'Bad package object')
599 if (!pkg.dist) return self.respondError(400, 'Missing package dist property')
600 if (!pkg.dist.tarball) return self.respondError(400, 'Missing dist.tarball property')
601 if (pkg.deprecated) return self.respondError(501, 'Deprecation is not supported')
602 var m = /\/-\/([^\/]+)$/.exec(pkg.dist.tarball)
603 if (!m) return self.respondError(400, 'Bad tarball URL \'' + pkg.dist.tarball + '\'')
604 var filename = m[1]
605 var link = links[filename]
606 if (!link) return self.respondError(501, 'Unable to find attachment \'' + filename + '\'')
607 // TODO?: try to find missing tarball mentioned in other messages
608 if (pkg.version && pkg.version !== version)
609 return self.respondError(400, 'Mismatched package version: ' + [pkg.version, version])
610 linksByVersion[version] = link
611 link.version = version
612 }
613
614 // associate blobs with dist-tags
615 var tags = data['dist-tags'] || {}
616 for (var tag in tags) {
617 var version = tags[tag]
618 var link = linksByVersion[version]
619 if (!link) return self.respondError(501, 'Setting a dist-tag for a version not being published is not supported.')
620 // TODO?: support setting dist-tag without version,
621 // by looking up a tarball blob for the version
622 link.tag = tag
623 }
624
625 // compute blob links to publish
626 var mentions = []
627 for (var filename in links) {
628 var link = links[filename] || {}
629 if (!link.version) return self.respondError(400, 'Attachment ' + filename + ' was not linked to in the package metadata')
630 mentions.push({
631 name: 'npm:' + name + ':' + link.version + (link.tag ? ':' + link.tag : ''),
632 link: link.link,
633 size: link.size,
634 })
635 }
636 return self.publishPkgs(mentions)
637}
638
639Req.prototype.publishPkgs = function (mentions) {
640 var self = this
641 exports.publishPkgMentions(self.server.sbot, mentions, function (err, msgs) {
642 if (err) self.respondError(500, err.stack || err)
643 self.server.pushBlobs(self.blobsToPush, function (err) {
644 if (err) console.error('[npm-registry] Failed to push blob ' + id + ': ' + (err.stack || err))
645 self.respond(201)
646 console.log(msgs.map(function (msg) { return msg.key }).join('\n'))
647 })
648 })
649}
650
651Req.prototype.populatePackageJson = function (obj, cb) {
652 var blobId = obj.dist.tarball.replace(/.*\/blobs\/get\//, '')
653 this.getPackageJsonFromTarballBlob(blobId, function (err, pkg) {
654 if (err) return cb(err)
655 pkg.dist = obj.dist
656 pkg.dist.shasum = pkg._shasum
657 pkg.author = pkg.author || obj.author
658 pkg.version = pkg.version || obj.version
659 pkg.name = pkg.name || obj.name
660 cb(null, pkg)
661 })
662}
663
664Req.prototype.getPackageJsonFromTarballBlob = function (id, cb) {
665 var self = this
666 self.server.getBlob(id, function (err, readBlob) {
667 if (err) return cb(err)
668 cb = once(cb)
669 var extract = tar.extract()
670 var pkg, shasum
671 extract.on('entry', function (header, stream, next) {
672 if (/^[^\/]*\/package\.json$/.test(header.name)) {
673 pull(toPull.source(stream), pull.collect(function (err, bufs) {
674 if (err) return cb(err)
675 try { pkg = JSON.parse(Buffer.concat(bufs)) }
676 catch(e) { return cb(e) }
677 next()
678 }))
679 } else {
680 stream.on('end', next)
681 stream.resume()
682 }
683 })
684 extract.on('finish', function () {
685 pkg._shasum = shasum
686 cb(null, pkg)
687 })
688 pull(
689 readBlob,
690 hash('sha256', 'hex', function (err, sum) {
691 if (err) return cb(err)
692 shasum = sum
693 }),
694 toPull(zlib.createGunzip()),
695 toPull(extract)
696 )
697 })
698}
699

Built with git-ssb-web