diff --git a/gulpfile.js b/gulpfile.js index 6362301c..57740491 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,7 +9,6 @@ const ts = require("gulp-typescript"); const log = require("fancy-log"); const os = require("os"); const path = require("path"); -const Q = require("q"); const typescript = require("typescript"); const del = require("del"); const nls = require("vscode-nls-dev"); @@ -26,32 +25,32 @@ function executeCordovaCommand(cwd, command) { } function executeCommand(cwd, commandToExecute) { - var deferred = Q.defer(); - var process = child_process.exec( - commandToExecute, - { cwd: cwd }, - (error, stdout, stderr) => { - if (error) { - console.error("An error occurred: " + error); - return; + return new Promise((resolve, reject) => { + let process = child_process.exec( + commandToExecute, + { cwd: cwd }, + (error, stdout, stderr) => { + if (error) { + console.error("An error occurred: " + error); + return; + } + console.log(stderr); + console.log(stdout); } - console.log(stderr); - console.log(stdout); - } - ); - process.on("error", function (err) { - console.log("Command failed with error: " + err); - deferred.reject(err); - }); - process.stdout.on("close", function (exitCode) { - if (exitCode) { - console.log("Command failed with exit code " + exitCode); - deferred.reject(exitCode); - } else { - deferred.resolve({}); - } + ); + process.on("error", function (err) { + console.log("Command failed with error: " + err); + reject(err); + }); + process.stdout.on("close", function (exitCode) { + if (exitCode) { + console.log("Command failed with exit code " + exitCode); + reject(exitCode); + } else { + resolve({}); + } + }); }); - return deferred.promise; } var sources = ["src/**/*.ts"]; @@ -341,7 +340,7 @@ gulp.task("release", function () { fs.mkdirSync(backupFolder); } - return Q({}) + return Promise.resolve() .then(function () { /* back up LICENSE.txt, ThirdPartyNotices.txt, README.md */ console.log("Backing up license files to " + backupFolder + "..."); @@ -393,6 +392,12 @@ gulp.task("clean-test", function () { var pathsToDelete = [ "test/**/*.js", "test/**/*.js.map", + "test/resources/testCordovaProject/.vscode", + "test/resources/testCordovaProject/node_modules", + "test/resources/testCordovaProject/plugins", + "test/resources/testCordovaProject/typings", + "test/resources/testCordovaProject/jsconfig.json", + "test/resources/testCordovaProject/package-lock.json", "!test/resources/testCordovaProject/**/*.js", "!test/resources/testCordovaProject/**/*.js.map", ]; diff --git a/package-lock.json b/package-lock.json index 23e1fff8..64a2680d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -329,12 +329,6 @@ "integrity": "sha512-dJLCxrpQmgyxYGcl0Ae9MTsQgI22qHHcGFj/8VKu7McJA5zQpnuGjoksnxbo1JxSjW/Nahnl13W8MYZf01CZHA==", "dev": true }, - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", - "dev": true - }, "@types/rimraf": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.4.tgz", @@ -2070,9 +2064,9 @@ } }, "cordova-simulate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cordova-simulate/-/cordova-simulate-1.0.1.tgz", - "integrity": "sha512-rjOloq45DLgsICkBFMpxHMw7i/jb9C7sDarnfkTskHL0s5xXDZoWE88YtiWyi9NrV82j6VyhXBarrbRffMkLkQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cordova-simulate/-/cordova-simulate-1.1.0.tgz", + "integrity": "sha512-1hkQh86y093HuG4qAF4q68MFvR2OzbcXxNj6tFeY8XS760/tuORCyCB5OKdNNaUsSZx+mQuwpeaj3o9YwgNDmQ==", "dev": true, "requires": { "body-parser": "^1.19.0", @@ -2084,10 +2078,9 @@ "csp-parse": "^0.0.2", "glob": "^7.0.5", "minimist": "^1.2.5", - "q": "^1.4.1", "replacestream": "^4.0.0", "send-transform": "^0.15.1", - "socket.io": "^2.2.0", + "socket.io": "^2.4.1", "through2": "^2.0.0", "uuid": "^3.3.2" }, @@ -3489,9 +3482,9 @@ "dev": true }, "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz", + "integrity": "sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==", "dev": true }, "fastq": { @@ -3780,9 +3773,9 @@ } }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true }, "fragment-cache": { @@ -6015,12 +6008,6 @@ } } }, - "mockery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", - "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=", - "dev": true - }, "module-deps": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", @@ -6818,12 +6805,12 @@ } }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, @@ -6893,11 +6880,6 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -7821,6 +7803,12 @@ "socket.io-parser": "~3.4.0" }, "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -7830,95 +7818,12 @@ "ms": "^2.1.1" } }, - "engine.io-client": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz", - "integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==", - "dev": true, - "requires": { - "component-emitter": "~1.3.0", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.2.0", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "ws": "~7.4.2", - "xmlhttprequest-ssl": "~1.6.2", - "yeast": "0.1.2" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "component-bind": "1.0.0", - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "engine.io-client": "~3.5.0", - "has-binary2": "~1.0.2", - "indexof": "0.0.1", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "socket.io-parser": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", - "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", - "dev": true, - "requires": { - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "isarray": "2.0.1" - } - } - } - }, "socket.io-parser": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", @@ -7928,21 +7833,7 @@ "component-emitter": "1.2.1", "debug": "~4.1.0", "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - } } - }, - "xmlhttprequest-ssl": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.2.tgz", - "integrity": "sha512-tYOaldF/0BLfKuoA39QMwD4j2m8lq4DIncqj1yuNELX4vz9+z/ieG/vwmctjJce+boFHXstqhWnHSxc4W8f4qg==", - "dev": true } } }, diff --git a/package.json b/package.json index 2c8202db..01b3578d 100644 --- a/package.json +++ b/package.json @@ -381,7 +381,7 @@ "devServerTimeout": { "type": "number", "description": "%cordova.properties.launch.devServerTimeout%", - "default": 20000 + "default": 60000 }, "simulatePort": { "type": "number", @@ -604,7 +604,6 @@ "execa": "^4.0.0", "ip": "^1.1.5", "plist": "^3.0.2", - "q": "^1.4.1", "semver": "5.1.0", "socket.io-client": "^2.4.0", "uuid": "^8.3.1", @@ -622,7 +621,6 @@ "@types/fancy-log": "^1.3.1", "@types/mocha": "^8.0.3", "@types/node": "^10.17.17", - "@types/q": "^1.0.3", "@types/rimraf": "^2.0.3", "@types/semver": "^5.5.0", "@types/sinon": "^9.0.7", @@ -631,7 +629,7 @@ "@types/vscode": "1.40.0", "@typescript-eslint/eslint-plugin": "^4.2.0", "@typescript-eslint/parser": "^4.2.0", - "cordova-simulate": "^1.0.1", + "cordova-simulate": "^1.1.0", "del": "^2.2.2", "devtools-protocol": "0.0.760817", "eslint": "^7.9.0", @@ -645,7 +643,6 @@ "gulp-typescript": "^5.0.1", "minimist": "^1.2.5", "mocha": "^8.1.3", - "mockery": "^1.4.0", "rimraf": "^2.7.1", "should": "^13.2.1", "sinon": "^9.1.0", diff --git a/src/common/customRequire.ts b/src/common/customRequire.ts index fb3a78fd..2c5b4173 100644 --- a/src/common/customRequire.ts +++ b/src/common/customRequire.ts @@ -11,7 +11,7 @@ try { : eval("require"); // tslint:disable-line:no-eval } catch { // Use a noop in case both `__non_webpack_require__` and `require` does not exist - customRequire = () => { }; // tslint:disable-line:no-empty + customRequire = () => {}; // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function } export default customRequire; \ No newline at end of file diff --git a/src/common/node/promise.ts b/src/common/node/promise.ts new file mode 100644 index 00000000..8f1e2964 --- /dev/null +++ b/src/common/node/promise.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +export class DeferredPromise { + private resolveSelf: (value: T | PromiseLike) => void; + private rejectSelf: (reason?: any) => void; + private _promise: Promise; + + constructor() { + this._promise = new Promise((resolve, reject) => { + this.resolveSelf = resolve; + this.rejectSelf = reject; + }); + } + + public resolve(val: T): void { + this.resolveSelf(val); + } + + public reject(reason: any): void { + this.rejectSelf(reason); + } + + public get promise(): Promise { + return this._promise; + } +} diff --git a/src/cordova.ts b/src/cordova.ts index c167f053..c4e39e0f 100644 --- a/src/cordova.ts +++ b/src/cordova.ts @@ -7,7 +7,6 @@ import { SimulateOptions } from "cordova-simulate"; import * as vscode from "vscode"; import { CordovaProjectHelper } from "./utils/cordovaProjectHelper"; import { CordovaCommandHelper } from "./utils/cordovaCommandHelper"; -import * as Q from "q"; import * as semver from "semver"; import { Telemetry } from "./utils/telemetry"; import { TelemetryHelper } from "./utils/telemetryHelper"; @@ -116,8 +115,7 @@ export function onFolderAdded(folder: vscode.WorkspaceFolder): void { }) .finally(() => { Telemetry.send(cordovaProjectTypeEvent); - }) - .done(); + }); // We need to update the type definitions added to the project // as and when plugins are added or removed. For this reason, @@ -201,9 +199,9 @@ export function onFolderAdded(folder: vscode.WorkspaceFolder): void { let jsconfigPath: string = path.join(workspaceRoot, JSCONFIG_FILENAME); let tsconfigPath: string = path.join(workspaceRoot, TSCONFIG_FILENAME); - Q.all([Q.nfcall(fs.exists, jsconfigPath), Q.nfcall(fs.exists, tsconfigPath)]).spread((jsExists: boolean, tsExists: boolean) => { + Promise.all([CordovaProjectHelper.exists(jsconfigPath), CordovaProjectHelper.exists(tsconfigPath)]).then(([jsExists, tsExists]) => { if (!jsExists && !tsExists) { - Q.nfcall(fs.writeFile, jsconfigPath, "{}").then(() => { + fs.promises.writeFile(jsconfigPath, "{}").then(() => { // Any open file must be reloaded to enable intellisense on them, so inform the user vscode.window.showInformationMessage("A 'jsconfig.json' file was created to enable IntelliSense. You may need to reload your open JS file(s)."); }); @@ -349,7 +347,7 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void { } /* Launches a simulate command and records telemetry for it */ -function launchSimulateCommand(cordovaProjectRoot: string, options: SimulateOptions): Q.Promise { +function launchSimulateCommand(cordovaProjectRoot: string, options: SimulateOptions): Promise { return TelemetryHelper.generate("simulateCommand", (generator) => { return TelemetryHelper.determineProjectTypes(cordovaProjectRoot) .then((projectType) => { @@ -394,10 +392,10 @@ function registerCordovaCommands(cordovaSessionManager: CordovaSessionManager): })); } -function selectProject(): Q.Promise { +function selectProject(): Promise { let keys = Object.keys(ProjectsStorage.projectsCache); if (keys.length > 1) { - return Q.Promise((resolve, reject) => { + return new Promise((resolve, reject) => { vscode.window.showQuickPick(keys) .then((selected) => { if (selected) { @@ -406,9 +404,9 @@ function selectProject(): Q.Promise { }, reject); }); } else if (keys.length === 1) { - return Q.resolve(ProjectsStorage.projectsCache[keys[0]]); + return Promise.resolve(ProjectsStorage.projectsCache[keys[0]]); } else { - return Q.reject(new Error(localize("NoCordovaProjectIsFound", "No Cordova project is found"))); + return Promise.reject(new Error(localize("NoCordovaProjectIsFound", "No Cordova project is found"))); } } diff --git a/src/debugger/cdp-proxy/CDPMessageHandlers/chromeCDPMessageHandler.ts b/src/debugger/cdp-proxy/CDPMessageHandlers/chromeCDPMessageHandler.ts index 2b6a4ca5..9d5b857f 100644 --- a/src/debugger/cdp-proxy/CDPMessageHandlers/chromeCDPMessageHandler.ts +++ b/src/debugger/cdp-proxy/CDPMessageHandlers/chromeCDPMessageHandler.ts @@ -63,7 +63,7 @@ export class ChromeCDPMessageHandler extends CDPMessageHandlerBase { }; } - public configureHandlerAccordingToProcessedAttachArgs(args: ICordovaAttachRequestArgs) { } + public configureHandlerAccordingToProcessedAttachArgs(args: ICordovaAttachRequestArgs): void { } private fixSourcemapLocation(reqParams: any): any { let absoluteSourcePath = this.sourcemapPathTransformer.getClientPathFromHttpBasedUrl(reqParams.url); diff --git a/src/debugger/cdp-proxy/CDPMessageHandlers/safariCDPMessageHandler.ts b/src/debugger/cdp-proxy/CDPMessageHandlers/safariCDPMessageHandler.ts index 50ab5299..7d2ae750 100644 --- a/src/debugger/cdp-proxy/CDPMessageHandlers/safariCDPMessageHandler.ts +++ b/src/debugger/cdp-proxy/CDPMessageHandlers/safariCDPMessageHandler.ts @@ -37,7 +37,7 @@ export class SafariCDPMessageHandler extends CDPMessageHandlerBase { } } - public configureHandlerAccordingToProcessedAttachArgs(args: ICordovaAttachRequestArgs) { + public configureHandlerAccordingToProcessedAttachArgs(args: ICordovaAttachRequestArgs): void { this.isTargeted = semver.gte(args.iOSVersion, "12.2.0"); if (!this.isIonicProject) { if (args.iOSAppPackagePath) { diff --git a/src/debugger/cdp-proxy/cordovaCDPProxy.ts b/src/debugger/cdp-proxy/cordovaCDPProxy.ts index c644a017..dccaa528 100644 --- a/src/debugger/cdp-proxy/cordovaCDPProxy.ts +++ b/src/debugger/cdp-proxy/cordovaCDPProxy.ts @@ -103,11 +103,11 @@ export class CordovaCDPProxy { this.applicationTargetPort = applicationTargetPort; } - public setBrowserInspectUri(browserInspectUri: string) { + public setBrowserInspectUri(browserInspectUri: string): void { this.browserInspectUri = browserInspectUri; } - public configureCDPMessageHandlerAccordingToProcessedAttachArgs(args: ICordovaAttachRequestArgs) { + public configureCDPMessageHandlerAccordingToProcessedAttachArgs(args: ICordovaAttachRequestArgs): void { if ( args.iOSVersion && !this.communicationPreparationsDone diff --git a/src/debugger/cordovaDebugSession.ts b/src/debugger/cordovaDebugSession.ts index 34faeb88..27d14244 100644 --- a/src/debugger/cordovaDebugSession.ts +++ b/src/debugger/cordovaDebugSession.ts @@ -3,7 +3,6 @@ import * as vscode from "vscode"; import * as child_process from "child_process"; -import * as Q from "q"; import * as path from "path"; import * as fs from "fs"; import * as url from "url"; @@ -25,6 +24,7 @@ import { execCommand, cordovaRunCommand, killChildProcess, cordovaStartCommand } import { CordovaCDPProxy } from "./cdp-proxy/cordovaCDPProxy"; import { SimulationInfo } from "../common/simulationInfo"; import { settingsHome } from "../utils/settingsHelper"; +import { DeferredPromise } from "../common/node/promise"; import { SimulateHelper } from "../utils/simulateHelper"; import { LogLevel } from "../utils/log/logHelper"; import { CordovaIosDeviceLauncher } from "./cordovaIosDeviceLauncher"; @@ -73,6 +73,8 @@ export enum PlatformType { Browser = "browser", } +export type DebugConsoleLogger = (message: string, error?: boolean | string) => void; + interface IOSProcessedParams { iOSVersion: string; iOSAppPackagePath: string; @@ -98,13 +100,13 @@ export class CordovaDebugSession extends LoggingDebugSession { private readonly pwaSessionName: PwaDebugType; private workspaceManager: CordovaWorkspaceManager; - private outputLogger: (message: string, error?: boolean | string) => void; + private outputLogger: DebugConsoleLogger; private adbPortForwardingInfo: { targetDevice: string, port: number }; private ionicLivereloadProcess: child_process.ChildProcess; private ionicDevServerUrls: string[]; private simulateDebugHost: SocketIOClient.Socket; private telemetryInitialized: boolean; - private attachedDeferred: Q.Deferred; + private attachedDeferred: DeferredPromise; private cdpProxyLogLevel: LogLevel; private jsDebugConfigAdapter: JsDebugConfigAdapter; private isSettingsInitialized: boolean; // used to prevent parameters reinitialization when attach is called from launch function @@ -157,7 +159,7 @@ export class CordovaDebugSession extends LoggingDebugSession { } this.sendEvent(new OutputEvent(message + newLine, category)); }; - this.attachedDeferred = Q.defer(); + this.attachedDeferred = new DeferredPromise(); } /** @@ -208,7 +210,7 @@ export class CordovaDebugSession extends LoggingDebugSession { TelemetryHelper.sendPluginsList(launchArgs.cwd, CordovaProjectHelper.getInstalledPlugins(launchArgs.cwd)); - return Q.all([ + return Promise.all([ TelemetryHelper.determineProjectTypes(launchArgs.cwd), this.workspaceManager.getRunArguments(launchArgs.cwd), this.workspaceManager.getCordovaExecutable(launchArgs.cwd), @@ -351,7 +353,7 @@ export class CordovaDebugSession extends LoggingDebugSession { .catch(err => reject(err)) ) .then(() => { - this.attachedDeferred.resolve(void 0); + this.attachedDeferred.resolve(); this.sendResponse(response); this.cordovaSession.setStatus(CordovaSessionStatus.Activated); }) @@ -452,7 +454,7 @@ export class CordovaDebugSession extends LoggingDebugSession { /** * Initializes telemetry. */ - private initializeTelemetry(projectRoot: string): Q.Promise { + private initializeTelemetry(projectRoot: string): Promise { if (!this.telemetryInitialized) { this.telemetryInitialized = true; let version = JSON.parse(fs.readFileSync(findFileInFolderHierarchy(__dirname, "package.json"), "utf-8")).version; @@ -462,11 +464,11 @@ export class CordovaDebugSession extends LoggingDebugSession { this.outputLogger(localize("CouldNotInitializeTelemetry", "Could not initialize telemetry. {0}", e.message || e.error || e.data || e)); }); } else { - return Q.resolve(void 0); + return Promise.resolve(); } } - private runAdbCommand(args, errorLogger): Q.Promise { + private runAdbCommand(args, errorLogger): Promise { const originalPath = process.env["PATH"]; if (process.env["ANDROID_HOME"]) { process.env["PATH"] += path.delimiter + path.join(process.env["ANDROID_HOME"], "platform-tools"); @@ -476,7 +478,7 @@ export class CordovaDebugSession extends LoggingDebugSession { }); } - private launchSimulate(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType, generator: TelemetryGenerator): Q.Promise { + private launchSimulate(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType, generator: TelemetryGenerator): Promise { let simulateTelemetryPropts: ISimulateTelemetryProperties = { platform: launchArgs.platform, target: launchArgs.target, @@ -507,7 +509,7 @@ export class CordovaDebugSession extends LoggingDebugSession { this.outputLogger(localize("CouldntoReadTheVisibleTextEditors", "Could not read the visible text editors. {0}", this.getErrorMessage(e))); }); - let launchSimulate = Q(void 0) + let launchSimulate = Promise.resolve() .then(() => { let simulateOptions = this.convertLaunchArgsToSimulateArgs(launchArgs); return this.workspaceManager.launchSimulateServer(launchArgs.cwd, simulateOptions, projectType); @@ -529,10 +531,10 @@ export class CordovaDebugSession extends LoggingDebugSession { throw e; }).then(() => void 0); - return Q.all([launchSimulate, getEditorsTelemetry]); + return Promise.all([launchSimulate, getEditorsTelemetry]); } - private changeSimulateViewport(data: simulate.ResizeViewportData): Q.Promise { + private changeSimulateViewport(data: simulate.ResizeViewportData): Promise { return this.attachedDeferred.promise .then(() => { if (this.cordovaCdpProxy) { @@ -546,30 +548,28 @@ export class CordovaDebugSession extends LoggingDebugSession { }); } - private connectSimulateDebugHost(simulateInfo: SimulationInfo): Q.Promise { + private connectSimulateDebugHost(simulateInfo: SimulationInfo): Promise { // Connect debug-host to cordova-simulate let viewportResizeFailMessage = localize("ViewportResizingFailed", "Viewport resizing failed. Please try again."); - let simulateDeferred: Q.Deferred = Q.defer(); - - let simulateConnectErrorHandler = (err: any): void => { - this.outputLogger("Error connecting to the simulated app."); - simulateDeferred.reject(err); - }; + return new Promise((resolve, reject) => { + let simulateConnectErrorHandler = (err: any): void => { + this.outputLogger("Error connecting to the simulated app."); + reject(err); + }; - this.simulateDebugHost = io.connect(simulateInfo.urlRoot); - this.simulateDebugHost.on("connect_error", simulateConnectErrorHandler); - this.simulateDebugHost.on("connect_timeout", simulateConnectErrorHandler); - this.simulateDebugHost.on("connect", () => { - this.simulateDebugHost.on("resize-viewport", (data: simulate.ResizeViewportData) => { - this.changeSimulateViewport(data).catch(() => { - this.outputLogger(viewportResizeFailMessage, true); - }).done(); + this.simulateDebugHost = io.connect(simulateInfo.urlRoot); + this.simulateDebugHost.on("connect_error", simulateConnectErrorHandler); + this.simulateDebugHost.on("connect_timeout", simulateConnectErrorHandler); + this.simulateDebugHost.on("connect", () => { + this.simulateDebugHost.on("resize-viewport", (data: simulate.ResizeViewportData) => { + this.changeSimulateViewport(data).catch(() => { + this.outputLogger(viewportResizeFailMessage, true); + }); + }); + this.simulateDebugHost.emit("register-debug-host", { handlers: ["resize-viewport"] }); + resolve(void 0); }); - this.simulateDebugHost.emit("register-debug-host", { handlers: ["resize-viewport"] }); - simulateDeferred.resolve(void 0); }); - - return simulateDeferred.promise; } private convertLaunchArgsToSimulateArgs(launchArgs: ICordovaLaunchRequestArgs): simulate.SimulateOptions { @@ -601,9 +601,9 @@ export class CordovaDebugSession extends LoggingDebugSession { return runArgs; } - private launchIos(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType, runArguments: string[]): Q.Promise { + private launchIos(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType, runArguments: string[]): Promise { if (os.platform() !== "darwin") { - return Q.reject(localize("UnableToLaunchiOSOnNonMacMachnines", "Unable to launch iOS on non-mac machines")); + return Promise.reject(localize("UnableToLaunchiOSOnNonMacMachnines", "Unable to launch iOS on non-mac machines")); } let workingDirectory = launchArgs.cwd; let errorLogger = (message) => this.outputLogger(message, true); @@ -645,11 +645,9 @@ export class CordovaDebugSession extends LoggingDebugSession { // cordova run ios does not terminate, so we do not know when to try and attach. // Therefore we parse the command's output to find the special key, which means that the application has been successfully launched. this.outputLogger(localize("InstallingAndLaunchingAppOnDevice", "Installing and launching app on device")); - return cordovaRunCommand(command, args, launchArgs.allEnv, workingDirectory) + return cordovaRunCommand(command, args, launchArgs.allEnv, workingDirectory, this.outputLogger) .then(() => { return CordovaIosDeviceLauncher.startDebugProxy(iosDebugProxyPort); - }, undefined, (progress) => { - this.outputLogger(progress[0], progress[1]); }) .then(() => void (0)); } else { @@ -688,10 +686,8 @@ export class CordovaDebugSession extends LoggingDebugSession { return this.startIonicDevServer(launchArgs, args).then(() => void 0); } - return cordovaRunCommand(command, args, launchArgs.allEnv, workingDirectory) - .progress((progress) => { - this.outputLogger(progress[0], progress[1]); - }).catch((err) => { + return cordovaRunCommand(command, args, launchArgs.allEnv, workingDirectory, this.outputLogger) + .catch((err) => { if (target === TargetType.Emulator) { return cordovaRunCommand(command, ["emulate", "ios", "--list"], launchArgs.allEnv, workingDirectory).then((output) => { // List out available targets @@ -707,7 +703,7 @@ export class CordovaDebugSession extends LoggingDebugSession { } } - private checkIfTargetIsiOSSimulator(target: string, cordovaCommand: string, env: any, workingDirectory: string): Q.Promise { + private checkIfTargetIsiOSSimulator(target: string, cordovaCommand: string, env: any, workingDirectory: string): Promise { const simulatorTargetIsNotSupported = () => { const message = localize("InvalidTargetPleaseCheckTargetParameter", "Invalid target. Please, check target parameter value in your debug configuration and make sure it's a valid iPhone device identifier. Proceed to https://aka.ms/AA3xq86 for more information."); throw new Error(message); @@ -737,7 +733,7 @@ export class CordovaDebugSession extends LoggingDebugSession { }); } - private attachIos(attachArgs: ICordovaAttachRequestArgs): Q.Promise { + private attachIos(attachArgs: ICordovaAttachRequestArgs): Promise { let target = attachArgs.target.toLowerCase() === TargetType.Emulator ? TargetType.Emulator : attachArgs.target; let workingDirectory = attachArgs.cwd; const command = CordovaProjectHelper.getCliCommand(workingDirectory); @@ -751,16 +747,16 @@ export class CordovaDebugSession extends LoggingDebugSession { // Start the tunnel through to the webkit debugger on the device this.outputLogger("Configuring debugging proxy"); - const retry = function (func, condition, retryCount, cancellationToken): Q.Promise { + const retry = function (func, condition, retryCount, cancellationToken): Promise { return retryAsync(func, condition, retryCount, 1, attachArgs.attachDelay, localize("UnableToFindWebview", "Unable to find Webview"), cancellationToken); }; - const getBundleIdentifier = (): Q.IWhenable => { + const getBundleIdentifier = () => { if (attachArgs.target.toLowerCase() === TargetType.Device) { return CordovaIosDeviceLauncher.getBundleIdentifier(attachArgs.cwd) .then(CordovaIosDeviceLauncher.getPathOnDevice); } else { - return Q.nfcall(fs.readdir, path.join(attachArgs.cwd, "platforms", "ios", "build", "emulator")).then((entries: string[]) => { + return fs.promises.readdir(path.join(attachArgs.cwd, "platforms", "ios", "build", "emulator")).then((entries: string[]) => { // TODO requires changes in case of implementing debugging on iOS simulators let filtered = entries.filter((entry) => /\.app$/.test(entry)); if (filtered.length > 0) { @@ -772,7 +768,7 @@ export class CordovaDebugSession extends LoggingDebugSession { } }; - const getSimulatorProxyPort = (iOSAppPackagePath): Q.IWhenable<{ iOSAppPackagePath: string, targetPort: number, iOSVersion: string }> => { + const getSimulatorProxyPort = (iOSAppPackagePath): Promise<{ iOSAppPackagePath: string, targetPort: number, iOSVersion: string }> => { return promiseGet(`http://localhost:${attachArgs.port}/json`, localize("UnableToCommunicateWithiOSWebkitDebugProxy", "Unable to communicate with ios_webkit_debug_proxy")).then((response: string) => { try { // An example of a json response from IWDP @@ -800,7 +796,7 @@ export class CordovaDebugSession extends LoggingDebugSession { }); }; - const getWebSocketDebuggerUrl = ({ iOSAppPackagePath, targetPort, iOSVersion }): Q.IWhenable => { + const getWebSocketDebuggerUrl = ({ iOSAppPackagePath, targetPort, iOSVersion }): Promise => { return retry(() => promiseGet(`http://localhost:${targetPort}/json`, localize("UnableToCommunicateWithTarget", "Unable to communicate with target")) .then((response: string) => { @@ -843,7 +839,7 @@ export class CordovaDebugSession extends LoggingDebugSession { ); }; - const getAttachRequestArgs = (): Q.Promise => + const getAttachRequestArgs = (): Promise => CordovaIosDeviceLauncher.startWebkitDebugProxy(attachArgs.port, attachArgs.webkitRangeMin, attachArgs.webkitRangeMax) .then(getBundleIdentifier) .then(getSimulatorProxyPort) @@ -887,7 +883,7 @@ export class CordovaDebugSession extends LoggingDebugSession { } // Stop ADB port forwarding if necessary - let adbPortPromise: Q.Promise; + let adbPortPromise: Promise; if (this.adbPortForwardingInfo) { const adbForwardStopArgs = @@ -897,11 +893,11 @@ export class CordovaDebugSession extends LoggingDebugSession { adbPortPromise = this.runAdbCommand(adbForwardStopArgs, errorLogger) .then(() => void 0); } else { - adbPortPromise = Q(void 0); + adbPortPromise = Promise.resolve(); } // Kill the Ionic dev server if necessary - let killServePromise: Q.Promise; + let killServePromise: Promise; if (this.ionicLivereloadProcess) { this.ionicLivereloadProcess.removeAllListeners("exit"); @@ -909,7 +905,7 @@ export class CordovaDebugSession extends LoggingDebugSession { this.ionicLivereloadProcess = null; }); } else { - killServePromise = Q(void 0); + killServePromise = Promise.resolve(); } // Clear the Ionic dev server URL if necessary @@ -939,13 +935,19 @@ export class CordovaDebugSession extends LoggingDebugSession { await logger.dispose(); // Wait on all the cleanups - return Q.allSettled([adbPortPromise, killServePromise]).then(() => void 0); + return adbPortPromise + .finally(() => killServePromise); } /** * Starts an Ionic livereload server ("serve" or "run / emulate --livereload"). Returns a promise fulfilled with the full URL to the server. */ - private startIonicDevServer(launchArgs: ICordovaLaunchRequestArgs, cliArgs: string[]): Q.Promise { + private startIonicDevServer(launchArgs: ICordovaLaunchRequestArgs, cliArgs: string[]): Promise { + enum IonicDevServerStatus { + ServerReady, + AppReady, + } + if (!launchArgs.runArguments || launchArgs.runArguments.length === 0) { if (launchArgs.devServerAddress) { cliArgs.push("--address", launchArgs.devServerAddress); @@ -955,19 +957,19 @@ export class CordovaDebugSession extends LoggingDebugSession { if (typeof launchArgs.devServerPort === "number" && launchArgs.devServerPort >= 0 && launchArgs.devServerPort <= 65535) { cliArgs.push("--port", launchArgs.devServerPort.toString()); } else { - return Q.reject(new Error(localize("TheValueForDevServerPortMustBeInInterval", "The value for \"devServerPort\" must be a number between 0 and 65535"))); + return Promise.reject(new Error(localize("TheValueForDevServerPortMustBeInInterval", "The value for \"devServerPort\" must be a number between 0 and 65535"))); } } } let isServe: boolean = cliArgs[0] === "serve"; let errorRegex: RegExp = /error:.*/i; - let serverReady: boolean = false; - let appReady: boolean = false; - let serverReadyTimeout: number = launchArgs.devServerTimeout || 30000; + let ionicLivereloadProcessStatus = { + serverReady: false, + appReady: false, + }; + let serverReadyTimeout: number = launchArgs.devServerTimeout || 60000; let appReadyTimeout: number = launchArgs.devServerTimeout || 120000; // If we're not serving, the app needs to build and deploy (and potentially start the emulator), which can be very long - let serverDeferred = Q.defer(); - let appDeferred = Q.defer(); let serverOut: string = ""; let serverErr: string = ""; const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; @@ -1023,170 +1025,185 @@ export class CordovaDebugSession extends LoggingDebugSession { const command = launchArgs.cordovaExecutable || CordovaProjectHelper.getCliCommand(launchArgs.cwd); this.ionicLivereloadProcess = cordovaStartCommand(command, cliArgs, launchArgs.allEnv, launchArgs.cwd); - this.ionicLivereloadProcess.on("error", (err: { code: string }) => { - if (err.code === "ENOENT") { - serverDeferred.reject(new Error(localize("IonicNotFound", "Ionic not found, please run 'npm install –g ionic' to install it globally"))); - } else { - serverDeferred.reject(err); - } - }); - this.ionicLivereloadProcess.on("exit", (() => { - this.ionicLivereloadProcess = null; - let exitMessage: string = "The Ionic live reload server exited unexpectedly"; - let errorMsg = getServerErrorMessage(serverErr); + const serverStarting = new Promise((_resolve, reject) => { + let rejectTimeout = setTimeout(() => { + reject(localize("StartingIonicDevServerTimedOut", "Starting the Ionic dev server timed out ({0} ms)", serverReadyTimeout)); + }, serverReadyTimeout); + + let resolveIfPossible = (ready: IonicDevServerStatus, serverUrls?: string[]) => { + if (ready === IonicDevServerStatus.ServerReady && !ionicLivereloadProcessStatus.serverReady) { + clearTimeout(rejectTimeout); + ionicLivereloadProcessStatus.serverReady = true; + this.outputLogger("Building and deploying app"); + rejectTimeout = setTimeout(() => { + reject(localize("BuildingAndDeployingTheAppTimedOut", "Building and deploying the app timed out ({0} ms)", appReadyTimeout)); + }, appReadyTimeout); + } else if (ready === IonicDevServerStatus.AppReady && ionicLivereloadProcessStatus.serverReady) { + clearTimeout(rejectTimeout); + ionicLivereloadProcessStatus.appReady = true; + _resolve(serverUrls); + } + }; - if (errorMsg) { - // The Ionic live reload server has an error; check if it is related to the devServerAddress to give a better message - if (errorMsg.indexOf("getaddrinfo ENOTFOUND") !== -1 || errorMsg.indexOf("listen EADDRNOTAVAIL") !== -1) { - exitMessage += os.EOL + localize("InvalidAddress", "Invalid address: please provide a valid IP address or hostname for the \"devServerAddress\" property in launch.json"); + this.ionicLivereloadProcess.on("error", (err: { code: string }) => { + if (err.code === "ENOENT") { + reject(new Error(localize("IonicNotFound", "Ionic not found, please run 'npm install –g ionic' to install it globally"))); } else { - exitMessage += os.EOL + errorMsg; + reject(err); } - } - - if (!serverDeferred.promise.isPending() && !appDeferred.promise.isPending()) { - // We are already debugging; disconnect the session - this.outputLogger(exitMessage, true); - this.stop(); - throw new Error(exitMessage); - } else { - // The Ionic dev server wasn't ready yet, so reject its promises - serverDeferred.reject(new Error(exitMessage)); - appDeferred.reject(new Error(exitMessage)); - } - }).bind(this)); - - let serverOutputHandler = (data: Buffer) => { - serverOut += data.toString(); - this.outputLogger(data.toString(), "stdout"); - - // Listen for the server to be ready. We check for the "Running dev server: http://localhost:/" and "dev server running: http://localhost:/" strings to decide that. + }); + this.ionicLivereloadProcess.on("exit", (() => { + this.ionicLivereloadProcess = null; - // Example output of Ionic 1 dev server: - // - // [OK] Development server running! - // Local: http://localhost:8100 - // External: http://10.0.75.1:8100, http://172.28.124.161:8100, http://169.254.80.80:8100, http://192.169.8.39:8100 + let exitMessage: string = "The Ionic live reload server exited unexpectedly"; + let errorMsg = getServerErrorMessage(serverErr); - // Example output of Ionic 2 dev server: - // - // Running live reload server: undefined - // Watching: 0=www/**/*, 1=!www/lib/**/* - // Running dev server: http://localhost:8100 - // Ionic server commands, enter: - // restart or r to restart the client app from the root - // goto or g and a url to have the app navigate to the given url - // consolelogs or c to enable/disable console log output - // serverlogs or s to enable/disable server log output - // quit or q to shutdown the server and exit - // - // ionic $ + if (errorMsg) { + // The Ionic live reload server has an error; check if it is related to the devServerAddress to give a better message + if (errorMsg.indexOf("getaddrinfo ENOTFOUND") !== -1 || errorMsg.indexOf("listen EADDRNOTAVAIL") !== -1) { + exitMessage += os.EOL + localize("InvalidAddress", "Invalid address: please provide a valid IP address or hostname for the \"devServerAddress\" property in launch.json"); + } else { + exitMessage += os.EOL + errorMsg; + } + } - // Example output of Ionic dev server (for Ionic2): - // - // > ionic-hello-world@ ionic:serve - // > ionic-app-scripts serve "--v2" "--address" "0.0.0.0" "--port" "8100" "--livereload-port" "35729" - // ionic-app-scripts - // watch started - // build dev started - // clean started - // clean finished - // copy started - // transpile started - // transpile finished - // webpack started - // copy finished - // webpack finished - // sass started - // sass finished - // build dev finished - // watch ready - // dev server running: http://localhost:8100/ - - const SERVER_URL_RE = /(dev server running|Running dev server|Local):.*(http:\/\/.[^\s]*)/gmi; - let localServerMatchResult = SERVER_URL_RE.exec(serverOut); - if (!serverReady && localServerMatchResult) { - serverReady = true; - serverDeferred.resolve(void 0); - } + if (!ionicLivereloadProcessStatus.serverReady && !ionicLivereloadProcessStatus.appReady) { + // We are already debugging; disconnect the session + this.outputLogger(exitMessage, true); + this.stop(); + throw new Error(exitMessage); + } else { + // The Ionic dev server wasn't ready yet, so reject its promises + reject(new Error(exitMessage)); + } + }).bind(this)); + + let serverOutputHandler = (data: Buffer) => { + serverOut += data.toString(); + this.outputLogger(data.toString(), "stdout"); + + // Listen for the server to be ready. We check for the "Running dev server: http://localhost:/" and "dev server running: http://localhost:/" strings to decide that. + + // Example output of Ionic 1 dev server: + // + // [OK] Development server running! + // Local: http://localhost:8100 + // External: http://10.0.75.1:8100, http://172.28.124.161:8100, http://169.254.80.80:8100, http://192.169.8.39:8100 + + // Example output of Ionic 2 dev server: + // + // Running live reload server: undefined + // Watching: 0=www/**/*, 1=!www/lib/**/* + // Running dev server: http://localhost:8100 + // Ionic server commands, enter: + // restart or r to restart the client app from the root + // goto or g and a url to have the app navigate to the given url + // consolelogs or c to enable/disable console log output + // serverlogs or s to enable/disable server log output + // quit or q to shutdown the server and exit + // + // ionic $ + + // Example output of Ionic dev server (for Ionic2): + // + // > ionic-hello-world@ ionic:serve + // > ionic-app-scripts serve "--v2" "--address" "0.0.0.0" "--port" "8100" "--livereload-port" "35729" + // ionic-app-scripts + // watch started + // build dev started + // clean started + // clean finished + // copy started + // transpile started + // transpile finished + // webpack started + // copy finished + // webpack finished + // sass started + // sass finished + // build dev finished + // watch ready + // dev server running: http://localhost:8100/ + + const SERVER_URL_RE = /(dev server running|Running dev server|Local):.*(http:\/\/.[^\s]*)/gmi; + let localServerMatchResult = SERVER_URL_RE.exec(serverOut); + if (!ionicLivereloadProcessStatus.serverReady && localServerMatchResult) { + resolveIfPossible(IonicDevServerStatus.ServerReady); + } - if (serverReady && !appReady) { - let regex: RegExp = getRegexToResolveAppDefer(cliArgs); + if (ionicLivereloadProcessStatus.serverReady && !ionicLivereloadProcessStatus.appReady) { + let regex: RegExp = getRegexToResolveAppDefer(cliArgs); - if (isServe || regex.test(serverOut)) { - appReady = true; - const serverUrls = [localServerMatchResult[2]]; - const externalUrls = /External:\s(.*)$/im.exec(serverOut); - if (externalUrls) { - const urls = externalUrls[1].split(", ").map(x => x.trim()); - serverUrls.push(...urls); + if (isServe || regex.test(serverOut)) { + const serverUrls = [localServerMatchResult[2]]; + const externalUrls = /External:\s(.*)$/im.exec(serverOut); + if (externalUrls) { + const urls = externalUrls[1].split(", ").map(x => x.trim()); + serverUrls.push(...urls); + } + launchArgs.devServerPort = CordovaProjectHelper.getPortFromURL(serverUrls[0]); + resolveIfPossible(IonicDevServerStatus.AppReady, serverUrls); } - launchArgs.devServerPort = CordovaProjectHelper.getPortFromURL(serverUrls[0]); - appDeferred.resolve(serverUrls); } - } - if (/Multiple network interfaces detected/.test(serverOut)) { - // Ionic does not know which address to use for the dev server, and requires human interaction; error out and let the user know - let errorMessage: string = localize("YourMachineHasMultipleNetworkAddresses", - `Your machine has multiple network addresses. Please specify which one your device or emulator will use to communicate with the dev server by adding a \"devServerAddress\": \"ADDRESS\" property to .vscode/launch.json. -To get the list of addresses run "ionic cordova run PLATFORM --livereload" (where PLATFORM is platform name to run) and wait until prompt with this list is appeared.`); - let addresses: string[] = []; - let addressRegex = /(\d+\) .*)/gm; - let match: string[] = addressRegex.exec(serverOut); - - while (match) { - addresses.push(match[1]); - match = addressRegex.exec(serverOut); - } + if (/Multiple network interfaces detected/.test(serverOut)) { + // Ionic does not know which address to use for the dev server, and requires human interaction; error out and let the user know + let errorMessage: string = localize("YourMachineHasMultipleNetworkAddresses", + `Your machine has multiple network addresses. Please specify which one your device or emulator will use to communicate with the dev server by adding a \"devServerAddress\": \"ADDRESS\" property to .vscode/launch.json. + To get the list of addresses run "ionic cordova run PLATFORM --livereload" (where PLATFORM is platform name to run) and wait until prompt with this list is appeared.`); + let addresses: string[] = []; + let addressRegex = /(\d+\) .*)/gm; + let match: string[] = addressRegex.exec(serverOut); + + while (match) { + addresses.push(match[1]); + match = addressRegex.exec(serverOut); + } + + if (addresses.length > 0) { + // Give the user the list of addresses that Ionic found + // NOTE: since ionic started to use inquirer.js for showing _interactive_ prompts this trick does not work as no output + // of prompt are sent from ionic process which we starts with --no-interactive parameter + errorMessage += [localize("AvailableAdresses", " Available addresses:")].concat(addresses).join(os.EOL + " "); + } - if (addresses.length > 0) { - // Give the user the list of addresses that Ionic found - // NOTE: since ionic started to use inquirer.js for showing _interactive_ prompts this trick does not work as no output - // of prompt are sent from ionic process which we starts with --no-interactive parameter - errorMessage += [localize("AvailableAdresses", " Available addresses:")].concat(addresses).join(os.EOL + " "); + reject(new Error(errorMessage)); } - serverDeferred.reject(new Error(errorMessage)); - } + let errorMsg = getServerErrorMessage(serverOut); - let errorMsg = getServerErrorMessage(serverOut); + if (errorMsg) { + reject(new Error(errorMsg)); + } + }; - if (errorMsg) { - appDeferred.reject(new Error(errorMsg)); - } - }; + let serverErrorOutputHandler = (data: Buffer) => { + serverErr += data.toString(); - let serverErrorOutputHandler = (data: Buffer) => { - serverErr += data.toString(); + let errorMsg = getServerErrorMessage(serverErr); - let errorMsg = getServerErrorMessage(serverErr); + if (errorMsg) { + reject(new Error(errorMsg)); + } + }; - if (errorMsg) { - appDeferred.reject(new Error(errorMsg)); - } - }; + this.ionicLivereloadProcess.stdout.on("data", serverOutputHandler); + this.ionicLivereloadProcess.stderr.on("data", (data: Buffer) => { + if (isIonic4) { + // Ionic 4 writes all logs to stderr completely ignoring stdout + serverOutputHandler(data); + } + serverErrorOutputHandler(data); + }); - this.ionicLivereloadProcess.stdout.on("data", serverOutputHandler); - this.ionicLivereloadProcess.stderr.on("data", (data: Buffer) => { - if (isIonic4) { - // Ionic 4 writes all logs to stderr completely ignoring stdout - serverOutputHandler(data); - } - serverErrorOutputHandler(data); + this.outputLogger(localize("StartingIonicDevServer", "Starting Ionic dev server (live reload: {0})", launchArgs.ionicLiveReload)); }); - this.outputLogger(localize("StartingIonicDevServer", "Starting Ionic dev server (live reload: {0})", launchArgs.ionicLiveReload)); - - return serverDeferred.promise.timeout(serverReadyTimeout, localize("StartingIonicDevServerTimedOut", "Starting the Ionic dev server timed out ({0} ms)", serverReadyTimeout)).then(() => { - this.outputLogger("Building and deploying app"); - - return appDeferred.promise.timeout(appReadyTimeout, localize("BuildingAndDeployingTheAppTimedOut", "Building and deploying the app timed out ({0} ms)", appReadyTimeout)); - }).then((ionicDevServerUrls: string[]) => { + return serverStarting.then((ionicDevServerUrls: string[]) => { if (!ionicDevServerUrls || !ionicDevServerUrls.length) { - return Q.reject(new Error(localize("UnableToDetermineTheIonicDevServerAddress", "Unable to determine the Ionic dev server address, please try re-launching the debugger"))); + throw new Error(localize("UnableToDetermineTheIonicDevServerAddress", "Unable to determine the Ionic dev server address, please try re-launching the debugger")); } // The dev server address is the captured group at index 1 of the match @@ -1194,7 +1211,7 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher // When ionic 2 cli is installed, output includes ansi characters for color coded output. this.ionicDevServerUrls = this.ionicDevServerUrls.map(url => url.replace(ansiRegex, "")); - return Q(this.ionicDevServerUrls); + return this.ionicDevServerUrls; }); } @@ -1242,7 +1259,7 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher } - private launchServe(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType, runArguments: string[]): Q.Promise { + private launchServe(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType, runArguments: string[]): Promise { let errorLogger = (message) => this.outputLogger(message, true); // Currently, "ionic serve" is only supported for Ionic projects @@ -1251,7 +1268,7 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher errorLogger(errorMessage); - return Q.reject(new Error(errorMessage)); + return Promise.reject(new Error(errorMessage)); } let args = ["serve"]; @@ -1271,16 +1288,15 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher // Deploy app to browser - return Q(void 0).then(() => { - return this.startIonicDevServer(launchArgs, args); - }).then((devServerUrls: string[]) => { - // Prepare Chrome launch args - launchArgs.url = devServerUrls[0]; - launchArgs.userDataDir = path.join(settingsHome(), CordovaDebugSession.CHROME_DATA_DIR); - - // Launch Chrome and attach - return this.launchChromiumBasedBrowser(launchArgs); - }); + return this.startIonicDevServer(launchArgs, args) + .then((devServerUrls: string[]) => { + // Prepare Chrome launch args + launchArgs.url = devServerUrls[0]; + launchArgs.userDataDir = path.join(settingsHome(), CordovaDebugSession.CHROME_DATA_DIR); + + // Launch Chrome and attach + return this.launchChromiumBasedBrowser(launchArgs); + }); } private getErrorMessage(e: any): string { @@ -1369,26 +1385,30 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher return this.startIonicDevServer(launchArgs, args).then(() => void 0); } const command = launchArgs.cordovaExecutable || CordovaProjectHelper.getCliCommand(workingDirectory); - let cordovaResult = cordovaRunCommand(command, args, launchArgs.allEnv, workingDirectory).then((output) => { - let runOutput = output[0]; - let stderr = output[1]; - - // Ionic ends process with zero code, so we need to look for - // strings with error content to detect failed process - let errorMatch = /(ERROR.*)/.test(runOutput) || /error:.*/i.test(stderr); - if (errorMatch) { - throw new Error(localize("ErrorRunningAndroid", "Error running android")); - } + let cordovaResult = cordovaRunCommand( + command, + args, + launchArgs.allEnv, + workingDirectory, + this.outputLogger, + ).then((output) => { + let runOutput = output[0]; + let stderr = output[1]; + + // Ionic ends process with zero code, so we need to look for + // strings with error content to detect failed process + let errorMatch = /(ERROR.*)/.test(runOutput) || /error:.*/i.test(stderr); + if (errorMatch) { + throw new Error(localize("ErrorRunningAndroid", "Error running android")); + } - this.outputLogger(localize("AppSuccessfullyLaunched", "App successfully launched")); - }, undefined, (progress) => { - this.outputLogger(progress[0], progress[1]); - }); + this.outputLogger(localize("AppSuccessfullyLaunched", "App successfully launched")); + }); return cordovaResult; } - private attachAndroid(attachArgs: ICordovaAttachRequestArgs): Q.Promise { + private attachAndroid(attachArgs: ICordovaAttachRequestArgs): Promise { let errorLogger = (message: string) => this.outputLogger(message, true); // Determine which device/emulator we are targeting @@ -1396,7 +1416,7 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher const deviceFilter = (line: string) => /\w+\tdevice/.test(line) && !/emulator/.test(line); const emulatorFilter = (line: string) => /device/.test(line) && /emulator/.test(line); - let adbDevicesResult: Q.Promise = this.runAdbCommand(["devices"], errorLogger) + let adbDevicesResult: Promise = this.runAdbCommand(["devices"], errorLogger) .then((devicesOutput) => { const targetFilter = attachArgs.target.toLowerCase() === TargetType.Device ? deviceFilter : @@ -1422,10 +1442,10 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher throw err; }); - let packagePromise: Q.Promise = Q.nfcall(fs.readFile, path.join(attachArgs.cwd, ANDROID_MANIFEST_PATH)) + let packagePromise: Promise = fs.promises.readFile(path.join(attachArgs.cwd, ANDROID_MANIFEST_PATH)) .catch((err) => { if (err && err.code === "ENOENT") { - return Q.nfcall(fs.readFile, path.join(attachArgs.cwd, ANDROID_MANIFEST_PATH_8)); + return fs.promises.readFile(path.join(attachArgs.cwd, ANDROID_MANIFEST_PATH_8)); } throw err; }) @@ -1435,8 +1455,8 @@ To get the list of addresses run "ionic cordova run PLATFORM --livereload" (wher return parsedFile.attrib[packageKey]; }); - return Q.all([packagePromise, adbDevicesResult]) - .spread((appPackageName: string, targetDevice: string) => { + return Promise.all([packagePromise, adbDevicesResult]) + .then(([appPackageName, targetDevice]) => { let pidofCommandArguments = ["-s", targetDevice, "shell", "pidof", appPackageName]; let getPidCommandArguments = ["-s", targetDevice, "shell", "ps"]; let getSocketsCommandArguments = ["-s", targetDevice, "shell", "cat /proc/net/unix"]; diff --git a/src/debugger/cordovaIosDeviceLauncher.ts b/src/debugger/cordovaIosDeviceLauncher.ts index 7e35cde4..2b1d79e1 100755 --- a/src/debugger/cordovaIosDeviceLauncher.ts +++ b/src/debugger/cordovaIosDeviceLauncher.ts @@ -7,14 +7,13 @@ import * as child_process from "child_process"; import * as fs from "fs"; import * as path from "path"; import * as pl from "plist"; -import * as Q from "q"; import * as xcode from "xcode"; +import { delay } from "../utils/extensionHelper"; +import { ChildProcess } from "../common/node/childProcess"; import * as nls from "vscode-nls"; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); const localize = nls.loadMessageBundle(); -let promiseExec = Q.denodeify(child_process.exec); - export class CordovaIosDeviceLauncher { private static nativeDebuggerProxyInstance: child_process.ChildProcess; private static webDebuggerProxyInstance: child_process.ChildProcess; @@ -30,8 +29,8 @@ export class CordovaIosDeviceLauncher { } } - public static getBundleIdentifier(projectRoot: string): Q.Promise { - return Q.nfcall(fs.readdir, path.join(projectRoot, "platforms", "ios")).then((files: string[]) => { + public static getBundleIdentifier(projectRoot: string): Promise { + return fs.promises.readdir(path.join(projectRoot, "platforms", "ios")).then((files: string[]) => { let xcodeprojfiles = files.filter((file: string) => /\.xcodeproj$/.test(file)); if (xcodeprojfiles.length === 0) { throw new Error(localize("UnableToFindXCodeProjFile", "Unable to find xcodeproj file")); @@ -49,51 +48,50 @@ export class CordovaIosDeviceLauncher { }); } - public static startDebugProxy(proxyPort: number): Q.Promise { + public static startDebugProxy(proxyPort: number): Promise { if (CordovaIosDeviceLauncher.nativeDebuggerProxyInstance) { CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill("SIGHUP"); // idevicedebugserver does not exit from SIGTERM CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = null; } - return CordovaIosDeviceLauncher.mountDeveloperImage().then(function (): Q.Promise { - let deferred = Q.defer(); - CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = child_process.spawn("idevicedebugserverproxy", [proxyPort.toString()]); - CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.on("error", function (err: any): void { - deferred.reject(err); + return CordovaIosDeviceLauncher.mountDeveloperImage().then(() => { + return new Promise((resolve, reject) => { + CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = child_process.spawn("idevicedebugserverproxy", [proxyPort.toString()]); + CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.on("error", function (err: any): void { + reject(err); + }); + // Allow 200ms for the spawn to error out, ~125ms isn't uncommon for some failures + return delay(200).then(() => resolve(CordovaIosDeviceLauncher.nativeDebuggerProxyInstance)); }); - // Allow 200ms for the spawn to error out, ~125ms isn't uncommon for some failures - Q.delay(200).then(() => deferred.resolve(CordovaIosDeviceLauncher.nativeDebuggerProxyInstance)); - - return deferred.promise; }); } - public static startWebkitDebugProxy(proxyPort: number, proxyRangeStart: number, proxyRangeEnd: number): Q.Promise { + public static startWebkitDebugProxy(proxyPort: number, proxyRangeStart: number, proxyRangeEnd: number): Promise { if (CordovaIosDeviceLauncher.webDebuggerProxyInstance) { CordovaIosDeviceLauncher.webDebuggerProxyInstance.kill(); CordovaIosDeviceLauncher.webDebuggerProxyInstance = null; } - let deferred = Q.defer(); - let portRange = `null:${proxyPort},:${proxyRangeStart}-${proxyRangeEnd}`; - CordovaIosDeviceLauncher.webDebuggerProxyInstance = child_process.spawn("ios_webkit_debug_proxy", ["-c", portRange]); - CordovaIosDeviceLauncher.webDebuggerProxyInstance.on("error", function () { - deferred.reject(new Error(localize("UnableToStartIosWebkitDebugProxy", "Unable to start ios_webkit_debug_proxy."))); + return new Promise((resolve, reject) => { + let portRange = `null:${proxyPort},:${proxyRangeStart}-${proxyRangeEnd}`; + CordovaIosDeviceLauncher.webDebuggerProxyInstance = child_process.spawn("ios_webkit_debug_proxy", ["-c", portRange]); + CordovaIosDeviceLauncher.webDebuggerProxyInstance.on("error", function () { + reject(new Error(localize("UnableToStartIosWebkitDebugProxy", "Unable to start ios_webkit_debug_proxy."))); + }); + // Allow some time for the spawned process to error out + return delay(250).then(() => resolve(void 0)); }); - // Allow some time for the spawned process to error out - Q.delay(250).then(() => deferred.resolve({})); - - return deferred.promise; } - public static getPathOnDevice(packageId: string): Q.Promise { - return promiseExec("ideviceinstaller -l -o xml > /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller") + public static getPathOnDevice(packageId: string): Promise { + const cp = new ChildProcess(); + return cp.execToString("ideviceinstaller -l -o xml > /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller") .catch(function (err: any): any { if (err.code === "ENOENT") { throw new Error(localize("UnableToStartiDeviceInstaller", "Unable to find ideviceinstaller.")); } throw err; - }).spread(function (stdout: string): string { + }).then((stdout: string) => { // First find the path of the app on the device let filename: string = stdout.trim(); if (!/^\/tmp\/[0-9]+\.ideviceinstaller$/.test(filename)) { @@ -118,7 +116,7 @@ export class CordovaIosDeviceLauncher { return packagePath.split("").map((c: string) => c.charCodeAt(0).toString(16)).join("").toUpperCase(); } - private static getBundleIdentifierFromPbxproj(xcodeprojFilePath: string): Q.Promise { + private static getBundleIdentifierFromPbxproj(xcodeprojFilePath: string): Promise { const pbxprojFilePath = path.join(xcodeprojFilePath, "project.pbxproj"); const pbxproj = xcode.project(pbxprojFilePath).parseSync(); const target = pbxproj.getFirstTarget(); @@ -128,73 +126,73 @@ export class CordovaIosDeviceLauncher { const targetConfigUUID = targetConfigs[0].value; // 0 is "Debug, 1 is Release" - usually they have the same associated bundleId, it's highly unlikely someone would change it const allConfigs = pbxproj.pbxXCBuildConfigurationSection(); const bundleId = allConfigs[targetConfigUUID].buildSettings.PRODUCT_BUNDLE_IDENTIFIER; - return Q.resolve(bundleId); + return Promise.resolve(bundleId); } - private static mountDeveloperImage(): Q.Promise { + + private static mountDeveloperImage(): Promise { return CordovaIosDeviceLauncher.getDiskImage() - .then(function (path: string): Q.Promise { + .then((path: string) => { let imagemounter: child_process.ChildProcess = child_process.spawn("ideviceimagemounter", [path, path + ".signature"]); - let deferred: Q.Deferred = Q.defer(); - let stdout: string = ""; - imagemounter.stdout.on("data", function (data: any): void { - stdout += data.toString(); - }); - imagemounter.on("close", function (code: number): void { - if (code !== 0) { - if (stdout.indexOf("Error:") !== -1) { - deferred.resolve({}); // Technically failed, but likely caused by the image already being mounted. - } else if (stdout.indexOf("No device found, is it plugged in?") !== -1) { - deferred.reject(localize("UnableToFindDevice", "Unable to find device. Is the device plugged in?")); + return new Promise((resolve, reject) => { + let stdout: string = ""; + imagemounter.stdout.on("data", function (data: any): void { + stdout += data.toString(); + }); + imagemounter.on("close", function (code: number): void { + if (code !== 0) { + if (stdout.indexOf("Error:") !== -1) { + resolve(); // Technically failed, but likely caused by the image already being mounted. + } else if (stdout.indexOf("No device found, is it plugged in?") !== -1) { + reject(localize("UnableToFindDevice", "Unable to find device. Is the device plugged in?")); + } + + reject(localize("UnableToMountDeveloperDiskImage", "Unable to mount developer disk image.")); + } else { + resolve(); } - - deferred.reject(localize("UnableToMountDeveloperDiskImage", "Unable to mount developer disk image.")); - } else { - deferred.resolve({}); - } - }); - imagemounter.on("error", function (err: any): void { - deferred.reject(err); + }); + imagemounter.on("error", function (err: any): void { + reject(err); + }); }); - return deferred.promise; }); } - private static getDiskImage(): Q.Promise { + private static getDiskImage(): Promise { + const cp = new ChildProcess(); // Attempt to find the OS version of the iDevice, e.g. 7.1 - let versionInfo: Q.Promise = promiseExec("ideviceinfo -s -k ProductVersion") - .spread(function (stdout: string): string { + let versionInfo: Promise = cp.execToString("ideviceinfo -s -k ProductVersion") + .then(stdout => { // Versions for DeveloperDiskImage seem to be X.Y, while some device versions are X.Y.Z return /^(\d+\.\d+)(?:\.\d+)?$/gm.exec(stdout.trim())[1]; }) - .catch(function (e): string { - throw new Error(localize("UnableToGetDeviceOSVersion", "Unable to get device OS version. Details: {0}", e.message)); + .catch(err => { + throw new Error(localize("UnableToGetDeviceOSVersion", "Unable to get device OS version. Details: {0}", err.message)); }); // Attempt to find the path where developer resources exist. - let pathInfo: Q.Promise = promiseExec("xcrun -sdk iphoneos --show-sdk-platform-path").spread(function (stdout: string): string { + let pathInfo: Promise = cp.execToString("xcrun -sdk iphoneos --show-sdk-platform-path").then(stdout => { let sdkpath: string = stdout.trim(); return sdkpath; }); // Attempt to find the developer disk image for the appropriate - return Q.all([versionInfo, pathInfo]).spread(function (version: string, sdkpath: string): Q.Promise { + return Promise.all([versionInfo, pathInfo]).then(([version, sdkpath]) => { let find: child_process.ChildProcess = child_process.spawn("find", [sdkpath, "-path", "*" + version + "*", "-name", "DeveloperDiskImage.dmg"]); - let deferred: Q.Deferred = Q.defer(); - - find.stdout.on("data", function (data: any): void { - let dataStr: string = data.toString(); - let path: string = dataStr.split("\n")[0].trim(); - if (!path) { - deferred.reject(localize("UnableToFindDeveloperDiskImage", "Unable to find developer disk image.")); - } else { - deferred.resolve(path); - } - }); - find.on("close", function (): void { - deferred.reject(localize("UnableToFindDeveloperDiskImage", "Unable to find developer disk image.")); + return new Promise((resolve, reject) => { + find.stdout.on("data", function (data: any): void { + let dataStr: string = data.toString(); + let path: string = dataStr.split("\n")[0].trim(); + if (!path) { + reject(localize("UnableToFindDeveloperDiskImage", "Unable to find developer disk image.")); + } else { + resolve(path); + } + }); + find.on("close", function (): void { + reject(localize("UnableToFindDeveloperDiskImage", "Unable to find developer disk image.")); + }); }); - - return deferred.promise; }); } } diff --git a/src/debugger/extension.ts b/src/debugger/extension.ts index 48e5523c..ee2fb2b7 100644 --- a/src/debugger/extension.ts +++ b/src/debugger/extension.ts @@ -3,96 +3,94 @@ import * as child_process from "child_process"; import { CordovaProjectHelper } from "../utils/cordovaProjectHelper"; -import * as Q from "q"; import * as path from "path"; import * as nls from "vscode-nls"; import { findFileInFolderHierarchy } from "../utils/extensionHelper"; +import { DebugConsoleLogger } from "./cordovaDebugSession"; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); const localize = nls.loadMessageBundle(); // suppress the following strings because they are not actual errors: const errorsToSuppress = ["Run an Ionic project on a connected device"]; -export function execCommand(command: string, args: string[], errorLogger: (message: string) => void): Q.Promise { - let deferred = Q.defer(); - let proc = child_process.spawn(command, args, { stdio: "pipe" }); - let stderr = ""; - let stdout = ""; - proc.stderr.on("data", (data: Buffer) => { - stderr += data.toString(); - }); - proc.stdout.on("data", (data: Buffer) => { - stdout += data.toString(); - }); - proc.on("error", (err: Error) => { - deferred.reject(err); - }); - proc.on("close", (code: number) => { - if (code !== 0) { - errorLogger(stderr); - errorLogger(stdout); - deferred.reject(new Error(localize("ErrorRunningCommand", "Error running {0} {1}", command, args.join(" ")))); - } - deferred.resolve(stdout); +export function execCommand(command: string, args: string[], errorLogger: (message: string) => void): Promise { + return new Promise((resolve, reject) => { + const proc = child_process.spawn(command, args, { stdio: "pipe" }); + let stderr = ""; + let stdout = ""; + proc.stderr.on("data", (data: Buffer) => { + stderr += data.toString(); + }); + proc.stdout.on("data", (data: Buffer) => { + stdout += data.toString(); + }); + proc.on("error", (err: Error) => { + reject(err); + }); + proc.on("close", (code: number) => { + if (code !== 0) { + errorLogger(stderr); + errorLogger(stdout); + reject(new Error(localize("ErrorRunningCommand", "Error running {0} {1}", command, args.join(" ")))); + } + resolve(stdout); + }); }); - - return deferred.promise; } -export function cordovaRunCommand(command: string, args: string[], env, cordovaRootPath: string): Q.Promise { - let defer = Q.defer(); - let isIonicProject = CordovaProjectHelper.isIonicAngularProject(cordovaRootPath); - let output = ""; - let stderr = ""; - let cordovaProcess = cordovaStartCommand(command, args, env, cordovaRootPath); - - // Prevent these lines to be shown more than once - // to prevent debug console pollution - let isShown = { - "Running command": false, - "cordova prepare": false, - "cordova platform add": false, - }; - - cordovaProcess.stderr.on("data", data => { - stderr += data.toString(); - for (let i = 0; i < errorsToSuppress.length; i++) { - if (data.toString().indexOf(errorsToSuppress[i]) >= 0) { - return; +export function cordovaRunCommand(command: string, args: string[], env, cordovaRootPath: string, outputLogger?: DebugConsoleLogger): Promise { + return new Promise((resolve, reject) => { + let isIonicProject = CordovaProjectHelper.isIonicAngularProject(cordovaRootPath); + let output = ""; + let stderr = ""; + let cordovaProcess = cordovaStartCommand(command, args, env, cordovaRootPath); + + // Prevent these lines to be shown more than once + // to prevent debug console pollution + let isShown = { + "Running command": false, + "cordova prepare": false, + "cordova platform add": false, + }; + + cordovaProcess.stderr.on("data", data => { + stderr += data.toString(); + for (let i = 0; i < errorsToSuppress.length; i++) { + if (data.toString().indexOf(errorsToSuppress[i]) >= 0) { + return; + } } - } - defer.notify([data.toString(), "stderr"]); - }); - cordovaProcess.stdout.on("data", (data: Buffer) => { - let str = data.toString().replace(/\u001b/g, "").replace(/\[2K\[G/g, ""); // Erasing `[2K[G` artifacts from DEBUG CONSOLE output - output += str; - for (let message in isShown) { - if (str.indexOf(message) > -1) { - if (!isShown[message]) { - isShown[message] = true; - defer.notify([str, "stdout"]); + outputLogger && outputLogger(data.toString(), "stderr"); + }); + cordovaProcess.stdout.on("data", (data: Buffer) => { + let str = data.toString().replace(/\u001b/g, "").replace(/\[2K\[G/g, ""); // Erasing `[2K[G` artifacts from DEBUG CONSOLE output + output += str; + for (let message in isShown) { + if (str.indexOf(message) > -1) { + if (!isShown[message]) { + isShown[message] = true; + outputLogger && outputLogger(str, "stdout"); + } + return; } - return; } - } - defer.notify([str, "stdout"]); + outputLogger && outputLogger(str, "stdout"); - if (isIonicProject && str.indexOf("LAUNCH SUCCESS") >= 0) { - defer.resolve([output, stderr]); - } - }); - cordovaProcess.on("exit", exitCode => { - if (exitCode) { - defer.reject(new Error(localize("CommandFailedWithExitCode", "{0} {1} failed with exit code {2}", command, args.join(" "), exitCode))); - } else { - defer.resolve([output, stderr]); - } - }); - cordovaProcess.on("error", error => { - defer.reject(error); + if (isIonicProject && str.indexOf("LAUNCH SUCCESS") >= 0) { + resolve([output, stderr]); + } + }); + cordovaProcess.on("exit", exitCode => { + if (exitCode) { + reject(new Error(localize("CommandFailedWithExitCode", "{0} {1} failed with exit code {2}", command, args.join(" "), exitCode))); + } else { + resolve([output, stderr]); + } + }); + cordovaProcess.on("error", error => { + reject(error); + }); }); - - return defer.promise; } export function cordovaStartCommand(command: string, args: string[], env: any, cordovaRootPath: string): child_process.ChildProcess { @@ -127,7 +125,7 @@ export function killTree(processId: number): void { } } -export function killChildProcess(childProcess: child_process.ChildProcess): Q.Promise { +export function killChildProcess(childProcess: child_process.ChildProcess): Promise { killTree(childProcess.pid); - return Q(void 0); + return Promise.resolve(); } diff --git a/src/extension/cordovaWorkspaceManager.ts b/src/extension/cordovaWorkspaceManager.ts index ea9b7023..09d930e7 100644 --- a/src/extension/cordovaWorkspaceManager.ts +++ b/src/extension/cordovaWorkspaceManager.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -import * as Q from "q"; import { PluginSimulator } from "./simulate"; import { SimulationInfo } from "../common/simulationInfo"; import { SimulateOptions } from "cordova-simulate"; @@ -54,7 +53,7 @@ export class CordovaWorkspaceManager implements vscode.Disposable { * * Returns info about the running simulate server */ - public simulate(fsPath: string, simulateOptions: SimulateOptions, projectType: IProjectType): Q.Promise { + public simulate(fsPath: string, simulateOptions: SimulateOptions, projectType: IProjectType): Promise { return this.launchSimulateServer(fsPath, simulateOptions, projectType) .then((simulateInfo: SimulationInfo) => { return this.launchSimHost(simulateOptions.target).then(() => simulateInfo); @@ -66,31 +65,30 @@ export class CordovaWorkspaceManager implements vscode.Disposable { * * Returns info about the running simulate server */ - public launchSimulateServer(fsPath: string, simulateOptions: SimulateOptions, projectType: IProjectType): Q.Promise { + public launchSimulateServer(fsPath: string, simulateOptions: SimulateOptions, projectType: IProjectType): Promise { return this.pluginSimulator.launchServer(fsPath, simulateOptions, projectType); } /** * Launches sim-host using an already running simulate server. */ - public launchSimHost(target: string): Q.Promise { + public launchSimHost(target: string): Promise { return this.pluginSimulator.launchSimHost(target); } /** * Returns the number of currently visible editors. */ - public getVisibleEditorsCount(): Q.Promise { + public getVisibleEditorsCount(): Promise { // visibleTextEditors is null proof (returns empty array if no editors visible) - return Q.resolve(vscode.window.visibleTextEditors.length); + return Promise.resolve(vscode.window.visibleTextEditors.length); } - public getRunArguments(fsPath: string): Q.Promise { - return Q.resolve(CordovaCommandHelper.getRunArguments(fsPath)); + public getRunArguments(fsPath: string): Promise { + return Promise.resolve(CordovaCommandHelper.getRunArguments(fsPath)); } - - public getCordovaExecutable(fsPath: string): Q.Promise { - return Q.resolve(CordovaCommandHelper.getCordovaExecutable(fsPath)); + public getCordovaExecutable(fsPath: string): Promise { + return Promise.resolve(CordovaCommandHelper.getCordovaExecutable(fsPath)); } } diff --git a/src/extension/simulate.ts b/src/extension/simulate.ts index 46776ea8..fddfc908 100644 --- a/src/extension/simulate.ts +++ b/src/extension/simulate.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -import * as Q from "q"; import * as path from "path"; import * as CordovaSimulate from "cordova-simulate"; import { CordovaSimulateTelemetry } from "../utils/cordovaSimulateTelemetry"; @@ -40,22 +39,22 @@ export class PluginSimulator implements vscode.Disposable { private simulatePackage: typeof CordovaSimulate; private packageInstallProc: cp.ChildProcess | null = null; - public simulate(fsPath: string, simulateOptions: CordovaSimulate.SimulateOptions, projectType: IProjectType): Q.Promise { + public simulate(fsPath: string, simulateOptions: CordovaSimulate.SimulateOptions, projectType: IProjectType): Promise { return this.launchServer(fsPath, simulateOptions, projectType) .then(() => this.launchSimHost(simulateOptions.target)) .then(() => this.launchAppHost(simulateOptions.target)); } - public launchAppHost(target: string): Q.Promise { + public launchAppHost(target: string): Promise { return this.getPackage() .then(simulate => { return simulate.launchBrowser(target, this.simulationInfo.appHostUrl); }); } - public launchSimHost(target: string): Q.Promise { + public launchSimHost(target: string): Promise { if (!this.simulator) { - return Q.reject(new Error(localize("LaunchingSimHostBeforeStartSimulationServer", "Launching sim host before starting simulation server"))); + return Promise.reject(new Error(localize("LaunchingSimHostBeforeStartSimulationServer", "Launching sim host before starting simulation server"))); } return this.getPackage() .then(simulate => { @@ -63,7 +62,7 @@ export class PluginSimulator implements vscode.Disposable { }); } - public launchServer(fsPath: string, simulateOptions: CordovaSimulate.SimulateOptions, projectType: IProjectType): Q.Promise { + public launchServer(fsPath: string, simulateOptions: CordovaSimulate.SimulateOptions, projectType: IProjectType): Promise { const uri = vscode.Uri.file(fsPath); const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri); simulateOptions.dir = workspaceFolder.uri.fsPath; @@ -124,20 +123,20 @@ export class PluginSimulator implements vscode.Disposable { } if (this.simulator) { - this.simulator.stopSimulation().done(() => { }, () => { }); + this.simulator.stopSimulation().then(() => { }, () => { }); this.simulator = null; } } - public getPackage(): Q.Promise { + public getPackage(): Promise { if (this.simulatePackage) { - return Q.resolve(this.simulatePackage); + return Promise.resolve(this.simulatePackage); } // Don't do the require if we don't actually need it try { const simulate = customRequire(this.CORDOVA_SIMULATE_PACKAGE) as typeof CordovaSimulate; this.simulatePackage = simulate; - return Q.resolve(this.simulatePackage); + return Promise.resolve(this.simulatePackage); } catch (e) { if (e.code === "MODULE_NOT_FOUND") { OutputChannelLogger.getMainChannel().log(localize("CordovaSimulateDepNotPresent", "cordova-simulate dependency not present. Installing it...")); @@ -146,47 +145,47 @@ export class PluginSimulator implements vscode.Disposable { } } - const packageFound = Q.defer(); - if (!this.packageInstallProc) { - this.packageInstallProc = cp.spawn(process.platform === "win32" ? "npm.cmd" : "npm", - ["install", this.CORDOVA_SIMULATE_PACKAGE, "--verbose", "--no-save"], - { cwd: path.dirname(findFileInFolderHierarchy(__dirname, "package.json")) }); - - this.packageInstallProc.once("exit", (code: number) => { - if (code === 0) { - this.simulatePackage = customRequire(this.CORDOVA_SIMULATE_PACKAGE); - packageFound.resolve(this.simulatePackage); - } else { - OutputChannelLogger.getMainChannel().log(localize("ErrorWhileInstallingCordovaSimulateDep", "Error while installing cordova-simulate dependency to the extension")); - packageFound.reject(localize("ErrorWhileInstallingCordovaSimulateDep", "Error while installing cordova-simulate dependency to the extension")); - } - }); - - let lastDotTime = 0; - const printDot = () => { - const now = Date.now(); - if (now - lastDotTime > 1500) { - lastDotTime = now; - OutputChannelLogger.getMainChannel().append("."); - } - }; + return new Promise((resolve, reject) => { + if (!this.packageInstallProc) { + this.packageInstallProc = cp.spawn(process.platform === "win32" ? "npm.cmd" : "npm", + ["install", this.CORDOVA_SIMULATE_PACKAGE, "--verbose", "--no-save"], + { cwd: path.dirname(findFileInFolderHierarchy(__dirname, "package.json")) }); + + this.packageInstallProc.once("exit", (code: number) => { + if (code === 0) { + this.simulatePackage = customRequire(this.CORDOVA_SIMULATE_PACKAGE); + resolve(this.simulatePackage); + } else { + OutputChannelLogger.getMainChannel().log(localize("ErrorWhileInstallingCordovaSimulateDep", "Error while installing cordova-simulate dependency to the extension")); + reject(localize("ErrorWhileInstallingCordovaSimulateDep", "Error while installing cordova-simulate dependency to the extension")); + } + }); + + let lastDotTime = 0; + const printDot = () => { + const now = Date.now(); + if (now - lastDotTime > 1500) { + lastDotTime = now; + OutputChannelLogger.getMainChannel().append("."); + } + }; - this.packageInstallProc.stdout.on("data", () => { - printDot(); - }); + this.packageInstallProc.stdout.on("data", () => { + printDot(); + }); - this.packageInstallProc.stderr.on("data", (data: Buffer) => { - printDot(); - }); - } else { - const packageCheck = setInterval(() => { - if (this.simulatePackage) { - clearInterval(packageCheck); - packageFound.resolve(this.simulatePackage); - } - }, 1000); - } - return packageFound.promise; + this.packageInstallProc.stderr.on("data", (data: Buffer) => { + printDot(); + }); + } else { + const packageCheck = setInterval(() => { + if (this.simulatePackage) { + clearInterval(packageCheck); + resolve(this.simulatePackage); + } + }, 1000); + } + }); } private isServerRunning(): boolean { diff --git a/src/utils/cordovaCommandHelper.ts b/src/utils/cordovaCommandHelper.ts index 634260a1..9c9cc307 100644 --- a/src/utils/cordovaCommandHelper.ts +++ b/src/utils/cordovaCommandHelper.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. import * as child_process from "child_process"; -import * as Q from "q"; import * as os from "os"; import { window, WorkspaceConfiguration, workspace, Uri, commands } from "vscode"; import { CordovaSessionManager } from "../extension/cordovaSessionManager"; @@ -24,7 +23,7 @@ export class CordovaCommandHelper { private static IONIC_DISPLAY_NAME: string = "Ionic"; private static readonly RESTART_SESSION_COMMAND: string = "workbench.action.debug.restart"; - public static executeCordovaCommand(projectRoot: string, command: string, useIonic: boolean = false) { + public static executeCordovaCommand(projectRoot: string, command: string, useIonic: boolean = false): Promise { let telemetryEventName: string = CordovaCommandHelper.CORDOVA_TELEMETRY_EVENT_NAME; let cliCommandName: string = CordovaCommandHelper.CORDOVA_CMD_NAME; let cliDisplayName: string = CordovaCommandHelper.CORDOVA_DISPLAY_NAME; @@ -66,38 +65,40 @@ export class CordovaCommandHelper { env: CordovaCommandHelper.getEnvArgs(projectRoot), envFile: CordovaCommandHelper.getEnvFile(projectRoot), }); - let process = child_process.exec(commandToExecute, { cwd: projectRoot, env }); - let deferred = Q.defer(); - process.on("error", (err: any) => { - // ENOENT error will be thrown if no Cordova.cmd or ionic.cmd is found - if (err.code === "ENOENT") { - window.showErrorMessage(localize("PackageNotFoundPleaseInstall", "{0} not found, please run \"npm install –g {1}\" to install {2} globally", cliDisplayName, cliDisplayName.toLowerCase(), cliDisplayName)); - } - deferred.reject(err); - }); - - process.stderr.on("data", (data: any) => { - logger.append(data); - }); - - process.stdout.on("data", (data: any) => { - logger.append(data); - }); - - process.stdout.on("close", () => { - logger.log(localize("FinishedExecuting", "########### FINISHED EXECUTING: {0} ###########", commandToExecute)); - deferred.resolve({}); + const execution = new Promise((resolve, reject) => { + const process = child_process.exec(commandToExecute, { cwd: projectRoot, env }); + + process.on("error", (err: any) => { + // ENOENT error will be thrown if no Cordova.cmd or ionic.cmd is found + if (err.code === "ENOENT") { + window.showErrorMessage(localize("PackageNotFoundPleaseInstall", "{0} not found, please run \"npm install –g {1}\" to install {2} globally", cliDisplayName, cliDisplayName.toLowerCase(), cliDisplayName)); + } + reject(err); + }); + + process.stderr.on("data", (data: any) => { + logger.append(data); + }); + + process.stdout.on("data", (data: any) => { + logger.append(data); + }); + + process.stdout.on("close", () => { + logger.log(localize("FinishedExecuting", "########### FINISHED EXECUTING: {0} ###########", commandToExecute)); + resolve({}); + }); }); return TelemetryHelper.determineProjectTypes(projectRoot) .then((projectType) => generator.add("projectType", projectType, false)) - .then(() => deferred.promise); + .then(() => execution); }); }); } - public static restartCordovaDebugging(projectRoot: string, cordovaSessionManager: CordovaSessionManager) { + public static restartCordovaDebugging(projectRoot: string, cordovaSessionManager: CordovaSessionManager): void { const cordovaDebugSession = cordovaSessionManager.getCordovaDebugSessionByProjectRoot(projectRoot); if (cordovaDebugSession) { switch (cordovaDebugSession.getStatus()) { @@ -145,40 +146,39 @@ export class CordovaCommandHelper { } } - private static selectPlatform(projectRoot: string, command: string, useIonic: boolean): Q.Promise { + private static selectPlatform(projectRoot: string, command: string, useIonic: boolean): Promise { let platforms = CordovaProjectHelper.getInstalledPlatforms(projectRoot); platforms = CordovaCommandHelper.filterAvailablePlatforms(platforms); - return Q({}) - .then(() => { - if (["prepare", "build", "run"].indexOf(command) > -1) { - if (platforms.length > 1) { - platforms.unshift("all"); - // Ionic doesn't support prepare and run command without platform - if (useIonic && (command === "prepare" || command === "run")) { - platforms.shift(); - } - return window.showQuickPick(platforms) - .then((platform) => { - if (!platform) { - throw new Error(localize("PlatformSelectionWasCancelled", "Platform selection was canceled. Please select target platform to continue!")); - } - - if (platform === "all") { - return ""; - } - - return platform; - }); - } else if (platforms.length === 1) { - return platforms[0]; - } else { - throw new Error(localize("NoAnyPlatformInstalled", "No any platforms installed")); + return new Promise((resolve, reject) => { + if (["prepare", "build", "run"].indexOf(command) > -1) { + if (platforms.length > 1) { + platforms.unshift("all"); + // Ionic doesn't support prepare and run command without platform + if (useIonic && (command === "prepare" || command === "run")) { + platforms.shift(); } + return window.showQuickPick(platforms) + .then((platform) => { + if (!platform) { + throw new Error(localize("PlatformSelectionWasCancelled", "Platform selection was canceled. Please select target platform to continue!")); + } + + if (platform === "all") { + return resolve(""); + } + + return resolve(platform); + }); + } else if (platforms.length === 1) { + return resolve(platforms[0]); + } else { + throw new Error(localize("NoAnyPlatformInstalled", "No any platforms installed")); } + } - return ""; - }); + return resolve(""); + }); } private static filterAvailablePlatforms(platforms: string[]): string[] { diff --git a/src/utils/cordovaProjectHelper.ts b/src/utils/cordovaProjectHelper.ts index 338af00c..07fb0abf 100644 --- a/src/utils/cordovaProjectHelper.ts +++ b/src/utils/cordovaProjectHelper.ts @@ -4,7 +4,6 @@ import * as child_process from "child_process"; import * as fs from "fs"; import * as path from "path"; -import * as Q from "q"; import { URL } from "url"; import * as semver from "semver"; import * as os from "os"; @@ -93,6 +92,18 @@ export class CordovaProjectHelper { } } + /** + * Helper (asynchronous) function to check if a file or directory exists + */ + public static exists(filename: string): Promise { + return fs.promises.stat(filename) + .then(() => { + return true; + }) + .catch(() => { + return false; + }); + } /** * Helper (synchronous) function to create a directory recursively @@ -109,7 +120,7 @@ export class CordovaProjectHelper { /** * Helper (synchronous) function to delete a directory recursively */ - public static deleteDirectoryRecursive(dirPath: string) { + public static deleteDirectoryRecursive(dirPath: string): void { if (fs.existsSync(dirPath)) { if (fs.lstatSync(dirPath).isDirectory()) { fs.readdirSync(dirPath).forEach(function (file) { @@ -127,24 +138,8 @@ export class CordovaProjectHelper { /** * Helper function to asynchronously copy a file */ - public static copyFile(from: string, to: string, encoding?: string): Q.Promise { - let deferred: Q.Deferred = Q.defer(); - let destFile: fs.WriteStream = fs.createWriteStream(to, { encoding: encoding }); - let srcFile: fs.ReadStream = fs.createReadStream(from, { encoding: encoding }); - destFile.on("finish", function (): void { - deferred.resolve({}); - }); - - destFile.on("error", function (e: Error): void { - deferred.reject(e); - }); - - srcFile.on("error", function (e: Error): void { - deferred.reject(e); - }); - - srcFile.pipe(destFile); - return deferred.promise; + public static copyFile(from: string, to: string): Promise { + return fs.promises.copyFile(from, to); } /** @@ -380,7 +375,7 @@ export class CordovaProjectHelper { return fs.existsSync(path.resolve(projectRoot, "tsconfig.json")); } - public static getCliCommand(fsPath: string) { + public static getCliCommand(fsPath: string): string { const cliName = CordovaProjectHelper.isIonicAngularProject(fsPath) ? "ionic" : "cordova"; const commandExtension = os.platform() === "win32" ? ".cmd" : ""; const command = cliName + commandExtension; diff --git a/src/utils/cordovaSimulateTelemetry.ts b/src/utils/cordovaSimulateTelemetry.ts index 4560e445..6ad5bdfc 100644 --- a/src/utils/cordovaSimulateTelemetry.ts +++ b/src/utils/cordovaSimulateTelemetry.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -import {Telemetry} from "./telemetry"; -import {TelemetryGenerator, TelemetryHelper} from "./telemetryHelper"; +import { Telemetry } from "./telemetry"; +import { TelemetryGenerator, TelemetryHelper } from "./telemetryHelper"; /** * This class is a telemetry wrapper compatible with cordova-simulate's telemetry. Cordova-simulate expects an object with a sendTelemetry() function, and calls it every time there is a diff --git a/src/utils/extensionHelper.ts b/src/utils/extensionHelper.ts index b551e5ac..2800ffb7 100644 --- a/src/utils/extensionHelper.ts +++ b/src/utils/extensionHelper.ts @@ -1,18 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -import * as Q from "q"; import * as http from "http"; import * as path from "path"; import * as fs from "fs"; import { CancellationToken } from "vscode"; import { CANCELLATION_ERROR_NAME } from "../debugger/cordovaDebugSession"; -export function generateRandomPortNumber() { +export function generateRandomPortNumber(): number { return Math.round(Math.random() * 40000 + 3000); } -export function retryAsync(func: () => Q.Promise, condition: (result: T) => boolean, maxRetries: number, iteration: number, delay: number, failure: string, cancellationToken?: CancellationToken): Q.Promise { +export function retryAsync(func: () => Promise, condition: (result: T) => boolean, maxRetries: number, iteration: number, delayTime: number, failure: string, cancellationToken?: CancellationToken): Promise { const retry = () => { if (cancellationToken && cancellationToken.isCancellationRequested) { let cancelError = new Error(CANCELLATION_ERROR_NAME); @@ -20,7 +19,7 @@ export function retryAsync(func: () => Q.Promise, condition: (result: T) = throw cancelError; } if (iteration < maxRetries) { - return Q.delay(delay).then(() => retryAsync(func, condition, maxRetries, iteration + 1, delay, failure, cancellationToken)); + return delay(delayTime).then(() => retryAsync(func, condition, maxRetries, iteration + 1, delayTime, failure, cancellationToken)); } throw new Error(failure); @@ -41,22 +40,22 @@ export function delay(duration: number): Promise { return new Promise(resolve => setTimeout(resolve, duration)); } -export function promiseGet(url: string, reqErrMessage: string): Q.Promise { - let deferred = Q.defer(); - let req = http.get(url, function (res) { - let responseString = ""; - res.on("data", (data: Buffer) => { - responseString += data.toString(); +export function promiseGet(url: string, reqErrMessage: string): Promise { + return new Promise((resolve, reject) => { + let req = http.get(url, function (res) { + let responseString = ""; + res.on("data", (data: Buffer) => { + responseString += data.toString(); + }); + res.on("end", () => { + resolve(responseString); + }); }); - res.on("end", () => { - deferred.resolve(responseString); + req.on("error", (err: Error) => { + this.outputLogger(reqErrMessage); + reject(err); }); }); - req.on("error", (err: Error) => { - this.outputLogger(reqErrMessage); - deferred.reject(err); - }); - return deferred.promise; } export function findFileInFolderHierarchy(dir: string, filename: string): string | null { diff --git a/src/utils/simulateHelper.ts b/src/utils/simulateHelper.ts index 79b6578c..cb92e451 100644 --- a/src/utils/simulateHelper.ts +++ b/src/utils/simulateHelper.ts @@ -4,7 +4,7 @@ import { SimulateTargets } from "../extension/simulate"; export class SimulateHelper { - public static isSimulateTarget(target: string) { + public static isSimulateTarget(target: string): boolean { return Object.values(SimulateTargets).includes(target as SimulateTargets); } } diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index 775964b8..4a60e38e 100755 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -7,9 +7,9 @@ import * as crypto from "crypto"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; -import * as Q from "q"; import * as winreg from "winreg"; import { settingsHome } from "./settingsHelper"; +import { DeferredPromise } from "../common/node/promise"; /** * Telemetry module specialized for vscode integration. @@ -50,7 +50,8 @@ export module Telemetry { public static userType: string; public static sessionId: string; public static optInCollectedForCurrentSession: boolean; - public static initDeferred: Q.Deferred = Q.defer(); + + private static initDeferred: DeferredPromise = new DeferredPromise(); private static userId: string; private static telemetrySettings: ITelemetrySettings = null; @@ -65,7 +66,11 @@ export module Telemetry { return path.join(settingsHome(), TelemetryUtils.TELEMETRY_SETTINGS_FILENAME); } - public static init(appVersion: string, initOptions: ITelemetryInitOptions): Q.Promise { + public static get initDeferredPromise(): Promise { + return TelemetryUtils.initDeferred.promise; + } + + public static init(appVersion: string, initOptions: ITelemetryInitOptions): Promise { TelemetryUtils.loadSettings(); if (initOptions.isExtensionProcess) { @@ -141,22 +146,19 @@ export module Telemetry { return userType; } - private static getRegistryValue(key: string, value: string, hive: string): Q.Promise { - let deferred: Q.Deferred = Q.defer(); - let regKey = new winreg({ - hive: hive, - key: key, - }); - regKey.get(value, function (err: any, itemValue: winreg.RegistryItem) { - if (err) { - // Fail gracefully by returning null if there was an error. - deferred.resolve(null); - } else { - deferred.resolve(itemValue.value); - } - }); + private static getRegistryValue(key: string, value: string, hive: string): Promise { + return new Promise((resolve, reject) => { + let regKey = new winreg({ hive, key }); - return deferred.promise; + regKey.get(value, function (err: any, itemValue: winreg.RegistryItem) { + if (err) { + // Fail gracefully by returning null if there was an error. + resolve(null); + } else { + resolve(itemValue.value); + } + }); + }); } /* @@ -184,34 +186,34 @@ export module Telemetry { fs.writeFileSync(TelemetryUtils.telemetrySettingsFile, JSON.stringify(TelemetryUtils.telemetrySettings)); } - private static getUniqueId(regValue: string, regHive: string, fallback: () => string): Q.Promise { + private static getUniqueId(regValue: string, regHive: string, fallback: () => string): Promise { let uniqueId: string; if (os.platform() === "win32") { return TelemetryUtils.getRegistryValue(TelemetryUtils.REGISTRY_SQMCLIENT_NODE, regValue, regHive) - .then(function (id: string): Q.Promise { + .then((id: string) => { if (id) { uniqueId = id.replace(/[{}]/g, ""); - return Q.resolve(uniqueId); + return uniqueId; } else { - return Q.resolve(fallback()); + return fallback(); } }); } else { - return Q.resolve(fallback()); + return Promise.resolve(fallback()); } } - private static getUserId(): Q.Promise { + private static getUserId(): Promise { let userId: string = TelemetryUtils.telemetrySettings.userId; if (!userId) { return TelemetryUtils.getUniqueId(TelemetryUtils.REGISTRY_USERID_VALUE, winreg.HKCU, TelemetryUtils.generateGuid) - .then(function (id: string): Q.Promise { + .then((id: string) => { TelemetryUtils.telemetrySettings.userId = id; - return Q.resolve(id); + return id; }); } else { TelemetryUtils.telemetrySettings.userId = userId; - return Q.resolve(userId); + return Promise.resolve(userId); } } } @@ -276,18 +278,18 @@ export module Telemetry { projectRoot: string; } - export function init(appNameValue: string, appVersion: string, initOptions: ITelemetryInitOptions): Q.Promise { + export function init(appNameValue: string, appVersion: string, initOptions: ITelemetryInitOptions): Promise { try { Telemetry.appName = appNameValue; return TelemetryUtils.init(appVersion, initOptions); } catch (err) { console.error(err); - return Q.reject(err); + return Promise.reject(err); } } - export function send(event: TelemetryEvent, ignoreOptIn: boolean = false): Q.Promise { - return TelemetryUtils.initDeferred.promise.then(function () { + export function send(event: TelemetryEvent, ignoreOptIn: boolean = false): Promise { + return TelemetryUtils.initDeferredPromise.then(function () { if (Telemetry.isOptedIn || ignoreOptIn) { if (event instanceof TelemetryActivity) { (event).end(); diff --git a/src/utils/telemetryHelper.ts b/src/utils/telemetryHelper.ts index cf12324e..3e9573c2 100755 --- a/src/utils/telemetryHelper.ts +++ b/src/utils/telemetryHelper.ts @@ -2,12 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. /* tslint:disable:no-use-before-declare */ -import { CordovaProjectHelper, IPluginDetails } from "./cordovaProjectHelper"; +import { CordovaProjectHelper, IPluginDetails, IProjectType } from "./cordovaProjectHelper"; import * as fs from "fs"; import * as path from "path"; -import * as Q from "q"; import { Telemetry } from "./telemetry"; -import { IProjectType } from "./cordovaProjectHelper"; import * as nls from "vscode-nls"; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); const localize = nls.loadMessageBundle(); @@ -81,12 +79,14 @@ export abstract class TelemetryGeneratorBase { return this; } - public time(name: string, codeToMeasure: { (): Thenable }): Q.Promise { + public time(name: string, codeToMeasure: { (): Promise }): Promise { let startTime: [number, number] = process.hrtime(); - return Q(codeToMeasure()).finally(() => this.finishTime(name, startTime)).fail((reason: any): Q.Promise => { - this.addError(reason); - throw reason; - }); + return codeToMeasure() + .finally(() => this.finishTime(name, startTime)) + .catch((reason: any) => { + this.addError(reason); + throw reason; + }); } public step(name: string): TelemetryGeneratorBase { @@ -163,32 +163,24 @@ export class TelemetryHelper { return new Telemetry.TelemetryActivity(eventName); } - public static determineProjectTypes(projectRoot: string): Q.Promise { - let promiseExists = (file: string) => { - let deferred = Q.defer(); - fs.exists(file, (exist: boolean) => deferred.resolve(exist)); - return deferred.promise; - }; - + public static determineProjectTypes(projectRoot: string): Promise { let ionicVersions = CordovaProjectHelper.checkIonicVersions(projectRoot); - let meteor = promiseExists(path.join(projectRoot, ".meteor")); - let mobilefirst = promiseExists(path.join(projectRoot, ".project")); - let phonegap = promiseExists(path.join(projectRoot, "www", "res", ".pgbomit")); - let cordova = promiseExists(path.join(projectRoot, "config.xml")); - return Q.all([meteor, mobilefirst, phonegap, cordova]) - .spread((isMeteor: boolean, isMobilefirst: boolean, isPhonegap: boolean, isCordova: boolean) => { - return { - isIonic1: ionicVersions.isIonic1, - isIonic2: ionicVersions.isIonic2, - isIonic3: ionicVersions.isIonic3, - isIonic4: ionicVersions.isIonic4, - isIonic5: ionicVersions.isIonic5, - isMeteor: isMeteor, - isMobilefirst: isMobilefirst, - isPhonegap: isPhonegap, - isCordova: isCordova, - }; - }); + let meteor = CordovaProjectHelper.exists(path.join(projectRoot, ".meteor")); + let mobilefirst = CordovaProjectHelper.exists(path.join(projectRoot, ".project")); + let phonegap = CordovaProjectHelper.exists(path.join(projectRoot, "www", "res", ".pgbomit")); + let cordova = CordovaProjectHelper.exists(path.join(projectRoot, "config.xml")); + return Promise.all([meteor, mobilefirst, phonegap, cordova]) + .then(([isMeteor, isMobilefirst, isPhonegap, isCordova]) => ({ + isIonic1: ionicVersions.isIonic1, + isIonic2: ionicVersions.isIonic2, + isIonic3: ionicVersions.isIonic3, + isIonic4: ionicVersions.isIonic4, + isIonic5: ionicVersions.isIonic5, + isMeteor: isMeteor, + isMobilefirst: isMobilefirst, + isPhonegap: isPhonegap, + isCordova: isCordova, + })); } public static telemetryProperty(propertyValue: any, pii?: boolean): ITelemetryPropertyInfo { @@ -213,7 +205,7 @@ export class TelemetryHelper { } } - public static generate(name: string, codeGeneratingTelemetry: { (telemetry: TelemetryGenerator): Thenable }): Q.Promise { + public static generate(name: string, codeGeneratingTelemetry: { (telemetry: TelemetryGenerator): Promise }): Promise { let generator: TelemetryGenerator = new TelemetryGenerator(name); return generator.time(null, () => codeGeneratingTelemetry(generator)).finally(() => generator.send()); } @@ -264,7 +256,7 @@ export class TelemetryHelper { // Write out new list of previousPlugins pluginFileJson.plugins = pluginsFileList; try { - fs.writeFileSync(pluginFilePath, JSON.stringify(pluginFileJson), "utf8"); + fs.writeFileSync(pluginFilePath, JSON.stringify(pluginFileJson)); } catch (err) { throw new Error(err.message + localize("CWDDoesntReferToTheWorkspaceRootDirectory", " It seems that 'cwd' parameter doesn't refer to the workspace root directory. Please make sure that 'cwd' contains the path to the workspace root directory.")); } diff --git a/src/utils/tsdHelper.ts b/src/utils/tsdHelper.ts index 5a130016..6f44eee3 100644 --- a/src/utils/tsdHelper.ts +++ b/src/utils/tsdHelper.ts @@ -3,7 +3,6 @@ import * as path from "path"; import * as fs from "fs"; -import * as Q from "q"; import { TelemetryHelper } from "./telemetryHelper"; import { CordovaProjectHelper } from "./cordovaProjectHelper"; import { findFileInFolderHierarchy } from "./extensionHelper"; @@ -24,13 +23,13 @@ export class TsdHelper { TelemetryHelper.generate("addTypings", (generator) => { generator.add("addedTypeDefinitions", typeDefsPath, false); - return Q.all(typeDefsPath.map((relativePath: string): Q.Promise => { + return Promise.all(typeDefsPath.map((relativePath: string) => { let src = path.resolve(TsdHelper.CORDOVA_TYPINGS_PATH, relativePath); let dest = path.resolve(typingsFolderPath, relativePath); // Check if we've previously copied these typings if (CordovaProjectHelper.existsSync(dest)) { - return Q.resolve(void 0); + return Promise.resolve(void 0); } // Check if the user has these typings somewhere else in his project @@ -40,7 +39,7 @@ export class TsdHelper { let userTypingsLongPath = path.join(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME, relativePath); if (CordovaProjectHelper.existsSync(userTypingsShortPath) || CordovaProjectHelper.existsSync(userTypingsLongPath)) { - return Q.resolve(void 0); + return Promise.resolve(void 0); } } @@ -49,31 +48,31 @@ export class TsdHelper { .then(() => installedTypeDefs.push(dest)); })); }) - .finally(() => { - if (installedTypeDefs.length === 0) return; - - let typingsFolder = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME); - let indexFile = path.resolve(typingsFolder, "cordova-typings.d.ts"); - - // Ensure that the 'typings' folder exits; if not, create it - if (!CordovaProjectHelper.existsSync(typingsFolder)) { - CordovaProjectHelper.makeDirectoryRecursive(typingsFolder); - } - - let references = CordovaProjectHelper.existsSync(indexFile) ? fs.readFileSync(indexFile, "utf8") : ""; - let referencesToAdd = installedTypeDefs - // Do not add references to typedefs that are not exist, - // this rarely happens if typedef file fails to copy - .filter(typeDef => CordovaProjectHelper.existsSync(typeDef)) - .map(typeDef => path.relative(typingsFolder, typeDef)) - // Avoid adding duplicates if reference already exist in index - .filter(typeDef => references.indexOf(typeDef) < 0) - .map(typeDef => `/// `); - - if (referencesToAdd.length === 0) return; - - fs.writeFileSync(indexFile, [references].concat(referencesToAdd).join("\n"), "utf8"); - }); + .finally(() => { + if (installedTypeDefs.length === 0) return; + + let typingsFolder = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME); + let indexFile = path.resolve(typingsFolder, "cordova-typings.d.ts"); + + // Ensure that the 'typings' folder exits; if not, create it + if (!CordovaProjectHelper.existsSync(typingsFolder)) { + CordovaProjectHelper.makeDirectoryRecursive(typingsFolder); + } + + let references = CordovaProjectHelper.existsSync(indexFile) ? fs.readFileSync(indexFile, "utf8") : ""; + let referencesToAdd = installedTypeDefs + // Do not add references to typedefs that are not exist, + // this rarely happens if typedef file fails to copy + .filter(typeDef => CordovaProjectHelper.existsSync(typeDef)) + .map(typeDef => path.relative(typingsFolder, typeDef)) + // Avoid adding duplicates if reference already exist in index + .filter(typeDef => references.indexOf(typeDef) < 0) + .map(typeDef => `/// `); + + if (referencesToAdd.length === 0) return; + + fs.writeFileSync(indexFile, [references].concat(referencesToAdd).join("\n")); + }); } public static removeTypings(typingsFolderPath: string, typeDefsToRemove: string[], projectRoot: string): void { @@ -105,7 +104,7 @@ export class TsdHelper { fs.writeFileSync(indexFile, referencesToPersist.join("\n"), "utf8"); } - private static installTypeDefinitionFile(src: string, dest: string): Q.Promise { + private static installTypeDefinitionFile(src: string, dest: string): Promise { // Ensure that the parent folder exits; if not, create the hierarchy of directories let parentFolder = path.resolve(dest, ".."); if (!CordovaProjectHelper.existsSync(parentFolder)) { diff --git a/test/cordova.test.ts b/test/cordova.test.ts index 0335eeb1..c0ea3358 100644 --- a/test/cordova.test.ts +++ b/test/cordova.test.ts @@ -3,7 +3,7 @@ import * as assert from "assert"; import * as path from "path"; -import * as Q from "q"; +import { delay } from "../src/utils/extensionHelper"; import * as rimraf from "rimraf"; import * as vscode from "vscode"; import * as testUtils from "./testUtils"; @@ -36,7 +36,7 @@ suite("extensionContext", () => { .then(() => { return vscode.commands.executeCommand("cordova.build"); }).then(() => { - return Q.delay(10000); + return delay(10000); }).then(_res => { let androidBuildPath = path.resolve(testProjectPath, "platforms", "android", "app", "build"); assert.ok(CordovaProjectHelper.existsSync(androidBuildPath)); @@ -53,7 +53,7 @@ suite("extensionContext", () => { .then((simHostStarted: boolean) => assert(simHostStarted, "The simulation host is running.")) .then(() => testUtils.isUrlReachable("http://localhost:8000/index.html")) .then((appHostStarted: boolean) => assert(appHostStarted, "The application host is running.")) - .fin(() => testUtils.removeCordovaComponents("platform", testProjectPath, ["android"])); + .finally(() => testUtils.removeCordovaComponents("platform", testProjectPath, ["android"])); }); }); }); diff --git a/test/debugger/cordovaIosDeviceLauncher.test.ts b/test/debugger/cordovaIosDeviceLauncher.test.ts index 782dc200..81e723df 100755 --- a/test/debugger/cordovaIosDeviceLauncher.test.ts +++ b/test/debugger/cordovaIosDeviceLauncher.test.ts @@ -1,55 +1,28 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -import * as mockery from "mockery"; import * as should from "should"; +import * as fs from "fs"; +import * as sinon from "sinon"; +import * as plist from "plist"; -// Used only for the type to allow mocking -import {CordovaIosDeviceLauncher as _CordovaIosDeviceLauncher} from "../../src/debugger/cordovaIosDeviceLauncher"; -import { suiteSetup } from "mocha"; - -let CordovaIosDeviceLauncher: typeof _CordovaIosDeviceLauncher; +import { CordovaIosDeviceLauncher } from "../../src/debugger/cordovaIosDeviceLauncher"; suite("cordovaIosDeviceLauncher", function () { - let plistMock: any = {}; - let fsMock: any = {}; - - suiteSetup(() => { - // warnOnUnregistered is set to false because of https://github.com/mfncooper/mockery/issues/59 - mockery.enable({warnOnReplace: false, useCleanCache: true, warnOnUnregistered: false}); - mockery.registerAllowables([ - "../../src/debugger/cordovaIosDeviceLauncher", - "path", - "q", - ]); - mockery.registerMock("child_process", {exec: () => {}}); - mockery.registerMock("net", {}); + let readdirMock; + let readFileSyncMock; + let parseMock; - mockery.registerMock("fs", fsMock); - mockery.registerMock("plist", plistMock); - CordovaIosDeviceLauncher = require("../../src/debugger/cordovaIosDeviceLauncher").CordovaIosDeviceLauncher; - }); suiteTeardown(() => { - mockery.disable(); - }); - - setup(() => { - let mocksToReset = [fsMock, plistMock]; - mocksToReset.forEach(mock => { - for (let prop in mock) { - if (mock.hasOwnProperty(prop)) { - delete mock[prop]; - } - } - }); + readdirMock.restore(); + readFileSyncMock.restore(); + parseMock.restore(); }); test("should be able to find the bundle identifier", () => { - fsMock.readFileSync = (_file: string) => ""; - fsMock.readdir = (_path: string, callback: (err: Error, result: string[]) => void) => callback(null, ["foo", "bar.xcodeproj"]); - plistMock.parse = (_file: string) => { - return {CFBundleIdentifier: "test.bundle.identifier"}; - }; + readdirMock = (sinon.stub(fs.promises, "readdir") as any).returns(Promise.resolve(["foo", "bar.xcodeproj"])); + readFileSyncMock = sinon.stub(fs, "readFileSync").returns(""); + parseMock = sinon.stub(plist, "parse").returns({CFBundleIdentifier: "test.bundle.identifier"}); return CordovaIosDeviceLauncher.getBundleIdentifier("testApp").then((bundleId) => { should.equal(bundleId, "test.bundle.identifier"); diff --git a/test/extension/telemetryHelper.test.ts b/test/extension/telemetryHelper.test.ts index bff21a20..1ea79451 100644 --- a/test/extension/telemetryHelper.test.ts +++ b/test/extension/telemetryHelper.test.ts @@ -6,6 +6,7 @@ import * as path from "path"; import * as fs from "fs"; import * as sinon from "sinon"; import { TelemetryHelper } from "../../src/utils/telemetryHelper"; +import { CordovaProjectHelper } from "../../src/utils/cordovaProjectHelper"; suite("telemetryHelper", function () { const testProjectPath = path.join(__dirname, "..", "resources", "testCordovaProject"); @@ -34,12 +35,12 @@ suite("telemetryHelper", function () { suite("not Ionic projects", function () { teardown(() => { - (fs.exists as any).restore(); + (CordovaProjectHelper.exists as any).restore(); }); test("should detect Cordova and Meteor project", () => { - sinon.stub(fs, "exists").callsFake((path: fs.PathLike, callback: (exists: boolean) => void) => { - return callback(path.toString().includes(".meteor") || path.toString().includes("config.xml")); + sinon.stub(CordovaProjectHelper, "exists").callsFake((filename: string): Promise => { + return Promise.resolve(filename.toString().includes(".meteor") || filename.toString().includes("config.xml")); }); return TelemetryHelper.determineProjectTypes(testProjectPath) diff --git a/test/testUtils.ts b/test/testUtils.ts index 2611d84a..a8c59671 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -5,50 +5,48 @@ import * as child_process from "child_process"; import * as fs from "fs"; import * as http from "http"; import * as os from "os"; -import * as Q from "q"; import {CordovaProjectHelper} from "../src/utils/cordovaProjectHelper"; -export function executeCordovaCommand(cwd: string, command: string): Q.Promise { - let deferred = Q.defer(); - let cordovaCmd = os.platform() === "win32" ? "cordova.cmd" : "cordova"; - let commandToExecute = cordovaCmd + " " + command; - let process = child_process.exec(commandToExecute, { cwd: cwd }); - - let stderr = ""; - process.stderr.on("data", (data: Buffer) => { - stderr += data.toString(); - }); - - process.stdout.on("data", (data: Buffer) => { - console.log(data.toString()); - }); - - process.on("error", function (err: any): void { - deferred.reject(err); +export function executeCordovaCommand(cwd: string, command: string): Promise { + return new Promise((resolve, reject) => { + let cordovaCmd = os.platform() === "win32" ? "cordova.cmd" : "cordova"; + let commandToExecute = cordovaCmd + " " + command; + let process = child_process.exec(commandToExecute, { cwd: cwd }); + + let stderr = ""; + process.stderr.on("data", (data: Buffer) => { + stderr += data.toString(); + }); + + process.stdout.on("data", (data: Buffer) => { + console.log(data.toString()); + }); + + process.on("error", function (err: any): void { + reject(err); + }); + + process.on("close" , exitCode => { + if (exitCode !== 0) { + return reject(`Cordova command failed with exit code ${exitCode}. Error: ${stderr}`); + } + resolve({}); + }); }); - - process.on("close" , exitCode => { - if (exitCode !== 0) { - return deferred.reject(`Cordova command failed with exit code ${exitCode}. Error: ${stderr}`); - } - deferred.resolve({}); - }); - - return deferred.promise; } -export function createCordovaProject(cwd: string, projectName: string): Q.Promise { +export function createCordovaProject(cwd: string, projectName: string): Promise { let cordovaCommandToRun = "create " + projectName; return executeCordovaCommand(cwd, cordovaCommandToRun); } -export function addCordovaComponents(componentName: string, projectRoot: string, componentsToAdd: string[]): Q.Promise { +export function addCordovaComponents(componentName: string, projectRoot: string, componentsToAdd: string[]): Promise { let cordovaCommandToRun = componentName + " add " + componentsToAdd.join(" "); return executeCordovaCommand(projectRoot, cordovaCommandToRun); } -export function removeCordovaComponents(componentName: string, projectRoot: string, componentsToRemove: string[]): Q.Promise { +export function removeCordovaComponents(componentName: string, projectRoot: string, componentsToRemove: string[]): Promise { let cordovaCommandToRun = componentName + " remove " + componentsToRemove.join(" "); return executeCordovaCommand(projectRoot, cordovaCommandToRun); } @@ -64,17 +62,15 @@ export function enumerateListOfTypeDefinitions(projectRoot: string): string[] { } } -export function isUrlReachable(url: string): Q.Promise { - let deferred = Q.defer(); - - http.get(url, (res) => { - deferred.resolve(true); - res.resume(); - }).on("error", (_err: Error) => { - deferred.resolve(false); +export function isUrlReachable(url: string): Promise { + return new Promise((resolve, reject) => { + http.get(url, (res) => { + resolve(true); + res.resume(); + }).on("error", (_err: Error) => { + resolve(false); + }); }); - - return deferred.promise; } export function convertWindowsPathToUnixOne(path: string) { diff --git a/typings/cordova-simulate/cordova-simulate.d.ts b/typings/cordova-simulate/cordova-simulate.d.ts index 45f90753..f363151c 100644 --- a/typings/cordova-simulate/cordova-simulate.d.ts +++ b/typings/cordova-simulate/cordova-simulate.d.ts @@ -2,9 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. declare module "cordova-simulate" { - import * as Q from "q"; - function simulate(opts: simulate.SimulateOptions): Q.Promise; + function simulate(opts: simulate.SimulateOptions): Promise; module simulate { interface PropertyDictionary { [propName: string]: any; @@ -36,8 +35,8 @@ declare module "cordova-simulate" { export class Simulator { constructor(opts: SimulateOptions); - startSimulation(): Q.Promise; - stopSimulation(): Q.Promise; + startSimulation(): Promise; + stopSimulation(): Promise; simHostUrl(): string; appUrl(): string; @@ -45,7 +44,7 @@ declare module "cordova-simulate" { isRunning(): boolean; } - export function launchBrowser(target: string, url: string): Q.Promise; + export function launchBrowser(target: string, url: string): Promise; export var dirs: { common: string, "sim-host": string