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;
|
||||||
|
|
146
lib/index.js
146
lib/index.js
|
@ -9,18 +9,42 @@ function validate_name(req, res, next, value, name) {
|
||||||
req.params.package = String(req.params.package);
|
req.params.package = String(req.params.package);
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
res.status(403);
|
next(new Error({
|
||||||
return res.send({
|
status: 403,
|
||||||
error: 'invalid package name'
|
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) {
|
module.exports = function(settings) {
|
||||||
var app = express();
|
var app = express();
|
||||||
app.use(express.logger());
|
app.use(express.logger());
|
||||||
app.use(express.bodyParser());
|
app.use(express.bodyParser());
|
||||||
app.param('package', validate_name);
|
app.param('package', validate_name);
|
||||||
|
app.param('filename', validate_name);
|
||||||
|
|
||||||
/* app.get('/', function(req, res) {
|
/* app.get('/', function(req, res) {
|
||||||
res.send({
|
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) {
|
storage.get_package(req.params.package, function(err, info) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (!info) {
|
res.send(info);
|
||||||
res.status(404);
|
});
|
||||||
return res.send({
|
});
|
||||||
error: 'package not found'
|
|
||||||
|
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,77 +88,67 @@ module.exports = function(settings) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// publishing a package
|
// 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;
|
var name = req.params.package;
|
||||||
if (req.headers['content-type'] !== 'application/json') {
|
try {
|
||||||
res.status(415);
|
var metadata = utils.validate_metadata(req.body, name);
|
||||||
return res.send({
|
} catch(err) {
|
||||||
error: 'wrong content-type, we expect application/json',
|
return next({
|
||||||
});
|
status: 422,
|
||||||
}
|
msg: 'bad incoming package data',
|
||||||
if (typeof(req.body) !== 'object') {
|
|
||||||
res.status(400);
|
|
||||||
return res.send({
|
|
||||||
error: 'can\'t parse incoming json',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.create_package(name, req.body, function(err, created) {
|
storage.add_package(name, metadata, function(err) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (created) {
|
res.status(201);
|
||||||
res.status(201);
|
return res.send({
|
||||||
return res.send({
|
ok: 'created new package'
|
||||||
ok: 'created new package'
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(409);
|
|
||||||
return res.send({
|
|
||||||
error: 'package already exists'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// uploading package tarball
|
// uploading package tarball
|
||||||
app.put('/:package/-/:filename/*', function(req, res, next) {
|
app.put('/:package/-/:filename/*', media('application/octet-stream'), function(req, res, next) {
|
||||||
res.status(201);
|
var name = req.params.package;
|
||||||
return res.send({
|
|
||||||
ok: 'tarball uploaded successfully'
|
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
|
// 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;
|
var name = req.params.package;
|
||||||
if (req.headers['content-type'] !== 'application/json') {
|
var version = req.params.version;
|
||||||
res.status(415);
|
var tag = req.params.tag;
|
||||||
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',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (err) return next(err);
|
||||||
if (created) {
|
res.status(201);
|
||||||
res.status(201);
|
return res.send({
|
||||||
return res.send({
|
ok: 'package published'
|
||||||
ok: 'package published'
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(409);
|
|
||||||
return res.send({
|
|
||||||
error: 'this version already exists'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 {
|
||||||
|
console.log(err);
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(500);
|
||||||
|
res.send({error: 'internal server error'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
|
|
||||||
module.exports.add_package = function(name, version, tarball) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
107
lib/storage.js
107
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) {
|
function wrap(driver) {
|
||||||
if (packages[name] == null) {
|
if (typeof(driver.create_json) !== 'function') {
|
||||||
packages[name] = {
|
driver.create_json = function(name, value, cb) {
|
||||||
meta: metadata,
|
driver.create(name, JSON.stringify(value), cb);
|
||||||
versions: {},
|
|
||||||
};
|
};
|
||||||
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) {
|
module.exports.add_package = function(name, metadata, callback) {
|
||||||
if (packages[name] == null) {
|
storage.create_json(name + '/' + info_file, metadata, function(err) {
|
||||||
callback(null, false);
|
if (err && err.code === 'EEXISTS') {
|
||||||
} else {
|
return callback(new UError({
|
||||||
packages[name].versions[version] = metadata;
|
status: 409,
|
||||||
callback(null, true);
|
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) {
|
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
|
// from normalize-package-data/lib/fixer.js
|
||||||
module.exports.validate_name = function(name) {
|
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