UNPKG

@hkvstore/taco-cli

Version:

taco-cli is a command-line interface for rapid Apache Cordova development (forked from Microsoft taco-cli)

627 lines (625 loc) 33.1 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/node.d.ts" /> /// <reference path="../../../typings/request.d.ts" /> /// <reference path="../../../typings/fstream.d.ts" /> /// <reference path="../../../typings/tar.d.ts" /> /// <reference path="../../../typings/remoteBuild.d.ts" /> /// <reference path="../../../typings/tacoRemote.d.ts" /> /// <reference path="../../../typings/tacoUtils.d.ts" /> /// <reference path="../../../typings/adm-zip.d.ts" /> "use strict"; var AdmZip = require("adm-zip"); var fs = require("fs"); var fstream = require("fstream"); var path = require("path"); var Q = require("q"); var querystring = require("querystring"); var request = require("request"); var tar = require("tar"); var url = require("url"); var util = require("util"); var zlib = require("zlib"); var ConnectionSecurityHelper = require("./connectionSecurityHelper"); var resources = require("../../resources/resourceManager"); var Settings = require("../utils/settings"); var TacoErrorCodes = require("../tacoErrorCodes"); var errorHelper = require("../tacoErrorHelper"); var tacoUtils = require("taco-utils"); var BuildInfo = tacoUtils.BuildInfo; var CountStream = tacoUtils.CountStream; var LogLevel = tacoUtils.LogLevel; var Logger = tacoUtils.Logger; var GlobalConfig = tacoUtils.TacoGlobalConfig; var NewlineNormalizerStream = tacoUtils.NewlineNormalizerStream; var UtilHelper = tacoUtils.UtilHelper; var telemetryProperty = tacoUtils.TelemetryHelper.telemetryProperty; var RemoteBuildClientHelper = (function () { function RemoteBuildClientHelper() { } /** * Submit a build to a remote build server, poll for completion, print the build log when the build completes, and if building for a device then download the end result */ RemoteBuildClientHelper.build = function (settings, telemetryProperties) { var outputBuildDir = settings.platformConfigurationBldDir; var buildInfoFilePath = settings.buildInfoFilePath; if (!RemoteBuildClientHelper.isValidBuildServerUrl(settings.buildServerUrl)) { throw errorHelper.get(TacoErrorCodes.InvalidRemoteBuildUrl, settings.buildServerUrl); } var changeTimeFile = path.join(settings.platformConfigurationBldDir, "lastChangeTime.json"); var lastChangeTime = {}; var buildLog = RemoteBuildClientHelper.buildLogPath(settings); var promise = RemoteBuildClientHelper.checkForBuildOnServer(settings, buildInfoFilePath) .then(function (buildInfo) { settings.incrementalBuild = buildInfo ? buildInfo.buildNumber : null; Logger.log(resources.getString("IncrementalBuild", !!settings.incrementalBuild)); if (!settings.incrementalBuild) { try { fs.unlinkSync(changeTimeFile); } catch (e) { } } // Clear build log if it exists if (fs.existsSync(buildLog)) { try { fs.unlinkSync(buildLog); } catch (e) { } } var platformJsonFile = path.join(settings.projectSourceDir, "plugins", util.format("remote_%s.json", settings.platform)); if (fs.existsSync(platformJsonFile)) { fs.unlinkSync(platformJsonFile); } }) .then(function () { return RemoteBuildClientHelper.appAsTgzStream(settings, lastChangeTime, changeTimeFile, telemetryProperties); }) .then(function (tgz) { return RemoteBuildClientHelper.submitBuildRequestToServer(settings, tgz); }) .then(function (buildingUrl) { return RemoteBuildClientHelper.pollForBuildComplete(settings, buildingUrl, RemoteBuildClientHelper.PING_INTERVAL, 0); }) .then(function (result) { if (result.buildNumber) { Logger.log(resources.getString("RemoteBuildSuccessful")); return RemoteBuildClientHelper.logBuildOutput(result, settings); } }, function (err) { if (err.buildInfo) { // If we successfully submitted a build but the remote build server reports an error about the build, then grab the // build log from the remote machine before propagating the reported failure return RemoteBuildClientHelper.logBuildOutput(err.buildInfo, settings) .then(function (buildInfo) { throw errorHelper.wrap(TacoErrorCodes.RemoteBuildUnsuccessful, err); }); } throw errorHelper.wrap(TacoErrorCodes.RemoteBuildUnsuccessful, err); }) .then(function (buildInfo) { return RemoteBuildClientHelper.downloadRemotePluginFile(buildInfo, settings, path.join(settings.projectSourceDir, "plugins")); }) .then(function (buildInfo) { UtilHelper.createDirectoryIfNecessary(outputBuildDir); fs.writeFileSync(buildInfoFilePath, JSON.stringify(buildInfo)); fs.writeFileSync(changeTimeFile, JSON.stringify(lastChangeTime)); return buildInfo; }); // If build is for a device we will download the build as a zip with the build output in it if (RemoteBuildClientHelper.isDeviceBuild(settings)) { promise = promise .then(function (buildInfo) { return RemoteBuildClientHelper.downloadBuild(buildInfo, settings, outputBuildDir) .then(function (zipFile) { return RemoteBuildClientHelper.unzipBuildFiles(zipFile, outputBuildDir); }).then(function () { return buildInfo; }); }); } return promise; }; RemoteBuildClientHelper.run = function (buildInfo, serverSettings) { var buildUrlBase = Settings.getRemoteServerUrl(serverSettings) + "/build/" + buildInfo.buildNumber; var httpSettings = { language: buildInfo.buildLang, agent: ConnectionSecurityHelper.getAgent(serverSettings) }; return RemoteBuildClientHelper.httpOptions(buildUrlBase + "/deploy", httpSettings).then(RemoteBuildClientHelper.promiseForHttpGet) .then(function () { return RemoteBuildClientHelper.httpOptions(buildUrlBase + "/run", httpSettings).then(RemoteBuildClientHelper.promiseForHttpGet); }).then(function (responseAndBody) { return BuildInfo.createNewBuildInfoFromDataObject(JSON.parse(responseAndBody.body)); }); }; RemoteBuildClientHelper.emulate = function (buildInfo, serverSettings, target) { var buildUrlBase = Settings.getRemoteServerUrl(serverSettings) + "/build/" + buildInfo.buildNumber; var httpSettings = { language: buildInfo.buildLang, agent: ConnectionSecurityHelper.getAgent(serverSettings) }; return RemoteBuildClientHelper.httpOptions(buildUrlBase + "/emulate?" + querystring.stringify({ target: target }), httpSettings).then(RemoteBuildClientHelper.promiseForHttpGet) .then(function (responseAndBody) { return BuildInfo.createNewBuildInfoFromDataObject(JSON.parse(responseAndBody.body)); }); }; RemoteBuildClientHelper.debug = function (buildInfo, serverSettings) { var buildUrlBase = Settings.getRemoteServerUrl(serverSettings) + "/build/" + buildInfo.buildNumber; var httpSettings = { language: buildInfo.buildLang, agent: ConnectionSecurityHelper.getAgent(serverSettings) }; return RemoteBuildClientHelper.httpOptions(buildUrlBase + "/debug", httpSettings).then(RemoteBuildClientHelper.promiseForHttpGet).then(function (responseAndBody) { return BuildInfo.createNewBuildInfoFromDataObject(JSON.parse(responseAndBody.body)); }); }; /** * Try to find whether the server has a previous build of this project */ RemoteBuildClientHelper.checkForBuildOnServer = function (settings, buildInfoFilePath) { var deferred = Q.defer(); // If we don't have a buildInfo.json file then we cannot query the server. if (!fs.existsSync(buildInfoFilePath)) { deferred.resolve(null); return deferred.promise; } var buildInfo; try { var fileContents = fs.readFileSync(buildInfoFilePath).toString(); buildInfo = JSON.parse(fileContents); } catch (e) { deferred.resolve(null); return deferred.promise; } var buildNumber = buildInfo.buildNumber; var serverUrl = settings.buildServerUrl; var buildUrl = serverUrl + "/build/" + buildNumber; // If we do have a buildInfo.json, check whether the server still has that build number around return RemoteBuildClientHelper.httpOptions(buildUrl, settings).then(function (requestOptions) { request.get((requestOptions), function (error, response, body) { if (error) { deferred.reject(RemoteBuildClientHelper.errorFromRemoteBuildServer(serverUrl, error, TacoErrorCodes.RemoteBuildError)); } else if (response.statusCode === 200) { deferred.resolve(buildInfo); } else { // Build was bad: remove outdated buildInfo file try { fs.unlinkSync(buildInfoFilePath); } catch (e) { } deferred.resolve(null); } }); return deferred.promise; }); }; RemoteBuildClientHelper.isValidBuildServerUrl = function (serverUrl) { return (serverUrl.indexOf("http://") === 0 || serverUrl.indexOf("https://") === 0); }; RemoteBuildClientHelper.httpOptions = function (url, settings) { return settings.agent.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 return { url: url, headers: { "Accept-Language": settings.language }, agent: agent }; }).finally(function () { // Add a slight delay to work around node race condition to do with certificates. // See https://github.com/nodejs/io.js/issues/712 for more discussion return Q.delay(1); }); }; /** * Convert errors from error codes to localizable strings */ RemoteBuildClientHelper.errorFromRemoteBuildServer = function (serverUrl, requestError, fallbackErrorCode) { if (!requestError.code) { return errorHelper.wrap(fallbackErrorCode, requestError, serverUrl); } else if (requestError.code.indexOf("CERT_") !== -1) { return errorHelper.get(TacoErrorCodes.InvalidRemoteBuildClientCert); } else if (serverUrl.indexOf("https://") === 0 && requestError.code === "ECONNRESET") { return errorHelper.get(TacoErrorCodes.RemoteBuildSslConnectionReset, serverUrl); } else if (serverUrl.indexOf("http://") === 0 && requestError.code === "ECONNRESET") { return errorHelper.get(TacoErrorCodes.RemoteBuildNonSslConnectionReset, serverUrl); } else if (requestError.code === "ENOTFOUND") { // Host unreachable regardless of whether http or https return errorHelper.get(TacoErrorCodes.RemoteBuildHostNotFound, serverUrl); } else if (requestError.code === "ECONNREFUSED") { // Host reachable but connection not established (e.g. Server not running) return errorHelper.get(TacoErrorCodes.RemoteBuildNoConnection, serverUrl); } return errorHelper.wrap(fallbackErrorCode, requestError, serverUrl); }; /** * Create a gzipped tarball of the cordova project ready to submit to the server. */ RemoteBuildClientHelper.appAsTgzStream = function (settings, lastChangeTime, changeTimeFile, telemetryProperties) { var platform = settings.platform; var projectSourceDir = settings.projectSourceDir; var changeListFile = path.join(projectSourceDir, "changeList.json"); var newChangeTime = {}; var isIncremental = false; try { var json = JSON.parse(fs.readFileSync(changeTimeFile, "utf8")); Object.keys(json).forEach(function (file) { lastChangeTime[file] = json[file]; }); isIncremental = true; } catch (e) { } telemetryProperties["remoteBuild." + platform + ".wasIncremental"] = telemetryProperty(isIncremental, /*isPii*/ false); var upToDateFiles = []; var filterForChanges = function (reader, props) { var shouldInclude = RemoteBuildClientHelper.filterForRemote(settings, reader, lastChangeTime); var appRelPath = path.relative(settings.projectSourceDir, reader.path); if (shouldInclude) { var newmtime = reader.props.mtime.getTime(); if (reader.props.type === "Directory" || !lastChangeTime[appRelPath] || lastChangeTime[appRelPath] !== newmtime) { // If this is a directory, or a new file, or a file that has changed since we last looked, then include it newChangeTime[appRelPath] = newmtime; return true; } } // Either we did not want to include the file, or it has not changed if (appRelPath in lastChangeTime) { // It is a file that has not changed. Remember that, and do not include it. upToDateFiles.push(appRelPath); } return false; }; var property = telemetryProperty(0, /*isPii*/ false); telemetryProperties["remoteBuild." + platform + ".filesChangedCount"] = property; var filterForTar = function (reader, props) { var appRelPath = path.relative(settings.projectSourceDir, reader.path); var wasModifiedRecently = appRelPath === "changeList.json" || appRelPath in newChangeTime; if (wasModifiedRecently && reader.props.type !== "Directory") { property.value++; // We found another file that was modified } return wasModifiedRecently; }; var deferred = Q.defer(); // 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 firstPassReader = new fstream.Reader({ path: projectSourceDir, type: "Directory", filter: filterForChanges }); firstPassReader.on("close", function () { // We have now determined which files are new and which files are old. Construct changeList.json var previousFiles = Object.keys(lastChangeTime); var changeList = { deletedFiles: previousFiles.filter(function (file) { return !(file in newChangeTime) && upToDateFiles.indexOf(file) === -1; }) }; // Save the changeList.json file fs.writeFileSync(changeListFile, JSON.stringify(changeList)); // Save the new modification times changeList.deletedFiles.forEach(function (file) { // Stop tracking deleted files delete lastChangeTime[file]; }); Object.keys(newChangeTime).forEach(function (file) { // Update the modification time of changed and new files lastChangeTime[file] = newChangeTime[file]; }); deferred.resolve({}); }); firstPassReader.on("error", function (err) { deferred.reject(errorHelper.wrap(TacoErrorCodes.ErrorPatchCreation, err)); }); return deferred.promise.then(function () { // 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 projectSourceDirReader = new fstream.Reader({ path: projectSourceDir, type: "Directory", filter: filterForTar }); var tarProducingStream = CountStream.count(projectSourceDirReader.pipe(tar.Pack()), function (sz) { return telemetryProperties["remotebuild." + platform + ".projectSizeInBytes"] = telemetryProperty(sz, /*isPii*/ false); }); var tgzProducingStream = CountStream.count(tarProducingStream.pipe(zlib.createGzip()), function (sz) { return telemetryProperties["remotebuild." + platform + ".gzipedProjectSizeInBytes"] = telemetryProperty(sz, /*isPii*/ false); }); return tgzProducingStream; }); }; /** * Determine which files should be included in the tarball which is sent to the server. * We want to include all changed user data, and exclude all irrelevant metadata or data for different platforms */ RemoteBuildClientHelper.filterForRemote = function (settings, reader, lastChangeTime) { var appRelPath = path.relative(settings.projectSourceDir, reader.path); var exclusions = [ "remote", "platforms" // The /platforms folder is for locally installed platforms, and thus irrelevant to remote builds ]; if (exclusions.indexOf(appRelPath) !== -1) { return false; } // We want to exclude /merges/<x> if x is not the current platform // Similarly for the others here var otherPlatformExclusions = [ "merges", path.join("res", "screens"), path.join("res", "icons"), path.join("res", "cert"), path.join("res", "native") ]; var shouldBeExcluded = function (exclusion) { // If we are looking at <exclusion>\<basename> and basename is not the platform, then it should be excluded var checkFullPath = path.join(exclusion, reader.basename); return reader.basename !== settings.platform && !!appRelPath.match(new RegExp("^" + checkFullPath + "$")); }; if (appRelPath && otherPlatformExclusions.some(shouldBeExcluded)) { return false; } if (settings.incrementalBuild && appRelPath) { var stat = fs.statSync(reader.path); if (stat.isDirectory()) { // Consider all directories, since their contents may be modified return true; } if (appRelPath === "changeList.json") { // Always include changeList.json return true; } if (appRelPath in lastChangeTime && lastChangeTime[appRelPath] === stat.mtime.getTime()) { // If we know when the file was last modified, and it hasn't been changed since then, we must have sent this file to the server already. return false; } } return true; }; /* * Submit a new build request to the remote server, including the project to be compiled as a tarball attached to the POST */ RemoteBuildClientHelper.submitBuildRequestToServer = function (settings, appAsTgzStream) { var serverUrl = settings.buildServerUrl; var vcordova = settings.cordovaVersion; var cfg = settings.configuration ? settings.configuration.toLowerCase() : "release"; var cliVersion = require("../../package.json").version; var deferred = Q.defer(); var params = { command: "build", vcordova: vcordova, vcli: cliVersion, cfg: cfg, platform: settings.platform }; var buildOptions = []; if (RemoteBuildClientHelper.isDeviceBuild(settings)) { buildOptions.push("--device"); } if (settings.options) { buildOptions.concat(settings.options); } if (buildOptions.length > 0) { params["options"] = buildOptions.join(" "); } if (settings.incrementalBuild) { params["buildNumber"] = settings.incrementalBuild.toString(); } var buildUrl = serverUrl + "/build/tasks?" + querystring.stringify(params); Logger.log(resources.getString("SubmittingRemoteBuild", buildUrl)); appAsTgzStream.on("error", function (error) { deferred.reject(errorHelper.wrap(TacoErrorCodes.ErrorUploadingRemoteBuild, error, serverUrl)); }); return RemoteBuildClientHelper.httpOptions(buildUrl, settings).then(function (requestOptions) { appAsTgzStream.pipe(request.post(requestOptions, function (error, response, body) { if (error) { deferred.reject(RemoteBuildClientHelper.errorFromRemoteBuildServer(serverUrl, error, TacoErrorCodes.ErrorUploadingRemoteBuild)); } else if (response.statusCode === 400) { // Build server sends back http 400 for invalid submissions with response like this. We will fail the build with a formatted message. // {"status": "Invalid build submission", "errors": ["The requested cordova version 3.5.0-0.2.4 is not supported by this build manager. Installed cordova version is 3.4.1-0.1.0"]} var errorsJson = JSON.parse(body); deferred.reject(errorHelper.get(TacoErrorCodes.InvalidBuildSubmission400, errorsJson.status, errorsJson.errors.toString())); } else if (response.statusCode === 202) { // Expect http 202 for a valid submission which is "Accepted" with a content-location to the Url to check for build status if (GlobalConfig.logLevel === LogLevel.Diagnostic) { Logger.log(resources.getString("NewRemoteBuildInfo", body)); } // The server responds with a content-location header indicating where the newly submitted build is now located // However the URL in that header may have a different host to what we expect, especially in the case of proxies // or ipv6 addresses. To fix this, we'll ignore the host part of the URL and replace it with the host we want to // communicate with ourselves. var reportedBuildUrl = url.parse(response.headers["content-location"]); var buildServerUrl = url.parse(buildUrl); reportedBuildUrl.host = buildServerUrl.host; deferred.resolve(url.format(reportedBuildUrl)); } else { deferred.reject(errorHelper.get(TacoErrorCodes.ErrorDuringRemoteBuildSubmission, body)); } })); return deferred.promise; }); }; RemoteBuildClientHelper.isDeviceBuild = function (settings) { return settings.buildTarget && (settings.buildTarget.toLowerCase() === "device"); }; /* * Status progression: uploaded -> extracted -> building -> [complete|invalid|error] -> downloaded [if a device targeted build] */ RemoteBuildClientHelper.pollForBuildComplete = function (settings, buildingUrl, interval, attempts, logOffset) { var thisAttempt = attempts + 1; Logger.log(resources.getString("CheckingRemoteBuildStatus", (new Date()).toLocaleTimeString(), buildingUrl, thisAttempt)); return RemoteBuildClientHelper.httpOptions(buildingUrl, settings).then(RemoteBuildClientHelper.promiseForHttpGet) .then(function (responseAndBody) { if (responseAndBody.response.statusCode !== 200) { throw errorHelper.get(TacoErrorCodes.RemoteBuildStatusPollFailed, responseAndBody.response.statusCode, responseAndBody.body); } var buildInfo = BuildInfo.createNewBuildInfoFromDataObject(JSON.parse(responseAndBody.body)); Logger.log(buildInfo.status + " - " + buildInfo.message); buildInfo["logOffset"] = logOffset || 0; if (buildInfo.status === BuildInfo.COMPLETE) { return Q(buildInfo); } else if (buildInfo.status === BuildInfo.INVALID) { throw errorHelper.get(TacoErrorCodes.InvalidRemoteBuild, buildInfo.message); } else if (buildInfo.status === BuildInfo.ERROR) { var err = errorHelper.get(TacoErrorCodes.RemoteBuildError, buildInfo.message); err.buildInfo = buildInfo; throw err; } return RemoteBuildClientHelper.logBuildOutput(buildInfo, settings) .then(function (loggedBuildInfo) { return Q.all([Q.delay(interval), RemoteBuildClientHelper.checkQueuePosition(settings, loggedBuildInfo)]) .then(function () { return RemoteBuildClientHelper.pollForBuildComplete(settings, buildingUrl, interval, thisAttempt, loggedBuildInfo["logOffset"]); }); }); }); }; RemoteBuildClientHelper.checkQueuePosition = function (settings, buildInfo) { if (buildInfo.status === BuildInfo.BUILDING) { return Q({}); } var queueUrl = settings.buildServerUrl + "/build/tasks"; return RemoteBuildClientHelper.httpOptions(queueUrl, settings).then(RemoteBuildClientHelper.promiseForHttpGet) .then(function (responseAndBody) { try { var serverInfo = JSON.parse(responseAndBody.body); var queueIndex = serverInfo.queuedBuilds.map(function (bi) { return bi.buildNumber === buildInfo.buildNumber; }).indexOf(true); if (queueIndex >= 0) { Logger.log(resources.getString("RemoteBuildQueued", queueIndex + 1)); } } catch (e) { Logger.log(e); } return Q({}); }); }; /* * Download the finished log of a build, regardless of whether it succeeded or failed */ RemoteBuildClientHelper.logBuildOutput = function (buildInfo, settings) { var serverUrl = settings.buildServerUrl; var deferred = Q.defer(); var offset = buildInfo["logOffset"] || 0; var logFlags = offset > 0 ? "r+" : "w"; var buildNumber = buildInfo.buildNumber; var downloadUrl = util.format("%s/build/tasks/%d/log?offset=%d", serverUrl, buildNumber, offset); return RemoteBuildClientHelper.httpOptions(downloadUrl, settings).then(request).then(function (req) { var logPath = RemoteBuildClientHelper.buildLogPath(settings); UtilHelper.createDirectoryIfNecessary(settings.platformConfigurationBldDir); var endOfFile = 0; if (offset > 0 && fs.existsSync(logPath)) { var logFileStat = fs.statSync(logPath); endOfFile = logFileStat.size; } // 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 logStream = fs.createWriteStream(logPath, { start: endOfFile, flags: logFlags }); var countStream = new CountStream(); var newlineNormalizerStream = new NewlineNormalizerStream(); logStream.on("finish", function () { Logger.log(resources.getString("BuildLogWrittenTo", logPath)); deferred.resolve(buildInfo); }); req.on("end", function () { buildInfo["logOffset"] = offset + countStream.count; }).pipe(countStream).pipe(newlineNormalizerStream); // Output to build log newlineNormalizerStream.pipe(logStream); // Output to stdout newlineNormalizerStream.pipe(process.stdout); return deferred.promise; }); }; /* * Download the <platform>.json file that tracks the installed plugins on the remote machine. * This file is used by vs-mda/vs-tac, but it is also good for checking what plugins are actually installed remotely. */ RemoteBuildClientHelper.downloadRemotePluginFile = function (buildInfo, settings, toDir) { var serverUrl = settings.buildServerUrl; var deferred = Q.defer(); var buildNumber = buildInfo.buildNumber; var downloadUrl = util.format("%s/files/%d/cordovaApp/plugins/%s.json", serverUrl, buildNumber, settings.platform); UtilHelper.createDirectoryIfNecessary(toDir); var remotePluginStream = fs.createWriteStream(path.join(toDir, util.format("remote_%s.json", settings.platform))); remotePluginStream.on("finish", function () { deferred.resolve(buildInfo); }); return RemoteBuildClientHelper.httpOptions(downloadUrl, settings).then(request).invoke("pipe", remotePluginStream).then(function () { return deferred.promise; }); }; /* * Download a completed build from the remote server as a zip */ RemoteBuildClientHelper.downloadBuild = function (buildInfo, settings, toDir) { var serverUrl = settings.buildServerUrl; UtilHelper.createDirectoryIfNecessary(toDir); var deferred = Q.defer(); var buildNumber = buildInfo.buildNumber; var downloadUrl = serverUrl + "/build/" + buildNumber + "/download"; Logger.log(resources.getString("DownloadingRemoteBuild", downloadUrl, toDir)); var zipFile = path.join(toDir, buildNumber + ".zip"); var outZip = fs.createWriteStream(zipFile); outZip.on("error", function (error) { deferred.reject(errorHelper.wrap(TacoErrorCodes.ErrorDownloadingRemoteBuild, error, toDir)); }); outZip.on("finish", function () { Logger.log(resources.getString("DownloadedRemoteBuild", toDir)); deferred.resolve(zipFile); }); return RemoteBuildClientHelper.httpOptions(downloadUrl, settings).then(request).invoke("pipe", outZip).then(function () { return deferred.promise; }); }; /* * Unzip the downloaded build */ RemoteBuildClientHelper.unzipBuildFiles = function (zipFile, toDir) { Logger.log(resources.getString("ExtractingRemoteBuild", toDir)); UtilHelper.createDirectoryIfNecessary(toDir); var deferred = Q.defer(); try { var zip = new AdmZip(zipFile); zip.extractAllTo(toDir, true); Logger.log(resources.getString("DoneExtractingRemoteBuild", toDir)); fs.unlink(zipFile, function (err) { if (err) { Logger.log(resources.getString("FailedToDeleteRemoteZip", zipFile)); } deferred.resolve({}); }); } catch (error) { deferred.reject(errorHelper.wrap(TacoErrorCodes.ErrorDownloadingRemoteBuild, error, toDir)); } return deferred.promise; }; /* * perform a HTTP GET request and return a promise which is resolved with the response or rejected with an error */ RemoteBuildClientHelper.promiseForHttpGet = function (urlOptions) { var deferred = Q.defer(); request.get(urlOptions, function (error, response, body) { if (error) { deferred.reject(errorHelper.wrap(TacoErrorCodes.ErrorHttpGet, error, urlOptions.url)); } else { if (response.statusCode !== 200 && response.statusCode !== 202) { // see if the response is JSON with a message try { var bodyJson = JSON.parse(response.body); if (bodyJson.message) { deferred.reject(errorHelper.get(TacoErrorCodes.HttpGetFailed, response.statusCode, bodyJson.message)); return; } } catch (e) { } deferred.reject(errorHelper.get(TacoErrorCodes.HttpGetFailed, response.statusCode, response.body)); } deferred.resolve({ response: response, body: body }); } }); return deferred.promise; }; /** * Returns the path to the local build log. */ RemoteBuildClientHelper.buildLogPath = function (settings) { return path.join(settings.platformConfigurationBldDir, "build.log"); }; RemoteBuildClientHelper.PING_INTERVAL = 5000; return RemoteBuildClientHelper; }()); module.exports = RemoteBuildClientHelper; //# sourceMappingURL=remoteBuildClientHelper.js.map