git ssb

3+

cel / ssb-npm-registry



Tree: 58de1d615660eb5d4c847daa1b86913adec2c4b1

Files: 58de1d615660eb5d4c847daa1b86913adec2c4b1 / index.js

22529 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 done()
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 obj = pkg || obj
486 done()
487 })
488 }
489 function resolveAll() {
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) done()
498 })
499 }(version))
500 if (!waiting) done()
501 }
502 function done() {
503 self.respond(200, obj)
504 }
505}
506
507Req.prototype.servePrebuild = function (name) {
508 var self = this
509 var getMention = self.server.getMentions('prebuild:' + name)
510 var blobsByAuthor = {/* <author>: BlobId */}
511 getMention(null, function next(err, link) {
512 if (err === true) return done()
513 if (err) return self.respondError(500, err.stack || err)
514 blobsByAuthor[link.author] = link.link
515 getMention(null, next)
516 })
517 function done() {
518 var authorsByLink = {/* <BlobId>: [FeedId...] */}
519 var blobId
520 for (var feed in blobsByAuthor) {
521 var blob = blobId = blobsByAuthor[feed]
522 var feeds = authorsByLink[blob] || (authorsByLink[blob] = [])
523 feeds.push(feed)
524 }
525 switch (Object.keys(authorsByLink).length) {
526 case 0:
527 return self.respondError(404, 'Not Found')
528 case 1:
529 self.res.writeHead(303, {Location: self.server.blobsPrefix + blobId})
530 return self.res.end()
531 default:
532 return self.respond(300, {choices: authorsByLink})
533 }
534 }
535}
536
537var localhosts = {
538 '::1': true,
539 '127.0.0.1': true,
540 '::ffff:127.0.0.1': true,
541}
542
543Req.prototype.publishPkg = function (pkgName) {
544 var self = this
545 var remoteAddress = self.req.socket.remoteAddress
546 if (!(remoteAddress in localhosts)) {
547 return self.respondError(403, 'You may not publish as this user.')
548 }
549
550 var chunks = []
551 self.req.on('data', function (data) {
552 chunks.push(data)
553 })
554 self.req.on('end', function () {
555 var data
556 try {
557 data = JSON.parse(Buffer.concat(chunks))
558 } catch(e) {
559 return self.respondError(400, e.stack)
560 }
561 return self.publishPkg2(pkgName, data || {})
562 })
563}
564
565Req.prototype.publishPkg2 = function (name, data) {
566 var self = this
567 if (data.users) console.trace('[npm-registry] users property is not supported')
568 var attachments = data._attachments || {}
569 var links = {/* <name>-<version>.tgz: {link: <BlobId>, size: number} */}
570 var waiting = 0
571 Object.keys(attachments).forEach(function (filename) {
572 waiting++
573 var tarball = new Buffer(attachments[filename].data, 'base64')
574 var length = attachments[filename].length
575 if (length && length !== tarball.length) return self.respondError(400,
576 'Length mismatch for attachment \'' + filename + '\'')
577 self.server.sbot.blobs.add(function (err, id) {
578 if (err) return self.respondError(500,
579 'Adding attachment \'' + filename + '\' as blob failed')
580 self.blobsToPush.push(id)
581 links[filename] = {link: id, size: tarball.length}
582 if (!--waiting) next()
583 })(pull.once(tarball))
584 })
585 function next() {
586 try {
587 self.publishPkg3(name, data, links)
588 } catch(e) {
589 self.respondError(500, e.stack || e)
590 }
591 }
592}
593
594Req.prototype.publishPkg3 = function (name, data, links) {
595 var self = this
596 var versions = data.versions || {}
597 var linksByVersion = {/* <version>: link */}
598
599 // associate tarball blobs with versions
600 for (var version in versions) {
601 var pkg = versions[version]
602 if (!pkg) return self.respondError(400, 'Bad package object')
603 if (!pkg.dist) return self.respondError(400, 'Missing package dist property')
604 if (!pkg.dist.tarball) return self.respondError(400, 'Missing dist.tarball property')
605 if (pkg.deprecated) return self.respondError(501, 'Deprecation is not supported')
606 var m = /\/-\/([^\/]+)$/.exec(pkg.dist.tarball)
607 if (!m) return self.respondError(400, 'Bad tarball URL \'' + pkg.dist.tarball + '\'')
608 var filename = m[1]
609 var link = links[filename]
610 if (!link) return self.respondError(501, 'Unable to find attachment \'' + filename + '\'')
611 // TODO?: try to find missing tarball mentioned in other messages
612 if (pkg.version && pkg.version !== version)
613 return self.respondError(400, 'Mismatched package version: ' + [pkg.version, version])
614 linksByVersion[version] = link
615 link.version = version
616 }
617
618 // associate blobs with dist-tags
619 var tags = data['dist-tags'] || {}
620 for (var tag in tags) {
621 var version = tags[tag]
622 var link = linksByVersion[version]
623 if (!link) return self.respondError(501, 'Setting a dist-tag for a version not being published is not supported.')
624 // TODO?: support setting dist-tag without version,
625 // by looking up a tarball blob for the version
626 link.tag = tag
627 }
628
629 // compute blob links to publish
630 var mentions = []
631 for (var filename in links) {
632 var link = links[filename] || {}
633 if (!link.version) return self.respondError(400, 'Attachment ' + filename + ' was not linked to in the package metadata')
634 mentions.push({
635 name: 'npm:' + name + ':' + link.version + (link.tag ? ':' + link.tag : ''),
636 link: link.link,
637 size: link.size,
638 })
639 }
640 return self.publishPkgs(mentions)
641}
642
643Req.prototype.publishPkgs = function (mentions) {
644 var self = this
645 exports.publishPkgMentions(self.server.sbot, mentions, function (err, msgs) {
646 if (err) self.respondError(500, err.stack || err)
647 self.server.pushBlobs(self.blobsToPush, function (err) {
648 if (err) console.error('[npm-registry] Failed to push blob ' + id + ': ' + (err.stack || err))
649 self.respond(201)
650 console.log(msgs.map(function (msg) { return msg.key }).join('\n'))
651 })
652 })
653}
654
655Req.prototype.populatePackageJson = function (obj, cb) {
656 var blobId = obj.dist.tarball.replace(/.*\/blobs\/get\//, '')
657 this.getPackageJsonFromTarballBlob(blobId, function (err, pkg) {
658 if (err) return cb(err)
659 pkg.dist = obj.dist
660 pkg.dist.shasum = pkg._shasum
661 pkg.author = pkg.author || obj.author
662 pkg.version = pkg.version || obj.version
663 pkg.name = pkg.name || obj.name
664 cb(null, pkg)
665 })
666}
667
668Req.prototype.getPackageJsonFromTarballBlob = function (id, cb) {
669 var self = this
670 self.server.getBlob(id, function (err, readBlob) {
671 if (err) return cb(err)
672 cb = once(cb)
673 var extract = tar.extract()
674 var pkg, shasum
675 extract.on('entry', function (header, stream, next) {
676 if (/^[^\/]*\/package\.json$/.test(header.name)) {
677 pull(toPull.source(stream), pull.collect(function (err, bufs) {
678 if (err) return cb(err)
679 try { pkg = JSON.parse(Buffer.concat(bufs)) }
680 catch(e) { return cb(e) }
681 next()
682 }))
683 } else {
684 stream.on('end', next)
685 stream.resume()
686 }
687 })
688 extract.on('finish', function () {
689 pkg._shasum = shasum
690 cb(null, pkg)
691 })
692 pull(
693 readBlob,
694 hash('sha1', 'hex', function (err, sum) {
695 if (err) return cb(err)
696 shasum = sum
697 }),
698 toPull(zlib.createGunzip()),
699 toPull(extract)
700 )
701 })
702}
703

Built with git-ssb-web