From c166c20701798bbaa07e15c33ed679e220faf62f Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 11:47:37 +0200 Subject: [PATCH 01/14] Remove es6 polyfills and use native Promises --- index.js | 1 - lib/cli.js | 1 - package.json | 2 -- test/functional/gzip.js | 1 - test/functional/index.js | 1 - test/functional/lib/server.js | 1 - test/functional/lib/smart_request.js | 1 - 7 files changed, 8 deletions(-) diff --git a/index.js b/index.js index baf766e56..787844980 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,3 @@ -require('es6-shim') module.exports = require('./lib') /**package diff --git a/lib/cli.js b/lib/cli.js index 9f95fcf8a..8e3cb7c8a 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -7,7 +7,6 @@ if (process.getuid && process.getuid() === 0) { } process.title = 'verdaccio' -require('es6-shim') try { // for debugging memory leaks diff --git a/package.json b/package.json index 46486c70d..3ef46df65 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "commander": "^2.9.0", "compression": "^1.6.1", "cookies": "^0.6.1", - "es6-shim": "^0.35.0", "express": "^4.13.4", "handlebars": "^4.0.5", "highlight.js": "^9.3.0", @@ -42,7 +41,6 @@ "unix-crypt-td-js": "^1.0.0" }, "devDependencies": { - "bluebird": "^3.3.5", "browserify": "^13.0.0", "browserify-handlebars": "^1.0.0", "eslint": "^2.9.0", diff --git a/test/functional/gzip.js b/test/functional/gzip.js index 64fbf2e6b..7af4c2b14 100644 --- a/test/functional/gzip.js +++ b/test/functional/gzip.js @@ -1,7 +1,6 @@ require('./lib/startup') var assert = require('assert') -var Promise = require('bluebird') function readfile(x) { return require('fs').readFileSync(__dirname + '/' + x) diff --git a/test/functional/index.js b/test/functional/index.js index 34df7fd1c..cf7daaa7e 100644 --- a/test/functional/index.js +++ b/test/functional/index.js @@ -1,6 +1,5 @@ //require('es6-shim') require('./lib/startup') -var Promise = require('bluebird') var assert = require('assert') var async = require('async') diff --git a/test/functional/lib/server.js b/test/functional/lib/server.js index 0a720b14c..9f9d64778 100644 --- a/test/functional/lib/server.js +++ b/test/functional/lib/server.js @@ -1,6 +1,5 @@ var assert = require('assert') var request = require('./smart_request') -var Promise = require('bluebird') function Server(url) { var self = Object.create(Server.prototype) diff --git a/test/functional/lib/smart_request.js b/test/functional/lib/smart_request.js index bae6022e5..3aee93c25 100644 --- a/test/functional/lib/smart_request.js +++ b/test/functional/lib/smart_request.js @@ -1,7 +1,6 @@ var assert = require('assert') var request = require('request') -var Promise = require('bluebird') var sym = Symbol('smart_request_data') function smart_request(options) { From 4edbd470880321160639f6d661426d086c1730cd Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 11:54:08 +0200 Subject: [PATCH 02/14] update eslint, add coverage to ignore list --- .eslintignore | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index f7eb78c62..35fe41826 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ node_modules lib/static +coverage/ \ No newline at end of file diff --git a/package.json b/package.json index 3ef46df65..f2735a109 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "devDependencies": { "browserify": "^13.0.0", "browserify-handlebars": "^1.0.0", - "eslint": "^2.9.0", + "eslint": "^3.19.0", "grunt": "^1.0.1", "grunt-browserify": "^5.0.0", "grunt-cli": "^1.2.0", From 92a9174603d449a706a3d4c7869b7d247cd7ba81 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 12:18:13 +0200 Subject: [PATCH 03/14] Update min node version, md5 dependency requires it --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2735a109..620ab5d5b 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "build-docker:rpi": "docker build -f Dockerfile.rpi -t verdaccio:rpi ." }, "engines": { - "node": ">=4" + "node": ">=4.6.1" }, "preferGlobal": true, "publishConfig": { From e50d143d969e69f1239638600c1fb502329af278 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 12:20:35 +0200 Subject: [PATCH 04/14] Allow use yarn on dev --- .gitignore | 4 ---- yarn.lock | Bin 0 -> 126601 bytes 2 files changed, 4 deletions(-) create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 95da521cf..541dea446 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,3 @@ coverage/ .jscsrc .jshintrc jsconfig.json - - -# Yarn -yarn* \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000000000000000000000000000000000..1fbc7c1dd711eac4f23b6ee5cdcdf23f428a2754 GIT binary patch literal 126601 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 literal 0 HcmV?d00001 From f51ea1f2064b6644be4355f506cebf65516c9e72 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 12:53:17 +0200 Subject: [PATCH 05/14] Force use npm on travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index abb19441a..badc3ea5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ node_js: - '6' - '7' sudo: false +install: + - npm install matrix: - allow_failures: - - node_js: 'iojs' fast_finish: true script: npm install . && npm run test-travis \ No newline at end of file From 69d51e88d85cb8e9bb48058f84d824a1fecb9a9b Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 12:57:03 +0200 Subject: [PATCH 06/14] clean minimach warning --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 620ab5d5b..d186bb4da 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "js-yaml": "^3.6.0", "lockfile": "^1.0.1", "lunr": "^0.7.0", - "minimatch": "^3.0.0", + "minimatch": "^3.0.2", "mkdirp": "^0.5.1", "pkginfo": "^0.4.0", "readable-stream": "^2.1.2", From d66aa7097a2081fa2db1710d995b8d2610ff5c7e Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 13:51:42 +0200 Subject: [PATCH 07/14] Update mocha, clean warning --- package.json | 2 +- yarn.lock | Bin 126601 -> 130474 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d186bb4da..98eb7bd0f 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "grunt-cli": "^1.2.0", "grunt-contrib-less": "^1.3.0", "grunt-contrib-watch": "^1.0.0", - "mocha": "^2.4.5", + "mocha": "^3.2.0", "nyc": "^10.1.2", "onclick": "^0.1.0", "rimraf": "^2.5.2", diff --git a/yarn.lock b/yarn.lock index 1fbc7c1dd711eac4f23b6ee5cdcdf23f428a2754..006914cf16ec40478e8270dafa6c5c200939018a 100644 GIT binary patch delta 7890 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 b6QtKtRiW<%o}ooWdfbGO++L@@hmHRSry5qP delta 15781 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 MIIryY-)Yr<01y?X2mk;8 From d484bb4f2f9ba94a9aa963bf245f8e492747642d Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 15:12:31 +0200 Subject: [PATCH 08/14] clean up phase, moving es6 local storage --- lib/auth.js | 10 ++-- lib/config.js | 24 ++++++--- lib/local-storage.js | 124 ++++++++++++++++++++----------------------- lib/plugin-loader.js | 4 +- lib/status-cats.js | 2 +- lib/storage.js | 2 + lib/up-storage.js | 12 +++-- lib/utils.js | 4 +- 8 files changed, 96 insertions(+), 86 deletions(-) diff --git a/lib/auth.js b/lib/auth.js index 1addcba6e..67f1e67b2 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,8 +1,8 @@ -var Crypto = require('crypto') -var jju = require('jju') -var Error = require('http-errors') -var Logger = require('./logger') -var load_plugins = require('./plugin-loader').load_plugins +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 diff --git a/lib/config.js b/lib/config.js index 50377e239..4d558a534 100644 --- a/lib/config.js +++ b/lib/config.js @@ -28,7 +28,10 @@ function Config(config) { for (var i in config) { if (self[i] == null) self[i] = config[i] } - if (!self.user_agent) self.user_agent = pkgName + '/' + pkgVersion + + if (!self.user_agent) { + self.user_agent = `${pkgName}/${pkgVersion}`; + } // 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') @@ -50,7 +53,13 @@ function Config(config) { } } - var users = {all:true, anonymous:true, 'undefined':true, owner:true, none:true} + var users = { + all: true, + anonymous: true, + 'undefined': 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) @@ -64,13 +73,16 @@ function Config(config) { assert(Utils.is_object(self[x]), 'CONFIG: bad "'+x+'" value (object expected)') }) - for (var i in self.users) check_user_or_uplink(i) - for (var i in self.uplinks) check_user_or_uplink(i) + for (var i in self.users) { + check_user_or_uplink(i); + } + for (var i in self.uplinks) { + check_user_or_uplink(i); + } for (var i in self.users) { assert(self.users[i].password, 'CONFIG: no password for user: ' + i) - assert( - typeof(self.users[i].password) === 'string' && + 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') } diff --git a/lib/local-storage.js b/lib/local-storage.js index 3a7dfd0d3..f41a8a6f5 100644 --- a/lib/local-storage.js +++ b/lib/local-storage.js @@ -1,3 +1,5 @@ +"use strict"; + var assert = require('assert') var async = require('async') var Crypto = require('crypto') @@ -63,14 +65,13 @@ Storage.prototype.add_package = function(name, info, callback) { } Storage.prototype.remove_package = function(name, callback) { - var self = this - self.logger.info( { name: name } + this.logger.info( { name: name } , 'unpublishing @{name} (all)') - var storage = self.storage(name) + var storage = this.storage(name) if (!storage) return callback( Error[404]('no such package available') ) - storage.read_json(info_file, function(err, data) { + storage.read_json(info_file, (err, data) => { if (err) { if (err.code === 'ENOENT') { return callback( Error[404]('no such package available') ) @@ -78,7 +79,7 @@ Storage.prototype.remove_package = function(name, callback) { return callback(err) } } - self._normalize_package(data) + this._normalize_package(data) storage.unlink(info_file, function(err) { if (err) return callback(err) @@ -108,24 +109,23 @@ Storage.prototype.remove_package = function(name, callback) { } Storage.prototype._read_create_package = function(name, callback) { - var self = this - var storage = self.storage(name) + var storage = this.storage(name) if (!storage) { var data = get_boilerplate(name) - self._normalize_package(data) + this._normalize_package(data) return callback(null, data) } - storage.read_json(info_file, function(err, 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) } else { - return callback(self._internal_error(err, info_file, 'error reading')) + return callback(this._internal_error(err, info_file, 'error reading')) } } - self._normalize_package(data) + this._normalize_package(data) callback(null, data) }) } @@ -133,8 +133,7 @@ Storage.prototype._read_create_package = function(name, callback) { // synchronize remote package info with the local one // TODO: readfile called twice Storage.prototype.update_versions = function(name, newdata, callback) { - var self = this - self._read_create_package(name, function(err, data) { + this._read_create_package(name, (err, data) => { if (err) return callback(err) var change = false @@ -164,7 +163,7 @@ Storage.prototype.update_versions = function(name, newdata, callback) { // // see https://github.com/rlidwka/sinopia/issues/166 var tarball_url = URL.parse(hash.url) - var uplink_url = URL.parse(self.config.uplinks[verdata._verdaccio_uplink].url) + var 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 @@ -197,8 +196,8 @@ Storage.prototype.update_versions = function(name, newdata, callback) { } if (change) { - self.logger.debug('updating package info') - self._write_package(name, data, function(err) { + this.logger.debug('updating package info') + this._write_package(name, data, function(err) { callback(err, data) }) } else { @@ -208,8 +207,7 @@ Storage.prototype.update_versions = function(name, newdata, callback) { } Storage.prototype.add_version = function(name, version, metadata, tag, callback) { - var self = this - self.update_package(name, function updater(data, cb) { + this.update_package(name, (data, cb) => { // keep only one readme per package data.readme = metadata.readme delete metadata.readme @@ -236,16 +234,14 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback) data.versions[version] = metadata Utils.tag_version(data, version, tag) - self.config.localList.add(name) + this.config.localList.add(name) cb() }, callback) } Storage.prototype.merge_tags = function(name, tags, callback) { - var self = this - - self.update_package(name, function updater(data, cb) { - for (var t in tags) { + this.update_package(name, function updater(data, cb) { + for (let t in tags) { if (tags[t] === null) { delete data['dist-tags'][t] continue @@ -262,12 +258,10 @@ Storage.prototype.merge_tags = function(name, tags, callback) { } Storage.prototype.replace_tags = function(name, tags, callback) { - var self = this - - self.update_package(name, function updater(data, cb) { + this.update_package(name, function updater(data, cb) { data['dist-tags'] = {} - for (var t in tags) { + for (let t in tags) { if (tags[t] === null) { delete data['dist-tags'][t] continue @@ -285,16 +279,15 @@ Storage.prototype.replace_tags = function(name, tags, callback) { // currently supports unpublishing only Storage.prototype.change_package = function(name, metadata, revision, callback) { - var self = this if (!Utils.is_object(metadata.versions) || !Utils.is_object(metadata['dist-tags'])) { return callback( Error[422]('bad data') ) } - self.update_package(name, function updater(data, cb) { - for (var ver in data.versions) { + this.update_package(name, (data, cb) => { + for (let ver in data.versions) { if (metadata.versions[ver] == null) { - self.logger.info( { name: name, version: ver } + this.logger.info( { name: name, version: ver } , 'unpublishing @{name}@@{version}') delete data.versions[ver] @@ -315,9 +308,8 @@ Storage.prototype.change_package = function(name, metadata, revision, callback) Storage.prototype.remove_tarball = function(name, filename, revision, callback) { assert(Utils.validate_name(filename)) - var self = this - self.update_package(name, function updater(data, cb) { + this.update_package(name, (data, cb) => { if (data._attachments[filename]) { delete data._attachments[filename] cb() @@ -326,7 +318,7 @@ Storage.prototype.remove_tarball = function(name, filename, revision, callback) } }, function(err) { if (err) return callback(err) - var storage = self.storage(name) + var storage = this.storage(name) if (storage) storage.unlink(filename, callback) }) } @@ -347,7 +339,6 @@ Storage.prototype.add_tarball = function(name, filename) { _transform.apply(stream, arguments) } - var self = this if (name === info_file || name === '__proto__') { process.nextTick(function() { stream.emit('error', Error[403]("can't use this filename")) @@ -355,7 +346,7 @@ Storage.prototype.add_tarball = function(name, filename) { return stream } - var storage = self.storage(name) + var storage = this.storage(name) if (!storage) { process.nextTick(function() { stream.emit('error', Error[404]("can't upload this package")) @@ -365,12 +356,12 @@ Storage.prototype.add_tarball = function(name, filename) { var wstream = storage.write_stream(filename) - wstream.on('error', function(err) { + wstream.on('error', (err) => { if (err.code === 'EEXISTS') { stream.emit('error', Error[409]('this tarball is already present')) } else if (err.code === 'ENOENT') { // check if package exists to throw an appropriate message - self.get_package(name, function(_err, res) { + this.get_package(name, function(_err, res) { if (_err) { stream.emit('error', _err) } else { @@ -386,8 +377,8 @@ Storage.prototype.add_tarball = function(name, filename) { // re-emitting open because it's handled in storage.js stream.emit('open') }) - wstream.on('success', function() { - self.update_package(name, function updater(data, cb) { + wstream.on('success', () => { + this.update_package(name, function updater(data, cb) { data._attachments[filename] = { shasum: shasum.digest('hex'), } @@ -453,41 +444,41 @@ Storage.prototype.get_tarball = function(name, filename, callback) { } Storage.prototype.get_package = function(name, options, callback) { - if (typeof(options) === 'function') callback = options, options = {} + if (typeof(options) === 'function') { + callback = options, options = {}; + } - var self = this - var storage = self.storage(name) + var storage = this.storage(name) if (!storage) return callback( Error[404]('no such package available') ) - storage.read_json(info_file, function(err, result) { + storage.read_json(info_file, (err, result) => { if (err) { if (err.code === 'ENOENT') { return callback( Error[404]('no such package available') ) } else { - return callback(self._internal_error(err, info_file, 'error reading')) + return callback(this._internal_error(err, info_file, 'error reading')) } } - self._normalize_package(result) + this._normalize_package(result) callback(err, result) }) } // walks through each package and calls `on_package` on them Storage.prototype._each_package = function (on_package, on_end) { - var self = this var storages = {} - storages[self.config.storage] = true + storages[this.config.storage] = true; - if (self.config.packages) { - Object.keys(self.packages || {}).map(function (pkg) { - if (self.config.packages[pkg].storage) { - storages[self.config.packages[pkg].storage] = true + if (this.config.packages) { + Object.keys(this.packages || {}).map( pkg => { + if (this.config.packages[pkg].storage) { + storages[this.config.packages[pkg].storage] = true } }) } - var base = Path.dirname(self.config.self_path); + 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) { @@ -502,7 +493,7 @@ Storage.prototype._each_package = function (on_package, on_end) { async.eachSeries(files, function (file2, cb) { if (Utils.validate_name(file2)) { on_package({ - name: file + '/' + file2, + name: `${file}/${file2}`, path: Path.resolve(base, storage, file, file2), }, cb) } else { @@ -583,16 +574,14 @@ Storage.prototype.update_package = function(name, updateFn, _callback) { } Storage.prototype.search = function(startkey, options) { - var self = this - var stream = new Stream.PassThrough({ objectMode: true }) - self._each_package(function on_package(item, cb) { - fs.stat(item.path, function(err, stats) { + this._each_package((item, cb) => { + fs.stat(item.path, (err, stats) => { if (err) return cb(err) if (stats.mtime > startkey) { - self.get_package(item.name, options, function(err, data) { + this.get_package(item.name, options, function(err, data) { if (err) return cb(err) var versions = Utils.semver_sort(Object.keys(data.versions)) @@ -635,8 +624,9 @@ Storage.prototype._normalize_package = function(pkg) { ;['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' - + if (typeof(pkg._rev) !== 'string') { + pkg._rev = '0-0000000000000000'; + } // normalize dist-tags Utils.normalize_dist_tags(pkg) } @@ -644,7 +634,9 @@ Storage.prototype._normalize_package = function(pkg) { Storage.prototype._write_package = function(name, json, callback) { // calculate revision a la couchdb - if (typeof(json._rev) !== 'string') json._rev = '0-0000000000000000' + 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') @@ -653,18 +645,18 @@ Storage.prototype._write_package = function(name, json, callback) { storage.write_json(info_file, json, callback) } -Storage.prototype.storage = function(package) { - var path = this.config.get_package_spec(package).storage +Storage.prototype.storage = function(pkg) { + var path = this.config.get_package_spec(pkg).storage if (path == null) path = this.config.storage if (path == null || path === false) { - this.logger.debug( { name: package } + 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), - package + pkg ) ) } diff --git a/lib/plugin-loader.js b/lib/plugin-loader.js index 17d84203c..c12ea036e 100644 --- a/lib/plugin-loader.js +++ b/lib/plugin-loader.js @@ -20,10 +20,10 @@ function load_plugins(config, plugin_configs, params, sanity_check) { // 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}`) } } diff --git a/lib/status-cats.js b/lib/status-cats.js index 5c0016a86..df328d02b 100644 --- a/lib/status-cats.js +++ b/lib/status-cats.js @@ -1,7 +1,7 @@ // see https://secure.flickr.com/photos/girliemac/sets/72157628409467125 -var images = { +const images = { 100: 'aVvDhR', // '6512768893', // 100 - Continue 101: 'aXXExP', // '6540479029', // 101 - Switching Protocols 200: 'aVuVsF', // '6512628175', // 200 - OK diff --git a/lib/storage.js b/lib/storage.js index 823bd5281..5abd84ebf 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -1,3 +1,5 @@ +"use strict"; + var assert = require('assert') var async = require('async') var Error = require('http-errors') diff --git a/lib/up-storage.js b/lib/up-storage.js index 8f7606d4d..b94863165 100644 --- a/lib/up-storage.js +++ b/lib/up-storage.js @@ -1,3 +1,5 @@ +"use strict"; + var JSONStream = require('JSONStream') var Error = require('http-errors') var request = require('request') @@ -68,12 +70,14 @@ function _setupProxy(hostname, config, mainconfig, isHTTPS) { } // use wget-like algorithm to determine if proxy shouldn't be used - if (hostname[0] !== '.') hostname = '.' + hostname + if (hostname[0] !== '.') { + hostname = '.' + hostname; + } if (typeof(no_proxy) === 'string' && no_proxy.length) { no_proxy = no_proxy.split(',') } if (Array.isArray(no_proxy)) { - for (var i=0; i Date: Mon, 17 Apr 2017 15:52:36 +0200 Subject: [PATCH 09/14] Migrate Search to class --- lib/search.js | 104 ++++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/lib/search.js b/lib/search.js index 6bed026da..b04d617f9 100644 --- a/lib/search.js +++ b/lib/search.js @@ -1,49 +1,61 @@ -var lunr = require('lunr') +"use strict"; -function Search() { - var self = Object.create(Search.prototype) - self.index = lunr(function() { - this.field('name' , { boost: 10 }) - this.field('description' , { boost: 4 }) - this.field('author' , { boost: 6 }) - this.field('readme') - }) - return self +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') + }) + } + + query(q) { + return q === '*' + ? this.storage.config.localList.get().map( function( pkg ) { + 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 : '???', + }) + } + + remove(name) { + this.index.remove({ id: name }) + } + + reindex() { + var self = this + this.storage.get_local(function(err, packages) { + if (err) throw err // that function shouldn't produce any + var i = packages.length + while (i--) { + self.add(packages[i]) + } + }) + } + + configureStorage(storage) { + this.storage = storage + this.reindex() + } } -Search.prototype.query = function(q) { - return q === '*' - ? this.storage.config.localList.get().map( function( package ){ return { ref: package, score: 1 }; } ) - : this.index.search(q); -} - -Search.prototype.add = function(package) { - this.index.add({ - id: package.name, - name: package.name, - description: package.description, - author: package._npmUser ? package._npmUser.name : '???', - }) -}, - -Search.prototype.remove = function(name) { - this.index.remove({ id: name }) -} - -Search.prototype.reindex = function() { - var self = this - this.storage.get_local(function(err, packages) { - if (err) throw err // that function shouldn't produce any - var i = packages.length - while (i--) { - self.add(packages[i]) - } - }) -} - -Search.prototype.configureStorage = function(storage) { - this.storage = storage - this.reindex() -} - -module.exports = Search() +module.exports = new Search(); From e267ef1f548f3b8e0a659b8380b964b962818b53 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 17 Apr 2017 16:10:21 +0200 Subject: [PATCH 10/14] Migrate Storages to classes --- lib/config.js | 2 +- lib/local-data.js | 68 +++++++++++++++++++++++--------------------- lib/local-fs.js | 12 ++++---- lib/local-storage.js | 10 +++---- lib/search.js | 2 +- lib/storage.js | 2 +- test/unit/search.js | 2 +- 7 files changed, 51 insertions(+), 47 deletions(-) diff --git a/lib/config.js b/lib/config.js index 4d558a534..1c0b54511 100644 --- a/lib/config.js +++ b/lib/config.js @@ -37,7 +37,7 @@ function Config(config) { assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file') assert(self.storage, 'CONFIG: storage path not defined') - self.localList = LocalData( + self.localList = new LocalData( Path.join( Path.resolve(Path.dirname(self.self_path || ''), self.storage), '.sinopia-db.json' diff --git a/lib/local-data.js b/lib/local-data.js index bc050562b..8c2fe8899 100644 --- a/lib/local-data.js +++ b/lib/local-data.js @@ -1,44 +1,46 @@ -var fs = require('fs') -var Path = require('path') +"use strict"; -module.exports = LocalData +const fs = require('fs'); +const Path = require('path'); -function LocalData(path) { - var self = Object.create(LocalData.prototype) - self.path = path - try { - self.data = JSON.parse(fs.readFileSync(self.path, 'utf8')) - } catch(_) { - self.data = { list: [] } + class LocalData { + + constructor(path) { + this.path = path + try { + this.data = JSON.parse(fs.readFileSync(this.path, 'utf8')) + } catch(_) { + this.data = { list: [] } + } } - return self -} -LocalData.prototype.add = function(name) { - if (this.data.list.indexOf(name) === -1) { - this.data.list.push(name) + add(name) { + if (this.data.list.indexOf(name) === -1) { + this.data.list.push(name) + this.sync() + } + } + + remove(name) { + const i = this.data.list.indexOf(name) + if (i !== -1) { + this.data.list.splice(i, 1) + } this.sync() } -} -LocalData.prototype.remove = function(name) { - var i = this.data.list.indexOf(name) - if (i !== -1) { - this.data.list.splice(i, 1) + get() { + return this.data.list + } + + sync() { + // Uses sync to prevent ugly race condition + try { + require('mkdirp').sync(Path.dirname(this.path)) + } catch(err) {} + fs.writeFileSync(this.path, JSON.stringify(this.data)) } - this.sync() -} - -LocalData.prototype.get = function() { - return this.data.list -} - -LocalData.prototype.sync = function() { - // Uses sync to prevent ugly race condition - try { - require('mkdirp').sync(Path.dirname(this.path)) - } catch(err) {} - fs.writeFileSync(this.path, JSON.stringify(this.data)) } +module.exports = LocalData; diff --git a/lib/local-fs.js b/lib/local-fs.js index 8d7a66719..efa4626c1 100644 --- a/lib/local-fs.js +++ b/lib/local-fs.js @@ -1,8 +1,10 @@ -var fs = require('fs') -var Error = require('http-errors') -var mkdirp = require('mkdirp') -var Path = require('path') -var MyStreams = require('./streams') +"use strict"; + +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) diff --git a/lib/local-storage.js b/lib/local-storage.js index f41a8a6f5..3c688c8df 100644 --- a/lib/local-storage.js +++ b/lib/local-storage.js @@ -19,11 +19,11 @@ var info_file = 'package.json' // Implements Storage interface // (same for storage.js, local-storage.js, up-storage.js) // -function Storage(config) { - var self = Object.create(Storage.prototype) - self.config = config - self.logger = Logger.logger.child({ sub: 'fs' }) - return self +class Storage { + constructor(config) { + this.config = config + this.logger = Logger.logger.child({ sub: 'fs' }) + } } // returns the minimal package file diff --git a/lib/search.js b/lib/search.js index b04d617f9..740e0ab47 100644 --- a/lib/search.js +++ b/lib/search.js @@ -15,7 +15,7 @@ class Search { 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); } diff --git a/lib/storage.js b/lib/storage.js index 5abd84ebf..bab9fc159 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -27,7 +27,7 @@ function Storage(config) { self.uplinks[p] = Proxy(config.uplinks[p], config) self.uplinks[p].upname = p } - self.local = Local(config) + self.local = new Local(config) self.logger = Logger.logger.child() return self diff --git a/test/unit/search.js b/test/unit/search.js index 7c2bebdca..30cfa69e1 100644 --- a/test/unit/search.js +++ b/test/unit/search.js @@ -27,7 +27,7 @@ var packages = [ _npmUser: { name: 'test_user', } - }, + }, ] describe('search', function() { From 20f9436672952edf525e1fac97051ed556833a3d Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Wed, 19 Apr 2017 20:45:48 +0200 Subject: [PATCH 11/14] Migrate storages to classes --- lib/index.js | 8 +- lib/local-storage.js | 1242 +++++++++++++++++++++++------------------- lib/storage.js | 944 ++++++++++++++++---------------- lib/up-storage.js | 111 ++-- 4 files changed, 1224 insertions(+), 1081 deletions(-) diff --git a/lib/index.js b/lib/index.js index 8e9c8ecc8..c669fc01c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,10 +11,10 @@ var Storage = require('./storage') module.exports = function(config_hash) { Logger.setup(config_hash.logs) - var config = Config(config_hash) - var storage = Storage(config) - var auth = Auth(config) - var app = express() + var config = Config(config_hash); + var storage = new Storage(config); + var auth = Auth(config); + var app = express(); // run in production mode by default, just in case // it shouldn't make any difference anyway diff --git a/lib/local-storage.js b/lib/local-storage.js index 3c688c8df..a3e7a7f3a 100644 --- a/lib/local-storage.js +++ b/lib/local-storage.js @@ -1,30 +1,19 @@ "use strict"; -var assert = require('assert') -var async = require('async') -var Crypto = require('crypto') -var fs = require('fs') -var Error = require('http-errors') -var Path = require('path') -var Stream = require('readable-stream') -var URL = require('url') -var fs_storage = require('./local-fs') -var Logger = require('./logger') -var Search = require('./search') -var MyStreams = require('./streams') -var Utils = require('./utils') -var info_file = 'package.json' - -// -// Implements Storage interface -// (same for storage.js, local-storage.js, up-storage.js) -// -class Storage { - constructor(config) { - this.config = config - this.logger = Logger.logger.child({ sub: 'fs' }) - } -} +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'; // returns the minimal package file function get_boilerplate(name) { @@ -39,626 +28,739 @@ function get_boilerplate(name) { '_attachments': {}, '_uplinks': {}, } -} +}; -Storage.prototype._internal_error = function(err, file, message) { - this.logger.error( { err: err, file: file } - , message + ' @{file}: @{!err.message}' ) - return Error[500]() -} +// +// Implements Storage interface +// (same for storage.js, local-storage.js, up-storage.js) +// +class Storage { + constructor(config) { + this.config = config + this.logger = Logger.logger.child({ sub: 'fs' }) + } -Storage.prototype.add_package = function(name, info, callback) { - var storage = this.storage(name) - if (!storage) return callback( Error[404]('this package cannot be added') ) + /** + * + * @param {*} err + * @param {*} file + * @param {*} message + */ + _internal_error(err, file, message) { + this.logger.error( { err: err, file: file } + , message + ' @{file}: @{!err.message}' ) + return Error[500]() + } - storage.create_json(info_file, get_boilerplate(name), function(err) { - if (err && err.code === 'EEXISTS') { - return callback( Error[409]('this package is already present') ) - } + /** + * + * @param {*} name + * @param {*} info + * @param {*} callback + */ + add_package(name, info, callback) { + var storage = this.storage(name) + if (!storage) return callback( Error[404]('this package cannot be added') ) - var latest = info['dist-tags'].latest - if (latest && info.versions[latest]) { - Search.add(info.versions[latest]) - } - callback() - }) -} - -Storage.prototype.remove_package = function(name, callback) { - this.logger.info( { name: name } - , 'unpublishing @{name} (all)') - - var 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') ) - } else { - return callback(err) - } - } - this._normalize_package(data) - - storage.unlink(info_file, function(err) { - if (err) return callback(err) - - var files = Object.keys(data._attachments) - - function unlinkNext(cb) { - if (files.length === 0) return cb() - - var file = files.shift() - storage.unlink(file, function() { - unlinkNext(cb) - }) + storage.create_json(info_file, get_boilerplate(name), function(err) { + if (err && err.code === 'EEXISTS') { + return callback( Error[409]('this package is already present') ) } - unlinkNext(function() { - // try to unlink the directory, but ignore errors because it can fail - storage.rmdir('.', function(err) { - callback(err) + var latest = info['dist-tags'].latest + if (latest && info.versions[latest]) { + Search.add(info.versions[latest]) + } + callback() + }) + } + + /** + * + * @param {*} name + * @param {*} callback + */ + remove_package(name, callback) { + this.logger.info( { name: name } + , 'unpublishing @{name} (all)') + + var 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') ) + } else { + return callback(err) + } + } + this._normalize_package(data) + + storage.unlink(info_file, function(err) { + if (err) return callback(err) + + var files = Object.keys(data._attachments) + + function unlinkNext(cb) { + if (files.length === 0) return cb() + + var file = files.shift() + storage.unlink(file, function() { + unlinkNext(cb) + }) + } + + unlinkNext(function() { + // try to unlink the directory, but ignore errors because it can fail + storage.rmdir('.', function(err) { + callback(err) + }) }) }) }) - }) - Search.remove(name) - this.config.localList.remove(name) -} - -Storage.prototype._read_create_package = function(name, callback) { - var storage = this.storage(name) - if (!storage) { - var data = get_boilerplate(name) - this._normalize_package(data) - return callback(null, data) + Search.remove(name) + this.config.localList.remove(name) } - 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) - } else { - return callback(this._internal_error(err, info_file, 'error reading')) - } + + /** + * + * @param {*} name + * @param {*} callback + */ + _read_create_package(name, callback) { + var storage = this.storage(name) + if (!storage) { + var data = get_boilerplate(name) + this._normalize_package(data) + return callback(null, data) } - this._normalize_package(data) - 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) + } else { + return callback(this._internal_error(err, info_file, 'error reading')) + } + } + this._normalize_package(data) + callback(null, data) + }) + } -// synchronize remote package info with the local one -// TODO: readfile called twice -Storage.prototype.update_versions = function(name, newdata, callback) { - this._read_create_package(name, (err, data) => { - if (err) return callback(err) + /** + * Synchronize remote package info with the local one + * @param {*} name + * @param {*} newdata + * @param {*} callback + */ + update_versions(name, newdata, callback) { + this._read_create_package(name, (err, data) => { + if (err) return callback(err) - var change = false - for (var ver in newdata.versions) { - if (data.versions[ver] == null) { - var verdata = newdata.versions[ver] + var change = false + for (var ver in newdata.versions) { + if (data.versions[ver] == null) { + var verdata = newdata.versions[ver] - // we don't keep readmes for package versions, - // only one readme per package - delete verdata.readme + // we don't keep readmes for package versions, + // only one readme per package + 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(/^.*\//, '') - // we do NOT overwrite any existing records - if (data._distfiles[filename] == null) { - var hash = data._distfiles[filename] = { - url: verdata.dist.tarball, - sha: verdata.dist.shasum, - } - - 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) - if (uplink_url.host === tarball_url.host) { - tarball_url.protocol = uplink_url.protocol - hash.registry = verdata._verdaccio_uplink - hash.url = URL.format(tarball_url) + if (verdata.dist && verdata.dist.tarball) { + var 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] = { + 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) + if (uplink_url.host === tarball_url.host) { + tarball_url.protocol = uplink_url.protocol + hash.registry = verdata._verdaccio_uplink + hash.url = URL.format(tarball_url) + } } } } } } - } - for (var 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] - } - } - for (var up in newdata._uplinks) { - var need_change = !Utils.is_object(data._uplinks[up]) - || newdata._uplinks[up].etag !== data._uplinks[up].etag - || newdata._uplinks[up].fetched !== data._uplinks[up].fetched - - if (need_change) { - change = true - data._uplinks[up] = newdata._uplinks[up] - } - } - if (newdata.readme !== data.readme) { - data.readme = newdata.readme - change = true - } - - if (change) { - this.logger.debug('updating package info') - this._write_package(name, data, function(err) { - callback(err, data) - }) - } else { - callback(null, data) - } - }) -} - -Storage.prototype.add_version = function(name, version, metadata, tag, callback) { - this.update_package(name, (data, cb) => { - // keep only one readme per package - data.readme = metadata.readme - delete metadata.readme - - if (data.versions[version] != null) { - 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(/.*\//, '') - 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) ) - } + for (var 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] } - - data._attachments[tarball].version = version } - } + for (var up in newdata._uplinks) { + var need_change = !Utils.is_object(data._uplinks[up]) + || newdata._uplinks[up].etag !== data._uplinks[up].etag + || newdata._uplinks[up].fetched !== data._uplinks[up].fetched - data.versions[version] = metadata - Utils.tag_version(data, version, tag) - this.config.localList.add(name) - cb() - }, callback) -} - -Storage.prototype.merge_tags = function(name, tags, callback) { - this.update_package(name, function updater(data, cb) { - for (let t in tags) { - if (tags[t] === null) { - delete data['dist-tags'][t] - continue + if (need_change) { + change = true + data._uplinks[up] = newdata._uplinks[up] + } + } + if (newdata.readme !== data.readme) { + data.readme = newdata.readme + change = true } - if (data.versions[tags[t]] == null) { - return cb( Error[404]("this version doesn't exist") ) + if (change) { + this.logger.debug('updating package info') + this._write_package(name, data, function(err) { + callback(err, data) + }) + } else { + callback(null, data) } - - Utils.tag_version(data, tags[t], t) - } - cb() - }, callback) -} - -Storage.prototype.replace_tags = function(name, tags, callback) { - this.update_package(name, function updater(data, cb) { - data['dist-tags'] = {} - - for (let t in tags) { - if (tags[t] === null) { - delete data['dist-tags'][t] - continue - } - - if (data.versions[tags[t]] == null) { - return cb( Error[404]("this version doesn't exist") ) - } - - Utils.tag_version(data, tags[t], t) - } - cb() - }, callback) -} - -// currently supports unpublishing only -Storage.prototype.change_package = function(name, metadata, revision, callback) { - - if (!Utils.is_object(metadata.versions) || !Utils.is_object(metadata['dist-tags'])) { - 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] + /** + * + * @param {*} name + * @param {*} version + * @param {*} metadata + * @param {*} tag + * @param {*} callback + */ + 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 - for (var file in data._attachments) { - if (data._attachments[file].version === ver) { - delete data._attachments[file].version + if (data.versions[version] != null) { + 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(/.*\//, '') + 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) ) + } } + data._attachments[tarball].version = version } } - } - data['dist-tags'] = metadata['dist-tags'] - cb() - }, function(err) { - if (err) return callback(err) - callback() - }) -} -Storage.prototype.remove_tarball = function(name, filename, revision, callback) { - assert(Utils.validate_name(filename)) - - this.update_package(name, (data, cb) => { - if (data._attachments[filename]) { - delete data._attachments[filename] + data.versions[version] = metadata + Utils.tag_version(data, version, tag) + this.config.localList.add(name) cb() - } else { - 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) - }) -} - -Storage.prototype.add_tarball = function(name, filename) { - assert(Utils.validate_name(filename)) - - var stream = MyStreams.UploadTarballStream() - var _transform = stream._transform - var length = 0 - var shasum = Crypto.createHash('sha1') - - stream.abort = stream.done = function(){} - - stream._transform = function(data) { - shasum.update(data) - length += data.length - _transform.apply(stream, arguments) + }, callback) } - if (name === info_file || name === '__proto__') { - process.nextTick(function() { - stream.emit('error', Error[403]("can't use this filename")) - }) - return stream - } - - var storage = this.storage(name) - if (!storage) { - process.nextTick(function() { - stream.emit('error', Error[404]("can't upload this package")) - }) - return stream - } - - var wstream = storage.write_stream(filename) - - wstream.on('error', (err) => { - if (err.code === 'EEXISTS') { - 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) - } else { - stream.emit('error', err) - } - }) - } else { - stream.emit('error', err) - } - }) - - wstream.on('open', function() { - // re-emitting open because it's handled in storage.js - stream.emit('open') - }) - wstream.on('success', () => { + /** + * + * @param {*} name + * @param {*} tags + * @param {*} callback + */ + merge_tags(name, tags, callback) { this.update_package(name, function updater(data, cb) { - data._attachments[filename] = { - shasum: shasum.digest('hex'), + for (let t in tags) { + if (tags[t] === null) { + delete data['dist-tags'][t] + continue + } + + if (data.versions[tags[t]] == null) { + return cb( Error[404]("this version doesn't exist") ) + } + + Utils.tag_version(data, tags[t], t) } cb() + }, callback) + } + + /** + * + * @param {*} name + * @param {*} tags + * @param {*} callback + */ + replace_tags(name, tags, callback) { + this.update_package(name, function updater(data, cb) { + data['dist-tags'] = {} + + for (let t in tags) { + if (tags[t] === null) { + delete data['dist-tags'][t] + continue + } + + if (data.versions[tags[t]] == null) { + return cb( Error[404]("this version doesn't exist") ) + } + + Utils.tag_version(data, tags[t], t) + } + cb() + }, callback) + } + + /** + * Currently supports unpublishing only + * @param {*} name + * @param {*} metadata + * @param {*} revision + * @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') ) + } + + 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] + + for (var file in data._attachments) { + if (data._attachments[file].version === ver) { + delete data._attachments[file].version + } + } + } + } + data['dist-tags'] = metadata['dist-tags'] + cb() }, function(err) { - if (err) { - stream.emit('error', err) + if (err) return callback(err) + callback() + }) + } + + /** + * + * @param {*} name + * @param {*} filename + * @param {*} revision + * @param {*} callback + */ + remove_tarball(name, filename, revision, callback) { + assert(Utils.validate_name(filename)) + + this.update_package(name, (data, cb) => { + if (data._attachments[filename]) { + delete data._attachments[filename] + cb() } else { - stream.emit('success') + 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) + }) + } + + /** + * + * @param {*} name + * @param {*} filename + */ + add_tarball(name, filename) { + assert(Utils.validate_name(filename)) + + var stream = MyStreams.UploadTarballStream() + var _transform = stream._transform + var length = 0 + var shasum = Crypto.createHash('sha1') + + stream.abort = stream.done = function(){} + + stream._transform = function(data) { + 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 + } + + var storage = this.storage(name) + if (!storage) { + process.nextTick(function() { + stream.emit('error', Error[404]("can't upload this package")) + }) + return stream + } + + var wstream = storage.write_stream(filename) + + wstream.on('error', (err) => { + if (err.code === 'EEXISTS') { + 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) + } else { + stream.emit('error', err) + } + }) + } else { + stream.emit('error', err) } }) - }) - stream.abort = function() { - wstream.abort() - } - stream.done = function() { - if (!length) { - stream.emit('error', Error[422]('refusing to accept zero-length file')) + + wstream.on('open', function() { + // re-emitting open because it's handled in storage.js + stream.emit('open') + }) + wstream.on('success', () => { + this.update_package(name, function updater(data, cb) { + data._attachments[filename] = { + shasum: shasum.digest('hex'), + } + cb() + }, function(err) { + if (err) { + stream.emit('error', err) + } else { + stream.emit('success') + } + }) + }) + stream.abort = function() { wstream.abort() - } else { - wstream.done() } - } - stream.pipe(wstream) + stream.done = function() { + if (!length) { + stream.emit('error', Error[422]('refusing to accept zero-length file')) + wstream.abort() + } else { + wstream.done() + } + } + stream.pipe(wstream) - return stream -} - -Storage.prototype.get_tarball = function(name, filename, callback) { - assert(Utils.validate_name(filename)) - var self = this - - var stream = MyStreams.ReadTarballStream() - stream.abort = function() { - if (rstream) rstream.abort() + return stream } - var storage = self.storage(name) - if (!storage) { - process.nextTick(function() { - stream.emit('error', Error[404]('no such file available')) + /** + * + * @param {*} name + * @param {*} filename + * @param {*} callback + */ + get_tarball(name, filename, callback) { + assert(Utils.validate_name(filename)) + var self = this + + var stream = MyStreams.ReadTarballStream() + stream.abort = function() { + if (rstream) rstream.abort() + } + + var storage = self.storage(name) + if (!storage) { + process.nextTick(function() { + stream.emit('error', Error[404]('no such file available')) + }) + return stream + } + + 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')) + } else { + stream.emit('error', err) + } + }) + rstream.on('content-length', function(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 } - 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')) - } else { - stream.emit('error', err) + /** + * + * @param {*} name + * @param {*} options + * @param {*} callback + */ + get_package(name, options, callback) { + if (typeof(options) === 'function') { + callback = options, options = {}; } - }) - rstream.on('content-length', function(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 -} -Storage.prototype.get_package = function(name, options, callback) { - if (typeof(options) === 'function') { - callback = options, options = {}; - } + var storage = this.storage(name) + if (!storage) return callback( Error[404]('no such package available') ) - var 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') ) - } else { - return callback(this._internal_error(err, info_file, 'error reading')) - } - } - this._normalize_package(result) - callback(err, result) - }) -} - -// walks through each package and calls `on_package` on them -Storage.prototype._each_package = function (on_package, on_end) { - var storages = {} - - storages[this.config.storage] = true; - - if (this.config.packages) { - Object.keys(this.packages || {}).map( pkg => { - if (this.config.packages[pkg].storage) { - 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(files, function (file, cb) { - if (file.match(/^@/)) { - // scoped - fs.readdir(Path.resolve(base, storage, file), function (err, files) { - if (err) return cb(err) - - async.eachSeries(files, function (file2, cb) { - if (Utils.validate_name(file2)) { - on_package({ - name: `${file}/${file2}`, - path: Path.resolve(base, storage, file, file2), - }, cb) - } else { - cb() - } - }, cb) - }) - } else if (Utils.validate_name(file)) { - on_package({ - name: file, - path: Path.resolve(base, storage, file) - }, cb) + storage.read_json(info_file, (err, result) => { + if (err) { + if (err.code === 'ENOENT') { + return callback( Error[404]('no such package available') ) } else { - cb() + return callback(this._internal_error(err, info_file, 'error reading')) } - }, cb) + } + this._normalize_package(result) + callback(err, result) }) - }, on_end) -} + } -// -// This function allows to update the package thread-safely -// -// Arguments: -// - name - package name -// - updateFn - function(package, cb) - update function -// - callback - callback that gets invoked after it's all updated -// -// Algorithm: -// 1. lock package.json for writing -// 2. read package.json -// 3. updateFn(pkg, cb), and wait for cb -// 4. write package.json.tmp -// 5. move package.json.tmp package.json -// 6. callback(err?) -// -Storage.prototype.update_package = function(name, updateFn, _callback) { - var self = this - var 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 + /** + * Walks through each package and calls `on_package` on them + * @param {*} on_package + * @param {*} on_end + */ + _each_package(on_package, on_end) { + var storages = {} - // callback that cleans up lock first - function callback(err) { - var _args = arguments - if (locked) { - storage.unlock_file(info_file, function () { - // ignore any error from the unlock - _callback.apply(err, _args) + storages[this.config.storage] = true; + + if (this.config.packages) { + Object.keys(this.packages || {}).map( pkg => { + if (this.config.packages[pkg].storage) { + 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(files, function (file, cb) { + if (file.match(/^@/)) { + // scoped + fs.readdir(Path.resolve(base, storage, file), function (err, files) { + if (err) return cb(err) + + async.eachSeries(files, function (file2, cb) { + if (Utils.validate_name(file2)) { + on_package({ + name: `${file}/${file2}`, + path: Path.resolve(base, storage, file, file2), + }, cb) + } else { + cb() + } + }, cb) + }) + } else if (Utils.validate_name(file)) { + on_package({ + name: file, + path: Path.resolve(base, storage, file) + }, cb) + } else { + cb() + } + }, cb) }) - } else { - _callback.apply(null, _args) - } + }, on_end); } - if (!err) { - locked = true - } + /** + * This function allows to update the package thread-safely + Algorithm: + 1. lock package.json for writing + 2. read package.json + 3. updateFn(pkg, cb), and wait for cb + 4. write package.json.tmp + 5. move package.json.tmp package.json + 6. callback(err?) + * @param {*} name package name + * @param {*} updateFn function(package, cb) - update function + * @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') ) + storage.lock_and_read_json(info_file, function(err, json) { + var locked = false - if (err) { - if (err.code === 'EAGAIN') { - return callback( Error[503]('resource temporarily unavailable') ) - } else if (err.code === 'ENOENT') { - return callback( Error[404]('no such package available') ) - } else { - return callback(err) - } - } - - self._normalize_package(json) - updateFn(json, function(err) { - if (err) return callback(err) - - self._write_package(name, json, callback) - }) - }) -} - -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]._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 }, - versions : {}, + // callback that cleans up lock first + function callback(err) { + var _args = arguments + if (locked) { + storage.unlock_file(info_file, function () { + // ignore any error from the unlock + _callback.apply(err, _args) }) + } else { + _callback.apply(null, _args) + } + } + + if (!err) { + locked = true + } + + if (err) { + if (err.code === 'EAGAIN') { + return callback( Error[503]('resource temporarily unavailable') ) + } else if (err.code === 'ENOENT') { + return callback( Error[404]('no such package available') ) + } else { + return callback(err) + } + } + + self._normalize_package(json) + updateFn(json, function(err) { + if (err) return callback(err) + + self._write_package(name, json, callback) + }) + }) + } + + /** + * + * @param {*} startkey + * @param {*} options + */ + search(startkey, options) { + const stream = new Stream.PassThrough({ objectMode: true }); + + this._each_package((item, cb) => { + fs.stat(item.path, (err, stats) => { + if (err) { + return cb(err); } - cb() + 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]._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 + }, + versions : {}, + }) + } + + cb() + }) + } else { + cb() + } }) - } else { - cb() + }, function on_end(err) { + if (err) return stream.emit('error', err) + stream.end() + }) + + return stream + } + + /** + * + * @param {*} pkg + */ + _normalize_package(pkg) { + ;['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'; } - }) - }, function on_end(err) { - if (err) return stream.emit('error', err) - stream.end() - }) + // normalize dist-tags + Utils.normalize_dist_tags(pkg) + } - return stream -} + /** + * + * @param {*} name + * @param {*} json + * @param {*} callback + */ + _write_package(name, json, callback) { -Storage.prototype._normalize_package = function(pkg) { - ;['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) -} + // 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') -Storage.prototype._write_package = function(name, json, callback) { + var storage = this.storage(name) + if (!storage) return callback() + storage.write_json(info_file, 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') - - var storage = this.storage(name) - if (!storage) return callback() - storage.write_json(info_file, json, callback) -} - -Storage.prototype.storage = function(pkg) { - var path = this.config.get_package_spec(pkg).storage - if (path == null) path = this.config.storage - if (path == null || path === false) { - 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 - ) - ) + /** + * + * @param {*} pkg + */ + storage(pkg) { + let path = this.config.get_package_spec(pkg).storage + if (path == null) { + path = this.config.storage + } + if (path == null || path === false) { + 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() { diff --git a/lib/storage.js b/lib/storage.js index bab9fc159..5b0ac16e3 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -1,523 +1,555 @@ "use strict"; -var assert = require('assert') -var async = require('async') -var Error = require('http-errors') -var Stream = require('stream') -var Local = require('./local-storage') -var Logger = require('./logger') -var MyStreams = require('./streams') -var Proxy = require('./up-storage') -var Utils = require('./utils') - -module.exports = Storage +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 // (same for storage.js, local-storage.js, up-storage.js) // -function Storage(config) { - var self = Object.create(Storage.prototype) - self.config = config +class Storage { - // we support a number of uplinks, but only one local storage - // Proxy and Local classes should have similar API interfaces - self.uplinks = {} - for (var p in config.uplinks) { - self.uplinks[p] = Proxy(config.uplinks[p], config) - self.uplinks[p].upname = p + /** + * + * @param {*} config + */ + constructor(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 = {} + 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 + } + // an instance for local storage + this.local = new Local(config) + this.logger = Logger.logger.child(); } - self.local = new Local(config) - self.logger = Logger.logger.child() - return self -} + /** + * Add a {name} package to a system + Function checks if package with the same name is available from uplinks. + If it isn't, we create package locally + Used storages: local (write) && uplinks + * @param {*} name + * @param {*} metadata + * @param {*} callback + */ + add_package(name, metadata, callback) { + var self = this -// -// Add a {name} package to a system -// -// Function checks if package with the same name is available from uplinks. -// If it isn't, we create package locally -// -// Used storages: local (write) && uplinks -// -Storage.prototype.add_package = function(name, metadata, callback) { - var self = this + // NOTE: + // - when we checking package for existance, we ask ALL uplinks + // - when we publishing package, we only publish it to some of them + // so all requests are necessary - // NOTE: - // - when we checking package for existance, we ask ALL uplinks - // - when we publishing package, we only publish it to some of them - // so all requests are necessary - - check_package_local(function(err) { - if (err) return callback(err) - - check_package_remote(function(err) { + check_package_local(function(err) { if (err) return callback(err) - publish_package(function(err) { + check_package_remote(function(err) { if (err) return callback(err) - callback() + + publish_package(function(err) { + 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) + function check_package_local(cb) { + self.local.get_package(name, {}, function(err, results) { + 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) + 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) - // checking package - if (results) return cb( Error[409]('this package is already present') ) + // checking package + if (results) return cb( Error[409]('this package is already present') ) - for (var i=0; i= 500)) { - // report internal errors right away - return callback(err) + /** + Retrieve a package metadata for {name} package + Function invokes local.get_package and uplink.get_package for every + uplink with proxy_access rights against {name} and combines results + into one json object + Used storages: local && uplink (proxy_access) + * @param {*} name + * @param {*} options + * @param {*} callback + */ + get_package(name, options, callback) { + if (typeof(options) === 'function') { + callback = options, options = {}; } - self._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) { - if (err) return callback(err) - var whitelist = [ '_rev', 'name', 'versions', 'dist-tags', 'readme' ] - for (var i in result) { - if (whitelist.indexOf(i) === -1) delete result[i] + this.local.get_package(name, options, (err, data) => { + if (err && (!err.status || err.status >= 500)) { + // report internal errors right away + return callback(err) } - Utils.normalize_dist_tags(result) + 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] + } - // npm can throw if this field doesn't exist - result._attachments = {} + Utils.normalize_dist_tags(result) - callback(null, result, uplink_errors) + // npm can throw if this field doesn't exist + result._attachments = {} + + callback(null, result, uplink_errors) + }) }) - }) -} + } -// -// Retrieve remote and local packages more recent than {startkey} -// -// Function streams all packages from all uplinks first, and then -// local packages. -// -// Note that local packages could override registry ones just because -// they appear in JSON last. That's a trade-off we make to avoid -// memory issues. -// -// Used storages: local && uplink (proxy_access) -// -Storage.prototype.search = function(startkey, options) { - var self = this + /** + Retrieve remote and local packages more recent than {startkey} + Function streams all packages from all uplinks first, and then + local packages. + Note that local packages could override registry ones just because + they appear in JSON last. That's a trade-off we make to avoid + memory issues. + Used storages: local && uplink (proxy_access) + * @param {*} startkey + * @param {*} options + */ + search(startkey, options) { + var self = this - var stream = new Stream.PassThrough({ objectMode: true }) + var stream = new Stream.PassThrough({ objectMode: true }) - async.eachSeries(Object.keys(self.uplinks), function(up_name, cb) { - // shortcut: if `local=1` is supplied, don't call uplinks - if (options.req.query.local !== undefined) return cb() + 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() - 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 () {} + 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 () {} + }) + + 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 () { - 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() - }) - }) + return stream; + } - return stream -} + /** + * + * @param {*} callback + */ + get_local(callback) { + var self = this + var locals = this.config.localList.get() + var packages = [] -Storage.prototype.get_local = function(callback) { - var self = this - var locals = this.config.localList.get() - var packages = [] + var getPackage = function(i) { + self.local.get_package(locals[i], function(err, info) { + if (!err) { + var latest = info['dist-tags'].latest + if (latest && info.versions[latest]) { + packages.push(info.versions[latest]) + } else { + self.logger.warn( { package: locals[i] } + , 'package @{package} does not have a "latest" tag?' ) + } + } - var getPackage = function(i) { - self.local.get_package(locals[i], function(err, info) { - if (!err) { - var latest = info['dist-tags'].latest - if (latest && info.versions[latest]) { - packages.push(info.versions[latest]) + if (i >= locals.length - 1) { + callback(null, packages) } else { - self.logger.warn( { package: locals[i] } - , 'package @{package} does not have a "latest" tag?' ) + getPackage(i + 1) + } + }) + } + + if (locals.length) { + getPackage(0) + } else { + callback(null, []) + } + } + + /** + * Function fetches package information from uplinks and synchronizes it with local data + if package is available locally, it MUST be provided in pkginfo + returns callback(err, result, uplink_errors) + * @param {*} name + * @param {*} pkginfo + * @param {*} options + * @param {*} callback + */ + _sync_package_with_uplinks(name, pkginfo, options, callback) { + var self = this + let exists = false; + if (!pkginfo) { + exists = false + + pkginfo = { + name : name, + versions : {}, + 'dist-tags' : {}, + _uplinks : {}, + } + } else { + exists = true + } + + var uplinks = [] + for (let i in self.uplinks) { + if (self.config.can_proxy_to(name, i)) { + uplinks.push(self.uplinks[i]) + } + } + + async.map(uplinks, function(up, cb) { + var _options = Object.assign({}, options) + if (Utils.is_object(pkginfo._uplinks[up.upname])) { + var fetched = pkginfo._uplinks[up.upname].fetched + if (fetched && fetched > (Date.now() - up.maxage)) { + return cb() + } + + _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() + + if (err || !up_res) return cb(null, [err || Error('no data')]) + + try { + 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 ]) + } + + pkginfo._uplinks[up.upname] = { + etag: etag, + 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? + Object.defineProperty(up_res.versions[i], '_verdaccio_uplink', { + value : up.upname, + enumerable : false, + configurable : false, + writable : true, + }); + } + + try { + Storage._merge_versions(pkginfo, up_res, self.config); + } catch(err) { + self.logger.error({ + sub: 'out', + err: 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() + }) + }, function(err, uplink_errors) { + assert(!err && Array.isArray(uplink_errors)) + + if (!exists) { + return callback( Error[404]('no such package available') + , null + , uplink_errors ) + } + + self.local.update_versions(name, pkginfo, function(err, pkginfo) { + if (err) return callback(err) + return callback(null, pkginfo, uplink_errors) + }) + }) + } + + /** + * Function gets a local info and an info from uplinks and tries to merge it + exported for unit tests only. + * @param {*} local + * @param {*} up + * @param {*} config + */ + static _merge_versions(local, up, config) { + // copy new versions to a cache + // 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] + } + } + + // 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] + if (i === 'latest') { + // if remote has more fresh package, we should borrow its readme + local.readme = up.readme } } - - if (i >= locals.length - 1) { - callback(null, packages) - } else { - getPackage(i + 1) - } - }) + } } - if (locals.length) { - getPackage(0) - } else { - callback(null, []) - } } -// function fetches package information from uplinks and synchronizes it with local data -// if package is available locally, it MUST be provided in pkginfo -// returns callback(err, result, uplink_errors) -Storage.prototype._sync_package_with_uplinks = function(name, pkginfo, options, callback) { - var self = this - - if (!pkginfo) { - var exists = false - - pkginfo = { - name : name, - versions : {}, - 'dist-tags' : {}, - _uplinks : {}, - } - } else { - var exists = true - } - - var uplinks = [] - for (var i in self.uplinks) { - if (self.config.can_proxy_to(name, i)) { - uplinks.push(self.uplinks[i]) - } - } - - async.map(uplinks, function(up, cb) { - var _options = Object.assign({}, options) - if (Utils.is_object(pkginfo._uplinks[up.upname])) { - var fetched = pkginfo._uplinks[up.upname].fetched - if (fetched && fetched > (Date.now() - up.maxage)) { - return cb() - } - - _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() - - if (err || !up_res) return cb(null, [err || Error('no data')]) - - try { - 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 ]) - } - - pkginfo._uplinks[up.upname] = { - etag: etag, - fetched: Date.now() - } - - for (var i in up_res.versions) { - // this won't be serialized to json, - // kinda like an ES6 Symbol - Object.defineProperty(up_res.versions[i], '_verdaccio_uplink', { - value : up.upname, - enumerable : false, - configurable : false, - writable : true, - }) - } - - try { - Storage._merge_versions(pkginfo, up_res, self.config) - } catch(err) { - self.logger.error({ - sub: 'out', - err: 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() - }) - }, function(err, uplink_errors) { - assert(!err && Array.isArray(uplink_errors)) - - if (!exists) { - return callback( Error[404]('no such package available') - , null - , uplink_errors ) - } - - self.local.update_versions(name, pkginfo, function(err, pkginfo) { - if (err) return callback(err) - return callback(null, pkginfo, uplink_errors) - }) - }) -} - -// function gets a local info and an info from uplinks and tries to merge it -// exported for unit tests only -Storage._merge_versions = function(local, up, config) { - // copy new versions to a cache - // 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] - } - } - - // 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] - if (i === 'latest') { - // if remote has more fresh package, we should borrow its readme - local.readme = up.readme - } - } - } -} +module.exports = Storage; diff --git a/lib/up-storage.js b/lib/up-storage.js index b94863165..65b2b384d 100644 --- a/lib/up-storage.js +++ b/lib/up-storage.js @@ -1,59 +1,19 @@ "use strict"; -var JSONStream = require('JSONStream') -var Error = require('http-errors') -var request = require('request') -var Stream = require('readable-stream') -var URL = require('url') -var parse_interval = require('./config').parse_interval -var Logger = require('./logger') -var MyStreams = require('./streams') -var Utils = require('./utils') -var 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/, '@'); }; -module.exports = Storage - -// -// Implements Storage interface -// (same for storage.js, local-storage.js, up-storage.js) -// -function Storage(config, mainconfig) { - var self = Object.create(Storage.prototype) - self.config = config - self.failed_requests = 0 - self.userAgent = mainconfig.user_agent - self.ca = config.ca - self.logger = Logger.logger.child({sub: 'out'}) - self.server_id = mainconfig.server_id - - self.url = URL.parse(self.config.url) - - _setupProxy.call(self, self.url.hostname, config, mainconfig, self.url.protocol === 'https:') - - self.config.url = self.config.url.replace(/\/$/, '') - if (Number(self.config.timeout) >= 1000) { - self.logger.warn([ 'Too big timeout value: ' + self.config.timeout, - 'We changed time format to nginx-like one', - '(see http://wiki.nginx.org/ConfigNotation)', - 'so please update your config accordingly' ].join('\n')) - } - - // a bunch of different configurable timers - self.maxage = parse_interval(config_get('maxage' , '2m' )) - self.timeout = parse_interval(config_get('timeout' , '30s')) - self.max_fails = Number(config_get('max_fails' , 2 )) - self.fail_timeout = parse_interval(config_get('fail_timeout', '5m' )) - return self - - // just a helper (`config[key] || default` doesn't work because of zeroes) - function config_get(key, def) { - return config[key] != null ? config[key] : def - } -} - -function _setupProxy(hostname, config, mainconfig, isHTTPS) { +const _setupProxy = function(hostname, config, mainconfig, isHTTPS) { var no_proxy var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy' @@ -100,6 +60,52 @@ function _setupProxy(hostname, config, mainconfig, isHTTPS) { } } +// +// Implements Storage interface +// (same for storage.js, local-storage.js, up-storage.js) +// +class Storage { + + /** + * + * @param {*} config + * @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.url = URL.parse(this.config.url) + + _setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:') + + this.config.url = this.config.url.replace(/\/$/, '') + if (Number(this.config.timeout) >= 1000) { + 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')) + } + + // 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 + + // just a helper (`config[key] || default` doesn't work because of zeroes) + function config_get(key, def) { + return config[key] != null ? config[key] : def + } + } +} + + Storage.prototype.request = function(options, cb) { if (!this.status_check()) { var req = new Stream.Readable() @@ -396,3 +402,6 @@ Storage.prototype._add_proxy_headers = function(req, headers) { headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)' } + + +module.exports = Storage; From 338ea47b6ca78125c4e7f62bf6a0bc9ed32805cd Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Wed, 19 Apr 2017 21:15:28 +0200 Subject: [PATCH 12/14] Update unit test es6 --- test/functional/access.js | 117 +++++++-------- test/functional/addtag.js | 63 ++++---- test/functional/adduser.js | 56 +++---- test/functional/basic.js | 156 ++++++++++---------- test/functional/gh29.js | 70 ++++----- test/functional/gzip.js | 112 +++++++------- test/functional/incomplete.js | 96 ++++++------ test/functional/index.js | 155 ++++++++++---------- test/functional/lib/package.js | 17 +-- test/functional/lib/server.js | 130 +++++++++-------- test/functional/lib/smart_request.js | 133 ++++++++--------- test/functional/lib/startup.js | 74 +++++----- test/functional/logout.js | 14 +- test/functional/mirror.js | 70 ++++----- test/functional/newnpmreg.js | 150 +++++++++---------- test/functional/nullstorage.js | 84 +++++------ test/functional/plugins.js | 128 ++++++++-------- test/functional/plugins/authenticate.js | 25 ++-- test/functional/plugins/authorize.js | 37 ++--- test/functional/race.js | 138 +++++++++--------- test/functional/racycrash.js | 88 +++++------ test/functional/scoped.js | 90 ++++++------ test/functional/security.js | 82 ++++++----- test/functional/tags.js | 186 ++++++++++++------------ test/unit/config_def.js | 9 +- test/unit/listen_addr.js | 48 +++--- test/unit/mystreams.js | 22 +-- test/unit/no_proxy.js | 132 ++++++++--------- test/unit/parse_interval.js | 48 +++--- test/unit/partials/config.js | 8 +- test/unit/search.js | 38 ++--- test/unit/st_merge.js | 54 +++---- test/unit/tag_version.js | 53 +++---- test/unit/toplevel.js | 56 +++---- test/unit/utils.js | 56 +++---- test/unit/validate_all.js | 38 ++--- 36 files changed, 1451 insertions(+), 1382 deletions(-) diff --git a/test/functional/access.js b/test/functional/access.js index f0acddffc..e33682614 100644 --- a/test/functional/access.js +++ b/test/functional/access.js @@ -1,80 +1,73 @@ +'use strict'; -module.exports = function () { - describe('access control', function () { - var server = process.server - var oldauth +module.exports = function() { + describe('access control', function() { + let server = process.server; + let oldauth; - before(function () { - oldauth = server.authstr - }) + before(function() { + oldauth = server.authstr; + }); - after(function () { - server.authstr = oldauth - }) + after(function() { + server.authstr = oldauth; + }); function check_access(auth, pkg, ok) { - it((ok ? 'allows' : 'forbids') +' access ' + auth + ' to ' + pkg, function () { - server.authstr = auth - ? 'Basic '+(new Buffer(auth).toString('base64')) - : undefined - - var req = server.get_package(pkg) - + it((ok ? 'allows' : 'forbids') +' access ' + auth + ' to ' + pkg, function() { + server.authstr = auth? `Basic ${(new Buffer(auth).toString('base64'))}`: undefined; + let req = server.get_package(pkg); if (ok) { - return req.status(404) - .body_error(/no such package available/) + return req.status(404).body_error(/no such package available/); } else { - return req.status(403) - .body_error(/not allowed to access package/) + return req.status(403).body_error(/not allowed to access package/); } - }) + }); } function check_publish(auth, pkg, ok) { - it((ok ? 'allows' : 'forbids') + ' publish ' + auth + ' to ' + pkg, function () { - server.authstr = auth - ? 'Basic '+(new Buffer(auth).toString('base64')) - : undefined - - var req = server.put_package(pkg, require('./lib/package')(pkg)) - + it(`${(ok ? 'allows' : 'forbids')} publish ${auth} to ${pkg}`, function() { + server.authstr = auth? `Basic ${(new Buffer(auth).toString('base64'))}`: undefined; + let req = server.put_package(pkg, require('./lib/package')(pkg)); if (ok) { - return req.status(404) - .body_error(/this package cannot be added/) + return req.status(404).body_error(/this package cannot be added/); } else { - return req.status(403) - .body_error(/not allowed to publish package/) + return req.status(403).body_error(/not allowed to publish package/); } - }) + }); } + const badPass = 'test:badpass'; + const testPass = 'test:test'; + const testAccessOnly = 'test-access-only'; + const testPublishOnly = 'test-publish-only'; + const testOnlyTest = 'test-only-test'; + const testOnlyAuth = 'test-only-auth'; + check_access(testPass, testAccessOnly, true); + check_access(undefined, testAccessOnly, true); + check_access(badPass, testAccessOnly, true); + check_publish(testPass, testAccessOnly, false); + check_publish(undefined, testAccessOnly, false); + check_publish(badPass, testAccessOnly, false); - check_access('test:test', 'test-access-only', true) - check_access(undefined, 'test-access-only', true) - check_access('test:badpass', 'test-access-only', true) - check_publish('test:test', 'test-access-only', false) - check_publish(undefined, 'test-access-only', false) - check_publish('test:badpass', 'test-access-only', false) + check_access(testPass, testPublishOnly, false); + check_access(undefined, testPublishOnly, false); + check_access(badPass, testPublishOnly, false); + check_publish(testPass, testPublishOnly, true); + check_publish(undefined, testPublishOnly, true); + check_publish(badPass, testPublishOnly, true); - check_access('test:test', 'test-publish-only', false) - check_access(undefined, 'test-publish-only', false) - check_access('test:badpass', 'test-publish-only', false) - check_publish('test:test', 'test-publish-only', true) - check_publish(undefined, 'test-publish-only', true) - check_publish('test:badpass', 'test-publish-only', true) - - check_access('test:test', 'test-only-test', true) - check_access(undefined, 'test-only-test', false) - check_access('test:badpass', 'test-only-test', false) - check_publish('test:test', 'test-only-test', true) - check_publish(undefined, 'test-only-test', false) - check_publish('test:badpass', 'test-only-test', false) - - check_access('test:test', 'test-only-auth', true) - check_access(undefined, 'test-only-auth', false) - check_access('test:badpass', 'test-only-auth', false) - check_publish('test:test', 'test-only-auth', true) - check_publish(undefined, 'test-only-auth', false) - check_publish('test:badpass', 'test-only-auth', false) - }) -} + check_access(testPass, testOnlyTest, true); + check_access(undefined, testOnlyTest, false); + check_access(badPass, testOnlyTest, false); + check_publish(testPass, testOnlyTest, true); + check_publish(undefined, testOnlyTest, false); + check_publish(badPass, testOnlyTest, false); + check_access(testPass, testOnlyAuth, true); + check_access(undefined, testOnlyAuth, false); + check_access(badPass, testOnlyAuth, false); + check_publish(testPass, testOnlyAuth, true); + check_publish(undefined, testOnlyAuth, false); + check_publish(badPass, testOnlyAuth, false); + }); +}; diff --git a/test/functional/addtag.js b/test/functional/addtag.js index 6303c6370..1e2445abe 100644 --- a/test/functional/addtag.js +++ b/test/functional/addtag.js @@ -1,46 +1,47 @@ +'use strict'; function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } -module.exports = function () { - var server = process.server +module.exports = function() { + let server = process.server; - it('add tag - 404', function () { - return server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1') - .status(404) - .body_error(/no such package/) - }) + it('add tag - 404', function() { + return server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1').status(404).body_error(/no such package/); + }); describe('addtag', function() { - before(function () { + before(function() { return server.put_package('testpkg-tag', eval( - '(' + readfile('fixtures/publish.json5') - .toString('utf8') - .replace(/__NAME__/g, 'testpkg-tag') - .replace(/__VERSION__/g, '0.0.1') - + ')' - )).status(201) - }) + '(' + readfile('fixtures/publish.json5') + .toString('utf8') + .replace(/__NAME__/g, 'testpkg-tag') + .replace(/__VERSION__/g, '0.0.1') + + ')' + )).status(201); + }); - it('add testpkg-tag', function(){}) + it('add testpkg-tag', function() { + // TODO: ? + }); - it('add tag - bad ver', function () { + it('add tag - bad ver', function() { return server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1-x') - .status(404) - .body_error(/version doesn't exist/) - }) + .status(404) + .body_error(/version doesn't exist/); + }); - it('add tag - bad tag', function () { + it('add tag - bad tag', function() { return server.add_tag('testpkg-tag', 'tag/tag/tag', '0.0.1-x') - .status(403) - .body_error(/invalid tag/) - }) + .status(403) + .body_error(/invalid tag/); + }); - it('add tag - good', function () { + it('add tag - good', function() { return server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1') - .status(201) - .body_ok(/tagged/) - }) - }) -} + .status(201) + .body_ok(/tagged/); + }); + }); +}; diff --git a/test/functional/adduser.js b/test/functional/adduser.js index b0c6bcf53..5fd38e39f 100644 --- a/test/functional/adduser.js +++ b/test/functional/adduser.js @@ -1,47 +1,49 @@ -var Server = require('./lib/server') -var fs = require('fs') -var path = require('path') +'use strict'; + +const Server = require('./lib/server'); +const fs = require('fs'); +const path = require('path'); module.exports = function() { - var server = new Server('http://localhost:55551/') + const server = new Server('http://localhost:55551/'); describe('adduser', function() { - var user = String(Math.random()) - var pass = String(Math.random()) - before(function () { + const user = String(Math.random()); + const pass = String(Math.random()); + before(function() { return server.auth(user, pass) .status(201) - .body_ok(/user .* created/) - }) + .body_ok(/user .* created/); + }); - it('creating new user', function(){}) + it('creating new user', function() {}); - it('should log in', function () { + it('should log in', function() { return server.auth(user, pass) .status(201) - .body_ok(/you are authenticated as/) - }) + .body_ok(/you are authenticated as/); + }); - it('should not register more users', function () { + it('should not register more users', function() { return server.auth(String(Math.random()), String(Math.random())) .status(409) - .body_error(/maximum amount of users reached/) - }) - }) + .body_error(/maximum amount of users reached/); + }); + }); describe('adduser created with htpasswd', function() { - var user = 'preexisting' - var pass = 'preexisting' - before(function () { + let user = 'preexisting'; + let pass = 'preexisting'; + before(function() { return fs.appendFileSync( path.join(__dirname, 'test-storage', '.htpasswd'), 'preexisting:$apr1$4YSboUa9$yVKjE7.PxIOuK3M4D7VjX.' - ) - }) - it('should log in', function () { + ); + }); + it('should log in', function() { return server.auth(user, pass) .status(201) - .body_ok(/you are authenticated as/) - }) - }) -} + .body_ok(/you are authenticated as/); + }); + }); +}; diff --git a/test/functional/basic.js b/test/functional/basic.js index 542c5fa9c..6aa4c9d73 100644 --- a/test/functional/basic.js +++ b/test/functional/basic.js @@ -1,119 +1,121 @@ -require('./lib/startup') +'use strict'; -var assert = require('assert') -var crypto = require('crypto') +require('./lib/startup'); + +const assert = require('assert'); +const crypto = require('crypto'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } -module.exports = function () { - var server = process.server - var server2 = process.server2 +module.exports = function() { + let server = process.server; + let server2 = process.server2; - it('trying to fetch non-existent package', function () { - return server.get_package('testpkg').status(404).body_error(/no such package/) - }) + it('trying to fetch non-existent package', function() { + return server.get_package('testpkg').status(404).body_error(/no such package/); + }); - describe('testpkg', function () { - before(function () { - return server.add_package('testpkg') - }) + describe('testpkg', function() { + before(function() { + return server.add_package('testpkg'); + }); - it('creating new package', function (){/* test for before() */}) + it('creating new package', function() {/* test for before() */}); - it('downloading non-existent tarball', function () { - return server.get_tarball('testpkg', 'blahblah').status(404).body_error(/no such file/) - }) + it('downloading non-existent tarball', function() { + return server.get_tarball('testpkg', 'blahblah').status(404).body_error(/no such file/); + }); - it('uploading incomplete tarball', function () { - return server.put_tarball_incomplete('testpkg', 'blahblah1', readfile('fixtures/binary'), 3000) - }) + it('uploading incomplete tarball', function() { + return server.put_tarball_incomplete('testpkg', 'blahblah1', readfile('fixtures/binary'), 3000); + }); - describe('tarball', function () { - before(function () { + describe('tarball', function() { + before(function() { return server.put_tarball('testpkg', 'blahblah', readfile('fixtures/binary')) .status(201) - .body_ok(/.*/) - }) + .body_ok(/.*/); + }); - it('uploading new tarball', function (){/* test for before() */}) + it('uploading new tarball', function() {/* test for before() */}); - it('downloading newly created tarball', function () { + it('downloading newly created tarball', function() { return server.get_tarball('testpkg', 'blahblah') .status(200) - .then(function (body) { - assert.deepEqual(body, readfile('fixtures/binary')) - }) - }) + .then(function(body) { + assert.deepEqual(body, readfile('fixtures/binary')); + }); + }); - it('uploading new package version (bad sha)', function () { - var pkg = require('./lib/package')('testpkg') - pkg.dist.shasum = crypto.createHash('sha1').update('fake').digest('hex') + it('uploading new package version (bad sha)', function() { + let pkg = require('./lib/package')('testpkg'); + pkg.dist.shasum = crypto.createHash('sha1').update('fake').digest('hex'); return server.put_version('testpkg', '0.0.1', pkg) .status(400) - .body_error(/shasum error/) - }) + .body_error(/shasum error/); + }); - describe('version', function () { - before(function () { - var pkg = require('./lib/package')('testpkg') - pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex') + describe('version', function() { + before(function() { + let pkg = require('./lib/package')('testpkg'); + pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex'); return server.put_version('testpkg', '0.0.1', pkg) .status(201) - .body_ok(/published/) - }) + .body_ok(/published/); + }); - it('uploading new package version', function (){/* test for before() */}) + it('uploading new package version', function() {/* test for before() */}); - it('downloading newly created package', function () { + it('downloading newly created package', function() { return server.get_package('testpkg') .status(200) - .then(function (body) { - assert.equal(body.name, 'testpkg') - assert.equal(body.versions['0.0.1'].name, 'testpkg') - assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/testpkg/-/blahblah') - assert.deepEqual(body['dist-tags'], {latest: '0.0.1'}) - }) - }) + .then(function(body) { + assert.equal(body.name, 'testpkg'); + assert.equal(body.versions['0.0.1'].name, 'testpkg'); + assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/testpkg/-/blahblah'); + assert.deepEqual(body['dist-tags'], {latest: '0.0.1'}); + }); + }); - it('downloading package via server2', function () { + it('downloading package via server2', function() { return server2.get_package('testpkg') .status(200) - .then(function (body) { - assert.equal(body.name, 'testpkg') - assert.equal(body.versions['0.0.1'].name, 'testpkg') - assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55552/testpkg/-/blahblah') - assert.deepEqual(body['dist-tags'], {latest: '0.0.1'}) - }) - }) - }) - }) - }) + .then(function(body) { + assert.equal(body.name, 'testpkg'); + assert.equal(body.versions['0.0.1'].name, 'testpkg'); + assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55552/testpkg/-/blahblah'); + assert.deepEqual(body['dist-tags'], {latest: '0.0.1'}); + }); + }); + }); + }); + }); - it('uploading new package version for bad pkg', function () { + it('uploading new package version for bad pkg', function() { return server.put_version('testpxg', '0.0.1', require('./lib/package')('testpxg')) .status(404) - .body_error(/no such package/) - }) + .body_error(/no such package/); + }); - it('doubleerr test', function () { + it('doubleerr test', function() { return server.put_tarball('testfwd2', 'blahblah', readfile('fixtures/binary')) .status(404) - .body_error(/no such/) - }) + .body_error(/no such/); + }); - it('publishing package / bad ro uplink', function () { + it('publishing package / bad ro uplink', function() { return server.put_package('baduplink', require('./lib/package')('baduplink')) .status(503) - .body_error(/one of the uplinks is down, refuse to publish/) - }) + .body_error(/one of the uplinks is down, refuse to publish/); + }); - it('who am I?', function () { - return server.whoami().then(function (username) { - assert.equal(username, 'test') - }) - }) -} + it('who am I?', function() { + return server.whoami().then(function(username) { + assert.equal(username, 'test'); + }); + }); +}; diff --git a/test/functional/gh29.js b/test/functional/gh29.js index f130c84c4..a04b76616 100644 --- a/test/functional/gh29.js +++ b/test/functional/gh29.js @@ -1,64 +1,66 @@ -var assert = require('assert') -var crypto = require('crypto') +'use strict'; + +const assert = require('assert'); +const crypto = require('crypto'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } module.exports = function() { - var server = process.server - var server2 = process.server2 + let server = process.server; + let server2 = process.server2; - it('downloading non-existent tarball #1 / srv2', function () { + it('downloading non-existent tarball #1 / srv2', function() { return server2.get_tarball('testpkg-gh29', 'blahblah') .status(404) - .body_error(/no such package/) - }) + .body_error(/no such package/); + }); describe('pkg-gh29', function() { - before(function () { + before(function() { return server.put_package('testpkg-gh29', require('./lib/package')('testpkg-gh29')) .status(201) - .body_ok(/created new package/) - }) + .body_ok(/created new package/); + }); - it('creating new package / srv1', function(){}) + it('creating new package / srv1', function() {}); - it('downloading non-existent tarball #2 / srv2', function () { + it('downloading non-existent tarball #2 / srv2', function() { return server2.get_tarball('testpkg-gh29', 'blahblah') .status(404) - .body_error(/no such file/) - }) + .body_error(/no such file/); + }); describe('tarball', function() { - before(function () { + before(function() { return server.put_tarball('testpkg-gh29', 'blahblah', readfile('fixtures/binary')) .status(201) - .body_ok(/.*/) - }) + .body_ok(/.*/); + }); - it('uploading new tarball / srv1', function(){}) + it('uploading new tarball / srv1', function() {}); describe('pkg version', function() { - before(function () { - var pkg = require('./lib/package')('testpkg-gh29') - pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex') + before(function() { + let pkg = require('./lib/package')('testpkg-gh29'); + pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex'); return server.put_version('testpkg-gh29', '0.0.1', pkg) .status(201) - .body_ok(/published/) - }) + .body_ok(/published/); + }); - it('uploading new package version / srv1', function(){}) + it('uploading new package version / srv1', function() {}); - it('downloading newly created tarball / srv2', function () { + it('downloading newly created tarball / srv2', function() { return server2.get_tarball('testpkg-gh29', 'blahblah') .status(200) - .then(function (body) { - assert.deepEqual(body, readfile('fixtures/binary')) - }) - }) - }) - }) - }) -} + .then(function(body) { + assert.deepEqual(body, readfile('fixtures/binary')); + }); + }); + }); + }); + }); +}; diff --git a/test/functional/gzip.js b/test/functional/gzip.js index 7af4c2b14..90063c5dc 100644 --- a/test/functional/gzip.js +++ b/test/functional/gzip.js @@ -1,69 +1,71 @@ -require('./lib/startup') +'use strict'; -var assert = require('assert') +require('./lib/startup'); + +let assert = require('assert'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } module.exports = function() { - var server = process.server - var express = process.express + let server = process.server; + let express = process.express; describe('testexp_gzip', function() { before(function() { express.get('/testexp_gzip', function(req, res) { - var x = eval( + let x = eval( '(' + readfile('fixtures/publish.json5') .toString('utf8') .replace(/__NAME__/g, 'testexp_gzip') .replace(/__VERSION__/g, '0.0.1') + ')' - ) + ); // overcoming compress threshold - x.versions['0.0.2'] = x.versions['0.0.1'] - x.versions['0.0.3'] = x.versions['0.0.1'] - x.versions['0.0.4'] = x.versions['0.0.1'] - x.versions['0.0.5'] = x.versions['0.0.1'] - x.versions['0.0.6'] = x.versions['0.0.1'] - x.versions['0.0.7'] = x.versions['0.0.1'] - x.versions['0.0.8'] = x.versions['0.0.1'] - x.versions['0.0.9'] = x.versions['0.0.1'] + x.versions['0.0.2'] = x.versions['0.0.1']; + x.versions['0.0.3'] = x.versions['0.0.1']; + x.versions['0.0.4'] = x.versions['0.0.1']; + x.versions['0.0.5'] = x.versions['0.0.1']; + x.versions['0.0.6'] = x.versions['0.0.1']; + x.versions['0.0.7'] = x.versions['0.0.1']; + x.versions['0.0.8'] = x.versions['0.0.1']; + x.versions['0.0.9'] = x.versions['0.0.1']; require('zlib').gzip(JSON.stringify(x), function(err, buf) { - assert.equal(err, null) - assert.equal(req.headers['accept-encoding'], 'gzip') - res.header('content-encoding', 'gzip') - res.send(buf) - }) - }) + assert.equal(err, null); + assert.equal(req.headers['accept-encoding'], 'gzip'); + res.header('content-encoding', 'gzip'); + res.send(buf); + }); + }); express.get('/testexp_baddata', function(req, res) { - assert.equal(req.headers['accept-encoding'], 'gzip') - res.header('content-encoding', 'gzip') - res.send(new Buffer([1,2,3,4,5,6,7,7,6,5,4,3,2,1])) - }) - }) + assert.equal(req.headers['accept-encoding'], 'gzip'); + res.header('content-encoding', 'gzip'); + res.send(new Buffer([1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1])); + }); + }); - it('should not fail on bad gzip', function () { + it('should not fail on bad gzip', function() { return server.get_package('testexp_baddata') - .status(404) - }) + .status(404); + }); - it('should understand gzipped data from uplink', function () { + it('should understand gzipped data from uplink', function() { return server.get_package('testexp_gzip') .status(200) - .response(function (res) { - assert.equal(res.headers['content-encoding'], undefined) + .response(function(res) { + assert.equal(res.headers['content-encoding'], undefined); }) - .then(function (body) { - assert.equal(body.name, 'testexp_gzip') - assert.equal(Object.keys(body.versions).length, 9) - }) - }) + .then(function(body) { + assert.equal(body.name, 'testexp_gzip'); + assert.equal(Object.keys(body.versions).length, 9); + }); + }); - it('should serve gzipped data', function () { + it('should serve gzipped data', function() { return server.request({ uri: '/testexp_gzip', encoding: null, @@ -72,25 +74,25 @@ module.exports = function() { }, json: false, }).status(200) - .response(function (res) { - assert.equal(res.headers['content-encoding'], 'gzip') + .response(function(res) { + assert.equal(res.headers['content-encoding'], 'gzip'); }) - .then(function (body) { + .then(function(body) { assert.throws(function() { - JSON.parse(body.toString('utf8')) - }) + JSON.parse(body.toString('utf8')); + }); - return new Promise(function (resolve) { + return new Promise(function(resolve) { require('zlib').gunzip(body, function(err, buf) { - assert.equal(err, null) - body = JSON.parse(buf) - assert.equal(body.name, 'testexp_gzip') - assert.equal(Object.keys(body.versions).length, 9) - resolve() - }) - }) - }) - }) - }) -} + assert.equal(err, null); + body = JSON.parse(buf); + assert.equal(body.name, 'testexp_gzip'); + assert.equal(Object.keys(body.versions).length, 9); + resolve(); + }); + }); + }); + }); + }); +}; diff --git a/test/functional/incomplete.js b/test/functional/incomplete.js index f093de2ac..4e76f04d7 100644 --- a/test/functional/incomplete.js +++ b/test/functional/incomplete.js @@ -1,67 +1,71 @@ -var assert = require('assert') +'use strict'; + +let assert = require('assert'); module.exports = function() { - var server = process.server - var express = process.express + let server = process.server; + let express = process.express; describe('Incomplete', function() { before(function() { express.get('/testexp-incomplete', function(_, res) { res.send({ - "name": "testexp-incomplete", - "versions": { - "0.1.0": { - "name": "testexp_tags", - "version": "0.1.0", - "dist": { - "shasum": "fake", - "tarball": "http://localhost:55550/testexp-incomplete/-/content-length.tar.gz" - } + 'name': 'testexp-incomplete', + 'versions': { + '0.1.0': { + 'name': 'testexp_tags', + 'version': '0.1.0', + 'dist': { + 'shasum': 'fake', + 'tarball': 'http://localhost:55550/testexp-incomplete/-/content-length.tar.gz', + }, }, - "0.1.1": { - "name": "testexp_tags", - "version": "0.1.1", - "dist": { - "shasum": "fake", - "tarball": "http://localhost:55550/testexp-incomplete/-/chunked.tar.gz" - } - } - } - }) - }) + '0.1.1': { + 'name': 'testexp_tags', + 'version': '0.1.1', + 'dist': { + 'shasum': 'fake', + 'tarball': 'http://localhost:55550/testexp-incomplete/-/chunked.tar.gz', + }, + }, + }, + }); + }); }) - ;[ 'content-length', 'chunked' ].forEach(function(type) { + ;['content-length', 'chunked'].forEach(function(type) { it('should not store tarballs / ' + type, function(_cb) { - var called + let called; express.get('/testexp-incomplete/-/'+type+'.tar.gz', function(_, res) { - if (called) return res.socket.destroy() - called = true - if (type !== 'chunked') res.header('content-length', 1e6) - res.write('test test test\n') + if (called) return res.socket.destroy(); + called = true; + if (type !== 'chunked') res.header('content-length', 1e6); + res.write('test test test\n'); setTimeout(function() { - res.socket.write('200\nsss\n') - res.socket.destroy() - cb() - }, 10) - }) + res.socket.write('200\nsss\n'); + res.socket.destroy(); + cb(); + }, 10); + }); - server.request({ uri: '/testexp-incomplete/-/'+type+'.tar.gz' }) + server.request({uri: '/testexp-incomplete/-/'+type+'.tar.gz'}) .status(200) - .response(function (res) { - if (type !== 'chunked') assert.equal(res.headers['content-length'], 1e6) - }) - .then(function (body) { - assert(body.match(/test test test/)) + .response(function(res) { + if (type !== 'chunked') assert.equal(res.headers['content-length'], 1e6); }) + .then(function(body) { + assert(body.match(/test test test/)); + }); function cb() { - server.request({ uri: '/testexp-incomplete/-/'+type+'.tar.gz' }) + server.request({uri: '/testexp-incomplete/-/'+type+'.tar.gz'}) .body_error('internal server error') - .then(function () { _cb() }) + .then(function() { + _cb(); +}); } - }) - }) - }) -} + }); + }); + }); +}; diff --git a/test/functional/index.js b/test/functional/index.js index cf7daaa7e..18b9daa47 100644 --- a/test/functional/index.js +++ b/test/functional/index.js @@ -1,87 +1,94 @@ -//require('es6-shim') -require('./lib/startup') +'use strict'; -var assert = require('assert') -var async = require('async') -var exec = require('child_process').exec +require('./lib/startup'); + +const assert = require('assert'); +const exec = require('child_process').exec; describe('Func', function() { - var server = process.server - var server2 = process.server2 + const server = process.server; + const server2 = process.server2; - before(function (cb) { - async.parallel([ - function (cb) { - require('./lib/startup').start('./test-storage', './config-1.yaml', cb) - }, - function (cb) { - require('./lib/startup').start('./test-storage2', './config-2.yaml', cb) - }, - ], cb) - }) + before(function(done) { + Promise.all([ + require('./lib/startup').start('./test-storage', './config-1.yaml'), + require('./lib/startup').start('./test-storage2', './config-2.yaml'), + ]).then(() => { + done(); + }); + }); before(function() { - return Promise.all([ server, server2 ].map(function(server) { - return server.debug().status(200).then(function (body) { - server.pid = body.pid - - return new Promise(function (resolve, reject) { + return Promise.all([server, server2].map(function(server) { + return server.debug().status(200).then(function(body) { + server.pid = body.pid; + return new Promise(function(resolve, reject) { exec('lsof -p ' + Number(server.pid), function(err, result) { - assert.equal(err, null) - server.fdlist = result.replace(/ +/g, ' ') - resolve() - }) - }) - }) - })) - }) + assert.equal(err, null); + server.fdlist = result.replace(/ +/g, ' '); + resolve(); + }); + }); + }); + })); + }); before(function auth() { - return Promise.all([ server, server2 ].map(function(server, cb) { - return server.auth('test', 'test').status(201).body_ok(/'test'/) - })) - }) + return Promise.all([server, server2].map(function(server, cb) { + return server.auth('test', 'test').status(201).body_ok(/'test'/); + })); + }); - it('authenticate', function(){/* test for before() */}) + it('authenticate', function() {/* test for before() */}); - require('./access')() - require('./basic')() - require('./gh29')() - require('./tags')() - require('./gzip')() - require('./incomplete')() - require('./mirror')() - require('./newnpmreg')() - require('./nullstorage')() - require('./race')() - require('./racycrash')() - require('./scoped')() - require('./security')() - require('./adduser')() - require('./logout')() - require('./addtag')() - require('./plugins')() + require('./access')(); + require('./basic')(); + require('./gh29')(); + require('./tags')(); + require('./gzip')(); + require('./incomplete')(); + require('./mirror')(); + require('./newnpmreg')(); + require('./nullstorage')(); + require('./race')(); + require('./racycrash')(); + require('./scoped')(); + require('./security')(); + require('./adduser')(); + require('./logout')(); + require('./addtag')(); + require('./plugins')(); - after(function (cb) { - async.map([ server, server2 ], function(server, cb) { - exec('lsof -p ' + Number(server.pid), function(err, result) { - assert.equal(err, null) - result = result.split('\n').filter(function(q) { - if (q.match(/TCP .*->.* \(ESTABLISHED\)/)) return false - if (q.match(/\/libcrypt-[^\/]+\.so/)) return false - if (q.match(/\/node_modules\/crypt3\/build\/Release/)) return false - return true - }).join('\n').replace(/ +/g, ' ') + after(function(done) { + const check = (server) => { + return new Promise(function(resolve, reject) { + exec('lsof -p ' + parseInt(server.pid, 10), function(err, result) { + if (err) { + reject(); + } else { + result = result.split('\n').filter(function(q) { + if (q.match(/TCP .*->.* \(ESTABLISHED\)/)) return false; + if (q.match(/\/libcrypt-[^\/]+\.so/)) return false; + if (q.match(/\/node_modules\/crypt3\/build\/Release/)) return false; + return true; + }).join('\n').replace(/ +/g, ' '); + assert.equal(server.fdlist, result); + resolve(); + } + }); + }); + }; + Promise.all([check(server), check(server2)]).then(function() { + done(); + }, (reason) => { + assert.equal(reason, null); + done(); + }); + }); +}); - assert.equal(server.fdlist, result) - cb() - }) - }, cb) - }) -}) - -process.on('unhandledRejection', function (err) { - process.nextTick(function () { - throw err - }) -}) +process.on('unhandledRejection', function(err) { + process.nextTick(function() { + throw err; + }); +}); diff --git a/test/functional/lib/package.js b/test/functional/lib/package.js index 8e0156639..0c12e864d 100644 --- a/test/functional/lib/package.js +++ b/test/functional/lib/package.js @@ -1,12 +1,11 @@ - module.exports = function(name, version) { return { - "name": name, - "version": version || "0.0.0", - "dist": { - "shasum": "fake", - "tarball": "http://localhost:55551/"+encodeURIComponent(name)+"/-/blahblah" - } - } -} + name, + version: version || '0.0.0', + dist: { + shasum: 'fake', + tarball: `http://localhost:55551/${encodeURIComponent(name)}/-/blahblah`, + }, + }; +}; diff --git a/test/functional/lib/server.js b/test/functional/lib/server.js index 9f9d64778..7a946a651 100644 --- a/test/functional/lib/server.js +++ b/test/functional/lib/server.js @@ -1,20 +1,22 @@ -var assert = require('assert') -var request = require('./smart_request') +'use strict'; + +const assert = require('assert'); +const request = require('./smart_request'); function Server(url) { - var self = Object.create(Server.prototype) - self.url = url.replace(/\/$/, '') - self.userAgent = 'node/v0.10.8 linux x64' - self.authstr = 'Basic '+(new Buffer('test:test')).toString('base64') - return self + let self = Object.create(Server.prototype); + self.url = url.replace(/\/$/, ''); + self.userAgent = 'node/v0.10.8 linux x64'; + self.authstr = 'Basic '+(new Buffer('test:test')).toString('base64'); + return self; } Server.prototype.request = function(options) { - assert(options.uri) - var headers = options.headers || {} - headers.accept = headers.accept || 'application/json' - headers['user-agent'] = headers['user-agent'] || this.userAgent - headers.authorization = headers.authorization || this.authstr + assert(options.uri); + let headers = options.headers || {}; + headers.accept = headers.accept || 'application/json'; + headers['user-agent'] = headers['user-agent'] || this.userAgent; + headers.authorization = headers.authorization || this.authstr; return request({ url: this.url + options.uri, @@ -22,11 +24,11 @@ Server.prototype.request = function(options) { headers: headers, encoding: options.encoding, json: options.json != null ? options.json : true, - }) -} + }); +}; Server.prototype.auth = function(user, pass) { - this.authstr = 'Basic '+(new Buffer(user+':'+pass)).toString('base64') + this.authstr = 'Basic '+(new Buffer(user+':'+pass)).toString('base64'); return this.request({ uri: '/-/user/org.couchdb.user:'+encodeURIComponent(user)+'/-rev/undefined', method: 'PUT', @@ -38,76 +40,76 @@ Server.prototype.auth = function(user, pass) { type: 'user', roles: [], date: new Date(), - } - }) -} + }, + }); +}; Server.prototype.logout = function(token) { return this.request({ uri: '/-/user/token/'+encodeURIComponent(token), - method: 'DELETE' - }) -} + method: 'DELETE', + }); +}; Server.prototype.get_package = function(name) { return this.request({ uri: '/'+encodeURIComponent(name), method: 'GET', - }) -} + }); +}; Server.prototype.put_package = function(name, data) { - if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data) + if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data); return this.request({ uri: '/'+encodeURIComponent(name), method: 'PUT', headers: { - 'content-type': 'application/json' + 'content-type': 'application/json', }, - }).send(data) -} + }).send(data); +}; Server.prototype.put_version = function(name, version, data) { - if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data) + if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data); return this.request({ uri: '/'+encodeURIComponent(name)+'/'+encodeURIComponent(version)+'/-tag/latest', method: 'PUT', headers: { - 'content-type': 'application/json' + 'content-type': 'application/json', }, - }).send(data) -} + }).send(data); +}; Server.prototype.get_tarball = function(name, filename) { return this.request({ uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename), method: 'GET', - encoding: null - }) -} + encoding: null, + }); +}; Server.prototype.put_tarball = function(name, filename, data) { return this.request({ uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever', method: 'PUT', headers: { - 'content-type': 'application/octet-stream' + 'content-type': 'application/octet-stream', }, - }).send(data) -} + }).send(data); +}; Server.prototype.add_tag = function(name, tag, version) { return this.request({ uri: '/'+encodeURIComponent(name)+'/'+encodeURIComponent(tag), method: 'PUT', headers: { - 'content-type': 'application/json' + 'content-type': 'application/json', }, - }).send(JSON.stringify(version)) -} + }).send(JSON.stringify(version)); +}; Server.prototype.put_tarball_incomplete = function(name, filename, data, size, cb) { - var promise = this.request({ + let promise = this.request({ uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever', method: 'PUT', headers: { @@ -115,51 +117,53 @@ Server.prototype.put_tarball_incomplete = function(name, filename, data, size, c 'content-length': size, }, timeout: 1000, - }) + }); - promise.request(function (req) { - req.write(data) + promise.request(function(req) { + req.write(data); setTimeout(function() { - req.req.abort() - }, 20) - }) + req.req.abort(); + }, 20); + }); - return new Promise(function (resolve, reject) { + return new Promise(function(resolve, reject) { promise .then(function() { - reject(Error('no error')) + reject(Error('no error')); }) .catch(function(err) { if (err.code === 'ECONNRESET') { - resolve() + resolve(); } else { - reject(err) + reject(err); } - }) - }) -} + }); + }); +}; Server.prototype.add_package = function(name) { return this.put_package(name, require('./package')(name)) .status(201) - .body_ok('created new package') -} + .body_ok('created new package'); +}; Server.prototype.whoami = function() { - return this.request({ uri:'/-/whoami' }) + return this.request({uri: '/-/whoami'}) .status(200) - .then(function(x) { return x.username }) -} + .then(function(x) { + return x.username; +}); +}; Server.prototype.debug = function() { return this.request({ uri: '/-/_debug', method: 'GET', headers: { - 'content-type': 'application/json' + 'content-type': 'application/json', }, - }) -} + }); +}; -module.exports = Server +module.exports = Server; diff --git a/test/functional/lib/smart_request.js b/test/functional/lib/smart_request.js index 3aee93c25..733e5ee8e 100644 --- a/test/functional/lib/smart_request.js +++ b/test/functional/lib/smart_request.js @@ -1,103 +1,104 @@ +'use strict'; -var assert = require('assert') -var request = require('request') -var sym = Symbol('smart_request_data') +const assert = require('assert'); +const request = require('request'); +const sym = Symbol('smart_request_data'); function smart_request(options) { - var self = {} - self[sym] = {} - self[sym].error = Error() - Error.captureStackTrace(self[sym].error, smart_request) + let self = {}; + self[sym] = {}; + self[sym].error = Error(); + Error.captureStackTrace(self[sym].error, smart_request); - var result = new Promise(function (resolve, reject) { - self[sym].request = request(options, function (err, res, body) { - if (err) return reject(err) - self[sym].response = res - resolve(body) - }) - }) + let result = new Promise(function(resolve, reject) { + self[sym].request = request(options, function(err, res, body) { + if (err) return reject(err); + self[sym].response = res; + resolve(body); + }); + }); - return extend(self, result) + return extend(self, result); } function extend(self, promise) { - promise[sym] = self[sym] - Object.setPrototypeOf(promise, extensions) - return promise + promise[sym] = self[sym]; + Object.setPrototypeOf(promise, extensions); + return promise; } -var extensions = Object.create(Promise.prototype) +var extensions = Object.create(Promise.prototype); -extensions.status = function (expected) { - var self_data = this[sym] +extensions.status = function(expected) { + let self_data = this[sym]; - return extend(this, this.then(function (body) { + return extend(this, this.then(function(body) { try { - assert.equal(self_data.response.statusCode, expected) + assert.equal(self_data.response.statusCode, expected); } catch(err) { - self_data.error.message = err.message - throw self_data.error + self_data.error.message = err.message; + throw self_data.error; } - return body - })) -} + return body; + })); +}; -extensions.body_ok = function (expected) { - var self_data = this[sym] +extensions.body_ok = function(expected) { + let self_data = this[sym]; - return extend(this, this.then(function (body) { + return extend(this, this.then(function(body) { try { if (Object.prototype.toString.call(expected) === '[object RegExp]') { - assert(body.ok.match(expected), "'" + body.ok + "' doesn't match " + expected) + assert(body.ok.match(expected), '\'' + body.ok + '\' doesn\'t match ' + expected); } else { - assert.equal(body.ok, expected) + assert.equal(body.ok, expected); } - assert.equal(body.error, null) + assert.equal(body.error, null); } catch(err) { - self_data.error.message = err.message - throw self_data.error + self_data.error.message = err.message; + throw self_data.error; } - return body - })) -} + return body; + })); +}; -extensions.body_error = function (expected) { - var self_data = this[sym] +extensions.body_error = function(expected) { + let self_data = this[sym]; - return extend(this, this.then(function (body) { + return extend(this, this.then(function(body) { try { if (Object.prototype.toString.call(expected) === '[object RegExp]') { - assert(body.error.match(expected), body.error + " doesn't match " + expected) + assert(body.error.match(expected), body.error + ' doesn\'t match ' + expected); } else { - assert.equal(body.error, expected) + assert.equal(body.error, expected); } - assert.equal(body.ok, null) + assert.equal(body.ok, null); } catch(err) { - self_data.error.message = err.message - throw self_data.error + self_data.error.message = err.message; + throw self_data.error; } - return body - })) -} + return body; + })); +}; -extensions.request = function (cb) { - cb(this[sym].request) - return this -} +extensions.request = function(cb) { + cb(this[sym].request); + return this; +}; -extensions.response = function (cb) { - var self_data = this[sym] +extensions.response = function(cb) { + let self_data = this[sym]; - return extend(this, this.then(function (body) { - cb(self_data.response) - return body - })) -} + return extend(this, this.then(function(body) { + cb(self_data.response); + return body; + })); +}; -extensions.send = function (data) { - this[sym].request.end(data) - return this -} +extensions.send = function(data) { + this[sym].request.end(data); + return this; +}; -module.exports = smart_request +module.exports = smart_request; diff --git a/test/functional/lib/startup.js b/test/functional/lib/startup.js index 7e250d955..27e04c2ac 100644 --- a/test/functional/lib/startup.js +++ b/test/functional/lib/startup.js @@ -1,40 +1,44 @@ -var fork = require('child_process').fork -var express = require('express') -var rimraf = require('rimraf') -var Server = require('./server') +'use strict'; -var forks = process.forks = [] -process.server = Server('http://localhost:55551/') -process.server2 = Server('http://localhost:55552/') -process.express = express() -process.express.listen(55550) +const fork = require('child_process').fork; +const express = require('express'); +const rimraf = require('rimraf'); +const Server = require('./server'); -module.exports.start = function start(dir, conf, cb) { - rimraf(__dirname + '/../' + dir, function() { - // filter out --debug-brk - var oldArgv = process.execArgv - process.execArgv = process.execArgv.filter(function(x) { - return x !== '--debug-brk' - }) +const forks = process.forks = []; +process.server = Server('http://localhost:55551/'); +process.server2 = Server('http://localhost:55552/'); +process.express = express(); +process.express.listen(55550); - var f = fork(__dirname + '/../../../bin/verdaccio' - , ['-c', __dirname + '/../' + conf] - , {silent: !process.env.TRAVIS} - ) - forks.push(f) - f.on('message', function(msg) { - if ('verdaccio_started' in msg) { - cb(), cb = function(){} - } - }) - f.on('error', function(err) { - throw err - }) - process.execArgv = oldArgv - }) -} +module.exports.start = function(dir, conf) { + return new Promise(function(resolve, reject) { + rimraf(__dirname + '/../' + dir, function() { + // filter out --debug-brk + let oldArgv = process.execArgv; + process.execArgv = process.execArgv.filter(function(x) { + return x !== '--debug-brk'; + }); + + const f = fork(__dirname + '/../../../bin/verdaccio' + , ['-c', __dirname + '/../' + conf] + , {silent: !process.env.TRAVIS} + ); + forks.push(f); + f.on('message', function(msg) { + if ('verdaccio_started' in msg) { + resolve(); + } + }); + f.on('error', function(err) { + reject(err); + }); + process.execArgv = oldArgv; + }); + }); +}; process.on('exit', function() { - if (forks[0]) forks[0].kill() - if (forks[1]) forks[1].kill() -}) + if (forks[0]) forks[0].kill(); + if (forks[1]) forks[1].kill(); +}); diff --git a/test/functional/logout.js b/test/functional/logout.js index 3729e70c9..8bc5858f4 100644 --- a/test/functional/logout.js +++ b/test/functional/logout.js @@ -1,11 +1,13 @@ +'use strict'; + module.exports = function() { - var server = process.server + let server = process.server; describe('logout', function() { - it('should log out', function () { + it('should log out', function() { return server.logout('some-token') .status(200) - .body_ok(/Logged out/) - }) - }) -} + .body_ok(/Logged out/); + }); + }); +}; diff --git a/test/functional/mirror.js b/test/functional/mirror.js index d1105ca1c..4b7662a4e 100644 --- a/test/functional/mirror.js +++ b/test/functional/mirror.js @@ -1,64 +1,66 @@ -var assert = require('assert') +'use strict'; + +let assert = require('assert'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } module.exports = function() { - var server = process.server - var server2 = process.server2 + let server = process.server; + let server2 = process.server2; - it('testing anti-loop', function () { + it('testing anti-loop', function() { return server2.get_package('testloop') .status(404) - .body_error(/no such package/) + .body_error(/no such package/); }) - ;['fwd', /*'loop'*/].forEach(function(pkg) { - var prefix = pkg + ': ' - pkg = 'test' + pkg + ;['fwd'].forEach(function(pkg) { + let prefix = pkg + ': '; + pkg = 'test' + pkg; describe(pkg, function() { - before(function () { + before(function() { return server.put_package(pkg, require('./lib/package')(pkg)) .status(201) - .body_ok(/created new package/) - }) + .body_ok(/created new package/); + }); - it(prefix+'creating new package', function(){}) + it(prefix+'creating new package', function() {}); describe(pkg, function() { - before(function () { + before(function() { return server.put_version(pkg, '0.1.1', require('./lib/package')(pkg)) .status(201) - .body_ok(/published/) - }) + .body_ok(/published/); + }); - it(prefix+'uploading new package version', function(){}) + it(prefix+'uploading new package version', function() {}); - it(prefix+'uploading incomplete tarball', function () { - return server.put_tarball_incomplete(pkg, pkg+'.bad', readfile('fixtures/binary'), 3000) - }) + it(prefix+'uploading incomplete tarball', function() { + return server.put_tarball_incomplete(pkg, pkg+'.bad', readfile('fixtures/binary'), 3000); + }); describe('tarball', function() { - before(function () { + before(function() { return server.put_tarball(pkg, pkg+'.file', readfile('fixtures/binary')) .status(201) - .body_ok(/.*/) - }) + .body_ok(/.*/); + }); - it(prefix+'uploading new tarball', function(){}) + it(prefix+'uploading new tarball', function() {}); - it(prefix+'downloading tarball from server1', function () { + it(prefix+'downloading tarball from server1', function() { return server.get_tarball(pkg, pkg+'.file') .status(200) - .then(function (body) { - assert.deepEqual(body, readfile('fixtures/binary')) - }) - }) - }) - }) - }) - }) -} + .then(function(body) { + assert.deepEqual(body, readfile('fixtures/binary')); + }); + }); + }); + }); + }); + }); +}; diff --git a/test/functional/newnpmreg.js b/test/functional/newnpmreg.js index ee9311420..03b751225 100644 --- a/test/functional/newnpmreg.js +++ b/test/functional/newnpmreg.js @@ -1,20 +1,22 @@ -var assert = require('assert') +'use strict'; + +let assert = require('assert'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } function sha(x) { - return require('crypto').createHash('sha1', 'binary').update(x).digest('hex') + return require('crypto').createHash('sha1', 'binary').update(x).digest('hex'); } module.exports = function() { - var server = process.server - var server2 = process.server2 - var express = process.express + let server = process.server; + let server2 = process.server2; + let express = process.express; describe('newnpmreg', function() { - before(function () { + before(function() { return server.request({ uri: '/testpkg-newnpmreg', headers: { @@ -22,100 +24,100 @@ module.exports = function() { }, method: 'PUT', json: JSON.parse(readfile('fixtures/newnpmreg.json')), - }).status(201) - }) + }).status(201); + }); - it('add pkg', function () {}) + it('add pkg', function() {}); - it('server1 - tarball', function () { + it('server1 - tarball', function() { return server.get_tarball('testpkg-newnpmreg', 'testpkg-newnpmreg-0.0.0.tgz') .status(200) - .then(function (body) { + .then(function(body) { // not real sha due to utf8 conversion - assert.strictEqual(sha(body), '8ee7331cbc641581b1a8cecd9d38d744a8feb863') - }) - }) + assert.strictEqual(sha(body), '8ee7331cbc641581b1a8cecd9d38d744a8feb863'); + }); + }); - it('server2 - tarball', function () { + it('server2 - tarball', function() { return server2.get_tarball('testpkg-newnpmreg', 'testpkg-newnpmreg-0.0.0.tgz') .status(200) - .then(function (body) { + .then(function(body) { // not real sha due to utf8 conversion - assert.strictEqual(sha(body), '8ee7331cbc641581b1a8cecd9d38d744a8feb863') - }) - }) + assert.strictEqual(sha(body), '8ee7331cbc641581b1a8cecd9d38d744a8feb863'); + }); + }); - it('server1 - package', function () { + it('server1 - package', function() { return server.get_package('testpkg-newnpmreg') .status(200) - .then(function (body) { - assert.equal(body.name, 'testpkg-newnpmreg') - assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg') - assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55551/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz') - assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'}) - }) - }) + .then(function(body) { + assert.equal(body.name, 'testpkg-newnpmreg'); + assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg'); + assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55551/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz'); + assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'}); + }); + }); - it('server2 - package', function () { + it('server2 - package', function() { return server2.get_package('testpkg-newnpmreg') .status(200) - .then(function (body) { - assert.equal(body.name, 'testpkg-newnpmreg') - assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg') - assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55552/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz') - assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'}) - }) - }) + .then(function(body) { + assert.equal(body.name, 'testpkg-newnpmreg'); + assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg'); + assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55552/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz'); + assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'}); + }); + }); - it('server1 - readme', function () { - return server.request({ uri: '/-/readme/testpkg-newnpmreg' }) + it('server1 - readme', function() { + return server.request({uri: '/-/readme/testpkg-newnpmreg'}) .status(200) - .then(function (body) { - assert.equal(body, '

blah blah blah

\n') - }) - }) + .then(function(body) { + assert.equal(body, '

blah blah blah

\n'); + }); + }); - it('server2 - readme', function () { - return server2.request({ uri: '/-/readme/testpkg-newnpmreg' }) + it('server2 - readme', function() { + return server2.request({uri: '/-/readme/testpkg-newnpmreg'}) .status(200) - .then(function (body) { - assert.equal(body, '

blah blah blah

\n') - }) - }) + .then(function(body) { + assert.equal(body, '

blah blah blah

\n'); + }); + }); describe('search', function() { function check(obj) { - obj['testpkg-newnpmreg'].time.modified = '2014-10-02T07:07:51.000Z' + obj['testpkg-newnpmreg'].time.modified = '2014-10-02T07:07:51.000Z'; assert.deepEqual(obj['testpkg-newnpmreg'], - { name: 'testpkg-newnpmreg', - description: '', - author: '', - license: 'ISC', - 'dist-tags': { latest: '0.0.0' }, - maintainers: [ { name: 'alex', email: 'alex@kocharin.ru' } ], - readmeFilename: '', - time: { modified: '2014-10-02T07:07:51.000Z' }, - versions: {}, - repository: { type: 'git', url: '' } }) + {'name': 'testpkg-newnpmreg', + 'description': '', + 'author': '', + 'license': 'ISC', + 'dist-tags': {latest: '0.0.0'}, + 'maintainers': [{name: 'alex', email: 'alex@kocharin.ru'}], + 'readmeFilename': '', + 'time': {modified: '2014-10-02T07:07:51.000Z'}, + 'versions': {}, + 'repository': {type: 'git', url: ''}}); } - before(function () { + before(function() { express.get('/-/all', function(req, res) { - res.send({}) - }) - }) + res.send({}); + }); + }); - it('server1 - search', function () { - return server.request({ uri: '/-/all' }) + it('server1 - search', function() { + return server.request({uri: '/-/all'}) .status(200) - .then(check) - }) + .then(check); + }); - it('server2 - search', function () { - return server2.request({ uri: '/-/all' }) + it('server2 - search', function() { + return server2.request({uri: '/-/all'}) .status(200) - .then(check) - }) - }) - }) -} + .then(check); + }); + }); + }); +}; diff --git a/test/functional/nullstorage.js b/test/functional/nullstorage.js index 11857a422..bdf714d8b 100644 --- a/test/functional/nullstorage.js +++ b/test/functional/nullstorage.js @@ -1,71 +1,73 @@ -require('./lib/startup') +'use strict'; -var assert = require('assert') -var crypto = require('crypto') +require('./lib/startup'); + +let assert = require('assert'); +let crypto = require('crypto'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } module.exports = function() { - var server = process.server - var server2 = process.server2 + let server = process.server; + let server2 = process.server2; - it('trying to fetch non-existent package / null storage', function () { + it('trying to fetch non-existent package / null storage', function() { return server.get_package('test-nullstorage-nonexist') .status(404) - .body_error(/no such package/) - }) + .body_error(/no such package/); + }); describe('test-nullstorage on server2', function() { - before(function () { - return server2.add_package('test-nullstorage2') - }) + before(function() { + return server2.add_package('test-nullstorage2'); + }); - it('creating new package - server2', function(){/* test for before() */}) + it('creating new package - server2', function() {/* test for before() */}); - it('downloading non-existent tarball', function () { + it('downloading non-existent tarball', function() { return server.get_tarball('test-nullstorage2', 'blahblah') .status(404) - .body_error(/no such file/) - }) + .body_error(/no such file/); + }); describe('tarball', function() { - before(function () { + before(function() { return server2.put_tarball('test-nullstorage2', 'blahblah', readfile('fixtures/binary')) .status(201) - .body_ok(/.*/) - }) + .body_ok(/.*/); + }); - before(function () { - var pkg = require('./lib/package')('test-nullstorage2') - pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex') + before(function() { + let pkg = require('./lib/package')('test-nullstorage2'); + pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex'); return server2.put_version('test-nullstorage2', '0.0.1', pkg) .status(201) - .body_ok(/published/) - }) + .body_ok(/published/); + }); - it('uploading new tarball', function () {/* test for before() */}) + it('uploading new tarball', function() {/* test for before() */}); - it('downloading newly created tarball', function () { + it('downloading newly created tarball', function() { return server.get_tarball('test-nullstorage2', 'blahblah') .status(200) - .then(function (body) { - assert.deepEqual(body, readfile('fixtures/binary')) - }) - }) + .then(function(body) { + assert.deepEqual(body, readfile('fixtures/binary')); + }); + }); - it('downloading newly created package', function () { + it('downloading newly created package', function() { return server.get_package('test-nullstorage2') .status(200) - .then(function (body) { - assert.equal(body.name, 'test-nullstorage2') - assert.equal(body.versions['0.0.1'].name, 'test-nullstorage2') - assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/test-nullstorage2/-/blahblah') - assert.deepEqual(body['dist-tags'], {latest: '0.0.1'}) - }) - }) - }) - }) -} + .then(function(body) { + assert.equal(body.name, 'test-nullstorage2'); + assert.equal(body.versions['0.0.1'].name, 'test-nullstorage2'); + assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/test-nullstorage2/-/blahblah'); + assert.deepEqual(body['dist-tags'], {latest: '0.0.1'}); + }); + }); + }); + }); +}; diff --git a/test/functional/plugins.js b/test/functional/plugins.js index 5b22d5170..803ae0f22 100644 --- a/test/functional/plugins.js +++ b/test/functional/plugins.js @@ -1,120 +1,122 @@ -require('./lib/startup') +'use strict'; -var assert = require('assert') +require('./lib/startup'); + +let assert = require('assert'); module.exports = function() { - var server2 = process.server2 + let server2 = process.server2; describe('authentication', function() { - var authstr + let authstr; before(function() { - authstr = server2.authstr - }) + authstr = server2.authstr; + }); - it('should not authenticate with wrong password', function () { + it('should not authenticate with wrong password', function() { return server2.auth('authtest', 'wrongpass') .status(409) .body_error('this user already exists') - .then(function () { - return server2.whoami() + .then(function() { + return server2.whoami(); }) - .then(function (username) { - assert.equal(username, null) - }) - }) + .then(function(username) { + assert.equal(username, null); + }); + }); - it('wrong password handled by plugin', function () { + it('wrong password handled by plugin', function() { return server2.auth('authtest2', 'wrongpass') .status(409) .body_error('registration is disabled') - .then(function () { - return server2.whoami() + .then(function() { + return server2.whoami(); }) - .then(function (username) { - assert.equal(username, null) - }) - }) + .then(function(username) { + assert.equal(username, null); + }); + }); - it('right password handled by plugin', function () { + it('right password handled by plugin', function() { return server2.auth('authtest2', 'blahblah') .status(201) .body_ok(/'authtest2'/) - .then(function () { - return server2.whoami() + .then(function() { + return server2.whoami(); }) - .then(function (username) { - assert.equal(username, 'authtest2') - }) - }) + .then(function(username) { + assert.equal(username, 'authtest2'); + }); + }); after(function() { - server2.authstr = authstr - }) - }) + server2.authstr = authstr; + }); + }); describe('authorization', function() { - var authstr + let authstr; before(function() { - authstr = server2.authstr - }) + authstr = server2.authstr; + }); describe('authtest', function() { - before(function () { + before(function() { return server2.auth('authtest', 'test') .status(201) - .body_ok(/'authtest'/) - }) + .body_ok(/'authtest'/); + }); - it('access test-auth-allow', function () { + it('access test-auth-allow', function() { return server2.get_package('test-auth-allow') .status(404) - .body_error('no such package available') - }) + .body_error('no such package available'); + }); - it('access test-auth-deny', function () { + it('access test-auth-deny', function() { return server2.get_package('test-auth-deny') .status(403) - .body_error("you're not allowed here") - }) + .body_error('you\'re not allowed here'); + }); - it('access test-auth-regular', function () { + it('access test-auth-regular', function() { return server2.get_package('test-auth-regular') .status(404) - .body_error('no such package available') - }) - }) + .body_error('no such package available'); + }); + }); describe('authtest2', function() { - before(function () { + before(function() { return server2.auth('authtest2', 'blahblah') .status(201) - .body_ok(/'authtest2'/) - }) + .body_ok(/'authtest2'/); + }); - it('access test-auth-allow', function () { + it('access test-auth-allow', function() { return server2.get_package('test-auth-allow') .status(403) - .body_error("i don't know anything about you") - }) + .body_error('i don\'t know anything about you'); + }); - it('access test-auth-deny', function () { + it('access test-auth-deny', function() { return server2.get_package('test-auth-deny') .status(403) - .body_error("i don't know anything about you") - }) + .body_error('i don\'t know anything about you'); + }); - it('access test-auth-regular', function () { + it('access test-auth-regular', function() { return server2.get_package('test-auth-regular') .status(404) - .body_error('no such package available') - }) - }) + .body_error('no such package available'); + }); + }); after(function() { - server2.authstr = authstr - }) - }) -} + server2.authstr = authstr; + }); + }); +}; diff --git a/test/functional/plugins/authenticate.js b/test/functional/plugins/authenticate.js index 2c4d68c69..6b638abe3 100644 --- a/test/functional/plugins/authenticate.js +++ b/test/functional/plugins/authenticate.js @@ -1,25 +1,26 @@ +'use strict'; -module.exports = Plugin +module.exports = Plugin; function Plugin(config, stuff) { - var self = Object.create(Plugin.prototype) - self._config = config - return self + let self = Object.create(Plugin.prototype); + self._config = config; + return self; } // plugin is expected to be compatible with... -Plugin.prototype.verdaccio_version = '1.1.0' +Plugin.prototype.verdaccio_version = '1.1.0'; Plugin.prototype.authenticate = function(user, password, cb) { - var self = this + let self = this; if (user !== self._config.accept_user) { // delegate to next plugin - return cb(null, false) + return cb(null, false); } if (password !== self._config.with_password) { - var err = Error("i don't like your password") - err.status = 403 - return cb(err) + let err = Error('i don\'t like your password'); + err.status = 403; + return cb(err); } - return cb(null, [ user ]) -} + return cb(null, [user]); +}; diff --git a/test/functional/plugins/authorize.js b/test/functional/plugins/authorize.js index 5c2a73227..c3a68c541 100644 --- a/test/functional/plugins/authorize.js +++ b/test/functional/plugins/authorize.js @@ -1,30 +1,31 @@ +'use strict'; -module.exports = Plugin +module.exports = Plugin; function Plugin(config, stuff) { - var self = Object.create(Plugin.prototype) - self._config = config - return self + let self = Object.create(Plugin.prototype); + self._config = config; + return self; } // plugin is expected to be compatible with... -Plugin.prototype.verdaccio_version = '1.1.0' +Plugin.prototype.verdaccio_version = '1.1.0'; -Plugin.prototype.allow_access = function(user, package, cb) { - var self = this - if (!package.handled_by_auth_plugin) { +Plugin.prototype.allow_access = function(user, pkg, cb) { + let self = this; + if (!pkg.handled_by_auth_plugin) { // delegate to next plugin - return cb(null, false) + return cb(null, false); } if (user.name !== self._config.allow_user) { - var err = Error("i don't know anything about you") - err.status = 403 - return cb(err) + var err = Error('i don\'t know anything about you'); + err.status = 403; + return cb(err); } - if (package.name !== self._config.to_access) { - var err = Error("you're not allowed here") - err.status = 403 - return cb(err) + if (pkg.name !== self._config.to_access) { + var err = Error('you\'re not allowed here'); + err.status = 403; + return cb(err); } - return cb(null, true) -} + return cb(null, true); +}; diff --git a/test/functional/race.js b/test/functional/race.js index 97aab5703..bd0af1866 100644 --- a/test/functional/race.js +++ b/test/functional/race.js @@ -1,99 +1,105 @@ -var assert = require('assert') -var async = require('async') -var _oksum = 0 +'use strict'; + +let assert = require('assert'); +let async = require('async'); +let _oksum = 0; module.exports = function() { - var server = process.server + let server = process.server; describe('race', function() { - before(function () { + before(function() { return server.put_package('race', require('./lib/package')('race')) .status(201) - .body_ok(/created new package/) - }) + .body_ok(/created new package/); + }); - it('creating new package', function(){}) + it('creating new package', function() {}); - it('uploading 10 same versions', function (callback) { - var fns = [] - for (var i=0; i<10; i++) { + it('uploading 10 same versions', function(callback) { + let fns = []; + for (let i=0; i<10; i++) { fns.push(function(cb_) { - var data = require('./lib/package')('race') - data.rand = Math.random() + let data = require('./lib/package')('race'); + data.rand = Math.random(); - var _res + let _res; server.put_version('race', '0.0.1', data) - .response(function (res) { _res = res }) - .then(function (body) { - cb_(null, [ _res, body ]) - }) - }) + .response(function(res) { + _res = res; +}) + .then(function(body) { + cb_(null, [_res, body]); + }); + }); } async.parallel(fns, function(err, res) { - var okcount = 0 - var failcount = 0 + let okcount = 0; + let failcount = 0; - assert.equal(err, null) + assert.equal(err, null); res.forEach(function(arr) { - var resp = arr[0] - var body = arr[1] + let resp = arr[0]; + let body = arr[1]; - if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++ - if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++ - if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++ - }) - assert.equal(okcount + failcount, 10) - assert.equal(okcount, 1) - _oksum += okcount + if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++; + if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++; + if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++; + }); + assert.equal(okcount + failcount, 10); + assert.equal(okcount, 1); + _oksum += okcount; - callback() - }) - }) + callback(); + }); + }); - it('uploading 10 diff versions', function (callback) { - var fns = [] - for (var i=0; i<10; i++) { + it('uploading 10 diff versions', function(callback) { + let fns = []; + for (let i=0; i<10; i++) { ;(function(i) { fns.push(function(cb_) { - var _res + let _res; server.put_version('race', '0.1.'+String(i), require('./lib/package')('race')) - .response(function (res) { _res = res }) - .then(function (body) { - cb_(null, [ _res, body ]) - }) - }) - })(i) + .response(function(res) { + _res = res; +}) + .then(function(body) { + cb_(null, [_res, body]); + }); + }); + })(i); } async.parallel(fns, function(err, res) { - var okcount = 0 - var failcount = 0 + let okcount = 0; + let failcount = 0; - assert.equal(err, null) + assert.equal(err, null); res.forEach(function(arr) { - var resp = arr[0] - var body = arr[1] - if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++ - if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++ - if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++ - }) - assert.equal(okcount + failcount, 10) - assert.notEqual(okcount, 1) - _oksum += okcount + let resp = arr[0]; + let body = arr[1]; + if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++; + if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++; + if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++; + }); + assert.equal(okcount + failcount, 10); + assert.notEqual(okcount, 1); + _oksum += okcount; - callback() - }) - }) + callback(); + }); + }); - after('downloading package', function () { + after('downloading package', function() { return server.get_package('race') .status(200) - .then(function (body) { - assert.equal(Object.keys(body.versions).length, _oksum) - }) - }) - }) -} + .then(function(body) { + assert.equal(Object.keys(body.versions).length, _oksum); + }); + }); + }); +}; diff --git a/test/functional/racycrash.js b/test/functional/racycrash.js index de5e5c21d..7e57082ea 100644 --- a/test/functional/racycrash.js +++ b/test/functional/racycrash.js @@ -1,66 +1,70 @@ -var assert = require('assert') +'use strict'; + +let assert = require('assert'); module.exports = function() { - var server = process.server - var express = process.express + let server = process.server; + let express = process.express; describe('Racy', function() { - var on_tarball + let on_tarball; before(function() { express.get('/testexp-racycrash', function(_, res) { res.send({ - "name": "testexp-racycrash", - "versions": { - "0.1.0": { - "name": "testexp_tags", - "version": "0.1.0", - "dist": { - "shasum": "fake", - "tarball": "http://localhost:55550/testexp-racycrash/-/test.tar.gz" - } - } - } - }) - }) + 'name': 'testexp-racycrash', + 'versions': { + '0.1.0': { + 'name': 'testexp_tags', + 'version': '0.1.0', + 'dist': { + 'shasum': 'fake', + 'tarball': 'http://localhost:55550/testexp-racycrash/-/test.tar.gz', + }, + }, + }, + }); + }); express.get('/testexp-racycrash/-/test.tar.gz', function(_, res) { - on_tarball(res) - }) - }) + on_tarball(res); + }); + }); it('should not crash on error if client disconnects', function(_cb) { on_tarball = function(res) { - res.header('content-length', 1e6) - res.write('test test test\n') + res.header('content-length', 1e6); + res.write('test test test\n'); setTimeout(function() { - res.write('test test test\n') - res.socket.destroy() - cb() - }, 200) - } + res.write('test test test\n'); + res.socket.destroy(); + cb(); + }, 200); + }; - server.request({ uri: '/testexp-racycrash/-/test.tar.gz' }) - .then(function (body) { - assert.equal(body, 'test test test\n') - }) + server.request({uri: '/testexp-racycrash/-/test.tar.gz'}) + .then(function(body) { + assert.equal(body, 'test test test\n'); + }); function cb() { // test for NOT crashing - server.request({ uri: '/testexp-racycrash' }) + server.request({uri: '/testexp-racycrash'}) .status(200) - .then(function () { _cb() }) + .then(function() { + _cb(); +}); } - }) + }); - it('should not store tarball', function () { + it('should not store tarball', function() { on_tarball = function(res) { - res.socket.destroy() - } + res.socket.destroy(); + }; - return server.request({ uri: '/testexp-racycrash/-/test.tar.gz' }) - .body_error('internal server error') - }) - }) -} + return server.request({uri: '/testexp-racycrash/-/test.tar.gz'}) + .body_error('internal server error'); + }); + }); +}; diff --git a/test/functional/scoped.js b/test/functional/scoped.js index f25f16260..ed06df644 100644 --- a/test/functional/scoped.js +++ b/test/functional/scoped.js @@ -1,19 +1,21 @@ -var assert = require('assert') +'use strict'; + +let assert = require('assert'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } function sha(x) { - return require('crypto').createHash('sha1', 'binary').update(x).digest('hex') + return require('crypto').createHash('sha1', 'binary').update(x).digest('hex'); } module.exports = function() { - var server = process.server - var server2 = process.server2 + let server = process.server; + let server2 = process.server2; describe('test-scoped', function() { - before(function () { + before(function() { return server.request({ uri: '/@test%2fscoped', headers: { @@ -21,58 +23,58 @@ module.exports = function() { }, method: 'PUT', json: JSON.parse(readfile('fixtures/scoped.json')), - }).status(201) - }) + }).status(201); + }); - it('add pkg', function () {}) + it('add pkg', function() {}); - it('server1 - tarball', function () { + it('server1 - tarball', function() { return server.get_tarball('@test/scoped', 'scoped-1.0.0.tgz') .status(200) - .then(function (body) { + .then(function(body) { // not real sha due to utf8 conversion - assert.strictEqual(sha(body), '6e67b14e2c0e450b942e2bc8086b49e90f594790') - }) - }) + assert.strictEqual(sha(body), '6e67b14e2c0e450b942e2bc8086b49e90f594790'); + }); + }); - it('server2 - tarball', function () { + it('server2 - tarball', function() { return server2.get_tarball('@test/scoped', 'scoped-1.0.0.tgz') .status(200) - .then(function (body) { + .then(function(body) { // not real sha due to utf8 conversion - assert.strictEqual(sha(body), '6e67b14e2c0e450b942e2bc8086b49e90f594790') - }) - }) + assert.strictEqual(sha(body), '6e67b14e2c0e450b942e2bc8086b49e90f594790'); + }); + }); - it('server1 - package', function () { + it('server1 - package', function() { return server.get_package('@test/scoped') .status(200) - .then(function (body) { - assert.equal(body.name, '@test/scoped') - assert.equal(body.versions['1.0.0'].name, '@test/scoped') - assert.equal(body.versions['1.0.0'].dist.tarball, 'http://localhost:55551/@test%2fscoped/-/scoped-1.0.0.tgz') - assert.deepEqual(body['dist-tags'], {latest: '1.0.0'}) - }) - }) + .then(function(body) { + assert.equal(body.name, '@test/scoped'); + assert.equal(body.versions['1.0.0'].name, '@test/scoped'); + assert.equal(body.versions['1.0.0'].dist.tarball, 'http://localhost:55551/@test%2fscoped/-/scoped-1.0.0.tgz'); + assert.deepEqual(body['dist-tags'], {latest: '1.0.0'}); + }); + }); - it('server2 - package', function () { + it('server2 - package', function() { return server2.get_package('@test/scoped') .status(200) - .then(function (body) { - assert.equal(body.name, '@test/scoped') - assert.equal(body.versions['1.0.0'].name, '@test/scoped') - assert.equal(body.versions['1.0.0'].dist.tarball, 'http://localhost:55552/@test%2fscoped/-/scoped-1.0.0.tgz') - assert.deepEqual(body['dist-tags'], {latest: '1.0.0'}) - }) - }) + .then(function(body) { + assert.equal(body.name, '@test/scoped'); + assert.equal(body.versions['1.0.0'].name, '@test/scoped'); + assert.equal(body.versions['1.0.0'].dist.tarball, 'http://localhost:55552/@test%2fscoped/-/scoped-1.0.0.tgz'); + assert.deepEqual(body['dist-tags'], {latest: '1.0.0'}); + }); + }); - it('server2 - nginx workaround', function () { - return server2.request({ uri: '/@test/scoped/1.0.0' }) + it('server2 - nginx workaround', function() { + return server2.request({uri: '/@test/scoped/1.0.0'}) .status(200) - .then(function (body) { - assert.equal(body.name, '@test/scoped') - assert.equal(body.dist.tarball, 'http://localhost:55552/@test%2fscoped/-/scoped-1.0.0.tgz') - }) - }) - }) -} + .then(function(body) { + assert.equal(body.name, '@test/scoped'); + assert.equal(body.dist.tarball, 'http://localhost:55552/@test%2fscoped/-/scoped-1.0.0.tgz'); + }); + }); + }); +}; diff --git a/test/functional/security.js b/test/functional/security.js index 961f56c16..51781ef37 100644 --- a/test/functional/security.js +++ b/test/functional/security.js @@ -1,70 +1,72 @@ -var assert = require('assert') +'use strict'; + +const assert = require('assert'); module.exports = function() { - var server = process.server + let server = process.server; describe('Security', function() { before(function() { - return server.add_package('testpkg-sec') - }) + return server.add_package('testpkg-sec'); + }); - it('bad pkg #1', function () { + it('bad pkg #1', function() { return server.get_package('package.json') .status(403) - .body_error(/invalid package/) - }) + .body_error(/invalid package/); + }); - it('bad pkg #2', function () { + it('bad pkg #2', function() { return server.get_package('__proto__') .status(403) - .body_error(/invalid package/) - }) + .body_error(/invalid package/); + }); - it('__proto__, connect stuff', function () { - return server.request({ uri: '/testpkg-sec?__proto__=1' }) - .then(function (body) { + it('__proto__, connect stuff', function() { + return server.request({uri: '/testpkg-sec?__proto__=1'}) + .then(function(body) { // test for NOT outputting stack trace - assert(!body || typeof(body) === 'object' || body.indexOf('node_modules') === -1) + assert(!body || typeof(body) === 'object' || body.indexOf('node_modules') === -1); // test for NOT crashing - return server.request({ uri: '/testpkg-sec' }).status(200) - }) - }) + return server.request({uri: '/testpkg-sec'}).status(200); + }); + }); - it('do not return package.json as an attachment', function () { - return server.request({ uri: '/testpkg-sec/-/package.json' }) + it('do not return package.json as an attachment', function() { + return server.request({uri: '/testpkg-sec/-/package.json'}) .status(403) - .body_error(/invalid filename/) - }) + .body_error(/invalid filename/); + }); - it('silly things - reading #1', function () { - return server.request({ uri: '/testpkg-sec/-/../../../../../../../../etc/passwd' }) - .status(404) - }) + it('silly things - reading #1', function() { + return server.request({uri: '/testpkg-sec/-/../../../../../../../../etc/passwd'}) + .status(404); + }); - it('silly things - reading #2', function () { - return server.request({ uri: '/testpkg-sec/-/%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd' }) + it('silly things - reading #2', function() { + return server.request({uri: '/testpkg-sec/-/%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'}) .status(403) - .body_error(/invalid filename/) - }) + .body_error(/invalid filename/); + }); - it('silly things - writing #1', function () { + it('silly things - writing #1', function() { return server.put_tarball('testpkg-sec', 'package.json', '{}') .status(403) - .body_error(/invalid filename/) - }) + .body_error(/invalid filename/); + }); - it('silly things - writing #3', function () { + it('silly things - writing #3', function() { return server.put_tarball('testpkg-sec', 'node_modules', '{}') .status(403) - .body_error(/invalid filename/) - }) + .body_error(/invalid filename/); + }); - it('silly things - writing #4', function () { + it('silly things - writing #4', function() { return server.put_tarball('testpkg-sec', '../testpkg.tgz', '{}') .status(403) - .body_error(/invalid filename/) - }) - }) -} + .body_error(/invalid filename/); + }); + }); +}; diff --git a/test/functional/tags.js b/test/functional/tags.js index b90068bc6..ebc62c872 100644 --- a/test/functional/tags.js +++ b/test/functional/tags.js @@ -1,168 +1,170 @@ -var assert = require('assert') +'use strict'; + +const assert = require('assert'); function readfile(x) { - return require('fs').readFileSync(__dirname + '/' + x) + return require('fs').readFileSync(__dirname + '/' + x); } module.exports = function() { - var server = process.server - var express = process.express + let server = process.server; + let express = process.express; - it('tags - testing for 404', function () { + it('tags - testing for 404', function() { return server.get_package('testexp_tags') // shouldn't exist yet .status(404) - .body_error(/no such package/) - }) + .body_error(/no such package/); + }); describe('tags', function() { - before(function () { + before(function() { express.get('/testexp_tags', function(req, res) { - var f = readfile('fixtures/tags.json').toString().replace(/__NAME__/g, 'testexp_tags') - res.send(JSON.parse(f)) - }) - }) + let f = readfile('fixtures/tags.json').toString().replace(/__NAME__/g, 'testexp_tags'); + res.send(JSON.parse(f)); + }); + }); - it('fetching package again', function () { + it('fetching package again', function() { return server.get_package('testexp_tags') .status(200) - .then(function (body) { - assert.equal(typeof(body.versions['1.1']), 'object') - assert.equal(body['dist-tags'].something, '0.1.1alpha') + .then(function(body) { + assert.equal(typeof(body.versions['1.1']), 'object'); + assert.equal(body['dist-tags'].something, '0.1.1alpha'); // note: 5.4.3 is invalid tag, 0.1.3alpha is highest semver - assert.equal(body['dist-tags'].latest, '0.1.3alpha') - assert.equal(body['dist-tags'].bad, null) - }) + assert.equal(body['dist-tags'].latest, '0.1.3alpha'); + assert.equal(body['dist-tags'].bad, null); + }); }) ;['0.1.1alpha', '0.1.1-alpha', '0000.00001.001-alpha'].forEach(function(ver) { - it('fetching '+ver, function () { - return server.request({uri:'/testexp_tags/'+ver}) + it('fetching '+ver, function() { + return server.request({uri: '/testexp_tags/'+ver}) .status(200) - .then(function (body) { - assert.equal(body.version, '0.1.1alpha') - }) - }) - }) - }) + .then(function(body) { + assert.equal(body.version, '0.1.1alpha'); + }); + }); + }); + }); describe('dist-tags methods', function() { - before(function () { + before(function() { express.get('/testexp_tags2', function(req, res) { - var f = readfile('fixtures/tags.json').toString().replace(/__NAME__/g, 'testexp_tags2') - res.send(JSON.parse(f)) - }) - }) + let f = readfile('fixtures/tags.json').toString().replace(/__NAME__/g, 'testexp_tags2'); + res.send(JSON.parse(f)); + }); + }); // populate cache - before(function () { + before(function() { return server.get_package('testexp_tags2') - .status(200) - }) + .status(200); + }); - beforeEach(function () { + beforeEach(function() { return server.request({ method: 'PUT', - uri: '/-/package/testexp_tags2/dist-tags', - json: { + uri: '/-/package/testexp_tags2/dist-tags', + json: { foo: '0.1.0', bar: '0.1.1alpha', baz: '0.1.2', }, - }).status(201).body_ok(/tags updated/) - }) + }).status(201).body_ok(/tags updated/); + }); - it('fetching tags', function () { + it('fetching tags', function() { return server.request({ method: 'GET', - uri: '/-/package/testexp_tags2/dist-tags', - }).status(200).then(function (body) { + uri: '/-/package/testexp_tags2/dist-tags', + }).status(200).then(function(body) { assert.deepEqual(body, - { foo: '0.1.0', + {foo: '0.1.0', bar: '0.1.1alpha', baz: '0.1.2', - latest: '0.1.3alpha' }) - }) - }) + latest: '0.1.3alpha'}); + }); + }); - it('merging tags', function () { + it('merging tags', function() { return server.request({ method: 'POST', - uri: '/-/package/testexp_tags2/dist-tags', - json: { + uri: '/-/package/testexp_tags2/dist-tags', + json: { foo: '0.1.2', quux: '0.1.0', }, - }).status(201).body_ok(/updated/).then(function () { + }).status(201).body_ok(/updated/).then(function() { return server.request({ method: 'GET', - uri: '/-/package/testexp_tags2/dist-tags', - }).status(200).then(function (body) { + uri: '/-/package/testexp_tags2/dist-tags', + }).status(200).then(function(body) { assert.deepEqual(body, - { foo: '0.1.2', + {foo: '0.1.2', bar: '0.1.1alpha', baz: '0.1.2', quux: '0.1.0', - latest: '0.1.3alpha' }) - }) - }) - }) + latest: '0.1.3alpha'}); + }); + }); + }); - it('replacing tags', function () { + it('replacing tags', function() { return server.request({ method: 'PUT', - uri: '/-/package/testexp_tags2/dist-tags', - json: { + uri: '/-/package/testexp_tags2/dist-tags', + json: { foo: '0.1.2', quux: '0.1.0', }, - }).status(201).body_ok(/updated/).then(function () { + }).status(201).body_ok(/updated/).then(function() { return server.request({ method: 'GET', - uri: '/-/package/testexp_tags2/dist-tags', - }).status(200).then(function (body) { + uri: '/-/package/testexp_tags2/dist-tags', + }).status(200).then(function(body) { assert.deepEqual(body, - { foo: '0.1.2', + {foo: '0.1.2', quux: '0.1.0', - latest: '0.1.3alpha' }) - }) - }) - }) + latest: '0.1.3alpha'}); + }); + }); + }); - it('adding a tag', function () { + it('adding a tag', function() { return server.request({ method: 'PUT', - uri: '/-/package/testexp_tags2/dist-tags/foo', - json: '0.1.3alpha', - }).status(201).body_ok(/tagged/).then(function () { + uri: '/-/package/testexp_tags2/dist-tags/foo', + json: '0.1.3alpha', + }).status(201).body_ok(/tagged/).then(function() { return server.request({ method: 'GET', - uri: '/-/package/testexp_tags2/dist-tags', - }).status(200).then(function (body) { + uri: '/-/package/testexp_tags2/dist-tags', + }).status(200).then(function(body) { assert.deepEqual(body, - { foo: '0.1.3alpha', + {foo: '0.1.3alpha', bar: '0.1.1alpha', baz: '0.1.2', - latest: '0.1.3alpha' }) - }) - }) - }) + latest: '0.1.3alpha'}); + }); + }); + }); - it('removing a tag', function () { + it('removing a tag', function() { return server.request({ method: 'DELETE', - uri: '/-/package/testexp_tags2/dist-tags/foo', - }).status(201).body_ok(/removed/).then(function () { + uri: '/-/package/testexp_tags2/dist-tags/foo', + }).status(201).body_ok(/removed/).then(function() { return server.request({ method: 'GET', - uri: '/-/package/testexp_tags2/dist-tags', - }).status(200).then(function (body) { + uri: '/-/package/testexp_tags2/dist-tags', + }).status(200).then(function(body) { assert.deepEqual(body, - { bar: '0.1.1alpha', + {bar: '0.1.1alpha', baz: '0.1.2', - latest: '0.1.3alpha' }) - }) - }) - }) - }) -} + latest: '0.1.3alpha'}); + }); + }); + }); + }); +}; diff --git a/test/unit/config_def.js b/test/unit/config_def.js index c3c0caae8..d540f62d7 100644 --- a/test/unit/config_def.js +++ b/test/unit/config_def.js @@ -1,8 +1,9 @@ +'use strict'; describe('config.yaml', function() { it('should be parseable', function() { - var source = require('fs').readFileSync(__dirname + '/../../conf/default.yaml', 'utf8') - require('js-yaml').safeLoad(source) - }) -}) + let source = require('fs').readFileSync(__dirname + '/../../conf/default.yaml', 'utf8'); + require('js-yaml').safeLoad(source); + }); +}); diff --git a/test/unit/listen_addr.js b/test/unit/listen_addr.js index dc2fd2ed8..7bfbd61ab 100644 --- a/test/unit/listen_addr.js +++ b/test/unit/listen_addr.js @@ -1,42 +1,44 @@ -var assert = require('assert') -var parse = require('../../lib/utils').parse_address +'use strict'; + +let assert = require('assert'); +let parse = require('../../lib/utils').parse_address; describe('Parse address', function() { function addTest(what, proto, host, port) { it(what, function() { if (proto === null) { - assert.strictEqual(parse(what), null) + assert.strictEqual(parse(what), null); } else if (port) { assert.deepEqual(parse(what), { proto: proto, host: host, port: port, - }) + }); } else { assert.deepEqual(parse(what), { proto: proto, path: host, - }) + }); } - }) + }); } - addTest('4873', 'http', 'localhost', '4873') - addTest(':4873', 'http', 'localhost', '4873') - addTest('blah:4873', 'http', 'blah', '4873') - addTest('http://:4873', 'http', 'localhost', '4873') - addTest('https::4873', 'https', 'localhost', '4873') - addTest('https:blah:4873', 'https', 'blah', '4873') - addTest('https://blah:4873/', 'https', 'blah', '4873') - addTest('[::1]:4873', 'http', '::1', '4873') - addTest('https:[::1]:4873', 'https', '::1', '4873') + addTest('4873', 'http', 'localhost', '4873'); + addTest(':4873', 'http', 'localhost', '4873'); + addTest('blah:4873', 'http', 'blah', '4873'); + addTest('http://:4873', 'http', 'localhost', '4873'); + addTest('https::4873', 'https', 'localhost', '4873'); + addTest('https:blah:4873', 'https', 'blah', '4873'); + addTest('https://blah:4873/', 'https', 'blah', '4873'); + addTest('[::1]:4873', 'http', '::1', '4873'); + addTest('https:[::1]:4873', 'https', '::1', '4873'); - addTest('unix:/tmp/foo.sock', 'http', '/tmp/foo.sock') - addTest('http:unix:foo.sock', 'http', 'foo.sock') - addTest('https://unix:foo.sock', 'https', 'foo.sock') + addTest('unix:/tmp/foo.sock', 'http', '/tmp/foo.sock'); + addTest('http:unix:foo.sock', 'http', 'foo.sock'); + addTest('https://unix:foo.sock', 'https', 'foo.sock'); - addTest('blah', null) - addTest('blah://4873', null) - addTest('https://blah:4873///', null) - addTest('unix:1234', 'http', 'unix', '1234') // not unix socket -}) + addTest('blah', null); + addTest('blah://4873', null); + addTest('https://blah:4873///', null); + addTest('unix:1234', 'http', 'unix', '1234'); // not unix socket +}); diff --git a/test/unit/mystreams.js b/test/unit/mystreams.js index de6894e4b..13ff29dbd 100644 --- a/test/unit/mystreams.js +++ b/test/unit/mystreams.js @@ -1,17 +1,19 @@ -var ReadTarball = require('../../lib/streams').ReadTarballStream +'use strict'; + +let ReadTarball = require('../../lib/streams').ReadTarballStream; describe('mystreams', function() { it('should delay events', function(cb) { - var test = new ReadTarball() - test.abort() + let test = new ReadTarball(); + test.abort(); setTimeout(function() { test.abort = function() { - cb() - } + cb(); + }; test.abort = function() { - throw Error('fail') - } - }, 10) - }) -}) + throw Error('fail'); + }; + }, 10); + }); +}); diff --git a/test/unit/no_proxy.js b/test/unit/no_proxy.js index 317667515..61e1fdbd7 100644 --- a/test/unit/no_proxy.js +++ b/test/unit/no_proxy.js @@ -1,88 +1,90 @@ -var assert = require('assert') -var Storage = require('../../lib/up-storage') +'use strict'; -require('../../lib/logger').setup([]) +let assert = require('assert'); +let Storage = require('../../lib/up-storage'); + +require('../../lib/logger').setup([]); function setup(host, config, mainconfig) { - config.url = host - return Storage(config, mainconfig) + config.url = host; + return new Storage(config, mainconfig); } describe('Use proxy', function() { it('should work fine without proxy', function() { - var x = setup('http://x/x', {}, {}) - assert.equal(x.proxy, null) - }) + let x = setup('http://x/x', {}, {}); + assert.equal(x.proxy, null); + }); it('local config should take priority', function() { - var x = setup('http://x/x', {http_proxy: '123'}, {http_proxy: '456'}) - assert.equal(x.proxy, '123') - }) + let x = setup('http://x/x', {http_proxy: '123'}, {http_proxy: '456'}); + assert.equal(x.proxy, '123'); + }); it('no_proxy is invalid', function() { - var 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}, {}) - assert.equal(x.proxy, '123') - var 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: ''}, {}) - assert.equal(x.proxy, '123') - }) + var 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}, {}); + assert.equal(x.proxy, '123'); + var 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: ''}, {}); + assert.equal(x.proxy, '123'); + }); it('no_proxy - simple/include', function() { - var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'localhost'}) - assert.equal(x.proxy, undefined) - }) + let x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'localhost'}); + assert.equal(x.proxy, undefined); + }); it('no_proxy - simple/not', function() { - var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'blah'}) - assert.equal(x.proxy, '123') - }) + let x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'blah'}); + assert.equal(x.proxy, '123'); + }); it('no_proxy - various, single string', function() { - var 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'}) - assert.equal(x.proxy, null) - var 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'}, {}) - assert.equal(x.proxy, null) - var 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'}, {}) - assert.equal(x.proxy, '123') - }) + var 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'}); + assert.equal(x.proxy, null); + var 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'}, {}); + assert.equal(x.proxy, null); + var 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'}, {}); + assert.equal(x.proxy, '123'); + }); it('no_proxy - various, array', function() { - var 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'}) - assert.equal(x.proxy, null) - var 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'}) - assert.equal(x.proxy, '123') - var 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']}) - assert.equal(x.proxy, null) - }) + var 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'}); + assert.equal(x.proxy, null); + var 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'}); + assert.equal(x.proxy, '123'); + var 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']}); + assert.equal(x.proxy, null); + }); it('no_proxy - hostport', function() { - var 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'}) - assert.equal(x.proxy, null) - }) + var 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'}); + assert.equal(x.proxy, null); + }); it('no_proxy - secure', function() { - var x = setup('https://something', {http_proxy: '123'}, {}) - assert.equal(x.proxy, null) - var x = setup('https://something', {https_proxy: '123'}, {}) - assert.equal(x.proxy, '123') - var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {}) - assert.equal(x.proxy, '123') - }) -}) + var x = setup('https://something', {http_proxy: '123'}, {}); + assert.equal(x.proxy, null); + var x = setup('https://something', {https_proxy: '123'}, {}); + assert.equal(x.proxy, '123'); + var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {}); + assert.equal(x.proxy, '123'); + }); +}); diff --git a/test/unit/parse_interval.js b/test/unit/parse_interval.js index f1e9ac643..c0567be4a 100644 --- a/test/unit/parse_interval.js +++ b/test/unit/parse_interval.js @@ -1,34 +1,36 @@ -var assert = require('assert') -var parse_interval = require('../../lib/config').parse_interval +'use strict'; + +let assert = require('assert'); +let parse_interval = require('../../lib/config').parse_interval; describe('Parse interval', function() { function add_test(str, res) { it('parse ' + str, function() { if (res === null) { assert.throws(function() { - console.log(parse_interval(str)) - }) + console.log(parse_interval(str)); + }); } else { - assert.strictEqual(parse_interval(str), res) + assert.strictEqual(parse_interval(str), res); } - }) + }); } - add_test(12345, 12345000) - add_test('1000', 1000000) - add_test('1.5s', 1500) - add_test('25ms', 25) - add_test('2m', 2*1000*60) - add_test('3h', 3*1000*60*60) - add_test('0.5d', 0.5*1000*60*60*24) - add_test('0.5w', 0.5*1000*60*60*24*7) - add_test('1M', 1000*60*60*24*30) - add_test('5s 20ms', 5020) - add_test('1y', 1000*60*60*24*365) - add_test('1y 5', null) - add_test('1m 1m', null) - add_test('1m 1y', null) - add_test('1y 1M 1w 1d 1h 1m 1s 1ms', 34822861001) - add_test(' 5s 25ms ', 5025) -}) + add_test(12345, 12345000); + add_test('1000', 1000000); + add_test('1.5s', 1500); + add_test('25ms', 25); + add_test('2m', 2*1000*60); + add_test('3h', 3*1000*60*60); + add_test('0.5d', 0.5*1000*60*60*24); + add_test('0.5w', 0.5*1000*60*60*24*7); + add_test('1M', 1000*60*60*24*30); + add_test('5s 20ms', 5020); + add_test('1y', 1000*60*60*24*365); + add_test('1y 5', null); + add_test('1m 1m', null); + add_test('1m 1y', null); + add_test('1y 1M 1w 1d 1h 1m 1s 1ms', 34822861001); + add_test(' 5s 25ms ', 5025); +}); diff --git a/test/unit/partials/config.js b/test/unit/partials/config.js index c6394f260..82f9cff69 100644 --- a/test/unit/partials/config.js +++ b/test/unit/partials/config.js @@ -1,4 +1,6 @@ -var config = { +'use strict'; + +let config = { storage: __dirname + '/test-storage', packages: { '*': { @@ -6,8 +8,8 @@ var config = { }, }, logs: [ - {type: 'stdout', format: 'pretty', level: 'fatal'} + {type: 'stdout', format: 'pretty', level: 'fatal'}, ], -} +}; module.exports = config; diff --git a/test/unit/search.js b/test/unit/search.js index 30cfa69e1..3117bb46f 100644 --- a/test/unit/search.js +++ b/test/unit/search.js @@ -1,39 +1,40 @@ -var assert = require('assert'); -var Search = require('../../lib/search'); -var Storage = require('../../lib/storage'); -var config_hash = require('./partials/config'); -var Config = require('../../lib/config'); +'use strict'; + +let assert = require('assert'); +let Search = require('../../lib/search'); +let Storage = require('../../lib/storage'); +let config_hash = require('./partials/config'); +let Config = require('../../lib/config'); require('../../lib/logger').setup([]); -var packages = [ +let packages = [ { name: 'test1', description: 'description', _npmUser: { name: 'test_user', - } + }, }, { name: 'test2', description: 'description', _npmUser: { name: 'test_user', - } + }, }, { name: 'test3', description: 'description', _npmUser: { name: 'test_user', - } + }, }, -] +]; describe('search', function() { - before(function() { - var config = Config(config_hash); + let config = Config(config_hash); this.storage = new Storage(config); Search.configureStorage(this.storage); packages.map(function(item) { @@ -42,17 +43,17 @@ describe('search', function() { }); it('search query item', function() { - var result = Search.query('t'); + let result = Search.query('t'); assert(result.length === 3); - }) + }); it('search remove item', function() { - var item = { + let item = { name: 'test6', description: 'description', _npmUser: { name: 'test_user', - } + }, }; Search.add(item); var result = Search.query('test6'); @@ -60,7 +61,6 @@ describe('search', function() { Search.remove(item.name); var result = Search.query('test6'); assert(result.length === 0); - }) - -}) + }); +}); diff --git a/test/unit/st_merge.js b/test/unit/st_merge.js index 8c98ba453..8c023be1d 100644 --- a/test/unit/st_merge.js +++ b/test/unit/st_merge.js @@ -1,41 +1,43 @@ -var assert = require('assert') -var semver_sort = require('../../lib/utils').semver_sort -var merge = require('../../lib/storage')._merge_versions +'use strict'; -require('../../lib/logger').setup([]) +let assert = require('assert'); +let semver_sort = require('../../lib/utils').semver_sort; +let merge = require('../../lib/storage')._merge_versions; + +require('../../lib/logger').setup([]); describe('Merge', function() { it('simple', function() { - var x = { - versions: {a:1,b:1,c:1}, + let x = { + 'versions': {a: 1, b: 1, c: 1}, 'dist-tags': {}, - } - merge(x, {versions: {a:2,q:2}}) + }; + merge(x, {versions: {a: 2, q: 2}}); assert.deepEqual(x, { - versions: {a:1,b:1,c:1,q:2}, + 'versions': {a: 1, b: 1, c: 1, q: 2}, 'dist-tags': {}, - }) - }) + }); + }); it('dist-tags - compat', function() { - var x = { - versions: {}, - 'dist-tags': {q:'1.1.1',w:'2.2.2'}, - } - merge(x, {'dist-tags':{q:'2.2.2',w:'3.3.3',t:'4.4.4'}}) + let x = { + 'versions': {}, + 'dist-tags': {q: '1.1.1', w: '2.2.2'}, + }; + merge(x, {'dist-tags': {q: '2.2.2', w: '3.3.3', t: '4.4.4'}}); assert.deepEqual(x, { - versions: {}, - 'dist-tags': {q:'2.2.2',w:'3.3.3',t:'4.4.4'}, - }) - }) + 'versions': {}, + 'dist-tags': {q: '2.2.2', w: '3.3.3', t: '4.4.4'}, + }); + }); it('semver_sort', function() { - assert.deepEqual(semver_sort(['1.2.3','1.2','1.2.3a','1.2.3c','1.2.3-b']), - [ '1.2.3a', + assert.deepEqual(semver_sort(['1.2.3', '1.2', '1.2.3a', '1.2.3c', '1.2.3-b']), + ['1.2.3a', '1.2.3-b', '1.2.3c', - '1.2.3' ] - ) - }) -}) + '1.2.3'] + ); + }); +}); diff --git a/test/unit/tag_version.js b/test/unit/tag_version.js index 036881d80..4616ffe93 100644 --- a/test/unit/tag_version.js +++ b/test/unit/tag_version.js @@ -1,44 +1,45 @@ -var assert = require('assert') -var tag_version = require('../../lib/utils').tag_version +'use strict'; -require('../../lib/logger').setup([]) +let assert = require('assert'); +let tag_version = require('../../lib/utils').tag_version; + +require('../../lib/logger').setup([]); describe('tag_version', function() { it('add new one', function() { - var x = { - versions: {}, + let x = { + 'versions': {}, 'dist-tags': {}, - } - assert(tag_version(x, '1.1.1', 'foo', {})) + }; + assert(tag_version(x, '1.1.1', 'foo', {})); assert.deepEqual(x, { - versions: {}, + 'versions': {}, 'dist-tags': {foo: '1.1.1'}, - }) - }) + }); + }); it('add (compat)', function() { - var x = { - versions: {}, + let x = { + 'versions': {}, 'dist-tags': {foo: '1.1.0'}, - } - assert(tag_version(x, '1.1.1', 'foo')) + }; + assert(tag_version(x, '1.1.1', 'foo')); assert.deepEqual(x, { - versions: {}, + 'versions': {}, 'dist-tags': {foo: '1.1.1'}, - }) - }) + }); + }); it('add fresh tag', function() { - var x = { - versions: {}, + let x = { + 'versions': {}, 'dist-tags': {foo: '1.1.0'}, - } - assert(tag_version(x, '1.1.1', 'foo')) + }; + assert(tag_version(x, '1.1.1', 'foo')); assert.deepEqual(x, { - versions: {}, + 'versions': {}, 'dist-tags': {foo: '1.1.1'}, - }) - }) - -}) + }); + }); +}); diff --git a/test/unit/toplevel.js b/test/unit/toplevel.js index 1959b3cb8..59199e8a1 100644 --- a/test/unit/toplevel.js +++ b/test/unit/toplevel.js @@ -1,45 +1,47 @@ -var assert = require('assert') -var express = require('express') -var request = require('request') -var rimraf = require('rimraf') -var verdaccio = require('../../') -var config = require('./partials/config'); +'use strict'; + +let assert = require('assert'); +let express = require('express'); +let request = require('request'); +let rimraf = require('rimraf'); +let verdaccio = require('../../'); +let config = require('./partials/config'); describe('toplevel', function() { - var port + let port; before(function(done) { - rimraf(__dirname + '/test-storage', done) - }) + rimraf(__dirname + '/test-storage', done); + }); before(function(done) { - var app = express() - app.use(verdaccio(config)) + let app = express(); + app.use(verdaccio(config)); - var server = require('http').createServer(app) + let server = require('http').createServer(app); server.listen(0, function() { - port = server.address().port - done() - }) - }) + port = server.address().port; + done(); + }); + }); it('should respond on /', function(done) { request({ url: 'http://localhost:' + port + '/', }, function(err, res, body) { - assert.equal(err, null) - assert(body.match(/Verdaccio<\/title>/)) - done() - }) - }) + assert.equal(err, null); + assert(body.match(/<title>Verdaccio<\/title>/)); + done(); + }); + }); it('should respond on /whatever', function(done) { request({ url: 'http://localhost:' + port + '/whatever', }, function(err, res, body) { - assert.equal(err, null) - assert(body.match(/no such package available/)) - done() - }) - }) -}) + assert.equal(err, null); + assert(body.match(/no such package available/)); + done(); + }); + }); +}); diff --git a/test/unit/utils.js b/test/unit/utils.js index 3b3009c53..9794e50f1 100644 --- a/test/unit/utils.js +++ b/test/unit/utils.js @@ -1,42 +1,44 @@ -var assert = require('assert') -var validate = require('../../lib/utils').validate_name +'use strict'; + +let assert = require('assert'); +let validate = require('../../lib/utils').validate_name; describe('Validate', function() { it('good ones', function() { - assert( validate('verdaccio') ) - assert( validate('some.weird.package-zzz') ) - assert( validate('old-package@0.1.2.tgz') ) - }) + assert( validate('verdaccio') ); + assert( validate('some.weird.package-zzz') ); + assert( validate('old-package@0.1.2.tgz') ); + }); it('uppercase', function() { - assert( validate('EVE') ) - assert( validate('JSONStream') ) - }) + assert( validate('EVE') ); + assert( validate('JSONStream') ); + }); it('no package.json', function() { - assert( !validate('package.json') ) - }) + assert( !validate('package.json') ); + }); it('no path seps', function() { - assert( !validate('some/thing') ) - assert( !validate('some\\thing') ) - }) + assert( !validate('some/thing') ); + assert( !validate('some\\thing') ); + }); it('no hidden', function() { - assert( !validate('.bin') ) - }) + assert( !validate('.bin') ); + }); it('no reserved', function() { - assert( !validate('favicon.ico') ) - assert( !validate('node_modules') ) - assert( !validate('__proto__') ) - }) + assert( !validate('favicon.ico') ); + assert( !validate('node_modules') ); + assert( !validate('__proto__') ); + }); it('other', function() { - assert( !validate('pk g') ) - assert( !validate('pk\tg') ) - assert( !validate('pk%20g') ) - assert( !validate('pk+g') ) - assert( !validate('pk:g') ) - }) -}) + assert( !validate('pk g') ); + assert( !validate('pk\tg') ); + assert( !validate('pk%20g') ); + assert( !validate('pk+g') ); + assert( !validate('pk:g') ); + }); +}); diff --git a/test/unit/validate_all.js b/test/unit/validate_all.js index 662d41b6b..0e56eef19 100644 --- a/test/unit/validate_all.js +++ b/test/unit/validate_all.js @@ -1,39 +1,41 @@ +'use strict'; + // ensure that all arguments are validated -var assert = require('assert') +let assert = require('assert'); -describe('index.js app', test('index.js')) -describe('index-api.js app', test('index-api.js')) -describe('index-web.js app', test('index-web.js')) +describe('index.js app', test('index.js')); +describe('index-api.js app', test('index-api.js')); +describe('index-web.js app', test('index-web.js')); function test(file) { return function() { - var source = require('fs').readFileSync(__dirname + '/../../lib/' + file, 'utf8') + let source = require('fs').readFileSync(__dirname + '/../../lib/' + file, 'utf8'); - var very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g - var m - var params = {} + let very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g; + let m; + let params = {}; while ((m = very_scary_regexp.exec(source)) != null) { - if (m[1] === 'set') continue + if (m[1] === 'set') continue; - var inner = m[2].slice(1, m[2].length-1) - var t + let inner = m[2].slice(1, m[2].length-1); + var t; inner.split('/').forEach(function(x) { if (m[1] === 'param') { - params[x] = 'ok' + params[x] = 'ok'; } else if (t = x.match(/^:([^?:]*)\??$/)) { - params[t[1]] = params[t[1]] || m[0].trim() + params[t[1]] = params[t[1]] || m[0].trim(); } - }) + }); } Object.keys(params).forEach(function(param) { it('should validate ":'+param+'"', function() { - assert.equal(params[param], 'ok') - }) - }) - } + assert.equal(params[param], 'ok'); + }); + }); + }; } From a57f99cbe0262b1ef5fe4861472abc47fcc3f060 Mon Sep 17 00:00:00 2001 From: Juan Picado <juanpicado19@gmail.com> Date: Wed, 19 Apr 2017 21:15:52 +0200 Subject: [PATCH 13/14] Update gitignore, IDE hidden folder: --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 541dea446..3d893c0d3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ coverage/ .jscsrc .jshintrc jsconfig.json + +.idea/ From dd3a4b4821cbba9f47a82e172108b90df6bf76ed Mon Sep 17 00:00:00 2001 From: Juan Picado <juanpicado19@gmail.com> Date: Sun, 23 Apr 2017 20:02:26 +0200 Subject: [PATCH 14/14] 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 | 83 ++-- lib/auth.js | 374 ++++++++--------- lib/cli.js | 132 +++--- lib/config-path.js | 64 +-- lib/config.js | 196 ++++----- lib/file-locking.js | 118 +++--- lib/index-api.js | 452 +++++++++++---------- lib/index-web.js | 188 ++++----- lib/index.js | 118 +++--- lib/local-data.js | 26 +- lib/local-fs.js | 218 +++++----- lib/local-storage.js | 584 +++++++++++++-------------- lib/logger.js | 281 +++++++------ lib/middleware.js | 184 ++++----- lib/notify.js | 35 +- lib/plugin-loader.js | 38 +- 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 | 388 +++++++++--------- 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, 2441 insertions(+), 2385 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 a089bad2e..98d005a37 100644 --- a/lib/GUI/js/search.js +++ b/lib/GUI/js/search.js @@ -1,76 +1,75 @@ -var $ = require('unopinionate').selector -var template = require('../entry.hbs') +let $ = require('unopinionate').selector; +let template = require('../entry.hbs'); $(function() { - ;(function(window, document) { - var $form = $('#search-form') - var $input = $form.find('input') - var $searchResults = $('#search-results') - var $pkgListing = $('#all-packages') - var $searchBtn = $('.js-search-btn') - var request - var currentResults + (function(window, document) { + let $form = $('#search-form'); + let $input = $form.find('input'); + let $searchResults = $('#search-results'); + let $pkgListing = $('#all-packages'); + let $searchBtn = $('.js-search-btn'); + let request; + let currentResults; - var toggle = function(validQuery) { - $searchResults.toggleClass('show', validQuery) - $pkgListing.toggleClass('hide', validQuery) + let toggle = function(validQuery) { + $searchResults.toggleClass('show', validQuery); + $pkgListing.toggleClass('hide', validQuery); - $searchBtn.find('i').toggleClass('icon-cancel', validQuery) - $searchBtn.find('i').toggleClass('icon-search', !validQuery) - } + $searchBtn.find('i').toggleClass('icon-cancel', validQuery); + $searchBtn.find('i').toggleClass('icon-search', !validQuery); + }; $form.bind('submit keyup', function(e) { - var q, qBool + let q, qBool; - e.preventDefault() + e.preventDefault(); - q = $input.val() - qBool = (q !== '') + q = $input.val(); + qBool = (q !== ''); - toggle(qBool) + toggle(qBool); if (!qBool) { if (request && typeof request.abort === 'function') { - request.abort() + request.abort(); } - currentResults = null - $searchResults.html('') - return + currentResults = null; + $searchResults.html(''); + return; } if (request && typeof request.abort === 'function') { - request.abort() + request.abort(); } if (!currentResults) { $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/' + q, function( results ) { - currentResults = results + currentResults = 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>'); } - }) - }) + }); + }); $(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 8e3cb7c8a..2f037ab9d 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 { @@ -131,11 +131,11 @@ function afterConfigLoad() { secureProtocol: 'SSLv23_method', // disable insecure SSLv2 and SSLv3 secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3, key: fs.readFileSync(config.https.key), - cert: fs.readFileSync(config.https.cert) - }, app) + cert: fs.readFileSync(config.https.cert), + }, 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); @@ -144,9 +144,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 @@ -162,17 +162,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 f53d152e1..c8ab718bf 100644 --- a/lib/index-api.js +++ b/lib/index-api.js @@ -1,186 +1,188 @@ -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 app.get('/-/all/:anything?', function(req, res, next) { - var received_end = false - var response_finished = false - var processing_pkgs = 0 + let received_end = false; + let response_finished = false; + let processing_pkgs = 0; - res.status(200) + res.status(200); res.write('{"_updated":' + Date.now()); - var stream = storage.search(req.query.startkey || 0, { req: req }) + 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) { - res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg)) + res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg)); } - 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; - response_finished = true - res.end('}\n') + response_finished = true; + res.end('}\n'); } - }) + }); // 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) { @@ -189,133 +191,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 @@ -323,121 +322,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 480763185..3ee9640df 100644 --- a/lib/index-web.js +++ b/lib/index-web.js @@ -1,163 +1,163 @@ -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 = [] + let results = Search.query(req.params.anything); + let packages = []; var getData = function(i) { storage.get_package(results[i].ref, function(err, entry) { if (!err && entry) { - 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 efa4626c1..02c1ea350 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) - _cb(err) + if (err) fs.unlink(src); + _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) - }) + 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 a3e7a7f3a..63089b966 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,20 +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); + }); } /** @@ -544,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); } @@ -605,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); + }); + }); } /** @@ -653,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) => { @@ -667,42 +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]._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, }, - versions : {}, - }) + 'versions': {}, + }); } - cb() - }) + 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; } /** @@ -710,14 +709,14 @@ class Storage { * @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); } /** @@ -727,17 +726,16 @@ class Storage { * @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); } /** @@ -745,47 +743,47 @@ class Storage { * @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 56fcb1f84..59b50e1a6 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, is_allowed) { req.resume(); if (error) { - next(error) + next(error); } else if (is_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 c12ea036e..66d5f6bb5 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 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 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 Error('"' + p + '" doesn\'t look like a valid plugin') + throw Error('"' + p + '" doesn\'t look like a valid plugin'); - return 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 65b2b384d..d5f977054 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,274 +108,274 @@ 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 - var headers = options.headers || {} - headers['Accept'] = headers['Accept'] || 'application/json' - headers['Accept-Encoding'] = headers['Accept-Encoding'] || 'gzip' - headers['User-Agent'] = headers['User-Agent'] || this.userAgent - this._add_proxy_headers(options.req, headers) + let self = this; + let headers = options.headers || {}; + headers['Accept'] = headers['Accept'] || 'application/json'; + headers['Accept-Encoding'] = headers['Accept-Encoding'] || 'gzip'; + headers['User-Agent'] = headers['User-Agent'] || this.userAgent; + this._add_proxy_headers(options.req, headers); // 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({ + let req = self.request({ uri: options.req.url, req: options.req, - }) + }); - req.on('response', function (res) { + req.on('response', function(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', function(pkg) { if (Utils.is_object(pkg)) { - stream.emit('data', pkg) + stream.emit('data', pkg); } - }) + }); - res.on('end', function () { - stream.emit('end') - }) - }) + res.on('end', function() { + stream.emit('end'); + }); + }); - req.on('error', function (err) { - stream.emit('error', err) - }) + req.on('error', function(err) { + stream.emit('error', err); + }); - stream.abort = function () { - req.abort() - stream.emit('end') - } + stream.abort = function() { + req.abort(); + stream.emit('end'); + }; - return stream -} + return stream; +}; Storage.prototype._add_proxy_headers = function(req, headers) { if (req) { @@ -390,7 +390,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; } } @@ -398,10 +398,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 98eb7bd0f..a251134ee 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 3117bb46f..8353f1e44 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(); } });