define(function(require, exports, module) { main.consumes = [ "Plugin", "cli_commands", "proc", "api", "auth", "installer", "installer.cli" ]; main.provides = ["cli.install"]; return main; function main(options, imports, register) { var Plugin = imports.Plugin; var cmd = imports.cli_commands; var proc = imports.proc; var auth = imports.auth; var api = imports.api; var installer = imports.installer; var installerCLI = imports["installer.cli"]; var dirname = require("path").dirname; var TEST_MODE = !!process.env.C9_TEST_MODE; var TAR = "tar"; var APIHOST = options.apiHost; var BASICAUTH = process.env.C9_TEST_AUTH; var SCM = { "git": { binary: "git", clone: "clone" }, "mercurial": { binary: "hg", clone: "clone" }, "hg": { binary: "hg", clone: "clone" } }; var fs = require("fs"); var join = require("path").join; var os = require("os"); var http = require(APIHOST.indexOf("localhost") > -1 ? "http" : "https"); var verbose = false; var force = false; // Set up basic auth for api if needed if (BASICAUTH) api.basicAuth = BASICAUTH; /***** Initialization *****/ var plugin = new Plugin("Ajax.org", main.consumes); // var emit = plugin.getEmitter(); var loaded; function load(){ if (loaded) return; loaded = true; cmd.addCommand({ name: "install", info: " Installs a cloud9 package.", usage: "[--verbose] [--force] [--global] [--local] [--debug] [[@] | . ]", options: { "local": { description: "", "default": false, "boolean": true }, "global": { description: "", "default": false, "boolean": true }, "debug": { description: "", "default": false, "boolean": true }, "verbose" : { "description": "Output more information", "alias": "v", "default": false, "boolean": true }, "force" : { "description": "Ignore warnings", "alias": "f", "default": false, "boolean": true } }, check: function(argv) { if (argv._.length < 2 && !argv["package"]) throw new Error("package"); }, exec: function(argv) { verbose = argv["verbose"]; force = argv["force"]; if (argv.accessToken) auth.accessToken = argv.accessToken; if (!argv.local && !argv.debug) { if (!process.env.C9_PID) { console.warn("It looks like you are not running on c9.io. Will default to local installation of the package"); argv.local = true; } } var name = argv._[1]; var test = name == "."; if (test) name = require("path").basename(process.cwd()); install( name, { global: argv.global, local: argv.local, debug: argv.debug, test: test }, function(err, data){ if (err) { console.error(err.message || "Terminated."); process.exit(1); } else { console.log("Successfully installed", name + (argv.debug ? "" : "@" + data.version)); process.exit(0); } }); } }); cmd.addCommand({ name: "remove", alias: "uninstall", info: " Removes a cloud9 package.", usage: "[--verbose] [--global] [--local] ", // @TODO --global options: { "local": { description: "", "default": false, "boolean": true }, "global": { description: "", "default": false, "boolean": true }, "verbose" : { "description": "Output more information", "alias": "v", "default": false, "boolean": true } }, check: function(argv) { if (argv._.length < 2 && !argv["package"]) throw new Error("package"); }, exec: function(argv) { verbose = argv["verbose"]; if (argv.accessToken) auth.accessToken = argv.accessToken; var name = argv._[1]; uninstall( name, { global: argv.global, local: argv.local }, function(err, data){ if (err) { console.error(err.message || "Terminated."); process.exit(1); } else { console.log("Successfully removed", name); process.exit(0); } }); } }); } /***** Methods *****/ function spawn(command, options, callback) { if (options.stdio == null) { // if verbose, echo stdout // always echo stderr options.stdio = [ "pipe", verbose ? process.stdout : "ignore", process.stderr ]; } proc.spawn(command, options, function(err, child) { if (err) return callback(err); child.on("exit", function(code) { if (code !== 0) { var error = new Error("Command failed: " + command); error.code = code; return callback(error); } callback(); }); }); } function install(packageName, options, callback){ // Call install url var parts = packageName.split("@"); var name = parts[0]; var version = parts[1]; var repository; if (!options.test) { if (verbose) console.log("Retrieving package info"); api.packages.get(name, function (err, info) { if (err) return callback(err); if (verbose) console.log("Found:", info); if (!version) version = info.latest; repository = info.repository; installPackage(); }); } else { installPackage(); } function prepareDirectory(callback){ // Create package dir var packagePath = process.env.HOME + "/.c9/plugins/" + name; var exists = fs.existsSync(packagePath); // Ignore when testing and in the same dir if (options.test && process.cwd() == packagePath) exists = false; if (exists) { if (!force && !options.test) return callback(new Error("WARNING: Directory not empty: " + packagePath + ". Use --force to overwrite.")); proc.execFile("rm", { args: ["-Rf", packagePath] }, function(){ mkdirP(packagePath); callback(null, packagePath); }); } else { mkdirP(packagePath); callback(null, packagePath); } } function installPackage(){ if (!version && !options.test) return callback(new Error("No version found for this package")); if (options.local) { installLocal(); } else if (options.debug) { installDebug(); } else { installFull(); } } function installLocal(){ if (verbose) console.log("Installing package locally"); prepareDirectory(function(err, packagePath){ if (err) return callback(err); var npmBin = [ join(process.env.HOME, process.platform == "win32"? ".c9/npm.cmd" : ".c9/node/bin/npm"), "/mnt/shared/sbin/npm" ]; function installNPM(){ spawn(npmBin[0], { args: ["install"], cwd: packagePath }, function(err) { if (err && err.code == 127) { npmBin.shift(); if (npmBin.length) return installNPM(); } if (err) return callback(err); callback(null, { version: version }); }); } if (options.test) { try { var json = JSON.parse(fs.readFileSync(join(process.cwd(), "package.json"))); if (json.private) return callback(new Error("ERROR: Private flag in package.json prevents from installing")); } catch(e) { return callback(new Error("ERROR: Invalid package: " + e.message)); } if (process.cwd() == packagePath) installNPM(); else { proc.execFile("cp", { args: ["-R", process.cwd(), dirname(packagePath)] }, function(err){ if (err) return callback(err); installNPM(); }); } return; } // Download package var gzPath = join(os.tmpDir(), name + "@" + version + ".tar.gz"); var file = fs.createWriteStream(gzPath); var path = "/packages/" + name + "/versions/" + version + "/download?access_token=" + encodeURIComponent(auth.accessToken); var host = APIHOST.split(":")[0]; var port = parseInt(APIHOST.split(":")[1]) || null; var request = http.get({ agent: false, method: "get", host: host, port: port, auth: BASICAUTH, path: path }, function(response){ response.pipe(file); }); if (verbose) console.log("Downloading package to", gzPath); request.on('response', function(res) { if (res.statusCode != 200) return callback(new Error("Unknown Error getting " + host + (port ? ":" + port : "") + path + ":" + res.statusCode)); }); file.on('finish', function() { if (verbose) console.log("Unpacking", gzPath, "to", packagePath); // Untargz package spawn(TAR, { args: [ (verbose ? "-v" : ""), "-C", normalizePath(packagePath), "-zxf", normalizePath(gzPath) ] }, function(err) { if (err) { var error = new Error("Failed to unpack package"); error.code = err.code; return callback(error); } installNPM(); }); }); }); } function installDebug(){ if (verbose) console.log("Installing debug version of package"); if (options.test) return callback(new Error("Test is not supported for debug installations")); prepareDirectory(function(err, packagePath){ if (err) return callback(err); if (verbose) console.log("Cloning repository: ", repository); // Git clone repository var scm = SCM[repository.type]; spawn(scm.binary, { args: [scm.clone, repository.url, packagePath] }, function(err) { if (err) { var error = new Error("Failed to clone package from repository. Do you have access?"); error.code = err.code; return callback(error); } callback(); }); }); } function installFull(){ // Install Locally options.local = true; install(name + "@" + version, options, function(err){ if (err) return callback(err); var path = process.env.HOME + "/.c9/plugins/" + name; fs.readFile(path + "/package.json", "utf8", function(err, data){ if (err) return callback(new Error("Package.json not found in " + path)); var installPath; try { installPath = JSON.parse(data).installer; } catch(e){ return callback(new Error("Could not parse package.json in " + path)); } if (installPath) { installerCLI.verbose = verbose; installer.createSession(name, version || "", require(path + "/" + installPath), function(err){ if (err) return callback(new Error("Error Installing Package " + name + "@" + version)); installToDatabase(); }, force || options.test); } else installToDatabase(); }); function installToDatabase(){ if (options.test) return callback(null, { version: "test" }); if (verbose) console.log("Notifying c9.io that package is installed"); var endpoint = options.global ? api.user : api.project; var url = "install/" + name + "/" + version + "?mode=silent"; endpoint.post(url, function(err, info){ callback(err, info); }); } }); } } function uninstall(packageName, options, callback){ // Call uninstall url var parts = packageName.split("@"); var name = parts[0]; var version = parts[1]; if (!version) { api.packages.get(name, function (err, info) { if (err) return callback(err); version = info.latest; uninstallPackage(); }); } else { uninstallPackage(); } function uninstallPackage(){ if (options.local || options.debug) { // rm -Rf var packagePath = process.env.HOME + "/.c9/plugins/" + name; spawn("rm", { args: ["-rf", packagePath] }, function(err) { if (err) { var error = new Error("Failed to remove package."); error.code = err.code; return callback(error); } // if debug > see if should be installed and put back original // @TODO callback(); }); } else { var endpoint = options.global ? api.user : api.project; var url = "uninstall/" + packageName; endpoint.post(url, function(err, info){ callback(err, info); }); } } } function mkdirP(path){ var dirs = path.split('/'); var prevDir = dirs.splice(0,1) + "/"; while (dirs.length > 0) { var curDir = prevDir + dirs.splice(0,1); if (! fs.existsSync(curDir) ) { fs.mkdirSync(curDir); } prevDir = curDir + '/'; } } function normalizePath(p) { if (process.platform == "win32") p = p.replace(/\\/g, "/").replace(/^(\w):/, "/$1"); return p; } /***** Lifecycle *****/ plugin.on("load", function(){ load(); }); plugin.on("enable", function(){ }); plugin.on("disable", function(){ }); plugin.on("unload", function(){ loaded = false; verbose = false; force = false; }); /***** Register and definfe API *****/ /** * **/ plugin.freezePublicAPI({ /** * */ install: install, /** * */ uninstall: uninstall }); register(null, { "cli.install": plugin }); } });