mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
working on drivers...
This commit is contained in:
parent
0bb1b34b7d
commit
d89494b533
8 changed files with 333 additions and 84 deletions
55
lib/drivers/fs.js
Normal file
55
lib/drivers/fs.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
var fs = require('fs');
|
||||
var Path = require('path');
|
||||
|
||||
function make_directories(dest, cb) {
|
||||
var dir = Path.dirname(dest);
|
||||
if (dir === '.' || dir === '..') return cb();
|
||||
fs.mkdir(dir, function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
make_directories(dir, function() {
|
||||
fs.mkdir(dir, cb);
|
||||
})
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function write_file(dest, data, cb) {
|
||||
var safe_write = function(cb) {
|
||||
fs.writeFile(dest, data, cb);
|
||||
}
|
||||
|
||||
safe_write(function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
make_directories(dest, function() {
|
||||
safe_write(cb);
|
||||
})
|
||||
} else {
|
||||
cb(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function create(name, contents, callback) {
|
||||
fs.exists(name, function(exists) {
|
||||
if (exists) return callback(new Error({code: 'EEXISTS'}));
|
||||
write_file(name, contents, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function update(name, contents, callback) {
|
||||
fs.exists(name, function(exists) {
|
||||
if (!exists) return callback(new Error({code: 'ENOENT'}));
|
||||
write_file(name, contents, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function read(name, callback) {
|
||||
fs.readFile(name, callback);
|
||||
}
|
||||
|
||||
module.exports.read = read;
|
||||
module.exports.create = create;
|
||||
module.exports.update = update;
|
||||
|
32
lib/drivers/memory.js
Normal file
32
lib/drivers/memory.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
var store = {};
|
||||
|
||||
function create(name, contents, callback) {
|
||||
if (store[name] != null) {
|
||||
return callback(new Error({code: 'EEXISTS'}));
|
||||
}
|
||||
store[name] = contents;
|
||||
callback();
|
||||
}
|
||||
|
||||
function update(name, contents, callback) {
|
||||
if (store[name] == null) {
|
||||
return callback(new Error({code: 'ENOENT'}));
|
||||
}
|
||||
store[name] = contents;
|
||||
callback();
|
||||
}
|
||||
|
||||
function read(name, callback) {
|
||||
if (store[name] == null) {
|
||||
return callback(new Error({code: 'ENOENT'}));
|
||||
}
|
||||
callback(null, store[name]);
|
||||
}
|
||||
|
||||
module.exports.read_json = read;
|
||||
module.exports.read = read;
|
||||
module.exports.create_json = create;
|
||||
module.exports.create = create;
|
||||
module.exports.update_json = update;
|
||||
module.exports.update = update;
|
||||
|
51
lib/error.js
Normal file
51
lib/error.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
var util = require('util');
|
||||
|
||||
function parse_error_params(params, status, msg) {
|
||||
if (typeof(params) === 'string') {
|
||||
return {
|
||||
msg: params,
|
||||
status: status,
|
||||
};
|
||||
} else if (typeof(params) === 'number') {
|
||||
return {
|
||||
msg: msg,
|
||||
status: params,
|
||||
};
|
||||
} else if (typeof(params) === 'object' && params != null) {
|
||||
if (params.msg == null) params.msg = msg;
|
||||
if (params.status == null) params.status = status;
|
||||
return params;
|
||||
} else {
|
||||
return {
|
||||
msg: msg,
|
||||
status: status,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Errors caused by malfunctioned code
|
||||
*/
|
||||
var AppError = function(params, constr) {
|
||||
Error.captureStackTrace(this, constr || this);
|
||||
params = parse_error_params(params, 500, 'Internal server error');
|
||||
this.msg = params.msg;
|
||||
this.status = params.status;
|
||||
};
|
||||
util.inherits(AppError, Error);
|
||||
AppError.prototype.name = 'Application Error';
|
||||
|
||||
/*
|
||||
* Errors caused by wrong request
|
||||
*/
|
||||
var UserError = function(params, constr) {
|
||||
params = parse_error_params(params, 404, 'The requested resource was not found');
|
||||
this.msg = params.msg;
|
||||
this.status = params.status;
|
||||
};
|
||||
util.inherits(UserError, Error);
|
||||
UserError.prototype.name = 'User Error';
|
||||
|
||||
module.exports.AppError = AppError;
|
||||
module.exports.UserError = UserError;
|
||||
|
122
lib/index.js
122
lib/index.js
|
@ -9,18 +9,42 @@ function validate_name(req, res, next, value, name) {
|
|||
req.params.package = String(req.params.package);
|
||||
next();
|
||||
} else {
|
||||
res.status(403);
|
||||
return res.send({
|
||||
error: 'invalid package name'
|
||||
});
|
||||
next(new Error({
|
||||
status: 403,
|
||||
msg: 'invalid package name',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
function media(expect) {
|
||||
return function(req, res, next) {
|
||||
if (req.headers['content-type'] !== expect) {
|
||||
next(new Error({
|
||||
status: 415,
|
||||
msg: 'wrong content-type, we expect '+expect,
|
||||
}));
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function expect_json(req, res, next) {
|
||||
if (typeof(req.body) !== 'object') {
|
||||
return next({
|
||||
status: 400,
|
||||
msg: 'can\'t parse incoming json',
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = function(settings) {
|
||||
var app = express();
|
||||
app.use(express.logger());
|
||||
app.use(express.bodyParser());
|
||||
app.param('package', validate_name);
|
||||
app.param('filename', validate_name);
|
||||
|
||||
/* app.get('/', function(req, res) {
|
||||
res.send({
|
||||
|
@ -28,16 +52,24 @@ module.exports = function(settings) {
|
|||
});
|
||||
});*/
|
||||
|
||||
app.get('/:package', function(req, res) {
|
||||
app.get('/:package', function(req, res, next) {
|
||||
storage.get_package(req.params.package, function(err, info) {
|
||||
if (err) return next(err);
|
||||
if (!info) {
|
||||
res.status(404);
|
||||
return res.send({
|
||||
error: 'package not found'
|
||||
res.send(info);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/:package/-/:filename', function(req, res, next) {
|
||||
storage.get_tarball(req.params.package, req.params.filename, function(err, stream) {
|
||||
if (err) return next(err);
|
||||
if (!stream) {
|
||||
return next({
|
||||
status: 404,
|
||||
msg: 'package not found'
|
||||
});
|
||||
}
|
||||
res.send(info);
|
||||
res.header('content-type', 'application/octet-stream');
|
||||
res.send(stream);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -56,76 +88,66 @@ module.exports = function(settings) {
|
|||
});
|
||||
|
||||
// publishing a package
|
||||
app.put('/:package', function(req, res, next) {
|
||||
app.put('/:package', media('application/json'), expect_json, function(req, res, next) {
|
||||
var name = req.params.package;
|
||||
if (req.headers['content-type'] !== 'application/json') {
|
||||
res.status(415);
|
||||
return res.send({
|
||||
error: 'wrong content-type, we expect application/json',
|
||||
});
|
||||
}
|
||||
if (typeof(req.body) !== 'object') {
|
||||
res.status(400);
|
||||
return res.send({
|
||||
error: 'can\'t parse incoming json',
|
||||
try {
|
||||
var metadata = utils.validate_metadata(req.body, name);
|
||||
} catch(err) {
|
||||
return next({
|
||||
status: 422,
|
||||
msg: 'bad incoming package data',
|
||||
});
|
||||
}
|
||||
|
||||
storage.create_package(name, req.body, function(err, created) {
|
||||
storage.add_package(name, metadata, function(err) {
|
||||
if (err) return next(err);
|
||||
if (created) {
|
||||
res.status(201);
|
||||
return res.send({
|
||||
ok: 'created new package'
|
||||
});
|
||||
} else {
|
||||
res.status(409);
|
||||
return res.send({
|
||||
error: 'package already exists'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// uploading package tarball
|
||||
app.put('/:package/-/:filename/*', function(req, res, next) {
|
||||
app.put('/:package/-/:filename/*', media('application/octet-stream'), function(req, res, next) {
|
||||
var name = req.params.package;
|
||||
|
||||
storage.add_tarball(name, req.params.filename, req, function(err) {
|
||||
if (err) return next(err);
|
||||
res.status(201);
|
||||
return res.send({
|
||||
ok: 'tarball uploaded successfully'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// adding a version
|
||||
app.put('/:package/:version/-tag/:tag', function(req, res, next) {
|
||||
app.put('/:package/:version/-tag/:tag', media('application/json'), expect_json, function(req, res, next) {
|
||||
var name = req.params.package;
|
||||
if (req.headers['content-type'] !== 'application/json') {
|
||||
res.status(415);
|
||||
return res.send({
|
||||
error: 'wrong content-type, we expect application/json',
|
||||
});
|
||||
}
|
||||
if (typeof(req.body) !== 'object') {
|
||||
res.status(400);
|
||||
return res.send({
|
||||
error: 'can\'t parse incoming json',
|
||||
});
|
||||
}
|
||||
var version = req.params.version;
|
||||
var tag = req.params.tag;
|
||||
|
||||
storage.add_version(req.params.package, req.params.version, req.body, function(err, created) {
|
||||
storage.add_version(name, version, req.body, tag, function(err) {
|
||||
if (err) return next(err);
|
||||
if (created) {
|
||||
res.status(201);
|
||||
return res.send({
|
||||
ok: 'package published'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.use(app.router);
|
||||
app.use(function(err, req, res, next) {
|
||||
if (err.status && err.msg && err.status >= 400 && err.status < 600) {
|
||||
res.status(err.status);
|
||||
res.send({error: err.msg});
|
||||
} else {
|
||||
res.status(409);
|
||||
return res.send({
|
||||
error: 'this version already exists'
|
||||
});
|
||||
console.log(err);
|
||||
console.log(err.stack);
|
||||
res.status(500);
|
||||
res.send({error: 'internal server error'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return app;
|
||||
};
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
module.exports.add_package = function(name, version, tarball) {
|
||||
|
||||
}
|
||||
|
105
lib/storage.js
105
lib/storage.js
|
@ -1,27 +1,100 @@
|
|||
var packages = {};
|
||||
var storage = wrap(require('./drivers/fs'));
|
||||
var UError = require('./error').UserError;
|
||||
var info_file = '.package.json';
|
||||
|
||||
module.exports.create_package = function(name, metadata, callback) {
|
||||
if (packages[name] == null) {
|
||||
packages[name] = {
|
||||
meta: metadata,
|
||||
versions: {},
|
||||
function wrap(driver) {
|
||||
if (typeof(driver.create_json) !== 'function') {
|
||||
driver.create_json = function(name, value, cb) {
|
||||
driver.create(name, JSON.stringify(value), cb);
|
||||
};
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(null, false);
|
||||
}
|
||||
if (typeof(driver.update_json) !== 'function') {
|
||||
driver.update_json = function(name, value, cb) {
|
||||
driver.update(name, JSON.stringify(value), cb);
|
||||
};
|
||||
}
|
||||
if (typeof(driver.read_json) !== 'function') {
|
||||
driver.read_json = function(name, cb) {
|
||||
driver.read(name, function(err, res) {
|
||||
if (err) return cb(err);
|
||||
cb(null, JSON.parse(res));
|
||||
});
|
||||
};
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
|
||||
module.exports.add_version = function(name, version, metadata, callback) {
|
||||
if (packages[name] == null) {
|
||||
callback(null, false);
|
||||
} else {
|
||||
packages[name].versions[version] = metadata;
|
||||
callback(null, true);
|
||||
module.exports.add_package = function(name, metadata, callback) {
|
||||
storage.create_json(name + '/' + info_file, metadata, function(err) {
|
||||
if (err && err.code === 'EEXISTS') {
|
||||
return callback(new UError({
|
||||
status: 409,
|
||||
msg: 'this package is already present'
|
||||
}));
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.add_version = function(name, version, metadata, tag, callback) {
|
||||
storage.read_json(name + '/' + info_file, function(err, data) {
|
||||
// TODO: race condition
|
||||
if (err) return callback(err);
|
||||
|
||||
if (data.versions[version] != null) {
|
||||
return callback(new UError({
|
||||
status: 409,
|
||||
msg: 'this version already present'
|
||||
}));
|
||||
}
|
||||
data.versions[version] = metadata;
|
||||
data['dist-tags'][tag] = version;
|
||||
storage.update_json(name + '/' + info_file, data, callback);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.add_tarball = function(name, filename, stream, callback) {
|
||||
var data = new Buffer(0);
|
||||
stream.on('data', function(d) {
|
||||
var tmp = data;
|
||||
data = new Buffer(tmp.length+d.length);
|
||||
tmp.copy(data, 0);
|
||||
d.copy(data, tmp.length);
|
||||
});
|
||||
stream.on('end', function(d) {
|
||||
storage.create(name + '/' + filename, data, function(err) {
|
||||
if (err && err.code === 'EEXISTS') {
|
||||
return callback(new UError({
|
||||
status: 409,
|
||||
msg: 'this tarball is already present'
|
||||
}));
|
||||
}
|
||||
callback.apply(null, arguments);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.get_tarball = function(name, filename, callback) {
|
||||
storage.read(name + '/' + filename, function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return callback(new UError({
|
||||
status: 404,
|
||||
msg: 'no such package available'
|
||||
}));
|
||||
}
|
||||
callback.apply(null, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.get_package = function(name, callback) {
|
||||
callback(null, packages[name]);
|
||||
storage.read_json(name + '/' + info_file, function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return callback(new UError({
|
||||
status: 404,
|
||||
msg: 'no such package available'
|
||||
}));
|
||||
}
|
||||
callback.apply(null, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
21
lib/utils.js
21
lib/utils.js
|
@ -1,3 +1,4 @@
|
|||
var assert = require('assert');
|
||||
|
||||
// from normalize-package-data/lib/fixer.js
|
||||
module.exports.validate_name = function(name) {
|
||||
|
@ -14,3 +15,23 @@ module.exports.validate_name = function(name) {
|
|||
}
|
||||
}
|
||||
|
||||
function is_object(obj) {
|
||||
return typeof(obj) === 'object' && !Array.isArray(obj);
|
||||
}
|
||||
|
||||
module.exports.validate_metadata = function(object, name) {
|
||||
assert(is_object(object));
|
||||
assert.equal(object._id, name);
|
||||
assert.equal(object.name, name);
|
||||
|
||||
if (!is_object(object['dist-tags'])) {
|
||||
object['dist-tags'] = {};
|
||||
}
|
||||
|
||||
if (!is_object(object['versions'])) {
|
||||
object['versions'] = {};
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue