@hkvstore/taco-cli
Version:
taco-cli is a command-line interface for rapid Apache Cordova development (forked from Microsoft taco-cli)
332 lines (330 loc) • 15.8 kB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/// <reference path="../../typings/tacoUtils.d.ts" />
/// <reference path="../../typings/request.d.ts" />
/// <reference path="../../typings/node.d.ts" />
;
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Q = require("q");
var request = require("request");
var util = require("util");
var ConnectionSecurityHelper = require("./remoteBuild/connectionSecurityHelper");
var HelpModule = require("./help");
var resources = require("../resources/resourceManager");
var Settings = require("./utils/settings");
var TacoErrorCodes = require("./tacoErrorCodes");
var errorHelper = require("./tacoErrorHelper");
var tacoUtility = require("taco-utils");
var CliTelemetryHelper = require("./utils/cliTelemetryHelper");
var commands = tacoUtility.Commands;
var CordovaHelper = tacoUtility.CordovaHelper;
var logger = tacoUtility.Logger;
var loggerHelper = tacoUtility.LoggerHelper;
var telemetryHelper = tacoUtility.TelemetryHelper;
;
/**
* Remote
*
* handles "taco remote"
*/
var Remote = (function (_super) {
__extends(Remote, _super);
function Remote() {
var _this = this;
_super.apply(this, arguments);
this.subcommands = [
{
// taco remote remove <platform>
name: "remove",
run: function () { return _this.remove(); }
},
{
// taco remote list
name: "list",
run: function () { return _this.list(); }
},
{
// taco remote add [platform]
name: "add",
run: function () { return _this.add(); }
},
{
// taco remote [unknown]
name: "help",
run: function () { return _this.help(); },
canHandleArgs: function () { return true; }
}
];
this.name = "remote";
}
/**
* Generates the telemetry properties for the remote operation
*/
Remote.generateTelemetryProperties = function (subCommand, platform, isSecure) {
return CliTelemetryHelper.getCurrentProjectTelemetryProperties().then(function (telemetryProperties) {
telemetryProperties["subCommand"] = telemetryHelper.telemetryProperty(subCommand);
if (platform) {
telemetryProperties["platform"] = telemetryHelper.telemetryProperty(platform);
}
if (typeof (isSecure) !== "undefined") {
telemetryProperties["isSecure"] = telemetryHelper.telemetryProperty(isSecure || false);
}
return Q.resolve(telemetryProperties);
});
};
Remote.prototype.remove = function () {
if (this.data.remain.length < 2) {
throw errorHelper.get(TacoErrorCodes.CommandRemoteDeleteNeedsPlatform);
}
var platform = (this.data.remain[1]).toLowerCase();
var telemetryProperties = {};
return Settings.loadSettings().catch(function (err) {
// No settings or the settings were corrupted: start from scratch
return {};
}).then(function (settings) {
if (!(settings.remotePlatforms && platform in settings.remotePlatforms)) {
throw errorHelper.get(TacoErrorCodes.CommandRemoteDeletePlatformNotAdded, platform);
}
else {
delete settings.remotePlatforms[platform];
return Settings.saveSettings(settings);
}
}).then(function () {
logger.log(resources.getString("CommandRemoteRemoveSuccessful", platform));
}).then(function () {
return Remote.generateTelemetryProperties("remove", platform);
});
};
Remote.prototype.list = function () {
return Settings.loadSettings().catch(function (err) {
// No settings or the settings were corrupted: start from scratch
return {};
}).then(function (settings) {
var platforms = Object.keys(settings.remotePlatforms || {}).map(function (platform) {
var remote = settings.remotePlatforms && settings.remotePlatforms[platform];
var url = util.format("[%s] %s://%s:%d/%s", remote.secure ? resources.getString("CommandRemoteListSecured") : resources.getString("CommandRemoteListNotSecured"), remote.secure ? "https" : "http", remote.host, remote.port, remote.mountPoint);
return { name: platform, description: url };
});
if (platforms && platforms.length > 0) {
logger.log(resources.getString("CommandRemoteListPrelude"));
logger.logLine();
var header = { name: resources.getString("CommandRemoteListPlatformHeader"), description: resources.getString("CommandRemoteListDescriptionHeader") };
loggerHelper.logNameDescriptionTableWithHeader(header, platforms, null, null, " ");
}
else {
logger.log(resources.getString("CommandRemoteListNoPlatforms"));
}
}).then(function () {
return Remote.generateTelemetryProperties("list");
});
};
Remote.prototype.add = function () {
var platform = (this.data.remain[1] || "ios").toLowerCase();
var host = this.data.remain[2], port = this.data.remain[3], pin = this.data.remain[4]; //***
var remoteInfo;
return CordovaHelper.getSupportedPlatforms().then(function (supportedPlatforms) {
if (supportedPlatforms && !(platform in supportedPlatforms)) {
throw errorHelper.get(TacoErrorCodes.RemoteBuildUnsupportedPlatform, platform);
}
logger.log(resources.getString("CommandRemoteHeader"));
return Remote.queryUserForRemoteConfig(host, port, pin) //***
.then(Remote.acquireCertificateIfRequired)
.then(Remote.constructRemotePlatformSettings)
.then(function (remoteConnectionInfo) {
remoteInfo = remoteConnectionInfo;
return Q.resolve(remoteInfo);
})
.then(Remote.saveRemotePlatformSettings.bind(Remote, platform))
.then(function () {
logger.log(resources.getString("CommandRemoteSettingsStored", Settings.settingsFile));
// Print the onboarding experience
logger.log(resources.getString("OnboardingExperienceTitle"));
loggerHelper.logList([
"HowToUseCommandBuildPlatform",
"HowToUseCommandEmulatePlatform",
"HowToUseCommandRunPlatform"].map(function (msg) { return resources.getString(msg); }));
["",
"HowToUseCommandHelp",
"HowToUseCommandDocs"].forEach(function (msg) { return logger.log(resources.getString(msg)); });
});
}).then(function () {
return Remote.generateTelemetryProperties("add", platform, remoteInfo.secure);
});
};
Remote.queryUserForRemoteConfig = function (hostAnswer, portAnswer, pinAnswer) {
var hostPromise = Q.defer();
var portPromise = Q.defer();
var pinPromise = Q.defer();
//***var cliSession: ICliSession = Remote.cliSession ? Remote.cliSession : readline.createInterface({ input: process.stdin, output: process.stdout });
// Query the user for the host, port, and PIN, but don't keep asking questions if they input a known-invalid argument
//***cliSession.question(resources.getString("CommandRemoteQueryHost"), function (hostAnswer: string): void {
hostPromise.resolve({ host: hostAnswer });
//***});
hostPromise.promise.done(function (host) {
//***cliSession.question(resources.getString("CommandRemoteQueryPort"), function (portAnswer: string): void {
var port = parseInt(portAnswer, 10);
if (port > 0 && port < 65536) {
// Port looks valid
portPromise.resolve({ host: host.host, port: port });
}
else {
portPromise.reject(errorHelper.get(TacoErrorCodes.CommandRemoteInvalidPort, portAnswer));
}
//***});
}, function (err) {
portPromise.reject(err);
});
portPromise.promise.done(function (hostAndPort) {
//***cliSession.question(resources.getString("CommandRemoteQueryPin"), function (pinAnswer: string): void {
var pin = parseInt(pinAnswer, 10);
if (pinAnswer && !Remote.pinIsValid(pin)) {
// A pin was provided but it is invalid
pinPromise.reject(errorHelper.get(TacoErrorCodes.CommandRemoteInvalidPin, pinAnswer));
}
else {
pinPromise.resolve({ host: hostAndPort.host, port: hostAndPort.port, pin: pin });
}
//***});
}, function (err) {
pinPromise.reject(err);
});
return pinPromise.promise.finally(function () {
// Make sure to close the session regardless of error conditions otherwise the node process won't terminate.
//***cliSession.close();
});
};
Remote.pinIsValid = function (pin) {
return pin && pin >= 100000 && pin <= 999999;
};
Remote.acquireCertificateIfRequired = function (hostPortAndPin) {
if (hostPortAndPin.pin) {
// Secure connection: try to acquire a cert and store it in the windows cert store
var certificateUrl = util.format("https://%s:%d/certs/%d", hostPortAndPin.host, hostPortAndPin.port, hostPortAndPin.pin);
var deferred = Q.defer();
// Note: we set strictSSL to be false here because we don't yet know who the server is. We are vulnerable to a MITM attack in this first instance here
request.get({ uri: certificateUrl, strictSSL: false, encoding: null, timeout: Remote.HTTP_TIMEOUT_IN_MS }, function (error, response, body) {
if (error) {
// Error contacting the build server
deferred.reject(Remote.getFriendlyHttpError(error, hostPortAndPin.host, hostPortAndPin.port, certificateUrl, !!hostPortAndPin.pin));
}
else {
if (response.statusCode !== 200) {
// Invalid PIN specified
deferred.reject(errorHelper.get(TacoErrorCodes.CommandRemoteRejectedPin));
}
else {
ConnectionSecurityHelper.saveCertificate(body, hostPortAndPin.host).then(function (certName) {
deferred.resolve(certName.trim());
}, function (err) {
deferred.reject(err);
});
}
}
});
return deferred.promise.then(function (certName) {
return { host: hostPortAndPin.host, port: hostPortAndPin.port, certName: certName, secure: true };
});
}
return Q({ host: hostPortAndPin.host, port: hostPortAndPin.port, secure: false });
};
Remote.findRemoteMountPath = function (hostPortAndCert) {
var mountDiscoveryUrl = util.format("http%s://%s:%d/modules/%s", hostPortAndCert.certName ? "s" : "", hostPortAndCert.host, hostPortAndCert.port, "taco-remote");
return ConnectionSecurityHelper.getAgent(hostPortAndCert).then(function (agent) {
// TODO: Remove the casting once we've get some complete/up-to-date .d.ts files. See https://github.com/Microsoft/TACO/issues/18
var options = {
url: mountDiscoveryUrl,
agent: agent,
timeout: Remote.HTTP_TIMEOUT_IN_MS
};
var deferred = Q.defer();
request.get(options, function (error, response, body) {
if (error) {
deferred.reject(Remote.getFriendlyHttpError(error, hostPortAndCert.host, hostPortAndCert.port, mountDiscoveryUrl, !!hostPortAndCert.certName));
}
else if (response.statusCode !== 200) {
deferred.reject(errorHelper.get(TacoErrorCodes.CommandRemoteCantFindRemoteMount, mountDiscoveryUrl));
}
else {
deferred.resolve(body);
}
});
return deferred.promise;
});
};
Remote.saveRemotePlatformSettings = function (platform, data) {
return Settings.loadSettings().catch(function (err) {
// No settings or the settings were corrupted: start from scratch
return {};
}).then(function (settings) {
if (!settings.remotePlatforms) {
settings.remotePlatforms = {};
}
settings.remotePlatforms[platform] = data;
return Settings.saveSettings(settings);
});
};
Remote.constructRemotePlatformSettings = function (hostPortAndCert) {
return Remote.findRemoteMountPath(hostPortAndCert).then(function (mountPoint) {
var setting = {
host: hostPortAndCert.host,
port: hostPortAndCert.port,
secure: hostPortAndCert.certName ? true : false,
mountPoint: mountPoint
};
if (hostPortAndCert.certName) {
setting.certName = hostPortAndCert.certName;
}
return setting;
});
};
Remote.getFriendlyHttpError = function (error, host, port, url, secure) {
if (!error.code) {
return errorHelper.wrap(TacoErrorCodes.ErrorHttpGet, error, url);
}
else if (error.code.indexOf("CERT_") !== -1) {
return errorHelper.get(TacoErrorCodes.InvalidRemoteBuildClientCert);
}
else if (error.code === "ECONNREFUSED") {
return errorHelper.get(TacoErrorCodes.CommandRemoteConnectionRefused, util.format("%s://%s:%s", secure ? "https" : "http", host, port));
}
else if (error.code === "ENOTFOUND") {
return errorHelper.get(TacoErrorCodes.CommandRemoteNotfound, host);
}
else if (error.code === "ETIMEDOUT") {
return errorHelper.get(TacoErrorCodes.CommandRemoteTimedout, host, port);
}
else if (error.code === "ECONNRESET") {
if (!secure) {
return errorHelper.get(TacoErrorCodes.RemoteBuildNonSslConnectionReset, url);
}
else {
return errorHelper.get(TacoErrorCodes.RemoteBuildSslConnectionReset, url);
}
}
else {
return errorHelper.wrap(TacoErrorCodes.ErrorHttpGet, error, url);
}
};
Remote.prototype.help = function () {
return new HelpModule().run(["remote"]).then(function () {
return Q({});
});
};
Remote.prototype.parseArgs = function (args) {
return tacoUtility.ArgsHelper.parseArguments(Remote.KNOWN_OPTIONS, Remote.SHORT_HANDS, args, 0);
};
/**
* Mockable CLI for test purposes
*/
Remote.cliSession = null;
Remote.HTTP_TIMEOUT_IN_MS = 20000;
Remote.KNOWN_OPTIONS = {};
Remote.SHORT_HANDS = {};
return Remote;
}(commands.TacoCommandBase));
module.exports = Remote;
//# sourceMappingURL=remote.js.map