git ssb

3+

cel / ssb-npm-registry



Tree: 250581514549cac18d69ab15db695535f98d2752

Files: 250581514549cac18d69ab15db695535f98d2752 / index.js

24488 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
196exports.expandPkgMentions = function (sbot, mentions, props, cb) {
197 cb = once(cb)
198 var waiting = 0
199 var expandedMentions = mentions.map(function (link) {
200 var id = link && link.link
201 if (!id) return link
202 waiting++
203 var newLink = {}
204 for (var k in link) newLink[k] = link[k]
205 getPackageJsonFromTarballBlob(sbot, id, function (err, pkg) {
206 if (err) return cb(err)
207 for (var k in props) newLink[k] = pkg[k]
208 if (!--waiting) next()
209 })
210 return newLink
211 })
212 if (!waiting) next()
213 function next() {
214 cb(null, expandedMentions)
215 }
216}
217
218function SsbNpmRegistryServer(sbot, config) {
219 this.sbot = sbot
220 this.config = config
221 this.npmConfig = config.npm || {}
222 this.host = this.npmConfig.host || 'localhost'
223 this.fetchAll = this.npmConfig.fetchAll
224 this.links2 = sbot.links2
225 if (!this.links2) throw new Error('missing ssb-links2 scuttlebot plugin')
226 this.wsPort = config.ws && Number(config.ws.port) || '8989'
227 this.blobsPrefix = 'http://' + (config.host || 'localhost') + ':'
228 + this.wsPort + '/blobs/get/'
229 this.getBootstrapInfo = onceify(this.getBootstrapInfo, this)
230}
231
232SsbNpmRegistryServer.prototype = Object.create(http.Server.prototype)
233SsbNpmRegistryServer.prototype.constructor = SsbNpmRegistryServer
234
235SsbNpmRegistryServer.prototype.pushBlobs = function (ids, cb) {
236 var self = this
237 if (!self.sbot.blobs.push) return cb(new Error('missing blobs.push'))
238 ;(function next(i) {
239 if (i >= ids.length) return cb()
240 self.sbot.blobs.push(ids[i], function (err) {
241 if (err) return cb(err)
242 next(i+1)
243 })
244 })(0)
245}
246
247function getBlob(sbot, id, cb) {
248 var blobs = sbot.blobs
249 blobs.size(id, function (err, size) {
250 if (typeof size === 'number') cb(null, blobs.get(id))
251 else blobs.want(id, function (err, got) {
252 if (err) cb(err)
253 else if (!got) cb('missing blob ' + id)
254 else cb(null, blobs.get(id))
255 })
256 })
257}
258
259SsbNpmRegistryServer.prototype.blobDist = function (id) {
260 var m = /^&([^.]+)\.([a-z0-9]+)$/.exec(id)
261 if (!m) throw new Error('bad blob id: ' + id)
262 return {
263 integrity: m[2] + '-' + m[1],
264 tarball: 'http://localhost:' + this.wsPort + '/blobs/get/' + id
265 }
266}
267
268SsbNpmRegistryServer.prototype.getMentions = function (name) {
269 return this.links2.read({
270 query: [
271 {$filter: {rel: ['mentions', name, {$gt: true}]}},
272 {$filter: {dest: {$prefix: '&'}}},
273 {$map: {
274 name: ['rel', 1],
275 size: ['rel', 2],
276 link: 'dest',
277 author: 'source',
278 ts: 'ts'
279 }}
280 ]
281 })
282}
283
284SsbNpmRegistryServer.prototype.getLocalPrebuildsLinks = function (cb) {
285 var self = this
286 var prebuildsDir = path.join(os.homedir(), '.npm', '_prebuilds')
287 var ids = {}
288 var nameRegex = new RegExp('^http-' + self.host.replace(/\./g, '.') + '-(?:[0-9]+)-prebuild-(.*)$')
289 fs.readdir(prebuildsDir, function (err, filenames) {
290 if (err) return cb(new Error(err.stack || err))
291 ;(function next(i) {
292 if (i >= filenames.length) return cb(null, ids)
293 var m = nameRegex.exec(filenames[i])
294 if (!m) return next(i+1)
295 var name = m[1]
296 fs.readFile(path.join(prebuildsDir, filenames[i]), function (err, data) {
297 if (err) return cb(new Error(err.stack || err))
298 self.sbot.blobs.add(function (err, id) {
299 if (err) return cb(new Error(err.stack || err))
300 ids[name] = id
301 next(i+1)
302 })(pull.once(data))
303 })
304 })(0)
305 })
306}
307
308SsbNpmRegistryServer.prototype.getBootstrapInfo = function (cb) {
309 var self = this
310 if (!self.sbot.bootstrap) return cb(new Error('missing sbot bootstrap plugin'))
311
312 self.sbot.bootstrap.getPackageLock(function (err, sbotPkgLock) {
313 if (err) return cb(new Error(err.stack || err))
314 var pkgs = pkgLockToRegistryPkgs(sbotPkgLock, self.wsPort)
315 if (pkgs._hasNonBlobUrl) {
316 console.error('[npm-registry] Warning: package-lock.json has non-blob URLs. Bootstrap installation may not be fully peer-to-peer.')
317 }
318
319 if (!sbotPkgLock.name) console.trace('missing pkg lock name')
320 if (!sbotPkgLock.version) console.trace('missing pkg lock version')
321
322 var waiting = 2
323
324 self.sbot.blobs.add(function (err, id) {
325 if (err) return next(new Error(err.stack || err))
326 var pkg = pkgs[sbotPkgLock.name] || (pkgs[sbotPkgLock.name] = {})
327 var versions = pkg.versions || (pkg.versions = {})
328 pkg.versions[sbotPkgLock.version] = {
329 name: sbotPkgLock.name,
330 version: sbotPkgLock.version,
331 dist: self.blobDist(id)
332 }
333 var distTags = pkg['dist-tags'] || (pkg['dist-tags'] = {})
334 distTags.latest = sbotPkgLock.version
335 next()
336 })(self.sbot.bootstrap.pack())
337
338 var prebuilds
339 self.getLocalPrebuildsLinks(function (err, _prebuilds) {
340 if (err) return next(err)
341 prebuilds = _prebuilds
342 next()
343 })
344
345 function next(err) {
346 if (err) return waiting = 0, cb(err)
347 if (--waiting) return
348 fs.readFile(path.join(__dirname, 'bootstrap.js'), {
349 encoding: 'utf8'
350 }, function (err, bootstrapScript) {
351 if (err) return cb(err)
352 var script = bootstrapScript + '\n' +
353 'exports.pkgs = ' + JSON.stringify(pkgs, 0, 2) + '\n' +
354 'exports.prebuilds = ' + JSON.stringify(prebuilds, 0, 2)
355
356 self.sbot.blobs.add(function (err, id) {
357 if (err) return cb(new Error(err.stack || err))
358 var m = /^&([^.]+)\.([a-z0-9]+)$/.exec(id)
359 if (!m) return cb(new Error('bad blob id: ' + id))
360 cb(null, {
361 name: sbotPkgLock.name,
362 blob: id,
363 hashType: m[2],
364 hashBuf: Buffer.from(m[1], 'base64'),
365 })
366 })(pull.once(script))
367 })
368 }
369 })
370}
371
372function Req(server, req, res) {
373 this.server = server
374 this.req = req
375 this.res = res
376 this.blobsToPush = []
377 this.fetchAll = server.fetchAll != null ? server.fetchAll : true
378}
379
380Req.prototype.serve = function () {
381 console.log(this.req.method, this.req.url, this.req.socket.remoteAddress.replace(/^::ffff:/, ''))
382 var pathname = this.req.url.replace(/\?.*/, '')
383 var m
384 if (pathname === '/') return this.serveHome()
385 if (pathname === '/bootstrap') return this.serveBootstrap()
386 if (pathname === '/-/whoami') return this.serveWhoami()
387 if (pathname === '/-/ping') return this.respond(200, true)
388 if (pathname === '/-/user/org.couchdb.user:1') return this.serveUser1()
389 if ((m = /^\/-\/prebuild\/(.*)$/.exec(pathname))) return this.servePrebuild(m[1])
390 if (!/^\/-\//.test(pathname)) return this.servePkg(pathname.substr(1))
391 return this.respond(404)
392}
393
394Req.prototype.respond = function (status, message) {
395 this.res.writeHead(status, {'content-type': 'application/json'})
396 this.res.end(message && JSON.stringify(message, 0, 2))
397}
398
399Req.prototype.respondError = function (status, message) {
400 this.respond(status, {error: message})
401}
402
403var bootstrapName = 'ssb-npm-bootstrap'
404
405Req.prototype.serveHome = function () {
406 var self = this
407 self.res.writeHead(200, {'content-type': 'text/html'})
408 var port = 8044
409 self.res.end('<!doctype html><html><head><meta charset=utf-8>' +
410 '<title>' + escapeHTML(pkg.name) + '</title></head><body>' +
411 '<h1>' + escapeHTML(pkg.name) + '</h1>\n' +
412 '<p><a href="/bootstrap">Bootstrap</a></p>\n' +
413 '</body></html>')
414}
415
416Req.prototype.serveBootstrap = function () {
417 var self = this
418 self.server.getBootstrapInfo(function (err, info) {
419 if (err) return self.respondError(err.stack || err)
420 var pkgNameText = info.name
421 var pkgTmpText = '/tmp/' + bootstrapName + '.js'
422 var host = String(self.req.headers.host).replace(/:[0-9]*$/, '') || self.req.socket.localAddress
423 var httpHost = /^[^\[]:.*:.*:/.test(host) ? '[' + host + ']' : host
424 var blobsHostname = httpHost + ':' + self.server.wsPort
425 var tarballLink = 'http://' + blobsHostname + '/blobs/get/' + info.blob
426 var pkgHashText = info.hashBuf.toString('hex')
427 var hashCmd = info.hashType + 'sum'
428
429 var script =
430 'wget \'' + tarballLink + '\' -O ' + pkgTmpText + ' &&\n' +
431 'echo ' + pkgHashText + ' ' + pkgTmpText + ' | ' + hashCmd + ' -c &&\n' +
432 'node ' + pkgTmpText + ' --blobs-remote ' + blobsHostname + ' -- ' +
433 'npm install -g ' + info.name + ' &&\n' +
434 'sbot server'
435
436 self.res.writeHead(200, {'content-type': 'text/plain'})
437 self.res.end(script)
438 })
439}
440
441Req.prototype.serveWhoami = function () {
442 var self = this
443 self.server.sbot.whoami(function (err, feed) {
444 if (err) return self.respondError(err.stack || err)
445 self.respond(200, {username: feed.id})
446 })
447}
448
449Req.prototype.serveUser1 = function () {
450 this.respond(this.req.method === 'PUT' ? 201 : 200, {token: '1'})
451}
452
453function decodeName(name) {
454 var parts = name.replace(/\.tgz$/, '').split(':')
455 return {
456 name: parts[1],
457 version: parts[2],
458 distTag: parts[3],
459 }
460}
461
462Req.prototype.servePkg = function (pathname) {
463 var self = this
464 var parts = pathname.split('/')
465 var pkgName = parts.shift().replace(/%2f/i, '/')
466 if (parts[0] === '-rev') return this.respondError(501, 'Unpublish is not supported')
467 var spec = parts.shift()
468 if (spec) try { spec = decodeURIComponent(spec) } finally {}
469 if (parts.length > 0) return this.respondError(404)
470 if (self.req.method === 'PUT') return self.publishPkg(pkgName)
471 var obj = {
472 _id: pkgName,
473 name: pkgName,
474 'dist-tags': {},
475 versions: {}
476 }
477 pull(
478 self.server.getMentions({$prefix: 'npm:' + pkgName + ':'}),
479 pull.drain(function (mention) {
480 var data = decodeName(mention.name)
481 if (!data.version) return
482 if (data.distTag) obj['dist-tags'][data.distTag] = data.version
483 obj.versions[data.version] = {
484 author: {
485 url: mention.author
486 },
487 name: pkgName,
488 version: data.version,
489 dist: self.server.blobDist(mention.link)
490 }
491 }, function (err) {
492 if (err) return self.respondError(500, err.stack || err)
493 if (spec) resolveSpec()
494 else if (self.fetchAll) resolveAll()
495 else done()
496 })
497 )
498 function resolveSpec() {
499 var version = obj['dist-tags'][spec]
500 || semver.maxSatisfying(Object.keys(obj.versions), spec)
501 obj = obj.versions[version]
502 if (!obj) return self.respondError(404, 'version not found: ' + spec)
503 populatePackageJson(self.server.sbot, obj, function (err, pkg) {
504 if (err) return self.respondError(500, err.stack || err)
505 obj = pkg || obj
506 done()
507 })
508 }
509 function resolveAll() {
510 var waiting = 0
511 for (var version in obj.versions) (function (version) {
512 waiting++
513 populatePackageJson(self.server.sbot, obj.versions[version], function (err, pkg) {
514 if (err && waiting <= 0) return console.trace(err)
515 if (err) return waiting = 0, self.respondError(500, err.stack || err)
516 if (pkg) obj.versions[version] = pkg
517 if (!--waiting) done()
518 })
519 }(version))
520 if (!waiting) done()
521 }
522 function done() {
523 self.respond(200, obj)
524 }
525}
526
527Req.prototype.servePrebuild = function (name) {
528 var self = this
529 var getMention = self.server.getMentions('prebuild:' + name)
530 var blobsByAuthor = {/* <author>: BlobId */}
531 getMention(null, function next(err, link) {
532 if (err === true) return done()
533 if (err) return self.respondError(500, err.stack || err)
534 blobsByAuthor[link.author] = link.link
535 getMention(null, next)
536 })
537 function done() {
538 var authorsByLink = {/* <BlobId>: [FeedId...] */}
539 var blobId
540 for (var feed in blobsByAuthor) {
541 var blob = blobId = blobsByAuthor[feed]
542 var feeds = authorsByLink[blob] || (authorsByLink[blob] = [])
543 feeds.push(feed)
544 }
545 switch (Object.keys(authorsByLink).length) {
546 case 0:
547 return self.respondError(404, 'Not Found')
548 case 1:
549 self.res.writeHead(303, {Location: self.server.blobsPrefix + blobId})
550 return self.res.end()
551 default:
552 return self.respond(300, {choices: authorsByLink})
553 }
554 }
555}
556
557var localhosts = {
558 '::1': true,
559 '127.0.0.1': true,
560 '::ffff:127.0.0.1': true,
561}
562
563Req.prototype.publishPkg = function (pkgName) {
564 var self = this
565 var remoteAddress = self.req.socket.remoteAddress
566 if (!(remoteAddress in localhosts)) {
567 return self.respondError(403, 'You may not publish as this user.')
568 }
569
570 var chunks = []
571 self.req.on('data', function (data) {
572 chunks.push(data)
573 })
574 self.req.on('end', function () {
575 var data
576 try {
577 data = JSON.parse(Buffer.concat(chunks))
578 } catch(e) {
579 return self.respondError(400, e.stack)
580 }
581 return self.publishPkg2(pkgName, data || {})
582 })
583}
584
585Req.prototype.publishPkg2 = function (name, data) {
586 var self = this
587 if (data.users) console.trace('[npm-registry] users property is not supported')
588 var attachments = data._attachments || {}
589 var links = {/* <name>-<version>.tgz: {link: <BlobId>, size: number} */}
590 var waiting = 0
591 Object.keys(attachments).forEach(function (filename) {
592 waiting++
593 var tarball = new Buffer(attachments[filename].data, 'base64')
594 var length = attachments[filename].length
595 if (length && length !== tarball.length) return self.respondError(400,
596 'Length mismatch for attachment \'' + filename + '\'')
597 self.server.sbot.blobs.add(function (err, id) {
598 if (err) return self.respondError(500,
599 'Adding attachment \'' + filename + '\' as blob failed')
600 self.blobsToPush.push(id)
601 links[filename] = {link: id, size: tarball.length}
602 if (!--waiting) next()
603 })(pull.once(tarball))
604 })
605 function next() {
606 try {
607 self.publishPkg3(name, data, links)
608 } catch(e) {
609 self.respondError(500, e.stack || e)
610 }
611 }
612}
613
614Req.prototype.publishPkg3 = function (name, data, links) {
615 var self = this
616 var versions = data.versions || {}
617 var linksByVersion = {/* <version>: link */}
618
619 // associate tarball blobs with versions
620 for (var version in versions) {
621 var pkg = versions[version]
622 if (!pkg) return self.respondError(400, 'Bad package object')
623 if (!pkg.dist) return self.respondError(400, 'Missing package dist property')
624 if (!pkg.dist.tarball) return self.respondError(400, 'Missing dist.tarball property')
625 if (pkg.deprecated) return self.respondError(501, 'Deprecation is not supported')
626 var m = /\/-\/([^\/]+)$/.exec(pkg.dist.tarball)
627 if (!m) return self.respondError(400, 'Bad tarball URL \'' + pkg.dist.tarball + '\'')
628 var filename = m[1]
629 var link = links[filename]
630 if (!link) return self.respondError(501, 'Unable to find attachment \'' + filename + '\'')
631 // TODO?: try to find missing tarball mentioned in other messages
632 if (pkg.version && pkg.version !== version)
633 return self.respondError(400, 'Mismatched package version: ' + [pkg.version, version])
634 linksByVersion[version] = link
635 link.version = version
636 link.dependencies = pkg.dependencies || {}
637 link.bundledDependencies = pkg.bundledDependencies || pkg.bundleDependencies
638 }
639
640 // associate blobs with dist-tags
641 var tags = data['dist-tags'] || {}
642 for (var tag in tags) {
643 var version = tags[tag]
644 var link = linksByVersion[version]
645 if (!link) return self.respondError(501, 'Setting a dist-tag for a version not being published is not supported.')
646 // TODO?: support setting dist-tag without version,
647 // by looking up a tarball blob for the version
648 link.tag = tag
649 }
650
651 // compute blob links to publish
652 var mentions = []
653 for (var filename in links) {
654 var link = links[filename] || {}
655 if (!link.version) return self.respondError(400, 'Attachment ' + filename + ' was not linked to in the package metadata')
656 mentions.push({
657 name: 'npm:' + name + ':' + link.version + (link.tag ? ':' + link.tag : ''),
658 link: link.link,
659 size: link.size,
660 dependencies: link.dependencies,
661 bundledDependencies: link.bundledDependencies,
662 })
663 }
664 return self.publishPkgs(mentions)
665}
666
667Req.prototype.publishPkgs = function (mentions) {
668 var self = this
669 exports.publishPkgMentions(self.server.sbot, mentions, function (err, msgs) {
670 if (err) self.respondError(500, err.stack || err)
671 self.server.pushBlobs(self.blobsToPush, function (err) {
672 if (err) console.error('[npm-registry] Failed to push blob ' + id + ': ' + (err.stack || err))
673 self.respond(201)
674 console.log(msgs.map(function (msg) { return msg.key }).join('\n'))
675 })
676 })
677}
678
679function populatePackageJson(sbot, obj, cb) {
680 var blobId = obj.dist.tarball.replace(/.*\/blobs\/get\//, '')
681 var deps, bundledDeps
682
683 // look for dependencies in links.
684 // then fallback to getting it from the tarball blob
685
686 pull(
687 sbot.links({
688 dest: blobId,
689 rel: 'mentions',
690 values: true,
691 }),
692 // decryption could be done here
693 pull.map(function (msg) {
694 var c = msg.value && msg.value.content
695 var mentions = c && c.mentions
696 return Array.isArray(mentions) ? mentions : []
697 }),
698 pull.flatten(),
699 pull.filter(function (link) {
700 return link && link.link === blobId
701 }),
702 pull.drain(function (link) {
703 if (link.dependencies) deps = link.dependencies
704 bundledDeps = link.bundledDependencies || link.bundleDependencies || bundledDeps
705 // how to handle multiple assignments of dependencies to a package?
706 }, function (err) {
707 if (err) return cb(new Error(err.stack || err))
708 if (deps) {
709 // assume that the dependencies in the links to the blob are
710 // correct.
711 obj.dependencies = deps
712 obj.bundledDependencies = bundledDeps
713 cb(null, obj)
714 } else {
715 // get dependencies from the tarball
716 getPackageJsonFromTarballBlob(sbot, blobId, function (err, pkg) {
717 if (err) return cb(err)
718 pkg.dist = obj.dist
719 pkg.dist.shasum = pkg._shasum
720 pkg.author = pkg.author || obj.author
721 pkg.version = pkg.version || obj.version
722 pkg.name = pkg.name || obj.name
723 cb(null, pkg)
724 })
725 }
726 })
727 )
728}
729
730function getPackageJsonFromTarballBlob(sbot, id, cb) {
731 var self = this
732 getBlob(sbot, id, function (err, readBlob) {
733 if (err) return cb(err)
734 cb = once(cb)
735 var extract = tar.extract()
736 var pkg, shasum
737 extract.on('entry', function (header, stream, next) {
738 if (/^[^\/]*\/package\.json$/.test(header.name)) {
739 pull(toPull.source(stream), pull.collect(function (err, bufs) {
740 if (err) return cb(err)
741 try { pkg = JSON.parse(Buffer.concat(bufs)) }
742 catch(e) { return cb(e) }
743 next()
744 }))
745 } else {
746 stream.on('end', next)
747 stream.resume()
748 }
749 })
750 extract.on('finish', function () {
751 pkg._shasum = shasum
752 cb(null, pkg)
753 })
754 pull(
755 readBlob,
756 hash('sha1', 'hex', function (err, sum) {
757 if (err) return cb(err)
758 shasum = sum
759 }),
760 toPull(zlib.createGunzip()),
761 toPull(extract)
762 )
763 })
764}
765

Built with git-ssb-web