diff --git a/bin/npmrepod b/bin/npmrepod index a03152761..ee2abdcd3 100755 --- a/bin/npmrepod +++ b/bin/npmrepod @@ -1,24 +1,24 @@ #!/usr/bin/env node +var fs = require('fs'); +var yaml = require('js-yaml'); var commander = require('commander'); +var pkg = yaml.safeLoad(fs.readFileSync('../package.yaml', 'utf8')); +var server = require('../lib/index'); commander - // re-inventing the wheel because I don't want to require package.json for every run - .option('-V, --version', 'output the version number') - .on('version', get_version) - - .option('-p, --port ', 'port number to listen on (default: 4873)', 4873) + .option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)', '4873') .option('-s, --storage ', 'path to package cache (default: "~/.npmrepod")') // todo: need something to do with invalid https certificate, but we just can't use http by default .option('-u, --uplink ', 'parent registry (default: "https://registry.npmjs.org/")') - .option('-c, --config ', 'use this configuration file') - + .option('-c, --config ', 'use this configuration file') + .version(pkg.version) .parse(process.argv); -function get_version() { - console.log(require('../package.json').version); - process.exit(0); +var hostport = commander.listen.split(':'); +if (hostport.length < 2) { + hostport = [undefined, hostport[0]]; } - -console.log('This thing doesn\'t work yet! Come back in a few weeks...'); +server({}).listen(hostport[1], hostport[0]); +console.log('Server is listening on http://%s:%s/', hostport[0] || 'localhost', hostport[1]); diff --git a/lib/index.js b/lib/index.js index e69de29bb..e3db6ea31 100644 --- a/lib/index.js +++ b/lib/index.js @@ -0,0 +1,132 @@ +var express = require('express'); +var cookies = require('cookies'); +var proxy = require('./proxy'); +var utils = require('./utils'); +var storage = require('./storage'); + +function validate_name(req, res, next, value, name) { + if (utils.validate_name(req.params.package)) { + req.params.package = String(req.params.package); + next(); + } else { + res.status(403); + return res.send({ + error: 'invalid package name' + }); + } +}; + +module.exports = function(settings) { + var app = express(); + app.use(express.logger()); + app.use(express.bodyParser()); + app.param('package', validate_name); + +/* app.get('/', function(req, res) { + res.send({ + error: 'unimplemented' + }); + });*/ + + app.get('/:package', function(req, res) { + 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('/*', function(req, res) { + // proxy.request(req, res); + //}); + + // 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) { + res.cookies.set('AuthSession', String(Math.random()), { + // npmjs.org sets 10h expire + expires: new Date(Date.now() + 10*60*60*1000) + }); + res.send({"ok":true,"name":"anonymous","roles":[]}); + }); + + // publishing a package + app.put('/:package', 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', + }); + } + + storage.create_package(name, req.body, function(err, created) { + 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) { + res.status(201); + return res.send({ + ok: 'tarball uploaded successfully' + }); + }); + + // adding a version + app.put('/:package/:version/-tag/:tag', 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', + }); + } + + storage.add_version(req.params.package, req.params.version, req.body, function(err, created) { + if (err) return next(err); + if (created) { + res.status(201); + return res.send({ + ok: 'package published' + }); + } else { + res.status(409); + return res.send({ + error: 'this version already exists' + }); + } + }); + }); + + return app; +}; + diff --git a/lib/plugins/fs.js b/lib/plugins/fs.js index e69de29bb..88f44e580 100644 --- a/lib/plugins/fs.js +++ b/lib/plugins/fs.js @@ -0,0 +1,5 @@ + +module.exports.add_package = function(name, version, tarball) { + +} + diff --git a/lib/proxy.js b/lib/proxy.js new file mode 100644 index 000000000..a563795a9 --- /dev/null +++ b/lib/proxy.js @@ -0,0 +1,25 @@ +var http = require('http'); + +module.exports.request = function(req, resp) { + console.log(req.headers); + http.get({ + host: 'registry.npmjs.org', + path: req.url, + headers: { + 'User-Agent': 'npmrepod/0.0.0', + 'Authorization': req.headers.authorization, + }, + }, function(res) { + resp.writeHead(res.statusCode, res.headers); + res.on('data', function(d) { + resp.write(d); + }); + res.on('end', function() { + resp.end(); + }); + }).on('error', function(err) { + console.error(err); + resp.send(500); + }); +} + diff --git a/lib/storage.js b/lib/storage.js new file mode 100644 index 000000000..7c61cde7a --- /dev/null +++ b/lib/storage.js @@ -0,0 +1,27 @@ +var packages = {}; + +module.exports.create_package = function(name, metadata, callback) { + if (packages[name] == null) { + packages[name] = { + meta: metadata, + versions: {}, + }; + callback(null, true); + } else { + callback(null, false); + } +} + +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.get_package = function(name, callback) { + callback(null, packages[name]); +} + diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 000000000..59aea551d --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,16 @@ + +// from normalize-package-data/lib/fixer.js +module.exports.validate_name = function(name) { + if ( + name.charAt(0) === "." || + name.match(/[\/@\s\+%:]/) || + name !== encodeURIComponent(name) || + name.toLowerCase() === "node_modules" || + name.toLowerCase() === "favicon.ico" + ) { + return false; + } else { + return true; + } +} + diff --git a/package.yaml b/package.yaml index 33687c03e..32715f90f 100644 --- a/package.yaml +++ b/package.yaml @@ -11,11 +11,13 @@ main: index.js bin: npmrepod: ./bin/npmrepod - + dependencies: - express: '*' - commander: '*' - + express: '>= 3.2.5' + commander: '>= 1.1.1' + js-yaml: '>= 2.0.5' + cookies: '>= 0.3.6' + preferGlobal: true license: BSD