UNPKG

@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
// 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" /> "use strict"; 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