blob: 2587f4114b051fa28943154beffeed28deff4d62 [file] [log] [blame]
var npm = require('npm'),
path = require('path'),
http = require('http'),
url = require('url'),
fs = require('fs'),
manifest = require('./manifest'),
os = require('os'),
rc = require('rc'),
Q = require('q'),
request = require('request'),
zlib = require('zlib'),
tar = require('tar'),
shell = require('shelljs'),
home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,
plugmanConfigDir = path.resolve(home, '.plugman'),
plugmanCacheDir = path.resolve(plugmanConfigDir, 'cache');
/**
* @method getPackageInfo
* @param {String} args Package names
* @return {Promise.<Object>} Promised package info.
*/
function getPackageInfo(args) {
var thing = args.length ? args.shift().split("@") : [],
name = thing.shift(),
version = thing.join("@") || 'latest';
var settings = module.exports.settings;
var d = Q.defer();
var req = makeRequest('GET', settings.registry + '/' + name + '/' + version, function(err, res, body){
if(err || res.statusCode != 200) {
d.reject(new Error('Failed to fetch package information for '+name));
} else {
d.resolve(JSON.parse(body));
}
});
req.on('error', function(err) {
d.reject(err);
});
return d.promise;
}
/**
* @method fetchPackage
* @param {String} info Package info
* @return {Promise.<string>} Promised path to the package.
*/
function fetchPackage(info, cl) {
var settings = module.exports.settings;
var d = Q.defer();
var cached = path.resolve(settings.cache, info.name, info.version, 'package');
if(fs.existsSync(cached)) {
d.resolve(cached);
} else {
var download_dir = path.join(os.tmpdir(), info.name);
shell.mkdir('-p', download_dir);
var req = makeRequest('GET', info.dist.tarball, function (err, res, body) {
if(err || res.statusCode != 200) {
d.reject(new Error('failed to fetch the plugin archive'));
} else {
// Update the download count for this plugin.
// Fingers crossed that the timestamps are unique, and that no plugin is downloaded
// twice in a single millisecond.
//
// This is acceptable, because the failure mode is Couch gracefully rejecting the second one
// (for lacking a _rev), and dropped a download count is not important.
var now = new Date();
var pkgId = info._id.substring(0, info._id.indexOf('@'));
var message = {
day: now.getUTCFullYear() + '-' + (now.getUTCMonth()+1) + '-' + now.getUTCDate(),
pkg: pkgId,
client: cl
};
var remote = settings.registry + '/downloads'
makeRequest('POST', remote, message, function (err, res, body) {
// ignore errors
});
}
});
req.pipe(zlib.createUnzip())
.pipe(tar.Extract({path:download_dir}))
.on('error', function(err) {
shell.rm('-rf', download_dir);
d.reject(err);
})
.on('end', function() {
d.resolve(path.resolve(download_dir, 'package'));
});
}
return d.promise;
}
module.exports = {
settings: null,
/**
* @method config
* @param {Array} args Command argument
* @return {Promise.<Object>} Promised configuration object.
*/
config: function(args) {
return initSettings().then(function(settings) {
return Q.ninvoke(npm, 'load', settings)
})
.then(function() {
return Q.ninvoke(npm.commands, 'config', args);
});
},
/**
* @method owner
* @param {Array} args Command argument
* @return {Promise.<void>} Promise for completion.
*/
owner: function(args) {
return initSettings().then(function(settings) {
return Q.ninvoke(npm, 'load', settings);
}).then(function() {
return Q.ninvoke(npm.commands, 'owner', args);
});
},
/**
* @method adduser
* @param {Array} args Command argument
* @return {Promise.<void>} Promise for completion.
*/
adduser: function(args) {
return initSettings().then(function(settings) {
return Q.ninvoke(npm, 'load', settings)
})
.then(function() {
return Q.ninvoke(npm.commands, 'adduser', args);
});
},
/**
* @method publish
* @param {Array} args Command argument
* @return {Promise.<Object>} Promised published data.
*/
publish: function(args) {
return initSettings()
.then(function(settings) {
var p = manifest.generatePackageJsonFromPluginXml(args[0]);
p.then(function() {
return Q.ninvoke(npm, 'load', settings);
}).then(function() {
return Q.ninvoke(npm.commands, 'publish', args)
}).fin(function() {
fs.unlink(path.resolve(args[0], 'package.json'));
});
});
},
/**
* @method search
* @param {Array} args Array of keywords
* @return {Promise.<Object>} Promised search results.
*/
search: function(args) {
return initSettings()
.then(function(settings) {
return Q.ninvoke(npm, 'load', settings);
}).then(function() {
return Q.ninvoke(npm.commands, 'search', args, true);
});
},
/**
* @method unpublish
* @param {Array} args Command argument
* @return {Promise.<Object>} Promised results.
*/
unpublish: function(args) {
return initSettings()
.then(function(settings) {
return Q.ninvoke(npm, 'load', settings);
}).then(function() {
return Q.ninvoke(npm.commands, 'unpublish', args);
}).then(function() {
return Q.ninvoke(npm.commands, 'cache', ["clean"]);
});
},
/**
* @method fetch
* @param {String} name Plugin name
* @return {Promise.<string>} Promised path to fetched package.
*/
fetch: function(args, client) {
var cl = (client === 'plugman' ? 'plugman' : 'cordova-cli');
return initSettings()
.then(function(settings) {
return getPackageInfo(args);
}).then(function(info) {
return fetchPackage(info, cl);
});
},
/**
* @method info
* @param {String} name Plugin name
* @return {Promise.<Object>} Promised package info.
*/
info: function(args) {
return initSettings()
.then(function() {
return getPackageInfo(args);
});
}
}
/**
* @method initSettings
* @return {Promise.<Object>} Promised settings.
*/
function initSettings() {
var settings = module.exports.settings;
// check if settings already set
if(settings != null) return Q(settings);
// setting up settings
// obviously if settings dir does not exist settings is going to be empty
if(!fs.existsSync(plugmanConfigDir)) {
fs.mkdirSync(plugmanConfigDir);
fs.mkdirSync(plugmanCacheDir);
}
settings =
module.exports.settings =
rc('plugman', {
cache: plugmanCacheDir,
force: true,
registry: 'http://registry.cordova.io',
logstream: fs.createWriteStream(path.resolve(plugmanConfigDir, 'plugman.log')),
userconfig: path.resolve(plugmanConfigDir, 'config')
});
return Q(settings);
}
function makeRequest (method, where, what, cb_) {
var settings = module.exports.settings
var remote = url.parse(where)
if (typeof cb_ !== "function") cb_ = what, what = null
var cbCalled = false
function cb () {
if (cbCalled) return
cbCalled = true
cb_.apply(null, arguments)
}
var strict = settings['strict-ssl']
if (strict === undefined) strict = true
var opts = { url: remote
, method: method
, ca: settings.ca
, strictSSL: strict }
, headers = opts.headers = {}
headers.accept = "application/json"
headers["user-agent"] = settings['user-agent'] ||
'node/' + process.version
var p = settings.proxy
var sp = settings['https-proxy'] || p
opts.proxy = remote.protocol === "https:" ? sp : p
// figure out wth 'what' is
if (what) {
if (Buffer.isBuffer(what) || typeof what === "string") {
opts.body = what
headers["content-type"] = "application/json"
headers["content-length"] = Buffer.byteLength(what)
} else {
opts.json = what
}
}
var req = request(opts, cb)
req.on("error", cb)
req.on("socket", function (s) {
s.on("error", cb)
})
return req
}