diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2b8102d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: node_js + +node_js: + - "8.0" + +script: + - npm run build + - npm test + - npm run tslint diff --git a/.vscode/launch.json b/.vscode/launch.json index ae25e4f..2da8ae4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,11 +2,11 @@ "version": "0.1.0", "configurations": [ { - "name": "launch as server", + "name": "Launch as server", "type": "node", "request": "launch", "program": "${workspaceRoot}/out/debug-adapter/nativeScriptDebug.js", - "runtimeArgs": ["--nolazy"], + "runtimeArgs": ["--nolazy"], "args": [ "--server=4712" ], "stopOnEntry": false, "sourceMaps": true, @@ -14,7 +14,7 @@ "cwd": "${workspaceFolder}" }, { - "name": "launch in extension host", + "name": "Launch in extension host", "type": "extensionHost", "request": "launch", // Path to VSCode executablensDebugClient @@ -24,61 +24,22 @@ ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": [ "${workspaceFolder}/out/**/*.js" ], + "outFiles": [ "${workspaceFolder}/out/**/*.js" ] }, - { - "name": "run tests on mac", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/src/tests/config/mocha.opts", - "--config", "${workspaceRoot}/src/tests/config/mac.json", - "${workspaceRoot}/out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out", - "cwd": "${workspaceRoot}" - }, - { - "name": "run tests on win", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/src/tests/config/mocha.opts", - "--config", "${workspaceRoot}/src/tests/config/win.json", - "${workspaceRoot}/out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out", - "cwd": "${workspaceRoot}" - }, - { - "name": "run tests (custom)", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/src/tests/config/mocha.opts", - "--config", "${workspaceRoot}/src/tests/config/custom.json", - "${workspaceRoot}/out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out", - "cwd": "${workspaceRoot}" - } + { + "name": "Run tests", + "type": "node", + "request": "launch", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", "test", "--" + ] + } ], - "compounds": [ - { - "name": "Extension + Server", - "configurations": [ "launch in extension host", "launch as server" ] - } - ] + "compounds": [ + { + "name": "Extension + Server", + "configurations": [ "Launch in extension host", "Launch as server" ] + } + ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index de4c0bf..cd7cee8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.insertSpaces": true, "files.trimTrailingWhitespace": true, + "editor.renderWhitespace": "all", "files.exclude": { ".git": true, "bin": true, diff --git a/package.json b/package.json index 06664f9..40dca2c 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "lodash": "^4.17.10", - "node-ipc": "8.10.3", + "semver": "^5.5.0", "universal-analytics": "0.4.13", "uuid": "^3.2.1", "vscode-chrome-debug-core": "^3.23.11", @@ -34,15 +34,15 @@ }, "devDependencies": { "@types/lodash": "^4.14.109", - "@types/mocha": "2.2.41", + "@types/mocha": "^5.2.1", "@types/node": "6.0.46", - "@types/source-map": "~0.1.0", - "chrome-remote-debug-protocol": "https://github.com/roblourens/chrome-remote-debug-protocol/tarball/master", - "mocha": "2.5.3", + "mocha": "^5.2.0", + "sinon": "^5.0.10", + "tslint": "5.10.0", + "tslint-eslint-rules": "^5.3.1", "typescript": "2.6.2", "vsce": "~1.36.0", "vscode": "~1.1.10", - "vscode-debugadapter-testsupport": "1.26.0", "vscode-debugprotocol": "^1.28.0-pre.1" }, "scripts": { @@ -52,9 +52,8 @@ "package": "vsce package", "full-build": "npm run clean && npm install && npm run build && npm run package", "launch-as-server": "node --nolazy ./out/debug-adapter/nativeScriptDebug.js --server=4712", - "test-mac": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/mac.json ./out/tests", - "test-win": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/win.json ./out/tests", - "test-custom": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/custom.json ./out/tests" + "test": "mocha --opts ./src/tests/config/mocha.opts", + "tslint": "tslint -p ./src/tsconfig.json -c tslint.json" }, "main": "./out/main", "activationEvents": [ diff --git a/src/analytics/analyticsBaseInfo.ts b/src/analytics/analyticsBaseInfo.ts index 0de72b5..6651382 100644 --- a/src/analytics/analyticsBaseInfo.ts +++ b/src/analytics/analyticsBaseInfo.ts @@ -2,12 +2,12 @@ export enum OperatingSystem { Windows, Linux, OSX, - Other + Other, } -export interface AnalyticsBaseInfo { - operatingSystem: OperatingSystem, - cliVersion: string, - extensionVersion: string, - clientId: string -} \ No newline at end of file +export interface IAnalyticsBaseInfo { + operatingSystem: OperatingSystem; + cliVersion: string; + extensionVersion: string; + clientId: string; +} diff --git a/src/analytics/analyticsService.ts b/src/analytics/analyticsService.ts index b488e36..e25dbb0 100644 --- a/src/analytics/analyticsService.ts +++ b/src/analytics/analyticsService.ts @@ -1,82 +1,91 @@ -import * as os from 'os'; -import { Version } from '../common/version'; -import { GUAService } from './guaService'; -import { AnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; -import { Services } from '../services/extensionHostServices'; -import * as utils from '../common/utilities'; +import * as _ from 'lodash'; +import * as uuid from 'uuid'; import * as vscode from 'vscode'; -import * as uuid from "uuid"; +import { ILogger } from '../common/logger'; +import { services } from '../services/extensionHostServices'; +import { IAnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; +import { GUAService } from './guaService'; export class AnalyticsService { - private static HAS_ANALYTICS_PROMPT_SHOWN_KEY = "nativescript.hasAnalyticsPromptShown"; - private static CLIENT_ID_KEY = "nativescript.analyticsClientId"; + private static HAS_ANALYTICS_PROMPT_SHOWN_KEY = 'nativescript.hasAnalyticsPromptShown'; + private static CLIENT_ID_KEY = 'nativescript.analyticsClientId'; + private static DOCS_LINK = 'https://github.com/NativeScript/nativescript-vscode-extension/blob/master/README.md#how-to-disable-the-analytics'; private static ANALYTICS_PROMPT_MESSAGE = `Help us improve the NativeScript extension by allowing Progress to collect anonymous usage data. - For more information about the gathered information and how it is used, read our [privacy statement](https://www.progress.com/legal/privacy-policy). - You can [disable the analytics and data collection](https://github.com/NativeScript/nativescript-vscode-extension/blob/master/README.md#how-to-disable-the-analytics) at any given time. + For more information about the gathered information and how it is used, + read our [privacy statement](https://www.progress.com/legal/privacy-policy). + You can [disable the analytics and data collection](${AnalyticsService.DOCS_LINK}) at any given time. Do you want to enable analytics?`; - private static ANALYTICS_PROMPT_ACCEPT_ACTION = "Yes"; - private static ANALYTICS_PROMPT_DENY_ACTION = "No"; + + private static ANALYTICS_PROMPT_ACCEPT_ACTION = 'Yes'; + private static ANALYTICS_PROMPT_DENY_ACTION = 'No'; + + private static getOperatingSystem(): OperatingSystem { + switch (process.platform) { + case 'win32': + return OperatingSystem.Windows; + case 'darwin': + return OperatingSystem.OSX; + case 'linux': + case 'freebsd': + return OperatingSystem.Linux; + default: + return OperatingSystem.Other; + } + } private _globalState: vscode.Memento; - private _baseInfo: AnalyticsBaseInfo; + private _logger: ILogger; + private _baseInfo: IAnalyticsBaseInfo; private _gua: GUAService; private _analyticsEnabled: boolean; - constructor(globalState: vscode.Memento) { + constructor(globalState: vscode.Memento, cliVersion: string, extensionVersion: string, logger: ILogger) { this._globalState = globalState; + this._logger = logger; vscode.workspace.onDidChangeConfiguration(() => this.updateAnalyticsEnabled()); this._baseInfo = { - cliVersion: Services.cli().version.toString(), - extensionVersion: utils.getInstalledExtensionVersion().toString(), + cliVersion, + clientId: this.getOrGenerateClientId(), + extensionVersion, operatingSystem: AnalyticsService.getOperatingSystem(), - clientId: this.getOrGenerateClientId() }; } public launchDebugger(request: string, platform: string): Promise { - if(this._analyticsEnabled) { + if (this._analyticsEnabled) { try { return this._gua.launchDebugger(request, platform); - } catch(e) {} + } catch (e) { + this._logger.log(`Analytics error: ${_.isString(e) ? e : e.message}`); + } } return Promise.resolve(); } public runRunCommand(platform: string): Promise { - if(this._analyticsEnabled) { + if (this._analyticsEnabled) { try { return this._gua.runRunCommand(platform); - } catch(e) { } + } catch (e) { + this._logger.log(`Analytics error: ${_.isString(e) ? e : e.message}`); + } } return Promise.resolve(); } - private static getOperatingSystem() : OperatingSystem { - switch(process.platform) { - case 'win32': - return OperatingSystem.Windows; - case 'darwin': - return OperatingSystem.OSX; - case 'linux': - case 'freebsd': - return OperatingSystem.Linux; - default: - return OperatingSystem.Other; - }; - } - - public initialize() : void { + public initialize(): void { const hasAnalyticsPromptShown = this._globalState.get(AnalyticsService.HAS_ANALYTICS_PROMPT_SHOWN_KEY); - if(!hasAnalyticsPromptShown) { + + if (!hasAnalyticsPromptShown) { vscode.window.showInformationMessage(AnalyticsService.ANALYTICS_PROMPT_MESSAGE, AnalyticsService.ANALYTICS_PROMPT_ACCEPT_ACTION, - AnalyticsService.ANALYTICS_PROMPT_DENY_ACTION + AnalyticsService.ANALYTICS_PROMPT_DENY_ACTION, ) - .then(result => this.onAnalyticsMessageConfirmation(result)); + .then((result) => this.onAnalyticsMessageConfirmation(result)); return; } @@ -87,7 +96,7 @@ export class AnalyticsService { private getOrGenerateClientId(): string { let clientId = this._globalState.get(AnalyticsService.CLIENT_ID_KEY); - if(!clientId) { + if (!clientId) { clientId = uuid.v4(); this._globalState.update(AnalyticsService.CLIENT_ID_KEY, clientId); } @@ -95,20 +104,20 @@ export class AnalyticsService { return clientId; } - private onAnalyticsMessageConfirmation(result: string) : void { + private onAnalyticsMessageConfirmation(result: string): void { const shouldEnableAnalytics = result === AnalyticsService.ANALYTICS_PROMPT_ACCEPT_ACTION ? true : false; this._globalState.update(AnalyticsService.HAS_ANALYTICS_PROMPT_SHOWN_KEY, true); - Services.workspaceConfigService().isAnalyticsEnabled = shouldEnableAnalytics; + services.workspaceConfigService.isAnalyticsEnabled = shouldEnableAnalytics; this.updateAnalyticsEnabled(); } private updateAnalyticsEnabled() { - this._analyticsEnabled = Services.workspaceConfigService().isAnalyticsEnabled; + this._analyticsEnabled = services.workspaceConfigService.isAnalyticsEnabled; - if(this._analyticsEnabled && !this._gua) { + if (this._analyticsEnabled && !this._gua) { this._gua = new GUAService('UA-111455-29', this._baseInfo); } } -} \ No newline at end of file +} diff --git a/src/analytics/guaService.ts b/src/analytics/guaService.ts index 6d50f49..8ecf4e4 100644 --- a/src/analytics/guaService.ts +++ b/src/analytics/guaService.ts @@ -1,5 +1,5 @@ import * as ua from 'universal-analytics'; -import { AnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; +import { IAnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; /** * Google Universal Analytics Service @@ -8,38 +8,42 @@ export class GUAService { private _visitor: any; private _getBasePayload: () => any; - constructor(trackingId: string, baseInfo: AnalyticsBaseInfo) { + constructor(trackingId: string, baseInfo: IAnalyticsBaseInfo) { this._visitor = ua(trackingId, baseInfo.clientId, { requestOptions: {}, strictCidFormat: false }); this._getBasePayload = () => { return { - cid: baseInfo.clientId, - dh: 'ns-vs-extension.org', cd5: baseInfo.cliVersion, cd6: OperatingSystem[baseInfo.operatingSystem], - cd7: baseInfo.extensionVersion + cd7: baseInfo.extensionVersion, + cid: baseInfo.clientId, + dh: 'ns-vs-extension.org', }; }; } public launchDebugger(request: string, platform: string): Promise { - let payload = this._getBasePayload(); + const payload = this._getBasePayload(); + payload.ec = 'vscode-extension-debug'; // event category payload.ea = `debug-${request}-on-${platform}`; // event action + return this.sendEvent(payload); } public runRunCommand(platform: string): Promise { - let payload = this._getBasePayload(); + const payload = this._getBasePayload(); + payload.ec = 'vscode-extension-command'; // event category payload.ea = `command-run-on-${platform}`; // event action + return this.sendEvent(payload); } private sendEvent(params): Promise { return new Promise((res, rej) => { - this._visitor.event(params, err => { + this._visitor.event(params, (err) => { return err ? rej(err) : res(); }); }); } -} \ No newline at end of file +} diff --git a/src/common/extensionProtocol.ts b/src/common/extensionProtocol.ts new file mode 100644 index 0000000..67bc1b7 --- /dev/null +++ b/src/common/extensionProtocol.ts @@ -0,0 +1,14 @@ +export interface IRequest { + id: string; + service: string; + method: string; + args: any[]; +} + +export interface IResponse { + requestId: string; + result: object; +} + +export const BEFORE_DEBUG_START = 'before-debug-start'; +export const NS_DEBUG_ADAPTER_MESSAGE = 'ns-debug-adapter-message'; diff --git a/src/common/logger.ts b/src/common/logger.ts index 49fde9d..215398e 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -1,102 +1,13 @@ -import * as fs from 'fs'; - -export enum LoggerMessageType { - Log, - Info, - Warning, - Error +enum LogLevel { + Verbose = 0, + Log = 1, + Warn = 2, + Error = 3, + Stop = 4, } -export interface LoggerMessageEventArgs { - message: string, - type: LoggerMessageType +interface ILogger { + log(msg: string, level?: LogLevel): void; } -export type LoggerHandler = ((args: LoggerMessageEventArgs) => void); -type TaggedLoggerHandler = { handler: LoggerHandler, tags: string[] }; - -/** - * The logger is a singleton. - */ -export class Logger { - private _handlers: TaggedLoggerHandler[]; - - constructor() { - this._handlers = []; - } - - private handleMessage(message: string, type: LoggerMessageType = LoggerMessageType.Log, tag: string = null) { - for (let handler of this._handlers) { - if (!handler.tags || handler.tags.length == 0 || handler.tags.indexOf(tag) > -1) { - handler.handler({ message: message, type: type }); - } - } - } - - public log(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Log, tag); - } - - public info(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Info, tag); - } - - public warn(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Warning, tag); - } - - public error(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Error, tag); - } - - public addHandler(handler: LoggerHandler, tags: string[] = null) { - tags = tags || []; - this._handlers.push({ handler: handler, tags: tags }); - } - - /** - * Removes all occurrence of this handler, ignoring the associated tags - */ - public removeHandler(handlerToRemove: LoggerHandler) { - let i = this._handlers.length; - while (i--) { - if (this._handlers[i].handler == handlerToRemove) { - this._handlers.splice(i, 1); - } - } - } -} - -export namespace Tags { - export const FrontendMessage: string = "LoggerTag.FrontendMessage"; -} - -export namespace Handlers { - export function stdStreamsHandler(args: LoggerMessageEventArgs) { - var message = args.message.replace(/\n$/, ""); - switch(args.type) { - case LoggerMessageType.Log: - console.log(message); - break; - case LoggerMessageType.Info: - console.info(message); - break; - case LoggerMessageType.Warning: - console.warn(message); - break; - case LoggerMessageType.Error: - console.error(message); - break; - } - }; - - export function createStreamHandler(stream: fs.WriteStream, encoding: string = 'utf8'): LoggerHandler { - let isStreamClosed = false; - stream.on('close', () => { isStreamClosed = true; }); - return (args: LoggerMessageEventArgs) => { - if (stream && !isStreamClosed) { - stream.write(args.message, encoding); - } - } - } -} \ No newline at end of file +export { ILogger, LogLevel }; diff --git a/src/common/utilities.ts b/src/common/utilities.ts index c54e049..dfde8c8 100644 --- a/src/common/utilities.ts +++ b/src/common/utilities.ts @@ -1,34 +1,25 @@ -import {Version} from './version'; -import {ChildProcess, exec} from 'child_process'; +import { ChildProcess, exec } from 'child_process'; import * as os from 'os'; - -export function getInstalledExtensionVersion(): Version { - return Version.parse(require('../../package.json').version); -} - -export function getMinSupportedCliVersion(): Version { - return Version.parse(require('../../package.json').minNativescriptCliVersion); -} - -export function killProcess(childProcess: ChildProcess) : void { +export function killProcess(childProcess: ChildProcess): void { switch (process.platform) { - case "win32": + case 'win32': exec(`taskkill /pid ${childProcess.pid} /T /F`); break; default: - childProcess.kill("SIGINT"); + childProcess.kill('SIGINT'); break; } } export const enum Platform { - Windows, OSX, Linux + Windows, OSX, Linux, } export function getPlatform(): Platform { const platform = os.platform(); + return platform === 'darwin' ? Platform.OSX : platform === 'win32' ? Platform.Windows : Platform.Linux; diff --git a/src/common/version.ts b/src/common/version.ts deleted file mode 100644 index 59ba47d..0000000 --- a/src/common/version.ts +++ /dev/null @@ -1,28 +0,0 @@ -export class Version { - private _version: number[]; - - public static parse(versionStr: string): Version { - if (versionStr === null) { - return null; - } - let version: number[] = versionStr.split('.').map((str, index, array) => parseInt(str)); - for(let i = version.length; i < 3; i++) { - version.push(0); - } - return new Version(version); - } - - constructor(version: number[]) { - this._version = version; - } - - public toString(): string { - return `${this._version[0]}.${this._version[1]}.${this._version[2]}`; - } - - public compareBySubminorTo(other: Version): number { - let v1 = this._version; - let v2 = other._version; - return (v1[0] - v2[0] != 0) ? (v1[0] - v2[0]) : (v1[1] - v2[1] != 0) ? v1[1] - v2[1] : v1[2] - v2[2]; - } -} diff --git a/src/common/workspaceConfigService.ts b/src/common/workspaceConfigService.ts index 862ef29..89f82af 100644 --- a/src/common/workspaceConfigService.ts +++ b/src/common/workspaceConfigService.ts @@ -12,4 +12,4 @@ export class WorkspaceConfigService { public get tnsPath(): string { return vscode.workspace.getConfiguration('nativescript').get('tnsPath') as string; } -} \ No newline at end of file +} diff --git a/src/debug-adapter/nativeScriptDebug.ts b/src/debug-adapter/nativeScriptDebug.ts index dac102d..c229d87 100644 --- a/src/debug-adapter/nativeScriptDebug.ts +++ b/src/debug-adapter/nativeScriptDebug.ts @@ -1,43 +1,24 @@ -import { chromeConnection, chromeTargetDiscoveryStrategy, ChromeDebugAdapter, telemetry, logger, ChromeDebugSession, UrlPathTransformer } from 'vscode-chrome-debug-core'; -import * as path from 'path'; import * as os from 'os'; -import { NativeScriptDebugAdapter } from './nativeScriptDebugAdapter'; +import * as path from 'path'; +import { chromeConnection, ChromeDebugSession } from 'vscode-chrome-debug-core'; +import { AndroidProject } from '../project/androidProject'; +import { IosProject } from '../project/iosProject'; +import { NativeScriptCli } from '../project/nativeScriptCli'; +import { nativeScriptDebugAdapterGenerator } from './nativeScriptDebugAdapter'; import { NativeScriptPathTransformer } from './nativeScriptPathTransformer'; -import * as uuid from "uuid"; - -class Discovery extends chromeTargetDiscoveryStrategy.ChromeTargetDiscovery { - constructor() { - super(logger, new telemetry.TelemetryReporter()) - } - - public getTarget(address: string, port: number, targetFilter?: any, targetUrl?: string): Promise { - return Promise.resolve({ - webSocketDebuggerUrl: `ws://${address}:${port}`, - description: "NS Debug Target", - id: uuid.v4(), - title: "NS Debug Target", - type: "node", - devtoolsFrontendUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=${address}:${port}` - }); - } - - async getAllTargets(address: string, port: number, targetFilter?: chromeConnection.ITargetFilter, targetUrl?: string): Promise { - const target = await this.getTarget(address, port); - return Promise.resolve([ target ]); - } -} +import { NativeScriptTargetDiscovery } from './nativeScriptTargetDiscovery'; class NSAndroidConnection extends chromeConnection.ChromeConnection { constructor() { - super(new Discovery()); + super(new NativeScriptTargetDiscovery()); } } ChromeDebugSession.run(ChromeDebugSession.getSession( { - logFilePath: path.join(os.tmpdir(), 'nativescript-extension.txt'), - adapter: NativeScriptDebugAdapter, - extensionName: 'nativescript-extension', + adapter: nativeScriptDebugAdapterGenerator(IosProject, AndroidProject, NativeScriptCli), chromeConnection: NSAndroidConnection, - pathTransformer: NativeScriptPathTransformer + extensionName: 'nativescript-extension', + logFilePath: path.join(os.tmpdir(), 'nativescript-extension.txt'), + pathTransformer: NativeScriptPathTransformer, })); diff --git a/src/debug-adapter/nativeScriptDebugAdapter.ts b/src/debug-adapter/nativeScriptDebugAdapter.ts index 79223bf..6cc1196 100644 --- a/src/debug-adapter/nativeScriptDebugAdapter.ts +++ b/src/debug-adapter/nativeScriptDebugAdapter.ts @@ -1,178 +1,209 @@ -import { IInitializeRequestArgs, ChromeDebugAdapter, IAttachRequestArgs, ISetBreakpointsArgs, ISetBreakpointsResponseBody, ILaunchRequestArgs, ITelemetryPropertyCollector } from 'vscode-chrome-debug-core'; -import { OutputEvent, TerminatedEvent } from 'vscode-debugadapter'; -import * as utils from '../common/utilities'; -import {IosProject} from '../project/iosProject'; -import {AndroidProject} from '../project/androidProject'; import { ChildProcess } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import {DebugResult} from '../project/project'; -import {Services} from '../services/debugAdapterServices' +import { ChromeDebugAdapter, logger } from 'vscode-chrome-debug-core'; +import { Event, OutputEvent, TerminatedEvent } from 'vscode-debugadapter'; +import * as extProtocol from '../common/extensionProtocol'; +import * as utils from '../common/utilities'; +import { AndroidProject } from '../project/androidProject'; +import { IosProject } from '../project/iosProject'; +import { NativeScriptCli } from '../project/nativeScriptCli'; +import { IDebugResult } from '../project/project'; + +export function nativeScriptDebugAdapterGenerator(iosProject: typeof IosProject, + androidProject: typeof AndroidProject, + nativeScriptCli: typeof NativeScriptCli) { + return class NativeScriptDebugAdapter extends ChromeDebugAdapter { + private _tnsProcess: ChildProcess; + private _idCounter = 0; + private _pendingRequests: object = {}; + + public attach(args: any): Promise { + return this.processRequestAndAttach(args); + } -export class NativeScriptDebugAdapter extends ChromeDebugAdapter { - private _tnsProcess: ChildProcess; + public launch(args: any): Promise { + return this.processRequestAndAttach(args); + } - public async attach(args: any): Promise { - return await this.processRequestAndAttach(args); - } + public disconnect(args: any): void { + super.disconnect(args); - public async launch(args: any, telemetryPropertyCollector?: ITelemetryPropertyCollector): Promise { - return await this.processRequestAndAttach(args); - } + if (this._tnsProcess) { + this._tnsProcess.stdout.removeAllListeners(); + this._tnsProcess.stderr.removeAllListeners(); + this._tnsProcess.removeAllListeners(); + utils.killProcess(this._tnsProcess); + } + } - public disconnect(args: any): void { - super.disconnect(args); + public onExtensionResponse(response) { + this._pendingRequests[response.requestId](response.result); + delete this._pendingRequests[response.requestId]; + } - if (this._tnsProcess) { - this._tnsProcess.stdout.removeAllListeners(); - this._tnsProcess.stderr.removeAllListeners(); - this._tnsProcess.removeAllListeners(); - utils.killProcess(this._tnsProcess); + private async processRequestAndAttach(args: any) { + const transformedArgs = await this.processRequest(args); + + (this.pathTransformer as any).setTargetPlatform(args.platform); + (ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000; + + return super.attach(transformedArgs); } - } - - private async processRequestAndAttach(args: any) { - const transformedArgs = await this.processRequest(args); - (this.pathTransformer as any).setTargetPlatform(args.platform); - (ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000; - - return super.attach(transformedArgs); - } - - private async processRequest(args: any) : Promise { - args = this.translateArgs(args); - Services.appRoot = args.appRoot; - Services.extensionClient().cleanBeforeDebug(); - const settings = await Services.extensionClient().getInitSettings(); - - Services.cliPath = settings.tnsPath || Services.cliPath; - - const project = args.platform == "ios" ? - new IosProject(args.appRoot, Services.cli()) : - new AndroidProject(args.appRoot, Services.cli()); - - Services.extensionClient().analyticsLaunchDebugger({ request: args.request, platform: args.platform }); - - // Run CLI Command - this.log(`[NSDebugAdapter] Using tns CLI v${project.cli.version.version} on path '${project.cli.path}'\n`); - this.log('[NSDebugAdapter] Running tns command...\n'); - let cliCommand: DebugResult; - - if (args.request === "launch") { - let tnsArgs = args.tnsArgs; - - // For iOS the TeamID is required if there's more than one. - // Therefore if not set, show selection to the user. - if(args.platform && args.platform.toLowerCase() === 'ios') { - let teamId = this.getTeamId(path.join(Services.appRoot, 'app'), tnsArgs); - if(!teamId) { - let selectedTeam = (await Services.extensionClient().selectTeam()); - if(selectedTeam) { - // add the selected by the user Team Id - tnsArgs = (tnsArgs || []).concat(['--teamId', selectedTeam.id]); - this.log(`[NSDebugAdapter] Using iOS Team ID '${selectedTeam.id}', you can change this in the workspace settings.\n`); + + private async processRequest(args: any): Promise { + args = this.translateArgs(args); + + this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START)); + + const tnsPath = await this.callRemoteMethod('workspaceConfigService', 'tnsPath'); + const cli = new nativeScriptCli(tnsPath, logger); + + const project = args.platform === 'ios' ? + new iosProject(args.appRoot, cli) : + new androidProject(args.appRoot, cli); + + this.callRemoteMethod('analyticsService', 'launchDebugger', args.request, args.platform); + + // Run CLI Command + const version = project.cli.executeGetVersion(); + + this.log(`[NSDebugAdapter] Using tns CLI v${version} on path '${project.cli.path}'\n`); + this.log('[NSDebugAdapter] Running tns command...\n'); + let cliCommand: IDebugResult; + + if (args.request === 'launch') { + let tnsArgs = args.tnsArgs; + + // For iOS the TeamID is required if there's more than one. + // Therefore if not set, show selection to the user. + if (args.platform && args.platform.toLowerCase() === 'ios') { + const teamId = this.getTeamId(path.join(args.appRoot, 'app'), tnsArgs); + + if (!teamId) { + const selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam'); + + if (selectedTeam) { + // add the selected by the user Team Id + tnsArgs = (tnsArgs || []).concat(['--teamId', selectedTeam.id]); + this.log(`[NSDebugAdapter] Using iOS Team ID '${selectedTeam.id}', you can change this in the workspace settings.\n`); + } } } + + cliCommand = project.debug({ stopOnEntry: args.stopOnEntry, watch: args.watch }, tnsArgs); + } else if (args.request === 'attach') { + cliCommand = project.attach(args.tnsArgs); } - cliCommand = project.debug({ stopOnEntry: args.stopOnEntry, watch: args.watch }, tnsArgs); - } - else if (args.request === "attach") { - cliCommand = project.attach(args.tnsArgs); - } + if (cliCommand.tnsProcess) { + this._tnsProcess = cliCommand.tnsProcess; + cliCommand.tnsProcess.stdout.on('data', (data) => { this.log(data.toString()); }); + cliCommand.tnsProcess.stderr.on('data', (data) => { this.log(data.toString()); }); - if (cliCommand.tnsProcess) { - this._tnsProcess = cliCommand.tnsProcess; - cliCommand.tnsProcess.stdout.on('data', data => { this.log(data.toString()); }); - cliCommand.tnsProcess.stderr.on('data', data => { this.log(data.toString()); }); + cliCommand.tnsProcess.on('close', (code, signal) => { + this.log(`[NSDebugAdapter] The tns command finished its execution with code ${code}.\n`); - cliCommand.tnsProcess.on('close', (code, signal) => { - this.log(`[NSDebugAdapter] The tns command finished its execution with code ${code}.\n`); + // Sometimes we execute "tns debug android --start" and the process finishes + // which is totally fine. If there's an error we need to Terminate the session. + if (code !== 0) { + this.log(`The tns command finished its execution with code ${code}`); + this._session.sendEvent(new TerminatedEvent()); + } + }); + } - // Sometimes we execute "tns debug android --start" and the process finishes - // which is totally fine. If there's an error we need to Terminate the session. - if(code !== 0) { - this.log(`The tns command finished its execution with code ${code}`); - this._session.sendEvent(new TerminatedEvent()); - } + this.log('[NSDebugAdapter] Watching the tns CLI output to receive a connection token\n'); + + return new Promise ((res, rej) => { + cliCommand.tnsOutputEventEmitter.on('readyForConnection', (connectionToken: string | number) => { + this.log(`[NSDebugAdapter] Ready to attach to application on ${connectionToken}\n`); + args.port = connectionToken; + + res(args); + }); }); } - this.log('[NSDebugAdapter] Watching the tns CLI output to receive a connection token\n'); + private translateArgs(args): any { + if (args.diagnosticLogging) { + args.trace = args.diagnosticLogging; + } - return new Promise ((res, rej) => { - cliCommand.tnsOutputEventEmitter.on('readyForConnection', (connectionToken: string | number) => { - this.log(`[NSDebugAdapter] Ready to attach to application on ${connectionToken}\n`); - args.port = connectionToken; + if (args.appRoot) { + args.webRoot = args.appRoot; + } - res(args); - }); - }); - } + return args; + } - private translateArgs(args): any { - if(args.diagnosticLogging) { - args.trace = args.diagnosticLogging; + private log(text: string): void { + this._session.sendEvent(new OutputEvent(text)); } - if(args.appRoot) { - args.webRoot = args.appRoot; + private getTeamId(appRoot: string, tnsArgs?: string[]): string { + // try to get the TeamId from the TnsArgs + if (tnsArgs) { + const teamIdArgIndex = tnsArgs.indexOf('--teamId'); + + if (teamIdArgIndex > 0 && teamIdArgIndex + 1 < tnsArgs.length) { + return tnsArgs[ teamIdArgIndex + 1 ]; + } + } + + // try to get the TeamId from the buildxcconfig or teamid file + const teamIdFromConfig = this.readTeamId(appRoot); + + if (teamIdFromConfig) { + return teamIdFromConfig; + } + + // we should get the Teams from the machine and ask the user if they are more than 1 + return null; } - return args; - } + private readXCConfig(appRoot: string, flag: string): string { + const xcconfigFile = path.join(appRoot, 'App_Resources/iOS/build.xcconfig'); + + if (fs.existsSync(xcconfigFile)) { + const text = fs.readFileSync(xcconfigFile, { encoding: 'utf8'}); + let teamId: string; + + text.split(/\r?\n/).forEach((line) => { + line = line.replace(/\/(\/)[^\n]*$/, ''); + if (line.indexOf(flag) >= 0) { + teamId = line.split('=')[1].trim(); + if (teamId[teamId.length - 1] === ';') { + teamId = teamId.slice(0, -1); + } + } + }); + if (teamId) { + return teamId; + } + } - private log(text: string): void { - this._session.sendEvent(new OutputEvent(text)); - } + const fileName = path.join(appRoot, 'teamid'); - private getTeamId(appRoot: string, tnsArgs?: string[]): string { - // try to get the TeamId from the TnsArgs - if(tnsArgs) { - const teamIdArgIndex = tnsArgs.indexOf('--teamId'); - if(teamIdArgIndex > 0 && teamIdArgIndex + 1 < tnsArgs.length) { - return tnsArgs[ teamIdArgIndex + 1 ]; + if (fs.existsSync(fileName)) { + return fs.readFileSync(fileName, { encoding: 'utf8' }); } + + return null; } - // try to get the TeamId from the buildxcconfig or teamid file - const teamIdFromConfig = this.readTeamId(appRoot); - if(teamIdFromConfig) { - return teamIdFromConfig; + private readTeamId(appRoot): string { + return this.readXCConfig(appRoot, 'DEVELOPMENT_TEAM'); } - // we should get the Teams from the machine and ask the user if they are more than 1 - return null; - } - - private readXCConfig(appRoot: string, flag: string): string { - let xcconfigFile = path.join(appRoot, "App_Resources/iOS/build.xcconfig"); - if (fs.existsSync(xcconfigFile)) { - let text = fs.readFileSync(xcconfigFile, { encoding: 'utf8'}); - let teamId: string; - text.split(/\r?\n/).forEach((line) => { - line = line.replace(/\/(\/)[^\n]*$/, ""); - if (line.indexOf(flag) >= 0) { - teamId = line.split("=")[1].trim(); - if (teamId[teamId.length - 1] === ';') { - teamId = teamId.slice(0, -1); - } - } - }); - if (teamId) { - return teamId; - } - } - - let fileName = path.join(appRoot, "teamid"); - if (fs.existsSync(fileName)) { - return fs.readFileSync(fileName, { encoding: 'utf8' }); - } - - return null; - } - - private readTeamId(appRoot): string { - return this.readXCConfig(appRoot, "DEVELOPMENT_TEAM"); - } -} \ No newline at end of file + private callRemoteMethod(service: string, method: string, ...args: any[]): Promise { + const request: extProtocol.IRequest = { id: `req${++this._idCounter}`, service, method, args }; + + return new Promise((res, rej) => { + this._pendingRequests[request.id] = res; + + this._session.sendEvent(new Event(extProtocol.NS_DEBUG_ADAPTER_MESSAGE, request)); + }); + } + }; +} diff --git a/src/debug-adapter/nativeScriptPathTransformer.ts b/src/debug-adapter/nativeScriptPathTransformer.ts index 3e6e7dd..49237f3 100644 --- a/src/debug-adapter/nativeScriptPathTransformer.ts +++ b/src/debug-adapter/nativeScriptPathTransformer.ts @@ -1,12 +1,12 @@ -import * as path from 'path'; import * as fs from 'fs'; -import { UrlPathTransformer } from 'vscode-chrome-debug-core'; import * as _ from 'lodash'; +import * as path from 'path'; +import { UrlPathTransformer } from 'vscode-chrome-debug-core'; export class NativeScriptPathTransformer extends UrlPathTransformer { private filePatterns = { - android: new RegExp("^(file:)?/*data/(data|user/\\d+)/.*?/files/(.*)$", "i"), - ios: new RegExp("^(file:)?/*(.*)$", "i") + android: new RegExp('^(file:)?/*data/(data|user/\\d+)/.*?/files/(.*)$', 'i'), + ios: new RegExp('^(file:)?/*(.*)$', 'i'), }; private targetPlatform: string; @@ -16,51 +16,48 @@ export class NativeScriptPathTransformer extends UrlPathTransformer { } protected async targetUrlToClientPath(webRoot: string, scriptUrl: string): Promise { - if (!scriptUrl) - { + if (!scriptUrl) { return; } - if (_.startsWith(scriptUrl, "mdha:")) - { - scriptUrl = _.trimStart(scriptUrl, "mdha:"); + if (_.startsWith(scriptUrl, 'mdha:')) { + scriptUrl = _.trimStart(scriptUrl, 'mdha:'); } - if (path.isAbsolute(scriptUrl) && fs.existsSync(scriptUrl)) - { + if (path.isAbsolute(scriptUrl) && fs.existsSync(scriptUrl)) { return Promise.resolve(scriptUrl); } const filePattern = this.filePatterns[this.targetPlatform]; - const matches = filePattern.exec(scriptUrl) + const matches = filePattern.exec(scriptUrl); let relativePath = scriptUrl; - if(matches) { + + if (matches) { relativePath = this.targetPlatform === 'android' ? matches[3] : matches[2]; } - const nodePath = path.join("..", "node_modules"); - relativePath = relativePath.replace("tns_modules", nodePath); + const nodePath = path.join('..', 'node_modules'); + + relativePath = relativePath.replace('tns_modules', nodePath); const absolutePath = path.resolve(path.join(webRoot, relativePath)); - if (fs.existsSync(absolutePath)) - { + if (fs.existsSync(absolutePath)) { return Promise.resolve(absolutePath); } const fileExtension = path.extname(absolutePath); - if (fileExtension) - { - const platformSpecificAbsolutePath = absolutePath.replace(fileExtension, `.${this.targetPlatform}${fileExtension}`); - if (fs.existsSync(platformSpecificAbsolutePath)) - { - return Promise.resolve(platformSpecificAbsolutePath); + if (fileExtension) { + const platformSpecificPath = absolutePath.replace(fileExtension, `.${this.targetPlatform}${fileExtension}`); + + if (fs.existsSync(platformSpecificPath)) { + return Promise.resolve(platformSpecificPath); } } return Promise.resolve(scriptUrl); } -} \ No newline at end of file +} diff --git a/src/debug-adapter/nativeScriptTargetDiscovery.ts b/src/debug-adapter/nativeScriptTargetDiscovery.ts new file mode 100644 index 0000000..7dfa821 --- /dev/null +++ b/src/debug-adapter/nativeScriptTargetDiscovery.ts @@ -0,0 +1,27 @@ +import * as uuid from 'uuid'; +import { chromeConnection, chromeTargetDiscoveryStrategy, logger, telemetry } from 'vscode-chrome-debug-core'; + +export class NativeScriptTargetDiscovery extends chromeTargetDiscoveryStrategy.ChromeTargetDiscovery { + constructor() { + super(logger, new telemetry.TelemetryReporter()); + } + + public getTarget(address: string, port: number, targetFilter?: any, targetUrl?: string): Promise { + return Promise.resolve({ + description: 'NS Debug Target', + devtoolsFrontendUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=${address}:${port}`, + id: uuid.v4(), + title: 'NS Debug Target', + type: 'node', + webSocketDebuggerUrl: `ws://${address}:${port}`, + }); + } + + public async getAllTargets(address: string, + port: number, targetFilter?: chromeConnection.ITargetFilter, + targetUrl?: string): Promise { + const target = await this.getTarget(address, port); + + return Promise.resolve([ target ]); + } +} diff --git a/src/ipc/extensionClient.ts b/src/ipc/extensionClient.ts deleted file mode 100644 index 377f4fc..0000000 --- a/src/ipc/extensionClient.ts +++ /dev/null @@ -1,73 +0,0 @@ -import * as extProtocol from './extensionProtocol'; -import {Services} from "../services/debugAdapterServices"; -import {getSocketId} from "./sockedId"; - -const ipc = require('node-ipc'); - -export class ExtensionClient { - private _appRoot: string; - private _idCounter = 0; - private _pendingRequests: Object; - private _socketId: string; - - private _ipcClientInitialized: Promise; - - constructor(appRoot: string) { - this._appRoot = appRoot; - this._idCounter = 0; - this._pendingRequests = {}; - - this._socketId = getSocketId(); - - ipc.config.id = 'debug-adapter-' + process.pid; - ipc.config.retry = 1500; - ipc.config.maxRetries = 5; - - this._ipcClientInitialized = new Promise((res, rej) => { - ipc.connectTo( - this._socketId, - () => { - ipc.of[this._socketId].on('connect', () => { - res(); - }); - - ipc.of[this._socketId].on('error', error => { - Services.logger().log(`[ExtensionClient] error: ${JSON.stringify(error)}\n`); - }); - - ipc.of[this._socketId].on('extension-protocol-message', (response: extProtocol.Response) => { - (<(result: Object) => void>this._pendingRequests[response.requestId])(response.result); - }); - } - ); - }); - } - - private callRemoteMethod(method: string, args?: Object): Promise { - let request: extProtocol.Request = {id: 'req' + (++this._idCounter), method: method, args: args}; - return new Promise((res, rej) => { - this._pendingRequests[request.id] = res; - ipc.of[this._socketId].emit('extension-protocol-message', request); - }); - } - - public getInitSettings(): Promise { - return >(this.callRemoteMethod('getInitSettings')); - } - - public cleanBeforeDebug(): Promise { - return this.callRemoteMethod('cleanBeforeDebug'); - } - - public analyticsLaunchDebugger(args: extProtocol.AnalyticsLaunchDebuggerArgs): Promise { - return this.callRemoteMethod('analyticsLaunchDebugger', args); - } - - public runRunCommand(args: extProtocol.AnalyticsRunRunCommandArgs): Promise { - return this.callRemoteMethod('runRunCommand', args); - } - - public selectTeam(): Promise<{ id: string, name: string }> { - return >(this.callRemoteMethod('selectTeam')); - } -} \ No newline at end of file diff --git a/src/ipc/extensionProtocol.ts b/src/ipc/extensionProtocol.ts deleted file mode 100644 index c3740eb..0000000 --- a/src/ipc/extensionProtocol.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface Request { - id: string; - method: string; - args: Object; -} - -export interface Response { - requestId: string; - result: Object; -} - -export interface AnalyticsLaunchDebuggerArgs { - request: string; - platform: string; -} - -export interface AnalyticsRunRunCommandArgs { - platform: string; -} - -export interface InitSettingsResult { - tnsPath: string; -} \ No newline at end of file diff --git a/src/ipc/extensionServer.ts b/src/ipc/extensionServer.ts deleted file mode 100644 index bc9b247..0000000 --- a/src/ipc/extensionServer.ts +++ /dev/null @@ -1,149 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import * as vscode from 'vscode'; -import {QuickPickItem} from 'vscode'; -import * as extProtocol from './extensionProtocol'; -import {Services} from '../services/extensionHostServices'; -import {getSocketId} from "./sockedId"; -import {Disposable} from "vscode"; - -let ipc = require('node-ipc'); - -export class ExtensionServer { - private _isRunning: boolean; - private disposablesBeforeDebug: Disposable[] = new Array(); - - constructor() { - this._isRunning = false; - } - - public start() { - if (!this._isRunning) { - ipc.config.id = getSocketId(); - ipc.serve( - () => { - ipc.server.on('extension-protocol-message', (data: extProtocol.Request, socket) => { - return (>this[data.method].call(this, data.args)).then(result => { - let response: extProtocol.Response = {requestId: data.id, result: result}; - return ipc.server.emit(socket, 'extension-protocol-message', response); - }); - }); - }); - ipc.server.start(); - this._isRunning = true; - } - return this._isRunning; - } - - public stop() { - if (this._isRunning) { - ipc.server.stop(); - this._isRunning = false; - } - } - - public registerForCleanBeforeDebug(...disposables: Disposable[]) { - this.disposablesBeforeDebug.push(...disposables) - } - - public cleanBeforeDebug() { - this.disposablesBeforeDebug.forEach(disposable => disposable.dispose()); - return Promise.resolve(); - } - - public isRunning() { - return this._isRunning; - } - - public getInitSettings(): Promise { - let tnsPath = Services.workspaceConfigService().tnsPath; - return Promise.resolve({tnsPath: tnsPath}); - } - - public analyticsLaunchDebugger(args: extProtocol.AnalyticsLaunchDebuggerArgs): Promise { - return Services.analyticsService().launchDebugger(args.request, args.platform); - } - - public runRunCommand(args: extProtocol.AnalyticsRunRunCommandArgs): Promise { - return Services.analyticsService().runRunCommand(args.platform); - } - - public selectTeam(): Promise<{ id: string, name: string }> { - return new Promise((resolve, reject) => { - const workspaceTeamId = vscode.workspace.getConfiguration().get("nativescript.iosTeamId"); - - if (workspaceTeamId) { - resolve({ - id: workspaceTeamId, - name: undefined // irrelevant - }); - return; - } - - let developmentTeams = this.getDevelopmentTeams(); - if (developmentTeams.length > 1) { - let quickPickItems: Array = developmentTeams.map((team) => { - return { - label: team.name, - description: team.id - }; - }); - vscode.window.showQuickPick( - quickPickItems, { - placeHolder: "Select your development team" - }) - .then((val: QuickPickItem) => { - vscode.workspace.getConfiguration().update("nativescript.iosTeamId", val.description); - resolve({ - id: val.description, - name: val.label - }) - }); - } else { - resolve(); - } - }); - } - - private getDevelopmentTeams(): Array<{ id: string, name: string }> { - try { - let dir = path.join(process.env.HOME, "Library/MobileDevice/Provisioning Profiles/"); - let files = fs.readdirSync(dir); - let teamIds: any = {}; - for (let file of files) { - let filePath = path.join(dir, file); - let data = fs.readFileSync(filePath, {encoding: "utf8"}); - let teamId = this.getProvisioningProfileValue("TeamIdentifier", data); - let teamName = this.getProvisioningProfileValue("TeamName", data); - if (teamId) { - teamIds[teamId] = teamName; - } - } - - let teamIdsArray = new Array<{ id: string, name: string }>(); - for (let teamId in teamIds) { - teamIdsArray.push({id: teamId, name: teamIds[teamId]}); - } - - return teamIdsArray; - } catch (e) { - // no matter what happens, don't break - return new Array<{ id: string, name: string }>(); - } - } - - private getProvisioningProfileValue(name: string, text: string): string { - let findStr = "" + name + ""; - let index = text.indexOf(findStr); - if (index > 0) { - index = text.indexOf("", index + findStr.length); - if (index > 0) { - index += "".length; - let endIndex = text.indexOf("", index); - let result = text.substring(index, endIndex); - return result; - } - } - return null; - } -} \ No newline at end of file diff --git a/src/ipc/sockedId.ts b/src/ipc/sockedId.ts deleted file mode 100644 index 54328e3..0000000 --- a/src/ipc/sockedId.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function getSocketId(): string { - // let's KISS for now - I doubt users will want to simultaneously debug 2 apps anyway - return 'vs-ns-ext'; -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 8f30f9e..a4e8b25 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,94 +1,129 @@ +import * as semver from 'semver'; import * as vscode from 'vscode'; -import {CliVersion} from './project/nativeScriptCli'; -import {Services} from './services/extensionHostServices'; -import {Project} from './project/project'; -import {IosProject} from './project/iosProject'; -import {AndroidProject} from './project/androidProject'; +import * as extProtocol from './common/extensionProtocol'; import * as utils from './common/utilities'; +import { AndroidProject } from './project/androidProject'; +import { IosProject } from './project/iosProject'; +import { Project } from './project/project'; +import { ChannelLogger } from './services/channelLogger'; +import { services } from './services/extensionHostServices'; // this method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { - Services.globalState = context.globalState; - Services.cliPath = Services.workspaceConfigService().tnsPath || Services.cliPath; - Services.extensionServer().start(); - Services.analyticsService().initialize(); - - // Check if NativeScript CLI is installed globally and if it is compatible with the extension version - let cliVersion = Services.cli().version; - if (!cliVersion.isCompatible) { - vscode.window.showErrorMessage(cliVersion.errorMessage); + services.globalState = context.globalState; + services.cliPath = services.workspaceConfigService.tnsPath || services.cliPath; + + const channel = vscode.window.createOutputChannel('NativeScript Extension'); + + services.logger = new ChannelLogger(channel); + + const packageJSON = vscode.extensions.getExtension('Telerik.nativescript').packageJSON; + const cliVersion = services.cli().executeGetVersion(); + + if (!cliVersion) { + // tslint:disable-next-line:max-line-length + vscode.window.showErrorMessage("NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting to explicitly set the absolute path to the NativeScript CLI."); + + return; + } + + if (!semver.gte(cliVersion, packageJSON.minNativescriptCliVersion)) { + // tslint:disable-next-line:max-line-length + vscode.window.showErrorMessage(`The existing NativeScript extension is compatible with NativeScript CLI v${packageJSON.minNativescriptCliVersion} or greater. + The currently installed NativeScript CLI is v${cliVersion}.You can update the NativeScript CLI by executing 'npm install -g nativescript'.`); + + return; } - let channel = createInfoChannel(cliVersion.version.toString()); - let showOutputChannelCommand = vscode.commands.registerCommand('nativescript.showOutputChannel', () => { + services.cliVersion = cliVersion; + services.extensionVersion = packageJSON.version; + + logExtensionInfo(cliVersion, packageJSON); + + services.analyticsService.initialize(); + + const showOutputChannelCommand = vscode.commands.registerCommand('nativescript.showOutputChannel', () => { channel.show(); }); - let runCommand = (project: Project) => { + const beforeBuildDisposables = new Array(); + const runCommand = (project: Project) => { if (vscode.workspace.rootPath === undefined) { vscode.window.showErrorMessage('No workspace opened.'); + return; } // Show output channel - let runChannel: vscode.OutputChannel = vscode.window.createOutputChannel(`Run on ${project.platformName()}`); + const runChannel: vscode.OutputChannel = vscode.window.createOutputChannel(`Run on ${project.platformName()}`); runChannel.clear(); runChannel.show(vscode.ViewColumn.Two); - Services.analyticsService().runRunCommand(project.platformName()); + services.analyticsService.runRunCommand(project.platformName()); - let tnsProcess = project.run(); - tnsProcess.on('error', err => { + const tnsProcess = project.run(); + tnsProcess.on('error', (err) => { vscode.window.showErrorMessage('Unexpected error executing NativeScript Run command.'); }); - tnsProcess.stderr.on('data', data => { + tnsProcess.stderr.on('data', (data) => { runChannel.append(data.toString()); }); - tnsProcess.stdout.on('data', data => { + tnsProcess.stdout.on('data', (data) => { runChannel.append(data.toString()); }); - tnsProcess.on('exit', exitCode => { + tnsProcess.on('exit', (exitCode) => { tnsProcess.stdout.removeAllListeners('data'); tnsProcess.stderr.removeAllListeners('data'); }); - tnsProcess.on('close', exitCode => { + tnsProcess.on('close', (exitCode) => { runChannel.hide(); }); const disposable = { - dispose: () => utils.killProcess(tnsProcess) + dispose: () => utils.killProcess(tnsProcess), }; context.subscriptions.push(disposable); - - Services.extensionServer().registerForCleanBeforeDebug(disposable); + beforeBuildDisposables.push(disposable); }; - let runIosCommand = vscode.commands.registerCommand('nativescript.runIos', () => { - return runCommand(new IosProject(vscode.workspace.rootPath, Services.cli())); + const runIosCommand = vscode.commands.registerCommand('nativescript.runIos', () => { + return runCommand(new IosProject(vscode.workspace.rootPath, services.cli())); }); - let runAndroidCommand = vscode.commands.registerCommand('nativescript.runAndroid', () => { - return runCommand(new AndroidProject(vscode.workspace.rootPath, Services.cli())); + const runAndroidCommand = vscode.commands.registerCommand('nativescript.runAndroid', () => { + return runCommand(new AndroidProject(vscode.workspace.rootPath, services.cli())); }); + context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((event) => { + if (event.event === extProtocol.BEFORE_DEBUG_START) { + beforeBuildDisposables.forEach((disposable) => disposable.dispose()); + } + + if (event.event === extProtocol.NS_DEBUG_ADAPTER_MESSAGE) { + const request = event.body as extProtocol.IRequest; + const service = services[request.service]; + const method = service[request.method]; + const response = typeof method === 'function' ? service[request.method].call(service, ...request.args) : method; + + if (response.then) { + response.then((result) => event.session.customRequest('onExtensionResponse', { requestId: request.id, result })); + + return; + } + + event.session.customRequest('onExtensionResponse', { requestId: request.id, result: response }); + } + })); + context.subscriptions.push(runIosCommand); context.subscriptions.push(runAndroidCommand); context.subscriptions.push(showOutputChannelCommand); } -function createInfoChannel(cliVersion: string): vscode.OutputChannel { - let channel = vscode.window.createOutputChannel("NativeScript Extension"); - const packageJSON = vscode.extensions.getExtension("Telerik.nativescript").packageJSON; - - packageJSON.version && channel.appendLine(`Version: ${packageJSON.version}`); - packageJSON.buildVersion && channel.appendLine(`Build version: ${packageJSON.buildVersion}`); - packageJSON.commitId && channel.appendLine(`Commit id: ${packageJSON.commitId}`); - channel.appendLine(`NativeScript CLI: ${cliVersion}`); - - return channel; +function logExtensionInfo(cliVersion: string, packageJSON: any): void { + packageJSON.version && services.logger.log(`Version: ${packageJSON.version}`); + packageJSON.buildVersion && services.logger.log(`Build version: ${packageJSON.buildVersion}`); + packageJSON.commitId && services.logger.log(`Commit id: ${packageJSON.commitId}`); + services.logger.log(`NativeScript CLI: ${cliVersion}`); } - -export function deactivate() { - Services.extensionServer().stop(); -} \ No newline at end of file diff --git a/src/project/androidProject.ts b/src/project/androidProject.ts index 510fc03..c975e65 100644 --- a/src/project/androidProject.ts +++ b/src/project/androidProject.ts @@ -1,12 +1,9 @@ -import {ChildProcess} from 'child_process'; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; import * as stream from 'stream'; -import {EventEmitter} from 'events'; -import {Project, DebugResult} from './project'; +import { NativeScriptCli } from './nativeScriptCli'; +import { IDebugResult, Project } from './project'; import * as scanner from './streamScanner'; -import {Version} from '../common/version'; -import {NativeScriptCli} from './nativeScriptCli'; - -export type GetDebugPortResult = { tnsProcess: ChildProcess, debugPort: Promise }; export class AndroidProject extends Project { @@ -15,45 +12,52 @@ export class AndroidProject extends Project { } public platformName(): string { - return "android"; + return 'android'; } - public attach(tnsArgs?: string[]): DebugResult { - let args: string[] = ["--start"]; + public attach(tnsArgs?: string[]): IDebugResult { + let args: string[] = ['--start']; + args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter, true); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } - public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): DebugResult { + public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): IDebugResult { let args: string[] = []; - args.push(options.watch ? "--watch" : "--no-watch"); - if (options.stopOnEntry) { args.push("--debug-brk"); } + + args.push(options.watch ? '--watch' : '--no-watch'); + if (options.stopOnEntry) { args.push('--debug-brk'); } args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter, false); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } protected configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter, attach?: boolean): void { super.configureReadyEvent(readableStream, eventEmitter); let debugPort = null; - new scanner.StringMatchingScanner(readableStream).onEveryMatch(new RegExp("device: .* debug port: [0-9]+"), (match: scanner.MatchFound) => { - //device: {device-name} debug port: {debug-port} - debugPort = parseInt((match.matches[0]).match("(?:debug port: )([\\d]{5})")[1]); + + new scanner.StringMatchingScanner(readableStream).onEveryMatch(new RegExp('device: .* debug port: [0-9]+'), (match: scanner.IMatchFound) => { + // device: {device-name} debug port: {debug-port} + debugPort = parseInt((match.matches[0] as string).match('(?:debug port: )([\\d]{5})')[1], 10); if (attach) { // wait a little before trying to connect, this gives a chance for adb to be able to connect to the debug socket setTimeout(() => { eventEmitter.emit('readyForConnection', debugPort); }, 1000); } }); if (!attach) { - new scanner.StringMatchingScanner(readableStream).onEveryMatch('# NativeScript Debugger started #', (match: scanner.MatchFound) => { + new scanner.StringMatchingScanner(readableStream).onEveryMatch('# NativeScript Debugger started #', (match: scanner.IMatchFound) => { // wait a little before trying to connect, this gives a chance for adb to be able to connect to the debug socket setTimeout(() => { eventEmitter.emit('readyForConnection', debugPort); }, 1000); }); diff --git a/src/project/iosProject.ts b/src/project/iosProject.ts index 5effe6d..9cf77f3 100644 --- a/src/project/iosProject.ts +++ b/src/project/iosProject.ts @@ -1,12 +1,9 @@ -import {ChildProcess} from 'child_process'; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; import * as stream from 'stream'; -import {EventEmitter} from 'events'; -import {Project, DebugResult} from './project'; +import { NativeScriptCli } from './nativeScriptCli'; +import { IDebugResult, Project } from './project'; import * as scanner from './streamScanner'; -import {Version} from '../common/version'; -import {NativeScriptCli} from './nativeScriptCli'; -import {Services} from '../services/debugAdapterServices'; -import {Tags} from '../common/logger'; export class IosProject extends Project { @@ -19,38 +16,46 @@ export class IosProject extends Project { } public platformName(): string { - return "ios"; + return 'ios'; } - public attach(tnsArgs?: string[]): DebugResult { - let args: string[] = ["--start"]; + public attach(tnsArgs?: string[]): IDebugResult { + let args: string[] = ['--start']; + args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } - public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): DebugResult { + public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): IDebugResult { let args: string[] = []; - args.push(options.watch ? "--watch" : "--no-watch"); - if (options.stopOnEntry) { args.push("--debug-brk"); } + + args.push(options.watch ? '--watch' : '--no-watch'); + if (options.stopOnEntry) { args.push('--debug-brk'); } args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } protected configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter): void { super.configureReadyEvent(readableStream, eventEmitter); - let streamScanner = new scanner.StringMatchingScanner(readableStream); - streamScanner.onEveryMatch(new RegExp("Opened localhost (.*)"), (match: scanner.MatchFound) => { - let port = parseInt(match.matches[1]); - setTimeout(() => { eventEmitter.emit('readyForConnection', port) }, 1000); + const streamScanner = new scanner.StringMatchingScanner(readableStream); + + streamScanner.onEveryMatch(new RegExp('Opened localhost (.*)'), (match: scanner.IMatchFound) => { + const port = parseInt(match.matches[1] as string, 10); + + setTimeout(() => { eventEmitter.emit('readyForConnection', port); }, 1000); }); } diff --git a/src/project/nativeScriptCli.ts b/src/project/nativeScriptCli.ts index a7669b0..7083fa4 100644 --- a/src/project/nativeScriptCli.ts +++ b/src/project/nativeScriptCli.ts @@ -1,55 +1,15 @@ -import {spawn, execSync, ChildProcess} from 'child_process'; -import {Version} from '../common/version'; -import {Logger, Tags} from '../common/logger'; +import { ChildProcess, execSync, spawn } from 'child_process'; +import { ILogger } from '../common/logger'; import * as utils from '../common/utilities'; -import * as os from 'os'; - -export enum CliVersionState { - NotExisting, - OlderThanSupported, - Compatible -} - -export class CliVersion { - private _cliVersion: Version = undefined; - private _minExpectedCliVersion: Version = undefined; - private _cliVersionState: CliVersionState; - private _cliVersionErrorMessage: string; - - constructor(cliVersion: Version, minExpectedCliVersion: Version) { - this._cliVersion = cliVersion; - this._minExpectedCliVersion = minExpectedCliVersion; - - // Calculate CLI version state and CLI version error message - this._cliVersionState = CliVersionState.Compatible; - if (minExpectedCliVersion) { - if (this._cliVersion === null) { - this._cliVersionState = CliVersionState.NotExisting; - this._cliVersionErrorMessage = "NativeScript CLI not found, please run 'npm -g install nativescript' to install it."; - } - else if (this._cliVersion.compareBySubminorTo(minExpectedCliVersion) < 0) { - this._cliVersionState = CliVersionState.OlderThanSupported; - this._cliVersionErrorMessage = `The existing NativeScript extension is compatible with NativeScript CLI v${this._minExpectedCliVersion} or greater. The currently installed NativeScript CLI is v${this._cliVersion}. You can update the NativeScript CLI by executing 'npm install -g nativescript'.`; - } - } - } - - public get version() { return this._cliVersion; } - - public get state() { return this._cliVersionState; } - - public get isCompatible() { return this._cliVersionState == CliVersionState.Compatible; } - - public get errorMessage() { return this._cliVersionErrorMessage; } -} export class NativeScriptCli { + private static CLI_OUTPUT_VERSION_REGEXP = /^(?:\d+\.){2}\d+.*?$/m; + private _path: string; private _shellPath: string; - private _cliVersion: CliVersion; - private _logger: Logger; + private _logger: ILogger; - constructor(cliPath: string, logger: Logger) { + constructor(cliPath: string, logger: ILogger) { this._path = cliPath; this._logger = logger; @@ -58,47 +18,54 @@ export class NativeScriptCli { // always default to cmd on Windows // workaround for issue #121 https://github.com/NativeScript/nativescript-vscode-extension/issues/121 if (utils.getPlatform() === utils.Platform.Windows) { - this._shellPath = "cmd.exe"; - } - - let versionStr = null; - try { - versionStr = this.executeSync(["--version"], undefined); - } - catch(e) { - this._logger.log(e, Tags.FrontendMessage); - throw new Error("NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting to explicitly set the absolute path to the NativeScript CLI."); - } - let cliVersion: Version = versionStr ? Version.parse(versionStr) : null; - this._cliVersion = new CliVersion(cliVersion, utils.getMinSupportedCliVersion()); - if (!this._cliVersion.isCompatible) { - throw new Error(this._cliVersion.errorMessage); + this._shellPath = 'cmd.exe'; } } public get path(): string { return this._path; } - public get version(): CliVersion { - return this._cliVersion; + public executeGetVersion(): string { + try { + const versionOutput = this.executeSync(['--version'], undefined); + + return this.getVersionFromCLIOutput(versionOutput); + } catch (e) { + this._logger.log(e); + + const errorMessage = `NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting + to explicitly set the absolute path to the NativeScript CLI.`; + + throw new Error(errorMessage); + } } public executeSync(args: string[], cwd: string): string { - args.unshift("--analyticsClient", "VSCode"); - let command: string = `${this._path} ${args.join(' ')}`; - this._logger.log(`[NativeScriptCli] execute: ${command}\n`, Tags.FrontendMessage); + args.unshift('--analyticsClient', 'VSCode'); + const command: string = `${this._path} ${args.join(' ')}`; - return execSync(command, { encoding: "utf8", cwd: cwd, shell: this._shellPath}).toString().trim(); + this._logger.log(`[NativeScriptCli] execute: ${command}`); + + return execSync(command, { encoding: 'utf8', cwd, shell: this._shellPath}).toString().trim(); } public execute(args: string[], cwd: string): ChildProcess { - args.unshift("--analyticsClient", "VSCode"); - let command: string = `${this._path} ${args.join(' ')}`; - this._logger.log(`[NativeScriptCli] execute: ${command}\n`, Tags.FrontendMessage); + args.unshift('--analyticsClient', 'VSCode'); + const command: string = `${this._path} ${args.join(' ')}`; + + this._logger.log(`[NativeScriptCli] execute: ${command}`); + + const options = { cwd, shell: this._shellPath }; + const child: ChildProcess = spawn(this._path, args, options); - let options = { cwd: cwd, shell: this._shellPath }; - let child: ChildProcess = spawn(this._path, args, options); child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8'); + return child; } + + private getVersionFromCLIOutput(commandOutput: string): string { + const matches = commandOutput.match(NativeScriptCli.CLI_OUTPUT_VERSION_REGEXP); + + return matches && matches[0]; + } } diff --git a/src/project/project.ts b/src/project/project.ts index 52171fb..7e96475 100644 --- a/src/project/project.ts +++ b/src/project/project.ts @@ -1,11 +1,10 @@ -import {ChildProcess} from 'child_process'; -import {EventEmitter} from 'events'; -import {Version} from '../common/version'; -import {NativeScriptCli} from './nativeScriptCli'; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; import * as stream from 'stream'; +import { NativeScriptCli } from './nativeScriptCli'; import * as scanner from './streamScanner'; -export type DebugResult = { tnsProcess: ChildProcess, tnsOutputEventEmitter: EventEmitter }; +export interface IDebugResult { tnsProcess: ChildProcess; tnsOutputEventEmitter: EventEmitter; } export abstract class Project { private _appRoot: string; @@ -26,21 +25,21 @@ export abstract class Project { return this.executeRunCommand(tnsArgs); } - public abstract attach(tnsArgs?: string[]): DebugResult; + public abstract attach(tnsArgs?: string[]): IDebugResult; - public abstract debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): DebugResult; + public abstract debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): IDebugResult; protected configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter): void { - new scanner.StringMatchingScanner(readableStream).onEveryMatch("TypeScript compiler failed", () => { + new scanner.StringMatchingScanner(readableStream).onEveryMatch('TypeScript compiler failed', () => { eventEmitter.emit('tsCompilationError'); }); } protected executeRunCommand(args: string[]): ChildProcess { - return this.cli.execute(["run", this.platformName()].concat(args), this._appRoot); + return this._cli.execute(['run', this.platformName()].concat(args), this._appRoot); } protected executeDebugCommand(args: string[]): ChildProcess { - return this.cli.execute(["debug", this.platformName()].concat(args), this._appRoot); + return this._cli.execute(['debug', this.platformName()].concat(args), this._appRoot); } } diff --git a/src/project/streamScanner.ts b/src/project/streamScanner.ts index 3cc167d..ae64080 100644 --- a/src/project/streamScanner.ts +++ b/src/project/streamScanner.ts @@ -1,17 +1,17 @@ -import * as stream from 'stream'; +import { Readable } from 'stream'; export class StreamScanner { - private _stream: stream.Readable; - private _scanCallback: (data: string, stop: () => void) => void; + private _stream: Readable; + private _scanCallback: (data: string, stop: () => void) => void; - constructor(stream: stream.Readable, scanCallback: (data: string, stop: () => void) => void) { + constructor(stream: Readable, scanCallback: (data: string, stop: () => void) => void) { this._stream = stream; this._scanCallback = scanCallback; - this._stream.on("data", this.scan.bind(this)); - } + this._stream.on('data', this.scan.bind(this)); + } public stop() { - this._stream.removeListener("data", this.scan); + this._stream.removeListener('data', this.scan); } private scan(data: string | Buffer): void { @@ -19,74 +19,80 @@ export class StreamScanner { } } -export type MatchFound = { - chunk: string, - matches: RegExpMatchArray | number[] -}; +export interface IMatchFound { + chunk: string; + matches: RegExpMatchArray | number[]; +} -type MatchMeta = { - promise: Promise, - resolve: (match: MatchFound) => void, - reject: (err: Error) => void, - test: string | RegExp -}; +interface IMatchMeta { + promise: Promise; + resolve: (match: IMatchFound) => void; + reject: (err: Error) => void; + test: string | RegExp; +} export class StringMatchingScanner extends StreamScanner { - private _metas: MatchMeta[]; + private metas: IMatchMeta[]; - constructor(stream: stream.Readable) { + constructor(stream: Readable) { super(stream, (data: string, stop: () => void) => { - this._metas.forEach((meta, metaIndex) => { + this.metas.forEach((meta, metaIndex) => { if (meta.test instanceof RegExp) { - let result: RegExpMatchArray = data.match(meta.test); + const result: RegExpMatchArray = data.match(meta.test as RegExp); + if (result && result.length > 0) { this.matchFound(metaIndex, { chunk: data, matches: result }); } - } - else if (typeof meta.test === 'string') { - let result: number[] = []; // matches indices - let dataIndex = -1; - while((dataIndex = data.indexOf(meta.test, dataIndex + 1)) > -1) { + } else if (typeof meta.test === 'string') { + const result: number[] = []; // matches indices + let dataIndex = data.indexOf(meta.test as string, 0); + + while (dataIndex > -1) { result.push(dataIndex); + dataIndex = data.indexOf(meta.test as string, dataIndex + 1); } if (result.length > 0) { this.matchFound(metaIndex, { chunk: data, matches: result }); } - } - else { - throw new TypeError("Invalid type"); + } else { + throw new TypeError('Invalid type'); } }); }); - this._metas = []; + + this.metas = []; } - public onEveryMatch(test: string | RegExp, handler: (result: MatchFound) => void) { - let handlerWrapper = (result: MatchFound) => { + public onEveryMatch(test: string | RegExp, handler: (result: IMatchFound) => void) { + const handlerWrapper = (result: IMatchFound) => { handler(result); this.nextMatch(test).then(handlerWrapper); }; + this.nextMatch(test).then(handlerWrapper); } - public nextMatch(test: string | RegExp): Promise { - let meta: MatchMeta = { - test: test, - resolve: null, + public nextMatch(test: string | RegExp): Promise { + const meta: IMatchMeta = { + promise: null, reject: null, - promise: null + resolve: null, + test, }; - meta.promise = new Promise((resolve, reject) => { + + meta.promise = new Promise((resolve, reject) => { meta.resolve = resolve; meta.reject = reject; }); - this._metas.push(meta); + this.metas.push(meta); + return meta.promise; } - private matchFound(matchMetaIndex: number, matchResult: MatchFound) { - let meta: MatchMeta = this._metas[matchMetaIndex]; - this._metas.splice(matchMetaIndex, 1); // remove the meta + private matchFound(matchMetaIndex: number, matchResult: IMatchFound) { + const meta: IMatchMeta = this.metas[matchMetaIndex]; + + this.metas.splice(matchMetaIndex, 1); // remove the meta meta.resolve(matchResult); } } diff --git a/src/services/channelLogger.ts b/src/services/channelLogger.ts new file mode 100644 index 0000000..e72ae65 --- /dev/null +++ b/src/services/channelLogger.ts @@ -0,0 +1,17 @@ +import { OutputChannel } from 'vscode'; +import { ILogger, LogLevel } from '../common/logger'; + +export class ChannelLogger implements ILogger { + private minLogLevel: LogLevel = LogLevel.Log; + private channel: OutputChannel; + + constructor(channel: OutputChannel) { + this.channel = channel; + } + + public log(msg: string, level: LogLevel = LogLevel.Log): void { + if (level >= this.minLogLevel) { + this.channel.appendLine(msg); + } + } +} diff --git a/src/services/debugAdapterServices.ts b/src/services/debugAdapterServices.ts deleted file mode 100644 index 258dd83..0000000 --- a/src/services/debugAdapterServices.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Services as BaseServices} from './services'; -import {ExtensionClient} from '../ipc/extensionClient'; - -export class DebugAdapterServices extends BaseServices { - private _extensionClient: ExtensionClient; - private _appRoot: string; - - public get appRoot(): string { return this._appRoot; } - - public set appRoot(appRoot: string) { this._appRoot = appRoot; } - - public extensionClient(): ExtensionClient { - if (!this._extensionClient && !this._appRoot) { - throw new Error("appRoot has no value."); - } - this._extensionClient = this._extensionClient || new ExtensionClient(this._appRoot); - return this._extensionClient; - } -} - -export let Services = new DebugAdapterServices(); \ No newline at end of file diff --git a/src/services/extensionHostServices.ts b/src/services/extensionHostServices.ts index 855c5b2..b8210d8 100644 --- a/src/services/extensionHostServices.ts +++ b/src/services/extensionHostServices.ts @@ -1,34 +1,40 @@ import * as vscode from 'vscode'; -import {Services as BaseServices} from './services'; -import {ExtensionServer} from '../ipc/extensionServer'; -import {AnalyticsService} from '../analytics/analyticsService'; -import {WorkspaceConfigService} from '../common/workspaceConfigService'; +import { AnalyticsService } from '../analytics/analyticsService'; +import { WorkspaceConfigService } from '../common/workspaceConfigService'; +import { iOSTeamService } from './iOSTeamService'; +import { Services as BaseServices } from './services'; export class ExtensionHostServices extends BaseServices { + public cliVersion: string; + public extensionVersion: string; + private _globalState: vscode.Memento; private _workspaceConfigService: WorkspaceConfigService; - private _extensionServer: ExtensionServer; + private _iOSTeamService: iOSTeamService; private _analyticsService: AnalyticsService; public get globalState(): vscode.Memento { return this._globalState; } public set globalState(globalState: vscode.Memento) { this._globalState = globalState; } - public workspaceConfigService(): WorkspaceConfigService { + public get workspaceConfigService(): WorkspaceConfigService { this._workspaceConfigService = this._workspaceConfigService || new WorkspaceConfigService(); + return this._workspaceConfigService; } - public extensionServer(): ExtensionServer { - this._extensionServer = this._extensionServer || new ExtensionServer(); - return this._extensionServer; + public get iOSTeamService(): iOSTeamService { + this._iOSTeamService = this._iOSTeamService || new iOSTeamService(); + + return this._iOSTeamService; } - public analyticsService(): AnalyticsService { - this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState); + public get analyticsService(): AnalyticsService { + this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState, this.cliVersion, this.extensionVersion, this._logger); + return this._analyticsService; } } -export let Services = new ExtensionHostServices(); \ No newline at end of file +export let services = new ExtensionHostServices(); diff --git a/src/services/iOSTeamService.ts b/src/services/iOSTeamService.ts new file mode 100644 index 0000000..6996c37 --- /dev/null +++ b/src/services/iOSTeamService.ts @@ -0,0 +1,94 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +// tslint:disable-next-line:class-name +export class iOSTeamService { + public selectTeam(): Promise<{ id: string, name: string }> { + return new Promise((resolve, reject) => { + const workspaceTeamId = vscode.workspace.getConfiguration().get('nativescript.iosTeamId'); + + if (workspaceTeamId) { + resolve({ + id: workspaceTeamId, + name: undefined, // irrelevant + }); + + return; + } + + const developmentTeams = this.getDevelopmentTeams(); + + if (developmentTeams.length > 1) { + const quickPickItems: vscode.QuickPickItem[] = developmentTeams.map((team) => { + return { + description: team.id, + label: team.name, + }; + }); + + vscode.window.showQuickPick( + quickPickItems, { + placeHolder: 'Select your development team', + }) + .then((val: vscode.QuickPickItem) => { + vscode.workspace.getConfiguration().update('nativescript.iosTeamId', val.description); + resolve({ + id: val.description, + name: val.label, + }); + }); + } else { + resolve(); + } + }); + } + + private getDevelopmentTeams(): Array<{ id: string, name: string }> { + try { + const dir = path.join(process.env.HOME, 'Library/MobileDevice/Provisioning Profiles/'); + const files = fs.readdirSync(dir); + const teamIds: any = {}; + + for (const file of files) { + const filePath = path.join(dir, file); + const data = fs.readFileSync(filePath, {encoding: 'utf8'}); + const teamId = this.getProvisioningProfileValue('TeamIdentifier', data); + const teamName = this.getProvisioningProfileValue('TeamName', data); + + if (teamId) { + teamIds[teamId] = teamName; + } + } + + const teamIdsArray = new Array<{ id: string, name: string }>(); + + for (const teamId in teamIds) { + teamIdsArray.push({id: teamId, name: teamIds[teamId]}); + } + + return teamIdsArray; + } catch (e) { + // no matter what happens, don't break + return new Array<{ id: string, name: string }>(); + } + } + + private getProvisioningProfileValue(name: string, text: string): string { + const findStr = '' + name + ''; + let index = text.indexOf(findStr); + + if (index > 0) { + index = text.indexOf('', index + findStr.length); + if (index > 0) { + index += ''.length; + const endIndex = text.indexOf('', index); + const result = text.substring(index, endIndex); + + return result; + } + } + + return null; + } +} diff --git a/src/services/services.ts b/src/services/services.ts index 56b684e..417c3bd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,23 +1,23 @@ -import {Logger} from '../common/logger'; -import {NativeScriptCli} from '../project/nativeScriptCli'; +import { ILogger } from '../common/logger'; +import { NativeScriptCli } from '../project/nativeScriptCli'; export class Services { protected _cliPath: string; - protected _logger: Logger; + protected _logger: ILogger; protected _cli: NativeScriptCli; public get cliPath(): string { return this._cliPath; } public set cliPath(cliPath: string) { this._cliPath = cliPath; } - public logger(): Logger { - this._logger = this._logger || new Logger(); - return this._logger; - } + public get logger(): ILogger { return this._logger; } + + public set logger(logger: ILogger) { this._logger = logger; } public cli(): NativeScriptCli { - this._cli = this._cli || new NativeScriptCli(this._cliPath, this.logger()); + this._cli = this._cli || new NativeScriptCli(this._cliPath, this.logger); + return this._cli; } } diff --git a/src/tests/.vscode/launch.json b/src/tests/.vscode/launch.json deleted file mode 100644 index 9eb9aa5..0000000 --- a/src/tests/.vscode/launch.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "version": "0.1.0", - "configurations": [ - { - "name": "run tests on mac", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/config/mocha.opts", - "--config", "${workspaceRoot}/config/mac.json", - "${workspaceRoot}/../../out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/../../out", - "cwd": "${workspaceRoot}/../" - }, - { - "name": "run tests on win", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/config/mocha.opts", - "--config", "${workspaceRoot}/config/win.json", - "${workspaceRoot}/../../out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/../../out", - "cwd": "${workspaceRoot}/../" - }, - { - "name": "run tests (custom)", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/config/mocha.opts", - "--config", "${workspaceRoot}/config/custom.json", - "${workspaceRoot}/../../out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/../../out", - "cwd": "${workspaceRoot}/../" - } - ] -} \ No newline at end of file diff --git a/src/tests/.vscode/tasks.json b/src/tests/.vscode/tasks.json deleted file mode 100644 index 0d71c21..0000000 --- a/src/tests/.vscode/tasks.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "0.1.0", - "windows": { - "command": "..\\..\\node_modules\\.bin\\tsc" - }, - "command": "../../node_modules/.bin/tsc", - "isShellCommand": true, - "args": ["-p", "../"], - "problemMatcher": "$tsc" -} \ No newline at end of file diff --git a/src/tests/adapter.test.ts b/src/tests/adapter.test.ts deleted file mode 100644 index 9a4123d..0000000 --- a/src/tests/adapter.test.ts +++ /dev/null @@ -1,439 +0,0 @@ -import * as path from 'path'; -import * as assert from 'assert'; -import {DebugProtocol} from 'vscode-debugprotocol'; -import {NsDebugClient} from './nsDebugClient'; -import {Scenario} from './scenario'; -import {TestsConfig, TestsContext} from './testsContext'; - -describe('The adapter', () => { - - let dc: NsDebugClient; - let context: TestsContext = new TestsContext(); - let config: TestsConfig = context.getTestsConfig(); - - console.log(`Tests Configuration: ${JSON.stringify(config)}`); - - function waitFor(miliseconds) { - return new Promise(r => setTimeout(r, miliseconds)); - } - - function iosOrAndroid(platform, iosValue, androidValue) { - return platform == 'ios' ? iosValue : androidValue; - } - - before(function() { - if (!config.skipSuitePrepare) { - context.prepare(); - } - - dc = new NsDebugClient('node', context.getDebugAdapterMainPath(), 'nativescript'); - // dc.setTimeout(0); // No timeout. Useful when debugging the debug adapter. - }); - - beforeEach(function(done) { - dc.start(config.port || undefined).then(_ => done(), done); - }); - - afterEach(function(done) { - dc.removeAllListeners('stopped'); - dc.removeAllListeners('initialized'); - dc.stop().then(_ => waitFor(10000)).then(_ => done(), done); // Stop the DebugClient - }); - - it('should produce error on unknown request', done => { - dc.send('illegal_request').then(() => { - done(new Error('doesn\'t produce error error on unknown request')); - }, err => { done() }); - }); - - it('should return supported features', () => { - return dc.initializeRequest().then(response => { - assert.equal(response.body.supportsFunctionBreakpoints, false); - assert.equal(response.body.supportsConfigurationDoneRequest, true); - }); - }); - - it.skip('should produce error for invalid \'pathFormat\'', done => { - dc.initializeRequest({ - adapterID: 'mock', - linesStartAt1: true, - columnsStartAt1: true, - pathFormat: 'url' - }).then(response => { - done(new Error('does not report error on invalid \'pathFormat\' attribute')); - }).catch(err => { - done(); // error expected - }); - }); - - // Test cases are generated for all active platforms - config.platforms.forEach(platform => { - let meta = `on ${platform} ${config.emulator ? 'emulator' : 'device'}`; - let initialStopReason = iosOrAndroid(platform, 'pause', 'step'); - let breakpointStopReason = iosOrAndroid(platform, 'breakpoint', 'step'); - let exceptionStopReason = iosOrAndroid(platform, 'exception', 'step'); - - it(`${meta} should stop on the first line after launch`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.start(); - return scenario.client.assertStoppedLocation(initialStopReason, { line: 1 }); - }); - - it(`${meta} should disconnect`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - return scenario.start().then(() => { - return scenario.client.disconnectRequest(); - }); - }); - - it(`${meta} should stop on debugger statement`, () => { - let appRoot = context.getAppPath('DebuggerStatement'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - // continue after first stop - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, 'step', { line: 4 }); - }); - - it(`${meta} should stop on breakpoint`, () => { - let appRoot = context.getAppPath('JsApp'); - let bpPath = path.join(appRoot, 'app', 'main-page.js'); - let bpLine = 5; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 4 }); - }); - - it.skip(`${meta} should stop on breakpoint in file with spaces in its name`, () => { - let appRoot = context.getAppPath('TestApp1'); - let bpPath = path.join(appRoot, 'app', 'file with space in name.js'); - let bpLine = 5; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 0 }); - }); - - it(`${meta} should stop on conditional breakpoint when the condition is true`, () => { - let appRoot = context.getAppPath('TestApp1'); - let bpPath = path.join(appRoot, 'app', 'conditionalBreakpoint.js'); - let bpLine = 2; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine, condition: 'a === 3' }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 0 }) - .then(response => { - const frame = response.body.stackFrames[0]; - return scenario.client.evaluateRequest({ context: 'watch', frameId: frame.id, expression: 'a' }).then(response => { - assert.equal(response.body.result, 3, 'a !== 3'); - return response; - }); - }); - }); - - it(`${meta} should stop on typescript breakpoint`, () => { - let appRoot = context.getAppPath('TsApp'); - let bpPath = path.join(appRoot, 'app', 'main-page.ts'); - let bpLine = 9; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 4 }); - }); - - it(`${meta} should stop on typescript even if breakpoint was set in JavaScript`, () => { - let appRoot = context.getAppPath('TsApp'); - let jsPath = path.join(appRoot, 'app', 'main-page.js'); - let jsLine = 7; - let tsPath = path.join(appRoot, 'app', 'main-page.ts'); - let tsLine = 9; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(jsPath, [{ line: jsLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: tsPath, line: tsLine, column: 4 }); - }); - - it(`${meta} should stop on caught error`, () => { - let appRoot = context.getAppPath('TestApp2'); - let breakpointColumn = iosOrAndroid(platform, 35, 4); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.setExceptionBreakpointsRequest({ filters: ['all'] }); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, exceptionStopReason, { path: path.join(appRoot, 'app', 'app.js'), line: 3, column: breakpointColumn }); - }); - - it.skip(`${meta} should stop on uncaught error`, () => { - let appRoot = context.getAppPath('TestApp2'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.setExceptionBreakpointsRequest({ filters: ['uncaught'] }); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, exceptionStopReason, { path: path.join(appRoot, 'app', 'app.js'), line: 9, column: 24 }); - }); - - it(`${meta} should receive output event when console.log is called`, () => { - let appRoot = context.getAppPath('TestApp1'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.afterLaunchRequest = scenario.afterLaunchRequest.then(() => { - return scenario.client.onNextTime('output').then(e => { - let event = e as DebugProtocol.OutputEvent; - assert.equal(event.body.category, 'stdout', 'message category mismatch'); - assert.equal(event.body.output.startsWith('console.log called'), true, 'message mismatch'); - }); - }); - scenario.start(); - return scenario.afterLaunchRequest; - }); - - it(`${meta} should receive 2 output events when console.log is called twice with the same message`, () => { - let appRoot = context.getAppPath('TestApp1'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.afterLaunchRequest = scenario.afterLaunchRequest.then(() => { - let assertOutputEvent = e => { - let event = e as DebugProtocol.OutputEvent; - assert.equal(event.body.category, 'stdout', 'message category mismatch'); - assert.equal(event.body.output.startsWith('console.log called'), true, 'message mismatch'); - }; - - return Promise.all([ - scenario.client.onNthTime(1, 'output').then(assertOutputEvent), - scenario.client.onNthTime(2, 'output').then(assertOutputEvent)]); - }); - - scenario.start(); - return scenario.afterLaunchRequest; - }); - - it(`${meta} should step over`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 12; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return Promise.all([ - scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: filePath, line: bpLine, column: 4 }).then(() => { - return scenario.client.nextRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(3, 'step', { path: filePath, line: bpLine + 1, column: 4 }).then(() => { - return scenario.client.nextRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(4, 'step', { path: filePath, line: bpLine + 2, column: 4 }) - ]); - }); - - it(`${meta} should step in`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 14; - - let firstStepExpected = iosOrAndroid(platform, { path: filePath, line: 3, column: 19 }, { path: filePath, line: 4, column: 4 }); - let secondStepExpected = iosOrAndroid(platform, { path: filePath, line: 4, column: 4 }, { path: filePath, line: 7, column: 8 }); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return Promise.all([ - scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: filePath, line: bpLine, column: 4 }).then(() => { - return scenario.client.stepInRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(3, 'step', firstStepExpected).then(() => { - return scenario.client.stepInRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(4, 'step', secondStepExpected) - ]); - }); - - it(`${meta} should pause`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 14; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - - return scenario.start().then(() => { - return scenario.client.pauseRequest({ threadId: 1 }); - }); - }); - - it(`${meta} should evaluate expression when stopped on breakpoint`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 12; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return scenario.client.onNthTime(2, 'stopped').then(() => { - return scenario.client.stackTraceRequest({ threadId: 1, levels: 20 }).then((response) => { - return scenario.client.evaluateRequest({ - expression: 'getMessage(-5)', - context: 'watch', - frameId: response.body.stackFrames[0].id - }) - .then((response) => { - assert.equal(response.body.result, '"Hoorraaay! You unlocked the NativeScript clicker achievement!"', 'result mismatch'); - }); - }); - }); - }); - - it(`${meta} should return all threads`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - - return scenario.start().then(() => { - scenario.client.threadsRequest().then(response => { - assert.deepEqual(response.body.threads, [{ id: 1, name: 'Thread 1' }]); - }); - }); - }); - - it(`${meta} should return stack trace`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 13; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return scenario.client.onNthTime(2, 'stopped').then(e => { - return scenario.client.stackTraceRequest({ threadId: e.body.threadId }).then(response => { - let stackFrames = response.body.stackFrames; - let firstFrame = stackFrames[0]; - let lastFrame = stackFrames[stackFrames.length - 1]; - let expectedStackFramesCount = iosOrAndroid(platform, 22, 10); - assert.equal(stackFrames.length, expectedStackFramesCount, 'wrong stack frames count'); - assert.equal(firstFrame.name, 'createViewModel', 'wrong top frame name'); - assert.equal(firstFrame.source.path, filePath, 'wrong top frame path'); - assert.equal(lastFrame.name, 'promiseReactionJob', 'wrong last frame name'); - }); - }); - }); - - it(`${meta} should attach`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - - return scenario.start().then(() => { - return scenario.client.disconnectRequest().then(() => { - return scenario.client.attachRequest(Scenario.getDefaultAttachArgs(platform, appRoot, config.emulator)); - }); - }); - }); - - // iOS specifc tests - if (platform == 'ios') { - it(`${meta} should not hang on evaluating watch expression on call frame with unknown source`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - - - return Promise.all([ - scenario.start(), - scenario.client.onNextTime('stopped').then(e => { - return scenario.client.stackTraceRequest({ threadId: e.body.threadId }).then(response => { - let callFrame = response.body.stackFrames.filter(callFrame => !callFrame.source.path && !callFrame.source.sourceReference)[0]; - return scenario.client.evaluateRequest({ expression: 'Math.random()', frameId: callFrame.id, context: 'watch' }).then(response => { - assert.fail(undefined, undefined, 'Evaluate request should fail', undefined); - }, response => { - assert.equal(response.message, '-', 'error message mismatch'); - }); - }); - }) - ]); - }); - } - }); -}); \ No newline at end of file diff --git a/src/tests/config/mac.json b/src/tests/config/mac.json deleted file mode 100644 index 45dc5e1..0000000 --- a/src/tests/config/mac.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "platforms": ["android", "ios"], - "emulator": false, - "port": 4712, - "skipSuitePrepare": false -} \ No newline at end of file diff --git a/src/tests/config/mocha.opts b/src/tests/config/mocha.opts index ed83f01..0ae6543 100644 --- a/src/tests/config/mocha.opts +++ b/src/tests/config/mocha.opts @@ -1,4 +1,7 @@ --colors --ui tdd --timeout 60000 ---reporter spec \ No newline at end of file +--reporter spec +--exit + +./out/tests/**/*.tests.js \ No newline at end of file diff --git a/src/tests/config/win.json b/src/tests/config/win.json deleted file mode 100644 index b6e0393..0000000 --- a/src/tests/config/win.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "platforms": ["android"], - "emulator": false, - "port": 4712, - "skipSuitePrepare": false -} \ No newline at end of file diff --git a/src/tests/nativeScriptDebugAdapter.tests.ts b/src/tests/nativeScriptDebugAdapter.tests.ts new file mode 100644 index 0000000..858e142 --- /dev/null +++ b/src/tests/nativeScriptDebugAdapter.tests.ts @@ -0,0 +1,204 @@ +import { EventEmitter } from 'events'; +import * as _ from 'lodash'; +import * as sinon from 'sinon'; +import { ChromeDebugAdapter } from 'vscode-chrome-debug-core'; +import { Event } from 'vscode-debugadapter'; +import * as extProtocol from '../common/extensionProtocol'; +import { nativeScriptDebugAdapterGenerator } from '../debug-adapter/nativeScriptDebugAdapter'; + +const customMessagesResponses = { + workspaceConfigService: { + tnsPath: 'tnsPathMock', + }, +}; + +const defaultArgsMock: any = { + appRoot: 'appRootMock', + diagnosticLogging: true, + platform: 'android', + request: 'attach', + stopOnEntry: true, + tnsArgs: [ 'mockArgs'], + watch: true, +}; + +const mockConstructor = (mockObject: any): any => { + return function() { + return mockObject; + }; +}; + +describe('NativeScriptDebugAdapter', () => { + let nativeScriptDebugAdapter: any; + let chromeSessionMock: any; + let chromeConnectionMock: any; + let projectMock: any; + let nativeScriptCliMock: any; + let cliCommandMock: any; + let pathTransformerMock: any; + + beforeEach(() => { + chromeSessionMock = { + sendEvent: (e: Event) => { + const request = (e as any).body as extProtocol.IRequest; + + if (e.event === extProtocol.NS_DEBUG_ADAPTER_MESSAGE) { + const result = customMessagesResponses[request.service] && customMessagesResponses[request.service][request.method]; + + (nativeScriptDebugAdapter as any).onExtensionResponse({ requestId: request.id, result }); + } + }, + }; + + chromeConnectionMock = { + attach: () => Promise.resolve({}), + }; + + cliCommandMock = { + tnsOutputEventEmitter: { + on: (event, callback) => { + callback(); + }, + }, + }; + + pathTransformerMock = { + attach: () => ({}), + clearTargetContext: () => ({}), + setTargetPlatform: () => ({}), + }; + + projectMock = { + attach: () => cliCommandMock, + debug: () => cliCommandMock, + }; + + nativeScriptCliMock = { + executeGetVersion: () => 'cliVersionMock', + }; + + const projectClass: any = function(appRoot, cli) { + return _.merge({ + appRoot, + cli, + }, projectMock); + }; + + const nativeScriptDebugAdapterClass = nativeScriptDebugAdapterGenerator(projectClass, projectClass, mockConstructor(nativeScriptCliMock)); + + nativeScriptDebugAdapter = new nativeScriptDebugAdapterClass( + { chromeConnection: mockConstructor(chromeConnectionMock), pathTransformer: mockConstructor(pathTransformerMock) }, + chromeSessionMock, + ); + + ChromeDebugAdapter.prototype.attach = () => Promise.resolve(); + }); + + const platforms = [ 'android', 'ios' ]; + const launchMethods = [ 'launch', 'attach' ]; + + platforms.forEach((platform) => { + launchMethods.forEach((method) => { + const argsMock = _.merge({}, defaultArgsMock, { platform, request: method }); + + it(`${method} for ${platform} should raise debug start event`, async () => { + const spy = sinon.spy(chromeSessionMock, 'sendEvent'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ event: extProtocol.BEFORE_DEBUG_START })); + }); + + it(`${method} for ${platform} should call analyticsService`, async () => { + const spy = sinon.spy(chromeSessionMock, 'sendEvent'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ + body: { + args: [method, platform], + method: 'launchDebugger', + service: 'analyticsService', + }, + event: extProtocol.NS_DEBUG_ADAPTER_MESSAGE, + })); + }); + + it(`${method} for ${platform} should call project setTargetPlatform`, async () => { + const spy = sinon.spy(pathTransformerMock, 'setTargetPlatform'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, argsMock.platform); + }); + + it(`${method} for ${platform} should set debug port`, async () => { + const port = 1234; + + sinon.stub(cliCommandMock.tnsOutputEventEmitter, 'on').callsFake((event, callback) => callback(port)); + const spy = sinon.spy(ChromeDebugAdapter.prototype, 'attach'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ + port, + })); + }); + + it(`${method} for ${platform} should translate args to chrome debug args`, async () => { + const spy = sinon.spy(ChromeDebugAdapter.prototype, 'attach'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ + trace: true, + webRoot: 'appRootMock', + })); + }); + + it(`${method} for ${platform} - after process exit should send Terminate event`, async () => { + const spy = sinon.spy(chromeSessionMock, 'sendEvent'); + const fakeEmitter = { + on: () => ({}), + }; + + cliCommandMock.tnsProcess = new EventEmitter(); + cliCommandMock.tnsProcess.stderr = fakeEmitter; + cliCommandMock.tnsProcess.stdout = fakeEmitter; + + await nativeScriptDebugAdapter.attach(argsMock); + cliCommandMock.tnsProcess.emit('close', -1); + + sinon.assert.calledWith(spy, sinon.match({ + event: 'terminated', + })); + }); + }); + }); + + it('attach should call project attach method with correct args', async () => { + const attachSpy = sinon.spy(projectMock, 'attach'); + const debugSpy = sinon.spy(projectMock, 'debug'); + + const argsMock = _.merge({}, defaultArgsMock, { request: 'attach' }); + + await nativeScriptDebugAdapter.attach(argsMock); + + sinon.assert.calledOnce(attachSpy); + sinon.assert.calledWith(attachSpy, argsMock.tnsArgs); + sinon.assert.notCalled(debugSpy); + }); + + it('launch should call project debug method with correct args', async () => { + const attachSpy = sinon.spy(projectMock, 'attach'); + const debugSpy = sinon.spy(projectMock, 'debug'); + + const argsMock = _.merge({}, defaultArgsMock, { request: 'launch' }); + + await nativeScriptDebugAdapter.launch(argsMock); + + sinon.assert.calledOnce(debugSpy); + sinon.assert.calledWith(debugSpy, { stopOnEntry: argsMock.stopOnEntry, watch: argsMock.watch }, argsMock.tnsArgs); + sinon.assert.notCalled(attachSpy); + }); +}); diff --git a/src/tests/nativeScriptPathTransformer.tests.ts b/src/tests/nativeScriptPathTransformer.tests.ts new file mode 100644 index 0000000..691bd1b --- /dev/null +++ b/src/tests/nativeScriptPathTransformer.tests.ts @@ -0,0 +1,37 @@ +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as sinon from 'sinon'; +import { NativeScriptPathTransformer } from '../debug-adapter/nativeScriptPathTransformer'; +import * as tests from './pathTransformData'; + +describe('NativeScriptPathTransformer', () => { + let nativeScriptPathTransformer: any; + let existsSyncStub; + + before(() => { + nativeScriptPathTransformer = new NativeScriptPathTransformer(); + }); + + describe('targetUrlToClientPath() method', () => { + const webRoot = 'C:\\projectpath'; + + for (const test of tests as any) { + it(`should transform [${test.platform}] device path ${test.scriptUrl} -> ${test.expectedResult}`, async () => { + (path as any).join = path.win32.join; + (path as any).resolve = path.win32.resolve; + nativeScriptPathTransformer.setTargetPlatform(test.platform); + existsSyncStub = sinon.stub(fs, 'existsSync').callsFake((arg: string) => arg === test.existingPath); + + const result = await nativeScriptPathTransformer.targetUrlToClientPath(webRoot, test.scriptUrl); + + assert.equal(result, test.expectedResult); + }); + } + + afterEach(() => { + existsSyncStub.restore(); + }); + }); + +}); diff --git a/src/tests/nativeScriptTargetDiscovery.tests.ts b/src/tests/nativeScriptTargetDiscovery.tests.ts new file mode 100644 index 0000000..18d6749 --- /dev/null +++ b/src/tests/nativeScriptTargetDiscovery.tests.ts @@ -0,0 +1,45 @@ +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { NativeScriptTargetDiscovery } from '../debug-adapter/nativeScriptTargetDiscovery'; + +describe('NativeScriptTargetDiscovery', () => { + let nativeScriptTargetDiscovery: NativeScriptTargetDiscovery; + let stub; + + before(() => { + nativeScriptTargetDiscovery = new NativeScriptTargetDiscovery(); + }); + + it(`getTarget returns correct target`, async () => { + const address = 'localhost'; + const port = 41000; + + const target = await nativeScriptTargetDiscovery.getTarget(address, port); + + assert.equal(target.webSocketDebuggerUrl, `ws://${address}:${port}`); + assert.equal(target.devtoolsFrontendUrl, `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=${address}:${port}`); + }); + + it(`getTargets calls getTarget`, async () => { + const testTarget = { + devtoolsFrontendUrl: 'url', + webSocketDebuggerUrl: 'socket', + }; + const address = 'localhost'; + const port = 41000; + + stub = sinon.stub(nativeScriptTargetDiscovery, 'getTarget').callsFake(() => Promise.resolve(testTarget)); + const targets = await nativeScriptTargetDiscovery.getAllTargets(address, port); + + sinon.assert.calledOnce(stub); + sinon.assert.calledWith(stub, address, port); + assert.equal(targets.length, 1); + assert.deepEqual(targets[0], testTarget); + }); + + afterEach(() => { + if (stub) { + stub.restore(); + } + }); +}); diff --git a/src/tests/nsDebugClient.ts b/src/tests/nsDebugClient.ts deleted file mode 100644 index 57581f4..0000000 --- a/src/tests/nsDebugClient.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as assert from 'assert'; -import {DebugProtocol} from 'vscode-debugprotocol'; -import {DebugClient} from 'vscode-debugadapter-testsupport'; - -export class NsDebugClient extends DebugClient { - private _timeout: number = 90000; - - public getTimeout(): number { - return this._timeout; - } - - public setTimeout(timeout: number) { - this._timeout = timeout; - } - - // The method adds the following enhancements to its base implementation - // 1. It has no hardcoded value for the timeout - // 2. It removes the event listener after it is no needed anymore - public waitForEvent(eventType: string, timeout?: number): Promise { - timeout = timeout || this._timeout; - return new Promise((resolve, reject) => { - let eventListener: (event: DebugProtocol.Event) => any = (event) => { - resolve(event); - this.removeListener(eventType, eventListener); - }; - this.on(eventType, eventListener); - if (timeout) { - setTimeout(() => { - reject(new Error("no event '" + eventType + "' received after " + timeout + " ms")); - }, timeout); - } - }); - } - - public onNextTime(event: string): Promise { - return this.waitForEvent(event); - } - - public onNthTime(n: number, event: string): Promise { - return n == 1 ? - this.onNextTime(event) : - this.onNextTime(event).then(e => this.onNthTime(--n, event)); - } - - public assertSetBreakpoints(path: string, breakpoints: { line: number, condition?: string }[]): Promise { - return this.setBreakpointsRequest({ - lines: breakpoints.map(b => b.line), - breakpoints: breakpoints, - source: { path: path } - }) - .then(response => { - response.body.breakpoints.forEach((bp, i, a) => { - assert.equal(bp.verified, true, 'breakpoint verification mismatch: verified'); - assert.equal(bp.line, breakpoints[i].line, 'breakpoint verification mismatch: line'); - //assert.equal(bp.column, breakpointColumn, 'breakpoint verification mismatch: column'); - }); - return Promise.resolve(); - }); - } - - public assertNthStoppedLocation(n: number, reason: string, expected: { path?: string; line?: number; column?: number; }) - : Promise { - return n == 1 ? - this.assertStoppedLocation(reason, expected) : - this.onNextTime('stopped').then(e => this.assertNthStoppedLocation(--n, reason, expected)); - } -} \ No newline at end of file diff --git a/src/tests/pathTransformData.ts b/src/tests/pathTransformData.ts new file mode 100644 index 0000000..4e0c978 --- /dev/null +++ b/src/tests/pathTransformData.ts @@ -0,0 +1,20 @@ +/* tslint:disable:max-line-length */ +const tests = [ + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/main.js', expectedResult: 'C:\\projectpath\\app\\main.js', existingPath: 'C:\\projectpath\\app\\main.js' }, + { platform: 'android', scriptUrl: 'VM1', expectedResult: 'VM1' }, + { platform: 'android', scriptUrl: 'native prologue.js', expectedResult: 'native prologue.js' }, + { platform: 'android', scriptUrl: 'v8/gc', expectedResult: 'v8/gc' }, + { platform: 'android', scriptUrl: 'VM25', expectedResult: 'VM25' }, + { platform: 'android', scriptUrl: '/data/data/org.nativescript.TabNavigation/files/internal/ts_helpers.js', expectedResult: '/data/data/org.nativescript.TabNavigation/files/internal/ts_helpers.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/nativescript-angular/platform.js', expectedResult: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform.js', existingPath: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/nativescript-angular/platform-common.js', expectedResult: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform-common.js', existingPath: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform-common.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/@angular/common/bundles/common.umd.js', expectedResult: 'C:\\projectpath\\node_modules\\@angular\\common\\bundles\\common.umd.js', existingPath: 'C:\\projectpath\\node_modules\\@angular\\common\\bundles\\common.umd.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/gestures/gestures.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\gestures\\gestures.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\gestures\\gestures.android.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/frame/fragment.transitions.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\frame\\fragment.transitions.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\frame\\fragment.transitions.android.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/debugger/devtools-elements.common.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\debugger\\devtools-elements.common.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\debugger\\devtools-elements.common.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/page/page.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\page\\page.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\page\\page.android.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/layouts/layout-base.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\layouts\\layout-base.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\layouts\\layout-base.android.js' }, + { platform: 'android', scriptUrl: 'ng:///css/0/data/data/org.nativescript.TabNavigation/files/app/tabs/tabs.component.scss.ngstyle.js', expectedResult: 'ng:///css/0/data/data/org.nativescript.TabNavigation/files/app/tabs/tabs.component.scss.ngstyle.js' }, +]; + +export = tests; diff --git a/src/tests/scenario.ts b/src/tests/scenario.ts deleted file mode 100644 index badbf5d..0000000 --- a/src/tests/scenario.ts +++ /dev/null @@ -1,126 +0,0 @@ - -import {DebugProtocol} from 'vscode-debugprotocol'; -import {NsDebugClient} from './nsDebugClient'; - -export class Scenario { - private resolveStarted: () => any; - - private resolveBeforeLaunch: () => any; - private resolveBeforeAttach: () => any; - private resolveBeforeConfigurationDone: () => any; - - private resolveAfterLaunch: () => any; - private resolveAfterAttach: () => any; - private resolveAfterConfigurationDone: () => any; - - public client: NsDebugClient; - - public started: Promise = new Promise(r => this.resolveStarted = r); - public initializeRequest: Promise; - public launchRequest: Promise; - public attachRequest: Promise; - public configurationDoneRequest: Promise; - - // The corresponding command will not start before these promises are resolved - public beforeLaunchRequest: Promise = new Promise(r => this.resolveBeforeLaunch = r); - public beforeAttachRequest: Promise = new Promise(r => this.resolveBeforeAttach = r); - public beforeConfigurationDoneRequest: Promise = new Promise(r => this.resolveBeforeConfigurationDone = r); - - // The corresponding command promise will not be resolved/rejected before these promises are resolved - public afterLaunchRequest: Promise = new Promise(r => this.resolveAfterLaunch = r); - public afterAttachRequest: Promise = new Promise(r => this.resolveAfterAttach = r); - public afterConfigurationDoneRequest: Promise = new Promise(r => this.resolveAfterConfigurationDone = r); - - public initializedEvent: Promise; - public firstStoppedEvent: Promise; - - public initializeRequestArgs: DebugProtocol.InitializeRequestArguments; - public launchRequestArgs: DebugProtocol.LaunchRequestArguments; - public attachRequestArgs: DebugProtocol.AttachRequestArguments; - public configurationDoneArgs: DebugProtocol.ConfigurationDoneArguments; - - public attachInsteadOfLaunch: boolean; - - public static getDefaultInitArgs(): DebugProtocol.InitializeRequestArguments { - return { - adapterID: 'nativescript', - linesStartAt1: true, - columnsStartAt1: true, - pathFormat: 'path' - }; - } - - public static getDefaultLaunchArgs(platform: string, appRoot: string, emulator: boolean): DebugProtocol.LaunchRequestArguments { - let args = { - platform: platform, - request: "launch", - appRoot: appRoot, - sourceMaps: true, - emulator: emulator, - tnsArgs: process.env.DeviceId ? ['--device', process.env.DeviceId] : [] - }; - return args; - } - - public static getDefaultAttachArgs(platform: string, appRoot: string, emulator: boolean): DebugProtocol.LaunchRequestArguments { - let args = { - platform: platform, - request: "attach", - appRoot: appRoot, - sourceMaps: true, - emulator: emulator, - tnsArgs: process.env.DeviceId ? ['--device', process.env.DeviceId] : [] - }; - return args; - } - - constructor(client: NsDebugClient) { - this.client = client; - - this.initializedEvent = this.client.onNextTime('initialized'); - this.firstStoppedEvent = >this.client.onNextTime('stopped'); - - this.initializeRequest = this.started.then(() => { - return this.client.initializeRequest(this.initializeRequestArgs || Scenario.getDefaultInitArgs()); - }); - - this.launchRequest = this.initializeRequest.then(() => { - if (!this.attachInsteadOfLaunch) { - this.resolveBeforeLaunch(); - return this.beforeLaunchRequest.then(() => { - return this.client.launchRequest(this.launchRequestArgs).then(launchResponse => { - this.resolveAfterLaunch(); - return this.afterLaunchRequest.then(_ => launchResponse); - }); - }); - } - }); - - this.attachRequest = this.initializeRequest.then(() => { - if (this.attachInsteadOfLaunch) { - this.resolveBeforeAttach(); - return this.beforeAttachRequest.then(() => { - return this.client.attachRequest(this.attachRequestArgs).then(attachResponse => { - this.resolveAfterAttach(); - return this.afterAttachRequest.then(_ => attachResponse); - }); - }); - } - }); - - this.configurationDoneRequest = Promise.all<{}>([this.launchRequest, this.attachRequest, this.initializedEvent]).then(() => { - this.resolveBeforeConfigurationDone(); - return this.beforeConfigurationDoneRequest.then(() => { - return this.client.configurationDoneRequest(this.configurationDoneArgs).then(confDoneResponse => { - this.resolveAfterConfigurationDone(); - return this.afterConfigurationDoneRequest.then(_ => confDoneResponse); - }); - }); - }); - } - - public start(): Promise<{}> { - this.resolveStarted(); - return Promise.all<{}>([this.configurationDoneRequest, this.firstStoppedEvent]); - } -} \ No newline at end of file diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/AndroidManifest.xml b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/AndroidManifest.xml deleted file mode 100644 index 8d827dc..0000000 --- a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/AndroidManifest.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/app.gradle b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/app.gradle deleted file mode 100644 index 725fb59..0000000 --- a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/app.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Add your native dependencies here: - -// Uncomment to add recyclerview-v7 dependency -//dependencies { -// compile 'com.android.support:recyclerview-v7:+' -//} - -android { - defaultConfig { - generatedDensities = [] - } - aaptOptions { - additionalParameters "--no-version-vectors" - } -} diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-hdpi/icon.png b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-hdpi/icon.png deleted file mode 100755 index 1034356..0000000 Binary files a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-hdpi/icon.png and /dev/null differ diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-ldpi/icon.png b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-ldpi/icon.png deleted file mode 100755 index ddfc17a..0000000 Binary files a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-ldpi/icon.png and /dev/null differ diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-mdpi/icon.png b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-mdpi/icon.png deleted file mode 100755 index 486e410..0000000 Binary files a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-mdpi/icon.png and /dev/null differ diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png deleted file mode 100644 index bd53be2..0000000 Binary files a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png and /dev/null differ diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/iOS/Info.plist b/src/tests/testdata/DebuggerStatement/app/App_Resources/iOS/Info.plist deleted file mode 100644 index 0a8e1eb..0000000 --- a/src/tests/testdata/DebuggerStatement/app/App_Resources/iOS/Info.plist +++ /dev/null @@ -1,66 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - icon.png - CFBundleIcons - - CFBundlePrimaryIcon - - CFBundleIconFiles - - icon-40 - icon-60 - icon-72 - icon-76 - Icon-Small - Icon-Small-50 - - UIPrerenderedIcon - - - - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiresFullScreen - - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/src/tests/testdata/DebuggerStatement/app/app.js b/src/tests/testdata/DebuggerStatement/app/app.js deleted file mode 100644 index efaf44a..0000000 --- a/src/tests/testdata/DebuggerStatement/app/app.js +++ /dev/null @@ -1,2 +0,0 @@ -var application = require("application"); -application.start({ moduleName: "main-page" }); diff --git a/src/tests/testdata/DebuggerStatement/app/debuggerStatement.js b/src/tests/testdata/DebuggerStatement/app/debuggerStatement.js deleted file mode 100644 index 419cedc..0000000 --- a/src/tests/testdata/DebuggerStatement/app/debuggerStatement.js +++ /dev/null @@ -1,5 +0,0 @@ -function log(message) { - console.log(message); -} -debugger; -log('Debugger statment test'); \ No newline at end of file diff --git a/src/tests/testdata/DebuggerStatement/app/main-page.js b/src/tests/testdata/DebuggerStatement/app/main-page.js deleted file mode 100644 index 82c042f..0000000 --- a/src/tests/testdata/DebuggerStatement/app/main-page.js +++ /dev/null @@ -1,6 +0,0 @@ -var ds = require('./debuggerStatement'); - -function onNavigatingTo(args) { - var page = args.object; -} -exports.onNavigatingTo = onNavigatingTo; \ No newline at end of file diff --git a/src/tests/testdata/DebuggerStatement/app/main-page.xml b/src/tests/testdata/DebuggerStatement/app/main-page.xml deleted file mode 100644 index d7c0b3f..0000000 --- a/src/tests/testdata/DebuggerStatement/app/main-page.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -