From f28294107597e23e71685ce94efdd9f023a2146e Mon Sep 17 00:00:00 2001 From: Juan Picado <juanpicado19@gmail.com> Date: Sun, 23 Apr 2017 20:02:26 +0200 Subject: [PATCH] Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors --- .eslintignore | 3 +- .eslintrc | 22 + Gruntfile.js | 36 +- bin/verdaccio | 2 +- index.js | 4 +- lib/GUI/js/bootstrap-modal.js | 297 +++++++------ lib/GUI/js/entry.js | 63 ++- lib/GUI/js/main.js | 14 +- lib/GUI/js/search.js | 38 +- lib/auth.js | 374 ++++++++-------- lib/cli.js | 133 +++--- lib/config-path.js | 64 +-- lib/config.js | 196 ++++----- lib/file-locking.js | 118 ++--- lib/index-api.js | 457 ++++++++++---------- lib/index-web.js | 194 ++++----- lib/index.js | 118 ++--- lib/local-data.js | 26 +- lib/local-fs.js | 218 +++++----- lib/local-storage.js | 618 +++++++++++++-------------- lib/logger.js | 281 ++++++------ lib/middleware.js | 184 ++++---- lib/notify.js | 35 +- lib/plugin-loader.js | 36 +- lib/plugins/htpasswd/crypt3.js | 5 +- lib/plugins/htpasswd/index.js | 153 ++++--- lib/plugins/htpasswd/utils.js | 64 +-- lib/search.js | 51 +-- lib/status-cats.js | 21 +- lib/storage.js | 359 ++++++++-------- lib/streams.js | 50 +-- lib/up-storage.js | 379 ++++++++-------- lib/utils.js | 135 +++--- package.json | 6 +- test/.eslintrc | 7 + test/functional/plugins/authorize.js | 4 +- test/functional/race.js | 2 +- test/unit/no_proxy.js | 42 +- test/unit/search.js | 4 +- test/unit/validate_all.js | 3 +- 40 files changed, 2430 insertions(+), 2386 deletions(-) diff --git a/.eslintignore b/.eslintignore index 35fe41826..f1e4f3b08 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ node_modules lib/static -coverage/ \ No newline at end of file +coverage/ +lib/GUI/ diff --git a/.eslintrc b/.eslintrc index 7ff6e1ae7..14356f71b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,8 +9,11 @@ # Created to work with eslint@0.18.0 # +extends: ["eslint:recommended", "google"] + env: node: true + browser: true es6: true rules: @@ -43,3 +46,22 @@ rules: # useful for code clean-up no-unused-vars: [1, {"vars": "all", "args": "none"}] + max-len: [1, 160] + + # camelcase is standard, but this should be 1 and then 2 soon + camelcase: 0 + + # configuration that should be upgraded progresivelly + require-jsdoc: 1 + valid-jsdoc: 1 + prefer-spread: 1 + no-constant-condition: 1 + no-var: 1 + no-empty: 1 + guard-for-in: 1 + no-invalid-this: 1 + new-cap: 1 + one-var: 1 + no-redeclare: 1 + prefer-rest-params: 1 + no-console: [1, {"allow": ["log", "warn"]}] \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 2bf41a705..9425a3bc5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,36 +4,36 @@ module.exports = function(grunt) { browserify: { dist: { files: { - 'lib/static/main.js': [ 'lib/GUI/js/main.js' ] + 'lib/static/main.js': ['lib/GUI/js/main.js'], }, options: { debug: true, - transform: [ 'browserify-handlebars' ] - } - } + transform: ['browserify-handlebars'], + }, + }, }, less: { dist: { files: { - 'lib/static/main.css': [ 'lib/GUI/css/main.less' ] + 'lib/static/main.css': ['lib/GUI/css/main.less'], }, options: { - sourceMap: false - } - } + sourceMap: false, + }, + }, }, watch: { - files: [ 'lib/GUI/**/*' ], - tasks: [ 'default' ] - } - }) + files: ['lib/GUI/**/*'], + tasks: ['default'], + }, + }); - grunt.loadNpmTasks('grunt-browserify') - grunt.loadNpmTasks('grunt-contrib-watch') - grunt.loadNpmTasks('grunt-contrib-less') + grunt.loadNpmTasks('grunt-browserify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-less'); grunt.registerTask('default', [ 'browserify', - 'less' - ]) -} + 'less', + ]); +}; diff --git a/bin/verdaccio b/bin/verdaccio index 87170d755..d2316ff6d 100755 --- a/bin/verdaccio +++ b/bin/verdaccio @@ -1,3 +1,3 @@ #!/usr/bin/env node -require('../lib/cli') +require('../lib/cli'); diff --git a/index.js b/index.js index 787844980..3dc713f1a 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -module.exports = require('./lib') +module.exports = require('./lib'); -/**package +/** package { "name": "verdaccio", "version": "0.0.0", "dependencies": {"js-yaml": "*"}, diff --git a/lib/GUI/js/bootstrap-modal.js b/lib/GUI/js/bootstrap-modal.js index 8b0e269bc..634249010 100644 --- a/lib/GUI/js/bootstrap-modal.js +++ b/lib/GUI/js/bootstrap-modal.js @@ -7,275 +7,272 @@ * ======================================================================== */ -+function ($) { ++function($) { 'use strict'; // MODAL CLASS DEFINITION // ====================== - var Modal = function (element, options) { - this.options = options - this.$body = $(document.body) - this.$element = $(element) - this.$backdrop = - this.isShown = null - this.scrollbarWidth = 0 + let Modal = function(element, options) { + this.options = options; + this.$body = $(document.body); + this.$element = $(element); + this.$backdrop = + this.isShown = null; + this.scrollbarWidth = 0; if (this.options.remote) { this.$element .find('.modal-content') - .load(this.options.remote, $.proxy(function () { - this.$element.trigger('loaded.bs.modal') - }, this)) + .load(this.options.remote, $.proxy(function() { + this.$element.trigger('loaded.bs.modal'); + }, this)); } - } + }; - Modal.VERSION = '3.3.0' + Modal.VERSION = '3.3.0'; - Modal.TRANSITION_DURATION = 300 - Modal.BACKDROP_TRANSITION_DURATION = 150 + Modal.TRANSITION_DURATION = 300; + Modal.BACKDROP_TRANSITION_DURATION = 150; Modal.DEFAULTS = { backdrop: true, keyboard: true, - show: true - } + show: true, + }; - Modal.prototype.toggle = function (_relatedTarget) { - return this.isShown ? this.hide() : this.show(_relatedTarget) - } + Modal.prototype.toggle = function(_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget); + }; - Modal.prototype.show = function (_relatedTarget) { - var that = this - var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + Modal.prototype.show = function(_relatedTarget) { + let that = this; + let e = $.Event('show.bs.modal', {relatedTarget: _relatedTarget}); - this.$element.trigger(e) + this.$element.trigger(e); - if (this.isShown || e.isDefaultPrevented()) return + if (this.isShown || e.isDefaultPrevented()) return; - this.isShown = true + this.isShown = true; - this.checkScrollbar() - this.$body.addClass('modal-open') + this.checkScrollbar(); + this.$body.addClass('modal-open'); - this.setScrollbar() - this.escape() + this.setScrollbar(); + this.escape(); - this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)); - this.backdrop(function () { - var transition = $.support.transition && that.$element.hasClass('fade') + this.backdrop(function() { + let transition = $.support.transition && that.$element.hasClass('fade'); if (!that.$element.parent().length) { - that.$element.appendTo(that.$body) // don't move modals dom position + that.$element.appendTo(that.$body); // don't move modals dom position } that.$element .show() - .scrollTop(0) + .scrollTop(0); if (transition) { - that.$element[0].offsetWidth // force reflow + that.$element[0].offsetWidth; // force reflow } that.$element .addClass('in') - .attr('aria-hidden', false) + .attr('aria-hidden', false); - that.enforceFocus() + that.enforceFocus(); - var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + let e = $.Event('shown.bs.modal', {relatedTarget: _relatedTarget}); transition ? that.$element.find('.modal-dialog') // wait for modal to slide in - .one('bsTransitionEnd', function () { - that.$element.trigger('focus').trigger(e) + .one('bsTransitionEnd', function() { + that.$element.trigger('focus').trigger(e); }) .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - that.$element.trigger('focus').trigger(e) - }) - } + that.$element.trigger('focus').trigger(e); + }); + }; - Modal.prototype.hide = function (e) { - if (e) e.preventDefault() + Modal.prototype.hide = function(e) { + if (e) e.preventDefault(); - e = $.Event('hide.bs.modal') + e = $.Event('hide.bs.modal'); - this.$element.trigger(e) + this.$element.trigger(e); - if (!this.isShown || e.isDefaultPrevented()) return + if (!this.isShown || e.isDefaultPrevented()) return; - this.isShown = false + this.isShown = false; - this.escape() + this.escape(); - $(document).off('focusin.bs.modal') + $(document).off('focusin.bs.modal'); this.$element .removeClass('in') .attr('aria-hidden', true) - .off('click.dismiss.bs.modal') + .off('click.dismiss.bs.modal'); $.support.transition && this.$element.hasClass('fade') ? this.$element .one('bsTransitionEnd', $.proxy(this.hideModal, this)) .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - this.hideModal() - } + this.hideModal(); + }; - Modal.prototype.enforceFocus = function () { + Modal.prototype.enforceFocus = function() { $(document) .off('focusin.bs.modal') // guard against infinite focus loop - .on('focusin.bs.modal', $.proxy(function (e) { + .on('focusin.bs.modal', $.proxy(function(e) { if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { - this.$element.trigger('focus') + this.$element.trigger('focus'); } - }, this)) - } + }, this)); + }; - Modal.prototype.escape = function () { + Modal.prototype.escape = function() { if (this.isShown && this.options.keyboard) { - this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { - e.which == 27 && this.hide() - }, this)) + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function(e) { + e.which == 27 && this.hide(); + }, this)); } else if (!this.isShown) { - this.$element.off('keydown.dismiss.bs.modal') + this.$element.off('keydown.dismiss.bs.modal'); } - } + }; - Modal.prototype.hideModal = function () { - var that = this - this.$element.hide() - this.backdrop(function () { - that.$body.removeClass('modal-open') - that.resetScrollbar() - that.$element.trigger('hidden.bs.modal') - }) - } + Modal.prototype.hideModal = function() { + let that = this; + this.$element.hide(); + this.backdrop(function() { + that.$body.removeClass('modal-open'); + that.resetScrollbar(); + that.$element.trigger('hidden.bs.modal'); + }); + }; - Modal.prototype.removeBackdrop = function () { - this.$backdrop && this.$backdrop.remove() - this.$backdrop = null - } + Modal.prototype.removeBackdrop = function() { + this.$backdrop && this.$backdrop.remove(); + this.$backdrop = null; + }; - Modal.prototype.backdrop = function (callback) { - var that = this - var animate = this.$element.hasClass('fade') ? 'fade' : '' + Modal.prototype.backdrop = function(callback) { + let that = this; + let animate = this.$element.hasClass('fade') ? 'fade' : ''; if (this.isShown && this.options.backdrop) { - var doAnimate = $.support.transition && animate + let doAnimate = $.support.transition && animate; this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') .prependTo(this.$element) - .on('click.dismiss.bs.modal', $.proxy(function (e) { - if (e.target !== e.currentTarget) return + .on('click.dismiss.bs.modal', $.proxy(function(e) { + if (e.target !== e.currentTarget) return; this.options.backdrop == 'static' ? this.$element[0].focus.call(this.$element[0]) - : this.hide.call(this) - }, this)) + : this.hide.call(this); + }, this)); - if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + if (doAnimate) this.$backdrop[0].offsetWidth; // force reflow - this.$backdrop.addClass('in') + this.$backdrop.addClass('in'); - if (!callback) return + if (!callback) return; doAnimate ? this.$backdrop .one('bsTransitionEnd', callback) .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callback() - + callback(); } else if (!this.isShown && this.$backdrop) { - this.$backdrop.removeClass('in') + this.$backdrop.removeClass('in'); - var callbackRemove = function () { - that.removeBackdrop() - callback && callback() - } + let callbackRemove = function() { + that.removeBackdrop(); + callback && callback(); + }; $.support.transition && this.$element.hasClass('fade') ? this.$backdrop .one('bsTransitionEnd', callbackRemove) .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callbackRemove() - + callbackRemove(); } else if (callback) { - callback() + callback(); } - } + }; - Modal.prototype.checkScrollbar = function () { - this.scrollbarWidth = this.measureScrollbar() - } + Modal.prototype.checkScrollbar = function() { + this.scrollbarWidth = this.measureScrollbar(); + }; - Modal.prototype.setScrollbar = function () { - var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) - if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) - } + Modal.prototype.setScrollbar = function() { + let bodyPad = parseInt((this.$body.css('padding-right') || 0), 10); + if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth); + }; - Modal.prototype.resetScrollbar = function () { - this.$body.css('padding-right', '') - } + Modal.prototype.resetScrollbar = function() { + this.$body.css('padding-right', ''); + }; - Modal.prototype.measureScrollbar = function () { // thx walsh - if (document.body.clientWidth >= window.innerWidth) return 0 - var scrollDiv = document.createElement('div') - scrollDiv.className = 'modal-scrollbar-measure' - this.$body.append(scrollDiv) - var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth - this.$body[0].removeChild(scrollDiv) - return scrollbarWidth - } + Modal.prototype.measureScrollbar = function() { // thx walsh + if (document.body.clientWidth >= window.innerWidth) return 0; + let scrollDiv = document.createElement('div'); + scrollDiv.className = 'modal-scrollbar-measure'; + this.$body.append(scrollDiv); + let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; + this.$body[0].removeChild(scrollDiv); + return scrollbarWidth; + }; // MODAL PLUGIN DEFINITION // ======================= function Plugin(option, _relatedTarget) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.modal') - var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + return this.each(function() { + let $this = $(this); + let data = $this.data('bs.modal'); + let options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option); - if (!data) $this.data('bs.modal', (data = new Modal(this, options))) - if (typeof option == 'string') data[option](_relatedTarget) - else if (options.show) data.show(_relatedTarget) - }) + if (!data) $this.data('bs.modal', (data = new Modal(this, options))); + if (typeof option == 'string') data[option](_relatedTarget); + else if (options.show) data.show(_relatedTarget); + }); } - var old = $.fn.modal + let old = $.fn.modal; - $.fn.modal = Plugin - $.fn.modal.Constructor = Modal + $.fn.modal = Plugin; + $.fn.modal.Constructor = Modal; // MODAL NO CONFLICT // ================= - $.fn.modal.noConflict = function () { - $.fn.modal = old - return this - } + $.fn.modal.noConflict = function() { + $.fn.modal = old; + return this; + }; // MODAL DATA-API // ============== - $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { - var $this = $(this) - var href = $this.attr('href') - var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 - var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function(e) { + let $this = $(this); + let href = $this.attr('href'); + let $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))); // strip for ie7 + let option = $target.data('bs.modal') ? 'toggle' : $.extend({remote: !/#/.test(href) && href}, $target.data(), $this.data()); - if ($this.is('a')) e.preventDefault() - - $target.one('show.bs.modal', function (showEvent) { - if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown - $target.one('hidden.bs.modal', function () { - $this.is(':visible') && $this.trigger('focus') - }) - }) - Plugin.call($target, option, this) - }) + if ($this.is('a')) e.preventDefault(); + $target.one('show.bs.modal', function(showEvent) { + if (showEvent.isDefaultPrevented()) return; // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function() { + $this.is(':visible') && $this.trigger('focus'); + }); + }); + Plugin.call($target, option, this); + }); }(jQuery); diff --git a/lib/GUI/js/entry.js b/lib/GUI/js/entry.js index afa384bea..f7ebda372 100644 --- a/lib/GUI/js/entry.js +++ b/lib/GUI/js/entry.js @@ -1,54 +1,53 @@ -var $ = require('unopinionate').selector -var onClick = require('onclick') -var transitionComplete = require('transition-complete') +let $ = require('unopinionate').selector; +let onClick = require('onclick'); +let transitionComplete = require('transition-complete'); $(function() { onClick('.entry .name', function() { - var $this = $(this) - var $entry = $this.closest('.entry') + let $this = $(this); + let $entry = $this.closest('.entry'); if ($entry.hasClass('open')) { // Close entry $entry .height($entry.outerHeight()) - .removeClass('open') + .removeClass('open'); setTimeout(function() { - $entry.css('height', $entry.attr('data-height') + 'px') - }, 0) + $entry.css('height', $entry.attr('data-height') + 'px'); + }, 0); transitionComplete(function() { - $entry.find('.readme').remove() - $entry.css('height', 'auto') - }) - + $entry.find('.readme').remove(); + $entry.css('height', 'auto'); + }); } else { // Open entry $('.entry.open').each(function() { // Close open entries - var $entry = $(this) + let $entry = $(this); $entry .height($entry.outerHeight()) - .removeClass('open') + .removeClass('open'); setTimeout(function() { - $entry.css('height', $entry.attr('data-height') + 'px') - }, 0) + $entry.css('height', $entry.attr('data-height') + 'px'); + }, 0); transitionComplete(function() { - $entry.find('.readme').remove() - $entry.css('height', 'auto') - }) - }) + $entry.find('.readme').remove(); + $entry.css('height', 'auto'); + }); + }); // Add the open class - $entry.addClass('open') + $entry.addClass('open'); // Explicitly set heights for transitions - var height = $entry.outerHeight() + let height = $entry.outerHeight(); $entry .attr('data-height', height) - .css('height', height) + .css('height', height); // Get the data $.ajax({ @@ -57,17 +56,17 @@ $(function() { + encodeURIComponent($entry.attr('data-version')), dataType: 'text', success: function(html) { - var $readme = $("<div class='readme'>") + let $readme = $('<div class=\'readme\'>') .html(html) - .appendTo($entry) + .appendTo($entry); - $entry.height(height + $readme.outerHeight()) + $entry.height(height + $readme.outerHeight()); transitionComplete(function() { - $entry.css('height', 'auto') - }) - } - }) + $entry.css('height', 'auto'); + }); + }, + }); } - }) -}) + }); +}); diff --git a/lib/GUI/js/main.js b/lib/GUI/js/main.js index eb5217414..d47de0253 100644 --- a/lib/GUI/js/main.js +++ b/lib/GUI/js/main.js @@ -1,13 +1,13 @@ // twitter bootstrap stuff; // not in static 'cause I want it to be bundled with the rest of javascripts -require('./bootstrap-modal') +require('./bootstrap-modal'); // our own files -require('./search') -require('./entry') +require('./search'); +require('./entry'); -var $ = require('unopinionate').selector +let $ = require('unopinionate').selector; $(document).on('click', '.js-userLogoutBtn', function() { - $('#userLogoutForm').submit() - return false -}) + $('#userLogoutForm').submit(); + return false; +}); diff --git a/lib/GUI/js/search.js b/lib/GUI/js/search.js index 3b955f12b..d71736c33 100644 --- a/lib/GUI/js/search.js +++ b/lib/GUI/js/search.js @@ -1,5 +1,5 @@ -var $ = require('unopinionate').selector -var template = require('../entry.hbs') +let $ = require('unopinionate').selector; +let template = require('../entry.hbs'); $(function() { ;(function(window, document) { @@ -22,7 +22,8 @@ $(function() { $form.bind('submit keyup', function(e) { var query, isValidQuery - e.preventDefault() + + e.preventDefault(); query = $input.val() isValidQuery = (query !== '') @@ -31,35 +32,35 @@ $(function() { if (!isValidQuery) { if (request && typeof request.abort === 'function') { - request.abort() + request.abort(); } $searchResults.html('') - return + return; } if (request && typeof request.abort === 'function') { - request.abort() + request.abort(); } if (query !== lastQuery) { lastQuery = query $searchResults.html( - "<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>") + '<img class=\'search-ajax\' src=\'-/static/ajax.gif\' alt=\'Spinner\'/>'); } request = $.getJSON('-/search/' + query, function( results ) { if (results.length > 0) { - var html = '' + let html = ''; $.each(results, function(i, entry) { - html += template(entry) - }) + html += template(entry); + }); - $searchResults.html(html) + $searchResults.html(html); } else { $searchResults.html( - "<div class='no-results'><big>No Results</big></div>") + '<div class=\'no-results\'><big>No Results</big></div>'); } }).fail(function () { $searchResults.html( @@ -68,10 +69,9 @@ $(function() { }) $(document).on('click', '.icon-cancel', function(e) { - e.preventDefault() - $input.val('') - $form.keyup() - }) - - })(window, window.document) -}) + e.preventDefault(); + $input.val(''); + $form.keyup(); + }); + })(window, window.document); +}); diff --git a/lib/auth.js b/lib/auth.js index 67f1e67b2..0f1caf2ec 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,33 +1,37 @@ -const Crypto = require('crypto') -const jju = require('jju') -const Error = require('http-errors') -const Logger = require('./logger') +/* eslint prefer-spread: "off" */ + +'use strict'; + +const Crypto = require('crypto'); +const jju = require('jju'); +const Error = require('http-errors'); +const Logger = require('./logger'); const load_plugins = require('./plugin-loader').load_plugins; -module.exports = Auth +module.exports = Auth; function Auth(config) { - var self = Object.create(Auth.prototype) - self.config = config - self.logger = Logger.logger.child({ sub: 'auth' }) - self.secret = config.secret + let self = Object.create(Auth.prototype); + self.config = config; + self.logger = Logger.logger.child({sub: 'auth'}); + self.secret = config.secret; - var plugin_params = { + let plugin_params = { config: config, - logger: self.logger - } + logger: self.logger, + }; if (config.users_file) { if (!config.auth || !config.auth.htpasswd) { // b/w compat - config.auth = config.auth || {} - config.auth.htpasswd = { file: config.users_file } + config.auth = config.auth || {}; + config.auth.htpasswd = {file: config.users_file}; } } - self.plugins = load_plugins(config, config.auth, plugin_params, function (p) { - return p.authenticate || p.allow_access || p.allow_publish - }) + self.plugins = load_plugins(config, config.auth, plugin_params, function(p) { + return p.authenticate || p.allow_access || p.allow_publish; + }); self.plugins.unshift({ verdaccio_version: '1.1.0', @@ -38,342 +42,342 @@ function Auth(config) { && (Crypto.createHash('sha1').update(password).digest('hex') === config.users[user].password) ) { - return cb(null, [ user ]) + return cb(null, [user]); } - return cb() + return cb(); }, adduser: function(user, password, cb) { if (config.users && config.users[user]) - return cb( Error[403]('this user already exists') ) + return cb( Error[403]('this user already exists') ); - return cb() + return cb(); }, - }) + }); function allow_action(action) { - return function(user, package, cb) { - var ok = package[action].reduce(function(prev, curr) { - if (user.groups.indexOf(curr) !== -1) return true - return prev - }, false) + return function(user, pkg, cb) { + let ok = pkg[action].reduce(function(prev, curr) { + if (user.groups.indexOf(curr) !== -1) return true; + return prev; + }, false); - if (ok) return cb(null, true) + if (ok) return cb(null, true); if (user.name) { - cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + package.name) ) + cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + pkg.name) ); } else { - cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + package.name) ) + cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + pkg.name) ); } - } + }; } self.plugins.push({ authenticate: function(user, password, cb) { - return cb( Error[403]('bad username/password, access denied') ) + return cb( Error[403]('bad username/password, access denied') ); }, add_user: function(user, password, cb) { - return cb( Error[409]('registration is disabled') ) + return cb( Error[409]('registration is disabled') ); }, allow_access: allow_action('access'), allow_publish: allow_action('publish'), - }) + }); - return self + return self; } Auth.prototype.authenticate = function(user, password, cb) { - var plugins = this.plugins.slice(0) + let plugins = this.plugins.slice(0) ;(function next() { - var p = plugins.shift() + let p = plugins.shift(); if (typeof(p.authenticate) !== 'function') { - return next() + return next(); } p.authenticate(user, password, function(err, groups) { - if (err) return cb(err) + if (err) return cb(err); if (groups != null && groups != false) - return cb(err, AuthenticatedUser(user, groups)) - next() - }) - })() -} + return cb(err, authenticatedUser(user, groups)); + next(); + }); + })(); +}; Auth.prototype.add_user = function(user, password, cb) { - var self = this - var plugins = this.plugins.slice(0) + let self = this; + let plugins = this.plugins.slice(0) ;(function next() { - var p = plugins.shift() - var n = 'adduser' + let p = plugins.shift(); + let n = 'adduser'; if (typeof(p[n]) !== 'function') { - n = 'add_user' + n = 'add_user'; } if (typeof(p[n]) !== 'function') { - next() + next(); } else { p[n](user, password, function(err, ok) { - if (err) return cb(err) - if (ok) return self.authenticate(user, password, cb) - next() - }) + if (err) return cb(err); + if (ok) return self.authenticate(user, password, cb); + next(); + }); } - })() -} + })(); +}; Auth.prototype.allow_access = function(package_name, user, callback) { - var plugins = this.plugins.slice(0) - var package = Object.assign({ name: package_name }, + let plugins = this.plugins.slice(0); + let pkg = Object.assign({name: package_name}, this.config.get_package_spec(package_name)) ;(function next() { - var p = plugins.shift() + let p = plugins.shift(); if (typeof(p.allow_access) !== 'function') { - return next() + return next(); } - p.allow_access(user, package, function(err, ok) { - if (err) return callback(err) - if (ok) return callback(null, ok) - next() // cb(null, false) causes next plugin to roll - }) - })() -} + p.allow_access(user, pkg, function(err, ok) { + if (err) return callback(err); + if (ok) return callback(null, ok); + next(); // cb(null, false) causes next plugin to roll + }); + })(); +}; Auth.prototype.allow_publish = function(package_name, user, callback) { - var plugins = this.plugins.slice(0) - var package = Object.assign({ name: package_name }, + let plugins = this.plugins.slice(0); + let pkg = Object.assign({name: package_name}, this.config.get_package_spec(package_name)) ;(function next() { - var p = plugins.shift() + let p = plugins.shift(); if (typeof(p.allow_publish) !== 'function') { - return next() + return next(); } - p.allow_publish(user, package, function(err, ok) { - if (err) return callback(err) - if (ok) return callback(null, ok) - next() // cb(null, false) causes next plugin to roll - }) - })() -} + p.allow_publish(user, pkg, function(err, ok) { + if (err) return callback(err); + if (ok) return callback(null, ok); + next(); // cb(null, false) causes next plugin to roll + }); + })(); +}; Auth.prototype.basic_middleware = function() { - var self = this + let self = this; return function(req, res, _next) { - req.pause() + req.pause(); function next(err) { - req.resume() + req.resume(); // uncomment this to reject users with bad auth headers - //return _next.apply(null, arguments) + // return _next.apply(null, arguments) // swallow error, user remains unauthorized // set remoteUserError to indicate that user was attempting authentication - if (err) req.remote_user.error = err.message - return _next() + if (err) req.remote_user.error = err.message; + return _next(); } if (req.remote_user != null && req.remote_user.name !== undefined) - return next() - req.remote_user = AnonymousUser() + return next(); + req.remote_user = buildAnonymousUser(); - var authorization = req.headers.authorization - if (authorization == null) return next() + let authorization = req.headers.authorization; + if (authorization == null) return next(); - var parts = authorization.split(' ') + let parts = authorization.split(' '); if (parts.length !== 2) - return next( Error[400]('bad authorization header') ) + return next( Error[400]('bad authorization header') ); - var scheme = parts[0] + let scheme = parts[0]; if (scheme === 'Basic') { - var credentials = new Buffer(parts[1], 'base64').toString() + var credentials = new Buffer(parts[1], 'base64').toString(); } else if (scheme === 'Bearer') { - var credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8') - if (!credentials) return next() + var credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8'); + if (!credentials) return next(); } else { - return next() + return next(); } - var index = credentials.indexOf(':') - if (index < 0) return next() + let index = credentials.indexOf(':'); + if (index < 0) return next(); - var user = credentials.slice(0, index) - var pass = credentials.slice(index + 1) + let user = credentials.slice(0, index); + let pass = credentials.slice(index + 1); self.authenticate(user, pass, function(err, user) { if (!err) { - req.remote_user = user - next() + req.remote_user = user; + next(); } else { - req.remote_user = AnonymousUser() - next(err) + req.remote_user = buildAnonymousUser(); + next(err); } - }) - } -} + }); + }; +}; Auth.prototype.bearer_middleware = function() { - var self = this + let self = this; return function(req, res, _next) { - req.pause() + req.pause(); function next(_err) { - req.resume() - return _next.apply(null, arguments) + req.resume(); + return _next.apply(null, arguments); } if (req.remote_user != null && req.remote_user.name !== undefined) - return next() - req.remote_user = AnonymousUser() + return next(); + req.remote_user = buildAnonymousUser(); - var authorization = req.headers.authorization - if (authorization == null) return next() + let authorization = req.headers.authorization; + if (authorization == null) return next(); - var parts = authorization.split(' ') + let parts = authorization.split(' '); if (parts.length !== 2) - return next( Error[400]('bad authorization header') ) + return next( Error[400]('bad authorization header') ); - var scheme = parts[0] - var token = parts[1] + let scheme = parts[0]; + let token = parts[1]; if (scheme !== 'Bearer') - return next() + return next(); try { - var user = self.decode_token(token) + var user = self.decode_token(token); } catch(err) { - return next(err) + return next(err); } - req.remote_user = AuthenticatedUser(user.u, user.g) - req.remote_user.token = token - next() - } -} + req.remote_user = authenticatedUser(user.u, user.g); + req.remote_user.token = token; + next(); + }; +}; Auth.prototype.cookie_middleware = function() { - var self = this + let self = this; return function(req, res, _next) { - req.pause() + req.pause(); function next(_err) { - req.resume() - return _next() + req.resume(); + return _next(); } if (req.remote_user != null && req.remote_user.name !== undefined) - return next() + return next(); - req.remote_user = AnonymousUser() + req.remote_user = buildAnonymousUser(); - var token = req.cookies.get('token') - if (token == null) return next() + let token = req.cookies.get('token'); + if (token == null) return next(); - /*try { + /* try { var user = self.decode_token(token, 60*60) } catch(err) { return next() } - req.remote_user = AuthenticatedUser(user.u, user.g) + req.remote_user = authenticatedUser(user.u, user.g) req.remote_user.token = token next()*/ - var credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8') - if (!credentials) return next() + let credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8'); + if (!credentials) return next(); - var index = credentials.indexOf(':') - if (index < 0) return next() + let index = credentials.indexOf(':'); + if (index < 0) return next(); - var user = credentials.slice(0, index) - var pass = credentials.slice(index + 1) + let user = credentials.slice(0, index); + let pass = credentials.slice(index + 1); self.authenticate(user, pass, function(err, user) { if (!err) { - req.remote_user = user - next() + req.remote_user = user; + next(); } else { - req.remote_user = AnonymousUser() - next(err) + req.remote_user = buildAnonymousUser(); + next(err); } - }) - } -} + }); + }; +}; Auth.prototype.issue_token = function(user) { - var data = jju.stringify({ + let data = jju.stringify({ u: user.name, g: user.real_groups && user.real_groups.length ? user.real_groups : undefined, t: ~~(Date.now()/1000), - }, { indent: false }) + }, {indent: false}); - data = new Buffer(data, 'utf8') - var mac = Crypto.createHmac('sha256', this.secret).update(data).digest() - return Buffer.concat([ data, mac ]).toString('base64') -} + data = new Buffer(data, 'utf8'); + let mac = Crypto.createHmac('sha256', this.secret).update(data).digest(); + return Buffer.concat([data, mac]).toString('base64'); +}; Auth.prototype.decode_token = function(str, expire_time) { - var buf = new Buffer(str, 'base64') - if (buf.length <= 32) throw Error[401]('invalid token') + let buf = new Buffer(str, 'base64'); + if (buf.length <= 32) throw Error[401]('invalid token'); - var data = buf.slice(0, buf.length - 32) - var their_mac = buf.slice(buf.length - 32) - var good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest() + let data = buf.slice(0, buf.length - 32); + let their_mac = buf.slice(buf.length - 32); + let good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest(); - their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex') - good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex') - if (their_mac !== good_mac) throw Error[401]('bad signature') + their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex'); + good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex'); + if (their_mac !== good_mac) throw Error[401]('bad signature'); // make token expire in 24 hours // TODO: make configurable? - expire_time = expire_time || 24*60*60 + expire_time = expire_time || 24*60*60; - data = jju.parse(data.toString('utf8')) + data = jju.parse(data.toString('utf8')); if (Math.abs(data.t - ~~(Date.now()/1000)) > expire_time) - throw Error[401]('token expired') + throw Error[401]('token expired'); - return data -} + return data; +}; Auth.prototype.aes_encrypt = function(buf) { - var c = Crypto.createCipher('aes192', this.secret) - var b1 = c.update(buf) - var b2 = c.final() - return Buffer.concat([ b1, b2 ]) -} + let c = Crypto.createCipher('aes192', this.secret); + let b1 = c.update(buf); + let b2 = c.final(); + return Buffer.concat([b1, b2]); +}; Auth.prototype.aes_decrypt = function(buf) { try { - var c = Crypto.createDecipher('aes192', this.secret) - var b1 = c.update(buf) - var b2 = c.final() + let c = Crypto.createDecipher('aes192', this.secret); + let b1 = c.update(buf); + let b2 = c.final(); + return Buffer.concat([b1, b2]); } catch(_) { - return new Buffer(0) + return new Buffer(0); } - return Buffer.concat([ b1, b2 ]) -} +}; -function AnonymousUser() { +function buildAnonymousUser() { return { name: undefined, // groups without '$' are going to be deprecated eventually - groups: [ '$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous' ], + groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'], real_groups: [], - } + }; } -function AuthenticatedUser(name, groups) { - var _groups = (groups || []).concat([ '$all', '$authenticated', '@all', '@authenticated', 'all' ]) +function authenticatedUser(name, groups) { + let _groups = (groups || []).concat(['$all', '$authenticated', '@all', '@authenticated', 'all']); return { name: name, groups: _groups, real_groups: groups, - } + }; } diff --git a/lib/cli.js b/lib/cli.js index 356364425..bea78c61e 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,113 +1,113 @@ #!/usr/bin/env node -/*eslint no-sync:0*/ +/* eslint no-sync:0*/ +'use strict'; if (process.getuid && process.getuid() === 0) { - global.console.error("Verdaccio doesn't need superuser privileges. Don't run it under root.") + global.console.error('Verdaccio doesn\'t need superuser privileges. Don\'t run it under root.'); } -process.title = 'verdaccio' +process.title = 'verdaccio'; try { // for debugging memory leaks // totally optional - require('heapdump') + require('heapdump'); } catch(err) {} -var logger = require('./logger') -logger.setup() // default setup +let logger = require('./logger'); +logger.setup(); // default setup -var commander = require('commander') -var constants = require('constants') -var fs = require('fs') -var http = require('http') -var https = require('https') -var YAML = require('js-yaml') -var Path = require('path') -var URL = require('url') -var server = require('./index') -var Utils = require('./utils') -var pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars -var pkgVersion = module.exports.version -var pkgName = module.exports.name +let commander = require('commander'); +let constants = require('constants'); +let fs = require('fs'); +let http = require('http'); +let https = require('https'); +let YAML = require('js-yaml'); +let Path = require('path'); +let URL = require('url'); +let server = require('./index'); +let Utils = require('./utils'); +let pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars +let pkgVersion = module.exports.version; +let pkgName = module.exports.name; commander .option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)') .option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)') .version(pkgVersion) - .parse(process.argv) + .parse(process.argv); if (commander.args.length == 1 && !commander.config) { // handling "verdaccio [config]" case if "-c" is missing in commandline - commander.config = commander.args.pop() + commander.config = commander.args.pop(); } if (commander.args.length != 0) { - commander.help() + commander.help(); } -var config, config_path +let config, config_path; try { if (commander.config) { - config_path = Path.resolve(commander.config) + config_path = Path.resolve(commander.config); } else { - config_path = require('./config-path')() + config_path = require('./config-path')(); } - config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8')) - logger.logger.warn({ file: config_path }, 'config file - @{file}') + config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8')); + logger.logger.warn({file: config_path}, 'config file - @{file}'); } catch (err) { - logger.logger.fatal({ file: config_path, err: err }, 'cannot open config file @{file}: @{!err.message}') - process.exit(1) + logger.logger.fatal({file: config_path, err: err}, 'cannot open config file @{file}: @{!err.message}'); + process.exit(1); } -afterConfigLoad() +afterConfigLoad(); function get_listen_addresses() { // command line || config file || default - var addresses + let addresses; if (commander.listen) { - addresses = [ commander.listen ] + addresses = [commander.listen]; } else if (Array.isArray(config.listen)) { - addresses = config.listen + addresses = config.listen; } else if (config.listen) { - addresses = [ config.listen ] + addresses = [config.listen]; } else { - addresses = [ '4873' ] + addresses = ['4873']; } addresses = addresses.map(function(addr) { - var parsed_addr = Utils.parse_address(addr) + let parsed_addr = Utils.parse_address(addr); if (!parsed_addr) { - logger.logger.warn({ addr: addr }, + logger.logger.warn({addr: addr}, 'invalid address - @{addr}, we expect a port (e.g. "4873"),' + ' host:port (e.g. "localhost:4873") or full url' - + ' (e.g. "http://localhost:4873/")') + + ' (e.g. "http://localhost:4873/")'); } - return parsed_addr + return parsed_addr; + }).filter(Boolean); - }).filter(Boolean) - - return addresses + return addresses; } function afterConfigLoad() { - if (!config.self_path) config.self_path = Path.resolve(config_path) - if (!config.https) config.https = { enable: false }; + if (!config.self_path) config.self_path = Path.resolve(config_path); + if (!config.https) config.https = {enable: false}; - var app = server(config) + let app = server(config); get_listen_addresses().forEach(function(addr) { - var webServer + let webServer; if (addr.proto === 'https') { // https if (!config.https || !config.https.key || !config.https.cert) { - var conf_path = function(file) { - if (!file) return config_path - return Path.resolve(Path.dirname(config_path), file) - } + let conf_path = function(file) { + if (!file) return config_path; + return Path.resolve(Path.dirname(config_path), file); + }; logger.logger.fatal([ 'You need to specify "https.key" and "https.cert" to run https server', @@ -122,8 +122,8 @@ function afterConfigLoad() { ' https:', ' key: verdaccio-key.pem', ' cert: verdaccio-cert.pem', - ].join('\n')) - process.exit(2) + ].join('\n')); + process.exit(2); } try { @@ -132,12 +132,11 @@ function afterConfigLoad() { secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3, key: fs.readFileSync(config.https.key), cert: fs.readFileSync(config.https.cert), - ca: fs.readFileSync(config.https.ca) - - }, app) + ca: fs.readFileSync(config.https.ca), + }, app); } catch (err) { // catch errors related to certificate loading - logger.logger.fatal({ err: err }, 'cannot create server: @{err.message}') - process.exit(2) + logger.logger.fatal({err: err}, 'cannot create server: @{err.message}'); + process.exit(2); } } else { // http webServer = http.createServer(app); @@ -146,9 +145,9 @@ function afterConfigLoad() { webServer .listen(addr.port || addr.path, addr.host) .on('error', function(err) { - logger.logger.fatal({ err: err }, 'cannot create server: @{err.message}') - process.exit(2) - }) + logger.logger.fatal({err: err}, 'cannot create server: @{err.message}'); + process.exit(2); + }); logger.logger.warn({ addr: ( addr.path @@ -164,17 +163,17 @@ function afterConfigLoad() { }) ), version: pkgName + '/' + pkgVersion, - }, 'http address - @{addr}') - }) + }, 'http address - @{addr}'); + }); // undocumented stuff for tests if (typeof(process.send) === 'function') { - process.send({ verdaccio_started: true }) + process.send({verdaccio_started: true}); } } process.on('uncaughtException', function(err) { - logger.logger.fatal( { err: err } - , 'uncaught exception, please report this\n@{err.stack}' ) - process.exit(255) -}) + logger.logger.fatal( {err: err} + , 'uncaught exception, please report this\n@{err.stack}' ); + process.exit(255); +}); diff --git a/lib/config-path.js b/lib/config-path.js index 6cb7a2236..b83aa0e72 100644 --- a/lib/config-path.js +++ b/lib/config-path.js @@ -1,47 +1,49 @@ -var fs = require('fs') -var Path = require('path') -var logger = require('./logger') +'use strict'; -module.exports = find_config_file +let fs = require('fs'); +let Path = require('path'); +let logger = require('./logger'); + +module.exports = find_config_file; function find_config_file() { - var paths = get_paths() + let paths = get_paths(); - for (var i=0; i<paths.length; i++) { - if (file_exists(paths[i].path)) return paths[i].path + for (let i=0; i<paths.length; i++) { + if (file_exists(paths[i].path)) return paths[i].path; } - create_config_file(paths[0]) - return paths[0].path + create_config_file(paths[0]); + return paths[0].path; } function create_config_file(config_path) { - require('mkdirp').sync(Path.dirname(config_path.path)) - logger.logger.info({ file: config_path.path }, 'Creating default config file in @{file}') + require('mkdirp').sync(Path.dirname(config_path.path)); + logger.logger.info({file: config_path.path}, 'Creating default config file in @{file}'); - var created_config = fs.readFileSync(require.resolve('../conf/default.yaml'), 'utf8') + let created_config = fs.readFileSync(require.resolve('../conf/default.yaml'), 'utf8'); if (config_path.type === 'xdg') { - var data_dir = process.env.XDG_DATA_HOME - || Path.join(process.env.HOME, '.local', 'share') + let data_dir = process.env.XDG_DATA_HOME + || Path.join(process.env.HOME, '.local', 'share'); if (folder_exists(data_dir)) { - data_dir = Path.resolve(Path.join(data_dir, 'verdaccio', 'storage')) - created_config = created_config.replace(/^storage: .\/storage$/m, 'storage: ' + data_dir) + data_dir = Path.resolve(Path.join(data_dir, 'verdaccio', 'storage')); + created_config = created_config.replace(/^storage: .\/storage$/m, 'storage: ' + data_dir); } } - fs.writeFileSync(config_path.path, created_config) + fs.writeFileSync(config_path.path, created_config); } function get_paths() { - var try_paths = [] - var xdg_config = process.env.XDG_CONFIG_HOME - || process.env.HOME && Path.join(process.env.HOME, '.config') + let try_paths = []; + let xdg_config = process.env.XDG_CONFIG_HOME + || process.env.HOME && Path.join(process.env.HOME, '.config'); if (xdg_config && folder_exists(xdg_config)) { try_paths.push({ path: Path.join(xdg_config, 'verdaccio', 'config.yaml'), type: 'xdg', - }) + }); } if (process.platform === 'win32' @@ -50,40 +52,40 @@ function get_paths() { try_paths.push({ path: Path.resolve(Path.join(process.env.APPDATA, 'verdaccio', 'config.yaml')), type: 'win', - }) + }); } try_paths.push({ path: Path.resolve(Path.join('.', 'verdaccio', 'config.yaml')), type: 'def', - }) + }); // backward compatibility try_paths.push({ path: Path.resolve(Path.join('.', 'config.yaml')), type: 'old', - }) + }); - return try_paths + return try_paths; } function folder_exists(path) { try { - var stat = fs.statSync(path) + var stat = fs.statSync(path); } catch(_) { - return false + return false; } - return stat.isDirectory() + return stat.isDirectory(); } function file_exists(path) { try { - var stat = fs.statSync(path) + var stat = fs.statSync(path); } catch(_) { - return false + return false; } - return stat.isFile() + return stat.isFile(); } diff --git a/lib/config.js b/lib/config.js index 1c0b54511..96b2affe2 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,32 +1,34 @@ -var assert = require('assert') -var Crypto = require('crypto') -var Error = require('http-errors') -var minimatch = require('minimatch') -var Path = require('path') -var LocalData = require('./local-data') -var Utils = require('./utils') -var Utils = require('./utils') -var pkginfo = require('pkginfo')(module) // eslint-disable-line no-unused-vars -var pkgVersion = module.exports.version -var pkgName = module.exports.name +'use strict'; + +let assert = require('assert'); +let Crypto = require('crypto'); +let Error = require('http-errors'); +let minimatch = require('minimatch'); +let Path = require('path'); +let LocalData = require('./local-data'); +var Utils = require('./utils'); +var Utils = require('./utils'); +let pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars +let pkgVersion = module.exports.version; +let pkgName = module.exports.name; // [[a, [b, c]], d] -> [a, b, c, d] function flatten(array) { - var result = [] - for (var i=0; i<array.length; i++) { + let result = []; + for (let i=0; i<array.length; i++) { if (Array.isArray(array[i])) { - result.push.apply(result, flatten(array[i])) + result.push.apply(result, flatten(array[i])); } else { - result.push(array[i]) + result.push(array[i]); } } - return result + return result; } function Config(config) { - var self = Object.create(Config.prototype) + let self = Object.create(Config.prototype); for (var i in config) { - if (self[i] == null) self[i] = config[i] + if (self[i] == null) self[i] = config[i]; } if (!self.user_agent) { @@ -34,44 +36,44 @@ function Config(config) { } // some weird shell scripts are valid yaml files parsed as string - assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file') + assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file'); - assert(self.storage, 'CONFIG: storage path not defined') + assert(self.storage, 'CONFIG: storage path not defined'); self.localList = new LocalData( Path.join( Path.resolve(Path.dirname(self.self_path || ''), self.storage), '.sinopia-db.json' ) - ) + ); if (!self.secret) { - self.secret = self.localList.data.secret + self.secret = self.localList.data.secret; if (!self.secret) { - self.secret = Crypto.pseudoRandomBytes(32).toString('hex') - self.localList.data.secret = self.secret - self.localList.sync() + self.secret = Crypto.pseudoRandomBytes(32).toString('hex'); + self.localList.data.secret = self.secret; + self.localList.sync(); } } - var users = { - all: true, - anonymous: true, + let users = { + 'all': true, + 'anonymous': true, 'undefined': true, - owner: true, - none: true + 'owner': true, + 'none': true, }; - var check_user_or_uplink = function(arg) { - assert(arg !== 'all' && arg !== 'owner' && arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg) - assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg) - assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg) - users[arg] = true + let check_user_or_uplink = function(arg) { + assert(arg !== 'all' && arg !== 'owner' && arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg); + assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg); + assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg); + users[arg] = true; } - ;[ 'users', 'uplinks', 'packages' ].forEach(function(x) { - if (self[x] == null) self[x] = {} - assert(Utils.is_object(self[x]), 'CONFIG: bad "'+x+'" value (object expected)') - }) + ;['users', 'uplinks', 'packages'].forEach(function(x) { + if (self[x] == null) self[x] = {}; + assert(Utils.is_object(self[x]), 'CONFIG: bad "'+x+'" value (object expected)'); + }); for (var i in self.users) { check_user_or_uplink(i); @@ -81,127 +83,127 @@ function Config(config) { } for (var i in self.users) { - assert(self.users[i].password, 'CONFIG: no password for user: ' + i) + assert(self.users[i].password, 'CONFIG: no password for user: ' + i); assert(typeof(self.users[i].password) === 'string' && self.users[i].password.match(/^[a-f0-9]{40}$/) - , 'CONFIG: wrong password format for user: ' + i + ', sha1 expected') + , 'CONFIG: wrong password format for user: ' + i + ', sha1 expected'); } for (var i in self.uplinks) { - assert(self.uplinks[i].url, 'CONFIG: no url for uplink: ' + i) + assert(self.uplinks[i].url, 'CONFIG: no url for uplink: ' + i); assert( typeof(self.uplinks[i].url) === 'string' - , 'CONFIG: wrong url format for uplink: ' + i) - self.uplinks[i].url = self.uplinks[i].url.replace(/\/$/, '') + , 'CONFIG: wrong url format for uplink: ' + i); + self.uplinks[i].url = self.uplinks[i].url.replace(/\/$/, ''); } function normalize_userlist() { - var result = [] + let result = []; - for (var i=0; i<arguments.length; i++) { - if (arguments[i] == null) continue + for (let i=0; i<arguments.length; i++) { + if (arguments[i] == null) continue; // if it's a string, split it to array if (typeof(arguments[i]) === 'string') { - result.push(arguments[i].split(/\s+/)) + result.push(arguments[i].split(/\s+/)); } else if (Array.isArray(arguments[i])) { - result.push(arguments[i]) + result.push(arguments[i]); } else { - throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i])) + throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i])); } } - return flatten(result) + return flatten(result); } // add a default rule for all packages to make writing plugins easier if (self.packages['**'] == null) { - self.packages['**'] = {} + self.packages['**'] = {}; } for (var i in self.packages) { assert( typeof(self.packages[i]) === 'object' && !Array.isArray(self.packages[i]) - , 'CONFIG: bad "'+i+'" package description (object expected)') + , 'CONFIG: bad "'+i+'" package description (object expected)'); self.packages[i].access = normalize_userlist( self.packages[i].allow_access, self.packages[i].access ); - delete self.packages[i].allow_access + delete self.packages[i].allow_access; self.packages[i].publish = normalize_userlist( self.packages[i].allow_publish, self.packages[i].publish ); - delete self.packages[i].allow_publish + delete self.packages[i].allow_publish; self.packages[i].proxy = normalize_userlist( self.packages[i].proxy_access, self.packages[i].proxy ); - delete self.packages[i].proxy_access + delete self.packages[i].proxy_access; } // loading these from ENV if aren't in config - ;[ 'http_proxy', 'https_proxy', 'no_proxy' ].forEach((function(v) { + ['http_proxy', 'https_proxy', 'no_proxy'].forEach((function(v) { if (!(v in self)) { - self[v] = process.env[v] || process.env[v.toUpperCase()] + self[v] = process.env[v] || process.env[v.toUpperCase()]; } - }).bind(self)) + })); // unique identifier of self server (or a cluster), used to avoid loops if (!self.server_id) { - self.server_id = Crypto.pseudoRandomBytes(6).toString('hex') + self.server_id = Crypto.pseudoRandomBytes(6).toString('hex'); } - return self + return self; } -Config.prototype.can_proxy_to = function(package, uplink) { - return (this.get_package_spec(package).proxy || []).reduce(function(prev, curr) { - if (uplink === curr) return true - return prev - }, false) -} +Config.prototype.can_proxy_to = function(pkg, uplink) { + return (this.get_package_spec(pkg).proxy || []).reduce(function(prev, curr) { + if (uplink === curr) return true; + return prev; + }, false); +}; -Config.prototype.get_package_spec = function(package) { - for (var i in this.packages) { - if (minimatch.makeRe(i).exec(package)) { - return this.packages[i] +Config.prototype.get_package_spec = function(pkg) { + for (let i in this.packages) { + if (minimatch.makeRe(i).exec(pkg)) { + return this.packages[i]; } } - return {} -} + return {}; +}; -module.exports = Config +module.exports = Config; -var parse_interval_table = { +let parse_interval_table = { '': 1000, - ms: 1, - s: 1000, - m: 60*1000, - h: 60*60*1000, - d: 86400000, - w: 7*86400000, - M: 30*86400000, - y: 365*86400000, -} + 'ms': 1, + 's': 1000, + 'm': 60*1000, + 'h': 60*60*1000, + 'd': 86400000, + 'w': 7*86400000, + 'M': 30*86400000, + 'y': 365*86400000, +}; module.exports.parse_interval = function(interval) { - if (typeof(interval) === 'number') return interval * 1000 + if (typeof(interval) === 'number') return interval * 1000; - var result = 0 - var last_suffix = Infinity + let result = 0; + let last_suffix = Infinity; interval.split(/\s+/).forEach(function(x) { - if (!x) return - var m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/) + if (!x) return; + let m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/); if (!m - || parse_interval_table[m[4]] >= last_suffix - || (m[4] === '' && last_suffix !== Infinity)) { - throw Error('invalid interval: ' + interval) + || parse_interval_table[m[4]] >= last_suffix + || (m[4] === '' && last_suffix !== Infinity)) { + throw Error('invalid interval: ' + interval); } - last_suffix = parse_interval_table[m[4]] - result += Number(m[1]) * parse_interval_table[m[4]] - }) - return result -} + last_suffix = parse_interval_table[m[4]]; + result += Number(m[1]) * parse_interval_table[m[4]]; + }); + return result; +}; diff --git a/lib/file-locking.js b/lib/file-locking.js index 7b19e2e25..e2c0e619e 100644 --- a/lib/file-locking.js +++ b/lib/file-locking.js @@ -1,15 +1,17 @@ +'use strict'; + /** * file-locking.js - file system locking (replaces fs-ext) */ -var async = require('async'), +let async = require('async'), locker = require('lockfile'), fs = require('fs'), - path = require('path') + path = require('path'); // locks a file by creating a lock file function lockFile(name, next) { - var lockFileName = name + '.lock', + let lockFileName = name + '.lock', lockOpts = { wait: 1000, // time (ms) to wait when checking for stale locks pollPeriod: 100, // how often (ms) to re-check stale locks @@ -17,65 +19,64 @@ function lockFile(name, next) { stale: 5 * 60 * 1000, // locks are considered stale after 5 minutes retries: 100, // number of times to attempt to create a lock - retryWait: 100 // time (ms) between tries - } + retryWait: 100, // time (ms) between tries + }; async.series({ - statdir: function (callback) { + statdir: function(callback) { // test to see if the directory exists - fs.stat(path.dirname(name), function (err, stats) { + fs.stat(path.dirname(name), function(err, stats) { if (err) { - callback(err) + callback(err); } else if (!stats.isDirectory()) { - callback(new Error(path.dirname(name) + ' is not a directory')) + callback(new Error(path.dirname(name) + ' is not a directory')); } else { - callback(null) - } - }) - }, - - statfile: function (callback) { - // test to see if the file to lock exists - fs.stat(name, function (err, stats) { - if (err) { - callback(err) - } else if (!stats.isFile()) { - callback(new Error(path.dirname(name) + ' is not a file')) - } else { - callback(null) + callback(null); } }); }, - lockfile: function (callback) { - // try to lock the file - locker.lock(lockFileName, lockOpts, callback) - } + statfile: function(callback) { + // test to see if the file to lock exists + fs.stat(name, function(err, stats) { + if (err) { + callback(err); + } else if (!stats.isFile()) { + callback(new Error(path.dirname(name) + ' is not a file')); + } else { + callback(null); + } + }); + }, - }, function (err) { + lockfile: function(callback) { + // try to lock the file + locker.lock(lockFileName, lockOpts, callback); + }, + + }, function(err) { if (err) { // lock failed - return next(err) + return next(err); } // lock succeeded return next(null); - }) - + }); } // unlocks file by removing existing lock file function unlockFile(name, next) { - var lockFileName = name + '.lock' + let lockFileName = name + '.lock'; - locker.unlock(lockFileName, function (err) { + locker.unlock(lockFileName, function(err) { if (err) { - return next(err) + return next(err); } - return next(null) - }) + return next(null); + }); } /** @@ -87,63 +88,62 @@ function unlockFile(name, next) { function readFile(name, options, next) { if (typeof options === 'function' && next === null) { next = options; - options = {} + options = {}; } - options = options || {} - options.lock = options.lock || false - options.parse = options.parse || false + options = options || {}; + options.lock = options.lock || false; + options.parse = options.parse || false; function lock(callback) { if (!options.lock) { - return callback(null) + return callback(null); } - lockFile(name, function (err) { + lockFile(name, function(err) { if (err) { - return callback(err) + return callback(err); } - return callback(null) - }) + return callback(null); + }); } function read(callback) { - fs.readFile(name, 'utf8', function (err, contents) { + fs.readFile(name, 'utf8', function(err, contents) { if (err) { - return callback(err) + return callback(err); } - callback(null, contents) - - }) + callback(null, contents); + }); } function parseJSON(contents, callback) { if (!options.parse) { - return callback(null, contents) + return callback(null, contents); } try { - contents = JSON.parse(contents) - return callback(null, contents) + contents = JSON.parse(contents); + return callback(null, contents); } catch (err) { - return callback(err) + return callback(err); } } async.waterfall([ lock, read, - parseJSON + parseJSON, ], - function (err, result) { + function(err, result) { if (err) { - return next(err) + return next(err); } else { - return next(null, result) + return next(null, result); } - }) + }); } exports.lockFile = lockFile; diff --git a/lib/index-api.js b/lib/index-api.js index c624de91d..720b60cbf 100644 --- a/lib/index-api.js +++ b/lib/index-api.js @@ -1,99 +1,102 @@ -var Cookies = require('cookies') -var express = require('express') -var bodyParser = require('body-parser') -var Error = require('http-errors') -var Path = require('path') -var Middleware = require('./middleware') -var Notify = require('./notify') -var Utils = require('./utils') -var expect_json = Middleware.expect_json -var match = Middleware.match -var media = Middleware.media -var validate_name = Middleware.validate_name -var validate_pkg = Middleware.validate_package +'use strict'; + +let Cookies = require('cookies'); +let express = require('express'); +let bodyParser = require('body-parser'); +let Error = require('http-errors'); +let Path = require('path'); +let Middleware = require('./middleware'); +let Notify = require('./notify'); +let Utils = require('./utils'); +let expect_json = Middleware.expect_json; +let match = Middleware.match; +let media = Middleware.media; +let validate_name = Middleware.validate_name; +let validate_pkg = Middleware.validate_package; module.exports = function(config, auth, storage) { - var app = express.Router() - var can = Middleware.allow(auth) - var notify = Notify.notify; + let app = express.Router(); + let can = Middleware.allow(auth); + let notify = Notify.notify; // validate all of these params as a package name // this might be too harsh, so ask if it causes trouble - app.param('package', validate_pkg) - app.param('filename', validate_name) - app.param('tag', validate_name) - app.param('version', validate_name) - app.param('revision', validate_name) - app.param('token', validate_name) + app.param('package', validate_pkg); + app.param('filename', validate_name); + app.param('tag', validate_name); + app.param('version', validate_name); + app.param('revision', validate_name); + app.param('token', validate_name); // these can't be safely put into express url for some reason - app.param('_rev', match(/^-rev$/)) - app.param('org_couchdb_user', match(/^org\.couchdb\.user:/)) - app.param('anything', match(/.*/)) + app.param('_rev', match(/^-rev$/)); + app.param('org_couchdb_user', match(/^org\.couchdb\.user:/)); + app.param('anything', match(/.*/)); - app.use(auth.basic_middleware()) - //app.use(auth.bearer_middleware()) - app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' })) - app.use(Middleware.anti_loop(config)) + app.use(auth.basic_middleware()); + // app.use(auth.bearer_middleware()) + app.use(bodyParser.json({strict: false, limit: config.max_body_size || '10mb'})); + app.use(Middleware.anti_loop(config)); // encode / in a scoped package name to be matched as a single parameter in routes app.use(function(req, res, next) { if (req.url.indexOf('@') != -1) { // e.g.: /@org/pkg/1.2.3 -> /@org%2Fpkg/1.2.3, /@org%2Fpkg/1.2.3 -> /@org%2Fpkg/1.2.3 - req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F') + req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F'); } - next() - }) + next(); + }); // for "npm whoami" app.get('/whoami', function(req, res, next) { if (req.headers.referer === 'whoami') { - next({ username: req.remote_user.name }) + next({username: req.remote_user.name}); } else { - next('route') + next('route'); } - }) + }); app.get('/-/whoami', function(req, res, next) { - next({ username: req.remote_user.name }) - }) + next({username: req.remote_user.name}); + }); // TODO: anonymous user? app.get('/:package/:version?', can('access'), function(req, res, next) { - storage.get_package(req.params.package, { req: req }, function(err, info) { - if (err) return next(err) - info = Utils.filter_tarball_urls(info, req, config) + storage.get_package(req.params.package, {req: req}, function(err, info) { + if (err) return next(err); + info = Utils.filter_tarball_urls(info, req, config); - var version = req.params.version - if (!version) return next(info) + let version = req.params.version; + if (!version) return next(info); - var t = Utils.get_version(info, version) - if (t != null) return next(t) + let t = Utils.get_version(info, version); + if (t != null) return next(t); if (info['dist-tags'] != null) { if (info['dist-tags'][version] != null) { - version = info['dist-tags'][version] - t = Utils.get_version(info, version) - if (t != null) return next(t) + version = info['dist-tags'][version]; + t = Utils.get_version(info, version); + if (t != null) return next(t); } } - return next( Error[404]('version not found: ' + req.params.version) ) - }) - }) + return next( Error[404]('version not found: ' + req.params.version) ); + }); + }); app.get('/:package/-/:filename', can('access'), function(req, res, next) { - var stream = storage.get_tarball(req.params.package, req.params.filename) + let stream = storage.get_tarball(req.params.package, req.params.filename); stream.on('content-length', function(v) { - res.header('Content-Length', v) - }) + res.header('Content-Length', v); + }); stream.on('error', function(err) { - return res.report_error(err) - }) - res.header('Content-Type', 'application/octet-stream') - stream.pipe(res) - }) + return res.report_error(err); + }); + res.header('Content-Type', 'application/octet-stream'); + stream.pipe(res); + }); // searching packages +<<<<<<< HEAD app.get('/-/all(\/since)?', function(req, res, next) { var received_end = false var response_finished = false @@ -128,26 +131,36 @@ module.exports = function(config, auth, storage) { } else { res.write('{"_updated":' + 99999); } +======= + app.get('/-/all/:anything?', function(req, res, next) { + let received_end = false; + let response_finished = false; + let processing_pkgs = 0; - var stream = storage.search(req.query.startkey || 0, { req: req }) + res.status(200); + res.write('{"_updated":' + Date.now()); +>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors + + let stream = storage.search(req.query.startkey || 0, {req: req}); stream.on('data', function each(pkg) { - processing_pkgs++ + processing_pkgs++; auth.allow_access(pkg.name, req.remote_user, function(err, allowed) { - processing_pkgs-- + processing_pkgs--; if (err) { if (err.status && String(err.status).match(/^4\d\d$/)) { // auth plugin returns 4xx user error, // that's equivalent of !allowed basically - allowed = false + allowed = false; } else { - stream.abort(err) + stream.abort(err); } } if (allowed) { +<<<<<<< HEAD if (respShouldBeArray) { res.write(`${firstPackage ? '' : ','}${JSON.stringify(pkg)}\n`) if (firstPackage) { @@ -156,69 +169,77 @@ module.exports = function(config, auth, storage) { } else { res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg)) } +======= + res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg)); +>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors } - check_finish() - }) - }) + check_finish(); + }); + }); - stream.on('error', function (_err) { - res.socket.destroy() - }) + stream.on('error', function(_err) { + res.socket.destroy(); + }); - stream.on('end', function () { - received_end = true - check_finish() - }) + stream.on('end', function() { + received_end = true; + check_finish(); + }); function check_finish() { - if (!received_end) return - if (processing_pkgs) return - if (response_finished) return + if (!received_end) return; + if (processing_pkgs) return; + if (response_finished) return; +<<<<<<< HEAD response_finished = true if (respShouldBeArray) { res.end(']\n') } else { res.end('}\n') } +======= + response_finished = true; + res.end('}\n'); +>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors } - }) + }); // placeholder 'cause npm require to be authenticated to publish // we do not do any real authentication yet app.post('/_session', Cookies.express(), function(req, res, next) { res.cookies.set('AuthSession', String(Math.random()), { // npmjs.org sets 10h expire - expires: new Date(Date.now() + 10*60*60*1000) - }) - next({ ok: true, name: 'somebody', roles: [] }) - }) + expires: new Date(Date.now() + 10*60*60*1000), + }); + next({ok: true, name: 'somebody', roles: []}); + }); app.get('/-/user/:org_couchdb_user', function(req, res, next) { - res.status(200) + res.status(200); next({ ok: 'you are authenticated as "' + req.remote_user.name + '"', - }) - }) + }); + }); app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) { - var token = (req.body.name && req.body.password) + let token = (req.body.name && req.body.password) ? auth.aes_encrypt(req.body.name + ':' + req.body.password).toString('base64') - : undefined + : undefined; if (req.remote_user.name != null) { - res.status(201) + res.status(201); return next({ - ok: "you are authenticated as '" + req.remote_user.name + "'", - //token: auth.issue_token(req.remote_user), + ok: 'you are authenticated as \'' + req.remote_user.name + '\'', + // token: auth.issue_token(req.remote_user), token: token, - }) + }); } else { if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') { if (typeof(req.body.password_sha)) { - return next( Error[422]('your npm version is outdated\nPlease update to npm@1.4.5 or greater.\nSee https://github.com/rlidwka/sinopia/issues/93 for details.') ) + return next( Error[422]('your npm version is outdated\nPlease update to npm@1.4.5 or greater.\nSee https://github.com/rlidwka/sinopia/issues/93 for details.') ); } else { - return next( Error[422]('user/password is not found in request (npm issue?)') ) + return next( Error[422]('user/password is not found in request (npm issue?)') ); } } auth.add_user(req.body.name, req.body.password, function(err, user) { @@ -227,133 +248,130 @@ module.exports = function(config, auth, storage) { // With npm registering is the same as logging in, // and npm accepts only an 409 error. // So, changing status code here. - return next( Error[409](err.message) ) + return next( Error[409](err.message) ); } - return next(err) + return next(err); } - req.remote_user = user - res.status(201) + req.remote_user = user; + res.status(201); return next({ - ok: "user '" + req.body.name + "' created", - //token: auth.issue_token(req.remote_user), + ok: 'user \'' + req.body.name + '\' created', + // token: auth.issue_token(req.remote_user), token: token, - }) - }) + }); + }); } - }) + }); app.delete('/-/user/token/*', function(req, res, next) { - res.status(200) + res.status(200); next({ ok: 'Logged out', - }) - }) + }); + }); function tag_package_version(req, res, next) { - if (typeof(req.body) !== 'string') return next('route') + if (typeof(req.body) !== 'string') return next('route'); - var tags = {} - tags[req.params.tag] = req.body + let tags = {}; + tags[req.params.tag] = req.body; storage.merge_tags(req.params.package, tags, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'package tagged' }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'package tagged'}); + }); } // tagging a package app.put('/:package/:tag', - can('publish'), media('application/json'), tag_package_version) + can('publish'), media('application/json'), tag_package_version); app.post('/-/package/:package/dist-tags/:tag', - can('publish'), media('application/json'), tag_package_version) + can('publish'), media('application/json'), tag_package_version); app.put('/-/package/:package/dist-tags/:tag', - can('publish'), media('application/json'), tag_package_version) + can('publish'), media('application/json'), tag_package_version); - app.delete('/-/package/:package/dist-tags/:tag', can('publish'), function (req, res, next) { - var tags = {} - tags[req.params.tag] = null + app.delete('/-/package/:package/dist-tags/:tag', can('publish'), function(req, res, next) { + let tags = {}; + tags[req.params.tag] = null; storage.merge_tags(req.params.package, tags, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'tag removed' }) - }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'tag removed'}); + }); + }); app.get('/-/package/:package/dist-tags', can('access'), function(req, res, next) { - storage.get_package(req.params.package, { req: req }, function(err, info) { - if (err) return next(err) + storage.get_package(req.params.package, {req: req}, function(err, info) { + if (err) return next(err); - next(info['dist-tags']) - }) - }) + next(info['dist-tags']); + }); + }); app.post('/-/package/:package/dist-tags', can('publish'), media('application/json'), expect_json, function(req, res, next) { - storage.merge_tags(req.params.package, req.body, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'tags updated' }) - }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'tags updated'}); + }); + }); app.put('/-/package/:package/dist-tags', can('publish'), media('application/json'), expect_json, function(req, res, next) { - storage.replace_tags(req.params.package, req.body, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'tags updated' }) - }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'tags updated'}); + }); + }); app.delete('/-/package/:package/dist-tags', can('publish'), media('application/json'), function(req, res, next) { - storage.replace_tags(req.params.package, {}, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'tags removed' }) - }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'tags removed'}); + }); + }); // publishing a package app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) { - var name = req.params.package + let name = req.params.package; if (Object.keys(req.body).length == 1 && Utils.is_object(req.body.users)) { // 501 status is more meaningful, but npm doesn't show error message for 5xx - return next( Error[404]('npm star|unstar calls are not implemented') ) + return next( Error[404]('npm star|unstar calls are not implemented') ); } try { - var metadata = Utils.validate_metadata(req.body, name) + var metadata = Utils.validate_metadata(req.body, name); } catch(err) { - return next( Error[422]('bad incoming package data') ) + return next( Error[422]('bad incoming package data') ); } if (req.params._rev) { storage.change_package(name, metadata, req.params.revision, function(err) { - after_change(err, 'package changed') - }) + after_change(err, 'package changed'); + }); } else { storage.add_package(name, metadata, function(err) { - after_change(err, 'created new package') - }) + after_change(err, 'created new package'); + }); } function after_change(err, ok_message) { // old npm behaviour if (metadata._attachments == null) { - if (err) return next(err) - res.status(201) - return next({ ok: ok_message }) + if (err) return next(err); + res.status(201); + return next({ok: ok_message}); } // npm-registry-client 0.3+ embeds tarball into the json upload @@ -361,121 +379,120 @@ module.exports = function(config, auth, storage) { // issue #31, dealing with it here: if (typeof(metadata._attachments) !== 'object' - || Object.keys(metadata._attachments).length !== 1 - || typeof(metadata.versions) !== 'object' - || Object.keys(metadata.versions).length !== 1) { - + || Object.keys(metadata._attachments).length !== 1 + || typeof(metadata.versions) !== 'object' + || Object.keys(metadata.versions).length !== 1) { // npm is doing something strange again // if this happens in normal circumstances, report it as a bug - return next( Error[400]('unsupported registry call') ) + return next( Error[400]('unsupported registry call') ); } - if (err && err.status != 409) return next(err) + if (err && err.status != 409) return next(err); // at this point document is either created or existed before - var t1 = Object.keys(metadata._attachments)[0] + let t1 = Object.keys(metadata._attachments)[0]; create_tarball(Path.basename(t1), metadata._attachments[t1], function(err) { - if (err) return next(err) + if (err) return next(err); - var t2 = Object.keys(metadata.versions)[0] - metadata.versions[t2].readme = metadata.readme != null ? String(metadata.readme) : '' + let t2 = Object.keys(metadata.versions)[0]; + metadata.versions[t2].readme = metadata.readme != null ? String(metadata.readme) : ''; create_version(t2, metadata.versions[t2], function(err) { - if (err) return next(err) + if (err) return next(err); add_tags(metadata['dist-tags'], function(err) { - if (err) return next(err) - notify(metadata, config) - res.status(201) - return next({ ok: ok_message }) - }) - }) - }) + if (err) return next(err); + notify(metadata, config); + res.status(201); + return next({ok: ok_message}); + }); + }); + }); } function create_tarball(filename, data, cb) { - var stream = storage.add_tarball(name, filename) + let stream = storage.add_tarball(name, filename); stream.on('error', function(err) { - cb(err) - }) + cb(err); + }); stream.on('success', function() { - cb() - }) + cb(); + }); // this is dumb and memory-consuming, but what choices do we have? - stream.end(new Buffer(data.data, 'base64')) - stream.done() + stream.end(new Buffer(data.data, 'base64')); + stream.done(); } function create_version(version, data, cb) { - storage.add_version(name, version, data, null, cb) + storage.add_version(name, version, data, null, cb); } function add_tags(tags, cb) { - storage.merge_tags(name, tags, cb) + storage.merge_tags(name, tags, cb); } - }) + }); // unpublishing an entire package app.delete('/:package/-rev/*', can('publish'), function(req, res, next) { storage.remove_package(req.params.package, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'package removed' }) - }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'package removed'}); + }); + }); // removing a tarball app.delete('/:package/-/:filename/-rev/:revision', can('publish'), function(req, res, next) { storage.remove_tarball(req.params.package, req.params.filename, req.params.revision, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'tarball removed' }) - }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'tarball removed'}); + }); + }); // uploading package tarball app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) { - var name = req.params.package + let name = req.params.package; - var stream = storage.add_tarball(name, req.params.filename) - req.pipe(stream) + let stream = storage.add_tarball(name, req.params.filename); + req.pipe(stream); // checking if end event came before closing - var complete = false + let complete = false; req.on('end', function() { - complete = true - stream.done() - }) + complete = true; + stream.done(); + }); req.on('close', function() { if (!complete) { - stream.abort() + stream.abort(); } - }) + }); stream.on('error', function(err) { - return res.report_error(err) - }) + return res.report_error(err); + }); stream.on('success', function() { - res.status(201) + res.status(201); return next({ - ok: 'tarball uploaded successfully' - }) - }) - }) + ok: 'tarball uploaded successfully', + }); + }); + }); // adding a version app.put('/:package/:version/-tag/:tag', can('publish'), media('application/json'), expect_json, function(req, res, next) { - var name = req.params.package - var version = req.params.version - var tag = req.params.tag + let name = req.params.package; + let version = req.params.version; + let tag = req.params.tag; storage.add_version(name, version, req.body, tag, function(err) { - if (err) return next(err) - res.status(201) - return next({ ok: 'package published' }) - }) - }) + if (err) return next(err); + res.status(201); + return next({ok: 'package published'}); + }); + }); - return app -} + return app; +}; diff --git a/lib/index-web.js b/lib/index-web.js index 22b9a36dc..3c2c92644 100644 --- a/lib/index-web.js +++ b/lib/index-web.js @@ -1,167 +1,167 @@ -var async = require('async') -var bodyParser = require('body-parser') -var Cookies = require('cookies') -var express = require('express') -var fs = require('fs') -var Handlebars = require('handlebars') -var renderReadme = require('render-readme') -var Search = require('./search') -var Middleware = require('./middleware') -var match = Middleware.match -var validate_name = Middleware.validate_name -var validate_pkg = Middleware.validate_package +'use strict'; + +let async = require('async'); +let bodyParser = require('body-parser'); +let Cookies = require('cookies'); +let express = require('express'); +let fs = require('fs'); +let Handlebars = require('handlebars'); +let renderReadme = require('render-readme'); +let Search = require('./search'); +let Middleware = require('./middleware'); +let match = Middleware.match; +let validate_name = Middleware.validate_name; +let validate_pkg = Middleware.validate_package; module.exports = function(config, auth, storage) { - var app = express.Router() - var can = Middleware.allow(auth) + let app = express.Router(); + let can = Middleware.allow(auth); // validate all of these params as a package name // this might be too harsh, so ask if it causes trouble - app.param('package', validate_pkg) - app.param('filename', validate_name) - app.param('version', validate_name) - app.param('anything', match(/.*/)) + app.param('package', validate_pkg); + app.param('filename', validate_name); + app.param('version', validate_name); + app.param('anything', match(/.*/)); - app.use(Cookies.express()) - app.use(bodyParser.urlencoded({ extended: false })) - app.use(auth.cookie_middleware()) + app.use(Cookies.express()); + app.use(bodyParser.urlencoded({extended: false})); + app.use(auth.cookie_middleware()); app.use(function(req, res, next) { // disable loading in frames (clickjacking, etc.) - res.header('X-Frame-Options', 'deny') - next() - }) + res.header('X-Frame-Options', 'deny'); + next(); + }); - Search.configureStorage(storage) + Search.configureStorage(storage); - Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8')) - - if(config.web && config.web.template) { - var template = Handlebars.compile(fs.readFileSync(config.web.template, 'utf8')); - } - else { - var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8')) + Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8')); + let template; + if (config.web && config.web.template) { + template = Handlebars.compile(fs.readFileSync(config.web.template, 'utf8')); + } else { + template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8')); } app.get('/', function(req, res, next) { - var base = config.url_prefix + let base = config.url_prefix ? config.url_prefix.replace(/\/$/, '') - : req.protocol + '://' + req.get('host') - res.setHeader('Content-Type', 'text/html') + : req.protocol + '://' + req.get('host'); + res.setHeader('Content-Type', 'text/html'); storage.get_local(function(err, packages) { - if (err) throw err // that function shouldn't produce any - async.filterSeries(packages, function(package, cb) { - auth.allow_access(package.name, req.remote_user, function(err, allowed) { - setImmediate(function () { + if (err) throw err; // that function shouldn't produce any + async.filterSeries(packages, function(pkg, cb) { + auth.allow_access(pkg.name, req.remote_user, function(err, allowed) { + setImmediate(function() { if (err) { cb(null, false); } else { - cb(err, allowed) + cb(err, allowed); } - }) - }) + }); + }); }, function(err, packages) { - if (err) throw err + if (err) throw err; packages.sort(function(p1, p2) { if (p1.name < p2.name) { return -1; - } - else { + } else { return 1; } }); next(template({ - name: config.web && config.web.title ? config.web.title : 'Verdaccio', - tagline: config.web && config.web.tagline ? config.web.tagline : '', - packages: packages, - baseUrl: base, - username: req.remote_user.name, - })) - }) - }) - }) + name: config.web && config.web.title ? config.web.title : 'Verdaccio', + tagline: config.web && config.web.tagline ? config.web.tagline : '', + packages: packages, + baseUrl: base, + username: req.remote_user.name, + })); + }); + }); + }); // Static app.get('/-/static/:filename', function(req, res, next) { - var file = __dirname + '/static/' + req.params.filename + let file = __dirname + '/static/' + req.params.filename; res.sendFile(file, function(err) { - if (!err) return + if (!err) return; if (err.status === 404) { - next() + next(); } else { - next(err) + next(err); } - }) - }) + }); + }); app.get('/-/logo', function(req, res, next) { res.sendFile( config.web && config.web.logo ? config.web.logo - : __dirname + '/static/logo-sm.png' ) - }) + : __dirname + '/static/logo-sm.png' ); + }); app.post('/-/login', function(req, res, next) { auth.authenticate(req.body.user, req.body.pass, function(err, user) { if (!err) { - req.remote_user = user - //res.cookies.set('token', auth.issue_token(req.remote_user)) + req.remote_user = user; + // res.cookies.set('token', auth.issue_token(req.remote_user)) - var str = req.body.user + ':' + req.body.pass - res.cookies.set('token', auth.aes_encrypt(str).toString('base64')) + let str = req.body.user + ':' + req.body.pass; + res.cookies.set('token', auth.aes_encrypt(str).toString('base64')); } - var base = config.url_prefix + let base = config.url_prefix ? config.url_prefix.replace(/\/$/, '') - : req.protocol + '://' + req.get('host') - res.redirect(base) - }) - }) + : req.protocol + '://' + req.get('host'); + res.redirect(base); + }); + }); app.post('/-/logout', function(req, res, next) { - var base = config.url_prefix + let base = config.url_prefix ? config.url_prefix.replace(/\/$/, '') - : req.protocol + '://' + req.get('host') - res.cookies.set('token', '') - res.redirect(base) - }) + : req.protocol + '://' + req.get('host'); + res.cookies.set('token', ''); + res.redirect(base); + }); // Search app.get('/-/search/:anything', function(req, res, next) { - var results = Search.query(req.params.anything) - var packages = [] + const results = Search.query(req.params.anything); + const packages = []; - var getData = function(i) { + const getData = function(i) { storage.get_package(results[i].ref, function(err, entry) { if (!err && entry) { auth.allow_access(entry.name, req.remote_user, function(err, allowed) { // TODO: This may cause performance issue? - if (err || !allowed) return + if (err || !allowed) return; - packages.push(entry.versions[entry['dist-tags'].latest]) - }) + packages.push(entry.versions[entry['dist-tags'].latest]); + }); } if (i >= results.length - 1) { - next(packages) + next(packages); } else { - getData(i + 1) + getData(i + 1); } - }) - } + }); + }; if (results.length) { - getData(0) + getData(0); } else { - next([]) + next([]); } - }) + }); app.get('/-/readme(/@:scope?)?/:package/:version?', can('access'), function(req, res, next) { - var packageName = req.params.package; - if (req.params.scope) packageName = "@"+ req.params.scope + "/" + packageName; + let packageName = req.params.package; + if (req.params.scope) packageName = '@'+ req.params.scope + '/' + packageName; storage.get_package(packageName, {req: req}, function(err, info) { - if (err) return next(err) - next( renderReadme(info.readme || 'ERROR: No README data found!') ) - }) - }) - return app -} + if (err) return next(err); + next( renderReadme(info.readme || 'ERROR: No README data found!') ); + }); + }); + return app; +}; diff --git a/lib/index.js b/lib/index.js index c669fc01c..f244a1653 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,105 +1,107 @@ -var express = require('express') -var Error = require('http-errors') -var compression = require('compression') -var Auth = require('./auth') -var Logger = require('./logger') -var Config = require('./config') -var Middleware = require('./middleware') -var Cats = require('./status-cats') -var Storage = require('./storage') +'use strict'; + +let express = require('express'); +let Error = require('http-errors'); +let compression = require('compression'); +let Auth = require('./auth'); +let Logger = require('./logger'); +let Config = require('./config'); +let Middleware = require('./middleware'); +let Cats = require('./status-cats'); +let Storage = require('./storage'); module.exports = function(config_hash) { - Logger.setup(config_hash.logs) + Logger.setup(config_hash.logs); - var config = Config(config_hash); - var storage = new Storage(config); - var auth = Auth(config); - var app = express(); + let config = Config(config_hash); + let storage = new Storage(config); + let auth = Auth(config); + let app = express(); // run in production mode by default, just in case // it shouldn't make any difference anyway - app.set('env', process.env.NODE_ENV || 'production') + app.set('env', process.env.NODE_ENV || 'production'); function error_reporting_middleware(req, res, next) { res.report_error = res.report_error || function(err) { if (err.status && err.status >= 400 && err.status < 600) { if (!res.headersSent) { - res.status(err.status) - next({ error: err.message || 'unknown error' }) + res.status(err.status); + next({error: err.message || 'unknown error'}); } } else { - Logger.logger.error( { err: err } - , 'unexpected error: @{!err.message}\n@{err.stack}') + Logger.logger.error( {err: err} + , 'unexpected error: @{!err.message}\n@{err.stack}'); if (!res.status || !res.send) { - Logger.logger.error('this is an error in express.js, please report this') - res.destroy() + Logger.logger.error('this is an error in express.js, please report this'); + res.destroy(); } else if (!res.headersSent) { - res.status(500) - next({ error: 'internal server error' }) + res.status(500); + next({error: 'internal server error'}); } else { // socket should be already closed } } - } - next() + }; + next(); } - app.use(Middleware.log) - app.use(error_reporting_middleware) + app.use(Middleware.log); + app.use(error_reporting_middleware); app.use(function(req, res, next) { - res.setHeader('X-Powered-By', config.user_agent) - next() - }) - app.use(Cats.middleware) - app.use(compression()) + res.setHeader('X-Powered-By', config.user_agent); + next(); + }); + app.use(Cats.middleware); + app.use(compression()); app.get('/favicon.ico', function(req, res, next) { - req.url = '/-/static/favicon.png' - next() - }) + req.url = '/-/static/favicon.png'; + next(); + }); // hook for tests only if (config._debug) { app.get('/-/_debug', function(req, res, next) { - var do_gc = typeof(global.gc) !== 'undefined' - if (do_gc) global.gc() + let do_gc = typeof(global.gc) !== 'undefined'; + if (do_gc) global.gc(); next({ - pid : process.pid, - main : process.mainModule.filename, - conf : config.self_path, - mem : process.memoryUsage(), - gc : do_gc, - }) - }) + pid: process.pid, + main: process.mainModule.filename, + conf: config.self_path, + mem: process.memoryUsage(), + gc: do_gc, + }); + }); } - app.use(require('./index-api')(config, auth, storage)) + app.use(require('./index-api')(config, auth, storage)); if (config.web && config.web.enable === false) { app.get('/', function(req, res, next) { - next( Error[404]('web interface is disabled in the config file') ) - }) + next( Error[404]('web interface is disabled in the config file') ); + }); } else { - app.use(require('./index-web')(config, auth, storage)) + app.use(require('./index-web')(config, auth, storage)); } app.get('/*', function(req, res, next) { - next( Error[404]('file not found') ) - }) + next( Error[404]('file not found') ); + }); app.use(function(err, req, res, next) { - if (Object.prototype.toString.call(err) !== '[object Error]') return next(err) - if (err.code === 'ECONNABORT' && res.statusCode === 304) return next() + if (Object.prototype.toString.call(err) !== '[object Error]') return next(err); + if (err.code === 'ECONNABORT' && res.statusCode === 304) return next(); if (typeof(res.report_error) !== 'function') { // in case of very early error this middleware may not be loaded before error is generated // fixing that - error_reporting_middleware(req, res, function(){}) + error_reporting_middleware(req, res, function() {}); } - res.report_error(err) - }) + res.report_error(err); + }); - app.use(Middleware.final) + app.use(Middleware.final); - return app -} + return app; +}; diff --git a/lib/local-data.js b/lib/local-data.js index 8c2fe8899..7da7999a9 100644 --- a/lib/local-data.js +++ b/lib/local-data.js @@ -1,44 +1,44 @@ -"use strict"; +'use strict'; -const fs = require('fs'); +const fs = require('fs'); const Path = require('path'); class LocalData { constructor(path) { - this.path = path + this.path = path; try { - this.data = JSON.parse(fs.readFileSync(this.path, 'utf8')) + this.data = JSON.parse(fs.readFileSync(this.path, 'utf8')); } catch(_) { - this.data = { list: [] } + this.data = {list: []}; } } add(name) { if (this.data.list.indexOf(name) === -1) { - this.data.list.push(name) - this.sync() + this.data.list.push(name); + this.sync(); } } remove(name) { - const i = this.data.list.indexOf(name) + const i = this.data.list.indexOf(name); if (i !== -1) { - this.data.list.splice(i, 1) + this.data.list.splice(i, 1); } - this.sync() + this.sync(); } get() { - return this.data.list + return this.data.list; } sync() { // Uses sync to prevent ugly race condition try { - require('mkdirp').sync(Path.dirname(this.path)) + require('mkdirp').sync(Path.dirname(this.path)); } catch(err) {} - fs.writeFileSync(this.path, JSON.stringify(this.data)) + fs.writeFileSync(this.path, JSON.stringify(this.data)); } } diff --git a/lib/local-fs.js b/lib/local-fs.js index 5128cf877..fd8a64379 100644 --- a/lib/local-fs.js +++ b/lib/local-fs.js @@ -1,215 +1,215 @@ -"use strict"; +'use strict'; -const fs = require('fs') -const Error = require('http-errors') -const mkdirp = require('mkdirp') -const Path = require('path') -const MyStreams = require('./streams') +const fs = require('fs'); +const Error = require('http-errors'); +const mkdirp = require('mkdirp'); +const Path = require('path'); +const MyStreams = require('./streams'); function FSError(code) { - var err = Error(code) - err.code = code - return err + let err = Error(code); + err.code = code; + return err; } -var locker = require('./file-locking') +let locker = require('./file-locking'); function tempFile(str) { - return str + '.tmp' + String(Math.random()).substr(2) + return str + '.tmp' + String(Math.random()).substr(2); } function renameTmp(src, dst, _cb) { function cb(err) { - if (err) fs.unlink(src, function() {}) - _cb(err) + if (err) fs.unlink(src, function() {}); + _cb(err); } if (process.platform !== 'win32') { - return fs.rename(src, dst, cb) + return fs.rename(src, dst, cb); } // windows can't remove opened file, // but it seem to be able to rename it - var tmp = tempFile(dst) + let tmp = tempFile(dst); fs.rename(dst, tmp, function(err) { - fs.rename(src, dst, cb) - if (!err) fs.unlink(tmp, function () {}) - }) + fs.rename(src, dst, cb); + if (!err) fs.unlink(tmp, () => {}); + }); } function write(dest, data, cb) { - var safe_write = function(cb) { - var tmpname = tempFile(dest) + let safe_write = function(cb) { + let tmpname = tempFile(dest); fs.writeFile(tmpname, data, function(err) { - if (err) return cb(err) - renameTmp(tmpname, dest, cb) - }) - } + if (err) return cb(err); + renameTmp(tmpname, dest, cb); + }); + }; safe_write(function(err) { if (err && err.code === 'ENOENT') { mkdirp(Path.dirname(dest), function(err) { - if (err) return cb(err) - safe_write(cb) - }) + if (err) return cb(err); + safe_write(cb); + }); } else { - cb(err) + cb(err); } - }) + }); } function write_stream(name) { - var stream = MyStreams.UploadTarballStream() + let stream = MyStreams.UploadTarballStream(); - var _ended = 0 + let _ended = 0; stream.on('end', function() { - _ended = 1 - }) + _ended = 1; + }); fs.exists(name, function(exists) { - if (exists) return stream.emit('error', FSError('EEXISTS')) + if (exists) return stream.emit('error', FSError('EEXISTS')); - var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '') - var file = fs.createWriteStream(tmpname) - var opened = false - stream.pipe(file) + let tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, ''); + let file = fs.createWriteStream(tmpname); + let opened = false; + stream.pipe(file); stream.done = function() { function onend() { file.on('close', function() { renameTmp(tmpname, name, function(err) { if (err) { - stream.emit('error', err) + stream.emit('error', err); } else { - stream.emit('success') + stream.emit('success'); } - }) - }) - file.destroySoon() + }); + }); + file.destroySoon(); } if (_ended) { - onend() + onend(); } else { - stream.on('end', onend) + stream.on('end', onend); } - } + }; stream.abort = function() { if (opened) { - opened = false + opened = false; file.on('close', function() { - fs.unlink(tmpname, function(){}) - }) + fs.unlink(tmpname, function() {}); + }); } - file.destroySoon() - } + file.destroySoon(); + }; file.on('open', function() { - opened = true + opened = true; // re-emitting open because it's handled in storage.js - stream.emit('open') - }) + stream.emit('open'); + }); file.on('error', function(err) { - stream.emit('error', err) - }) - }) - return stream + stream.emit('error', err); + }); + }); + return stream; } function read_stream(name, stream, callback) { - var rstream = fs.createReadStream(name) + let rstream = fs.createReadStream(name); rstream.on('error', function(err) { - stream.emit('error', err) - }) + stream.emit('error', err); + }); rstream.on('open', function(fd) { fs.fstat(fd, function(err, stats) { - if (err) return stream.emit('error', err) - stream.emit('content-length', stats.size) - stream.emit('open') - rstream.pipe(stream) - }) - }) + if (err) return stream.emit('error', err); + stream.emit('content-length', stats.size); + stream.emit('open'); + rstream.pipe(stream); + }); + }); - stream = MyStreams.ReadTarballStream() + stream = MyStreams.ReadTarballStream(); stream.abort = function() { - rstream.close() - } - return stream + rstream.close(); + }; + return stream; } function create(name, contents, callback) { fs.exists(name, function(exists) { - if (exists) return callback( FSError('EEXISTS') ) - write(name, contents, callback) - }) + if (exists) return callback( FSError('EEXISTS') ); + write(name, contents, callback); + }); } function update(name, contents, callback) { fs.exists(name, function(exists) { - if (!exists) return callback( FSError('ENOENT') ) - write(name, contents, callback) - }) + if (!exists) return callback( FSError('ENOENT') ); + write(name, contents, callback); + }); } function read(name, callback) { - fs.readFile(name, callback) + fs.readFile(name, callback); } -module.exports.read = read +module.exports.read = read; module.exports.read_json = function(name, cb) { read(name, function(err, res) { - if (err) return cb(err) + if (err) return cb(err); - var args = [] + let args = []; try { - args = [ null, JSON.parse(res.toString('utf8')) ] + args = [null, JSON.parse(res.toString('utf8'))]; } catch(err) { - args = [ err ] + args = [err]; } - cb.apply(null, args) - }) -} + cb.apply(null, args); + }); +}; module.exports.lock_and_read = function(name, cb) { locker.readFile(name, {lock: true}, function(err, res) { - if (err) return cb(err) - return cb(null, res) - }) -} + if (err) return cb(err); + return cb(null, res); + }); +}; module.exports.lock_and_read_json = function(name, cb) { locker.readFile(name, {lock: true, parse: true}, function(err, res) { - if (err) return cb(err) + if (err) return cb(err); return cb(null, res); - }) -} + }); +}; -module.exports.unlock_file = function (name, cb) { - locker.unlockFile(name, cb) -} +module.exports.unlock_file = function(name, cb) { + locker.unlockFile(name, cb); +}; -module.exports.create = create +module.exports.create = create; module.exports.create_json = function(name, value, cb) { - create(name, JSON.stringify(value, null, '\t'), cb) -} + create(name, JSON.stringify(value, null, '\t'), cb); +}; -module.exports.update = update +module.exports.update = update; module.exports.update_json = function(name, value, cb) { - update(name, JSON.stringify(value, null, '\t'), cb) -} + update(name, JSON.stringify(value, null, '\t'), cb); +}; -module.exports.write = write +module.exports.write = write; module.exports.write_json = function(name, value, cb) { - write(name, JSON.stringify(value, null, '\t'), cb) -} + write(name, JSON.stringify(value, null, '\t'), cb); +}; -module.exports.write_stream = write_stream +module.exports.write_stream = write_stream; -module.exports.read_stream = read_stream +module.exports.read_stream = read_stream; -module.exports.unlink = fs.unlink +module.exports.unlink = fs.unlink; -module.exports.rmdir = fs.rmdir +module.exports.rmdir = fs.rmdir; diff --git a/lib/local-storage.js b/lib/local-storage.js index f6a98f9f9..b9b227b58 100644 --- a/lib/local-storage.js +++ b/lib/local-storage.js @@ -1,34 +1,34 @@ -"use strict"; +'use strict'; -const assert = require('assert'); -const async = require('async'); -const Crypto = require('crypto'); -const fs = require('fs'); -const Error = require('http-errors'); -const Path = require('path'); -const Stream = require('readable-stream'); -const URL = require('url'); +const assert = require('assert'); +const async = require('async'); +const Crypto = require('crypto'); +const fs = require('fs'); +const Error = require('http-errors'); +const Path = require('path'); +const Stream = require('readable-stream'); +const URL = require('url'); const fs_storage = require('./local-fs'); -const Logger = require('./logger'); -const Search = require('./search'); -const MyStreams = require('./streams'); -const Utils = require('./utils'); -const info_file = 'package.json'; +const Logger = require('./logger'); +const Search = require('./search'); +const MyStreams = require('./streams'); +const Utils = require('./utils'); +const info_file = 'package.json'; // returns the minimal package file function get_boilerplate(name) { return { // standard things - name: name, - versions: {}, + 'name': name, + 'versions': {}, 'dist-tags': {}, // our own object '_distfiles': {}, '_attachments': {}, '_uplinks': {}, - } -}; + }; +} // // Implements Storage interface @@ -36,8 +36,8 @@ function get_boilerplate(name) { // class Storage { constructor(config) { - this.config = config - this.logger = Logger.logger.child({ sub: 'fs' }) + this.config = config; + this.logger = Logger.logger.child({sub: 'fs'}); } /** @@ -47,9 +47,9 @@ class Storage { * @param {*} message */ _internal_error(err, file, message) { - this.logger.error( { err: err, file: file } - , message + ' @{file}: @{!err.message}' ) - return Error[500]() + this.logger.error( {err: err, file: file} + , message + ' @{file}: @{!err.message}' ); + return Error[500](); } /** @@ -59,20 +59,20 @@ class Storage { * @param {*} callback */ add_package(name, info, callback) { - var storage = this.storage(name) - if (!storage) return callback( Error[404]('this package cannot be added') ) + let storage = this.storage(name); + if (!storage) return callback( Error[404]('this package cannot be added') ); storage.create_json(info_file, get_boilerplate(name), function(err) { if (err && err.code === 'EEXISTS') { - return callback( Error[409]('this package is already present') ) + return callback( Error[409]('this package is already present') ); } - var latest = info['dist-tags'].latest + let latest = info['dist-tags'].latest; if (latest && info.versions[latest]) { - Search.add(info.versions[latest]) + Search.add(info.versions[latest]); } - callback() - }) + callback(); + }); } /** @@ -81,47 +81,47 @@ class Storage { * @param {*} callback */ remove_package(name, callback) { - this.logger.info( { name: name } - , 'unpublishing @{name} (all)') + this.logger.info( {name: name} + , 'unpublishing @{name} (all)'); - var storage = this.storage(name) - if (!storage) return callback( Error[404]('no such package available') ) + let storage = this.storage(name); + if (!storage) return callback( Error[404]('no such package available') ); storage.read_json(info_file, (err, data) => { if (err) { if (err.code === 'ENOENT') { - return callback( Error[404]('no such package available') ) + return callback( Error[404]('no such package available') ); } else { - return callback(err) + return callback(err); } } - this._normalize_package(data) + this._normalize_package(data); storage.unlink(info_file, function(err) { - if (err) return callback(err) + if (err) return callback(err); - var files = Object.keys(data._attachments) + let files = Object.keys(data._attachments); function unlinkNext(cb) { - if (files.length === 0) return cb() + if (files.length === 0) return cb(); - var file = files.shift() + let file = files.shift(); storage.unlink(file, function() { - unlinkNext(cb) - }) + unlinkNext(cb); + }); } unlinkNext(function() { // try to unlink the directory, but ignore errors because it can fail storage.rmdir('.', function(err) { - callback(err) - }) - }) - }) - }) + callback(err); + }); + }); + }); + }); - Search.remove(name) - this.config.localList.remove(name) + Search.remove(name); + this.config.localList.remove(name); } /** @@ -130,25 +130,25 @@ class Storage { * @param {*} callback */ _read_create_package(name, callback) { - var storage = this.storage(name) + let storage = this.storage(name); if (!storage) { - var data = get_boilerplate(name) - this._normalize_package(data) - return callback(null, data) + let data = get_boilerplate(name); + this._normalize_package(data); + return callback(null, data); } storage.read_json(info_file, (err, data) => { // TODO: race condition if (err) { if (err.code === 'ENOENT') { // if package doesn't exist, we create it here - data = get_boilerplate(name) + data = get_boilerplate(name); } else { - return callback(this._internal_error(err, info_file, 'error reading')) + return callback(this._internal_error(err, info_file, 'error reading')); } } - this._normalize_package(data) - callback(null, data) - }) + this._normalize_package(data); + callback(null, data); + }); } /** @@ -159,76 +159,76 @@ class Storage { */ update_versions(name, newdata, callback) { this._read_create_package(name, (err, data) => { - if (err) return callback(err) + if (err) return callback(err); - var change = false - for (var ver in newdata.versions) { + let change = false; + for (let ver in newdata.versions) { if (data.versions[ver] == null) { - var verdata = newdata.versions[ver] + let verdata = newdata.versions[ver]; // we don't keep readmes for package versions, // only one readme per package - delete verdata.readme + delete verdata.readme; - change = true - data.versions[ver] = verdata + change = true; + data.versions[ver] = verdata; if (verdata.dist && verdata.dist.tarball) { - var filename = URL.parse(verdata.dist.tarball).pathname.replace(/^.*\//, '') + let filename = URL.parse(verdata.dist.tarball).pathname.replace(/^.*\//, ''); // we do NOT overwrite any existing records if (data._distfiles[filename] == null) { - var hash = data._distfiles[filename] = { + let hash = data._distfiles[filename] = { url: verdata.dist.tarball, sha: verdata.dist.shasum, - } + }; // if (verdata[Symbol('_verdaccio_uplink')]) { if (verdata._verdaccio_uplink) { // if we got this information from a known registry, // use the same protocol for the tarball // // see https://github.com/rlidwka/sinopia/issues/166 - var tarball_url = URL.parse(hash.url) - var uplink_url = URL.parse(this.config.uplinks[verdata._verdaccio_uplink].url) + let tarball_url = URL.parse(hash.url); + let uplink_url = URL.parse(this.config.uplinks[verdata._verdaccio_uplink].url); if (uplink_url.host === tarball_url.host) { - tarball_url.protocol = uplink_url.protocol - hash.registry = verdata._verdaccio_uplink - hash.url = URL.format(tarball_url) + tarball_url.protocol = uplink_url.protocol; + hash.registry = verdata._verdaccio_uplink; + hash.url = URL.format(tarball_url); } } } } } } - for (var tag in newdata['dist-tags']) { + for (let tag in newdata['dist-tags']) { if (!data['dist-tags'][tag] || data['dist-tags'][tag] !== newdata['dist-tags'][tag]) { - change = true - data['dist-tags'][tag] = newdata['dist-tags'][tag] + change = true; + data['dist-tags'][tag] = newdata['dist-tags'][tag]; } } - for (var up in newdata._uplinks) { - var need_change = !Utils.is_object(data._uplinks[up]) + for (let up in newdata._uplinks) { + let need_change = !Utils.is_object(data._uplinks[up]) || newdata._uplinks[up].etag !== data._uplinks[up].etag - || newdata._uplinks[up].fetched !== data._uplinks[up].fetched + || newdata._uplinks[up].fetched !== data._uplinks[up].fetched; if (need_change) { - change = true - data._uplinks[up] = newdata._uplinks[up] + change = true; + data._uplinks[up] = newdata._uplinks[up]; } } if (newdata.readme !== data.readme) { - data.readme = newdata.readme - change = true + data.readme = newdata.readme; + change = true; } if (change) { - this.logger.debug('updating package info') + this.logger.debug('updating package info'); this._write_package(name, data, function(err) { - callback(err, data) - }) + callback(err, data); + }); } else { - callback(null, data) + callback(null, data); } - }) + }); } /** @@ -242,33 +242,33 @@ class Storage { add_version(name, version, metadata, tag, callback) { this.update_package(name, (data, cb) => { // keep only one readme per package - data.readme = metadata.readme - delete metadata.readme + data.readme = metadata.readme; + delete metadata.readme; if (data.versions[version] != null) { - return cb( Error[409]('this version already present') ) + return cb( Error[409]('this version already present') ); } // if uploaded tarball has a different shasum, it's very likely that we have some kind of error if (Utils.is_object(metadata.dist) && typeof(metadata.dist.tarball) === 'string') { - var tarball = metadata.dist.tarball.replace(/.*\//, '') + let tarball = metadata.dist.tarball.replace(/.*\//, ''); if (Utils.is_object(data._attachments[tarball])) { if (data._attachments[tarball].shasum != null && metadata.dist.shasum != null) { if (data._attachments[tarball].shasum != metadata.dist.shasum) { return cb( Error[400]('shasum error, ' + data._attachments[tarball].shasum - + ' != ' + metadata.dist.shasum) ) + + ' != ' + metadata.dist.shasum) ); } } - data._attachments[tarball].version = version + data._attachments[tarball].version = version; } } - data.versions[version] = metadata - Utils.tag_version(data, version, tag) - this.config.localList.add(name) - cb() - }, callback) + data.versions[version] = metadata; + Utils.tag_version(data, version, tag); + this.config.localList.add(name); + cb(); + }, callback); } /** @@ -281,18 +281,18 @@ class Storage { this.update_package(name, function updater(data, cb) { for (let t in tags) { if (tags[t] === null) { - delete data['dist-tags'][t] - continue + delete data['dist-tags'][t]; + continue; } if (data.versions[tags[t]] == null) { - return cb( Error[404]("this version doesn't exist") ) + return cb( Error[404]('this version doesn\'t exist') ); } - Utils.tag_version(data, tags[t], t) + Utils.tag_version(data, tags[t], t); } - cb() - }, callback) + cb(); + }, callback); } /** @@ -303,22 +303,22 @@ class Storage { */ replace_tags(name, tags, callback) { this.update_package(name, function updater(data, cb) { - data['dist-tags'] = {} + data['dist-tags'] = {}; for (let t in tags) { if (tags[t] === null) { - delete data['dist-tags'][t] - continue + delete data['dist-tags'][t]; + continue; } if (data.versions[tags[t]] == null) { - return cb( Error[404]("this version doesn't exist") ) + return cb( Error[404]('this version doesn\'t exist') ); } - Utils.tag_version(data, tags[t], t) + Utils.tag_version(data, tags[t], t); } - cb() - }, callback) + cb(); + }, callback); } /** @@ -329,31 +329,30 @@ class Storage { * @param {*} callback */ change_package(name, metadata, revision, callback) { - if (!Utils.is_object(metadata.versions) || !Utils.is_object(metadata['dist-tags'])) { - return callback( Error[422]('bad data') ) + return callback( Error[422]('bad data') ); } this.update_package(name, (data, cb) => { for (let ver in data.versions) { if (metadata.versions[ver] == null) { - this.logger.info( { name: name, version: ver } - , 'unpublishing @{name}@@{version}') - delete data.versions[ver] + this.logger.info( {name: name, version: ver} + , 'unpublishing @{name}@@{version}'); + delete data.versions[ver]; - for (var file in data._attachments) { + for (let file in data._attachments) { if (data._attachments[file].version === ver) { - delete data._attachments[file].version + delete data._attachments[file].version; } } } } - data['dist-tags'] = metadata['dist-tags'] - cb() + data['dist-tags'] = metadata['dist-tags']; + cb(); }, function(err) { - if (err) return callback(err) - callback() - }) + if (err) return callback(err); + callback(); + }); } /** @@ -364,20 +363,20 @@ class Storage { * @param {*} callback */ remove_tarball(name, filename, revision, callback) { - assert(Utils.validate_name(filename)) + assert(Utils.validate_name(filename)); this.update_package(name, (data, cb) => { if (data._attachments[filename]) { - delete data._attachments[filename] - cb() + delete data._attachments[filename]; + cb(); } else { - cb(Error[404]('no such file available')) + cb(Error[404]('no such file available')); } }, function(err) { - if (err) return callback(err) - var storage = this.storage(name) - if (storage) storage.unlink(filename, callback) - }) + if (err) return callback(err); + let storage = this.storage(name); + if (storage) storage.unlink(filename, callback); + }); } /** @@ -386,87 +385,87 @@ class Storage { * @param {*} filename */ add_tarball(name, filename) { - assert(Utils.validate_name(filename)) + assert(Utils.validate_name(filename)); - var stream = MyStreams.UploadTarballStream() - var _transform = stream._transform - var length = 0 - var shasum = Crypto.createHash('sha1') + let stream = MyStreams.UploadTarballStream(); + let _transform = stream._transform; + let length = 0; + let shasum = Crypto.createHash('sha1'); - stream.abort = stream.done = function(){} + stream.abort = stream.done = function() {}; stream._transform = function(data) { - shasum.update(data) - length += data.length - _transform.apply(stream, arguments) - } + shasum.update(data); + length += data.length; + _transform.apply(stream, arguments); + }; if (name === info_file || name === '__proto__') { process.nextTick(function() { - stream.emit('error', Error[403]("can't use this filename")) - }) - return stream + stream.emit('error', Error[403]('can\'t use this filename')); + }); + return stream; } - var storage = this.storage(name) + let storage = this.storage(name); if (!storage) { process.nextTick(function() { - stream.emit('error', Error[404]("can't upload this package")) - }) - return stream + stream.emit('error', Error[404]('can\'t upload this package')); + }); + return stream; } - var wstream = storage.write_stream(filename) + let wstream = storage.write_stream(filename); wstream.on('error', (err) => { if (err.code === 'EEXISTS') { - stream.emit('error', Error[409]('this tarball is already present')) + stream.emit('error', Error[409]('this tarball is already present')); } else if (err.code === 'ENOENT') { // check if package exists to throw an appropriate message this.get_package(name, function(_err, res) { if (_err) { - stream.emit('error', _err) + stream.emit('error', _err); } else { - stream.emit('error', err) + stream.emit('error', err); } - }) + }); } else { - stream.emit('error', err) + stream.emit('error', err); } - }) + }); wstream.on('open', function() { // re-emitting open because it's handled in storage.js - stream.emit('open') - }) + stream.emit('open'); + }); wstream.on('success', () => { this.update_package(name, function updater(data, cb) { data._attachments[filename] = { shasum: shasum.digest('hex'), - } - cb() + }; + cb(); }, function(err) { if (err) { - stream.emit('error', err) + stream.emit('error', err); } else { - stream.emit('success') + stream.emit('success'); } - }) - }) + }); + }); stream.abort = function() { - wstream.abort() - } + wstream.abort(); + }; stream.done = function() { if (!length) { - stream.emit('error', Error[422]('refusing to accept zero-length file')) - wstream.abort() + stream.emit('error', Error[422]('refusing to accept zero-length file')); + wstream.abort(); } else { - wstream.done() + wstream.done(); } - } - stream.pipe(wstream) + }; + stream.pipe(wstream); - return stream + return stream; } /** @@ -476,39 +475,39 @@ class Storage { * @param {*} callback */ get_tarball(name, filename, callback) { - assert(Utils.validate_name(filename)) - var self = this + assert(Utils.validate_name(filename)); + let self = this; - var stream = MyStreams.ReadTarballStream() + let stream = MyStreams.ReadTarballStream(); stream.abort = function() { - if (rstream) rstream.abort() - } + if (rstream) rstream.abort(); + }; - var storage = self.storage(name) + let storage = self.storage(name); if (!storage) { process.nextTick(function() { - stream.emit('error', Error[404]('no such file available')) - }) - return stream + stream.emit('error', Error[404]('no such file available')); + }); + return stream; } - var rstream = storage.read_stream(filename) + var rstream = storage.read_stream(filename); rstream.on('error', function(err) { if (err && err.code === 'ENOENT') { - stream.emit('error', Error(404, 'no such file available')) + stream.emit('error', Error(404, 'no such file available')); } else { - stream.emit('error', err) + stream.emit('error', err); } - }) + }); rstream.on('content-length', function(v) { - stream.emit('content-length', v) - }) + stream.emit('content-length', v); + }); rstream.on('open', function() { // re-emitting open because it's handled in storage.js - stream.emit('open') - rstream.pipe(stream) - }) - return stream + stream.emit('open'); + rstream.pipe(stream); + }); + return stream; } /** @@ -522,19 +521,20 @@ class Storage { callback = options, options = {}; } - var storage = this.storage(name) - if (!storage) return callback( Error[404]('no such package available') ) + let storage = this.storage(name); + if (!storage) return callback( Error[404]('no such package available') ); + storage.read_json(info_file, (err, result) => { if (err) { if (err.code === 'ENOENT') { - return callback( Error[404]('no such package available') ) + return callback( Error[404]('no such package available') ); } else { - return callback(this._internal_error(err, info_file, 'error reading')) + return callback(this._internal_error(err, info_file, 'error reading')); } } - this._normalize_package(result) - callback(err, result) - }) + this._normalize_package(result); + callback(err, result); + }); } /** @@ -543,50 +543,50 @@ class Storage { * @param {*} on_end */ _each_package(on_package, on_end) { - var storages = {} + let storages = {}; storages[this.config.storage] = true; if (this.config.packages) { - Object.keys(this.packages || {}).map( pkg => { + Object.keys(this.packages || {}).map( (pkg) => { if (this.config.packages[pkg].storage) { - storages[this.config.packages[pkg].storage] = true + storages[this.config.packages[pkg].storage] = true; } - }) + }); } const base = Path.dirname(this.config.self_path); - async.eachSeries(Object.keys(storages), function (storage, cb) { - fs.readdir(Path.resolve(base, storage), function (err, files) { - if (err) return cb(err) + async.eachSeries(Object.keys(storages), function(storage, cb) { + fs.readdir(Path.resolve(base, storage), function(err, files) { + if (err) return cb(err); - async.eachSeries(files, function (file, cb) { + async.eachSeries(files, function(file, cb) { if (file.match(/^@/)) { // scoped - fs.readdir(Path.resolve(base, storage, file), function (err, files) { - if (err) return cb(err) + fs.readdir(Path.resolve(base, storage, file), function(err, files) { + if (err) return cb(err); - async.eachSeries(files, function (file2, cb) { + async.eachSeries(files, function(file2, cb) { if (Utils.validate_name(file2)) { on_package({ name: `${file}/${file2}`, path: Path.resolve(base, storage, file, file2), - }, cb) + }, cb); } else { - cb() + cb(); } - }, cb) - }) + }, cb); + }); } else if (Utils.validate_name(file)) { on_package({ name: file, - path: Path.resolve(base, storage, file) - }, cb) + path: Path.resolve(base, storage, file), + }, cb); } else { - cb() + cb(); } - }, cb) - }) + }, cb); + }); }, on_end); } @@ -604,46 +604,46 @@ class Storage { * @param {*} _callback callback that gets invoked after it's all updated */ update_package(name, updateFn, _callback) { - var self = this - var storage = self.storage(name) - if (!storage) return _callback( Error[404]('no such package available') ) + let self = this; + let storage = self.storage(name); + if (!storage) return _callback( Error[404]('no such package available') ); storage.lock_and_read_json(info_file, function(err, json) { - var locked = false + let locked = false; // callback that cleans up lock first function callback(err) { - var _args = arguments + let _args = arguments; if (locked) { - storage.unlock_file(info_file, function () { + storage.unlock_file(info_file, function() { // ignore any error from the unlock - _callback.apply(err, _args) - }) + _callback.apply(err, _args); + }); } else { - _callback.apply(null, _args) + _callback.apply(null, _args); } } if (!err) { - locked = true + locked = true; } if (err) { if (err.code === 'EAGAIN') { - return callback( Error[503]('resource temporarily unavailable') ) + return callback( Error[503]('resource temporarily unavailable') ); } else if (err.code === 'ENOENT') { - return callback( Error[404]('no such package available') ) + return callback( Error[404]('no such package available') ); } else { - return callback(err) + return callback(err); } } - self._normalize_package(json) + self._normalize_package(json); updateFn(json, function(err) { - if (err) return callback(err) + if (err) return callback(err); - self._write_package(name, json, callback) - }) - }) + self._write_package(name, json, callback); + }); + }); } /** @@ -652,7 +652,7 @@ class Storage { * @param {*} options */ search(startkey, options) { - const stream = new Stream.PassThrough({ objectMode: true }); + const stream = new Stream.PassThrough({objectMode: true}); this._each_package((item, cb) => { fs.stat(item.path, (err, stats) => { @@ -666,75 +666,42 @@ class Storage { return cb(err); } - var versions = Utils.semver_sort(Object.keys(data.versions)) - var latest = data['dist-tags'] && data['dist-tags'].latest ? data['dist-tags'].latest : versions.pop() + let versions = Utils.semver_sort(Object.keys(data.versions)); + let latest = data['dist-tags'] && data['dist-tags'].latest ? data['dist-tags'].latest : versions.pop(); if (data.versions[latest]) { stream.push({ - name : data.versions[latest].name, - description : data.versions[latest].description, - 'dist-tags' : { latest: latest }, - maintainers : data.versions[latest].maintainers || - [ data.versions[latest]._npmUser ].filter(Boolean), - author : data.versions[latest].author, - repository : data.versions[latest].repository, - readmeFilename : data.versions[latest].readmeFilename || '', - homepage : data.versions[latest].homepage, - keywords : data.versions[latest].keywords, - bugs : data.versions[latest].bugs, - license : data.versions[latest].license, - time : { - modified: item.time ? new Date(item.time).toISOString() : undefined + 'name': data.versions[latest].name, + 'description': data.versions[latest].description, + 'dist-tags': {latest: latest}, + 'maintainers': data.versions[latest].maintainers || + [data.versions[latest].author].filter(Boolean), + 'author': data.versions[latest].author, + 'repository': data.versions[latest].repository, + 'readmeFilename': data.versions[latest].readmeFilename || '', + 'homepage': data.versions[latest].homepage, + 'keywords': data.versions[latest].keywords, + 'bugs': data.versions[latest].bugs, + 'license': data.versions[latest].license, + 'time': { + modified: item.time ? new Date(item.time).toISOString() : undefined, }, - versions : {}, - }) + 'versions': {}, + }); } -<<<<<<< HEAD -Storage.prototype.search = function(startkey, options) { - var stream = new Stream.PassThrough({ objectMode: true }) - - this._each_package((item, cb) => { - fs.stat(item.path, (err, stats) => { - if (err) return cb(err) - - if (stats.mtime > startkey) { - this.get_package(item.name, options, function(err, data) { - if (err) return cb(err) - - var versions = Utils.semver_sort(Object.keys(data.versions)) - var latest = data['dist-tags'] && data['dist-tags'].latest ? data['dist-tags'].latest : versions.pop() - - if (data.versions[latest]) { - stream.push({ - name : data.versions[latest].name, - description : data.versions[latest].description, - 'dist-tags' : { latest: latest }, - maintainers : data.versions[latest].maintainers || - [ data.versions[latest].author ].filter(Boolean), - author : data.versions[latest].author, - repository : data.versions[latest].repository, - readmeFilename : data.versions[latest].readmeFilename || '', - homepage : data.versions[latest].homepage, - keywords : data.versions[latest].keywords, - bugs : data.versions[latest].bugs, - license : data.versions[latest].license, - time : { modified: item.time ? new Date(item.time).toISOString() : undefined }, - versions : {}, -======= - cb() ->>>>>>> Migrate storages to classes - }) + cb(); + }); } else { - cb() + cb(); } - }) + }); }, function on_end(err) { - if (err) return stream.emit('error', err) - stream.end() - }) + if (err) return stream.emit('error', err); + stream.end(); + }); - return stream + return stream; } /** @@ -742,14 +709,14 @@ Storage.prototype.search = function(startkey, options) { * @param {*} pkg */ _normalize_package(pkg) { - ;['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks'].forEach(function(key) { - if (!Utils.is_object(pkg[key])) pkg[key] = {} - }) + ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks'].forEach(function(key) { + if (!Utils.is_object(pkg[key])) pkg[key] = {}; + }); if (typeof(pkg._rev) !== 'string') { pkg._rev = '0-0000000000000000'; } // normalize dist-tags - Utils.normalize_dist_tags(pkg) + Utils.normalize_dist_tags(pkg); } /** @@ -759,17 +726,16 @@ Storage.prototype.search = function(startkey, options) { * @param {*} callback */ _write_package(name, json, callback) { - // calculate revision a la couchdb if (typeof(json._rev) !== 'string') { json._rev = '0-0000000000000000'; } - var rev = json._rev.split('-') - json._rev = ((+rev[0] || 0) + 1) + '-' + Crypto.pseudoRandomBytes(8).toString('hex') + let rev = json._rev.split('-'); + json._rev = ((+rev[0] || 0) + 1) + '-' + Crypto.pseudoRandomBytes(8).toString('hex'); - var storage = this.storage(name) - if (!storage) return callback() - storage.write_json(info_file, json, callback) + let storage = this.storage(name); + if (!storage) return callback(); + storage.write_json(info_file, json, callback); } /** @@ -777,47 +743,47 @@ Storage.prototype.search = function(startkey, options) { * @param {*} pkg */ storage(pkg) { - let path = this.config.get_package_spec(pkg).storage + let path = this.config.get_package_spec(pkg).storage; if (path == null) { - path = this.config.storage + path = this.config.storage; } if (path == null || path === false) { - this.logger.debug( { name: pkg } - , 'this package has no storage defined: @{name}' ) - return null + this.logger.debug( {name: pkg} + , 'this package has no storage defined: @{name}' ); + return null; } return Path_Wrapper( Path.join( Path.resolve(Path.dirname(this.config.self_path || ''), path), pkg ) - ) + ); } } var Path_Wrapper = (function() { // a wrapper adding paths to fs_storage methods function Wrapper(path) { - var self = Object.create(Wrapper.prototype) - self.path = path - return self + let self = Object.create(Wrapper.prototype); + self.path = path; + return self; } - for (var i in fs_storage) { + for (let i in fs_storage) { if (fs_storage.hasOwnProperty(i)) { - Wrapper.prototype[i] = wrapper(i) + Wrapper.prototype[i] = wrapper(i); } } function wrapper(method) { - return function(/*...*/) { - var args = Array.prototype.slice.apply(arguments) - args[0] = Path.join(this.path, args[0] || '') - return fs_storage[method].apply(null, args) - } + return function(/* ...*/) { + let args = Array.prototype.slice.apply(arguments); + args[0] = Path.join(this.path, args[0] || ''); + return fs_storage[method].apply(null, args); + }; } - return Wrapper -})() + return Wrapper; +})(); -module.exports = Storage +module.exports = Storage; diff --git a/lib/logger.js b/lib/logger.js index 78a753667..5c7207766 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,157 +1,178 @@ -var Logger = require('bunyan') -var Error = require('http-errors') -var Stream = require('stream') -var Utils = require('./utils') +'use strict'; +const Logger = require('bunyan'); +const Error = require('http-errors'); +const Stream = require('stream'); +const chalk = require('chalk'); +const Utils = require('./utils'); +const pkgJSON = require('../package.json'); + +/** + * + * @param {*} x + */ function getlvl(x) { - switch(true) { - case x < 15 : return 'trace' - case x < 25 : return 'debug' - case x < 35 : return 'info' - case x == 35 : return 'http' - case x < 45 : return 'warn' - case x < 55 : return 'error' - default : return 'fatal' - } + switch(true) { + case x < 15 : return 'trace'; + case x < 25 : return 'debug'; + case x < 35 : return 'info'; + case x == 35 : return 'http'; + case x < 45 : return 'warn'; + case x < 55 : return 'error'; + default : return 'fatal'; + } } module.exports.setup = function(logs) { - var streams = [] - if (logs == null) logs = [{ type: 'stdout', format: 'pretty', level: 'http' }] + let streams = []; + if (logs == null) logs = [{type: 'stdout', format: 'pretty', level: 'http'}]; - logs.forEach(function(target) { - var stream = new Stream() - stream.writable = true + logs.forEach(function(target) { + const stream = new Stream(); + stream.writable = true; - if (target.type === 'stdout' || target.type === 'stderr') { - // destination stream - var dest = target.type === 'stdout' ? process.stdout : process.stderr + if (target.type === 'stdout' || target.type === 'stderr') { + // destination stream + const dest = target.type === 'stdout' ? process.stdout : process.stderr; - if (target.format === 'pretty') { - // making fake stream for prettypritting - stream.write = function(obj) { - dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n') - } - } else if (target.format === 'pretty-timestamped') { - // making fake stream for prettypritting - stream.write = function(obj) { - dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n') - } - } else { - stream.write = function(obj) { - dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n') - } - } - } else if (target.type === 'file') { - var dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'}) - dest.on('error', function (err) { - Logger.emit('error', err) - }) - stream.write = function(obj) { - if (target.format === 'pretty') { - dest.write(print(obj.level, obj.msg, obj, false) + '\n') - } else { - dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n') - } - } - } else { - throw Error('wrong target type for a log') - } + if (target.format === 'pretty') { + // making fake stream for prettypritting + stream.write = function(obj) { + dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n'); + }; + } else if (target.format === 'pretty-timestamped') { + // making fake stream for prettypritting + stream.write = function(obj) { + dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n'); + }; + } else { + stream.write = function(obj) { + dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n'); + }; + } + } else if (target.type === 'file') { + const dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'}); + dest.on('error', function(err) { + Logger.emit('error', err); + }); + stream.write = function(obj) { + if (target.format === 'pretty') { + dest.write(print(obj.level, obj.msg, obj, false) + '\n'); + } else { + dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n'); + } + }; + } else { + throw Error('wrong target type for a log'); + } - if (target.level === 'http') target.level = 35 - streams.push({ - type: 'raw', - level: target.level || 35, - stream: stream, - }) - }) + if (target.level === 'http') target.level = 35; + streams.push({ + type: 'raw', + level: target.level || 35, + stream: stream, + }); + }); - var logger = new Logger({ - name: 'verdaccio', - streams: streams, - serializers: { - err: Logger.stdSerializers.err, - req: Logger.stdSerializers.req, - res: Logger.stdSerializers.res, - }, - }) + let logger = new Logger({ + name: pkgJSON.name, + streams: streams, + serializers: { + err: Logger.stdSerializers.err, + req: Logger.stdSerializers.req, + res: Logger.stdSerializers.res, + }, + }); - module.exports.logger = logger -} + module.exports.logger = logger; +}; // adopted from socket.io // this part was converted to coffee-script and back again over the years, // so it might look weird // level to color -var levels = { - fatal : 31, - error : 31, - warn : 33, - http : 35, - info : 36, - debug : 90, - trace : 90, -} - -var max = 0 -for (var l in levels) { - max = Math.max(max, l.length) +let levels = { + fatal: chalk.red, + error: chalk.red, + warn: chalk.yellow, + http: chalk.magenta, + info: chalk.cyan, + debug: chalk.black, + trace: chalk.white, +}; + +let max = 0; +for (let l in levels) { + max = Math.max(max, l.length); } +/** + * + * @param {*} str + */ function pad(str) { - if (str.length < max) return str + ' '.repeat(max - str.length) - return str + if (str.length < max) return str + ' '.repeat(max - str.length); + return str; } -var subsystems = [{ - in : '\033[32m<--\033[39m', - out : '\033[33m-->\033[39m', - fs : '\033[90m-=-\033[39m', - default : '\033[34m---\033[39m', -}, { - in : '<--', - out : '-->', - fs : '-=-', - default : '---', -}] - +/** + * Build a string + * @param {*} type + * @param {*} msg + * @param {*} obj + * @param {*} colors + */ function print(type, msg, obj, colors) { - if (typeof type === 'number') type = getlvl(type) - var finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) { - var str = obj, is_error - if (name[0] === '!') { - name = name.substr(1) - is_error = true - } + if (typeof type === 'number') type = getlvl(type); + let finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) { + let str = obj; + let is_error; + if (name[0] === '!') { + name = name.substr(1); + is_error = true; + } - var _ref = name.split('.') - for (var _i = 0; _i < _ref.length; _i++) { - var id = _ref[_i] - if (Utils.is_object(str) || Array.isArray(str)) { - str = str[id] - } else { - str = undefined - } - } + let _ref = name.split('.'); + for (let _i = 0; _i < _ref.length; _i++) { + let id = _ref[_i]; + if (Utils.is_object(str) || Array.isArray(str)) { + str = str[id]; + } else { + str = undefined; + } + } - if (typeof(str) === 'string') { - if (!colors || str.includes('\n')) { - return str - } else if (is_error) { - return '\033[31m' + str + '\033[39m' - } else { - return '\033[32m' + str + '\033[39m' - } - } else { - return require('util').inspect(str, null, null, colors) - } - }) + if (typeof(str) === 'string') { + if (!colors || str.includes('\n')) { + return str; + } else if (is_error) { + return chalk.red(str); + } else { + return chalk.green(str); + } + } else { + return require('util').inspect(str, null, null, colors); + } + }); - var sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default - if (colors) { - return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg - } else { - return ' ' + (pad(type)) + ' ' + sub + ' ' + finalmsg - } + const subsystems = [{ + in: chalk.green('<--'), + out: chalk.yellow('-->'), + fs: chalk.black('-=-'), + default: chalk.blue('---'), + }, { + in: '<--', + out: '-->', + fs: '-=-', + default: '---', + }]; + + let sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default; + if (colors) { + // return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg + return ` ${levels[type]((pad(type)))}${chalk.white(`${sub} ${finalmsg}`)}`; + } else { + return ` ${(pad(type))}${sub} ${finalmsg}`; + } } diff --git a/lib/middleware.js b/lib/middleware.js index 16a9ee34f..38f0a3963 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,79 +1,81 @@ -var crypto = require('crypto') -var Error = require('http-errors') -var utils = require('./utils') -var Logger = require('./logger') +'use strict'; + +let crypto = require('crypto'); +let Error = require('http-errors'); +let utils = require('./utils'); +let Logger = require('./logger'); module.exports.match = function match(regexp) { return function(req, res, next, value, name) { if (regexp.exec(value)) { - next() + next(); } else { - next('route') + next('route'); } - } -} + }; +}; module.exports.validate_name = function validate_name(req, res, next, value, name) { if (value.charAt(0) === '-') { // special case in couchdb usually - next('route') + next('route'); } else if (utils.validate_name(value)) { - next() + next(); } else { - next( Error[403]('invalid ' + name) ) + next( Error[403]('invalid ' + name) ); } -} +}; module.exports.validate_package = function validate_package(req, res, next, value, name) { if (value.charAt(0) === '-') { // special case in couchdb usually - next('route') + next('route'); } else if (utils.validate_package(value)) { - next() + next(); } else { - next( Error[403]('invalid ' + name) ) + next( Error[403]('invalid ' + name) ); } -} +}; module.exports.media = function media(expect) { return function(req, res, next) { if (req.headers['content-type'] !== expect) { next( Error[415]('wrong content-type, expect: ' + expect - + ', got: '+req.headers['content-type']) ) + + ', got: '+req.headers['content-type']) ); } else { - next() + next(); } - } -} + }; +}; module.exports.expect_json = function expect_json(req, res, next) { if (!utils.is_object(req.body)) { - return next( Error[400]("can't parse incoming json") ) + return next( Error[400]('can\'t parse incoming json') ); } - next() -} + next(); +}; module.exports.anti_loop = function(config) { return function(req, res, next) { if (req.headers.via != null) { - var arr = req.headers.via.split(',') + let arr = req.headers.via.split(','); - for (var i=0; i<arr.length; i++) { - var m = arr[i].match(/\s*(\S+)\s+(\S+)/) + for (let i=0; i<arr.length; i++) { + let m = arr[i].match(/\s*(\S+)\s+(\S+)/); if (m && m[2] === config.server_id) { - return next( Error[508]('loop detected') ) + return next( Error[508]('loop detected') ); } } } - next() - } -} + next(); + }; +}; // express doesn't do etags with requests <= 1024b // we use md5 here, it works well on 1k+ bytes, but sucks with fewer data // could improve performance using crc32 after benchmarks function md5sum(data) { - return crypto.createHash('md5').update(data).digest('hex') + return crypto.createHash('md5').update(data).digest('hex'); } module.exports.allow = function(auth) { @@ -83,41 +85,41 @@ module.exports.allow = function(auth) { auth['allow_'+action](req.params.package, req.remote_user, function(error, allowed) { req.resume(); if (error) { - next(error) + next(error); } else if (allowed) { - next() + next(); } else { // last plugin (that's our built-in one) returns either // cb(err) or cb(null, true), so this should never happen - throw Error('bug in the auth plugin system') + throw Error('bug in the auth plugin system'); } - }) - } - } -} + }); + }; + }; +}; module.exports.final = function(body, req, res, next) { if (res.statusCode === 401 && !res.getHeader('WWW-Authenticate')) { // they say it's required for 401, so... - res.header('WWW-Authenticate', 'Basic, Bearer') + res.header('WWW-Authenticate', 'Basic, Bearer'); } try { if (typeof(body) === 'string' || typeof(body) === 'object') { if (!res.getHeader('Content-type')) { - res.header('Content-type', 'application/json') + res.header('Content-type', 'application/json'); } if (typeof(body) === 'object' && body != null) { if (typeof(body.error) === 'string') { - res._verdaccio_error = body.error + res._verdaccio_error = body.error; } - body = JSON.stringify(body, undefined, ' ') + '\n' + body = JSON.stringify(body, undefined, ' ') + '\n'; } // don't send etags with errors if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) { - res.header('ETag', '"' + md5sum(body) + '"') + res.header('ETag', '"' + md5sum(body) + '"'); } } else { // send(null), send(204), etc. @@ -127,77 +129,77 @@ module.exports.final = function(body, req, res, next) { // as an error handler, we can't report error properly, // and should just close socket if (err.message.match(/set headers after they are sent/)) { - if (res.socket != null) res.socket.destroy() - return + if (res.socket != null) res.socket.destroy(); + return; } else { - throw err + throw err; } } - res.send(body) -} + res.send(body); +}; module.exports.log = function(req, res, next) { // logger - req.log = Logger.logger.child({ sub: 'in' }) + req.log = Logger.logger.child({sub: 'in'}); - var _auth = req.headers.authorization - if (_auth != null) req.headers.authorization = '<Classified>' - var _cookie = req.headers.cookie - if (_cookie != null) req.headers.cookie = '<Classified>' + let _auth = req.headers.authorization; + if (_auth != null) req.headers.authorization = '<Classified>'; + let _cookie = req.headers.cookie; + if (_cookie != null) req.headers.cookie = '<Classified>'; - req.url = req.originalUrl - req.log.info( { req: req, ip: req.ip } - , '@{ip} requested \'@{req.method} @{req.url}\'' ) - req.originalUrl = req.url + req.url = req.originalUrl; + req.log.info( {req: req, ip: req.ip} + , '@{ip} requested \'@{req.method} @{req.url}\'' ); + req.originalUrl = req.url; - if (_auth != null) req.headers.authorization = _auth - if (_cookie != null) req.headers.cookie = _cookie + if (_auth != null) req.headers.authorization = _auth; + if (_cookie != null) req.headers.cookie = _cookie; - var bytesin = 0 + let bytesin = 0; req.on('data', function(chunk) { - bytesin += chunk.length - }) + bytesin += chunk.length; + }); - var bytesout = 0 - var _write = res.write + let bytesout = 0; + let _write = res.write; res.write = function(buf) { - bytesout += buf.length - _write.apply(res, arguments) - } + bytesout += buf.length; + _write.apply(res, arguments); + }; function log() { - var message = "@{status}, user: @{user}, req: '@{request.method} @{request.url}'" + let message = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\''; if (res._verdaccio_error) { - message += ', error: @{!error}' + message += ', error: @{!error}'; } else { - message += ', bytes: @{bytes.in}/@{bytes.out}' + message += ', bytes: @{bytes.in}/@{bytes.out}'; } - req.url = req.originalUrl + req.url = req.originalUrl; req.log.warn({ - request : { method: req.method, url: req.url }, - level : 35, // http - user : req.remote_user && req.remote_user.name, - status : res.statusCode, - error : res._verdaccio_error, - bytes : { - in : bytesin, - out : bytesout, - } - }, message) - req.originalUrl = req.url + request: {method: req.method, url: req.url}, + level: 35, // http + user: req.remote_user && req.remote_user.name, + status: res.statusCode, + error: res._verdaccio_error, + bytes: { + in: bytesin, + out: bytesout, + }, + }, message); + req.originalUrl = req.url; } req.on('close', function() { - log(true) - }) + log(true); + }); - var _end = res.end + let _end = res.end; res.end = function(buf) { - if (buf) bytesout += buf.length - _end.apply(res, arguments) - log() - } - next() -} + if (buf) bytesout += buf.length; + _end.apply(res, arguments); + log(); + }; + next(); +}; diff --git a/lib/notify.js b/lib/notify.js index c0658d0dd..59b2484d1 100644 --- a/lib/notify.js +++ b/lib/notify.js @@ -1,24 +1,24 @@ -var Handlebars = require('handlebars') -var request = require('request') -var Logger = require('./logger') +'use strict'; + +let Handlebars = require('handlebars'); +let request = require('request'); +let Logger = require('./logger'); module.exports.notify = function(metadata, config) { - if (config.notify && config.notify.content) { + let template = Handlebars.compile(config.notify.content); + let content = template( metadata ); - var template = Handlebars.compile(config.notify.content) - var content = template( metadata ) - - var options = { - body: content - } + let options = { + body: content, + }; // provides fallback support, it's accept an Object {} and Array of {} if ( config.notify.headers && Array.isArray(config.notify.headers) ) { - var header = {}; + let header = {}; config.notify.headers.map(function(item) { if (Object.is(item, item)) { - for (var key in item) { + for (let key in item) { header[key] = item[key]; } } @@ -31,19 +31,18 @@ module.exports.notify = function(metadata, config) { options.method = config.notify.method; if (config.notify.endpoint) { - options.url = config.notify.endpoint + options.url = config.notify.endpoint; } request(options, function(err, response, body) { if (err) { - Logger.logger.error( { err: err }, ' notify error: @{err.message}' ); + Logger.logger.error( {err: err}, ' notify error: @{err.message}' ); } else { - Logger.logger.info({ content: content}, 'A notification has been shipped: @{content}') + Logger.logger.info({content: content}, 'A notification has been shipped: @{content}'); if (body) { - Logger.logger.debug( { body: body }, ' body: @{body}' ); + Logger.logger.debug( {body: body}, ' body: @{body}' ); } } }); - } -} +}; diff --git a/lib/plugin-loader.js b/lib/plugin-loader.js index 97d235d2b..6ff4739e2 100644 --- a/lib/plugin-loader.js +++ b/lib/plugin-loader.js @@ -1,57 +1,59 @@ -var Path = require('path') +'use strict'; + +let Path = require('path'); function try_load(path) { try { - return require(path) + return require(path); } catch(err) { if (err.code === 'MODULE_NOT_FOUND') { - return null + return null; } - throw err + throw err; } } function load_plugins(config, plugin_configs, params, sanity_check) { - var plugins = Object.keys(plugin_configs || {}).map(function(p) { - var plugin + let plugins = Object.keys(plugin_configs || {}).map(function(p) { + let plugin; // try local plugins first - plugin = try_load(Path.resolve(__dirname + '/plugins', p)) + plugin = try_load(Path.resolve(__dirname + '/plugins', p)); // npm package if (plugin === null && p.match(/^[^\.\/]/)) { - plugin = try_load(`verdaccio-${p}`) + plugin = try_load(`verdaccio-${p}`); // compatibility for old sinopia plugins if (!plugin) { - plugin = try_load(`sinopia-${p}`) + plugin = try_load(`sinopia-${p}`); } } if (plugin === null) { - plugin = try_load(p) + plugin = try_load(p); } // relative to config path if (plugin === null && p.match(/^\.\.?($|\/)/)) { - plugin = try_load(Path.resolve(Path.dirname(config.self_path), p)) + plugin = try_load(Path.resolve(Path.dirname(config.self_path), p)); } if (plugin === null) { - throw new Error('"' + p + '" plugin not found\ntry "npm install verdaccio-' + p + '"') + throw Error('"' + p + '" plugin not found\ntry "npm install verdaccio-' + p + '"'); } if (typeof(plugin) !== 'function') - throw new Error('"' + p + '" doesn\'t look like a valid plugin') + throw Error('"' + p + '" doesn\'t look like a valid plugin'); - plugin = plugin(plugin_configs[p], params) + plugin = plugin(plugin_configs[p], params); if (plugin === null || !sanity_check(plugin)) - throw new Error('"' + p + '" doesn\'t look like a valid plugin') + throw Error('"' + p + '" doesn\'t look like a valid plugin'); return plugin; - }) + }); - return plugins + return plugins; } exports.load_plugins = load_plugins; diff --git a/lib/plugins/htpasswd/crypt3.js b/lib/plugins/htpasswd/crypt3.js index d992bcc79..8c0e7a34d 100644 --- a/lib/plugins/htpasswd/crypt3.js +++ b/lib/plugins/htpasswd/crypt3.js @@ -1,3 +1,5 @@ +'use strict'; + /** Node.js Crypt(3) Library Inspired by (and intended to be compatible with) sendanor/crypt3 @@ -10,7 +12,7 @@ */ -var crypt = require('unix-crypt-td-js'), +let crypt = require('unix-crypt-td-js'), crypto = require('crypto'); function createSalt(type) { @@ -33,7 +35,6 @@ function createSalt(type) { default: throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type); } - } function crypt3(key, salt) { diff --git a/lib/plugins/htpasswd/index.js b/lib/plugins/htpasswd/index.js index 69ea5fb48..dc34266b2 100644 --- a/lib/plugins/htpasswd/index.js +++ b/lib/plugins/htpasswd/index.js @@ -1,49 +1,51 @@ -var fs = require('fs') -var Path = require('path') -var utils = require('./utils') +'use strict'; -module.exports = HTPasswd +let fs = require('fs'); +let Path = require('path'); +let utils = require('./utils'); + +module.exports = HTPasswd; function HTPasswd(config, stuff) { - var self = Object.create(HTPasswd.prototype) - self._users = {} + let self = Object.create(HTPasswd.prototype); + self._users = {}; // config for this module - self._config = config + self._config = config; // verdaccio logger - self._logger = stuff.logger + self._logger = stuff.logger; // verdaccio main config object - self._verdaccio_config = stuff.config + self._verdaccio_config = stuff.config; // all this "verdaccio_config" stuff is for b/w compatibility only - self._maxusers = self._config.max_users - if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users + self._maxusers = self._config.max_users; + if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users; // set maxusers to Infinity if not specified - if (!self._maxusers) self._maxusers = Infinity + if (!self._maxusers) self._maxusers = Infinity; - self._last_time = null - var file = self._config.file - if (!file) file = self._verdaccio_config.users_file - if (!file) throw new Error('should specify "file" in config') - self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file) - return self + self._last_time = null; + let file = self._config.file; + if (!file) file = self._verdaccio_config.users_file; + if (!file) throw new Error('should specify "file" in config'); + self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file); + return self; } -HTPasswd.prototype.authenticate = function (user, password, cb) { - var self = this - self._reload(function (err) { - if (err) return cb(err.code === 'ENOENT' ? null : err) - if (!self._users[user]) return cb(null, false) - if (!utils.verify_password(user, password, self._users[user])) return cb(null, false) +HTPasswd.prototype.authenticate = function(user, password, cb) { + let self = this; + self._reload(function(err) { + if (err) return cb(err.code === 'ENOENT' ? null : err); + if (!self._users[user]) return cb(null, false); + if (!utils.verify_password(user, password, self._users[user])) return cb(null, false); // authentication succeeded! // return all usergroups this user has access to; // (this particular package has no concept of usergroups, so just return user herself) - return cb(null, [user]) - }) -} + return cb(null, [user]); + }); +}; // hopefully race-condition-free way to add users: // 1. lock file for writing (other processes can still read) @@ -52,85 +54,82 @@ HTPasswd.prototype.authenticate = function (user, password, cb) { // 4. move .htpasswd.tmp to .htpasswd // 5. reload .htpasswd // 6. unlock file -HTPasswd.prototype.adduser = function (user, password, real_cb) { - var self = this +HTPasswd.prototype.adduser = function(user, password, real_cb) { + let self = this; function sanity_check() { - var err = null + let err = null; if (self._users[user]) { - err = Error('this user already exists') + err = Error('this user already exists'); } else if (Object.keys(self._users).length >= self._maxusers) { - err = Error('maximum amount of users reached') + err = Error('maximum amount of users reached'); } - if (err) err.status = 403 - return err + if (err) err.status = 403; + return err; } // preliminary checks, just to ensure that file won't be reloaded if it's not needed - var s_err = sanity_check() - if (s_err) return real_cb(s_err, false) + let s_err = sanity_check(); + if (s_err) return real_cb(s_err, false); - utils.lock_and_read(self._path, function (err, res) { - var locked = false + utils.lock_and_read(self._path, function(err, res) { + let locked = false; // callback that cleans up lock first function cb(err) { if (locked) { - utils.unlock_file(self._path, function () { + utils.unlock_file(self._path, function() { // ignore any error from the unlock - real_cb(err, !err) - }) + real_cb(err, !err); + }); } else { - real_cb(err, !err) + real_cb(err, !err); } } if (!err) { - locked = true + locked = true; } // ignore ENOENT errors, we'll just create .htpasswd in that case - if (err && err.code !== 'ENOENT') return cb(err) + if (err && err.code !== 'ENOENT') return cb(err); - var body = (res || '').toString('utf8') - self._users = utils.parse_htpasswd(body) + let body = (res || '').toString('utf8'); + self._users = utils.parse_htpasswd(body); // real checks, to prevent race conditions - var s_err = sanity_check() - if (s_err) return cb(s_err) + let s_err = sanity_check(); + if (s_err) return cb(s_err); try { - body = utils.add_user_to_htpasswd(body, user, password) + body = utils.add_user_to_htpasswd(body, user, password); } catch (err) { - return cb(err) + return cb(err); } - fs.writeFile(self._path, body, function (err) { - if (err) return cb(err) - self._reload(function () { - cb(null, true) - }) - }) - }) -} - -HTPasswd.prototype._reload = function (_callback) { - var self = this - - fs.stat(self._path, function (err, stats) { - if (err) return _callback(err) - - if (self._last_time === stats.mtime) return _callback() - self._last_time = stats.mtime - - fs.readFile(self._path, 'utf8', function (err, buffer) { - if (err) return _callback(err) - - self._users = utils.parse_htpasswd(buffer) - - _callback() - + fs.writeFile(self._path, body, function(err) { + if (err) return cb(err); + self._reload(function() { + cb(null, true); + }); }); - }); +}; -} +HTPasswd.prototype._reload = function(_callback) { + let self = this; + + fs.stat(self._path, function(err, stats) { + if (err) return _callback(err); + + if (self._last_time === stats.mtime) return _callback(); + self._last_time = stats.mtime; + + fs.readFile(self._path, 'utf8', function(err, buffer) { + if (err) return _callback(err); + + self._users = utils.parse_htpasswd(buffer); + + _callback(); + }); + }); +}; diff --git a/lib/plugins/htpasswd/utils.js b/lib/plugins/htpasswd/utils.js index 425e7b085..5a5f30013 100644 --- a/lib/plugins/htpasswd/utils.js +++ b/lib/plugins/htpasswd/utils.js @@ -1,68 +1,70 @@ -var crypto = require('crypto') -var crypt3 = require('./crypt3') -var md5 = require('apache-md5') -var locker = require('../../file-locking') +'use strict'; + +let crypto = require('crypto'); +let crypt3 = require('./crypt3'); +let md5 = require('apache-md5'); +let locker = require('../../file-locking'); // this function neither unlocks file nor closes it // it'll have to be done manually later function lock_and_read(name, cb) { - locker.readFile(name, {lock: true}, function (err, res) { + locker.readFile(name, {lock: true}, function(err, res) { if (err) { - return cb(err) + return cb(err); } - return cb(null, res) - }) + return cb(null, res); + }); } // close and unlock file function unlock_file(name, cb) { - locker.unlockFile(name, cb) + locker.unlockFile(name, cb); } function parse_htpasswd(input) { - var result = {} + let result = {}; input.split('\n').forEach(function(line) { - var args = line.split(':', 3) - if (args.length > 1) result[args[0]] = args[1] - }) - return result + let args = line.split(':', 3); + if (args.length > 1) result[args[0]] = args[1]; + }); + return result; } function verify_password(user, passwd, hash) { if (hash.indexOf('{PLAIN}') === 0) { - return passwd === hash.substr(7) + return passwd === hash.substr(7); } else if (hash.indexOf('{SHA}') === 0) { - return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5) + return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5); } else { return ( // for backwards compatibility, first check md5 then check crypt3 md5(passwd, hash) === hash || crypt3(passwd, hash) === hash - ) + ); } } function add_user_to_htpasswd(body, user, passwd) { if (user !== encodeURIComponent(user)) { - var err = Error('username should not contain non-uri-safe characters') - err.status = 409 - throw err + let err = Error('username should not contain non-uri-safe characters'); + err.status = 409; + throw err; } if (crypt3) { - passwd = crypt3(passwd) + passwd = crypt3(passwd); } else { - passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64') + passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64'); } - var comment = 'autocreated ' + (new Date()).toJSON() + let comment = 'autocreated ' + (new Date()).toJSON(); - var newline = user + ':' + passwd + ':' + comment + '\n' - if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline - return body + newline + let newline = user + ':' + passwd + ':' + comment + '\n'; + if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline; + return body + newline; } -module.exports.parse_htpasswd = parse_htpasswd -module.exports.verify_password = verify_password -module.exports.add_user_to_htpasswd = add_user_to_htpasswd -module.exports.lock_and_read = lock_and_read -module.exports.unlock_file = unlock_file +module.exports.parse_htpasswd = parse_htpasswd; +module.exports.verify_password = verify_password; +module.exports.add_user_to_htpasswd = add_user_to_htpasswd; +module.exports.lock_and_read = lock_and_read; +module.exports.unlock_file = unlock_file; diff --git a/lib/search.js b/lib/search.js index 740e0ab47..eda8fcfb8 100644 --- a/lib/search.js +++ b/lib/search.js @@ -1,60 +1,51 @@ -"use strict"; +'use strict'; -const lunr = require('lunr') +const lunr = require('lunr'); class Search { constructor() { this.index = lunr(function() { - this.field('name' , { boost: 10 }) - this.field('description' , { boost: 4 }) - this.field('author' , { boost: 6 }) - this.field('readme') - }) + this.field('name', {boost: 10}); + this.field('description', {boost: 4}); + this.field('author', {boost: 6}); + this.field('readme'); + }); } query(q) { return q === '*' ? this.storage.config.localList.get().map( function( pkg ) { - return { ref: pkg, score: 1 }; + return {ref: pkg, score: 1}; }) : this.index.search(q); } add(pkg) { this.index.add({ - id: pkg.name, - name: pkg.name, - description: pkg.description, - author: pkg._npmUser ? pkg._npmUser.name : '???', - }) - } - - add(pkg) { - this.index.add({ - id: pkg.name, - name: pkg.name, - description: pkg.description, - author: pkg._npmUser ? pkg._npmUser.name : '???', - }) + id: pkg.name, + name: pkg.name, + description: pkg.description, + author: pkg._npmUser ? pkg._npmUser.name : '???', + }); } remove(name) { - this.index.remove({ id: name }) + this.index.remove({id: name}); } reindex() { - var self = this + let self = this; this.storage.get_local(function(err, packages) { - if (err) throw err // that function shouldn't produce any - var i = packages.length + if (err) throw err; // that function shouldn't produce any + let i = packages.length; while (i--) { - self.add(packages[i]) + self.add(packages[i]); } - }) + }); } configureStorage(storage) { - this.storage = storage - this.reindex() + this.storage = storage; + this.reindex(); } } diff --git a/lib/status-cats.js b/lib/status-cats.js index df328d02b..f1cb4dd7d 100644 --- a/lib/status-cats.js +++ b/lib/status-cats.js @@ -1,3 +1,4 @@ +'use strict'; // see https://secure.flickr.com/photos/girliemac/sets/72157628409467125 @@ -51,24 +52,24 @@ const images = { 508: 'aVdnYa', // '6509400445', // 508 - Loop Detected 509: 'aXXg1V', // '6540399865', // 509 - Bandwidth Limit Exceeded 599: 'aVdo7v', // '6509400929', // 599 - Network connect timeout error -} +}; module.exports.get_image = function(status) { if (status in images) { - return 'http://flic.kr/p/' + images[status] - //return 'https://secure.flickr.com/photos/girliemac/'+images[status]+'/in/set-72157628409467125/lightbox/' + return 'http://flic.kr/p/' + images[status]; + // return 'https://secure.flickr.com/photos/girliemac/'+images[status]+'/in/set-72157628409467125/lightbox/' } -} +}; module.exports.middleware = function(req, res, next) { - var _writeHead = res.writeHead + let _writeHead = res.writeHead; res.writeHead = function(status) { if (status in images) { - res.setHeader('X-Status-Cat', module.exports.get_image(status)) + res.setHeader('X-Status-Cat', module.exports.get_image(status)); } - _writeHead.apply(res, arguments) - } + _writeHead.apply(res, arguments); + }; - next() -} + next(); +}; diff --git a/lib/storage.js b/lib/storage.js index 5b0ac16e3..abafdb471 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -1,14 +1,14 @@ -"use strict"; +'use strict'; -const assert = require('assert') -const async = require('async') -const Error = require('http-errors') -const Stream = require('stream') -const Local = require('./local-storage') -const Logger = require('./logger') -const MyStreams = require('./streams') -const Proxy = require('./up-storage') -const Utils = require('./utils') +const assert = require('assert'); +const async = require('async'); +const Error = require('http-errors'); +const Stream = require('stream'); +const Local = require('./local-storage'); +const Logger = require('./logger'); +const MyStreams = require('./streams'); +const Proxy = require('./up-storage'); +const Utils = require('./utils'); // // Implements Storage interface @@ -21,17 +21,17 @@ class Storage { * @param {*} config */ constructor(config) { - this.config = config + this.config = config; // we support a number of uplinks, but only one local storage // Proxy and Local classes should have similar API interfaces - this.uplinks = {} + this.uplinks = {}; for (let p in config.uplinks) { // instance for each up-link definition - this.uplinks[p] = new Proxy(config.uplinks[p], config) - this.uplinks[p].upname = p + this.uplinks[p] = new Proxy(config.uplinks[p], config); + this.uplinks[p].upname = p; } // an instance for local storage - this.local = new Local(config) + this.local = new Local(config); this.logger = Logger.logger.child(); } @@ -45,7 +45,7 @@ class Storage { * @param {*} callback */ add_package(name, metadata, callback) { - var self = this + let self = this; // NOTE: // - when we checking package for existance, we ask ALL uplinks @@ -53,52 +53,52 @@ class Storage { // so all requests are necessary check_package_local(function(err) { - if (err) return callback(err) + if (err) return callback(err); check_package_remote(function(err) { - if (err) return callback(err) + if (err) return callback(err); publish_package(function(err) { - if (err) return callback(err) - callback() - }) - }) - }) + if (err) return callback(err); + callback(); + }); + }); + }); function check_package_local(cb) { self.local.get_package(name, {}, function(err, results) { - if (err && err.status !== 404) return cb(err) + if (err && err.status !== 404) return cb(err); - if (results) return cb( Error[409]('this package is already present') ) + if (results) return cb( Error[409]('this package is already present') ); - cb() - }) + cb(); + }); } function check_package_remote(cb) { self._sync_package_with_uplinks(name, null, {}, function(err, results, err_results) { // something weird - if (err && err.status !== 404) return cb(err) + if (err && err.status !== 404) return cb(err); // checking package - if (results) return cb( Error[409]('this package is already present') ) + if (results) return cb( Error[409]('this package is already present') ); - for (var i=0; i<err_results.length; i++) { + for (let i=0; i<err_results.length; i++) { // checking error // if uplink fails with a status other than 404, we report failure if (err_results[i][0] != null) { if (err_results[i][0].status !== 404) { - return cb( Error[503]('one of the uplinks is down, refuse to publish') ) + return cb( Error[503]('one of the uplinks is down, refuse to publish') ); } } } - return cb() - }) + return cb(); + }); } function publish_package(cb) { - self.local.add_package(name, metadata, callback) + self.local.add_package(name, metadata, callback); } } @@ -112,7 +112,7 @@ class Storage { * @param {*} callback */ add_version(name, version, metadata, tag, callback) { - return this.local.add_version(name, version, metadata, tag, callback) + return this.local.add_version(name, version, metadata, tag, callback); } /** @@ -123,7 +123,7 @@ class Storage { * @param {*} callback */ merge_tags(name, tag_hash, callback) { - return this.local.merge_tags(name, tag_hash, callback) + return this.local.merge_tags(name, tag_hash, callback); } /** @@ -134,7 +134,7 @@ class Storage { * @param {*} callback */ replace_tags(name, tag_hash, callback) { - return this.local.replace_tags(name, tag_hash, callback) + return this.local.replace_tags(name, tag_hash, callback); } /** @@ -147,7 +147,7 @@ class Storage { * @param {*} callback */ change_package(name, metadata, revision, callback) { - return this.local.change_package(name, metadata, revision, callback) + return this.local.change_package(name, metadata, revision, callback); } /** @@ -158,7 +158,7 @@ class Storage { * @param {*} callback */ remove_package(name, callback) { - return this.local.remove_package(name, callback) + return this.local.remove_package(name, callback); } /** @@ -173,7 +173,7 @@ class Storage { * @param {*} callback */ remove_tarball(name, filename, revision, callback) { - return this.local.remove_tarball(name, filename, revision, callback) + return this.local.remove_tarball(name, filename, revision, callback); } /** @@ -184,7 +184,7 @@ class Storage { * @param {*} filename */ add_tarball(name, filename) { - return this.local.add_tarball(name, filename) + return this.local.add_tarball(name, filename); } /** @@ -197,101 +197,100 @@ class Storage { * @param {*} filename */ get_tarball(name, filename) { - var stream = MyStreams.ReadTarballStream() - stream.abort = function() {} + let stream = MyStreams.ReadTarballStream(); + stream.abort = function() {}; - var self = this + let self = this; // if someone requesting tarball, it means that we should already have some // information about it, so fetching package info is unnecessary // trying local first - var rstream = self.local.get_tarball(name, filename) - var is_open = false + let rstream = self.local.get_tarball(name, filename); + let is_open = false; rstream.on('error', function(err) { if (is_open || err.status !== 404) { - return stream.emit('error', err) + return stream.emit('error', err); } // local reported 404 - var err404 = err - rstream.abort() - rstream = null // gc + let err404 = err; + rstream.abort(); + rstream = null; // gc self.local.get_package(name, function(err, info) { if (!err && info._distfiles && info._distfiles[filename] != null) { // information about this file exists locally - serve_file(info._distfiles[filename]) - + serve_file(info._distfiles[filename]); } else { // we know nothing about this file, trying to get information elsewhere self._sync_package_with_uplinks(name, info, {}, function(err, info) { - if (err) return stream.emit('error', err) + if (err) return stream.emit('error', err); if (!info._distfiles || info._distfiles[filename] == null) { - return stream.emit('error', err404) + return stream.emit('error', err404); } - serve_file(info._distfiles[filename]) - }) + serve_file(info._distfiles[filename]); + }); } - }) - }) + }); + }); rstream.on('content-length', function(v) { - stream.emit('content-length', v) - }) + stream.emit('content-length', v); + }); rstream.on('open', function() { - is_open = true - rstream.pipe(stream) - }) - return stream + is_open = true; + rstream.pipe(stream); + }); + return stream; function serve_file(file) { - var uplink = null - for (var p in self.uplinks) { + let uplink = null; + for (let p in self.uplinks) { if (self.uplinks[p].can_fetch_url(file.url)) { - uplink = self.uplinks[p] + uplink = self.uplinks[p]; } } if (uplink == null) { uplink = Proxy({ url: file.url, _autogenerated: true, - }, self.config) + }, self.config); } - var savestream = self.local.add_tarball(name, filename) + let savestream = self.local.add_tarball(name, filename); var on_open = function() { - on_open = function(){} // prevent it from being called twice - var rstream2 = uplink.get_url(file.url) + on_open = function() {}; // prevent it from being called twice + let rstream2 = uplink.get_url(file.url); rstream2.on('error', function(err) { - if (savestream) savestream.abort() - savestream = null - stream.emit('error', err) - }) + if (savestream) savestream.abort(); + savestream = null; + stream.emit('error', err); + }); rstream2.on('end', function() { - if (savestream) savestream.done() - }) + if (savestream) savestream.done(); + }); rstream2.on('content-length', function(v) { - stream.emit('content-length', v) - if (savestream) savestream.emit('content-length', v) - }) - rstream2.pipe(stream) - if (savestream) rstream2.pipe(savestream) - } + stream.emit('content-length', v); + if (savestream) savestream.emit('content-length', v); + }); + rstream2.pipe(stream); + if (savestream) rstream2.pipe(savestream); + }; savestream.on('open', function() { - on_open() - }) + on_open(); + }); savestream.on('error', function(err) { - self.logger.warn( { err: err } - , 'error saving file: @{err.message}\n@{err.stack}' ) - if (savestream) savestream.abort() - savestream = null - on_open() - }) + self.logger.warn( {err: err} + , 'error saving file: @{err.message}\n@{err.stack}' ); + if (savestream) savestream.abort(); + savestream = null; + on_open(); + }); } } @@ -313,24 +312,24 @@ class Storage { this.local.get_package(name, options, (err, data) => { if (err && (!err.status || err.status >= 500)) { // report internal errors right away - return callback(err) + return callback(err); } this._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) { - if (err) return callback(err) - const whitelist = [ '_rev', 'name', 'versions', 'dist-tags', 'readme' ] - for (var i in result) { - if (whitelist.indexOf(i) === -1) delete result[i] + if (err) return callback(err); + const whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme']; + for (let i in result) { + if (whitelist.indexOf(i) === -1) delete result[i]; } - Utils.normalize_dist_tags(result) + Utils.normalize_dist_tags(result); // npm can throw if this field doesn't exist - result._attachments = {} + result._attachments = {}; - callback(null, result, uplink_errors) - }) - }) + callback(null, result, uplink_errors); + }); + }); } /** @@ -345,37 +344,39 @@ class Storage { * @param {*} options */ search(startkey, options) { - var self = this + let self = this; - var stream = new Stream.PassThrough({ objectMode: true }) + let stream = new Stream.PassThrough({objectMode: true}); async.eachSeries(Object.keys(this.uplinks), function(up_name, cb) { // shortcut: if `local=1` is supplied, don't call uplinks - if (options.req.query.local !== undefined) return cb() + if (options.req.query.local !== undefined) return cb(); - var lstream = self.uplinks[up_name].search(startkey, options) - lstream.pipe(stream, { end: false }) - lstream.on('error', function (err) { - self.logger.error({ err: err }, 'uplink error: @{err.message}') - cb(), cb = function () {} - }) - lstream.on('end', function () { - cb(), cb = function () {} - }) + let lstream = self.uplinks[up_name].search(startkey, options); + lstream.pipe(stream, {end: false}); + lstream.on('error', function(err) { + self.logger.error({err: err}, 'uplink error: @{err.message}'); + cb(), cb = function() {}; + }); + lstream.on('end', function() { + cb(), cb = function() {}; + }); - stream.abort = function () { - if (lstream.abort) lstream.abort() - cb(), cb = function () {} - } - }, function () { - var lstream = self.local.search(startkey, options) - stream.abort = function () { lstream.abort() } - lstream.pipe(stream, { end: true }) - lstream.on('error', function (err) { - self.logger.error({ err: err }, 'search error: @{err.message}') - stream.end() - }) - }) + stream.abort = function() { + if (lstream.abort) lstream.abort(); + cb(), cb = function() {}; + }; + }, function() { + let lstream = self.local.search(startkey, options); + stream.abort = function() { + lstream.abort(); +}; + lstream.pipe(stream, {end: true}); + lstream.on('error', function(err) { + self.logger.error({err: err}, 'search error: @{err.message}'); + stream.end(); + }); + }); return stream; } @@ -385,34 +386,34 @@ class Storage { * @param {*} callback */ get_local(callback) { - var self = this - var locals = this.config.localList.get() - var packages = [] + let self = this; + let locals = this.config.localList.get(); + let packages = []; var getPackage = function(i) { self.local.get_package(locals[i], function(err, info) { if (!err) { - var latest = info['dist-tags'].latest + let latest = info['dist-tags'].latest; if (latest && info.versions[latest]) { - packages.push(info.versions[latest]) + packages.push(info.versions[latest]); } else { - self.logger.warn( { package: locals[i] } - , 'package @{package} does not have a "latest" tag?' ) + self.logger.warn( {package: locals[i]} + , 'package @{package} does not have a "latest" tag?' ); } } if (i >= locals.length - 1) { - callback(null, packages) + callback(null, packages); } else { - getPackage(i + 1) + getPackage(i + 1); } - }) - } + }); + }; if (locals.length) { - getPackage(0) + getPackage(0); } else { - callback(null, []) + callback(null, []); } } @@ -426,68 +427,68 @@ class Storage { * @param {*} callback */ _sync_package_with_uplinks(name, pkginfo, options, callback) { - var self = this + let self = this; let exists = false; if (!pkginfo) { - exists = false + exists = false; pkginfo = { - name : name, - versions : {}, - 'dist-tags' : {}, - _uplinks : {}, - } + 'name': name, + 'versions': {}, + 'dist-tags': {}, + '_uplinks': {}, + }; } else { - exists = true + exists = true; } - var uplinks = [] + let uplinks = []; for (let i in self.uplinks) { if (self.config.can_proxy_to(name, i)) { - uplinks.push(self.uplinks[i]) + uplinks.push(self.uplinks[i]); } } async.map(uplinks, function(up, cb) { - var _options = Object.assign({}, options) + let _options = Object.assign({}, options); if (Utils.is_object(pkginfo._uplinks[up.upname])) { - var fetched = pkginfo._uplinks[up.upname].fetched + let fetched = pkginfo._uplinks[up.upname].fetched; if (fetched && fetched > (Date.now() - up.maxage)) { - return cb() + return cb(); } - _options.etag = pkginfo._uplinks[up.upname].etag + _options.etag = pkginfo._uplinks[up.upname].etag; } up.get_package(name, _options, function(err, up_res, etag) { if (err && err.status === 304) - pkginfo._uplinks[up.upname].fetched = Date.now() + pkginfo._uplinks[up.upname].fetched = Date.now(); - if (err || !up_res) return cb(null, [err || Error('no data')]) + if (err || !up_res) return cb(null, [err || Error('no data')]); try { - Utils.validate_metadata(up_res, name) + Utils.validate_metadata(up_res, name); } catch(err) { self.logger.error({ sub: 'out', err: err, - }, 'package.json validating error @{!err.message}\n@{err.stack}') - return cb(null, [ err ]) + }, 'package.json validating error @{!err.message}\n@{err.stack}'); + return cb(null, [err]); } pkginfo._uplinks[up.upname] = { etag: etag, - fetched: Date.now() - } + fetched: Date.now(), + }; for (let i in up_res.versions) { // this won't be serialized to json, // kinda like an ES6 Symbol - //FIXME: perhaps Symbol('_verdaccio_uplink') here? + // FIXME: perhaps Symbol('_verdaccio_uplink') here? Object.defineProperty(up_res.versions[i], '_verdaccio_uplink', { - value : up.upname, - enumerable : false, - configurable : false, - writable : true, + value: up.upname, + enumerable: false, + configurable: false, + writable: true, }); } @@ -497,29 +498,29 @@ class Storage { self.logger.error({ sub: 'out', err: err, - }, 'package.json parsing error @{!err.message}\n@{err.stack}') - return cb(null, [ err ]) + }, 'package.json parsing error @{!err.message}\n@{err.stack}'); + return cb(null, [err]); } // if we got to this point, assume that the correct package exists // on the uplink - exists = true - cb() - }) + exists = true; + cb(); + }); }, function(err, uplink_errors) { - assert(!err && Array.isArray(uplink_errors)) + assert(!err && Array.isArray(uplink_errors)); if (!exists) { return callback( Error[404]('no such package available') , null - , uplink_errors ) + , uplink_errors ); } self.local.update_versions(name, pkginfo, function(err, pkginfo) { - if (err) return callback(err) - return callback(null, pkginfo, uplink_errors) - }) - }) + if (err) return callback(err); + return callback(null, pkginfo, uplink_errors); + }); + }); } /** @@ -534,17 +535,17 @@ class Storage { // NOTE: if a certain version was updated, we can't refresh it reliably for (var i in up.versions) { if (local.versions[i] == null) { - local.versions[i] = up.versions[i] + local.versions[i] = up.versions[i]; } } // refresh dist-tags for (var i in up['dist-tags']) { if (local['dist-tags'][i] !== up['dist-tags'][i]) { - local['dist-tags'][i] = up['dist-tags'][i] + local['dist-tags'][i] = up['dist-tags'][i]; if (i === 'latest') { // if remote has more fresh package, we should borrow its readme - local.readme = up.readme + local.readme = up.readme; } } } diff --git a/lib/streams.js b/lib/streams.js index 43a1e4a02..9a105aa17 100644 --- a/lib/streams.js +++ b/lib/streams.js @@ -1,60 +1,62 @@ -var Stream = require('stream') -var Util = require('util') +'use strict'; -module.exports.ReadTarballStream = ReadTarball -module.exports.UploadTarballStream = UploadTarball +let Stream = require('stream'); +let Util = require('util'); + +module.exports.ReadTarballStream = ReadTarball; +module.exports.UploadTarballStream = UploadTarball; // // This stream is used to read tarballs from repository // function ReadTarball(options) { - var self = new Stream.PassThrough(options) - Object.setPrototypeOf(self, ReadTarball.prototype) + let self = new Stream.PassThrough(options); + Object.setPrototypeOf(self, ReadTarball.prototype); // called when data is not needed anymore - add_abstract_method(self, 'abort') + add_abstract_method(self, 'abort'); - return self + return self; } -Util.inherits(ReadTarball, Stream.PassThrough) +Util.inherits(ReadTarball, Stream.PassThrough); // // This stream is used to upload tarballs to a repository // function UploadTarball(options) { - var self = new Stream.PassThrough(options) - Object.setPrototypeOf(self, UploadTarball.prototype) + let self = new Stream.PassThrough(options); + Object.setPrototypeOf(self, UploadTarball.prototype); // called when user closes connection before upload finishes - add_abstract_method(self, 'abort') + add_abstract_method(self, 'abort'); // called when upload finishes successfully - add_abstract_method(self, 'done') + add_abstract_method(self, 'done'); - return self + return self; } -Util.inherits(UploadTarball, Stream.PassThrough) +Util.inherits(UploadTarball, Stream.PassThrough); // // This function intercepts abstract calls and replays them allowing // us to attach those functions after we are ready to do so // function add_abstract_method(self, name) { - self._called_methods = self._called_methods || {} + self._called_methods = self._called_methods || {}; self.__defineGetter__(name, function() { return function() { - self._called_methods[name] = true - } - }) + self._called_methods[name] = true; + }; + }); self.__defineSetter__(name, function(fn) { - delete self[name] - self[name] = fn + delete self[name]; + self[name] = fn; if (self._called_methods && self._called_methods[name]) { - delete self._called_methods[name] - self[name]() + delete self._called_methods[name]; + self[name](); } - }) + }); } diff --git a/lib/up-storage.js b/lib/up-storage.js index a6e05129c..5278f0c5e 100644 --- a/lib/up-storage.js +++ b/lib/up-storage.js @@ -1,32 +1,32 @@ -"use strict"; +'use strict'; -const JSONStream = require('JSONStream') -const Error = require('http-errors') -const request = require('request') -const Stream = require('readable-stream') -const URL = require('url') -const parse_interval = require('./config').parse_interval -const Logger = require('./logger') -const MyStreams = require('./streams') -const Utils = require('./utils') -const encode = function(thing) { +const JSONStream = require('JSONStream'); +const Error = require('http-errors'); +const request = require('request'); +const Stream = require('readable-stream'); +const URL = require('url'); +const parse_interval = require('./config').parse_interval; +const Logger = require('./logger'); +const MyStreams = require('./streams'); +const Utils = require('./utils'); +const encode = function(thing) { return encodeURIComponent(thing).replace(/^%40/, '@'); }; const _setupProxy = function(hostname, config, mainconfig, isHTTPS) { - var no_proxy - var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy' + let no_proxy; + let proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy'; // get http_proxy and no_proxy configs if (proxy_key in config) { - this.proxy = config[proxy_key] + this.proxy = config[proxy_key]; } else if (proxy_key in mainconfig) { - this.proxy = mainconfig[proxy_key] + this.proxy = mainconfig[proxy_key]; } if ('no_proxy' in config) { - no_proxy = config.no_proxy + no_proxy = config.no_proxy; } else if ('no_proxy' in mainconfig) { - no_proxy = mainconfig.no_proxy + no_proxy = mainconfig.no_proxy; } // use wget-like algorithm to determine if proxy shouldn't be used @@ -34,17 +34,17 @@ const _setupProxy = function(hostname, config, mainconfig, isHTTPS) { hostname = '.' + hostname; } if (typeof(no_proxy) === 'string' && no_proxy.length) { - no_proxy = no_proxy.split(',') + no_proxy = no_proxy.split(','); } if (Array.isArray(no_proxy)) { for (let i=0; i<no_proxy.length; i++) { - var no_proxy_item = no_proxy[i] - if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item + let no_proxy_item = no_proxy[i]; + if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item; if (hostname.lastIndexOf(no_proxy_item) === hostname.length - no_proxy_item.length) { if (this.proxy) { this.logger.debug({url: this.url.href, rule: no_proxy_item}, - 'not using proxy for @{url}, excluded by @{rule} rule') - this.proxy = false + 'not using proxy for @{url}, excluded by @{rule} rule'); + this.proxy = false; } break; } @@ -53,12 +53,12 @@ const _setupProxy = function(hostname, config, mainconfig, isHTTPS) { // if it's non-string (i.e. "false"), don't use it if (typeof(this.proxy) !== 'string') { - delete this.proxy + delete this.proxy; } else { - this.logger.debug( { url: this.url.href, proxy: this.proxy } - , 'using proxy @{proxy} for @{url}' ) + this.logger.debug( {url: this.url.href, proxy: this.proxy} + , 'using proxy @{proxy} for @{url}' ); } -} +}; // // Implements Storage interface @@ -72,35 +72,35 @@ class Storage { * @param {*} mainconfig */ constructor(config, mainconfig) { - this.config = config - this.failed_requests = 0 - this.userAgent = mainconfig.user_agent - this.ca = config.ca - this.logger = Logger.logger.child({sub: 'out'}) - this.server_id = mainconfig.server_id + this.config = config; + this.failed_requests = 0; + this.userAgent = mainconfig.user_agent; + this.ca = config.ca; + this.logger = Logger.logger.child({sub: 'out'}); + this.server_id = mainconfig.server_id; - this.url = URL.parse(this.config.url) + this.url = URL.parse(this.config.url); - _setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:') + _setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:'); - this.config.url = this.config.url.replace(/\/$/, '') + this.config.url = this.config.url.replace(/\/$/, ''); if (Number(this.config.timeout) >= 1000) { - this.logger.warn([ 'Too big timeout value: ' + this.config.timeout, + this.logger.warn(['Too big timeout value: ' + this.config.timeout, 'We changed time format to nginx-like one', '(see http://wiki.nginx.org/ConfigNotation)', - 'so please update your config accordingly' ].join('\n')) + 'so please update your config accordingly'].join('\n')); } // a bunch of different configurable timers - this.maxage = parse_interval(config_get('maxage' , '2m' )) - this.timeout = parse_interval(config_get('timeout' , '30s')) - this.max_fails = Number(config_get('max_fails' , 2 )) - this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' )) - return this + this.maxage = parse_interval(config_get('maxage', '2m' )); + this.timeout = parse_interval(config_get('timeout', '30s')); + this.max_fails = Number(config_get('max_fails', 2 )); + this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' )); + return this; // just a helper (`config[key] || default` doesn't work because of zeroes) function config_get(key, def) { - return config[key] != null ? config[key] : def + return config[key] != null ? config[key] : def; } } } @@ -108,15 +108,15 @@ class Storage { Storage.prototype.request = function(options, cb) { if (!this.status_check()) { - var req = new Stream.Readable() + var req = new Stream.Readable(); process.nextTick(function() { - if (typeof(cb) === 'function') cb(Error('uplink is offline')) - req.emit('error', Error('uplink is offline')) - }) - req._read = function(){} + if (typeof(cb) === 'function') cb(Error('uplink is offline')); + req.emit('error', Error('uplink is offline')); + }); + req._read = function() {}; // preventing 'Uncaught, unspecified "error" event' - req.on('error', function(){}) - return req + req.on('error', function() {}); + return req; } var self = this @@ -129,256 +129,257 @@ Storage.prototype.request = function(options, cb) { // add/override headers specified in the config for (let key in this.config.headers) { - headers[key] = this.config.headers[key] + headers[key] = this.config.headers[key]; } - var method = options.method || 'GET' - var uri = options.uri_full || (this.config.url + options.uri) + let method = options.method || 'GET'; + let uri = options.uri_full || (this.config.url + options.uri); self.logger.info({ - method : method, - headers : headers, - uri : uri, - }, "making request: '@{method} @{uri}'") + method: method, + headers: headers, + uri: uri, + }, 'making request: \'@{method} @{uri}\''); if (Utils.is_object(options.json)) { - var json = JSON.stringify(options.json) - headers['Content-Type'] = headers['Content-Type'] || 'application/json' + var json = JSON.stringify(options.json); + headers['Content-Type'] = headers['Content-Type'] || 'application/json'; } - var request_callback = cb ? (function (err, res, body) { - var error - var res_length = err ? 0 : body.length + let request_callback = cb ? (function(err, res, body) { + let error; + let res_length = err ? 0 : body.length; - do_decode() - do_log() - cb(err, res, body) + do_decode(); + do_log(); + cb(err, res, body); function do_decode() { if (err) { - error = err.message - return + error = err.message; + return; } if (options.json && res.statusCode < 300) { try { - body = JSON.parse(body.toString('utf8')) + body = JSON.parse(body.toString('utf8')); } catch(_err) { - body = {} - err = _err - error = err.message + body = {}; + err = _err; + error = err.message; } } if (!err && Utils.is_object(body)) { if (typeof(body.error) === 'string') { - error = body.error + error = body.error; } } } function do_log() { - var message = '@{!status}, req: \'@{request.method} @{request.url}\'' + let message = '@{!status}, req: \'@{request.method} @{request.url}\''; message += error ? ', error: @{!error}' - : ', bytes: @{bytes.in}/@{bytes.out}' + : ', bytes: @{bytes.in}/@{bytes.out}'; self.logger.warn({ - err : err, - request : { method: method, url: uri }, - level : 35, // http - status : res != null ? res.statusCode : 'ERR', - error : error, - bytes : { - in : json ? json.length : 0, - out : res_length || 0, - } - }, message) + err: err, + request: {method: method, url: uri}, + level: 35, // http + status: res != null ? res.statusCode : 'ERR', + error: error, + bytes: { + in: json ? json.length : 0, + out: res_length || 0, + }, + }, message); } - }) : undefined + }) : undefined; var req = request({ - url : uri, - method : method, - headers : headers, - body : json, - ca : this.ca, - proxy : this.proxy, - encoding : null, - gzip : true, - timeout : this.timeout, - }, request_callback) + url: uri, + method: method, + headers: headers, + body: json, + ca: this.ca, + proxy: this.proxy, + encoding: null, + gzip: true, + timeout: this.timeout, + }, request_callback); - var status_called = false + let status_called = false; req.on('response', function(res) { if (!req._verdaccio_aborted && !status_called) { - status_called = true - self.status_check(true) + status_called = true; + self.status_check(true); } if (!request_callback) { - ;(function do_log() { - var message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)' + (function do_log() { + let message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)'; self.logger.warn({ - request : { method: method, url: uri }, - level : 35, // http - status : res != null ? res.statusCode : 'ERR', - }, message) - })() + request: {method: method, url: uri}, + level: 35, // http + status: res != null ? res.statusCode : 'ERR', + }, message); + })(); } - }) + }); req.on('error', function(_err) { if (!req._verdaccio_aborted && !status_called) { - status_called = true - self.status_check(false) + status_called = true; + self.status_check(false); } - }) - return req -} + }); + return req; +}; Storage.prototype.status_check = function(alive) { if (arguments.length === 0) { if (this.failed_requests >= this.max_fails && Math.abs(Date.now() - this.last_request_time) < this.fail_timeout) { - return false + return false; } else { - return true + return true; } } else { if (alive) { if (this.failed_requests >= this.max_fails) { - this.logger.warn({ host: this.url.host }, 'host @{host} is back online') + this.logger.warn({host: this.url.host}, 'host @{host} is back online'); } - this.failed_requests = 0 + this.failed_requests = 0; } else { - this.failed_requests++ + this.failed_requests++; if (this.failed_requests === this.max_fails) { - this.logger.warn({ host: this.url.host }, 'host @{host} is now offline') + this.logger.warn({host: this.url.host}, 'host @{host} is now offline'); } } - this.last_request_time = Date.now() + this.last_request_time = Date.now(); } -} +}; Storage.prototype.can_fetch_url = function(url) { - url = URL.parse(url) + url = URL.parse(url); return url.protocol === this.url.protocol && url.host === this.url.host - && url.path.indexOf(this.url.path) === 0 -} + && url.path.indexOf(this.url.path) === 0; +}; Storage.prototype.get_package = function(name, options, callback) { - if (typeof(options) === 'function') callback = options, options = {} + if (typeof(options) === 'function') callback = options, options = {}; - var headers = {} + let headers = {}; if (options.etag) { - headers['If-None-Match'] = options.etag - headers['Accept'] = 'application/octet-stream' + headers['If-None-Match'] = options.etag; + headers['Accept'] = 'application/octet-stream'; } this.request({ - uri : '/' + encode(name), - json : true, - headers : headers, - req : options.req, + uri: '/' + encode(name), + json: true, + headers: headers, + req: options.req, }, function(err, res, body) { - if (err) return callback(err) + if (err) return callback(err); if (res.statusCode === 404) { - return callback( Error[404]("package doesn't exist on uplink") ) + return callback( Error[404]('package doesn\'t exist on uplink') ); } if (!(res.statusCode >= 200 && res.statusCode < 300)) { - var error = Error('bad status code: ' + res.statusCode) - error.remoteStatus = res.statusCode - return callback(error) + let error = Error('bad status code: ' + res.statusCode); + error.remoteStatus = res.statusCode; + return callback(error); } - callback(null, body, res.headers.etag) - }) -} + callback(null, body, res.headers.etag); + }); +}; Storage.prototype.get_tarball = function(name, options, filename) { - if (!options) options = {} - return this.get_url(this.config.url + '/' + name + '/-/' + filename) -} + if (!options) options = {}; + return this.get_url(this.config.url + '/' + name + '/-/' + filename); +}; Storage.prototype.get_url = function(url) { - var stream = MyStreams.ReadTarballStream() - stream.abort = function() {} - var current_length = 0, expected_length + let stream = MyStreams.ReadTarballStream(); + stream.abort = function() {}; + let current_length = 0, expected_length; - var rstream = this.request({ + let rstream = this.request({ uri_full: url, encoding: null, - headers: { Accept: 'application/octet-stream' }, - }) + headers: {Accept: 'application/octet-stream'}, + }); rstream.on('response', function(res) { if (res.statusCode === 404) { - return stream.emit('error', Error[404]("file doesn't exist on uplink")) + return stream.emit('error', Error[404]('file doesn\'t exist on uplink')); } if (!(res.statusCode >= 200 && res.statusCode < 300)) { - return stream.emit('error', Error('bad uplink status code: ' + res.statusCode)) + return stream.emit('error', Error('bad uplink status code: ' + res.statusCode)); } if (res.headers['content-length']) { - expected_length = res.headers['content-length'] - stream.emit('content-length', res.headers['content-length']) + expected_length = res.headers['content-length']; + stream.emit('content-length', res.headers['content-length']); } - rstream.pipe(stream) - }) + rstream.pipe(stream); + }); rstream.on('error', function(err) { - stream.emit('error', err) - }) + stream.emit('error', err); + }); rstream.on('data', function(d) { - current_length += d.length - }) + current_length += d.length; + }); rstream.on('end', function(d) { - if (d) current_length += d.length + if (d) current_length += d.length; if (expected_length && current_length != expected_length) - stream.emit('error', Error('content length mismatch')) - }) - return stream -} + stream.emit('error', Error('content length mismatch')); + }); + return stream; +}; Storage.prototype.search = function(startkey, options) { - var self = this + let self = this; - var stream = new Stream.PassThrough({ objectMode: true }) + let stream = new Stream.PassThrough({objectMode: true}); var req = self.request({ uri: options.req.url, req: options.req, headers: { - referer: options.req.headers.referer - } - }) + referer: options.req.headers.referer, + }, + }); - req.on('response', function (res) { + req.on('response', (res) => { if (!String(res.statusCode).match(/^2\d\d$/)) { - return stream.emit('error', Error('bad status code ' + res.statusCode + ' from uplink')) + return stream.emit('error', Error('bad status code ' + res.statusCode + ' from uplink')); } - res.pipe(JSONStream.parse('*')).on('data', function (pkg) { + res.pipe(JSONStream.parse('*')).on('data', (pkg) => { if (Utils.is_object(pkg)) { - stream.emit('data', pkg) + stream.emit('data', pkg); } - }) - res.on('end', function () { - stream.emit('end') - }) - }) + }); + res.on('end', () => { + stream.emit('end'); + }); + }); - req.on('error', function (err) { - stream.emit('error', err) - }) + req.on('error', (err) => { + stream.emit('error', err); + }); - stream.abort = function () { - req.abort() - stream.emit('end') - } + stream.abort = () => { + req.abort(); + stream.emit('end'); + }; - return stream -} + + return stream; +}; Storage.prototype._add_proxy_headers = function(req, headers) { if (req) { @@ -393,7 +394,7 @@ Storage.prototype._add_proxy_headers = function(req, headers) { req && req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '' - ) + req.connection.remoteAddress + ) + req.connection.remoteAddress; } } @@ -401,10 +402,10 @@ Storage.prototype._add_proxy_headers = function(req, headers) { headers['Via'] = req && req.headers['via'] ? req.headers['via'] + ', ' - : '' + : ''; - headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)' -} + headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)'; +}; module.exports = Storage; diff --git a/lib/utils.js b/lib/utils.js index f2f4696a9..483287269 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,25 +1,27 @@ -var assert = require('assert') -var Semver = require('semver') -var URL = require('url') -var Logger = require('./logger') +'use strict'; + +let assert = require('assert'); +let Semver = require('semver'); +let URL = require('url'); +let Logger = require('./logger'); module.exports.validate_package = function(name) { - name = name.split('/', 2) + name = name.split('/', 2); if (name.length === 1) { // normal package - return module.exports.validate_name(name[0]) + return module.exports.validate_name(name[0]); } else { // scoped package return name[0][0] === '@' && module.exports.validate_name(name[0].slice(1)) - && module.exports.validate_name(name[1]) + && module.exports.validate_name(name[1]); } -} +}; // from normalize-package-data/lib/fixer.js module.exports.validate_name = function(name) { - if (typeof(name) !== 'string') return false - name = name.toLowerCase() + if (typeof(name) !== 'string') return false; + name = name.toLowerCase(); // all URL-safe characters and "@" for issue #75 if (!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/) @@ -30,88 +32,88 @@ module.exports.validate_name = function(name) { || name === 'package.json' || name === 'favicon.ico' ) { - return false + return false; } else { - return true + return true; } -} +}; module.exports.is_object = function(obj) { - return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj) -} + return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj); +}; module.exports.validate_metadata = function(object, name) { - assert(module.exports.is_object(object), 'not a json object') - assert.equal(object.name, name) + assert(module.exports.is_object(object), 'not a json object'); + assert.equal(object.name, name); if (!module.exports.is_object(object['dist-tags'])) { - object['dist-tags'] = {} + object['dist-tags'] = {}; } if (!module.exports.is_object(object['versions'])) { - object['versions'] = {} + object['versions'] = {}; } return object; -} +}; module.exports.filter_tarball_urls = function(pkg, req, config) { function filter(_url) { - if (!req.headers.host) return _url + if (!req.headers.host) return _url; - var filename = URL.parse(_url).pathname.replace(/^.*\//, '') + let filename = URL.parse(_url).pathname.replace(/^.*\//, ''); if (config.url_prefix != null) { - var result = config.url_prefix.replace(/\/$/, '') + var result = config.url_prefix.replace(/\/$/, ''); } else { - var result = req.protocol + '://' + req.headers.host + var result = req.protocol + '://' + req.headers.host; } return `${result}/${pkg.name.replace(/\//g, '%2f')}/-/${filename}`; } - for (var ver in pkg.versions) { - var dist = pkg.versions[ver].dist + for (let ver in pkg.versions) { + let dist = pkg.versions[ver].dist; if (dist != null && dist.tarball != null) { - //dist.__verdaccio_orig_tarball = dist.tarball - dist.tarball = filter(dist.tarball) + // dist.__verdaccio_orig_tarball = dist.tarball + dist.tarball = filter(dist.tarball); } } - return pkg -} + return pkg; +}; module.exports.tag_version = function(data, version, tag) { if (tag) { if (data['dist-tags'][tag] !== version) { if (Semver.parse(version, true)) { // valid version - store - data['dist-tags'][tag] = version - return true + data['dist-tags'][tag] = version; + return true; } } - Logger.logger.warn({ver: version, tag: tag}, 'ignoring bad version @{ver} in @{tag}') + Logger.logger.warn({ver: version, tag: tag}, 'ignoring bad version @{ver} in @{tag}'); if (tag && data['dist-tags'][tag]) { - delete data['dist-tags'][tag] + delete data['dist-tags'][tag]; } } - return false -} + return false; +}; // gets version from a package object taking into account semver weirdness module.exports.get_version = function(object, version) { - if (object.versions[version] != null) return object.versions[version] + if (object.versions[version] != null) return object.versions[version]; try { - version = Semver.parse(version, true) - for (var k in object.versions) { + version = Semver.parse(version, true); + for (let k in object.versions) { if (version.compare(Semver.parse(k, true)) === 0) { - return object.versions[k] + return object.versions[k]; } } } catch (err) { - return undefined + return undefined; } -} +}; module.exports.parse_address = function parse_address(addr) { // @@ -129,68 +131,67 @@ module.exports.parse_address = function parse_address(addr) { // TODO: refactor it to something more reasonable? // // protocol : // ( host )|( ipv6 ): port / - var m = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(addr) + var m = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(addr); if (m) return { proto: m[2] || 'http', - host: m[6] || m[7] || 'localhost', - port: m[8] || '4873', - } + host: m[6] || m[7] || 'localhost', + port: m[8] || '4873', + }; - var m = /^((https?):(\/\/)?)?unix:(.*)$/.exec(addr) + var m = /^((https?):(\/\/)?)?unix:(.*)$/.exec(addr); if (m) return { proto: m[2] || 'http', - path: m[4], - } + path: m[4], + }; - return null -} + return null; +}; // function filters out bad semver versions and sorts the array module.exports.semver_sort = function semver_sort(array) { return array .filter(function(x) { if (!Semver.parse(x, true)) { - Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' ) - return false + Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' ); + return false; } - return true + return true; }) .sort(Semver.compareLoose) - .map(String) -} + .map(String); +}; // flatten arrays of tags -module.exports.normalize_dist_tags = function (data) { - var sorted +module.exports.normalize_dist_tags = function(data) { + let sorted; if (!data['dist-tags'].latest) { // overwrite latest with highest known version based on semver sort - sorted = module.exports.semver_sort(Object.keys(data.versions)) + sorted = module.exports.semver_sort(Object.keys(data.versions)); if (sorted && sorted.length) { - data['dist-tags'].latest = sorted.pop() + data['dist-tags'].latest = sorted.pop(); } } - for (var tag in data['dist-tags']) { + for (let tag in data['dist-tags']) { if (Array.isArray(data['dist-tags'][tag])) { if (data['dist-tags'][tag].length) { // sort array - sorted = module.exports.semver_sort(data['dist-tags'][tag]) + sorted = module.exports.semver_sort(data['dist-tags'][tag]); if (sorted.length) { // use highest version based on semver sort - data['dist-tags'][tag] = sorted.pop() + data['dist-tags'][tag] = sorted.pop(); } - } else { - delete data['dist-tags'][tag] + delete data['dist-tags'][tag]; } } else if (typeof data['dist-tags'][tag] === 'string') { if (!Semver.parse(data['dist-tags'][tag], true)) { // if the version is invalid, delete the dist-tag entry - delete data['dist-tags'][tag] + delete data['dist-tags'][tag]; } } } -} +}; diff --git a/package.json b/package.json index 9146408ef..ea4b301ea 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "async": "^2.0.1", "body-parser": "^1.15.0", "bunyan": "^1.8.0", + "chalk": "^1.1.3", "commander": "^2.9.0", "compression": "^1.6.1", "cookies": "^0.6.1", @@ -44,6 +45,7 @@ "browserify": "^13.0.0", "browserify-handlebars": "^1.0.0", "eslint": "^3.19.0", + "eslint-config-google": "^0.7.1", "grunt": "^1.0.1", "grunt-browserify": "^5.0.0", "grunt-cli": "^1.2.0", @@ -66,9 +68,9 @@ "server" ], "scripts": { - "test": "eslint . && mocha ./test/functional ./test/unit", + "test": "npm run lint && mocha ./test/functional ./test/unit", "test:coverage": "nyc --reporter=html --reporter=text mocha -R spec ./test/functional ./test/unit", - "test-travis": "eslint . && npm run test:coverage", + "test-travis": "npm run lint && npm run test:coverage", "test-only": "mocha ./test/functional ./test/unit", "lint": "eslint .", "build-docker": "docker build -t verdaccio .", diff --git a/test/.eslintrc b/test/.eslintrc index b3e16f8ee..04edb2ff9 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,5 +1,12 @@ +# vim: syntax=yaml + +extends: ["eslint:recommended"] env: node: true mocha: true + es6: true +valid-jsdoc: 0 +no-redeclare: 1 +no-console: 1 \ No newline at end of file diff --git a/test/functional/plugins/authorize.js b/test/functional/plugins/authorize.js index c3a68c541..50aac4f7c 100644 --- a/test/functional/plugins/authorize.js +++ b/test/functional/plugins/authorize.js @@ -18,12 +18,12 @@ Plugin.prototype.allow_access = function(user, pkg, cb) { return cb(null, false); } if (user.name !== self._config.allow_user) { - var err = Error('i don\'t know anything about you'); + let err = Error('i don\'t know anything about you'); err.status = 403; return cb(err); } if (pkg.name !== self._config.to_access) { - var err = Error('you\'re not allowed here'); + let err = Error('you\'re not allowed here'); err.status = 403; return cb(err); } diff --git a/test/functional/race.js b/test/functional/race.js index bd0af1866..a619b8000 100644 --- a/test/functional/race.js +++ b/test/functional/race.js @@ -59,7 +59,7 @@ module.exports = function() { it('uploading 10 diff versions', function(callback) { let fns = []; for (let i=0; i<10; i++) { - ;(function(i) { + (function(i) { fns.push(function(cb_) { let _res; server.put_version('race', '0.1.'+String(i), require('./lib/package')('race')) diff --git a/test/unit/no_proxy.js b/test/unit/no_proxy.js index 61e1fdbd7..d86ccfb65 100644 --- a/test/unit/no_proxy.js +++ b/test/unit/no_proxy.js @@ -22,13 +22,13 @@ describe('Use proxy', function() { }); it('no_proxy is invalid', function() { - var x = setup('http://x/x', {http_proxy: '123', no_proxy: false}, {}); + let x = setup('http://x/x', {http_proxy: '123', no_proxy: false}, {}); assert.equal(x.proxy, '123'); - var x = setup('http://x/x', {http_proxy: '123', no_proxy: null}, {}); + x = setup('http://x/x', {http_proxy: '123', no_proxy: null}, {}); assert.equal(x.proxy, '123'); - var x = setup('http://x/x', {http_proxy: '123', no_proxy: []}, {}); + x = setup('http://x/x', {http_proxy: '123', no_proxy: []}, {}); assert.equal(x.proxy, '123'); - var x = setup('http://x/x', {http_proxy: '123', no_proxy: ''}, {}); + x = setup('http://x/x', {http_proxy: '123', no_proxy: ''}, {}); assert.equal(x.proxy, '123'); }); @@ -43,48 +43,48 @@ describe('Use proxy', function() { }); it('no_proxy - various, single string', function() { - var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'}); + let x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'}); assert.equal(x.proxy, '123'); - var x = setup('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'}); + x = setup('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'}); assert.equal(x.proxy, null); - var x = setup('http://blahblah', {}, {http_proxy: '123', no_proxy: '.blah'}); + x = setup('http://blahblah', {}, {http_proxy: '123', no_proxy: '.blah'}); assert.equal(x.proxy, '123'); - var x = setup('http://blah.blah', {http_proxy: '123', no_proxy: '.blah'}, {}); + x = setup('http://blah.blah', {http_proxy: '123', no_proxy: '.blah'}, {}); assert.equal(x.proxy, null); - var x = setup('http://blah', {http_proxy: '123', no_proxy: '.blah'}, {}); + x = setup('http://blah', {http_proxy: '123', no_proxy: '.blah'}, {}); assert.equal(x.proxy, null); - var x = setup('http://blahh', {http_proxy: '123', no_proxy: 'blah'}, {}); + x = setup('http://blahh', {http_proxy: '123', no_proxy: 'blah'}, {}); assert.equal(x.proxy, '123'); }); it('no_proxy - various, array', function() { - var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); + let x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); assert.equal(x.proxy, '123'); - var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); + x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); assert.equal(x.proxy, null); - var x = setup('http://blah.foo', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); + x = setup('http://blah.foo', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); assert.equal(x.proxy, null); - var x = setup('http://foo.baz', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); + x = setup('http://foo.baz', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); assert.equal(x.proxy, '123'); - var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']}); + x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']}); assert.equal(x.proxy, '123'); - var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']}); + x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']}); assert.equal(x.proxy, null); }); it('no_proxy - hostport', function() { - var x = setup('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'}); + let x = setup('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'}); assert.equal(x.proxy, null); - var x = setup('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'}); + x = setup('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'}); assert.equal(x.proxy, null); }); it('no_proxy - secure', function() { - var x = setup('https://something', {http_proxy: '123'}, {}); + let x = setup('https://something', {http_proxy: '123'}, {}); assert.equal(x.proxy, null); - var x = setup('https://something', {https_proxy: '123'}, {}); + x = setup('https://something', {https_proxy: '123'}, {}); assert.equal(x.proxy, '123'); - var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {}); + x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {}); assert.equal(x.proxy, '123'); }); }); diff --git a/test/unit/search.js b/test/unit/search.js index bb995447f..837bc8f69 100644 --- a/test/unit/search.js +++ b/test/unit/search.js @@ -56,10 +56,10 @@ describe('search', function() { }, }; Search.add(item); - var result = Search.query('test6'); + let result = Search.query('test6'); assert(result.length === 1); Search.remove(item.name); - var result = Search.query('test6'); + result = Search.query('test6'); assert(result.length === 0); }); }); diff --git a/test/unit/validate_all.js b/test/unit/validate_all.js index 0e56eef19..cf88af0e3 100644 --- a/test/unit/validate_all.js +++ b/test/unit/validate_all.js @@ -23,9 +23,10 @@ function test(file) { var t; inner.split('/').forEach(function(x) { + t = x.match(/^:([^?:]*)\??$/); if (m[1] === 'param') { params[x] = 'ok'; - } else if (t = x.match(/^:([^?:]*)\??$/)) { + } else if (t) { params[t[1]] = params[t[1]] || m[0].trim(); } });