From 8afc4192ddffaefb3e6cfe63746d36794c023892 Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Fri, 18 Nov 2016 15:32:58 +0200 Subject: [PATCH 1/9] Make Logger service more flexible. Remove custom message logging mechanisms. --- src/debug-adapter/adapter/adapterProxy.ts | 3 +- src/debug-adapter/adapter/pathTransformer.ts | 19 +-- .../adapter/sourceMaps/pathUtilities.ts | 8 +- .../sourceMaps/sourceMapTransformer.ts | 19 +-- .../adapter/sourceMaps/sourceMaps.ts | 2 +- .../connection/androidConnection.ts | 2 +- src/debug-adapter/connection/iosConnection.ts | 2 +- src/debug-adapter/utilities.ts | 60 ---------- src/debug-adapter/webKitDebug.ts | 3 +- src/debug-adapter/webKitDebugAdapter.ts | 61 +++++----- src/debug-adapter/webKitDebugSession.ts | 12 +- src/services/Logger.ts | 108 ++++++++++++++++++ src/services/NsCliService.ts | 28 ++--- 13 files changed, 179 insertions(+), 148 deletions(-) create mode 100644 src/services/Logger.ts diff --git a/src/debug-adapter/adapter/adapterProxy.ts b/src/debug-adapter/adapter/adapterProxy.ts index af75bf6..a0d4653 100644 --- a/src/debug-adapter/adapter/adapterProxy.ts +++ b/src/debug-adapter/adapter/adapterProxy.ts @@ -3,6 +3,7 @@ *--------------------------------------------------------*/ import * as utils from '../utilities'; +import {Logger} from '../../services/Logger'; import {DebugProtocol} from 'vscode-debugprotocol'; export type EventHandler = (event: DebugProtocol.Event) => void; @@ -77,7 +78,7 @@ export class AdapterProxy { this._eventHandler(event); } } catch (e) { - utils.Logger.log('Error handling adapter event: ' + (e ? e.stack : '')); + Logger.error('Error handling adapter event: ' + (e ? e.stack : '')); } } } diff --git a/src/debug-adapter/adapter/pathTransformer.ts b/src/debug-adapter/adapter/pathTransformer.ts index ef5aafb..045d267 100644 --- a/src/debug-adapter/adapter/pathTransformer.ts +++ b/src/debug-adapter/adapter/pathTransformer.ts @@ -3,6 +3,7 @@ *--------------------------------------------------------*/ import * as utils from '../utilities'; +import {Logger} from '../../services/Logger'; import {DebugProtocol} from 'vscode-debugprotocol'; import * as path from 'path'; @@ -43,7 +44,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { if (utils.isURL(args.source.path)) { // already a url, use as-is - utils.Logger.log(`Paths.setBP: ${args.source.path} is already a URL`); + Logger.log(`Paths.setBP: ${args.source.path} is already a URL`); resolve(); return; } @@ -51,7 +52,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { const url = utils.canonicalizeUrl(args.source.path); if (this._clientPathToWebkitUrl.has(url)) { args.source.path = this._clientPathToWebkitUrl.get(url); - utils.Logger.log(`Paths.setBP: Resolved ${url} to ${args.source.path}`); + Logger.log(`Paths.setBP: Resolved ${url} to ${args.source.path}`); resolve(); } else if (this.inferedDeviceRoot) { @@ -71,11 +72,11 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { inferedUrl = inferedUrl.replace(`.${this._platform}.`, '.'); args.source.path = inferedUrl; - utils.Logger.log(`Paths.setBP: Resolved (by infering) ${url} to ${args.source.path}`); + Logger.log(`Paths.setBP: Resolved (by infering) ${url} to ${args.source.path}`); resolve(); } else { - utils.Logger.log(`Paths.setBP: No target url cached for client path: ${url}, waiting for target script to be loaded.`); + Logger.log(`Paths.setBP: No target url cached for client path: ${url}, waiting for target script to be loaded.`); args.source.path = url; this._pendingBreakpointsByPath.set(args.source.path, { resolve, reject, args }); } @@ -96,7 +97,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { if (!this.inferedDeviceRoot && this._platform === "android") { this.inferedDeviceRoot = utils.inferDeviceRoot(this._webRoot, this._platform, webkitUrl); - utils.Logger.log("\n\n\n ***Inferred device root: " + this.inferedDeviceRoot + "\n\n\n"); + Logger.log("\n\n\n ***Inferred device root: " + this.inferedDeviceRoot + "\n\n\n"); if (this.inferedDeviceRoot.indexOf("/data/user/0/") != -1) { @@ -107,9 +108,9 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { const clientPath = utils.webkitUrlToClientPath(this._webRoot, this._platform, webkitUrl); if (!clientPath) { - utils.Logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._webRoot}`); + Logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._webRoot}`); } else { - utils.Logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._webRoot}`); + Logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._webRoot}`); this._clientPathToWebkitUrl.set(clientPath, webkitUrl); this._webkitUrlToClientPath.set(webkitUrl, clientPath); @@ -117,7 +118,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { } if (this._pendingBreakpointsByPath.has(event.body.scriptUrl)) { - utils.Logger.log(`Paths.scriptParsed: Resolving pending breakpoints for ${event.body.scriptUrl}`); + Logger.log(`Paths.scriptParsed: Resolving pending breakpoints for ${event.body.scriptUrl}`); const pendingBreakpoint = this._pendingBreakpointsByPath.get(event.body.scriptUrl); this._pendingBreakpointsByPath.delete(event.body.scriptUrl); this.setBreakpoints(pendingBreakpoint.args).then(pendingBreakpoint.resolve, pendingBreakpoint.reject); @@ -141,4 +142,4 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { } }); } -} +} \ No newline at end of file diff --git a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts index 411acd6..ed007ea 100644 --- a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts +++ b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts @@ -6,7 +6,7 @@ import * as Path from 'path'; import * as URL from 'url'; - +import {Logger} from '../../../services/Logger'; import * as utils from '../../utilities'; export function getPathRoot(p: string) { @@ -83,16 +83,16 @@ export function getAbsSourceRoot(sourceRoot: string, webRoot: string, generatedP } } - utils.Logger.log(`SourceMap: resolved sourceRoot ${sourceRoot} -> ${absSourceRoot}`); + Logger.log(`SourceMap: resolved sourceRoot ${sourceRoot} -> ${absSourceRoot}`); } else { if (Path.isAbsolute(generatedPath)) { absSourceRoot = Path.dirname(generatedPath); - utils.Logger.log(`SourceMap: no sourceRoot specified, using script dirname: ${absSourceRoot}`); + Logger.log(`SourceMap: no sourceRoot specified, using script dirname: ${absSourceRoot}`); } else { // runtime script is not on disk, resolve the sourceRoot location on disk const scriptPathDirname = Path.dirname(URL.parse(generatedPath).pathname); absSourceRoot = Path.join(webRoot, scriptPathDirname); - utils.Logger.log(`SourceMap: no sourceRoot specified, using webRoot + script path dirname: ${absSourceRoot}`); + Logger.log(`SourceMap: no sourceRoot specified, using webRoot + script path dirname: ${absSourceRoot}`); } } diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts index 787d31b..8f3b012 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import * as fs from 'fs'; +import {Logger} from '../../../services/Logger'; import {DebugProtocol} from 'vscode-debugprotocol'; import {ISourceMaps, SourceMaps} from './sourceMaps'; import * as utils from '../../utilities'; @@ -59,7 +60,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { const argsPath = args.source.path; const mappedPath = this._sourceMaps.MapPathFromSource(argsPath); if (mappedPath) { - utils.Logger.log(`SourceMaps.setBP: Mapped ${argsPath} to ${mappedPath}`); + Logger.log(`SourceMaps.setBP: Mapped ${argsPath} to ${mappedPath}`); args.authoredPath = argsPath; args.source.path = mappedPath; @@ -68,11 +69,11 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { const mappedLines = args.lines.map((line, i) => { const mapped = this._sourceMaps.MapFromSource(argsPath, line, /*column=*/0); if (mapped) { - utils.Logger.log(`SourceMaps.setBP: Mapped ${argsPath}:${line}:0 to ${mappedPath}:${mapped.line}:${mapped.column}`); + Logger.log(`SourceMaps.setBP: Mapped ${argsPath}:${line}:0 to ${mappedPath}:${mapped.line}:${mapped.column}`); mappedCols[i] = mapped.column; return mapped.line; } else { - utils.Logger.log(`SourceMaps.setBP: Mapped ${argsPath} but not line ${line}, column 0`); + Logger.log(`SourceMaps.setBP: Mapped ${argsPath} but not line ${line}, column 0`); mappedCols[i] = 0; return line; } @@ -100,10 +101,10 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { }); } else if (this._allRuntimeScriptPaths.has(argsPath)) { // It's a generated file which is loaded - utils.Logger.log(`SourceMaps.setBP: SourceMaps are enabled but ${argsPath} is a runtime script`); + Logger.log(`SourceMaps.setBP: SourceMaps are enabled but ${argsPath} is a runtime script`); } else { // Source (or generated) file which is not loaded, need to wait - utils.Logger.log(`SourceMaps.setBP: ${argsPath} can't be resolved to a loaded script.`); + Logger.log(`SourceMaps.setBP: ${argsPath} can't be resolved to a loaded script.`); this._pendingBreakpointsByPath.set(argsPath, { resolve, reject, args, requestSeq }); return; } @@ -131,10 +132,10 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { response.breakpoints.forEach((bp, i) => { const mapped = this._sourceMaps.MapToSource(args.source.path, args.lines[i], args.cols[i]); if (mapped) { - utils.Logger.log(`SourceMaps.setBP: Mapped ${args.source.path}:${bp.line}:${bp.column} to ${mapped.path}:${mapped.line}`); + Logger.log(`SourceMaps.setBP: Mapped ${args.source.path}:${bp.line}:${bp.column} to ${mapped.path}:${mapped.line}`); bp.line = mapped.line; } else { - utils.Logger.log(`SourceMaps.setBP: Can't map ${args.source.path}:${bp.line}:${bp.column}, keeping the line number as-is.`); + Logger.log(`SourceMaps.setBP: Can't map ${args.source.path}:${bp.line}:${bp.column}, keeping the line number as-is.`); } this._requestSeqToSetBreakpointsArgs.delete(requestSeq); @@ -200,7 +201,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { this._sourceMaps.ProcessNewSourceMap(event.body.scriptUrl, sourceMapUrlValue).then(() => { const sources = this._sourceMaps.AllMappedSources(event.body.scriptUrl); if (sources) { - utils.Logger.log(`SourceMaps.scriptParsed: ${event.body.scriptUrl} was just loaded and has mapped sources: ${JSON.stringify(sources)}`); + Logger.log(`SourceMaps.scriptParsed: ${event.body.scriptUrl} was just loaded and has mapped sources: ${JSON.stringify(sources)}`); sources.forEach(this.resolvePendingBreakpoints, this); } }); @@ -240,7 +241,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { private resolvePendingBreakpoints(sourcePath: string): void { // If there's a setBreakpoints request waiting on this script, go through setBreakpoints again if (this._pendingBreakpointsByPath.has(sourcePath)) { - utils.Logger.log(`SourceMaps.scriptParsed: Resolving pending breakpoints for ${sourcePath}`); + Logger.log(`SourceMaps.scriptParsed: Resolving pending breakpoints for ${sourcePath}`); const pendingBreakpoint = this._pendingBreakpointsByPath.get(sourcePath); this._pendingBreakpointsByPath.delete(sourcePath); diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts index 57c5c82..ee4487d 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts @@ -10,7 +10,7 @@ import * as FS from 'fs'; import {SourceMapConsumer} from 'source-map'; import * as PathUtils from './pathUtilities'; import * as utils from '../../utilities'; -import {Logger} from '../../utilities'; +import {Logger} from '../../../services/Logger'; export interface MappingResult { diff --git a/src/debug-adapter/connection/androidConnection.ts b/src/debug-adapter/connection/androidConnection.ts index 1d4c9e6..531a748 100644 --- a/src/debug-adapter/connection/androidConnection.ts +++ b/src/debug-adapter/connection/androidConnection.ts @@ -1,6 +1,6 @@ import * as http from 'http'; import {EventEmitter} from 'events'; -import {Logger} from '../utilities'; +import {Logger} from '../../services/Logger'; import * as Net from 'net'; import * as ns from '../../services/NsCliService'; import { INSDebugConnection } from './INSDebugConnection'; diff --git a/src/debug-adapter/connection/iosConnection.ts b/src/debug-adapter/connection/iosConnection.ts index c56540f..008eddd 100644 --- a/src/debug-adapter/connection/iosConnection.ts +++ b/src/debug-adapter/connection/iosConnection.ts @@ -7,7 +7,7 @@ import * as stream from 'stream'; import {EventEmitter} from 'events'; import {INSDebugConnection} from './INSDebugConnection'; import * as utils from '../utilities'; -import {Logger} from '../utilities'; +import {Logger} from '../../services/Logger'; import * as ns from '../../services/NsCliService'; interface IMessageWithId { diff --git a/src/debug-adapter/utilities.ts b/src/debug-adapter/utilities.ts index ff145d3..8350122 100644 --- a/src/debug-adapter/utilities.ts +++ b/src/debug-adapter/utilities.ts @@ -137,66 +137,6 @@ export function retryAsync(fn: () => Promise, timeoutMs: number): Promise void; - private _diagnosticLoggingEnabled: boolean; - - public static log(msg: string, forceDiagnosticLogging = false): void { - if (this._logger) this._logger._log(msg, forceDiagnosticLogging); - } - - public static init(isServer: boolean, logCallback: (msg: string) => void): void { - if (!this._logger) { - this._logger = new Logger(isServer); - this._logger._diagnosticLogCallback = logCallback; - - if (isServer) { - Logger.logVersionInfo(); - } - } - } - - public static enableDiagnosticLogging(): void { - if (this._logger) { - this._logger._diagnosticLoggingEnabled = true; - if (!this._logger._isServer) { - Logger.logVersionInfo(); - } - } - } - - public static logVersionInfo(): void { - Logger.log(`OS: ${os.platform() } ${os.arch() }`); - Logger.log('Node version: ' + process.version); - Logger.log('Adapter version: ' + require('../../package.json').version); - } - - constructor(isServer: boolean) { - this._isServer = isServer; - } - - private _log(msg: string, forceDiagnosticLogging: boolean): void { - if (this._isServer || this._diagnosticLoggingEnabled || forceDiagnosticLogging) { - this._sendLog(msg); - } - } - - private _sendLog(msg: string): void { - if (this._isServer) { - console.log(msg); - } else if (this._diagnosticLogCallback) { - this._diagnosticLogCallback(msg); - } - } -} - function tryFindSourcePathInNSProject(nsProjectPath: string, additionalFileExtension: string, resorcePath: string) : string { let guesses = []; const pathParts = resorcePath.split(path.sep); diff --git a/src/debug-adapter/webKitDebug.ts b/src/debug-adapter/webKitDebug.ts index 0a33ae0..1a250a3 100644 --- a/src/debug-adapter/webKitDebug.ts +++ b/src/debug-adapter/webKitDebug.ts @@ -3,6 +3,5 @@ *--------------------------------------------------------*/ import {WebKitDebugSession} from './webKitDebugSession'; -import {DebugSession} from 'vscode-debugadapter'; -DebugSession.run(WebKitDebugSession); +WebKitDebugSession.run(WebKitDebugSession); diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index fbd6f3a..054d3cb 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -2,6 +2,8 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ +import * as os from 'os'; +import * as fs from 'fs'; import {spawn, ChildProcess} from 'child_process'; import * as path from 'path'; import {Handles, StoppedEvent, InitializedEvent, TerminatedEvent, OutputEvent} from 'vscode-debugadapter'; @@ -14,6 +16,7 @@ import {formatConsoleMessage} from './consoleHelper'; import * as ns from '../services/NsCliService'; import {AnalyticsService} from '../services/analytics/AnalyticsService'; import {ExtensionClient} from '../services/ipc/ExtensionClient'; +import {Logger, LoggerHandler, Handlers, Tags} from '../services/Logger'; interface IScopeVarHandle { objectId: string; @@ -41,13 +44,19 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { private _eventHandler: (event: DebugProtocol.Event) => void; private appRoot: string; private platform: string; - private isAttached: boolean; private _lastOutputEvent: OutputEvent; + private _loggerFrontendHandler: LoggerHandler = args => this.fireEvent(new OutputEvent(` ›${args.message}\n`, args.type.toString())); public constructor() { this._variableHandles = new Handles(); this._overlayHelper = new utils.DebounceHelper(/*timeoutMs=*/200); + // Messages tagged with a special tag are sent to the frontend through the debugging protocol + Logger.addHandler(this._loggerFrontendHandler, [Tags.FrontendMessage]); + Logger.log(`OS: ${os.platform()} ${os.arch()}`); + Logger.log('Node version: ' + process.version); + Logger.log('Adapter version: ' + require('../../package.json').version); + this.clearEverything(); } @@ -104,30 +113,34 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { return this._attach(args); } - private initDiagnosticLogging(name: string, args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): void { + private configureLoggingForRequest(name: string, args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): void { if (args.diagnosticLogging) { - utils.Logger.enableDiagnosticLogging(); - utils.Logger.log(`initialize(${JSON.stringify(this._initArgs) })`); - utils.Logger.log(`${name}(${JSON.stringify(args) })`); + // The logger frontend handler is initially configured to handle messages with LoggerTagFrontendMessage tag only. + // We remove the handler and add it again for all messages. + Logger.removeHandler(this._loggerFrontendHandler); + Logger.addHandler(this._loggerFrontendHandler); + } + if (args.tnsOutput) { + Logger.addHandler(Handlers.createStreamHandler(fs.createWriteStream(args.tnsOutput))); } + Logger.log(`initialize(${JSON.stringify(this._initArgs) })`); + Logger.log(`${name}(${JSON.stringify(args) })`); } private _attach(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs) { ExtensionClient.setAppRoot(utils.getAppRoot(args)); let analyticsRequest = (args.request == "launch" && !(args as DebugProtocol.ILaunchRequestArgs).rebuild) ? "sync" : args.request; ExtensionClient.getInstance().analyticsLaunchDebugger({ request: analyticsRequest, platform: args.platform }); - this.initDiagnosticLogging(args.request, args); + this.configureLoggingForRequest(args.request, args); this.appRoot = utils.getAppRoot(args); this.platform = args.platform; - this.isAttached = false; return ((args.platform == 'ios') ? this._attachIos(args) : this._attachAndroid(args)) .then(() => { - this.isAttached = true; this.fireEvent(new InitializedEvent()); }, e => { - this.onTnsOutputMessage("Command failed: " + e, "error"); + Logger.error("Command failed: " + e, Tags.FrontendMessage); this.clearEverything(); return utils.errP(e); }); @@ -135,7 +148,6 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { private _attachIos(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): Promise { let iosProject : ns.IosProject = new ns.IosProject(this.appRoot, args.tnsOutput); - iosProject.on('TNS.outputMessage', (message, level) => this.onTnsOutputMessage.apply(this, [message, level])); return iosProject.debug(args) .then((socketFilePath) => { @@ -149,9 +161,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { let androidProject: ns.AndroidProject = new ns.AndroidProject(this.appRoot, args.tnsOutput); let thisAdapter: WebKitDebugAdapter = this; - androidProject.on('TNS.outputMessage', (message, level) => thisAdapter.onTnsOutputMessage.apply(thisAdapter, [message, level])); - - this.onTnsOutputMessage("Getting debug port"); + Logger.log("Getting debug port"); let androidConnection: AndroidConnection = null; let runDebugCommand: Promise = (args.request == 'launch') ? androidProject.debug(args) : Promise.resolve(); @@ -165,7 +175,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { this.setConnection(androidConnection, args); } }).then(() => { - this.onTnsOutputMessage("Attaching to debug application"); + Logger.log("Attaching to debug application"); return androidConnection.attach(port, 'localhost'); }); }); @@ -188,23 +198,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } private onConnected(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): void { - this.onTnsOutputMessage("Debugger connected"); - } - - private onTnsOutputMessage(message: string, level: string = "log"): void { - utils.Logger.log(message); - - if (!this.isAttached) { - let messageParams: WebKitProtocol.Console.MessageAddedParams = { - message: { - level: level, - type: 'log', - text: message - } - }; - - this.onConsoleMessage(messageParams); - } + Logger.log("Debugger connected"); } private fireEvent(event: DebugProtocol.Event): void { @@ -218,18 +212,17 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { this.fireEvent(new TerminatedEvent()); } - this.onTnsOutputMessage("Terminating debug session"); + Logger.log("Terminating debug session"); this.clearEverything(); } private clearEverything(): void { this.clearClientContext(); this.clearTargetContext(); - this.isAttached = false; this._chromeProc = null; if (this._webKitConnection) { - this.onTnsOutputMessage("Closing debug connection"); + Logger.log("Closing debug connection"); this._webKitConnection.close(); this._webKitConnection = null; diff --git a/src/debug-adapter/webKitDebugSession.ts b/src/debug-adapter/webKitDebugSession.ts index 7e0d66b..ae157aa 100644 --- a/src/debug-adapter/webKitDebugSession.ts +++ b/src/debug-adapter/webKitDebugSession.ts @@ -1,11 +1,8 @@ -/*--------------------------------------------------------- - * Copyright (C) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------*/ import {OutputEvent, DebugSession, ErrorDestination} from 'vscode-debugadapter'; import {DebugProtocol} from 'vscode-debugprotocol'; import {WebKitDebugAdapter} from './webKitDebugAdapter'; -import {Logger} from './utilities'; +import {Logger, Handlers, LoggerHandler} from '../services/Logger'; import {AdapterProxy} from './adapter/adapterProxy'; import {LineNumberTransformer} from './adapter/lineNumberTransformer'; @@ -18,7 +15,12 @@ export class WebKitDebugSession extends DebugSession { public constructor(targetLinesStartAt1: boolean, isServer: boolean = false) { super(targetLinesStartAt1, isServer); - Logger.init(isServer, msg => this.sendEvent(new OutputEvent(` ›${msg}\n`))); + // Logging on the std streams is only allowed when running in server mode, because otherwise it goes through + // the same channel that Code uses to communicate with the adapter, which can cause communication issues. + if (isServer) { + Logger.addHandler(Handlers.stdStreamsHandler); + } + process.removeAllListeners('unhandledRejection'); process.addListener('unhandledRejection', reason => { Logger.log(`******** ERROR! Unhandled promise rejection: ${reason}`); diff --git a/src/services/Logger.ts b/src/services/Logger.ts new file mode 100644 index 0000000..85113e8 --- /dev/null +++ b/src/services/Logger.ts @@ -0,0 +1,108 @@ +import * as fs from 'fs'; + +export enum LoggerMessageType { + Log, + Info, + Warning, + Error +} + +export interface LoggerMessageEventArgs { + message: string, + type: LoggerMessageType +} + +export type LoggerHandler = ((args: LoggerMessageEventArgs) => void); +type TaggedLoggerHandler = { handler: LoggerHandler, tags: string[] }; + +/** + * The logger is a singleton. + */ +export class Logger { + private static _instance: Logger; + + private _handlers: TaggedLoggerHandler[]; + + constructor() { + this._handlers = []; + } + + private static get instance(): Logger { + this._instance = this._instance || new Logger(); + return this._instance; + } + + private static handleMessage(message: string, type: LoggerMessageType = LoggerMessageType.Log, tag: string = null) { + for (let handler of this.instance._handlers) { + if (!handler.tags || handler.tags.length == 0 || handler.tags.indexOf(tag) > -1) { + handler.handler({ message: message, type: type }); + } + } + } + + public static log(message: string, tag: string = null): void { + this.handleMessage(message, LoggerMessageType.Log, tag); + } + + public static info(message: string, tag: string = null): void { + this.handleMessage(message, LoggerMessageType.Info, tag); + } + + public static warn(message: string, tag: string = null): void { + this.handleMessage(message, LoggerMessageType.Warning, tag); + } + + public static error(message: string, tag: string = null): void { + this.handleMessage(message, LoggerMessageType.Error, tag); + } + + public static addHandler(handler: LoggerHandler, tags: string[] = null) { + tags = tags || []; + this.instance._handlers.push({ handler: handler, tags: tags }); + } + + /** + * Removes all occurrence of this handler, ignoring the associated tags + */ + public static removeHandler(handlerToRemove: LoggerHandler) { + let i = this.instance._handlers.length; + while (i--) { + if (this.instance._handlers[i].handler == handlerToRemove) { + this.instance._handlers.splice(i, 1); + } + } + } +} + +export namespace Tags { + export const FrontendMessage: string = "LoggerTag.FrontendMessage"; +} + +export namespace Handlers { + export function stdStreamsHandler(args: LoggerMessageEventArgs) { + switch(args.type) { + case LoggerMessageType.Log: + console.log(args.message); + break; + case LoggerMessageType.Info: + console.info(args.message); + break; + case LoggerMessageType.Warning: + console.warn(args.message); + break; + case LoggerMessageType.Error: + console.error(args.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 diff --git a/src/services/NsCliService.ts b/src/services/NsCliService.ts index 2531492..b55b1d4 100644 --- a/src/services/NsCliService.ts +++ b/src/services/NsCliService.ts @@ -4,7 +4,7 @@ import {EventEmitter} from 'events'; import * as path from 'path'; import * as https from 'https'; import {Version} from '../common/Version'; -import {Logger} from '../debug-adapter/utilities'; +import {Logger, Handlers, Tags} from '../services/Logger'; import {ExtensionVersionInfo} from './ExtensionVersionInfo'; import {DebugProtocol} from 'vscode-debugprotocol'; @@ -67,13 +67,11 @@ export class CliVersionInfo { export abstract class NSProject extends EventEmitter { private _projectPath: string; - private _tnsOutputFileStream: fs.WriteStream; private _cliVersionInfo: CliVersionInfo; constructor(projectPath: string, tnsOutputFilePath?: string) { super(); this._projectPath = projectPath; - this._tnsOutputFileStream = tnsOutputFilePath ? fs.createWriteStream(tnsOutputFilePath) : null; this._cliVersionInfo = new CliVersionInfo(); } @@ -98,12 +96,6 @@ export abstract class NSProject extends EventEmitter { child.stderr.setEncoding('utf8'); return child; } - - protected writeToTnsOutputFile(message: string) { - if (this._tnsOutputFileStream) { - this._tnsOutputFileStream.write(message, 'utf8'); - } - } } export class IosProject extends NSProject { @@ -174,8 +166,7 @@ export class IosProject extends NSProject { child.stdout.on('data', (data) => { let cliOutput: string = data.toString(); - this.emit('TNS.outputMessage', cliOutput, 'log'); - this.writeToTnsOutputFile(cliOutput); + Logger.log(cliOutput, Tags.FrontendMessage); socketPath = socketPath || isSocketOpened(cliOutput); appSynced = rebuild ? false : (appSynced || isAppSynced(cliOutput)); @@ -186,8 +177,7 @@ export class IosProject extends NSProject { }); child.stderr.on('data', (data) => { - this.emit('TNS.outputMessage', data, 'error'); - this.writeToTnsOutputFile(data.toString()); + Logger.error(data.toString(), Tags.FrontendMessage); }); child.on('close', (code, signal) => { @@ -248,8 +238,7 @@ export class AndroidProject extends NSProject { let child: ChildProcess = this.spawnProcess(command.path, command.args, args.tnsOutput); child.stdout.on('data', function(data) { let strData: string = data.toString(); - that.emit('TNS.outputMessage', data.toString(), 'log'); - that.writeToTnsOutputFile(strData); + Logger.log(data.toString(), Tags.FrontendMessage); if (!launched) { if (args.request === "launch" && strData.indexOf('# NativeScript Debugger started #') > -1) { launched = true; @@ -262,8 +251,7 @@ export class AndroidProject extends NSProject { }); child.stderr.on('data', function(data) { - that.emit('TNS.outputMessage', data.toString(), 'error'); - that.writeToTnsOutputFile(data.toString()); + Logger.error(data.toString(), Tags.FrontendMessage); }); child.on('close', function(code) { @@ -298,8 +286,7 @@ export class AndroidProject extends NSProject { let child: ChildProcess = this.spawnProcess(command.path, command.args, args.tnsOutput); child.stdout.on('data', function(data) { - that.emit('TNS.outputMessage', data.toString(), 'log'); - that.writeToTnsOutputFile(data.toString()); + Logger.log(data.toString(), Tags.FrontendMessage); let regexp = new RegExp("(?:debug port: )([\\d]{5})"); @@ -325,8 +312,7 @@ export class AndroidProject extends NSProject { }); child.stderr.on('data', function(data) { - that.emit('TNS.outputMessage', data.toString(), 'error'); - that.writeToTnsOutputFile(data.toString()); + Logger.error(data.toString(), Tags.FrontendMessage); }); child.on('close', function(code) { From cd1a68d42d684d22dded3b441726fae6e0a94551 Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Fri, 18 Nov 2016 17:30:44 +0200 Subject: [PATCH 2/9] Cleanup the default debug configurations. Remove emulator option. Don't pass --debug-brk by default. --- package.json | 65 +++++-------------- .../debugProtocolExtensions.d.ts | 7 +- src/services/NsCliService.ts | 6 +- 3 files changed, 22 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index 18a1ba5..a9c76ed 100644 --- a/package.json +++ b/package.json @@ -111,81 +111,58 @@ { "name": "Sync on iOS", "type": "nativescript", - "platform": "ios", "request": "launch", + "platform": "ios", "appRoot": "${workspaceRoot}", - "sourceMaps": true, - "diagnosticLogging": false, - "emulator": false, - "rebuild": false, - "syncAllFiles": false + "sourceMaps": true }, { "name": "Launch on iOS", "type": "nativescript", - "platform": "ios", "request": "launch", + "platform": "ios", "appRoot": "${workspaceRoot}", "sourceMaps": true, - "diagnosticLogging": false, - "emulator": false, "rebuild": true }, { "name": "Attach on iOS", "type": "nativescript", - "platform": "ios", "request": "attach", + "platform": "ios", "appRoot": "${workspaceRoot}", - "sourceMaps": true, - "diagnosticLogging": false, - "emulator": false + "sourceMaps": true }, { "name": "Sync on Android", "type": "nativescript", - "platform": "android", "request": "launch", + "platform": "android", "appRoot": "${workspaceRoot}", - "sourceMaps": true, - "diagnosticLogging": false, - "emulator": false, - "rebuild": false + "sourceMaps": true }, { "name": "Launch on Android", "type": "nativescript", - "platform": "android", "request": "launch", + "platform": "android", "appRoot": "${workspaceRoot}", "sourceMaps": true, - "diagnosticLogging": false, - "emulator": false, "rebuild": true }, { "name": "Attach on Android", "type": "nativescript", - "platform": "android", "request": "attach", + "platform": "android", "appRoot": "${workspaceRoot}", - "sourceMaps": true, - "diagnosticLogging": false, - "emulator": false + "sourceMaps": true } ], "configurationAttributes": { "launch": { "required": [], "properties": { - "runtimeArgs": { - "type": "array", - "description": "Optional arguments passed to the runtime executable.", - "items": { - "type": "string" - }, - "default": [] - }, "tnsArgs": { "type": "array", "description": "Optional arguments passed to the NativeScript CLI executable.", @@ -219,9 +196,14 @@ "description": "NativeScript platform", "default": null }, - "emulator": { + "stopOnEntry": { "type": "boolean", - "description": "Whether the app to run in emulator or on a physical device.", + "description": "Automatically stop on the first line after lauch", + "default": false + }, + "noDebug": { + "type": "boolean", + "description": "If true the launch request will launch the program without enabling debugging", "default": false }, "rebuild": { @@ -244,14 +226,6 @@ "attach": { "required": [], "properties": { - "runtimeArgs": { - "type": "array", - "description": "Optional arguments passed to the runtime executable.", - "items": { - "type": "string" - }, - "default": [] - }, "tnsArgs": { "type": "array", "description": "Optional arguments passed to the NativeScript CLI executable.", @@ -285,11 +259,6 @@ "description": "NativeScript platform", "default": null }, - "emulator": { - "type": "boolean", - "description": "Whether the app to run in emulator or on a physical device.", - "default": false - }, "nativescriptCliPath": { "type": "string", "description": "Path to the nativescript CLI to be used by the NativeScript extension.", diff --git a/src/custom-typings/debugProtocolExtensions.d.ts b/src/custom-typings/debugProtocolExtensions.d.ts index a289a69..b318a29 100644 --- a/src/custom-typings/debugProtocolExtensions.d.ts +++ b/src/custom-typings/debugProtocolExtensions.d.ts @@ -2,15 +2,15 @@ import {DebugProtocol} from 'vscode-debugprotocol'; declare module 'vscode-debugprotocol' { namespace DebugProtocol { + + type RequestArguments = LaunchRequestArguments | AttachRequestArguments; + interface ILaunchRequestArgs extends DebugProtocol.LaunchRequestArguments { platform: string; appRoot?: string; - runtimeArgs?: string[]; - runtimeExecutable?: string; stopOnEntry?: boolean; sourceMaps?: boolean; diagnosticLogging?: boolean; - emulator?:boolean; request: string; tnsArgs?: string[]; tnsOutput?: string; @@ -24,7 +24,6 @@ declare module 'vscode-debugprotocol' { appRoot?: string; sourceMaps?: boolean; diagnosticLogging?: boolean; - emulator?:boolean; request: string; tnsArgs?: string[]; tnsOutput?: string; diff --git a/src/services/NsCliService.ts b/src/services/NsCliService.ts index b55b1d4..9bac02a 100644 --- a/src/services/NsCliService.ts +++ b/src/services/NsCliService.ts @@ -133,9 +133,8 @@ export class IosProject extends NSProject { let command = new CommandBuilder(args.nativescriptCliPath) .appendParam("debug") .appendParam(this.platform()) - .appendParamIf("--emulator", args.emulator) .appendParamIf("--start", args.request === "attach") - .appendParamIf("--debug-brk", args.request === "launch") + .appendParamIf("--debug-brk", args.request === "launch" && (args as DebugProtocol.ILaunchRequestArgs).stopOnEntry) .appendParamIf("--no-rebuild", !rebuild) .appendParamIf("--syncAllFiles", args.request === "launch" && !rebuild && (args as DebugProtocol.ILaunchRequestArgs).syncAllFiles) .appendParam("--no-client") @@ -225,9 +224,8 @@ export class AndroidProject extends NSProject { let command = new CommandBuilder(args.nativescriptCliPath) .appendParam("debug") .appendParam(this.platform()) - .appendParamIf("--emulator", args.emulator) .appendParamIf("--no-rebuild", args.rebuild !== true) - .appendParam("--debug-brk") + .appendParamIf("--debug-brk", args.stopOnEntry) .appendParam("--no-client") .appendParams(args.tnsArgs) .build(); From d058e1335d808b617fe9e5c2b1bda419fef758de Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Fri, 18 Nov 2016 17:42:03 +0200 Subject: [PATCH 3/9] Cleanup the webkit debug adapter --- src/debug-adapter/utilities.ts | 31 ------------------------- src/debug-adapter/webKitDebugAdapter.ts | 16 +------------ 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/src/debug-adapter/utilities.ts b/src/debug-adapter/utilities.ts index 8350122..f20371d 100644 --- a/src/debug-adapter/utilities.ts +++ b/src/debug-adapter/utilities.ts @@ -59,37 +59,6 @@ export function existsSync(path: string): boolean { } } -export class DebounceHelper { - private waitToken: NodeJS.Timer; - - constructor(private timeoutMs: number) { } - - /** - * If not waiting already, call fn after the timeout - */ - public wait(fn: () => any): void { - if (!this.waitToken) { - this.waitToken = setTimeout(() => { - this.waitToken = null; - fn(); - }, - this.timeoutMs); - } - } - - /** - * If waiting for something, cancel it and call fn immediately - */ - public doAndCancel(fn: () => any): void { - if (this.waitToken) { - clearTimeout(this.waitToken); - this.waitToken = null; - } - - fn(); - } -} - /** * Returns a reversed version of arr. Doesn't modify the input. */ diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index 054d3cb..d3b8615 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -29,17 +29,13 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { private _initArgs: DebugProtocol.InitializeRequestArguments; - private _clientAttached: boolean; private _variableHandles: Handles; private _currentStack: WebKitProtocol.Debugger.CallFrame[]; private _committedBreakpointsByUrl: Map; - private _overlayHelper: utils.DebounceHelper; private _exceptionValueObject: WebKitProtocol.Runtime.RemoteObject; private _expectingResumedEvent: boolean; private _scriptsById: Map; private _setBreakpointsRequestQ: Promise; - - private _chromeProc: ChildProcess; private _webKitConnection: INSDebugConnection; private _eventHandler: (event: DebugProtocol.Event) => void; private appRoot: string; @@ -49,7 +45,6 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { public constructor() { this._variableHandles = new Handles(); - this._overlayHelper = new utils.DebounceHelper(/*timeoutMs=*/200); // Messages tagged with a special tag are sent to the frontend through the debugging protocol Logger.addHandler(this._loggerFrontendHandler, [Tags.FrontendMessage]); @@ -72,7 +67,6 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } private clearClientContext(): void { - this._clientAttached = false; this.fireEvent({ seq: 0, type: 'event', event: 'clearClientContext'}); } @@ -208,9 +202,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } private terminateSession(): void { - if (this._clientAttached) { - this.fireEvent(new TerminatedEvent()); - } + //this.fireEvent(new TerminatedEvent()); Logger.log("Terminating debug session"); this.clearEverything(); @@ -219,7 +211,6 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { private clearEverything(): void { this.clearClientContext(); this.clearTargetContext(); - this._chromeProc = null; if (this._webKitConnection) { Logger.log("Closing debug connection"); @@ -333,11 +324,6 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } public disconnect(): Promise { - if (this._chromeProc) { - this._chromeProc.kill('SIGINT'); - this._chromeProc = null; - } - this.clearEverything(); return Promise.resolve(); From 82ec3b353b6cb923eea260f6999717bef8c36166 Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Fri, 18 Nov 2016 18:26:19 +0200 Subject: [PATCH 4/9] Version class refactoring --- src/common/Version.ts | 20 ------------- src/main.ts | 8 +++--- src/services/NsCliService.ts | 16 +++++------ src/services/analytics/AnalyticsService.ts | 8 +++--- ...sionInfo.ts => extensionVersionService.ts} | 22 +++++++-------- src/services/version.ts | 28 +++++++++++++++++++ 6 files changed, 55 insertions(+), 47 deletions(-) delete mode 100644 src/common/Version.ts rename src/services/{ExtensionVersionInfo.ts => extensionVersionService.ts} (83%) create mode 100644 src/services/version.ts diff --git a/src/common/Version.ts b/src/common/Version.ts deleted file mode 100644 index 424e6d4..0000000 --- a/src/common/Version.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class Version { - public static parse(versionStr: string): number[] { - 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 version; - } - - public static stringify(version: number[]): string { - return `${version[0]}.${version[1]}.${version[2]}`; - } - - public static compareBySubminor(v1, v2): number { - return (v1[0] - v2[0] != 0) ? (v1[0] - v2[0]) : (v1[1] - v2[1] != 0) ? v1[1] - v2[1] : v1[2] - v2[2]; - } -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 3a08fde..3fbc69f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import * as child from 'child_process'; import * as ns from './services/NsCliService'; -import {ExtensionVersionInfo} from './services/ExtensionVersionInfo'; +import {ExtensionVersionService} from './services/ExtensionVersionService'; import {AnalyticsService} from './services/analytics/AnalyticsService'; import {ExtensionServer} from './services/ipc/ExtensionServer'; @@ -13,12 +13,12 @@ function performVersionsCheck(context: vscode.ExtensionContext) { } else { // Checks whether a new version of the extension is available - let extensionVersionPromise: Promise = null; + let extensionVersionPromise: Promise = null; // Check the cache for extension version information let extensionVersion: any = context.globalState.get('ExtensionVersionInfo'); if (extensionVersion) { - let extensionVersionInfo = new ExtensionVersionInfo(extensionVersion.latestVersionMetadata, extensionVersion.timestamp); + let extensionVersionInfo = new ExtensionVersionService(extensionVersion.latestVersionMetadata, extensionVersion.timestamp); if (extensionVersionInfo.getTimestamp() > Date.now() - 24 * 60 * 60 * 1000 /* Cache the version for a day */) { extensionVersionPromise = Promise.resolve(extensionVersionInfo); } @@ -26,7 +26,7 @@ function performVersionsCheck(context: vscode.ExtensionContext) { if (!extensionVersionPromise) { // Takes the slow path and checks for newer version in the VS Code Marketplace - extensionVersionPromise = ExtensionVersionInfo.createFromMarketplace(); + extensionVersionPromise = ExtensionVersionService.createFromMarketplace(); } extensionVersionPromise.then(extensionInfo => { if (extensionInfo) { diff --git a/src/services/NsCliService.ts b/src/services/NsCliService.ts index 9bac02a..61ae5a6 100644 --- a/src/services/NsCliService.ts +++ b/src/services/NsCliService.ts @@ -3,9 +3,9 @@ import * as fs from 'fs'; import {EventEmitter} from 'events'; import * as path from 'path'; import * as https from 'https'; -import {Version} from '../common/Version'; +import {Version} from './version'; import {Logger, Handlers, Tags} from '../services/Logger'; -import {ExtensionVersionInfo} from './ExtensionVersionInfo'; +import {ExtensionVersionService} from './ExtensionVersionService'; import {DebugProtocol} from 'vscode-debugprotocol'; export enum CliVersionState { @@ -15,11 +15,11 @@ export enum CliVersionState { } export class CliVersionInfo { - private static installedCliVersion: number[] = null; + private static installedCliVersion: Version = null; private _state: CliVersionState; - public static getInstalledCliVersion(): number[] { + public static getInstalledCliVersion(): Version { if (this.installedCliVersion === null) { // get the currently installed CLI version let getVersionCommand: string = new CommandBuilder().appendParam('--version').buildAsString(); // tns --version @@ -35,13 +35,13 @@ export class CliVersionInfo { } constructor() { - let installedCliVersion: number[] = CliVersionInfo.getInstalledCliVersion(); + let installedCliVersion: Version = CliVersionInfo.getInstalledCliVersion(); if (installedCliVersion === null) { this._state = CliVersionState.NotExisting; } else { - let minSupportedCliVersion = ExtensionVersionInfo.getMinSupportedNativeScriptVersion(); - this._state = Version.compareBySubminor(installedCliVersion, minSupportedCliVersion) < 0 ? CliVersionState.OlderThanSupported : CliVersionState.Compatible; + let minSupportedCliVersion = ExtensionVersionService.getMinSupportedNativeScriptVersion(); + this._state = installedCliVersion.compareBySubminorTo(minSupportedCliVersion) < 0 ? CliVersionState.OlderThanSupported : CliVersionState.Compatible; } } @@ -58,7 +58,7 @@ export class CliVersionInfo { case CliVersionState.NotExisting: return `NativeScript CLI not found, please run 'npm -g install nativescript' to install it.`; case CliVersionState.OlderThanSupported: - return `The existing NativeScript extension is compatible with NativeScript CLI v${Version.stringify(ExtensionVersionInfo.getMinSupportedNativeScriptVersion())} or greater. The currently installed NativeScript CLI is v${Version.stringify(CliVersionInfo.getInstalledCliVersion())}. You can update the NativeScript CLI by executing 'npm install -g nativescript'.`; + return `The existing NativeScript extension is compatible with NativeScript CLI v${ExtensionVersionService.getMinSupportedNativeScriptVersion()} or greater. The currently installed NativeScript CLI is v${CliVersionInfo.getInstalledCliVersion()}. You can update the NativeScript CLI by executing 'npm install -g nativescript'.`; default: return null; } diff --git a/src/services/analytics/AnalyticsService.ts b/src/services/analytics/AnalyticsService.ts index ecbfadf..5f83856 100644 --- a/src/services/analytics/AnalyticsService.ts +++ b/src/services/analytics/AnalyticsService.ts @@ -1,10 +1,10 @@ import * as os from 'os'; import * as vscode from 'vscode'; -import { Version } from '../../common/Version'; +import { Version } from '../version'; import { GUAService } from './GUAService'; import { TelerikAnalyticsService } from './TelerikAnalyticsService'; import { AnalyticsBaseInfo, OperatingSystem } from './AnalyticsBaseInfo'; -import { ExtensionVersionInfo } from '../ExtensionVersionInfo'; +import { ExtensionVersionService } from '../ExtensionVersionService'; import * as ns from '../NsCliService'; export class AnalyticsService { @@ -48,8 +48,8 @@ export class AnalyticsService { }; this._baseInfo = { - cliVersion: Version.stringify(ns.CliVersionInfo.getInstalledCliVersion()), - extensionVersion: Version.stringify(ExtensionVersionInfo.getExtensionVersion()), + cliVersion: ns.CliVersionInfo.getInstalledCliVersion().toString(), + extensionVersion: ExtensionVersionService.getExtensionVersion().toString(), operatingSystem: operatingSystem, userId: AnalyticsService.generateMachineId() }; diff --git a/src/services/ExtensionVersionInfo.ts b/src/services/extensionVersionService.ts similarity index 83% rename from src/services/ExtensionVersionInfo.ts rename to src/services/extensionVersionService.ts index 67f7c6c..029b9f7 100644 --- a/src/services/ExtensionVersionInfo.ts +++ b/src/services/extensionVersionService.ts @@ -1,9 +1,9 @@ -import {Version} from '../common/Version'; import * as https from 'https'; +import {Version} from './version'; -export class ExtensionVersionInfo { - private static extensionVersion: number[] = null; - private static minNativescriptCliVersion: number[] = null; +export class ExtensionVersionService { + private static extensionVersion: Version = null; + private static minNativescriptCliVersion: Version = null; private static extensionId: string = '8d837914-d8fa-45b5-965d-f76ebd6dbf5c'; private static marketplaceQueryResult: Promise = null; @@ -16,14 +16,14 @@ export class ExtensionVersionInfo { this.minNativescriptCliVersion = Version.parse(packageJson.minNativescriptCliVersion); } - public static getExtensionVersion(): number[] { + public static getExtensionVersion(): Version { if (this.extensionVersion === null) { this.initVersionsFromPackageJson(); } return this.extensionVersion; } - public static getMinSupportedNativeScriptVersion(): number[] { + public static getMinSupportedNativeScriptVersion(): Version { if (this.minNativescriptCliVersion === null) { this.initVersionsFromPackageJson(); } @@ -33,7 +33,7 @@ export class ExtensionVersionInfo { public static getMarketplaceExtensionData(): Promise { if (this.marketplaceQueryResult == null) { this.marketplaceQueryResult = new Promise((resolve, reject) => { - let postData: string = `{ filters: [{ criteria: [{ filterType: 4, value: "${ExtensionVersionInfo.extensionId}" }] }], flags: 262 }`; + let postData: string = `{ filters: [{ criteria: [{ filterType: 4, value: "${ExtensionVersionService.extensionId}" }] }], flags: 262 }`; let request = https.request({ hostname: 'marketplace.visualstudio.com', @@ -69,16 +69,16 @@ export class ExtensionVersionInfo { return this.marketplaceQueryResult; } - public static createFromMarketplace(): Promise { + public static createFromMarketplace(): Promise { return this.getMarketplaceExtensionData() .then(marketplaceData => { let latestVersion = null; try { - if (marketplaceData.results[0].extensions[0].extensionId == ExtensionVersionInfo.extensionId) { + if (marketplaceData.results[0].extensions[0].extensionId == ExtensionVersionService.extensionId) { latestVersion = marketplaceData.results[0].extensions[0].versions[0]; } } catch (e) { } - return new ExtensionVersionInfo(latestVersion); + return new ExtensionVersionService(latestVersion); }); } @@ -95,7 +95,7 @@ export class ExtensionVersionInfo { if (!this.getLatestVersionMeta()) { return true; } - return Version.compareBySubminor(ExtensionVersionInfo.getExtensionVersion(), Version.parse(this.getLatestVersionMeta().version)) >= 0; + return ExtensionVersionService.getExtensionVersion().compareBySubminorTo(Version.parse(this.getLatestVersionMeta().version)) >= 0; } public getTimestamp(): number { diff --git a/src/services/version.ts b/src/services/version.ts new file mode 100644 index 0000000..59ba47d --- /dev/null +++ b/src/services/version.ts @@ -0,0 +1,28 @@ +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]; + } +} From b67c459ad91296c697f60ea62bb79c2e651878ca Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Fri, 18 Nov 2016 18:46:33 +0200 Subject: [PATCH 5/9] Remove getAppRoot and getBrowserPath from utilities --- src/debug-adapter/adapter/pathTransformer.ts | 18 +++++------ .../sourceMaps/sourceMapTransformer.ts | 2 +- src/debug-adapter/utilities.ts | 32 ------------------- src/debug-adapter/webKitDebugAdapter.ts | 4 +-- 4 files changed, 12 insertions(+), 44 deletions(-) diff --git a/src/debug-adapter/adapter/pathTransformer.ts b/src/debug-adapter/adapter/pathTransformer.ts index 045d267..1b5313c 100644 --- a/src/debug-adapter/adapter/pathTransformer.ts +++ b/src/debug-adapter/adapter/pathTransformer.ts @@ -17,7 +17,7 @@ interface IPendingBreakpoint { * Converts a local path from Code to a path on the target. */ export class PathTransformer implements DebugProtocol.IDebugTransformer { - private _webRoot: string; + private _appRoot: string; private _platform: string; private _clientPathToWebkitUrl = new Map(); private _webkitUrlToClientPath = new Map(); @@ -25,13 +25,13 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { private inferedDeviceRoot :string = null; public launch(args: DebugProtocol.ILaunchRequestArgs): void { - this._webRoot = utils.getAppRoot(args); + this._appRoot = args.appRoot; this._platform = args.platform; this.inferedDeviceRoot = (this._platform === 'ios') ? 'file://' : this.inferedDeviceRoot; } public attach(args: DebugProtocol.IAttachRequestArgs): void { - this._webRoot = utils.getAppRoot(args); + this._appRoot = args.appRoot; this._platform = args.platform; } @@ -56,7 +56,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { resolve(); } else if (this.inferedDeviceRoot) { - let inferedUrl = url.replace(this._webRoot, this.inferedDeviceRoot).replace(/\\/g, "/"); + let inferedUrl = url.replace(this._appRoot, this.inferedDeviceRoot).replace(/\\/g, "/"); //change device path if {N} core module or {N} module if (inferedUrl.indexOf("/node_modules/tns-core-modules/") != -1) @@ -96,7 +96,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { const webkitUrl: string = event.body.scriptUrl; if (!this.inferedDeviceRoot && this._platform === "android") { - this.inferedDeviceRoot = utils.inferDeviceRoot(this._webRoot, this._platform, webkitUrl); + this.inferedDeviceRoot = utils.inferDeviceRoot(this._appRoot, this._platform, webkitUrl); Logger.log("\n\n\n ***Inferred device root: " + this.inferedDeviceRoot + "\n\n\n"); if (this.inferedDeviceRoot.indexOf("/data/user/0/") != -1) @@ -105,12 +105,12 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { } } - const clientPath = utils.webkitUrlToClientPath(this._webRoot, this._platform, webkitUrl); + const clientPath = utils.webkitUrlToClientPath(this._appRoot, this._platform, webkitUrl); if (!clientPath) { - Logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._webRoot}`); + Logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._appRoot}`); } else { - Logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._webRoot}`); + Logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._appRoot}`); this._clientPathToWebkitUrl.set(clientPath, webkitUrl); this._webkitUrlToClientPath.set(webkitUrl, clientPath); @@ -132,7 +132,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { if (frame.source && frame.source.path) { const clientPath = this._webkitUrlToClientPath.has(frame.source.path) ? this._webkitUrlToClientPath.get(frame.source.path) : - utils.webkitUrlToClientPath(this._webRoot, this._platform, frame.source.path); + utils.webkitUrlToClientPath(this._appRoot, this._platform, frame.source.path); // Incoming stackFrames have sourceReference and path set. If the path was resolved to a file in the workspace, // clear the sourceReference since it's not needed. if (clientPath) { diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts index 8f3b012..a5e49f7 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts @@ -38,7 +38,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { private init(args: DebugProtocol.ILaunchRequestArgs | DebugProtocol.IAttachRequestArgs): void { if (args.sourceMaps) { - this._webRoot = utils.getAppRoot(args); + this._webRoot = args.appRoot; this._sourceMaps = new SourceMaps(this._webRoot); this._requestSeqToSetBreakpointsArgs = new Map(); this._allRuntimeScriptPaths = new Set(); diff --git a/src/debug-adapter/utilities.ts b/src/debug-adapter/utilities.ts index f20371d..aaa8f4c 100644 --- a/src/debug-adapter/utilities.ts +++ b/src/debug-adapter/utilities.ts @@ -9,31 +9,6 @@ import * as os from 'os'; import * as fs from 'fs'; import * as url from 'url'; import * as path from 'path'; -import {DebugProtocol} from 'vscode-debugprotocol'; - -const DEFAULT_CHROME_PATH = { - OSX: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', - WIN: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', - WINx86: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', - LINUX: '/usr/bin/google-chrome' -}; - -export function getBrowserPath(): string { - const platform = getPlatform(); - if (platform === Platform.OSX) { - return existsSync(DEFAULT_CHROME_PATH.OSX) ? DEFAULT_CHROME_PATH.OSX : null; - } else if (platform === Platform.Windows) { - if (existsSync(DEFAULT_CHROME_PATH.WINx86)) { - return DEFAULT_CHROME_PATH.WINx86; - } else if (existsSync(DEFAULT_CHROME_PATH.WIN)) { - return DEFAULT_CHROME_PATH.WIN; - } else { - return null; - } - } else { - return existsSync(DEFAULT_CHROME_PATH.LINUX) ? DEFAULT_CHROME_PATH.LINUX : null; - } -} export const enum Platform { Windows, OSX, Linux @@ -364,13 +339,6 @@ export function errP(msg: any): Promise { return Promise.reject(e); } -/** - * Calculates the appRoot from a launch/attach request. The appRoot is the root directory of the NativeScript app. - */ -export function getAppRoot(args: DebugProtocol.ILaunchRequestArgs | DebugProtocol.IAttachRequestArgs): string { - return (args.appRoot && path.isAbsolute(args.appRoot)) ? args.appRoot : ''; -} - /** * Helper function to GET the contents of a url */ diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index d3b8615..7276b32 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -122,11 +122,11 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } private _attach(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs) { - ExtensionClient.setAppRoot(utils.getAppRoot(args)); + ExtensionClient.setAppRoot(args.appRoot); let analyticsRequest = (args.request == "launch" && !(args as DebugProtocol.ILaunchRequestArgs).rebuild) ? "sync" : args.request; ExtensionClient.getInstance().analyticsLaunchDebugger({ request: analyticsRequest, platform: args.platform }); this.configureLoggingForRequest(args.request, args); - this.appRoot = utils.getAppRoot(args); + this.appRoot = args.appRoot; this.platform = args.platform; return ((args.platform == 'ios') ? this._attachIos(args) : this._attachAndroid(args)) From ee426f741a395ad6ad696a89b7a3837771413e2e Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Fri, 25 Nov 2016 18:41:33 +0200 Subject: [PATCH 6/9] Refactor debug configuration arguments --- .../debugProtocolExtensions.d.ts | 27 ++++++------ .../sourceMaps/sourceMapTransformer.ts | 2 +- src/debug-adapter/debugConfiguration.ts | 41 +++++++++++++++++++ src/debug-adapter/webKitDebugAdapter.ts | 37 ++++++++++------- src/services/NsCliService.ts | 8 ++-- 5 files changed, 80 insertions(+), 35 deletions(-) create mode 100644 src/debug-adapter/debugConfiguration.ts diff --git a/src/custom-typings/debugProtocolExtensions.d.ts b/src/custom-typings/debugProtocolExtensions.d.ts index b318a29..ee976f8 100644 --- a/src/custom-typings/debugProtocolExtensions.d.ts +++ b/src/custom-typings/debugProtocolExtensions.d.ts @@ -4,30 +4,27 @@ declare module 'vscode-debugprotocol' { namespace DebugProtocol { type RequestArguments = LaunchRequestArguments | AttachRequestArguments; + type RequestType = "launch" | "attach"; + type PlatformType = "android" | "ios"; - interface ILaunchRequestArgs extends DebugProtocol.LaunchRequestArguments { - platform: string; - appRoot?: string; - stopOnEntry?: boolean; + interface IRequestArgs { + request: RequestType; + platform: PlatformType; + appRoot: string; sourceMaps?: boolean; diagnosticLogging?: boolean; - request: string; tnsArgs?: string[]; tnsOutput?: string; + nativescriptCliPath?: string; + } + + interface ILaunchRequestArgs extends DebugProtocol.LaunchRequestArguments, IRequestArgs { + stopOnEntry?: boolean; rebuild?: boolean; syncAllFiles?: boolean; - nativescriptCliPath?: string; } - interface IAttachRequestArgs extends DebugProtocol.AttachRequestArguments { - platform: string; - appRoot?: string; - sourceMaps?: boolean; - diagnosticLogging?: boolean; - request: string; - tnsArgs?: string[]; - tnsOutput?: string; - nativescriptCliPath?: string; + interface IAttachRequestArgs extends DebugProtocol.AttachRequestArguments, IRequestArgs { } interface ISetBreakpointsArgs extends DebugProtocol.SetBreakpointsArguments { diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts index a5e49f7..ef5659d 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts @@ -36,7 +36,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { this.init(args); } - private init(args: DebugProtocol.ILaunchRequestArgs | DebugProtocol.IAttachRequestArgs): void { + private init(args: DebugProtocol.IRequestArgs): void { if (args.sourceMaps) { this._webRoot = args.appRoot; this._sourceMaps = new SourceMaps(this._webRoot); diff --git a/src/debug-adapter/debugConfiguration.ts b/src/debug-adapter/debugConfiguration.ts new file mode 100644 index 0000000..82d5ebd --- /dev/null +++ b/src/debug-adapter/debugConfiguration.ts @@ -0,0 +1,41 @@ +import {DebugProtocol} from 'vscode-debugprotocol'; + +export class DebugConfiguration { + private _requestArgs: DebugProtocol.IRequestArgs; + + constructor(requestArgs: DebugProtocol.IRequestArgs) { + this._requestArgs = requestArgs; + } + + public get isLaunch(): boolean { + return this.args.request === "launch" && (this._requestArgs).rebuild; + } + + public get isSync(): boolean { + return this.args.request == "launch" && !(this._requestArgs).rebuild; + } + + public get isAttach(): boolean { + return this.args.request === "attach"; + } + + public get isAndroid(): boolean { + return this.args.platform == "android"; + } + + public get isIos(): boolean { + return this.args.platform == "ios"; + } + + public get args(): DebugProtocol.IRequestArgs { + return this._requestArgs; + } + + public get launchArgs(): DebugProtocol.ILaunchRequestArgs { + return this.isLaunch ? this.args : null; + } + + public get attachArgs(): DebugProtocol.IAttachRequestArgs { + return this.isAttach ? this.args : null; + } +} diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index 7276b32..3ed460f 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -17,6 +17,7 @@ import * as ns from '../services/NsCliService'; import {AnalyticsService} from '../services/analytics/AnalyticsService'; import {ExtensionClient} from '../services/ipc/ExtensionClient'; import {Logger, LoggerHandler, Handlers, Tags} from '../services/Logger'; +import {DebugConfiguration} from './debugConfiguration'; interface IScopeVarHandle { objectId: string; @@ -42,6 +43,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { private platform: string; private _lastOutputEvent: OutputEvent; private _loggerFrontendHandler: LoggerHandler = args => this.fireEvent(new OutputEvent(` ›${args.message}\n`, args.type.toString())); + private _debugConfig: DebugConfiguration; public constructor() { this._variableHandles = new Handles(); @@ -100,14 +102,15 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } public launch(args: DebugProtocol.ILaunchRequestArgs): Promise { - return this._attach(args); + return this.processRequest(args); } public attach(args: DebugProtocol.IAttachRequestArgs): Promise { - return this._attach(args); + return this.processRequest(args); } - private configureLoggingForRequest(name: string, args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): void { + private configureLoggingForRequest(): void { + let args = this._debugConfig.args; if (args.diagnosticLogging) { // The logger frontend handler is initially configured to handle messages with LoggerTagFrontendMessage tag only. // We remove the handler and add it again for all messages. @@ -118,18 +121,19 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { Logger.addHandler(Handlers.createStreamHandler(fs.createWriteStream(args.tnsOutput))); } Logger.log(`initialize(${JSON.stringify(this._initArgs) })`); - Logger.log(`${name}(${JSON.stringify(args) })`); + Logger.log(`${this._debugConfig.args.request}(${JSON.stringify(this._debugConfig.isAttach ? this._debugConfig.attachArgs : this._debugConfig.launchArgs) })`); } - private _attach(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs) { + private processRequest(args: DebugProtocol.IRequestArgs) { + this._debugConfig = new DebugConfiguration(args); ExtensionClient.setAppRoot(args.appRoot); - let analyticsRequest = (args.request == "launch" && !(args as DebugProtocol.ILaunchRequestArgs).rebuild) ? "sync" : args.request; + let analyticsRequest = this._debugConfig.isSync ? "sync" : args.request; ExtensionClient.getInstance().analyticsLaunchDebugger({ request: analyticsRequest, platform: args.platform }); - this.configureLoggingForRequest(args.request, args); + this.configureLoggingForRequest(); this.appRoot = args.appRoot; this.platform = args.platform; - return ((args.platform == 'ios') ? this._attachIos(args) : this._attachAndroid(args)) + return ((args.platform == 'ios') ? this._attachIos() : this._attachAndroid()) .then(() => { this.fireEvent(new InitializedEvent()); }, @@ -140,18 +144,20 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { }); } - private _attachIos(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): Promise { + private _attachIos(): Promise { + let args = this._debugConfig.args; let iosProject : ns.IosProject = new ns.IosProject(this.appRoot, args.tnsOutput); return iosProject.debug(args) .then((socketFilePath) => { let iosConnection: IosConnection = new IosConnection(); - this.setConnection(iosConnection, args); + this.setConnection(iosConnection); return iosConnection.attach(socketFilePath); }); } - private _attachAndroid(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): Promise { + private _attachAndroid(): Promise { + let args = this._debugConfig.args; let androidProject: ns.AndroidProject = new ns.AndroidProject(this.appRoot, args.tnsOutput); let thisAdapter: WebKitDebugAdapter = this; @@ -166,7 +172,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { port = debugPort; if (!thisAdapter._webKitConnection) { androidConnection = new AndroidConnection(); - this.setConnection(androidConnection, args); + this.setConnection(androidConnection); } }).then(() => { Logger.log("Attaching to debug application"); @@ -175,7 +181,8 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { }); } - private setConnection(connection: INSDebugConnection, args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs) : INSDebugConnection { + private setConnection(connection: INSDebugConnection) : INSDebugConnection { + let args = this._debugConfig.args; connection.on('Debugger.paused', params => this.onDebuggerPaused(params)); connection.on('Debugger.resumed', () => this.onDebuggerResumed()); connection.on('Debugger.scriptParsed', params => this.onScriptParsed(params)); @@ -186,12 +193,12 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { connection.on('Inspector.detached', () => this.terminateSession()); connection.on('close', () => this.terminateSession()); connection.on('error', () => this.terminateSession()); - connection.on('connect', () => this.onConnected(args)) + connection.on('connect', () => this.onConnected()) this._webKitConnection = connection; return connection; } - private onConnected(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): void { + private onConnected(): void { Logger.log("Debugger connected"); } diff --git a/src/services/NsCliService.ts b/src/services/NsCliService.ts index 61ae5a6..b3e9301 100644 --- a/src/services/NsCliService.ts +++ b/src/services/NsCliService.ts @@ -87,7 +87,7 @@ export abstract class NSProject extends EventEmitter { public abstract run(): Promise; - public abstract debug(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): Promise; + public abstract debug(args: DebugProtocol.IRequestArgs): Promise; protected spawnProcess(commandPath: string, commandArgs: string[], tnsOutput?: string): ChildProcess { let options = { cwd: this.getProjectPath(), shell: true }; @@ -123,7 +123,7 @@ export class IosProject extends NSProject { return Promise.resolve(child); } - public debug(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): Promise { + public debug(args: DebugProtocol.IRequestArgs): Promise { if (!this.isOSX()) { return Promise.reject('iOS platform is supported only on OS X.'); } @@ -211,7 +211,7 @@ export class AndroidProject extends NSProject { return Promise.resolve(child); } - public debug(params: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): Promise { + public debug(params: DebugProtocol.IRequestArgs): Promise { if (params.request === "attach") { return Promise.resolve(); } @@ -266,7 +266,7 @@ export class AndroidProject extends NSProject { } } - public getDebugPort(args: DebugProtocol.IAttachRequestArgs | DebugProtocol.ILaunchRequestArgs): Promise { + public getDebugPort(args: DebugProtocol.IRequestArgs): Promise { //TODO: Call CLI to get the debug port //return Promise.resolve(40001); From dba405269ccb563f6d1c98f93101079d0ec72e53 Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Fri, 25 Nov 2016 20:15:38 +0200 Subject: [PATCH 7/9] Services refactoring --- src/debug-adapter/adapter/adapterProxy.ts | 4 +- src/debug-adapter/adapter/pathTransformer.ts | 18 +-- .../adapter/sourceMaps/pathUtilities.ts | 8 +- .../sourceMaps/sourceMapTransformer.ts | 20 +-- .../adapter/sourceMaps/sourceMaps.ts | 18 +-- .../connection/androidConnection.ts | 9 +- src/debug-adapter/connection/iosConnection.ts | 11 +- src/debug-adapter/webKitDebugAdapter.ts | 39 +++-- src/debug-adapter/webKitDebugSession.ts | 15 +- src/main.ts | 52 ++----- src/services/Logger.ts | 31 ++-- src/services/NsCliService.ts | 27 ++-- src/services/analytics/AnalyticsService.ts | 13 +- src/services/extensionVersionService.ts | 143 ++++++++---------- src/services/ipc/ExtensionClient.ts | 26 +--- src/services/ipc/ExtensionServer.ts | 15 +- src/services/services/debugAdapterServices.ts | 20 +++ .../services/extensionHostServices.ts | 35 +++++ src/services/services/services.ts | 10 ++ 19 files changed, 248 insertions(+), 266 deletions(-) create mode 100644 src/services/services/debugAdapterServices.ts create mode 100644 src/services/services/extensionHostServices.ts create mode 100644 src/services/services/services.ts diff --git a/src/debug-adapter/adapter/adapterProxy.ts b/src/debug-adapter/adapter/adapterProxy.ts index a0d4653..66d113a 100644 --- a/src/debug-adapter/adapter/adapterProxy.ts +++ b/src/debug-adapter/adapter/adapterProxy.ts @@ -3,7 +3,7 @@ *--------------------------------------------------------*/ import * as utils from '../utilities'; -import {Logger} from '../../services/Logger'; +import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; export type EventHandler = (event: DebugProtocol.Event) => void; @@ -78,7 +78,7 @@ export class AdapterProxy { this._eventHandler(event); } } catch (e) { - Logger.error('Error handling adapter event: ' + (e ? e.stack : '')); + Services.logger.error('Error handling adapter event: ' + (e ? e.stack : '')); } } } diff --git a/src/debug-adapter/adapter/pathTransformer.ts b/src/debug-adapter/adapter/pathTransformer.ts index 1b5313c..f56afb6 100644 --- a/src/debug-adapter/adapter/pathTransformer.ts +++ b/src/debug-adapter/adapter/pathTransformer.ts @@ -3,7 +3,7 @@ *--------------------------------------------------------*/ import * as utils from '../utilities'; -import {Logger} from '../../services/Logger'; +import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; import * as path from 'path'; @@ -44,7 +44,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { if (utils.isURL(args.source.path)) { // already a url, use as-is - Logger.log(`Paths.setBP: ${args.source.path} is already a URL`); + Services.logger.log(`Paths.setBP: ${args.source.path} is already a URL`); resolve(); return; } @@ -52,7 +52,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { const url = utils.canonicalizeUrl(args.source.path); if (this._clientPathToWebkitUrl.has(url)) { args.source.path = this._clientPathToWebkitUrl.get(url); - Logger.log(`Paths.setBP: Resolved ${url} to ${args.source.path}`); + Services.logger.log(`Paths.setBP: Resolved ${url} to ${args.source.path}`); resolve(); } else if (this.inferedDeviceRoot) { @@ -72,11 +72,11 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { inferedUrl = inferedUrl.replace(`.${this._platform}.`, '.'); args.source.path = inferedUrl; - Logger.log(`Paths.setBP: Resolved (by infering) ${url} to ${args.source.path}`); + Services.logger.log(`Paths.setBP: Resolved (by infering) ${url} to ${args.source.path}`); resolve(); } else { - Logger.log(`Paths.setBP: No target url cached for client path: ${url}, waiting for target script to be loaded.`); + Services.logger.log(`Paths.setBP: No target url cached for client path: ${url}, waiting for target script to be loaded.`); args.source.path = url; this._pendingBreakpointsByPath.set(args.source.path, { resolve, reject, args }); } @@ -97,7 +97,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { if (!this.inferedDeviceRoot && this._platform === "android") { this.inferedDeviceRoot = utils.inferDeviceRoot(this._appRoot, this._platform, webkitUrl); - Logger.log("\n\n\n ***Inferred device root: " + this.inferedDeviceRoot + "\n\n\n"); + Services.logger.log("\n\n\n ***Inferred device root: " + this.inferedDeviceRoot + "\n\n\n"); if (this.inferedDeviceRoot.indexOf("/data/user/0/") != -1) { @@ -108,9 +108,9 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { const clientPath = utils.webkitUrlToClientPath(this._appRoot, this._platform, webkitUrl); if (!clientPath) { - Logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._appRoot}`); + Services.logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._appRoot}`); } else { - Logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._appRoot}`); + Services.logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._appRoot}`); this._clientPathToWebkitUrl.set(clientPath, webkitUrl); this._webkitUrlToClientPath.set(webkitUrl, clientPath); @@ -118,7 +118,7 @@ export class PathTransformer implements DebugProtocol.IDebugTransformer { } if (this._pendingBreakpointsByPath.has(event.body.scriptUrl)) { - Logger.log(`Paths.scriptParsed: Resolving pending breakpoints for ${event.body.scriptUrl}`); + Services.logger.log(`Paths.scriptParsed: Resolving pending breakpoints for ${event.body.scriptUrl}`); const pendingBreakpoint = this._pendingBreakpointsByPath.get(event.body.scriptUrl); this._pendingBreakpointsByPath.delete(event.body.scriptUrl); this.setBreakpoints(pendingBreakpoint.args).then(pendingBreakpoint.resolve, pendingBreakpoint.reject); diff --git a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts index ed007ea..e15b9ec 100644 --- a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts +++ b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts @@ -6,7 +6,7 @@ import * as Path from 'path'; import * as URL from 'url'; -import {Logger} from '../../../services/Logger'; +import {DebugAdapterServices as Services} from '../../../services/services/debugAdapterServices'; import * as utils from '../../utilities'; export function getPathRoot(p: string) { @@ -83,16 +83,16 @@ export function getAbsSourceRoot(sourceRoot: string, webRoot: string, generatedP } } - Logger.log(`SourceMap: resolved sourceRoot ${sourceRoot} -> ${absSourceRoot}`); + Services.logger.log(`SourceMap: resolved sourceRoot ${sourceRoot} -> ${absSourceRoot}`); } else { if (Path.isAbsolute(generatedPath)) { absSourceRoot = Path.dirname(generatedPath); - Logger.log(`SourceMap: no sourceRoot specified, using script dirname: ${absSourceRoot}`); + Services.logger.log(`SourceMap: no sourceRoot specified, using script dirname: ${absSourceRoot}`); } else { // runtime script is not on disk, resolve the sourceRoot location on disk const scriptPathDirname = Path.dirname(URL.parse(generatedPath).pathname); absSourceRoot = Path.join(webRoot, scriptPathDirname); - Logger.log(`SourceMap: no sourceRoot specified, using webRoot + script path dirname: ${absSourceRoot}`); + Services.logger.log(`SourceMap: no sourceRoot specified, using webRoot + script path dirname: ${absSourceRoot}`); } } diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts index ef5659d..014c95b 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import * as fs from 'fs'; -import {Logger} from '../../../services/Logger'; +import {DebugAdapterServices as Services} from '../../../services/services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; import {ISourceMaps, SourceMaps} from './sourceMaps'; import * as utils from '../../utilities'; @@ -60,7 +60,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { const argsPath = args.source.path; const mappedPath = this._sourceMaps.MapPathFromSource(argsPath); if (mappedPath) { - Logger.log(`SourceMaps.setBP: Mapped ${argsPath} to ${mappedPath}`); + Services.logger.log(`SourceMaps.setBP: Mapped ${argsPath} to ${mappedPath}`); args.authoredPath = argsPath; args.source.path = mappedPath; @@ -69,11 +69,11 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { const mappedLines = args.lines.map((line, i) => { const mapped = this._sourceMaps.MapFromSource(argsPath, line, /*column=*/0); if (mapped) { - Logger.log(`SourceMaps.setBP: Mapped ${argsPath}:${line}:0 to ${mappedPath}:${mapped.line}:${mapped.column}`); + Services.logger.log(`SourceMaps.setBP: Mapped ${argsPath}:${line}:0 to ${mappedPath}:${mapped.line}:${mapped.column}`); mappedCols[i] = mapped.column; return mapped.line; } else { - Logger.log(`SourceMaps.setBP: Mapped ${argsPath} but not line ${line}, column 0`); + Services.logger.log(`SourceMaps.setBP: Mapped ${argsPath} but not line ${line}, column 0`); mappedCols[i] = 0; return line; } @@ -101,10 +101,10 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { }); } else if (this._allRuntimeScriptPaths.has(argsPath)) { // It's a generated file which is loaded - Logger.log(`SourceMaps.setBP: SourceMaps are enabled but ${argsPath} is a runtime script`); + Services.logger.log(`SourceMaps.setBP: SourceMaps are enabled but ${argsPath} is a runtime script`); } else { // Source (or generated) file which is not loaded, need to wait - Logger.log(`SourceMaps.setBP: ${argsPath} can't be resolved to a loaded script.`); + Services.logger.log(`SourceMaps.setBP: ${argsPath} can't be resolved to a loaded script.`); this._pendingBreakpointsByPath.set(argsPath, { resolve, reject, args, requestSeq }); return; } @@ -132,10 +132,10 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { response.breakpoints.forEach((bp, i) => { const mapped = this._sourceMaps.MapToSource(args.source.path, args.lines[i], args.cols[i]); if (mapped) { - Logger.log(`SourceMaps.setBP: Mapped ${args.source.path}:${bp.line}:${bp.column} to ${mapped.path}:${mapped.line}`); + Services.logger.log(`SourceMaps.setBP: Mapped ${args.source.path}:${bp.line}:${bp.column} to ${mapped.path}:${mapped.line}`); bp.line = mapped.line; } else { - Logger.log(`SourceMaps.setBP: Can't map ${args.source.path}:${bp.line}:${bp.column}, keeping the line number as-is.`); + Services.logger.log(`SourceMaps.setBP: Can't map ${args.source.path}:${bp.line}:${bp.column}, keeping the line number as-is.`); } this._requestSeqToSetBreakpointsArgs.delete(requestSeq); @@ -201,7 +201,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { this._sourceMaps.ProcessNewSourceMap(event.body.scriptUrl, sourceMapUrlValue).then(() => { const sources = this._sourceMaps.AllMappedSources(event.body.scriptUrl); if (sources) { - Logger.log(`SourceMaps.scriptParsed: ${event.body.scriptUrl} was just loaded and has mapped sources: ${JSON.stringify(sources)}`); + Services.logger.log(`SourceMaps.scriptParsed: ${event.body.scriptUrl} was just loaded and has mapped sources: ${JSON.stringify(sources)}`); sources.forEach(this.resolvePendingBreakpoints, this); } }); @@ -241,7 +241,7 @@ export class SourceMapTransformer implements DebugProtocol.IDebugTransformer { private resolvePendingBreakpoints(sourcePath: string): void { // If there's a setBreakpoints request waiting on this script, go through setBreakpoints again if (this._pendingBreakpointsByPath.has(sourcePath)) { - Logger.log(`SourceMaps.scriptParsed: Resolving pending breakpoints for ${sourcePath}`); + Services.logger.log(`SourceMaps.scriptParsed: Resolving pending breakpoints for ${sourcePath}`); const pendingBreakpoint = this._pendingBreakpointsByPath.get(sourcePath); this._pendingBreakpointsByPath.delete(sourcePath); diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts index ee4487d..f7f1b62 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts @@ -10,7 +10,7 @@ import * as FS from 'fs'; import {SourceMapConsumer} from 'source-map'; import * as PathUtils from './pathUtilities'; import * as utils from '../../utilities'; -import {Logger} from '../../../services/Logger'; +import {DebugAdapterServices as Services} from '../../../services/services/debugAdapterServices'; export interface MappingResult { @@ -191,7 +191,7 @@ export class SourceMaps implements ISourceMaps { const matches = SourceMaps.SOURCE_MAPPING_MATCHER.exec(line); if (matches && matches.length === 2) { const uri = matches[1].trim(); - Logger.log(`_findSourceMapUrlInFile: source map url at end of generated file '${generatedFilePath}''`); + Services.logger.log(`_findSourceMapUrlInFile: source map url at end of generated file '${generatedFilePath}''`); return uri; } } @@ -329,7 +329,7 @@ export class SourceMaps implements ISourceMaps { } if (!FS.existsSync(generatedFilePath)) { - Logger.log("findGeneratedToSourceMappingSync: can't find the sourceMapping for file: " + generatedFilePath); + Services.logger.log("findGeneratedToSourceMappingSync: can't find the sourceMapping for file: " + generatedFilePath); return null; } @@ -379,7 +379,7 @@ export class SourceMaps implements ISourceMaps { } } catch (e) { - Logger.log(`can't parse inlince sourcemap. exception while processing data url (${e.stack})`); + Services.logger.log(`can't parse inlince sourcemap. exception while processing data url (${e.stack})`); } } @@ -390,14 +390,14 @@ export class SourceMaps implements ISourceMaps { let contentsP: Promise; if (utils.isURL(mapPath)) { contentsP = utils.getURL(mapPath).catch(e => { - Logger.log(`SourceMaps.createSourceMap: Could not download map from ${mapPath}`); + Services.logger.log(`SourceMaps.createSourceMap: Could not download map from ${mapPath}`); return null; }); } else { contentsP = new Promise((resolve, reject) => { FS.readFile(mapPath, (err, data) => { if (err) { - Logger.log(`SourceMaps.createSourceMap: Could not read map from ${mapPath}`); + Services.logger.log(`SourceMaps.createSourceMap: Could not read map from ${mapPath}`); resolve(null); } else { resolve(data); @@ -412,7 +412,7 @@ export class SourceMaps implements ISourceMaps { // Throws for invalid contents JSON return new SourceMap(pathToGenerated, contents, this._webRoot); } catch (e) { - Logger.log(`SourceMaps.createSourceMap: exception while processing sourcemap: ${e.stack}`); + Services.logger.log(`SourceMaps.createSourceMap: exception while processing sourcemap: ${e.stack}`); return null; } } else { @@ -427,7 +427,7 @@ export class SourceMaps implements ISourceMaps { // Throws for invalid contents JSON return new SourceMap(pathToGenerated, contents, this._webRoot); } catch (e) { - Logger.log(`SourceMaps.createSourceMap: exception while processing sourcemap: ${e.stack}`); + Services.logger.log(`SourceMaps.createSourceMap: exception while processing sourcemap: ${e.stack}`); return null; } } @@ -452,7 +452,7 @@ class SourceMap { * webRoot - an absolute path */ public constructor(generatedPath: string, json: string, webRoot: string) { - Logger.log(`SourceMap: creating SM for ${generatedPath}`) + Services.logger.log(`SourceMap: creating SM for ${generatedPath}`) this._generatedPath = generatedPath; this._webRoot = webRoot; diff --git a/src/debug-adapter/connection/androidConnection.ts b/src/debug-adapter/connection/androidConnection.ts index 531a748..29bc88c 100644 --- a/src/debug-adapter/connection/androidConnection.ts +++ b/src/debug-adapter/connection/androidConnection.ts @@ -1,8 +1,7 @@ import * as http from 'http'; import {EventEmitter} from 'events'; -import {Logger} from '../../services/Logger'; +import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; import * as Net from 'net'; -import * as ns from '../../services/NsCliService'; import { INSDebugConnection } from './INSDebugConnection'; @@ -137,7 +136,7 @@ class ResReqNetSocket extends EventEmitter { this.debugBuffer = b.toString('utf8', this.msg.contentLength, b.length); if (this.msg.body.length > 0) { obj = JSON.parse(this.msg.body); - Logger.log('From target(' + (obj.type ? obj.type : '') + '): ' + this.msg.body); + Services.logger.log('From target(' + (obj.type ? obj.type : '') + '): ' + this.msg.body); if (typeof obj.running === 'boolean') { this.isRunning = obj.running; } @@ -182,7 +181,7 @@ class ResReqNetSocket extends EventEmitter { public send(data) { if (this.connected) { - Logger.log('To target: ' + data); + Services.logger.log('To target: ' + data); this.conn.write('Content-Length: ' + data.length + '\r\n\r\n' + data); this.hasNewDataMessage = true; if (!this.isMessageFlushLoopStarted) { @@ -438,7 +437,7 @@ export class AndroidConnection implements INSDebugConnection { } public attach(port: number, url?: string): Promise { - Logger.log('Attempting to attach on port ' + port); + Services.logger.log('Attempting to attach on port ' + port); return this._attach(port, url); //.then(() => this.sendMessage('Console.enable')) } diff --git a/src/debug-adapter/connection/iosConnection.ts b/src/debug-adapter/connection/iosConnection.ts index 008eddd..0672236 100644 --- a/src/debug-adapter/connection/iosConnection.ts +++ b/src/debug-adapter/connection/iosConnection.ts @@ -7,8 +7,7 @@ import * as stream from 'stream'; import {EventEmitter} from 'events'; import {INSDebugConnection} from './INSDebugConnection'; import * as utils from '../utilities'; -import {Logger} from '../../services/Logger'; -import * as ns from '../../services/NsCliService'; +import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; interface IMessageWithId { id: number; @@ -74,7 +73,7 @@ class ResReqTcpSocket extends EventEmitter { }); unixSocket.on('close', () => { - Logger.log('Unix socket closed'); + Services.logger.log('Unix socket closed'); this.emit('close'); }); @@ -83,7 +82,7 @@ class ResReqTcpSocket extends EventEmitter { packetsStream.on('data', (buffer: Buffer) => { let packet = buffer.toString('utf16le'); - Logger.log('From target: ' + packet); + Services.logger.log('From target: ' + packet); this.onMessage(JSON.parse(packet)); }); } catch (e) { @@ -111,7 +110,7 @@ class ResReqTcpSocket extends EventEmitter { this._pendingRequests.set(message.id, resolve); this._unixSocketAttached.then(socket => { const msgStr = JSON.stringify(message); - Logger.log('To target: ' + msgStr); + Services.logger.log('To target: ' + msgStr); let encoding = "utf16le"; let length = Buffer.byteLength(msgStr, encoding); let payload = new Buffer(length + 4); @@ -156,7 +155,7 @@ export class IosConnection implements INSDebugConnection { * Attach the underlying Unix socket */ public attach(filePath: string): Promise { - Logger.log('Attempting to attach to path ' + filePath); + Services.logger.log('Attempting to attach to path ' + filePath); return utils.retryAsync(() => this._attach(filePath), 6000) .then(() => { Promise.all([ diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index 3ed460f..d99f7f3 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -14,9 +14,8 @@ import {AndroidConnection} from './connection/androidConnection'; import * as utils from './utilities'; import {formatConsoleMessage} from './consoleHelper'; import * as ns from '../services/NsCliService'; -import {AnalyticsService} from '../services/analytics/AnalyticsService'; -import {ExtensionClient} from '../services/ipc/ExtensionClient'; -import {Logger, LoggerHandler, Handlers, Tags} from '../services/Logger'; +import {DebugAdapterServices as Services} from '../services/services/debugAdapterServices'; +import {LoggerHandler, Handlers, Tags} from '../services/Logger'; import {DebugConfiguration} from './debugConfiguration'; interface IScopeVarHandle { @@ -49,10 +48,10 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { this._variableHandles = new Handles(); // Messages tagged with a special tag are sent to the frontend through the debugging protocol - Logger.addHandler(this._loggerFrontendHandler, [Tags.FrontendMessage]); - Logger.log(`OS: ${os.platform()} ${os.arch()}`); - Logger.log('Node version: ' + process.version); - Logger.log('Adapter version: ' + require('../../package.json').version); + Services.logger.addHandler(this._loggerFrontendHandler, [Tags.FrontendMessage]); + Services.logger.log(`OS: ${os.platform()} ${os.arch()}`); + Services.logger.log('Node version: ' + process.version); + Services.logger.log('Adapter version: ' + require('../../package.json').version); this.clearEverything(); } @@ -114,21 +113,21 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { if (args.diagnosticLogging) { // The logger frontend handler is initially configured to handle messages with LoggerTagFrontendMessage tag only. // We remove the handler and add it again for all messages. - Logger.removeHandler(this._loggerFrontendHandler); - Logger.addHandler(this._loggerFrontendHandler); + Services.logger.removeHandler(this._loggerFrontendHandler); + Services.logger.addHandler(this._loggerFrontendHandler); } if (args.tnsOutput) { - Logger.addHandler(Handlers.createStreamHandler(fs.createWriteStream(args.tnsOutput))); + Services.logger.addHandler(Handlers.createStreamHandler(fs.createWriteStream(args.tnsOutput))); } - Logger.log(`initialize(${JSON.stringify(this._initArgs) })`); - Logger.log(`${this._debugConfig.args.request}(${JSON.stringify(this._debugConfig.isAttach ? this._debugConfig.attachArgs : this._debugConfig.launchArgs) })`); + Services.logger.log(`initialize(${JSON.stringify(this._initArgs) })`); + Services.logger.log(`${this._debugConfig.args.request}(${JSON.stringify(this._debugConfig.isAttach ? this._debugConfig.attachArgs : this._debugConfig.launchArgs) })`); } private processRequest(args: DebugProtocol.IRequestArgs) { this._debugConfig = new DebugConfiguration(args); - ExtensionClient.setAppRoot(args.appRoot); + Services.appRoot = args.appRoot; let analyticsRequest = this._debugConfig.isSync ? "sync" : args.request; - ExtensionClient.getInstance().analyticsLaunchDebugger({ request: analyticsRequest, platform: args.platform }); + Services.extensionClient.analyticsLaunchDebugger({ request: analyticsRequest, platform: args.platform }); this.configureLoggingForRequest(); this.appRoot = args.appRoot; this.platform = args.platform; @@ -138,7 +137,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { this.fireEvent(new InitializedEvent()); }, e => { - Logger.error("Command failed: " + e, Tags.FrontendMessage); + Services.logger.error("Command failed: " + e, Tags.FrontendMessage); this.clearEverything(); return utils.errP(e); }); @@ -161,7 +160,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { let androidProject: ns.AndroidProject = new ns.AndroidProject(this.appRoot, args.tnsOutput); let thisAdapter: WebKitDebugAdapter = this; - Logger.log("Getting debug port"); + Services.logger.log("Getting debug port"); let androidConnection: AndroidConnection = null; let runDebugCommand: Promise = (args.request == 'launch') ? androidProject.debug(args) : Promise.resolve(); @@ -175,7 +174,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { this.setConnection(androidConnection); } }).then(() => { - Logger.log("Attaching to debug application"); + Services.logger.log("Attaching to debug application"); return androidConnection.attach(port, 'localhost'); }); }); @@ -199,7 +198,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } private onConnected(): void { - Logger.log("Debugger connected"); + Services.logger.log("Debugger connected"); } private fireEvent(event: DebugProtocol.Event): void { @@ -211,7 +210,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { private terminateSession(): void { //this.fireEvent(new TerminatedEvent()); - Logger.log("Terminating debug session"); + Services.logger.log("Terminating debug session"); this.clearEverything(); } @@ -220,7 +219,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { this.clearTargetContext(); if (this._webKitConnection) { - Logger.log("Closing debug connection"); + Services.logger.log("Closing debug connection"); this._webKitConnection.close(); this._webKitConnection = null; diff --git a/src/debug-adapter/webKitDebugSession.ts b/src/debug-adapter/webKitDebugSession.ts index ae157aa..cc3ea9d 100644 --- a/src/debug-adapter/webKitDebugSession.ts +++ b/src/debug-adapter/webKitDebugSession.ts @@ -2,7 +2,8 @@ import {OutputEvent, DebugSession, ErrorDestination} from 'vscode-debugadapter'; import {DebugProtocol} from 'vscode-debugprotocol'; import {WebKitDebugAdapter} from './webKitDebugAdapter'; -import {Logger, Handlers, LoggerHandler} from '../services/Logger'; +import {Handlers} from '../services/Logger'; +import {DebugAdapterServices as Services} from '../services/services/debugAdapterServices'; import {AdapterProxy} from './adapter/adapterProxy'; import {LineNumberTransformer} from './adapter/lineNumberTransformer'; @@ -18,12 +19,12 @@ export class WebKitDebugSession extends DebugSession { // Logging on the std streams is only allowed when running in server mode, because otherwise it goes through // the same channel that Code uses to communicate with the adapter, which can cause communication issues. if (isServer) { - Logger.addHandler(Handlers.stdStreamsHandler); + Services.logger.addHandler(Handlers.stdStreamsHandler); } process.removeAllListeners('unhandledRejection'); process.addListener('unhandledRejection', reason => { - Logger.log(`******** ERROR! Unhandled promise rejection: ${reason}`); + Services.logger.log(`******** ERROR! Unhandled promise rejection: ${reason}`); }); this._adapterProxy = new AdapterProxy( @@ -42,7 +43,7 @@ export class WebKitDebugSession extends DebugSession { public sendEvent(event: DebugProtocol.Event): void { if (event.event !== 'output') { // Don't create an infinite loop... - Logger.log(`To client: ${JSON.stringify(event) }`); + Services.logger.log(`To client: ${JSON.stringify(event) }`); } super.sendEvent(event); @@ -52,7 +53,7 @@ export class WebKitDebugSession extends DebugSession { * Overload sendResponse to log */ public sendResponse(response: DebugProtocol.Response): void { - Logger.log(`To client: ${JSON.stringify(response) }`); + Services.logger.log(`To client: ${JSON.stringify(response) }`); super.sendResponse(response); } @@ -87,7 +88,7 @@ export class WebKitDebugSession extends DebugSession { // These errors show up in the message bar at the top (or nowhere), sometimes not obvious that they // come from the adapter response.message = '[NativeScript-Debug-Adapter] ' + eStr; - Logger.log('Error: ' + e ? e.stack : eStr); + Services.logger.log('Error: ' + e ? e.stack : eStr); } response.success = false; @@ -101,7 +102,7 @@ export class WebKitDebugSession extends DebugSession { protected dispatchRequest(request: DebugProtocol.Request): void { const response = { seq: 0, type: 'response', request_seq: request.seq, command: request.command, success: true }; try { - Logger.log(`From client: ${request.command}(${JSON.stringify(request.arguments) })`); + Services.logger.log(`From client: ${request.command}(${JSON.stringify(request.arguments) })`); this.sendResponseAsync( request, response, diff --git a/src/main.ts b/src/main.ts index 3fbc69f..fbaf5b6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,45 +4,25 @@ import * as ns from './services/NsCliService'; import {ExtensionVersionService} from './services/ExtensionVersionService'; import {AnalyticsService} from './services/analytics/AnalyticsService'; import {ExtensionServer} from './services/ipc/ExtensionServer'; +import {ExtensionHostServices as Services} from './services/services/extensionHostServices'; -function performVersionsCheck(context: vscode.ExtensionContext) { - // Check the state of the existing NativeScript CLI - let cliInfo: ns.CliVersionInfo = new ns.CliVersionInfo(); - if (cliInfo.getErrorMessage() !== null) { - vscode.window.showErrorMessage(cliInfo.getErrorMessage()); - } - else { - // Checks whether a new version of the extension is available - let extensionVersionPromise: Promise = null; +// this method is called when the extension is activated +export function activate(context: vscode.ExtensionContext) { + Services.globalState = context.globalState; + Services.extensionServer.start(); - // Check the cache for extension version information - let extensionVersion: any = context.globalState.get('ExtensionVersionInfo'); - if (extensionVersion) { - let extensionVersionInfo = new ExtensionVersionService(extensionVersion.latestVersionMetadata, extensionVersion.timestamp); - if (extensionVersionInfo.getTimestamp() > Date.now() - 24 * 60 * 60 * 1000 /* Cache the version for a day */) { - extensionVersionPromise = Promise.resolve(extensionVersionInfo); - } + // Check for newer extension version + Services.extensionVersionService.isLatestInstalled.then(result => { + if (!result.result) { + vscode.window.showWarningMessage(result.error); } + }); - if (!extensionVersionPromise) { - // Takes the slow path and checks for newer version in the VS Code Marketplace - extensionVersionPromise = ExtensionVersionService.createFromMarketplace(); - } - extensionVersionPromise.then(extensionInfo => { - if (extensionInfo) { - context.globalState.update('ExtensionVersionInfo', { latestVersionMetadata: extensionInfo.getLatestVersionMeta(), timestamp: extensionInfo.getTimestamp() }); // save in cache - if (!extensionInfo.isLatest()) { - vscode.window.showWarningMessage(`A new version of the NativeScript extension is available. Run "Extensions: Show Outdated Extensions" command and select "NativeScript" to update to v${extensionInfo.getLatestVersionMeta().version}.`); - } - } - }, error => { /* In case of error behave as if the extension verison is latest */ }); + // Check if NativeScript CLI is installed globally and if it is compatible with the extension version + let cliInfo: ns.CliVersionInfo = new ns.CliVersionInfo(); + if (cliInfo.getErrorMessage() !== null) { + vscode.window.showErrorMessage(cliInfo.getErrorMessage()); } -} - -// this method is called when the extension is activated -export function activate(context: vscode.ExtensionContext) { - ExtensionServer.getInstance().start(); - performVersionsCheck(context); let runCommand = (project: ns.NSProject) => { if (vscode.workspace.rootPath === undefined) { @@ -55,7 +35,7 @@ export function activate(context: vscode.ExtensionContext) { runChannel.clear(); runChannel.show(vscode.ViewColumn.Two); - AnalyticsService.getInstance().runRunCommand(project.platform()); + Services.analyticsService.runRunCommand(project.platform()); return project.run() .then(tnsProcess => { @@ -91,5 +71,5 @@ export function activate(context: vscode.ExtensionContext) { } export function deactivate() { - ExtensionServer.getInstance().stop(); + Services.extensionServer.stop(); } \ No newline at end of file diff --git a/src/services/Logger.ts b/src/services/Logger.ts index 85113e8..8a2c4f0 100644 --- a/src/services/Logger.ts +++ b/src/services/Logger.ts @@ -19,56 +19,49 @@ type TaggedLoggerHandler = { handler: LoggerHandler, tags: string[] }; * The logger is a singleton. */ export class Logger { - private static _instance: Logger; - private _handlers: TaggedLoggerHandler[]; constructor() { this._handlers = []; } - private static get instance(): Logger { - this._instance = this._instance || new Logger(); - return this._instance; - } - - private static handleMessage(message: string, type: LoggerMessageType = LoggerMessageType.Log, tag: string = null) { - for (let handler of this.instance._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 static log(message: string, tag: string = null): void { + public log(message: string, tag: string = null): void { this.handleMessage(message, LoggerMessageType.Log, tag); } - public static info(message: string, tag: string = null): void { + public info(message: string, tag: string = null): void { this.handleMessage(message, LoggerMessageType.Info, tag); } - public static warn(message: string, tag: string = null): void { + public warn(message: string, tag: string = null): void { this.handleMessage(message, LoggerMessageType.Warning, tag); } - public static error(message: string, tag: string = null): void { + public error(message: string, tag: string = null): void { this.handleMessage(message, LoggerMessageType.Error, tag); } - public static addHandler(handler: LoggerHandler, tags: string[] = null) { + public addHandler(handler: LoggerHandler, tags: string[] = null) { tags = tags || []; - this.instance._handlers.push({ handler: handler, tags: tags }); + this._handlers.push({ handler: handler, tags: tags }); } /** * Removes all occurrence of this handler, ignoring the associated tags */ - public static removeHandler(handlerToRemove: LoggerHandler) { - let i = this.instance._handlers.length; + public removeHandler(handlerToRemove: LoggerHandler) { + let i = this._handlers.length; while (i--) { - if (this.instance._handlers[i].handler == handlerToRemove) { - this.instance._handlers.splice(i, 1); + if (this._handlers[i].handler == handlerToRemove) { + this._handlers.splice(i, 1); } } } diff --git a/src/services/NsCliService.ts b/src/services/NsCliService.ts index b3e9301..d3e5312 100644 --- a/src/services/NsCliService.ts +++ b/src/services/NsCliService.ts @@ -5,6 +5,7 @@ import * as path from 'path'; import * as https from 'https'; import {Version} from './version'; import {Logger, Handlers, Tags} from '../services/Logger'; +import {Services} from '../services/services/services'; import {ExtensionVersionService} from './ExtensionVersionService'; import {DebugProtocol} from 'vscode-debugprotocol'; @@ -34,13 +35,17 @@ export class CliVersionInfo { return this.installedCliVersion; } + private static getMinNativeScriptCliVersionSupported(): Version { + return Version.parse(require('../../package.json').minNativescriptCliVersion); + } + constructor() { let installedCliVersion: Version = CliVersionInfo.getInstalledCliVersion(); if (installedCliVersion === null) { this._state = CliVersionState.NotExisting; } else { - let minSupportedCliVersion = ExtensionVersionService.getMinSupportedNativeScriptVersion(); + let minSupportedCliVersion = CliVersionInfo.getMinNativeScriptCliVersionSupported(); this._state = installedCliVersion.compareBySubminorTo(minSupportedCliVersion) < 0 ? CliVersionState.OlderThanSupported : CliVersionState.Compatible; } } @@ -58,7 +63,7 @@ export class CliVersionInfo { case CliVersionState.NotExisting: return `NativeScript CLI not found, please run 'npm -g install nativescript' to install it.`; case CliVersionState.OlderThanSupported: - return `The existing NativeScript extension is compatible with NativeScript CLI v${ExtensionVersionService.getMinSupportedNativeScriptVersion()} or greater. The currently installed NativeScript CLI is v${CliVersionInfo.getInstalledCliVersion()}. You can update the NativeScript CLI by executing 'npm install -g nativescript'.`; + return `The existing NativeScript extension is compatible with NativeScript CLI v${CliVersionInfo.getMinNativeScriptCliVersionSupported()} or greater. The currently installed NativeScript CLI is v${CliVersionInfo.getInstalledCliVersion()}. You can update the NativeScript CLI by executing 'npm install -g nativescript'.`; default: return null; } @@ -165,7 +170,7 @@ export class IosProject extends NSProject { child.stdout.on('data', (data) => { let cliOutput: string = data.toString(); - Logger.log(cliOutput, Tags.FrontendMessage); + Services.logger.log(cliOutput, Tags.FrontendMessage); socketPath = socketPath || isSocketOpened(cliOutput); appSynced = rebuild ? false : (appSynced || isAppSynced(cliOutput)); @@ -176,7 +181,7 @@ export class IosProject extends NSProject { }); child.stderr.on('data', (data) => { - Logger.error(data.toString(), Tags.FrontendMessage); + Services.logger.error(data.toString(), Tags.FrontendMessage); }); child.on('close', (code, signal) => { @@ -230,13 +235,13 @@ export class AndroidProject extends NSProject { .appendParams(args.tnsArgs) .build(); - Logger.log("tns debug command: " + command); + Services.logger.log("tns debug command: " + command); // run NativeScript CLI command let child: ChildProcess = this.spawnProcess(command.path, command.args, args.tnsOutput); child.stdout.on('data', function(data) { let strData: string = data.toString(); - Logger.log(data.toString(), Tags.FrontendMessage); + Services.logger.log(data.toString(), Tags.FrontendMessage); if (!launched) { if (args.request === "launch" && strData.indexOf('# NativeScript Debugger started #') > -1) { launched = true; @@ -249,7 +254,7 @@ export class AndroidProject extends NSProject { }); child.stderr.on('data', function(data) { - Logger.error(data.toString(), Tags.FrontendMessage); + Services.logger.error(data.toString(), Tags.FrontendMessage); }); child.on('close', function(code) { @@ -284,7 +289,7 @@ export class AndroidProject extends NSProject { let child: ChildProcess = this.spawnProcess(command.path, command.args, args.tnsOutput); child.stdout.on('data', function(data) { - Logger.log(data.toString(), Tags.FrontendMessage); + Services.logger.log(data.toString(), Tags.FrontendMessage); let regexp = new RegExp("(?:debug port: )([\\d]{5})"); @@ -299,10 +304,10 @@ export class AndroidProject extends NSProject { } if (portNumberMatch) { - Logger.log("port number match '" + portNumberMatch + "'"); + Services.logger.log("port number match '" + portNumberMatch + "'"); let portNumber = parseInt(portNumberMatch); if (portNumber) { - Logger.log("port number " + portNumber); + Services.logger.log("port number " + portNumber); child.stdout.removeAllListeners('data'); resolve(portNumber); } @@ -310,7 +315,7 @@ export class AndroidProject extends NSProject { }); child.stderr.on('data', function(data) { - Logger.error(data.toString(), Tags.FrontendMessage); + Services.logger.error(data.toString(), Tags.FrontendMessage); }); child.on('close', function(code) { diff --git a/src/services/analytics/AnalyticsService.ts b/src/services/analytics/AnalyticsService.ts index 5f83856..ee94302 100644 --- a/src/services/analytics/AnalyticsService.ts +++ b/src/services/analytics/AnalyticsService.ts @@ -8,20 +8,11 @@ import { ExtensionVersionService } from '../ExtensionVersionService'; import * as ns from '../NsCliService'; export class AnalyticsService { - private static _instance: AnalyticsService; - private _baseInfo: AnalyticsBaseInfo; private _gua: GUAService; private _ta: TelerikAnalyticsService; private _analyticsEnabled: boolean; - public static getInstance(): AnalyticsService { - if (!this._instance) { - this._instance = new AnalyticsService(); - } - return this._instance; - } - public static generateMachineId(): string { let machineId = ''; try { @@ -49,7 +40,7 @@ export class AnalyticsService { this._baseInfo = { cliVersion: ns.CliVersionInfo.getInstalledCliVersion().toString(), - extensionVersion: ExtensionVersionService.getExtensionVersion().toString(), + extensionVersion: require('../../../package.json').version, operatingSystem: operatingSystem, userId: AnalyticsService.generateMachineId() }; @@ -79,7 +70,7 @@ export class AnalyticsService { this._gua.runRunCommand(platform), this._ta.runRunCommand(platform) ]); - } catch(e) {} + } catch(e) { } } return Promise.resolve(); } diff --git a/src/services/extensionVersionService.ts b/src/services/extensionVersionService.ts index 029b9f7..77da9d9 100644 --- a/src/services/extensionVersionService.ts +++ b/src/services/extensionVersionService.ts @@ -1,104 +1,83 @@ import * as https from 'https'; +import * as vscode from 'vscode'; import {Version} from './version'; -export class ExtensionVersionService { - private static extensionVersion: Version = null; - private static minNativescriptCliVersion: Version = null; - private static extensionId: string = '8d837914-d8fa-45b5-965d-f76ebd6dbf5c'; - private static marketplaceQueryResult: Promise = null; - - private latestVersionMeta: any; - private timestamp: number; - - private static initVersionsFromPackageJson() { - let packageJson = require('../../package.json'); - this.extensionVersion = Version.parse(packageJson.version); - this.minNativescriptCliVersion = Version.parse(packageJson.minNativescriptCliVersion); - } - - public static getExtensionVersion(): Version { - if (this.extensionVersion === null) { - this.initVersionsFromPackageJson(); - } - return this.extensionVersion; - } +export type LatestPublishedVersionCheckResult = {latestPublishedVersion: string, timestamp: number}; - public static getMinSupportedNativeScriptVersion(): Version { - if (this.minNativescriptCliVersion === null) { - this.initVersionsFromPackageJson(); - } - return this.minNativescriptCliVersion; - } +export class ExtensionVersionService { + private static _extensionId: string = '8d837914-d8fa-45b5-965d-f76ebd6dbf5c'; + private static _getLatestPublishedVersionPromise: Promise = null; + private _memento: vscode.Memento; - public static getMarketplaceExtensionData(): Promise { - if (this.marketplaceQueryResult == null) { - this.marketplaceQueryResult = new Promise((resolve, reject) => { - let postData: string = `{ filters: [{ criteria: [{ filterType: 4, value: "${ExtensionVersionService.extensionId}" }] }], flags: 262 }`; + private static getExtensionMetadataFromVSCodeMarketplace(): Promise { + return new Promise((resolve, reject) =>{ + let postData: string = `{ filters: [{ criteria: [{ filterType: 4, value: "${ExtensionVersionService._extensionId}" }] }], flags: 262 }`; - let request = https.request({ - hostname: 'marketplace.visualstudio.com', - path: '/_apis/public/gallery/extensionquery', - method: 'POST', - headers: { - 'Accept': 'application/json;api-version=2.2-preview.1', - 'Content-Type': 'application/json', - 'Transfer-Encoding': 'chunked', - 'Content-Length': Buffer.byteLength(postData) - } - }, response => { - if (response.statusCode != 200) { - reject(`Unable to download data from Visual Studio Marketplace. Status code: ${response.statusCode}`); - return; - } - let body = ''; - response.on('data', chunk => { - body += chunk; - }); - response.on('end', () => { - resolve(JSON.parse(body)); - }); + let request = https.request({ + hostname: 'marketplace.visualstudio.com', + path: '/_apis/public/gallery/extensionquery', + method: 'POST', + headers: { + 'Accept': 'application/json;api-version=2.2-preview.1', + 'Content-Type': 'application/json', + 'Transfer-Encoding': 'chunked', + 'Content-Length': Buffer.byteLength(postData) + } + }, response => { + if (response.statusCode != 200) { + reject(`Unable to download data from Visual Studio Marketplace. Status code: ${response.statusCode}`); + return; + } + let body = ''; + response.on('data', chunk => { + body += chunk; }); - - request.on('error', (e) => { - reject(e); + response.on('end', () => { + let bodyObj = JSON.parse(body); + if (bodyObj.results[0].extensions[0].extensionId == ExtensionVersionService._extensionId) { + let latestPublishedVersion = bodyObj.results[0].extensions[0].versions[0].version; + resolve({ latestPublishedVersion: latestPublishedVersion, timestamp: Date.now() }); + } }); + }); - request.end(postData); + request.on('error', (e) => { + reject(e); }); - } - return this.marketplaceQueryResult; - } - public static createFromMarketplace(): Promise { - return this.getMarketplaceExtensionData() - .then(marketplaceData => { - let latestVersion = null; - try { - if (marketplaceData.results[0].extensions[0].extensionId == ExtensionVersionService.extensionId) { - latestVersion = marketplaceData.results[0].extensions[0].versions[0]; - } - } catch (e) { } - return new ExtensionVersionService(latestVersion); + request.end(postData); }); } - constructor(latestVersionMeta: any, timestamp?: number) { - this.latestVersionMeta = latestVersionMeta; - this.timestamp = timestamp || Date.now(); + constructor(context: vscode.Memento) { + this._memento = context; } - public getLatestVersionMeta(): any { - return this.latestVersionMeta; - } + public get latestPublishedVersion(): Promise { + if (ExtensionVersionService._getLatestPublishedVersionPromise) { + return ExtensionVersionService._getLatestPublishedVersionPromise.then(result => Version.parse(result.latestPublishedVersion) ); + } - public isLatest(): boolean { - if (!this.getLatestVersionMeta()) { - return true; + // Check the cache for extension version information + let cachedResult: LatestPublishedVersionCheckResult = this._memento.get('LatestPublishedExtensionVersion'); + if (cachedResult && cachedResult.timestamp > Date.now() - 24 * 60 * 60 * 1000) { // Version is cached for a day + ExtensionVersionService._getLatestPublishedVersionPromise = Promise.resolve(cachedResult); } - return ExtensionVersionService.getExtensionVersion().compareBySubminorTo(Version.parse(this.getLatestVersionMeta().version)) >= 0; + else { + ExtensionVersionService._getLatestPublishedVersionPromise = ExtensionVersionService.getExtensionMetadataFromVSCodeMarketplace().then((result: LatestPublishedVersionCheckResult) => { + this._memento.update('LatestPublishedExtensionVersion', result); // save in cache + return result; + }); + } + return ExtensionVersionService._getLatestPublishedVersionPromise.then(result => Version.parse(result.latestPublishedVersion)); } - public getTimestamp(): number { - return this.timestamp; + public get isLatestInstalled(): Promise<{ result: boolean, error: string }> { + return this.latestPublishedVersion.then(latestVersion => { + let extensionVersion = Version.parse(require('../../package.json').version); + let isLatest: boolean = extensionVersion.compareBySubminorTo(latestVersion) >= 0; + let error = isLatest ? null : `A new version of the NativeScript extension is available. Open "Extensions" panel to update to v${latestVersion}.`; + return {result: isLatest, error: error}; + }); } } \ No newline at end of file diff --git a/src/services/ipc/ExtensionClient.ts b/src/services/ipc/ExtensionClient.ts index 2921a39..3ff6738 100644 --- a/src/services/ipc/ExtensionClient.ts +++ b/src/services/ipc/ExtensionClient.ts @@ -5,39 +5,19 @@ import * as extProtocol from './ExtensionProtocol'; let ipc = require('node-ipc'); export class ExtensionClient { - private static _appRoot: string; - private static _instance: ExtensionClient; - + private _appRoot: string; private _idCounter = 0; private _pendingRequests: Object; private _ipcClientInitialized: Promise; - public static getInstance() { - if (!this._instance) { - this._instance = new ExtensionClient(); - } - return this._instance; - } - - public static getAppRoot() { - return this._appRoot; - } - public static getTempFilePathForDirectory(directoryPath: string) { let fileName: string = 'vsc-ns-ext-' + crypto.createHash('md5').update(directoryPath).digest("hex") + '.sock'; return path.join(os.tmpdir(), fileName); } - public static setAppRoot(appRoot: string) { + constructor(appRoot: string) { this._appRoot = appRoot; - } - - constructor() { - if (!ExtensionClient.getAppRoot()) { - throw new Error(`Unable to connect to extension host. App root is '${ExtensionClient.getAppRoot()}'`); - } - this._idCounter = 0; this._pendingRequests = {}; @@ -47,7 +27,7 @@ export class ExtensionClient { this._ipcClientInitialized = new Promise((res, rej) => { ipc.connectTo( 'extHost', - ExtensionClient.getTempFilePathForDirectory(ExtensionClient.getAppRoot()), + ExtensionClient.getTempFilePathForDirectory(this._appRoot), () => { ipc.of.extHost.on('connect', () => { res(); diff --git a/src/services/ipc/ExtensionServer.ts b/src/services/ipc/ExtensionServer.ts index ff3a60e..c1818ae 100644 --- a/src/services/ipc/ExtensionServer.ts +++ b/src/services/ipc/ExtensionServer.ts @@ -3,21 +3,12 @@ import * as os from 'os'; import * as crypto from 'crypto'; import * as vscode from 'vscode'; import * as extProtocol from './ExtensionProtocol'; -import {AnalyticsService} from '../analytics/AnalyticsService'; +import {ExtensionHostServices as Services} from '../services/extensionHostServices'; let ipc = require('node-ipc'); export class ExtensionServer { - private static _instance: ExtensionServer; - private _isRunning: boolean; - public static getInstance() { - if (!this._instance) { - this._instance = new ExtensionServer(); - } - return this._instance; - } - public static getTempFilePathForDirectory(directoryPath: string) { let fileName: string = 'vsc-ns-ext-' + crypto.createHash('md5').update(directoryPath).digest("hex") + '.sock'; return path.join(os.tmpdir(), fileName); @@ -66,10 +57,10 @@ export class ExtensionServer { } public analyticsLaunchDebugger(args: extProtocol.AnalyticsLaunchDebuggerArgs): Promise { - return AnalyticsService.getInstance().launchDebugger(args.request, args.platform); + return Services.analyticsService.launchDebugger(args.request, args.platform); } public runRunCommand(args: extProtocol.AnalyticsRunRunCommandArgs): Promise { - return AnalyticsService.getInstance().runRunCommand(args.platform); + return Services.analyticsService.runRunCommand(args.platform); } } \ No newline at end of file diff --git a/src/services/services/debugAdapterServices.ts b/src/services/services/debugAdapterServices.ts new file mode 100644 index 0000000..71c5719 --- /dev/null +++ b/src/services/services/debugAdapterServices.ts @@ -0,0 +1,20 @@ +import {Services} from './services'; +import {ExtensionClient} from '../ipc/extensionClient'; +import {Logger} from '../Logger'; + +export class DebugAdapterServices extends Services { + private static _appRoot: string; + private static _extensionClient: ExtensionClient; + + public static get appRoot(): string { return this._appRoot; } + + public static set appRoot(appRoot: string) { this._appRoot = appRoot; } + + public static get 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; + } +} \ No newline at end of file diff --git a/src/services/services/extensionHostServices.ts b/src/services/services/extensionHostServices.ts new file mode 100644 index 0000000..d7432f2 --- /dev/null +++ b/src/services/services/extensionHostServices.ts @@ -0,0 +1,35 @@ +import * as vscode from 'vscode'; +import {Services} from './services'; +import {ExtensionVersionService} from '../extensionVersionService'; +import {ExtensionServer} from '../ipc/extensionServer'; +import {AnalyticsService} from '../analytics/analyticsService'; + +export class ExtensionHostServices extends Services { + private static _globalState: vscode.Memento; + + private static _extensionVersionService: ExtensionVersionService; + private static _extensionServer: ExtensionServer; + private static _analyticsService: AnalyticsService; + + public static get globalState(): vscode.Memento { return this._globalState; } + + public static set globalState(globalState: vscode.Memento) { this._globalState = globalState; } + + public static get extensionVersionService(): ExtensionVersionService { + if (!this._extensionVersionService && !this._globalState) { + throw new Error("Global state has no value."); + } + this._extensionVersionService = this._extensionVersionService || new ExtensionVersionService(this.globalState); + return this._extensionVersionService; + } + + public static get extensionServer(): ExtensionServer { + this._extensionServer = this._extensionServer || new ExtensionServer(); + return this._extensionServer; + } + + public static get analyticsService(): AnalyticsService { + this._analyticsService = this._analyticsService || new AnalyticsService(); + return this._analyticsService; + } +} \ No newline at end of file diff --git a/src/services/services/services.ts b/src/services/services/services.ts new file mode 100644 index 0000000..5fb7c61 --- /dev/null +++ b/src/services/services/services.ts @@ -0,0 +1,10 @@ +import {Logger} from '../logger'; + +export class Services { + private static _logger: Logger; + + public static get logger(): Logger { + this._logger = this._logger || new Logger(); + return this._logger; + } +} From 5cca7597c3f2f289f995263dc0a030feebbaef8f Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Sun, 27 Nov 2016 13:40:28 +0200 Subject: [PATCH 8/9] Move all services to root level. --- src/{services => }/analytics/AnalyticsBaseInfo.ts | 0 src/{services => }/analytics/AnalyticsService.ts | 6 +++--- src/{services => }/analytics/EqatecMonitor.min.js | 0 src/{services => }/analytics/GUAService.ts | 0 src/{services => }/analytics/TelerikAnalyticsService.ts | 0 src/{services => common}/Logger.ts | 0 src/{services => common}/extensionVersionService.ts | 0 src/{services => common}/version.ts | 0 src/debug-adapter/adapter/adapterProxy.ts | 2 +- src/debug-adapter/adapter/pathTransformer.ts | 3 +-- src/debug-adapter/adapter/sourceMaps/pathUtilities.ts | 2 +- .../adapter/sourceMaps/sourceMapTransformer.ts | 2 +- src/debug-adapter/adapter/sourceMaps/sourceMaps.ts | 2 +- src/debug-adapter/connection/androidConnection.ts | 2 +- src/debug-adapter/connection/iosConnection.ts | 2 +- src/debug-adapter/webKitDebugAdapter.ts | 8 ++++---- src/debug-adapter/webKitDebugSession.ts | 4 ++-- src/{services => }/ipc/ExtensionClient.ts | 0 src/{services => }/ipc/ExtensionProtocol.ts | 0 src/{services => }/ipc/ExtensionServer.ts | 0 src/main.ts | 7 ++----- src/{services => project}/NsCliService.ts | 7 +++---- src/services/{services => }/debugAdapterServices.ts | 2 +- src/services/{services => }/extensionHostServices.ts | 2 +- src/services/{services => }/services.ts | 2 +- src/tsconfig.json | 2 +- 26 files changed, 25 insertions(+), 30 deletions(-) rename src/{services => }/analytics/AnalyticsBaseInfo.ts (100%) rename src/{services => }/analytics/AnalyticsService.ts (94%) rename src/{services => }/analytics/EqatecMonitor.min.js (100%) rename src/{services => }/analytics/GUAService.ts (100%) rename src/{services => }/analytics/TelerikAnalyticsService.ts (100%) rename src/{services => common}/Logger.ts (100%) rename src/{services => common}/extensionVersionService.ts (100%) rename src/{services => common}/version.ts (100%) rename src/{services => }/ipc/ExtensionClient.ts (100%) rename src/{services => }/ipc/ExtensionProtocol.ts (100%) rename src/{services => }/ipc/ExtensionServer.ts (100%) rename src/{services => project}/NsCliService.ts (98%) rename src/services/{services => }/debugAdapterServices.ts (94%) rename src/services/{services => }/extensionHostServices.ts (94%) rename src/services/{services => }/services.ts (82%) diff --git a/src/services/analytics/AnalyticsBaseInfo.ts b/src/analytics/AnalyticsBaseInfo.ts similarity index 100% rename from src/services/analytics/AnalyticsBaseInfo.ts rename to src/analytics/AnalyticsBaseInfo.ts diff --git a/src/services/analytics/AnalyticsService.ts b/src/analytics/AnalyticsService.ts similarity index 94% rename from src/services/analytics/AnalyticsService.ts rename to src/analytics/AnalyticsService.ts index ee94302..d341553 100644 --- a/src/services/analytics/AnalyticsService.ts +++ b/src/analytics/AnalyticsService.ts @@ -1,11 +1,11 @@ import * as os from 'os'; import * as vscode from 'vscode'; -import { Version } from '../version'; +import { Version } from '../common/version'; import { GUAService } from './GUAService'; import { TelerikAnalyticsService } from './TelerikAnalyticsService'; import { AnalyticsBaseInfo, OperatingSystem } from './AnalyticsBaseInfo'; -import { ExtensionVersionService } from '../ExtensionVersionService'; -import * as ns from '../NsCliService'; +import { ExtensionVersionService } from '../common/ExtensionVersionService'; +import * as ns from '../project/NsCliService'; export class AnalyticsService { private _baseInfo: AnalyticsBaseInfo; diff --git a/src/services/analytics/EqatecMonitor.min.js b/src/analytics/EqatecMonitor.min.js similarity index 100% rename from src/services/analytics/EqatecMonitor.min.js rename to src/analytics/EqatecMonitor.min.js diff --git a/src/services/analytics/GUAService.ts b/src/analytics/GUAService.ts similarity index 100% rename from src/services/analytics/GUAService.ts rename to src/analytics/GUAService.ts diff --git a/src/services/analytics/TelerikAnalyticsService.ts b/src/analytics/TelerikAnalyticsService.ts similarity index 100% rename from src/services/analytics/TelerikAnalyticsService.ts rename to src/analytics/TelerikAnalyticsService.ts diff --git a/src/services/Logger.ts b/src/common/Logger.ts similarity index 100% rename from src/services/Logger.ts rename to src/common/Logger.ts diff --git a/src/services/extensionVersionService.ts b/src/common/extensionVersionService.ts similarity index 100% rename from src/services/extensionVersionService.ts rename to src/common/extensionVersionService.ts diff --git a/src/services/version.ts b/src/common/version.ts similarity index 100% rename from src/services/version.ts rename to src/common/version.ts diff --git a/src/debug-adapter/adapter/adapterProxy.ts b/src/debug-adapter/adapter/adapterProxy.ts index 66d113a..3685b32 100644 --- a/src/debug-adapter/adapter/adapterProxy.ts +++ b/src/debug-adapter/adapter/adapterProxy.ts @@ -3,7 +3,7 @@ *--------------------------------------------------------*/ import * as utils from '../utilities'; -import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; +import {DebugAdapterServices as Services} from '../../services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; export type EventHandler = (event: DebugProtocol.Event) => void; diff --git a/src/debug-adapter/adapter/pathTransformer.ts b/src/debug-adapter/adapter/pathTransformer.ts index f56afb6..eea1e6a 100644 --- a/src/debug-adapter/adapter/pathTransformer.ts +++ b/src/debug-adapter/adapter/pathTransformer.ts @@ -3,9 +3,8 @@ *--------------------------------------------------------*/ import * as utils from '../utilities'; -import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; +import {DebugAdapterServices as Services} from '../../services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; -import * as path from 'path'; interface IPendingBreakpoint { resolve: () => void; diff --git a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts index e15b9ec..2466316 100644 --- a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts +++ b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts @@ -6,7 +6,7 @@ import * as Path from 'path'; import * as URL from 'url'; -import {DebugAdapterServices as Services} from '../../../services/services/debugAdapterServices'; +import {DebugAdapterServices as Services} from '../../../services/debugAdapterServices'; import * as utils from '../../utilities'; export function getPathRoot(p: string) { diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts index 014c95b..3224146 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import * as fs from 'fs'; -import {DebugAdapterServices as Services} from '../../../services/services/debugAdapterServices'; +import {DebugAdapterServices as Services} from '../../../services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; import {ISourceMaps, SourceMaps} from './sourceMaps'; import * as utils from '../../utilities'; diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts index f7f1b62..cc8eefd 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts @@ -10,7 +10,7 @@ import * as FS from 'fs'; import {SourceMapConsumer} from 'source-map'; import * as PathUtils from './pathUtilities'; import * as utils from '../../utilities'; -import {DebugAdapterServices as Services} from '../../../services/services/debugAdapterServices'; +import {DebugAdapterServices as Services} from '../../../services/debugAdapterServices'; export interface MappingResult { diff --git a/src/debug-adapter/connection/androidConnection.ts b/src/debug-adapter/connection/androidConnection.ts index 29bc88c..5329aeb 100644 --- a/src/debug-adapter/connection/androidConnection.ts +++ b/src/debug-adapter/connection/androidConnection.ts @@ -1,6 +1,6 @@ import * as http from 'http'; import {EventEmitter} from 'events'; -import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; +import {DebugAdapterServices as Services} from '../../services/debugAdapterServices'; import * as Net from 'net'; import { INSDebugConnection } from './INSDebugConnection'; diff --git a/src/debug-adapter/connection/iosConnection.ts b/src/debug-adapter/connection/iosConnection.ts index 0672236..7099207 100644 --- a/src/debug-adapter/connection/iosConnection.ts +++ b/src/debug-adapter/connection/iosConnection.ts @@ -7,7 +7,7 @@ import * as stream from 'stream'; import {EventEmitter} from 'events'; import {INSDebugConnection} from './INSDebugConnection'; import * as utils from '../utilities'; -import {DebugAdapterServices as Services} from '../../services/services/debugAdapterServices'; +import {DebugAdapterServices as Services} from '../../services/debugAdapterServices'; interface IMessageWithId { id: number; diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index d99f7f3..1900454 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -4,8 +4,8 @@ import * as os from 'os'; import * as fs from 'fs'; -import {spawn, ChildProcess} from 'child_process'; import * as path from 'path'; +import {spawn, ChildProcess} from 'child_process'; import {Handles, StoppedEvent, InitializedEvent, TerminatedEvent, OutputEvent} from 'vscode-debugadapter'; import {DebugProtocol} from 'vscode-debugprotocol'; import {INSDebugConnection} from './connection/INSDebugConnection'; @@ -13,9 +13,9 @@ import {IosConnection} from './connection/iosConnection'; import {AndroidConnection} from './connection/androidConnection'; import * as utils from './utilities'; import {formatConsoleMessage} from './consoleHelper'; -import * as ns from '../services/NsCliService'; -import {DebugAdapterServices as Services} from '../services/services/debugAdapterServices'; -import {LoggerHandler, Handlers, Tags} from '../services/Logger'; +import * as ns from '../project/NsCliService'; +import {DebugAdapterServices as Services} from '../services/debugAdapterServices'; +import {LoggerHandler, Handlers, Tags} from '../common/Logger'; import {DebugConfiguration} from './debugConfiguration'; interface IScopeVarHandle { diff --git a/src/debug-adapter/webKitDebugSession.ts b/src/debug-adapter/webKitDebugSession.ts index cc3ea9d..a127c7d 100644 --- a/src/debug-adapter/webKitDebugSession.ts +++ b/src/debug-adapter/webKitDebugSession.ts @@ -2,8 +2,8 @@ import {OutputEvent, DebugSession, ErrorDestination} from 'vscode-debugadapter'; import {DebugProtocol} from 'vscode-debugprotocol'; import {WebKitDebugAdapter} from './webKitDebugAdapter'; -import {Handlers} from '../services/Logger'; -import {DebugAdapterServices as Services} from '../services/services/debugAdapterServices'; +import {Handlers} from '../common/Logger'; +import {DebugAdapterServices as Services} from '../services/debugAdapterServices'; import {AdapterProxy} from './adapter/adapterProxy'; import {LineNumberTransformer} from './adapter/lineNumberTransformer'; diff --git a/src/services/ipc/ExtensionClient.ts b/src/ipc/ExtensionClient.ts similarity index 100% rename from src/services/ipc/ExtensionClient.ts rename to src/ipc/ExtensionClient.ts diff --git a/src/services/ipc/ExtensionProtocol.ts b/src/ipc/ExtensionProtocol.ts similarity index 100% rename from src/services/ipc/ExtensionProtocol.ts rename to src/ipc/ExtensionProtocol.ts diff --git a/src/services/ipc/ExtensionServer.ts b/src/ipc/ExtensionServer.ts similarity index 100% rename from src/services/ipc/ExtensionServer.ts rename to src/ipc/ExtensionServer.ts diff --git a/src/main.ts b/src/main.ts index fbaf5b6..7ad3c02 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,7 @@ import * as vscode from 'vscode'; import * as child from 'child_process'; -import * as ns from './services/NsCliService'; -import {ExtensionVersionService} from './services/ExtensionVersionService'; -import {AnalyticsService} from './services/analytics/AnalyticsService'; -import {ExtensionServer} from './services/ipc/ExtensionServer'; -import {ExtensionHostServices as Services} from './services/services/extensionHostServices'; +import * as ns from './project/NsCliService'; +import {ExtensionHostServices as Services} from './services/extensionHostServices'; // this method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { diff --git a/src/services/NsCliService.ts b/src/project/NsCliService.ts similarity index 98% rename from src/services/NsCliService.ts rename to src/project/NsCliService.ts index d3e5312..338a913 100644 --- a/src/services/NsCliService.ts +++ b/src/project/NsCliService.ts @@ -3,10 +3,9 @@ import * as fs from 'fs'; import {EventEmitter} from 'events'; import * as path from 'path'; import * as https from 'https'; -import {Version} from './version'; -import {Logger, Handlers, Tags} from '../services/Logger'; -import {Services} from '../services/services/services'; -import {ExtensionVersionService} from './ExtensionVersionService'; +import {Version} from '../common/version'; +import {Handlers, Tags} from '../common/Logger'; +import {Services} from '../services/services'; import {DebugProtocol} from 'vscode-debugprotocol'; export enum CliVersionState { diff --git a/src/services/services/debugAdapterServices.ts b/src/services/debugAdapterServices.ts similarity index 94% rename from src/services/services/debugAdapterServices.ts rename to src/services/debugAdapterServices.ts index 71c5719..e6201e5 100644 --- a/src/services/services/debugAdapterServices.ts +++ b/src/services/debugAdapterServices.ts @@ -1,6 +1,6 @@ import {Services} from './services'; import {ExtensionClient} from '../ipc/extensionClient'; -import {Logger} from '../Logger'; +import {Logger} from '../common/Logger'; export class DebugAdapterServices extends Services { private static _appRoot: string; diff --git a/src/services/services/extensionHostServices.ts b/src/services/extensionHostServices.ts similarity index 94% rename from src/services/services/extensionHostServices.ts rename to src/services/extensionHostServices.ts index d7432f2..146d0db 100644 --- a/src/services/services/extensionHostServices.ts +++ b/src/services/extensionHostServices.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import {Services} from './services'; -import {ExtensionVersionService} from '../extensionVersionService'; +import {ExtensionVersionService} from '../common/extensionVersionService'; import {ExtensionServer} from '../ipc/extensionServer'; import {AnalyticsService} from '../analytics/analyticsService'; diff --git a/src/services/services/services.ts b/src/services/services.ts similarity index 82% rename from src/services/services/services.ts rename to src/services/services.ts index 5fb7c61..9347cf3 100644 --- a/src/services/services/services.ts +++ b/src/services/services.ts @@ -1,4 +1,4 @@ -import {Logger} from '../logger'; +import {Logger} from '../common/logger'; export class Services { private static _logger: Logger; diff --git a/src/tsconfig.json b/src/tsconfig.json index 8eb0dbf..c139045 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -20,6 +20,6 @@ "./debug-adapter/webKitDebug.ts", "./main.ts", "./tests/adapter.test.ts", - "./services/analytics/EqatecMonitor.min.js" + "./analytics/EqatecMonitor.min.js" ] } \ No newline at end of file From 5cf564321dc415c9e44a25d5bb1ce43df5f23450 Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Sun, 27 Nov 2016 17:32:58 +0200 Subject: [PATCH 9/9] Decouple cli and project services --- src/analytics/AnalyticsService.ts | 8 +- src/common/extensionVersionService.ts | 3 +- src/{debug-adapter => common}/utilities.ts | 9 + src/debug-adapter/adapter/adapterProxy.ts | 2 +- src/debug-adapter/adapter/pathTransformer.ts | 2 +- .../adapter/sourceMaps/pathUtilities.ts | 2 +- .../sourceMaps/sourceMapTransformer.ts | 2 +- .../adapter/sourceMaps/sourceMaps.ts | 2 +- src/debug-adapter/connection/iosConnection.ts | 2 +- src/debug-adapter/consoleHelper.ts | 2 +- ...{debugConfiguration.ts => debugRequest.ts} | 25 +- src/debug-adapter/webKitDebugAdapter.ts | 115 +++--- src/main.ts | 56 +-- src/project/NsCliService.ts | 361 ------------------ src/project/androidProject.ts | 60 +++ src/project/iosProject.ts | 63 +++ src/project/nativeScriptCli.ts | 82 ++++ src/project/project.ts | 39 ++ src/project/streamScanner.ts | 84 ++++ src/services/debugAdapterServices.ts | 2 +- src/services/services.ts | 15 +- 21 files changed, 469 insertions(+), 467 deletions(-) rename src/{debug-adapter => common}/utilities.ts (97%) rename src/debug-adapter/{debugConfiguration.ts => debugRequest.ts} (52%) delete mode 100644 src/project/NsCliService.ts create mode 100644 src/project/androidProject.ts create mode 100644 src/project/iosProject.ts create mode 100644 src/project/nativeScriptCli.ts create mode 100644 src/project/project.ts create mode 100644 src/project/streamScanner.ts diff --git a/src/analytics/AnalyticsService.ts b/src/analytics/AnalyticsService.ts index d341553..577b6ed 100644 --- a/src/analytics/AnalyticsService.ts +++ b/src/analytics/AnalyticsService.ts @@ -4,8 +4,8 @@ import { Version } from '../common/version'; import { GUAService } from './GUAService'; import { TelerikAnalyticsService } from './TelerikAnalyticsService'; import { AnalyticsBaseInfo, OperatingSystem } from './AnalyticsBaseInfo'; -import { ExtensionVersionService } from '../common/ExtensionVersionService'; -import * as ns from '../project/NsCliService'; +import { ExtensionHostServices as Services } from '../services/extensionHostServices'; +import * as utils from '../common/utilities'; export class AnalyticsService { private _baseInfo: AnalyticsBaseInfo; @@ -39,8 +39,8 @@ export class AnalyticsService { }; this._baseInfo = { - cliVersion: ns.CliVersionInfo.getInstalledCliVersion().toString(), - extensionVersion: require('../../../package.json').version, + cliVersion: Services.cli.version.toString(), + extensionVersion: utils.getInstalledExtensionVersion().toString(), operatingSystem: operatingSystem, userId: AnalyticsService.generateMachineId() }; diff --git a/src/common/extensionVersionService.ts b/src/common/extensionVersionService.ts index 77da9d9..ee87139 100644 --- a/src/common/extensionVersionService.ts +++ b/src/common/extensionVersionService.ts @@ -1,6 +1,7 @@ import * as https from 'https'; import * as vscode from 'vscode'; import {Version} from './version'; +import * as utils from './utilities'; export type LatestPublishedVersionCheckResult = {latestPublishedVersion: string, timestamp: number}; @@ -74,7 +75,7 @@ export class ExtensionVersionService { public get isLatestInstalled(): Promise<{ result: boolean, error: string }> { return this.latestPublishedVersion.then(latestVersion => { - let extensionVersion = Version.parse(require('../../package.json').version); + let extensionVersion = utils.getInstalledExtensionVersion(); let isLatest: boolean = extensionVersion.compareBySubminorTo(latestVersion) >= 0; let error = isLatest ? null : `A new version of the NativeScript extension is available. Open "Extensions" panel to update to v${latestVersion}.`; return {result: isLatest, error: error}; diff --git a/src/debug-adapter/utilities.ts b/src/common/utilities.ts similarity index 97% rename from src/debug-adapter/utilities.ts rename to src/common/utilities.ts index aaa8f4c..22fb959 100644 --- a/src/debug-adapter/utilities.ts +++ b/src/common/utilities.ts @@ -9,6 +9,7 @@ import * as os from 'os'; import * as fs from 'fs'; import * as url from 'url'; import * as path from 'path'; +import {Version} from './Version'; export const enum Platform { Windows, OSX, Linux @@ -376,3 +377,11 @@ export function lstrip(s: string, lStr: string): string { s.substr(lStr.length) : s; } + +export function getInstalledExtensionVersion(): Version { + return Version.parse(require('../../package.json').version); +} + +export function getMinSupportedCliVersion(): Version { + return Version.parse(require('../../package.json').minNativescriptCliVersion); +} \ No newline at end of file diff --git a/src/debug-adapter/adapter/adapterProxy.ts b/src/debug-adapter/adapter/adapterProxy.ts index 3685b32..4ac4e3f 100644 --- a/src/debug-adapter/adapter/adapterProxy.ts +++ b/src/debug-adapter/adapter/adapterProxy.ts @@ -2,7 +2,7 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import * as utils from '../utilities'; +import * as utils from '../../common/utilities'; import {DebugAdapterServices as Services} from '../../services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; diff --git a/src/debug-adapter/adapter/pathTransformer.ts b/src/debug-adapter/adapter/pathTransformer.ts index eea1e6a..9e21f99 100644 --- a/src/debug-adapter/adapter/pathTransformer.ts +++ b/src/debug-adapter/adapter/pathTransformer.ts @@ -2,7 +2,7 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import * as utils from '../utilities'; +import * as utils from '../../common/utilities'; import {DebugAdapterServices as Services} from '../../services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; diff --git a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts index 2466316..f04177c 100644 --- a/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts +++ b/src/debug-adapter/adapter/sourceMaps/pathUtilities.ts @@ -7,7 +7,7 @@ import * as Path from 'path'; import * as URL from 'url'; import {DebugAdapterServices as Services} from '../../../services/debugAdapterServices'; -import * as utils from '../../utilities'; +import * as utils from '../../../common/utilities'; export function getPathRoot(p: string) { if (p) { diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts index 3224146..7982736 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMapTransformer.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import {DebugAdapterServices as Services} from '../../../services/debugAdapterServices'; import {DebugProtocol} from 'vscode-debugprotocol'; import {ISourceMaps, SourceMaps} from './sourceMaps'; -import * as utils from '../../utilities'; +import * as utils from '../../../common/utilities'; interface IPendingBreakpoint { resolve: () => void; diff --git a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts index cc8eefd..d608676 100644 --- a/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts +++ b/src/debug-adapter/adapter/sourceMaps/sourceMaps.ts @@ -9,7 +9,7 @@ import * as URL from 'url'; import * as FS from 'fs'; import {SourceMapConsumer} from 'source-map'; import * as PathUtils from './pathUtilities'; -import * as utils from '../../utilities'; +import * as utils from '../../../common/utilities'; import {DebugAdapterServices as Services} from '../../../services/debugAdapterServices'; diff --git a/src/debug-adapter/connection/iosConnection.ts b/src/debug-adapter/connection/iosConnection.ts index 7099207..5fce5cf 100644 --- a/src/debug-adapter/connection/iosConnection.ts +++ b/src/debug-adapter/connection/iosConnection.ts @@ -6,7 +6,7 @@ import * as net from 'net'; import * as stream from 'stream'; import {EventEmitter} from 'events'; import {INSDebugConnection} from './INSDebugConnection'; -import * as utils from '../utilities'; +import * as utils from '../../common/utilities'; import {DebugAdapterServices as Services} from '../../services/debugAdapterServices'; interface IMessageWithId { diff --git a/src/debug-adapter/consoleHelper.ts b/src/debug-adapter/consoleHelper.ts index 82b0052..1adfd4b 100644 --- a/src/debug-adapter/consoleHelper.ts +++ b/src/debug-adapter/consoleHelper.ts @@ -3,7 +3,7 @@ *--------------------------------------------------------*/ import * as url from 'url'; -import * as Utilities from './utilities'; +import * as Utilities from '../common/utilities'; export function formatConsoleMessage(m: WebKitProtocol.Console.Message, isClientPath :boolean = false): { text: string, isError: boolean } { let outputText: string; diff --git a/src/debug-adapter/debugConfiguration.ts b/src/debug-adapter/debugRequest.ts similarity index 52% rename from src/debug-adapter/debugConfiguration.ts rename to src/debug-adapter/debugRequest.ts index 82d5ebd..0413c24 100644 --- a/src/debug-adapter/debugConfiguration.ts +++ b/src/debug-adapter/debugRequest.ts @@ -1,10 +1,17 @@ import {DebugProtocol} from 'vscode-debugprotocol'; +import {Project} from '../project/project'; +import {IosProject} from '../project/iosProject'; +import {AndroidProject} from '../project/androidProject'; +import {DebugAdapterServices as Services} from '../services/debugAdapterServices'; +import {NativeScriptCli} from '../project/nativeScriptCli'; -export class DebugConfiguration { +export class DebugRequest { private _requestArgs: DebugProtocol.IRequestArgs; + private _project: Project; - constructor(requestArgs: DebugProtocol.IRequestArgs) { + constructor(requestArgs: DebugProtocol.IRequestArgs, cli: NativeScriptCli) { this._requestArgs = requestArgs; + this._project = this.isIos ? new IosProject(this.args.appRoot, cli) : new AndroidProject(this.args.appRoot, cli); } public get isLaunch(): boolean { @@ -32,10 +39,22 @@ export class DebugConfiguration { } public get launchArgs(): DebugProtocol.ILaunchRequestArgs { - return this.isLaunch ? this.args : null; + return (this.isLaunch || this.isSync) ? this.args : null; } public get attachArgs(): DebugProtocol.IAttachRequestArgs { return this.isAttach ? this.args : null; } + + public get project(): Project { + return this._project; + } + + public get iosProject(): IosProject { + return this.isIos ? this.project : null; + } + + public get androidProject(): AndroidProject { + return this.isAndroid ? this.project : null; + } } diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index 1900454..06e6229 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -5,18 +5,19 @@ import * as os from 'os'; import * as fs from 'fs'; import * as path from 'path'; -import {spawn, ChildProcess} from 'child_process'; import {Handles, StoppedEvent, InitializedEvent, TerminatedEvent, OutputEvent} from 'vscode-debugadapter'; import {DebugProtocol} from 'vscode-debugprotocol'; import {INSDebugConnection} from './connection/INSDebugConnection'; import {IosConnection} from './connection/iosConnection'; import {AndroidConnection} from './connection/androidConnection'; -import * as utils from './utilities'; +import {Project, DebugResult} from '../project/project'; +import {IosProject} from '../project/iosProject'; +import {AndroidProject} from '../project/androidProject'; +import * as utils from '../common/utilities'; import {formatConsoleMessage} from './consoleHelper'; -import * as ns from '../project/NsCliService'; import {DebugAdapterServices as Services} from '../services/debugAdapterServices'; import {LoggerHandler, Handlers, Tags} from '../common/Logger'; -import {DebugConfiguration} from './debugConfiguration'; +import {DebugRequest} from './debugRequest'; interface IScopeVarHandle { objectId: string; @@ -38,11 +39,9 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { private _setBreakpointsRequestQ: Promise; private _webKitConnection: INSDebugConnection; private _eventHandler: (event: DebugProtocol.Event) => void; - private appRoot: string; - private platform: string; private _lastOutputEvent: OutputEvent; private _loggerFrontendHandler: LoggerHandler = args => this.fireEvent(new OutputEvent(` ›${args.message}\n`, args.type.toString())); - private _debugConfig: DebugConfiguration; + private _request: DebugRequest; public constructor() { this._variableHandles = new Handles(); @@ -51,7 +50,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { Services.logger.addHandler(this._loggerFrontendHandler, [Tags.FrontendMessage]); Services.logger.log(`OS: ${os.platform()} ${os.arch()}`); Services.logger.log('Node version: ' + process.version); - Services.logger.log('Adapter version: ' + require('../../package.json').version); + Services.logger.log('Adapter version: ' + utils.getInstalledExtensionVersion().toString()); this.clearEverything(); } @@ -108,8 +107,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { return this.processRequest(args); } - private configureLoggingForRequest(): void { - let args = this._debugConfig.args; + private configureLoggingForRequest(args: DebugProtocol.IRequestArgs): void { if (args.diagnosticLogging) { // The logger frontend handler is initially configured to handle messages with LoggerTagFrontendMessage tag only. // We remove the handler and add it again for all messages. @@ -120,68 +118,63 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { Services.logger.addHandler(Handlers.createStreamHandler(fs.createWriteStream(args.tnsOutput))); } Services.logger.log(`initialize(${JSON.stringify(this._initArgs) })`); - Services.logger.log(`${this._debugConfig.args.request}(${JSON.stringify(this._debugConfig.isAttach ? this._debugConfig.attachArgs : this._debugConfig.launchArgs) })`); + Services.logger.log(`${args.request}(${JSON.stringify(args)})`); } private processRequest(args: DebugProtocol.IRequestArgs) { - this._debugConfig = new DebugConfiguration(args); + this.configureLoggingForRequest(args); + // Initialize the request Services.appRoot = args.appRoot; - let analyticsRequest = this._debugConfig.isSync ? "sync" : args.request; - Services.extensionClient.analyticsLaunchDebugger({ request: analyticsRequest, platform: args.platform }); - this.configureLoggingForRequest(); - this.appRoot = args.appRoot; - this.platform = args.platform; - - return ((args.platform == 'ios') ? this._attachIos() : this._attachAndroid()) - .then(() => { - this.fireEvent(new InitializedEvent()); - }, - e => { - Services.logger.error("Command failed: " + e, Tags.FrontendMessage); - this.clearEverything(); - return utils.errP(e); - }); - } + Services.cliPath = args.nativescriptCliPath || Services.cliPath; + this._request = new DebugRequest(args, Services.cli); + Services.extensionClient.analyticsLaunchDebugger({ request: this._request.isSync ? "sync" : args.request, platform: args.platform }); + + // Run CLI Command + let cliCommand: DebugResult; + if (this._request.isLaunch) { + cliCommand = this._request.project.debug({ stopOnEntry: this._request.launchArgs.stopOnEntry }, this._request.args.tnsArgs); + } + else if (this._request.isSync) { + cliCommand = this._request.project.debugWithSync({ stopOnEntry: this._request.launchArgs.stopOnEntry, syncAllFiles: this._request.launchArgs.syncAllFiles }, this._request.args.tnsArgs); + } + else if (this._request.isAttach) { + cliCommand = this._request.project.attach(this._request.args.tnsArgs); + } - private _attachIos(): Promise { - let args = this._debugConfig.args; - let iosProject : ns.IosProject = new ns.IosProject(this.appRoot, args.tnsOutput); + if (cliCommand.tnsProcess) { + cliCommand.tnsProcess.stdout.on('data', data => { Services.logger.log(data.toString(), Tags.FrontendMessage); }); + cliCommand.tnsProcess.stderr.on('data', data => { Services.logger.error(data.toString(), Tags.FrontendMessage); }); + cliCommand.tnsProcess.on('close', (code, signal) => { Services.logger.error(`The tns command finished its execution with code ${code}.`, Tags.FrontendMessage); }); + } - return iosProject.debug(args) - .then((socketFilePath) => { - let iosConnection: IosConnection = new IosConnection(); - this.setConnection(iosConnection); - return iosConnection.attach(socketFilePath); + // Attach to the running application + let connectionEstablished = cliCommand.backendIsReadyForConnection.then((connectionToken: string | number) => { + if (this._request.isAndroid) { + Services.logger.log(`Attaching to application on port ${connectionToken}`); + let androidConnection = new AndroidConnection(); + this.setConnection(androidConnection); + let port: number = connectionToken; + return androidConnection.attach(port, 'localhost'); + } + if (this._request.isIos) { + Services.logger.log(`Attaching to application on socket path ${connectionToken}`); + let iosConnection = new IosConnection(); + this.setConnection(iosConnection); + let socketFilePath: string = connectionToken; + return iosConnection.attach(socketFilePath); + } }); - } - - private _attachAndroid(): Promise { - let args = this._debugConfig.args; - let androidProject: ns.AndroidProject = new ns.AndroidProject(this.appRoot, args.tnsOutput); - let thisAdapter: WebKitDebugAdapter = this; - - Services.logger.log("Getting debug port"); - let androidConnection: AndroidConnection = null; - let runDebugCommand: Promise = (args.request == 'launch') ? androidProject.debug(args) : Promise.resolve(); - - return runDebugCommand.then(_ => { - let port: number; - return androidProject.getDebugPort(args).then(debugPort => { - port = debugPort; - if (!thisAdapter._webKitConnection) { - androidConnection = new AndroidConnection(); - this.setConnection(androidConnection); - } - }).then(() => { - Services.logger.log("Attaching to debug application"); - return androidConnection.attach(port, 'localhost'); - }); + // Send InitializedEvent + return connectionEstablished.then(() => this.fireEvent(new InitializedEvent()), e => { + Services.logger.error(`Error: ${e}`, Tags.FrontendMessage); + this.clearEverything(); + return utils.errP(e); }); } private setConnection(connection: INSDebugConnection) : INSDebugConnection { - let args = this._debugConfig.args; + let args = this._request.args; connection.on('Debugger.paused', params => this.onDebuggerPaused(params)); connection.on('Debugger.resumed', () => this.onDebuggerResumed()); connection.on('Debugger.scriptParsed', params => this.onScriptParsed(params)); @@ -308,7 +301,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { let isClientPath = false; if (localMessage.url) { - const clientPath = utils.webkitUrlToClientPath(this.appRoot, this.platform, localMessage.url); + const clientPath = utils.webkitUrlToClientPath(this._request.args.appRoot, this._request.args.platform, localMessage.url); if (clientPath !== '') { localMessage.url = clientPath; isClientPath = true; diff --git a/src/main.ts b/src/main.ts index 7ad3c02..e561700 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,9 @@ import * as vscode from 'vscode'; -import * as child from 'child_process'; -import * as ns from './project/NsCliService'; +import {CliVersion} from './project/nativeScriptCli'; import {ExtensionHostServices as Services} from './services/extensionHostServices'; +import {Project} from './project/project'; +import {IosProject} from './project/iosProject'; +import {AndroidProject} from './project/androidProject'; // this method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { @@ -16,51 +18,49 @@ export function activate(context: vscode.ExtensionContext) { }); // Check if NativeScript CLI is installed globally and if it is compatible with the extension version - let cliInfo: ns.CliVersionInfo = new ns.CliVersionInfo(); - if (cliInfo.getErrorMessage() !== null) { - vscode.window.showErrorMessage(cliInfo.getErrorMessage()); + let cliVersion = Services.cli.version; + if (!cliVersion.isCompatible) { + vscode.window.showErrorMessage(cliVersion.errorMessage); } - let runCommand = (project: ns.NSProject) => { + let 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.platform()}`); + let runChannel: vscode.OutputChannel = vscode.window.createOutputChannel(`Run on ${project.platformName()}`); runChannel.clear(); runChannel.show(vscode.ViewColumn.Two); - Services.analyticsService.runRunCommand(project.platform()); + Services.analyticsService.runRunCommand(project.platformName()); - return project.run() - .then(tnsProcess => { - tnsProcess.on('error', err => { - vscode.window.showErrorMessage('Unexpected error executing NativeScript Run command.'); - }); - tnsProcess.stderr.on('data', data => { - runChannel.append(data.toString()); - }); - tnsProcess.stdout.on('data', data => { - runChannel.append(data.toString()); - }); - tnsProcess.on('exit', exitCode => { - tnsProcess.stdout.removeAllListeners('data'); - tnsProcess.stderr.removeAllListeners('data'); - }); - tnsProcess.on('close', exitCode => { - runChannel.hide(); - }); + let tnsProcess = project.run(); + tnsProcess.on('error', err => { + vscode.window.showErrorMessage('Unexpected error executing NativeScript Run command.'); + }); + tnsProcess.stderr.on('data', data => { + runChannel.append(data.toString()); + }); + tnsProcess.stdout.on('data', data => { + runChannel.append(data.toString()); + }); + tnsProcess.on('exit', exitCode => { + tnsProcess.stdout.removeAllListeners('data'); + tnsProcess.stderr.removeAllListeners('data'); + }); + tnsProcess.on('close', exitCode => { + runChannel.hide(); }); }; let runIosCommand = vscode.commands.registerCommand('nativescript.runIos', () => { - return runCommand(new ns.IosProject(vscode.workspace.rootPath)); + return runCommand(new IosProject(vscode.workspace.rootPath, Services.cli)); }); let runAndroidCommand = vscode.commands.registerCommand('nativescript.runAndroid', () => { - return runCommand(new ns.AndroidProject(vscode.workspace.rootPath)); + return runCommand(new AndroidProject(vscode.workspace.rootPath, Services.cli)); }); context.subscriptions.push(runIosCommand); diff --git a/src/project/NsCliService.ts b/src/project/NsCliService.ts deleted file mode 100644 index 338a913..0000000 --- a/src/project/NsCliService.ts +++ /dev/null @@ -1,361 +0,0 @@ -import {spawn, execSync, ChildProcess} from 'child_process'; -import * as fs from 'fs'; -import {EventEmitter} from 'events'; -import * as path from 'path'; -import * as https from 'https'; -import {Version} from '../common/version'; -import {Handlers, Tags} from '../common/Logger'; -import {Services} from '../services/services'; -import {DebugProtocol} from 'vscode-debugprotocol'; - -export enum CliVersionState { - NotExisting, - OlderThanSupported, - Compatible -} - -export class CliVersionInfo { - private static installedCliVersion: Version = null; - - private _state: CliVersionState; - - public static getInstalledCliVersion(): Version { - if (this.installedCliVersion === null) { - // get the currently installed CLI version - let getVersionCommand: string = new CommandBuilder().appendParam('--version').buildAsString(); // tns --version - try { - let versionStr: string = execSync(getVersionCommand).toString().trim(); // execute it - this.installedCliVersion = versionStr ? Version.parse(versionStr) : null; // parse the version string - } catch(e) { - this.installedCliVersion = null; - } - } - - return this.installedCliVersion; - } - - private static getMinNativeScriptCliVersionSupported(): Version { - return Version.parse(require('../../package.json').minNativescriptCliVersion); - } - - constructor() { - let installedCliVersion: Version = CliVersionInfo.getInstalledCliVersion(); - if (installedCliVersion === null) { - this._state = CliVersionState.NotExisting; - } - else { - let minSupportedCliVersion = CliVersionInfo.getMinNativeScriptCliVersionSupported(); - this._state = installedCliVersion.compareBySubminorTo(minSupportedCliVersion) < 0 ? CliVersionState.OlderThanSupported : CliVersionState.Compatible; - } - } - - public getState(): CliVersionState { - return this._state; - } - - public isCompatible(): boolean { - return this._state === CliVersionState.Compatible; - } - - public getErrorMessage(): string { - switch (this._state) { - case CliVersionState.NotExisting: - return `NativeScript CLI not found, please run 'npm -g install nativescript' to install it.`; - case CliVersionState.OlderThanSupported: - return `The existing NativeScript extension is compatible with NativeScript CLI v${CliVersionInfo.getMinNativeScriptCliVersionSupported()} or greater. The currently installed NativeScript CLI is v${CliVersionInfo.getInstalledCliVersion()}. You can update the NativeScript CLI by executing 'npm install -g nativescript'.`; - default: - return null; - } - } -} - -export abstract class NSProject extends EventEmitter { - private _projectPath: string; - private _cliVersionInfo: CliVersionInfo; - - constructor(projectPath: string, tnsOutputFilePath?: string) { - super(); - this._projectPath = projectPath; - this._cliVersionInfo = new CliVersionInfo(); - } - - public getProjectPath(): string { - return this._projectPath; - } - - public getCliVersionInfo() { - return this._cliVersionInfo; - } - - public abstract platform(): string; - - public abstract run(): Promise; - - public abstract debug(args: DebugProtocol.IRequestArgs): Promise; - - protected spawnProcess(commandPath: string, commandArgs: string[], tnsOutput?: string): ChildProcess { - let options = { cwd: this.getProjectPath(), shell: true }; - let child: ChildProcess = spawn(commandPath, commandArgs, options); - child.stdout.setEncoding('utf8'); - child.stderr.setEncoding('utf8'); - return child; - } -} - -export class IosProject extends NSProject { - - constructor(projectPath: string, tnsOutputFilePath?: string) { - super(projectPath, tnsOutputFilePath); - } - - public platform(): string { - return 'ios'; - } - - public run(): Promise { - if (!this.isOSX()) { - return Promise.reject('iOS platform is only supported on OS X.'); - } - - // build command to execute - let command = new CommandBuilder() - .appendParam("run") - .appendParam(this.platform()) - .build(); - - let child: ChildProcess = this.spawnProcess(command.path, command.args); - return Promise.resolve(child); - } - - public debug(args: DebugProtocol.IRequestArgs): Promise { - if (!this.isOSX()) { - return Promise.reject('iOS platform is supported only on OS X.'); - } - - let rebuild = (args.request == "launch") ? (args as DebugProtocol.ILaunchRequestArgs).rebuild : true; - // build command to execute - let command = new CommandBuilder(args.nativescriptCliPath) - .appendParam("debug") - .appendParam(this.platform()) - .appendParamIf("--start", args.request === "attach") - .appendParamIf("--debug-brk", args.request === "launch" && (args as DebugProtocol.ILaunchRequestArgs).stopOnEntry) - .appendParamIf("--no-rebuild", !rebuild) - .appendParamIf("--syncAllFiles", args.request === "launch" && !rebuild && (args as DebugProtocol.ILaunchRequestArgs).syncAllFiles) - .appendParam("--no-client") - .appendParams(args.tnsArgs) - .build(); - - let socketPathPrefix = 'socket-file-location: '; - let socketPathPattern: RegExp = new RegExp(socketPathPrefix + '.*\.sock'); - - let isSocketOpened = (cliOutput: string): string => { - let matches: RegExpMatchArray = cliOutput.match(socketPathPattern); - if(matches && matches.length > 0) { - return matches[0].substr(socketPathPrefix.length); - } - return null; - }; - - let isAppSynced = (cliOutput: string) => { - return cliOutput.indexOf('Successfully synced application') > -1; - }; - - return new Promise((resolve, reject) => { - // run NativeScript CLI command - let child: ChildProcess = this.spawnProcess(command.path, command.args, args.tnsOutput); - - let appSynced = false; - let socketPath: string = null; - - child.stdout.on('data', (data) => { - let cliOutput: string = data.toString(); - Services.logger.log(cliOutput, Tags.FrontendMessage); - - socketPath = socketPath || isSocketOpened(cliOutput); - appSynced = rebuild ? false : (appSynced || isAppSynced(cliOutput)); - - if ((rebuild && socketPath) || (!rebuild && socketPath && appSynced)) { - resolve(socketPath); - } - }); - - child.stderr.on('data', (data) => { - Services.logger.error(data.toString(), Tags.FrontendMessage); - }); - - child.on('close', (code, signal) => { - reject("The debug process exited unexpectedly code:" + code); - }); - }); - } - - private isOSX(): boolean { - return /^darwin/.test(process.platform); - } -} - -export class AndroidProject extends NSProject { - - constructor(projectPath: string, tnsOutputFilePath?: string) { - super(projectPath, tnsOutputFilePath); - } - - public platform(): string { - return 'android'; - } - - public run(): Promise { - // build command to execute - let command = new CommandBuilder() - .appendParam("run") - .appendParam(this.platform()) - .build(); - - let child: ChildProcess = this.spawnProcess(command.path, command.args); - return Promise.resolve(child); - } - - public debug(params: DebugProtocol.IRequestArgs): Promise { - if (params.request === "attach") { - return Promise.resolve(); - } - else if (params.request === "launch") { - let args: DebugProtocol.ILaunchRequestArgs = params as DebugProtocol.ILaunchRequestArgs; - let that = this; - let launched = false; - - return new Promise((resolve, reject) => { - let command = new CommandBuilder(args.nativescriptCliPath) - .appendParam("debug") - .appendParam(this.platform()) - .appendParamIf("--no-rebuild", args.rebuild !== true) - .appendParamIf("--debug-brk", args.stopOnEntry) - .appendParam("--no-client") - .appendParams(args.tnsArgs) - .build(); - - Services.logger.log("tns debug command: " + command); - - // run NativeScript CLI command - let child: ChildProcess = this.spawnProcess(command.path, command.args, args.tnsOutput); - child.stdout.on('data', function(data) { - let strData: string = data.toString(); - Services.logger.log(data.toString(), Tags.FrontendMessage); - if (!launched) { - if (args.request === "launch" && strData.indexOf('# NativeScript Debugger started #') > -1) { - launched = true; - //wait a little before trying to connect, this gives a changes for adb to be able to connect to the debug socket - setTimeout(() => { - resolve(); - }, 500); - } - } - }); - - child.stderr.on('data', function(data) { - Services.logger.error(data.toString(), Tags.FrontendMessage); - }); - - child.on('close', function(code) { - if (!args.rebuild) { - setTimeout(() => { - reject("The debug process exited unexpectedly code:" + code); - }, 3000); - } - else { - reject("The debug process exited unexpectedly code:" + code); - } - }); - }); - } - } - - public getDebugPort(args: DebugProtocol.IRequestArgs): Promise { - //TODO: Call CLI to get the debug port - //return Promise.resolve(40001); - - //return Promise.resolve(40001); - - let command = new CommandBuilder(args.nativescriptCliPath) - .appendParam("debug") - .appendParam(this.platform()) - .appendParam("--get-port") - .appendParams(args.tnsArgs) - .build(); - let that = this; - // run NativeScript CLI command - return new Promise((resolve, reject) => { - let child: ChildProcess = this.spawnProcess(command.path, command.args, args.tnsOutput); - - child.stdout.on('data', function(data) { - Services.logger.log(data.toString(), Tags.FrontendMessage); - - let regexp = new RegExp("(?:debug port: )([\\d]{5})"); - - //for the new output - // var input = "device: 030b258308e6ce89 debug port: 40001"; - - let portNumberMatch = null; - let match = data.toString().match(regexp); - if (match) - { - portNumberMatch = match[1]; - } - - if (portNumberMatch) { - Services.logger.log("port number match '" + portNumberMatch + "'"); - let portNumber = parseInt(portNumberMatch); - if (portNumber) { - Services.logger.log("port number " + portNumber); - child.stdout.removeAllListeners('data'); - resolve(portNumber); - } - } - }); - - child.stderr.on('data', function(data) { - Services.logger.error(data.toString(), Tags.FrontendMessage); - }); - - child.on('close', function(code) { - reject("Getting debug port failed with code: " + code); - }); - }); - } -} - -class CommandBuilder { - - private _tnsPath: string; - private _command: string[] = []; - - constructor(tnsPath?: string) { - this._tnsPath = tnsPath || "tns"; - } - - public appendParam(parameter: string): CommandBuilder { - this._command.push(parameter); - return this; - } - - public appendParams(parameters: string[] = []): CommandBuilder { - parameters.forEach(param => this.appendParam(param)); - return this; - } - - public appendParamIf(parameter: string, condtion: boolean): CommandBuilder { - if (condtion) { - this._command.push(parameter); - } - return this; - } - - public build(): { path: string, args: string[] } { - return { path: this._tnsPath, args: this._command }; - } - - public buildAsString(): string { - let result = this.build(); - return `${result.path} ` + result.args.join(' '); - } -} diff --git a/src/project/androidProject.ts b/src/project/androidProject.ts new file mode 100644 index 0000000..c1660d8 --- /dev/null +++ b/src/project/androidProject.ts @@ -0,0 +1,60 @@ +import {ChildProcess} from 'child_process'; +import * as stream from 'stream'; +import {Project, DebugResult} 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 { + + constructor(appRoot: string, cli: NativeScriptCli) { + super(appRoot, cli); + } + + public platformName(): string { + return "android"; + } + + public attach(tnsArgs?: string[]): DebugResult { + return { tnsProcess: null, backendIsReadyForConnection: this.getDebugPort().debugPort }; + } + + public debugWithSync(options: { stopOnEntry: boolean, syncAllFiles: boolean }, tnsArgs?: string[]): DebugResult { + let args: string[] = ["--no-rebuild"]; + if (options.syncAllFiles) { args.push("--syncAllFiles"); } + args = args.concat(tnsArgs); + + return this.debug({stopOnEntry: options.stopOnEntry}, args); + } + + public debug(options: { stopOnEntry: boolean }, tnsArgs?: string[]): DebugResult { + let args: string[] = []; + if (options.stopOnEntry) { args.push("--debug-brk"); } + args = args.concat(tnsArgs); + + let debugProcess : ChildProcess = super.executeDebugCommand(args); + let backendIsReady = new scanner.StringMatchingScanner(debugProcess.stdout).nextMatch('# NativeScript Debugger started #').then(_ => { + // wait a little before trying to connect, this gives a chance for adb to be able to connect to the debug socket + return new Promise((resolve, reject) => setTimeout(() => { resolve(); }, 500)); + }).then(() => this.getDebugPort().debugPort); + + return { tnsProcess: debugProcess, backendIsReadyForConnection: backendIsReady }; + } + + private getDebugPort(): GetDebugPortResult { + let debugProcess : ChildProcess = super.executeDebugCommand(["--get-port"]); + let portNumberPromise: Promise = this.waitForPortNumber(debugProcess.stdout); + return { tnsProcess: debugProcess, debugPort: portNumberPromise }; + } + + private waitForPortNumber(readableStream: stream.Readable): Promise { + let streamScanner = new scanner.StringMatchingScanner(readableStream); + return streamScanner.nextMatch(new RegExp("(?:debug port: )([\\d]{5})")).then((match: scanner.MatchFound) => { + let portNumber = parseInt(match.matches[1]); + streamScanner.stop(); + return portNumber; + }); + } +} diff --git a/src/project/iosProject.ts b/src/project/iosProject.ts new file mode 100644 index 0000000..4de4849 --- /dev/null +++ b/src/project/iosProject.ts @@ -0,0 +1,63 @@ +import {ChildProcess} from 'child_process'; +import * as stream from 'stream'; +import {Project, DebugResult} from './project'; +import * as scanner from './streamScanner'; +import {Version} from '../common/version'; +import {NativeScriptCli} from './NativeScriptCli'; + +export class IosProject extends Project { + + constructor(appRoot: string, cli: NativeScriptCli) { + super(appRoot, cli); + + if (!this.isPlatformOSX()) { + throw new Error('iOS platform is supported only on OS X.'); + } + } + + public platformName(): string { + return "ios"; + } + + public attach(tnsArgs?: string[]): DebugResult { + let args: string[] = ["--start"]; + args = args.concat(tnsArgs); + + let debugProcess : ChildProcess = super.executeDebugCommand(args); + let socketPathPromise = this.waitForSocketPath(debugProcess.stdout); + return { tnsProcess: debugProcess, backendIsReadyForConnection: socketPathPromise }; + } + + public debugWithSync(options: { stopOnEntry: boolean, syncAllFiles: boolean }, tnsArgs?: string[]): DebugResult { + let args: string[] = ["--no-rebuild"]; + if (options.syncAllFiles) { args.push("--syncAllFiles"); } + args = args.concat(tnsArgs); + + return this.debug({stopOnEntry: options.stopOnEntry}, args); + } + + public debug(options: { stopOnEntry: boolean }, tnsArgs?: string[]): DebugResult { + let args: string[] = []; + if (options.stopOnEntry) { args.push("--debug-brk"); } + args = args.concat(tnsArgs); + + let debugProcess : ChildProcess = super.executeDebugCommand(args); + let socketPathPromise = this.waitForSocketPath(debugProcess.stdout); + return { tnsProcess: debugProcess, backendIsReadyForConnection: socketPathPromise }; + } + + private waitForSocketPath(readableStream: stream.Readable): Promise { + let socketPathPrefix = 'socket-file-location: '; + let streamScanner = new scanner.StringMatchingScanner(readableStream); + + return streamScanner.nextMatch(new RegExp(socketPathPrefix + '.*\.sock')).then((match: scanner.MatchFound) => { + let socketPath = (match.matches[0]).substr(socketPathPrefix.length); + streamScanner.stop(); + return socketPath; + }); + } + + private isPlatformOSX(): boolean { + return /^darwin/.test(process.platform); + } +} diff --git a/src/project/nativeScriptCli.ts b/src/project/nativeScriptCli.ts new file mode 100644 index 0000000..b5e61ee --- /dev/null +++ b/src/project/nativeScriptCli.ts @@ -0,0 +1,82 @@ +import {spawn, execSync, ChildProcess} from 'child_process'; +import {Version} from '../common/version'; +import {Services} from '../services/services'; +import * as utils from '../common/utilities'; + +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 _path: string; + private _cliVersion: CliVersion; + + constructor(cliPath: string) { + this._path = cliPath; + + let versionStr = this.executeSync(["--version"], undefined); + 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); + } + } + + public get path(): string { return this._path; } + + public get version(): CliVersion { + return this._cliVersion; + } + + public executeSync(args: string[], cwd: string): string { + let command: string = `${this._path} ` + args.join(' '); + Services.logger.log(`[NativeScriptCli] execute sync: ${command}`); + return execSync(command, { encoding: "utf8", cwd: cwd }).toString().trim(); + } + + public execute(args: string[], cwd: string): ChildProcess { + let command: string = `${this._path} ` + args.join(' '); + Services.logger.log(`[NativeScriptCli] execute async: ${command}`); + + let options = { cwd: cwd, shell: true }; + let child: ChildProcess = spawn(this._path, args, options); + child.stdout.setEncoding('utf8'); + child.stderr.setEncoding('utf8'); + return child; + } +} diff --git a/src/project/project.ts b/src/project/project.ts new file mode 100644 index 0000000..426ff3c --- /dev/null +++ b/src/project/project.ts @@ -0,0 +1,39 @@ +import {ChildProcess} from 'child_process'; +import {Version} from '../common/version'; +import {NativeScriptCli} from './nativeScriptCli'; + +export type DebugResult = { tnsProcess: ChildProcess, backendIsReadyForConnection: Promise }; + +export abstract class Project { + private _appRoot: string; + private _cli: NativeScriptCli; + + constructor(appRoot: string, cli: NativeScriptCli) { + this._appRoot = appRoot; + this._cli = cli; + } + + public get rootPath(): string { return this._appRoot; } + + public get cli(): NativeScriptCli { return this._cli; } + + public abstract platformName(): string; + + public run(tnsArgs?: string[]): ChildProcess { + return this.executeRunCommand(tnsArgs); + } + + public abstract attach(tnsArgs?: string[]): DebugResult; + + public abstract debugWithSync(options: { stopOnEntry: boolean, syncAllFiles: boolean }, tnsArgs?: string[]): DebugResult; + + public abstract debug(options: { stopOnEntry: boolean }, tnsArgs?: string[]): DebugResult; + + protected executeRunCommand(args: string[]): ChildProcess { + return this.cli.execute(["run", this.platformName()].concat(args), this._appRoot); + } + + protected executeDebugCommand(args: string[]): ChildProcess { + return this.cli.execute(["debug", this.platformName(), "--no-client"].concat(args), this._appRoot); + } +} diff --git a/src/project/streamScanner.ts b/src/project/streamScanner.ts new file mode 100644 index 0000000..9fc0b1f --- /dev/null +++ b/src/project/streamScanner.ts @@ -0,0 +1,84 @@ +import * as stream from 'stream'; + +export class StreamScanner { + private _stream: stream.Readable; + private _scanCallback: (data: string, stop: () => void) => void; + + constructor(stream: stream.Readable, scanCallback: (data: string, stop: () => void) => void) { + this._stream = stream; + this._scanCallback = scanCallback; + this._stream.on("data", this.scan.bind(this)); + } + + public stop() { + this._stream.removeListener("data", this.scan); + } + + private scan(data: string | Buffer): void { + this._scanCallback(data.toString(), this.stop); + } +} + +export type MatchFound = { + chunk: string, + matches: RegExpMatchArray | number[] +}; + +type MatchMeta = { + promise: Promise, + resolve: (match: MatchFound) => void, + reject: (err: Error) => void, + test: string | RegExp +}; + +export class StringMatchingScanner extends StreamScanner { + private _metas: MatchMeta[]; + + constructor(stream: stream.Readable) { + super(stream, (data: string, stop: () => void) => { + this._metas.forEach((meta, metaIndex) => { + if (meta.test instanceof RegExp) { + let result: RegExpMatchArray = data.match(meta.test); + 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) { + result.push(dataIndex); + } + if (result.length > 0) { + this.matchFound(metaIndex, { chunk: data, matches: result }); + } + } + else { + throw new TypeError("Invalid type"); + } + }); + }); + this._metas = []; + } + + public nextMatch(test: string | RegExp): Promise { + let meta: MatchMeta = { + test: test, + resolve: null, + reject: null, + promise: null + }; + meta.promise = new Promise((resolve, reject) => { + meta.resolve = resolve; + meta.reject = reject; + }); + 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 + meta.resolve(matchResult); + } +} diff --git a/src/services/debugAdapterServices.ts b/src/services/debugAdapterServices.ts index e6201e5..0ba5927 100644 --- a/src/services/debugAdapterServices.ts +++ b/src/services/debugAdapterServices.ts @@ -3,8 +3,8 @@ import {ExtensionClient} from '../ipc/extensionClient'; import {Logger} from '../common/Logger'; export class DebugAdapterServices extends Services { - private static _appRoot: string; private static _extensionClient: ExtensionClient; + private static _appRoot: string; public static get appRoot(): string { return this._appRoot; } diff --git a/src/services/services.ts b/src/services/services.ts index 9347cf3..d1701c3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,10 +1,23 @@ import {Logger} from '../common/logger'; +import {NativeScriptCli} from '../project/nativeScriptCli'; export class Services { - private static _logger: Logger; + protected static _cliPath: string = "tns"; + + protected static _logger: Logger; + protected static _cli: NativeScriptCli; + + public static get cliPath(): string { return this._cliPath; } + + public static set cliPath(cliPath: string) { this._cliPath = cliPath; } public static get logger(): Logger { this._logger = this._logger || new Logger(); return this._logger; } + + public static get cli(): NativeScriptCli { + this._cli = this._cli || new NativeScriptCli(this._cliPath); + return this._cli; + } }