kopia lustrzana https://github.com/c9/core
Merge pull request +8663 from c9/new-sfdc-updater
Add npm update mechanism for SFDC pluginspull/134/head
commit
fd8a81cb5d
|
@ -0,0 +1,439 @@
|
||||||
|
define(function(require, exports, module) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
main.consumes = [
|
||||||
|
"Plugin",
|
||||||
|
"c9", "proc", "Dialog",
|
||||||
|
];
|
||||||
|
main.provides = ["plugin.updater.npm"];
|
||||||
|
return main;
|
||||||
|
|
||||||
|
function main(options, imports, register) {
|
||||||
|
var Plugin = imports["Plugin"];
|
||||||
|
var c9 = imports["c9"];
|
||||||
|
var proc = imports["proc"];
|
||||||
|
var Dialog = imports["Dialog"];
|
||||||
|
|
||||||
|
var async = require("async");
|
||||||
|
var path = require("path");
|
||||||
|
var semverCompare = require("semver-compare");
|
||||||
|
|
||||||
|
var NPM_MIN_VERSION = "2.6.0";
|
||||||
|
|
||||||
|
/***** Initialization *****/
|
||||||
|
|
||||||
|
var npmBin = options.npmBin || "/home/ubuntu/.nvm/nvm-exec";
|
||||||
|
var pluginsPath = options.pluginsPath || "/home/ubuntu/.c9/plugins";
|
||||||
|
var managedPath = options.managedPath || "/home/ubuntu/.c9/managed";
|
||||||
|
|
||||||
|
var managedNpmPath = [managedPath, "npm"].join("/");
|
||||||
|
var managedEtcPath = [managedNpmPath, "etc"].join("/");
|
||||||
|
var managedRcPath = [managedEtcPath, "npmrc"].join("/");
|
||||||
|
var managedCachePath = [managedPath, "npm", "cache"].join("/");
|
||||||
|
var managedPluginsPath = [managedPath, "plugins"].join("/");
|
||||||
|
var managedModulesPath = [managedPath, "node_modules"].join("/");
|
||||||
|
|
||||||
|
var plugin = new Plugin("Ajax.org", main.consumes);
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
var pkgs = options.packages;
|
||||||
|
|
||||||
|
if (!pkgs) {
|
||||||
|
console.warn("[plugin.updater.npm] no managed packages configured, not loading.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: DRY error handling
|
||||||
|
|
||||||
|
fsMkdirs([ managedPath, managedEtcPath, managedModulesPath, pluginsPath ], function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fsWriteNpmrc(function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
npmCheckVersion(function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: clean up the flow for detecting and installing missing
|
||||||
|
// packages. the nested functions are messy.
|
||||||
|
|
||||||
|
async.filter(pkgs, function(pkg, done) {
|
||||||
|
npmExplorePath(pkg, function(err, path) {
|
||||||
|
var isMissing = !!err;
|
||||||
|
|
||||||
|
if (isMissing)
|
||||||
|
debug("missing package:", pkg, err);
|
||||||
|
|
||||||
|
done(isMissing);
|
||||||
|
});
|
||||||
|
}, function(missing) {
|
||||||
|
if (missing.length) {
|
||||||
|
console.info("[plugin.updater.npm] Installing missing plugins:", missing);
|
||||||
|
showUpdateDialog();
|
||||||
|
|
||||||
|
npmInstall(missing, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildLinks(pkgs, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload browser
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
npmOutdated(pkgs, function(err, outdated) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outdated) {
|
||||||
|
debug("Plugins up-to-date.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info("[plugin.updater.npm] Updating outdated plugins:", outdated);
|
||||||
|
showUpdateDialog();
|
||||||
|
|
||||||
|
buildLinks(pkgs, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
npmUpdate(outdated, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("[plugin.updater.npm]", err);
|
||||||
|
alert("[plugin.updater.npm] Error: " + err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload browser
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function unload() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- Helper Functions ----- */
|
||||||
|
|
||||||
|
function debug(format) {
|
||||||
|
if (c9.debug) {
|
||||||
|
var util = require("util");
|
||||||
|
console.info(util.format.apply(null,
|
||||||
|
Array.prototype.slice.call(arguments)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function trimRight(str) {
|
||||||
|
return str.replace(/\s+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- npm Management ----- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the installed npm version matches the NPM_MIN_VERSION
|
||||||
|
* required
|
||||||
|
*
|
||||||
|
* @param {Function} callback
|
||||||
|
* @param {Error=} callback.err An error if the version is lower than required
|
||||||
|
*/
|
||||||
|
function npmCheckVersion(callback) {
|
||||||
|
npmExec("npm", ["-v"], function(err, stdout, stderr) {
|
||||||
|
if (err) return callback(err, stdout, stderr);
|
||||||
|
|
||||||
|
var version = stdout;
|
||||||
|
|
||||||
|
debug("npm version", version);
|
||||||
|
|
||||||
|
if (semverCompare(version, NPM_MIN_VERSION) === -1) {
|
||||||
|
var error = new Error("npm version " + NPM_MIN_VERSION + " or greater required");
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function npmExec(command, args, callback) {
|
||||||
|
debug(npmBin, { args: [ "npm", command ].concat(args) });
|
||||||
|
|
||||||
|
proc.execFile(npmBin, {
|
||||||
|
args: [ "npm", command ].concat(args),
|
||||||
|
cwd: managedPath,
|
||||||
|
env: {
|
||||||
|
"npm_config_production": "true",
|
||||||
|
"npm_config_depth": 0,
|
||||||
|
"npm_config_link": "true",
|
||||||
|
"npm_config_userconfig": "/dev/null",
|
||||||
|
"npm_config_prefix": managedNpmPath,
|
||||||
|
"npm_config_cache": managedCachePath,
|
||||||
|
},
|
||||||
|
}, function(err, stdout, stderr) {
|
||||||
|
debug([err, stdout, stderr]);
|
||||||
|
|
||||||
|
if (err) return callback(err, stdout, err.message);
|
||||||
|
|
||||||
|
stdout = trimRight(stdout);
|
||||||
|
stderr = trimRight(stderr);
|
||||||
|
|
||||||
|
callback(null, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- Package Management ----- */
|
||||||
|
|
||||||
|
function npmOutdated(pkgs, callback) {
|
||||||
|
npmExec("outdated", ["--json"], function(err, stdout, stderr) {
|
||||||
|
if (err)
|
||||||
|
return callback(err, null, stdout, stderr);
|
||||||
|
|
||||||
|
if (!stdout)
|
||||||
|
return callback(null, null, stdout, stderr);
|
||||||
|
|
||||||
|
var outdated = JSON.parse(stdout);
|
||||||
|
outdated = Object.keys(outdated);
|
||||||
|
|
||||||
|
callback(null, outdated, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function npmInstall(pkgs, callback) {
|
||||||
|
npmExec("install", ["--"].concat(pkgs), function(err, stdout, stderr) {
|
||||||
|
callback(err, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function npmUpdate(pkgs, callback) {
|
||||||
|
npmExec("update", ["--"].concat(pkgs), function(err, stdout, stderr) {
|
||||||
|
callback(err, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function npmExplorePath(pkg, callback) {
|
||||||
|
npmExec("explore", [pkg, "--", "pwd"], function(err, stdout, stderr) {
|
||||||
|
if (err)
|
||||||
|
return callback(err, null, stderr);
|
||||||
|
|
||||||
|
callback(null, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the symbolic links from `~/.c9/plugins` to the managed plugins.
|
||||||
|
*
|
||||||
|
* @param {String[]} pkgs A list of package names to link.
|
||||||
|
*
|
||||||
|
* @param {Function} callback
|
||||||
|
* @param {Error=} callback.err
|
||||||
|
*/
|
||||||
|
function buildLinks(pkgs, callback) {
|
||||||
|
async.each(pkgs, function(pkg, done) {
|
||||||
|
npmExplorePath(pkg, function(err, pkgPath) {
|
||||||
|
if (err) return done(err);
|
||||||
|
fsForceLink(pkgPath, done);
|
||||||
|
});
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes symbolic links from the `~/.c9/plugins` folder.
|
||||||
|
*/
|
||||||
|
function fsRmLinks(callback) {
|
||||||
|
debug("find", { args: [ pluginsPath, "-maxdepth", "1", "-type", "l", "-exec", "rm", "{}", ";" ] });
|
||||||
|
|
||||||
|
// find . -maxdepth 1 -type l -exec rm {} \;
|
||||||
|
|
||||||
|
proc.execFile("find", {
|
||||||
|
args: [
|
||||||
|
pluginsPath,
|
||||||
|
"-maxdepth", "1",
|
||||||
|
"-type", "l",
|
||||||
|
"-exec", "rm", "{}", ";"
|
||||||
|
],
|
||||||
|
}, function(err, stdout, stderr) {
|
||||||
|
debug([err, stdout, stderr]);
|
||||||
|
callback(err, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a symbolic link in `~/.c9/plugins` pointing to the given
|
||||||
|
* plugin path.
|
||||||
|
*
|
||||||
|
* @param {String} pkgPath Path to the source package folder
|
||||||
|
*/
|
||||||
|
function fsLink(pkgPath, callback) {
|
||||||
|
debug("ls", { args: [ "-s", "-f", pkgPath, [ pluginsPath, "." ].join("/") ]});
|
||||||
|
|
||||||
|
proc.execFile("ln", {
|
||||||
|
args: [
|
||||||
|
"-s", "-f",
|
||||||
|
pkgPath,
|
||||||
|
[ pluginsPath, "." ].join("/"),
|
||||||
|
],
|
||||||
|
}, function(err, stdout, stderr) {
|
||||||
|
debug([err, stdout, stderr]);
|
||||||
|
callback(err, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forcefully delete an existing plugin folder and change it to
|
||||||
|
* a symbolic link in `~/.c9/plugins` pointing to the given plugin
|
||||||
|
* path.
|
||||||
|
*
|
||||||
|
* @param {String} pkgPath Path to the source package folder
|
||||||
|
*/
|
||||||
|
function fsForceLink(pkgPath, callback) {
|
||||||
|
var basename = path.basename(pkgPath);
|
||||||
|
|
||||||
|
proc.execFile("rm", {
|
||||||
|
args: [
|
||||||
|
"-rf", basename,
|
||||||
|
],
|
||||||
|
cwd: pluginsPath,
|
||||||
|
}, function(err, stdout, stderr) {
|
||||||
|
debug([err, stdout, stderr]);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return callback(err, stdout, stderr);
|
||||||
|
|
||||||
|
fsLink(pkgPath, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fsMkdirs(dirPaths, callback) {
|
||||||
|
debug("mkdir", { args: [ "-p", "--" ].concat(dirPaths) });
|
||||||
|
|
||||||
|
proc.execFile("mkdir", {
|
||||||
|
args: [ "-p", "--" ].concat(dirPaths),
|
||||||
|
}, function(err, stdout, stderr) {
|
||||||
|
callback(err, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fsWriteNpmrc(callback) {
|
||||||
|
var config = [
|
||||||
|
"//registry.npmjs.org/:_authToken = a7c61f6e-5b10-41db-947f-8bc8f1f9468b",
|
||||||
|
];
|
||||||
|
|
||||||
|
//
|
||||||
|
// HACK: - fs.writeFile() does not always work? we are using echo
|
||||||
|
// instead
|
||||||
|
//
|
||||||
|
// - config is not escaped
|
||||||
|
//
|
||||||
|
|
||||||
|
debug("sh", { args: [ "-c", "echo '" + config.join("\\n") + "' > " + managedRcPath ] });
|
||||||
|
|
||||||
|
proc.execFile("sh", {
|
||||||
|
args: [
|
||||||
|
"-c",
|
||||||
|
"echo '" + config.join("\\n") + "' > " + managedRcPath
|
||||||
|
],
|
||||||
|
}, function(err, stdout, stderr) {
|
||||||
|
callback(err, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- Interface ----- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a modal upgrade progress dialog, blocking the IDE interface
|
||||||
|
* while we are updating the plugins.
|
||||||
|
*/
|
||||||
|
function showUpdateDialog() {
|
||||||
|
var dialog = new Dialog("Ajax.org", [], {
|
||||||
|
name: "plugin.updater.npm.dialog",
|
||||||
|
allowClose: false,
|
||||||
|
modal: true,
|
||||||
|
elements: [
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.title = "Installing Updates";
|
||||||
|
dialog.heading = "";
|
||||||
|
dialog.body = "Your workspace will be updated to the newest version and reload automatically.";
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an upgrade error dialog, requesting to delete and recreate the
|
||||||
|
* workspace. This is shown for critical update errors.
|
||||||
|
*/
|
||||||
|
function showErrorDialog(err) {
|
||||||
|
var dialog = new Dialog("Ajax.org", [], {
|
||||||
|
name: "plugin.updater.npm.error_dialog",
|
||||||
|
allowClose: true,
|
||||||
|
modal: true,
|
||||||
|
elements: [
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
var errorMessage = (err && err.message) ? "" + err.message : err;
|
||||||
|
|
||||||
|
dialog.title = "Error installing updates";
|
||||||
|
dialog.heading = "";
|
||||||
|
dialog.body = "<strong>Important updates could not be installed on this workspace.</strong><br><br>"
|
||||||
|
+ "Please delete this workspace and create a new one, in order to continue "
|
||||||
|
+ "working in an up-to-date environment.<br><br>"
|
||||||
|
+ "<div style='max-height: 100px; overflow: auto;'><small><code>" + errorMessage + "</code></small></div>";
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** Register and define API *****/
|
||||||
|
|
||||||
|
plugin.on("load", load);
|
||||||
|
plugin.on("unload", unload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class salesforc.sync
|
||||||
|
*/
|
||||||
|
plugin.freezePublicAPI({
|
||||||
|
});
|
||||||
|
|
||||||
|
register(null, {
|
||||||
|
"plugin.updater.npm" : plugin
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
// semver-compare (1.0.0)
|
||||||
|
// https://github.com/substack/semver-compare
|
||||||
|
//
|
||||||
|
// This software is released under the MIT license:
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
|
module.exports = function cmp (a, b) {
|
||||||
|
var pa = a.split('.');
|
||||||
|
var pb = b.split('.');
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
var na = Number(pa[i]);
|
||||||
|
var nb = Number(pb[i]);
|
||||||
|
if (na > nb) return 1;
|
||||||
|
if (nb > na) return -1;
|
||||||
|
if (!isNaN(na) && isNaN(nb)) return 1;
|
||||||
|
if (isNaN(na) && !isNaN(nb)) return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
Ładowanie…
Reference in New Issue