1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-11-08 23:25:51 +01:00

Merge pull request #18 from steve-p-com/master

Remove platform specific deps. Introduce bundled-in plugins.
This commit is contained in:
Trent Earl 2016-05-01 09:23:05 -05:00
commit 06980e0cc1
9 changed files with 496 additions and 256 deletions

152
lib/file-locking.js Normal file

@ -0,0 +1,152 @@
/**
* file-locking.js - file system locking (replaces fs-ext)
*/
var async = require('async'),
locker = require('lockfile'),
fs = require('fs'),
path = require('path')
// locks a file by creating a lock file
function lockFile(name, next) {
var 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
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
}
async.series({
statdir: function (callback) {
// test to see if the directory exists
fs.stat(path.dirname(name), function (err, stats) {
if (err) {
callback(err)
} else if (!stats.isDirectory()) {
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)
}
});
},
lockfile: function (callback) {
// try to lock the file
locker.lock(lockFileName, lockOpts, callback)
}
}, function (err) {
if (err) {
// lock failed
return next(err)
}
// lock succeeded
return next(null);
})
}
// unlocks file by removing existing lock file
function unlockFile(name, next) {
var lockFileName = name + '.lock'
locker.unlock(lockFileName, function (err) {
if (err) {
return next(err)
}
return next(null)
})
}
/**
* reads a local file, which involves
* optionally taking a lock
* reading the file contents
* optionally parsing JSON contents
*/
function readFile(name, options, next) {
if (typeof options === 'function' && next === null) {
next = options;
options = {}
}
options = options || {}
options.lock = options.lock || false
options.parse = options.parse || false
function lock(callback) {
if (!options.lock) {
return callback(null)
}
lockFile(name, function (err) {
if (err) {
return callback(err)
}
return callback(null)
})
}
function read(callback) {
fs.readFile(name, 'utf8', function (err, contents) {
if (err) {
return callback(err)
}
callback(null, contents)
})
}
function parseJSON(contents, callback) {
if (!options.parse) {
return callback(null, contents)
}
try {
contents = JSON.parse(contents)
return callback(null, contents)
} catch (err) {
return callback(err)
}
}
async.waterfall([
lock,
read,
parseJSON
],
function (err, result) {
if (err) {
return next(err)
} else {
return next(null, result)
}
})
}
exports.lockFile = lockFile;
exports.unlockFile = unlockFile;
exports.readFile = readFile;

@ -10,15 +10,7 @@ function FSError(code) {
return err
}
try {
var fsExt = require('fs-ext')
} catch (e) {
fsExt = {
flock: function() {
arguments[arguments.length-1]()
}
}
}
var locker = require('./file-locking')
function tempFile(str) {
return str + '.tmp' + String(Math.random()).substr(2)
@ -134,7 +126,7 @@ function read_stream(name, stream, callback) {
})
})
var stream = MyStreams.ReadTarballStream()
stream = MyStreams.ReadTarballStream()
stream.abort = function() {
rstream.close()
}
@ -159,64 +151,6 @@ function read(name, callback) {
fs.readFile(name, callback)
}
// open and flock with exponential backoff
function open_flock(name, opmod, flmod, tries, backoff, cb) {
fs.open(name, opmod, function(err, fd) {
if (err) return cb(err, fd)
fsExt.flock(fd, flmod, function(err) {
if (err) {
if (!tries) {
fs.close(fd, function() {
cb(err)
})
} else {
fs.close(fd, function() {
setTimeout(function() {
open_flock(name, opmod, flmod, tries-1, backoff*2, cb)
}, backoff)
})
}
} else {
cb(null, fd)
}
})
})
}
// this function neither unlocks file nor closes it
// it'll have to be done manually later
function lock_and_read(name, _callback) {
open_flock(name, 'r', 'exnb', 4, 10, function(err, fd) {
function callback(err) {
if (err && fd) {
fs.close(fd, function(err2) {
_callback(err)
})
} else {
_callback.apply(null, arguments)
}
}
if (err) return callback(err, fd)
fs.fstat(fd, function(err, st) {
if (err) return callback(err, fd)
var buffer = Buffer(st.size)
if (st.size === 0) return onRead(null, 0, buffer)
fs.read(fd, buffer, 0, st.size, null, onRead)
function onRead(err, bytesRead, buffer) {
if (err) return callback(err, fd)
if (bytesRead != st.size) return callback(Error('st.size != bytesRead'), fd)
callback(null, fd, buffer)
}
})
})
}
module.exports.read = read
module.exports.read_json = function(name, cb) {
@ -233,22 +167,24 @@ module.exports.read_json = function(name, cb) {
})
}
module.exports.lock_and_read = lock_and_read
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)
})
}
module.exports.lock_and_read_json = function(name, cb) {
lock_and_read(name, function(err, fd, res) {
if (err) return cb(err, fd)
var args = []
try {
args = [ null, fd, JSON.parse(res.toString('utf8')) ]
} catch(err) {
args = [ err, fd ]
}
cb.apply(null, args)
locker.readFile(name, {lock: true, parse: true}, function(err, res) {
if (err) return cb(err)
return cb(null, res);
})
}
module.exports.unlock_file = function (name, cb) {
locker.unlockFile(name, cb)
}
module.exports.create = create
module.exports.create_json = function(name, value, cb) {

@ -543,19 +543,26 @@ 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, fd, json) {
function callback() {
storage.lock_and_read_json(info_file, function(err, json) {
var locked = false
// callback that cleans up lock first
function callback(err) {
var _args = arguments
if (fd) {
fs.close(fd, function(err) {
if (err) return _callback(err)
_callback.apply(null, _args)
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') )

@ -15,23 +15,25 @@ function load_plugins(config, plugin_configs, params, sanity_check) {
var plugins = Object.keys(plugin_configs || {}).map(function(p) {
var plugin
// try local plugins first
plugin = try_load(Path.resolve('./lib/plugins', p))
// npm package
if (plugin == null && p.match(/^[^\.\/]/)) {
if (plugin === null && p.match(/^[^\.\/]/)) {
plugin = try_load('sinopia-' + p)
}
if (plugin == null) {
if (plugin === null) {
plugin = try_load(p)
}
// relative to config path
if (plugin == null && p.match(/^\.\.?($|\/)/)) {
if (plugin === null && p.match(/^\.\.?($|\/)/)) {
plugin = try_load(Path.resolve(Path.dirname(config.self_path), p))
}
if (plugin == null) {
throw Error('"' + p + '" plugin not found\n'
+ 'try "npm install sinopia-' + p + '"')
if (plugin === null) {
throw Error('"' + p + '" plugin not found\ntry "npm install sinopia-' + p + '"')
}
if (typeof(plugin) !== 'function')
@ -39,7 +41,7 @@ function load_plugins(config, plugin_configs, params, sanity_check) {
plugin = plugin(plugin_configs[p], params)
if (plugin == null || !sanity_check(plugin))
if (plugin === null || !sanity_check(plugin))
throw Error('"' + p + '" doesn\'t look like a valid plugin')
return plugin

@ -0,0 +1,56 @@
/** Node.js Crypt(3) Library
Inspired by (and intended to be compatible with) sendanor/crypt3
see https://github.com/sendanor/node-crypt3
The key difference is the removal of the dependency on the unix crypt(3) function
which is not platform independent, and requires compilation. Instead, a pure
javascript version is used.
*/
var crypt = require('unix-crypt-td-js'),
crypto = require('crypto');
function createSalt(type) {
type = type || 'sha512';
switch (type) {
case 'md5':
return '$1$' + crypto.randomBytes(10).toString('base64');
case 'blowfish':
return '$2a$' + crypto.randomBytes(10).toString('base64');
case 'sha256':
return '$5$' + crypto.randomBytes(10).toString('base64');
case 'sha512':
return '$6$' + crypto.randomBytes(10).toString('base64');
default:
throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type);
}
}
function crypt3(key, salt) {
salt = salt || createSalt();
return crypt(key, salt);
}
/** Crypt(3) password and data encryption.
* @param {string} key user's typed password
* @param {string} salt Optional salt, for example SHA-512 use "$6$salt$".
* @returns {string} A generated hash in format $id$salt$encrypted
* @see https://en.wikipedia.org/wiki/Crypt_(C)
*/
module.exports = crypt3;
/** Create salt
* @param {string} type The type of salt: md5, blowfish (only some linux distros), sha256 or sha512. Default is sha512.
* @returns {string} Generated salt string
*/
module.exports.createSalt = createSalt;

@ -0,0 +1,138 @@
var fs = require('fs')
var Path = require('path')
var utils = require('./utils')
module.exports = HTPasswd
function HTPasswd(config, stuff) {
var self = Object.create(HTPasswd.prototype)
self._users = {}
// config for this module
self._config = config
// sinopia logger
self._logger = stuff.logger
// sinopia main config object
self._sinopia_config = stuff.config
// all this "sinopia_config" stuff is for b/w compatibility only
self._maxusers = self._config.max_users
if (!self._maxusers) self._maxusers = self._sinopia_config.max_users
// set maxusers to Infinity if not specified
if (!self._maxusers) self._maxusers = Infinity
self._last_time = null
var file = self._config.file
if (!file) file = self._sinopia_config.users_file
if (!file) throw new Error('should specify "file" in config')
self._path = Path.resolve(Path.dirname(self._sinopia_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)
// 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])
})
}
// hopefully race-condition-free way to add users:
// 1. lock file for writing (other processes can still read)
// 2. reload .htpasswd
// 3. write new data into .htpasswd.tmp
// 4. move .htpasswd.tmp to .htpasswd
// 5. reload .htpasswd
// 6. unlock file
HTPasswd.prototype.adduser = function (user, password, real_cb) {
var self = this
function sanity_check() {
var err = null
if (self._users[user]) {
err = Error('this user already exists')
} else if (Object.keys(self._users).length >= self._maxusers) {
err = Error('maximum amount of users reached')
}
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)
utils.lock_and_read(self._path, function (err, res) {
var locked = false
// callback that cleans up lock first
function cb(err) {
if (locked) {
utils.unlock_file(self._path, function () {
// ignore any error from the unlock
real_cb(err, !err)
})
} else {
real_cb(err, !err)
}
}
if (!err) {
locked = true
}
// ignore ENOENT errors, we'll just create .htpasswd in that case
if (err && err.code !== 'ENOENT') return cb(err)
var 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)
try {
console.log('body = utils.add_user_to_htpasswd(body, user, password)')
console.log(user, password)
body = utils.add_user_to_htpasswd(body, user, password)
} catch (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()
});
});
}

@ -0,0 +1,65 @@
var crypto = require('crypto')
var crypt3 = require('./crypt3')
var 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) {
if (err) {
return cb(err)
}
return cb(null, res)
})
}
// close and unlock file
function unlock_file(name, cb) {
locker.unlockFile(name, cb)
}
function parse_htpasswd(input) {
var result = {}
input.split('\n').forEach(function(line) {
var 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)
} else if (hash.indexOf('{SHA}') === 0) {
return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5)
} else if (crypt3) {
return crypt3(passwd, hash) === hash
} else {
return false
}
}
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
}
if (crypt3) {
passwd = crypt3(passwd)
} else {
passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64')
}
var 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
}
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

199
npm-shrinkwrap.json generated

@ -65,7 +65,7 @@
},
"iconv-lite": {
"version": "0.4.13",
"from": "iconv-lite@>=0.4.13 <0.5.0",
"from": "iconv-lite@0.4.13",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
},
"on-finished": {
@ -139,9 +139,9 @@
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz",
"dependencies": {
"nan": {
"version": "2.2.1",
"version": "2.3.2",
"from": "nan@>=2.0.8 <3.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz"
"resolved": "https://registry.npmjs.org/nan/-/nan-2.3.2.tgz"
}
}
},
@ -316,18 +316,6 @@
}
}
},
"crypt3": {
"version": "0.2.0",
"from": "crypt3@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/crypt3/-/crypt3-0.2.0.tgz",
"dependencies": {
"nan": {
"version": "2.2.1",
"from": "nan@>=2.1.0 <3.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz"
}
}
},
"es6-shim": {
"version": "0.35.0",
"from": "es6-shim@>=0.35.0 <0.36.0",
@ -546,7 +534,7 @@
},
"mime-types": {
"version": "2.1.10",
"from": "mime-types@>=2.1.10 <2.2.0",
"from": "mime-types@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
"dependencies": {
"mime-db": {
@ -570,18 +558,6 @@
}
}
},
"fs-ext": {
"version": "0.5.0",
"from": "fs-ext@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/fs-ext/-/fs-ext-0.5.0.tgz",
"dependencies": {
"nan": {
"version": "2.2.1",
"from": "nan@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz"
}
}
},
"handlebars": {
"version": "4.0.5",
"from": "handlebars@>=4.0.5 <5.0.0",
@ -632,9 +608,9 @@
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
},
"source-map": {
"version": "0.5.3",
"version": "0.5.5",
"from": "source-map@>=0.5.1 <0.6.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.5.tgz"
},
"uglify-to-browserify": {
"version": "1.0.2",
@ -691,9 +667,9 @@
}
},
"lazy-cache": {
"version": "1.0.3",
"version": "1.0.4",
"from": "lazy-cache@>=1.0.3 <2.0.0",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.3.tgz"
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz"
}
}
},
@ -807,6 +783,11 @@
}
}
},
"lockfile": {
"version": "1.0.1",
"from": "lockfile@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.1.tgz"
},
"lunr": {
"version": "0.7.0",
"from": "lunr@>=0.7.0 <0.8.0",
@ -854,9 +835,9 @@
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.0.tgz"
},
"readable-stream": {
"version": "2.1.0",
"from": "readable-stream@>=2.1.0 <3.0.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.0.tgz",
"version": "2.1.2",
"from": "readable-stream@>=2.1.2 <3.0.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.2.tgz",
"dependencies": {
"core-util-is": {
"version": "1.0.2",
@ -868,64 +849,6 @@
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"inline-process-browser": {
"version": "2.0.1",
"from": "inline-process-browser@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-2.0.1.tgz",
"dependencies": {
"falafel": {
"version": "1.2.0",
"from": "falafel@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
"dependencies": {
"acorn": {
"version": "1.2.2",
"from": "acorn@>=1.0.3 <2.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz"
},
"foreach": {
"version": "2.0.5",
"from": "foreach@>=2.0.5 <3.0.0",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz"
},
"isarray": {
"version": "0.0.1",
"from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
},
"object-keys": {
"version": "1.0.9",
"from": "object-keys@>=1.0.6 <2.0.0",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.9.tgz"
}
}
},
"through2": {
"version": "0.6.5",
"from": "through2@>=0.6.5 <0.7.0",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"dependencies": {
"readable-stream": {
"version": "1.0.34",
"from": "readable-stream@>=1.0.33-1 <1.1.0-0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"dependencies": {
"isarray": {
"version": "0.0.1",
"from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
}
}
},
"xtend": {
"version": "4.0.1",
"from": "xtend@>=4.0.0 <4.1.0-0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
}
}
}
}
},
"isarray": {
"version": "1.0.0",
"from": "isarray@>=1.0.0 <1.1.0",
@ -941,45 +864,6 @@
"from": "string_decoder@>=0.10.0 <0.11.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
},
"unreachable-branch-transform": {
"version": "0.5.1",
"from": "unreachable-branch-transform@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.5.1.tgz",
"dependencies": {
"esmangle-evaluator": {
"version": "1.0.0",
"from": "esmangle-evaluator@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/esmangle-evaluator/-/esmangle-evaluator-1.0.0.tgz"
},
"recast": {
"version": "0.11.5",
"from": "recast@>=0.11.4 <0.12.0",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.11.5.tgz",
"dependencies": {
"ast-types": {
"version": "0.8.16",
"from": "ast-types@0.8.16",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.16.tgz"
},
"esprima": {
"version": "2.7.2",
"from": "esprima@>=2.6.0 <3.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz"
},
"private": {
"version": "0.1.6",
"from": "private@>=0.1.5 <0.2.0",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz"
},
"source-map": {
"version": "0.5.3",
"from": "source-map@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
}
}
}
}
},
"util-deprecate": {
"version": "1.0.2",
"from": "util-deprecate@>=1.0.1 <1.1.0",
@ -1015,9 +899,9 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz"
},
"linkify-it": {
"version": "1.2.0",
"version": "1.2.1",
"from": "linkify-it@>=1.2.0 <1.3.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.0.tgz"
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.1.tgz"
},
"mdurl": {
"version": "1.0.1",
@ -1372,26 +1256,29 @@
}
},
"sshpk": {
"version": "1.7.4",
"version": "1.8.3",
"from": "sshpk@>=1.7.0 <2.0.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.8.3.tgz",
"dependencies": {
"asn1": {
"version": "0.2.3",
"from": "asn1@>=0.2.3 <0.3.0",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz"
},
"assert-plus": {
"version": "1.0.0",
"from": "assert-plus@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
},
"dashdash": {
"version": "1.13.0",
"from": "dashdash@>=1.10.1 <2.0.0",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz",
"dependencies": {
"assert-plus": {
"version": "1.0.0",
"from": "assert-plus@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
}
}
"version": "1.13.1",
"from": "dashdash@>=1.12.0 <2.0.0",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.1.tgz"
},
"getpass": {
"version": "0.1.6",
"from": "getpass@>=0.1.1 <0.2.0",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz"
},
"jsbn": {
"version": "0.1.0",
@ -1399,9 +1286,9 @@
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz"
},
"tweetnacl": {
"version": "0.14.3",
"from": "tweetnacl@>=0.13.0 <1.0.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz"
"version": "0.13.3",
"from": "tweetnacl@>=0.13.0 <0.14.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz"
},
"jodid25519": {
"version": "1.0.2",
@ -1410,7 +1297,7 @@
},
"ecc-jsbn": {
"version": "0.1.1",
"from": "ecc-jsbn@>=0.0.1 <1.0.0",
"from": "ecc-jsbn@>=0.1.1 <0.2.0",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz"
}
}
@ -1434,7 +1321,7 @@
},
"mime-types": {
"version": "2.1.10",
"from": "mime-types@>=2.1.10 <2.2.0",
"from": "mime-types@>=2.1.7 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
"dependencies": {
"mime-db": {
@ -1481,15 +1368,15 @@
"from": "semver@>=5.1.0 <6.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz"
},
"sinopia-htpasswd": {
"version": "0.4.5",
"from": "sinopia-htpasswd@>=0.4.5 <0.5.0",
"resolved": "https://registry.npmjs.org/sinopia-htpasswd/-/sinopia-htpasswd-0.4.5.tgz"
},
"symbol": {
"version": "0.2.1",
"from": "symbol@>=0.2.1 <0.3.0",
"resolved": "https://registry.npmjs.org/symbol/-/symbol-0.2.1.tgz"
},
"unix-crypt-td-js": {
"version": "1.0.0",
"from": "unix-crypt-td-js@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.0.0.tgz"
}
}
}

@ -29,26 +29,23 @@
"http-errors": "^1.4.0",
"jju": "^1.3.0",
"js-yaml": "^3.6.0",
"lockfile": "^1.0.1",
"lunr": "^0.7.0",
"minimatch": "^3.0.0",
"mkdirp": "^0.5.1",
"pkginfo": "^0.4.0",
"readable-stream": "^2.1.0",
"readable-stream": "^2.1.2",
"render-readme": "^1.3.1",
"request": "^2.72.0",
"semver": "^5.1.0",
"sinopia-htpasswd": "^0.4.5",
"symbol": "^0.2.1"
},
"optionalDependencies": {
"fs-ext": "^0.5.0",
"crypt3": "^0.2.0"
"symbol": "^0.2.1",
"unix-crypt-td-js": "^1.0.0"
},
"devDependencies": {
"rimraf": "^2.5.2",
"bluebird": "^3.3.5",
"mocha": "^2.4.5",
"eslint": "^2.8.0",
"eslint": "^2.9.0",
"browserify": "^13.0.0",
"browserify-handlebars": "^1.0.0",
"grunt": "^1.0.1",