diff --git a/PublicAPI.md b/PublicAPI.md index 066640eb0d..3fdbba0e30 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -72,6 +72,16 @@ const tns = require("nativescript"); * [getPlaygroundAppQrCode](#getplaygroundappqrcode) * [cleanupService](#cleanupservice) * [setCleanupLogFile](#setcleanuplogfile) +* [initializeService](#initializeService) + * [initialize](#initialize) +* [logger](#logger) + * [initialize](#initialize) + * [getLevel](#getlevel) + * [appenders](#appenders) + * [emit-appender](#emit-appender) + * [cli-appender](#cli-appender) + * [custom layouts](#custom-layouts) + ## Module projectService @@ -1514,6 +1524,176 @@ const tns = require("nativescript"); tns.cleanupService.setCleanupLogFile("/Users/username/cleanup-logs.txt"); ``` +## initializeService +The `initializeService` is used to initialize CLI's configuration at the beginning and print all warnings related to current environment. + +### initialize +This method executes initialization actions based on the passed parameters. In case `loggerOptions` are not passed, the default CLI logger will be used. +After initialization, the method will print all system warnings. + +* Definition +```TypeScript +interface IInitializeOptions { + loggerOptions?: ILoggerOptions; + settingsServiceOptions?: IConfigurationSettings; + extensibilityOptions?: { pathToExtensions: string }; +} + +interface IInitializeService { + initialize(initOpts?: IInitializeOptions): Promise; +} + +``` + +> NOTE: For more information about loggerOptions, you can check `logger`. + +* Usage + * Initialization without passing any data - `logger` will be initialized with default CLI settings. Warnings will be printed if there are any. + ```JavaScript + const tns = require("nativescript"); + tns.initializeService.initialize(); + ``` + * Initialize with custom settings service options: + ```JavaScript + const tns = require("nativescript"); + tns.initializeService.initialize({ settingsServiceOptions: { profileDir: "/Users/username/customDir", userAgentName: "MyApp" } }); + ``` + * Initialize with custom extensibility path: + ```JavaScript + const tns = require("nativescript"); + tns.initializeService.initialize({ extensibilityOptions: { pathToExtensions: "/Users/username/customDir/extensions" } }); + ``` + +## logger + +`logger` module is used to show any kind of information to the user. The `logger` uses `log4js` internally, which allows setting different levels for the messages. +The levels are available in `tns.constants.LoggerLevel` enum. Only messages from the current log level (or higher) are shown to the user, i.e. in case the log level is set to `INFO`, `DEBUG` and `TRACE` messages will not be shown to the user, but `WARN` and `ERROR` messages will be shown.
+`logger` module can be configured how to show the messages by using different appenders and layouts.
+* `appenders` are responsible for output of log events. They may write events to files, send emails, store them in a database, or anything. Most appenders use layouts to serialise the events to strings for output. +* `layout` is a function for converting a LogEvent into a string representation. + +`log4js` has predefined appenders and layouts that can be used. In case you do not pass any options to logger's initialization, CLI will default to [console appender](https://log4js-node.github.io/log4js-node/console.html) with [messagePassThrough layout](https://log4js-node.github.io/log4js-node/layouts.html#message-pass-through) with `INFO` log level.
+You can override only the properties you want, i.e. only the log level, the layout or the appender.
+`nativescript` itself has additional appenders that you can use. More information about them can be found below. You can get a full list of the available appenders by checking the `tns.constants.LoggerAppenders` object.
+ +> NOTE: When CLI is used as a command-line tool, it uses a custom appender and layout in order to write coloured messages to stdout or stderr. + +### initialize +The `initialize` method initializes the log4js settings - level, appender and layout. Once called, the settings cannot be changed anymore for the current process. + +* Definition +```TypeScript +interface IAppenderOptions extends IDictionary { + type: string; + layout?: Layout; +} + +interface ILoggerOptions { + level?: LoggerLevel; + appenderOptions?: IAppenderOptions; +} + +initialize(opts?: ILoggerOptions): void; +``` + +* Usage + * Initialize with default settings: + ```JavaScript + tns.logger.initialize(); + ``` + * Initialize with DEBUG log level: + ```JavaScript + tns.logger.initialize({ level: tns.constants.LoggerLevel.DEBUG }); + ``` + * Initialize with different appender, for example [fileSync](https://log4js-node.github.io/log4js-node/fileSync.html) appender: + ```JavaScript + tns.logger.initialize({ appenderOptions: { type: "fileSync" } }); + ``` + * Initialize with different layout, for example [Pattern](https://log4js-node.github.io/log4js-node/layouts.html#pattern) layout: + ```JavaScript + tns.logger.initialize({ appenderOptions: { layout: { type: "pattern" } } }); + ``` + * Initialize with custom appender, layout and level: + ```JavaScript + tns.logger.initialize({ appenderOptions: { type: "fileSync", layout: { type: "pattern" } }, level: tns.constants.LoggerLevel.DEBUG }); + ``` + +### getLevel +This method returns information for the current log level. + +* Definition +```TypeScript +getLevel(): string; +``` + +* Usage +```JavaScript +console.log(`Current log level is: ${tns.logger.getLevel()}`); +``` + +### appenders +The `appenders` are log4js concept. `appenders` are responsible for output of log events. You can use all predefined [log4js appenders](https://log4js-node.github.io/log4js-node/appenders.html) and also several predefined CLI appenders + +#### emit-appender +The `emit-appender` is used to emit the log events through a passed emitter instead of writing the messages. Whenever a message should be shown, the `emit-appender` emits `logData` event with an object containing the `loggingEvent` and the message passed through the specified layout stored in `formattedMessage` property. + +* Usage: +```JavaScript +const tns = require("nativescript"); +const { EventEmitter } = require("events"); +const { EMIT_APPENDER_EVENT_NAME, LoggerAppenders } = tns.constants; +const emitter = new EventEmitter(); +// IMPORTANT: Add the event handler before calling logger's initialize method. +// This is required as log4js makes a copy of the appenderOptions, where the emitter is passed +// NOTE: In case you want to debug the event handler, place `debugger` in it. +emitter.on(EMIT_APPENDER_EVENT_NAME, (logData) => { + if (logData.loggingEvent.level.levelStr === LoggerLevel.WARN) { + console.log(`WARNING: ${logData.formattedMessage}`); + } +}); + +const logger = tns.logger; +logger.initialize({ + appenderOptions: { + type: LoggerAppenders.emitAppender, + emitter + } +}); +``` + +> NOTE: In several cases CLI passes additional configuration properties in the `context` of the `loggingEvent`. Full list is available in the `tns.constants.LoggerConfigData` object. These properties are used by CLI's layout and appender to change the way the message is printed on the terminal and if it should be on stderr or stdout. + +#### cli-appender +`cli-appender` prints messages to stdout or stderr based on the passed options for the message. + +* Usage +```JavaScript +const tns = require("nativescript"); +const { EventEmitter } = require("events"); +const { EMIT_APPENDER_EVENT_NAME, LoggerAppenders } = tns.constants; + +const logger = tns.logger; +logger.initialize({ + appenderOptions: { + type: LoggerAppenders.cliAppender, + } +}); +``` + +### custom layouts +You can define your own layout function in the following way: +```JavaScript +const log4js = require("nativescript/node_modules/log4js"); +const util = require("util"); +log4js.addLayout("myCustomLayout", (config) => { + return (loggingEvent) => { + return util.format.apply(null, loggingEvent.data); + } +}); + +tns.logger.initialize({ appenderOptions: { layout: { type: "myCustomLayout" } } }); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index d99ca32d44..3b77b4214b 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -1,5 +1,5 @@ require("./common/bootstrap"); -$injector.require("logger", "./common/logger"); +$injector.requirePublicClass("logger", "./common/logger/logger"); $injector.require("config", "./config"); $injector.require("options", "./options"); // note: order above is important! @@ -109,7 +109,7 @@ $injector.require("xcprojService", "./services/xcproj-service"); $injector.require("versionsService", "./services/versions-service"); $injector.requireCommand("install", "./commands/install"); -$injector.require("initService", "./services/init-service"); +$injector.require("projectInitService", "./services/project-init-service"); $injector.requireCommand("init", "./commands/init"); $injector.require("infoService", "./services/info-service"); @@ -199,3 +199,5 @@ $injector.requirePublic("cleanupService", "./services/cleanup-service"); $injector.require("applePortalSessionService", "./services/apple-portal/apple-portal-session-service"); $injector.require("applePortalCookieService", "./services/apple-portal/apple-portal-cookie-service"); $injector.require("applePortalApplicationService", "./services/apple-portal/apple-portal-application-service"); + +$injector.requirePublicClass("initializeService", "./services/initialize-service"); diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 40391c7047..aadc7e665b 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -34,14 +34,14 @@ export class ListiOSApps implements ICommand { const applications = await this.$applePortalApplicationService.getApplications({ username, password }); if (!applications || !applications.length) { - this.$logger.out("Seems you don't have any applications yet."); + this.$logger.info("Seems you don't have any applications yet."); } else { const table: any = createTable(["Application Name", "Bundle Identifier", "In Flight Version"], applications.map(application => { const version = (application && application.versionSets && application.versionSets.length && application.versionSets[0].inFlightVersion && application.versionSets[0].inFlightVersion.version) || ""; return [application.name, application.bundleId, version]; })); - this.$logger.out(table.toString()); + this.$logger.info(table.toString()); } } } diff --git a/lib/commands/extensibility/list-extensions.ts b/lib/commands/extensibility/list-extensions.ts index aabc3f9c13..d3a1761bc7 100644 --- a/lib/commands/extensibility/list-extensions.ts +++ b/lib/commands/extensibility/list-extensions.ts @@ -13,7 +13,7 @@ export class ListExtensionsCommand implements ICommand { }); const table = helpers.createTable(["Name", "Version"], data); - this.$logger.out(table.toString()); + this.$logger.info(table.toString()); } else { this.$logger.info("No extensions installed."); } diff --git a/lib/commands/init.ts b/lib/commands/init.ts index a704c25022..36984c0dd6 100644 --- a/lib/commands/init.ts +++ b/lib/commands/init.ts @@ -1,12 +1,12 @@ -export class InitCommand implements ICommand { +export class ProjectInitCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; public enableHooks = false; - constructor(private $initService: IInitService) { } + constructor(private $projectInitService: IProjectInitService) { } public async execute(args: string[]): Promise { - return this.$initService.initialize(); + return this.$projectInitService.initialize(); } } -$injector.registerCommand("init", InitCommand); +$injector.registerCommand("init", ProjectInitCommand); diff --git a/lib/commands/list-platforms.ts b/lib/commands/list-platforms.ts index 7210609be8..bd450f52b9 100644 --- a/lib/commands/list-platforms.ts +++ b/lib/commands/list-platforms.ts @@ -15,16 +15,16 @@ export class ListPlatformsCommand implements ICommand { if (installedPlatforms.length > 0) { const preparedPlatforms = this.$platformService.getPreparedPlatforms(this.$projectData); if (preparedPlatforms.length > 0) { - this.$logger.out("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and")); + this.$logger.info("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and")); } else { - this.$logger.out("The project is not prepared for any platform"); + this.$logger.info("The project is not prepared for any platform"); } - this.$logger.out("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and")); + this.$logger.info("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and")); } else { const formattedPlatformsList = helpers.formatListOfNames(this.$platformService.getAvailablePlatforms(this.$projectData), "and"); - this.$logger.out("Available platforms for this OS: ", formattedPlatformsList); - this.$logger.out("No installed platforms found. Use $ tns platform add"); + this.$logger.info("Available platforms for this OS: ", formattedPlatformsList); + this.$logger.info("No installed platforms found. Use $ tns platform add"); } } } diff --git a/lib/commands/plugin/list-plugins.ts b/lib/commands/plugin/list-plugins.ts index f9afa9c316..ac91549e79 100644 --- a/lib/commands/plugin/list-plugins.ts +++ b/lib/commands/plugin/list-plugins.ts @@ -16,18 +16,18 @@ export class ListPluginsCommand implements ICommand { const dependenciesData: string[][] = this.createTableCells(installedPlugins.dependencies); const dependenciesTable: any = createTable(headers, dependenciesData); - this.$logger.out("Dependencies:"); - this.$logger.out(dependenciesTable.toString()); + this.$logger.info("Dependencies:"); + this.$logger.info(dependenciesTable.toString()); if (installedPlugins.devDependencies && installedPlugins.devDependencies.length) { const devDependenciesData: string[][] = this.createTableCells(installedPlugins.devDependencies); const devDependenciesTable: any = createTable(headers, devDependenciesData); - this.$logger.out("Dev Dependencies:"); - this.$logger.out(devDependenciesTable.toString()); + this.$logger.info("Dev Dependencies:"); + this.$logger.info(devDependenciesTable.toString()); } else { - this.$logger.out("There are no dev dependencies."); + this.$logger.info("There are no dev dependencies."); } const viewDependenciesCommand: string = "npm view grep dependencies".cyan.toString(); diff --git a/lib/commands/post-install.ts b/lib/commands/post-install.ts index f9539d84f3..aa07aee4f0 100644 --- a/lib/commands/post-install.ts +++ b/lib/commands/post-install.ts @@ -38,7 +38,7 @@ export class PostInstallCliCommand implements ICommand { } // Make sure the success message is separated with at least one line from all other messages. - this.$logger.out(); + this.$logger.info(); this.$logger.printMarkdown("Installation successful. You are good to go. Connect with us on `http://twitter.com/NativeScript`."); if (canExecutePostInstallTask) { diff --git a/lib/common/bootstrap.ts b/lib/common/bootstrap.ts index 2aa2c255c8..d5276f5ae4 100644 --- a/lib/common/bootstrap.ts +++ b/lib/common/bootstrap.ts @@ -27,7 +27,6 @@ $injector.require("childProcess", "./child-process"); $injector.require("prompter", "./prompter"); $injector.require("projectHelper", "./project-helper"); $injector.require("pluginVariablesHelper", "./plugin-variables-helper"); -$injector.require("progressIndicator", "./progress-indicator"); $injector.requireCommand(["help", "/?"], "./commands/help"); $injector.requireCommand("usage-reporting", "./commands/analytics"); diff --git a/lib/common/commands/analytics.ts b/lib/common/commands/analytics.ts index 52e9d10871..bc35003138 100644 --- a/lib/common/commands/analytics.ts +++ b/lib/common/commands/analytics.ts @@ -41,7 +41,7 @@ class AnalyticsCommand implements ICommand { break; case "status": case "": - this.$logger.out(await this.$analyticsService.getStatusMessage(this.settingName, this.$options.json, this.humanReadableSettingName)); + this.$logger.info(await this.$analyticsService.getStatusMessage(this.settingName, this.$options.json, this.humanReadableSettingName)); break; } } diff --git a/lib/common/commands/autocompletion.ts b/lib/common/commands/autocompletion.ts index 1bc11bb526..40c1efb0cd 100644 --- a/lib/common/commands/autocompletion.ts +++ b/lib/common/commands/autocompletion.ts @@ -19,7 +19,7 @@ export class AutoCompleteCommand implements ICommand { this.$logger.info("Autocompletion is already enabled"); } } else { - this.$logger.out("If you are using bash or zsh, you can enable command-line completion."); + this.$logger.info("If you are using bash or zsh, you can enable command-line completion."); const message = "Do you want to enable it now?"; const autoCompetionStatus = await this.$prompter.confirm(message, () => true); diff --git a/lib/common/commands/device/list-applications.ts b/lib/common/commands/device/list-applications.ts index 5c9fe66513..f90fa36125 100644 --- a/lib/common/commands/device/list-applications.ts +++ b/lib/common/commands/device/list-applications.ts @@ -19,7 +19,7 @@ export class ListApplicationsCommand implements ICommand { }; await this.$devicesService.execute(action); - this.$logger.out(output.join(EOL)); + this.$logger.info(output.join(EOL)); } } $injector.registerCommand(["device|list-applications", "devices|list-applications"], ListApplicationsCommand); diff --git a/lib/common/commands/device/list-devices.ts b/lib/common/commands/device/list-devices.ts index 3601650592..1e49785fac 100644 --- a/lib/common/commands/device/list-devices.ts +++ b/lib/common/commands/device/list-devices.ts @@ -23,7 +23,7 @@ export class ListDevicesCommand implements ICommand { this.printEmulators("\nAvailable emulators", emulators); } - this.$logger.out("\nConnected devices & emulators"); + this.$logger.info("\nConnected devices & emulators"); let index = 1; await this.$devicesService.initialize({ platform: args[0], deviceId: null, skipInferPlatform: true, skipDeviceDetectionInterval: true, skipEmulatorStart: true }); @@ -31,7 +31,7 @@ export class ListDevicesCommand implements ICommand { let action: (_device: Mobile.IDevice) => Promise; if (this.$options.json) { action = async (device) => { - this.$logger.out(JSON.stringify(device.deviceInfo)); + this.$logger.info(JSON.stringify(device.deviceInfo)); }; } else { action = async (device) => { @@ -44,18 +44,18 @@ export class ListDevicesCommand implements ICommand { await this.$devicesService.execute(action, undefined, { allowNoDevices: true }); if (!this.$options.json && table.length) { - this.$logger.out(table.toString()); + this.$logger.info(table.toString()); } } private printEmulators(title: string, emulators: Mobile.IDeviceInfo[]) { - this.$logger.out(title); + this.$logger.info(title); const table: any = createTable(["Device Name", "Platform", "Version", "Device Identifier", "Image Identifier", "Error Help"], []); for (const info of emulators) { table.push([info.displayName, info.platform, info.version, info.identifier || "", info.imageIdentifier || "", info.errorHelp || ""]); } - this.$logger.out(table.toString()); + this.$logger.info(table.toString()); } } diff --git a/lib/common/commands/proxy/proxy-clear.ts b/lib/common/commands/proxy/proxy-clear.ts index 859e491ab0..78e0a8523d 100644 --- a/lib/common/commands/proxy/proxy-clear.ts +++ b/lib/common/commands/proxy/proxy-clear.ts @@ -10,7 +10,7 @@ export class ProxyClearCommand extends ProxyCommandBase { public async execute(args: string[]): Promise { await this.$proxyService.clearCache(); - this.$logger.out("Successfully cleared proxy."); + this.$logger.info("Successfully cleared proxy."); await this.tryTrackUsage(); } } diff --git a/lib/common/commands/proxy/proxy-get.ts b/lib/common/commands/proxy/proxy-get.ts index 8b376dc8ac..4f49e891e8 100644 --- a/lib/common/commands/proxy/proxy-get.ts +++ b/lib/common/commands/proxy/proxy-get.ts @@ -10,7 +10,7 @@ export class ProxyGetCommand extends ProxyCommandBase { } public async execute(args: string[]): Promise { - this.$logger.out(await this.$proxyService.getInfo()); + this.$logger.info(await this.$proxyService.getInfo()); await this.tryTrackUsage(); } } diff --git a/lib/common/commands/proxy/proxy-set.ts b/lib/common/commands/proxy/proxy-set.ts index 8bbfaab1d0..461d26c8ff 100644 --- a/lib/common/commands/proxy/proxy-set.ts +++ b/lib/common/commands/proxy/proxy-set.ts @@ -107,8 +107,8 @@ export class ProxySetCommand extends ProxyCommandBase { this.$logger.warn(`${messageNote}Run '${clientName} proxy set --help' for more information.`); await this.$proxyService.setCache(settings); - this.$logger.out(`Successfully setup proxy.${EOL}`); - this.$logger.out(await this.$proxyService.getInfo()); + this.$logger.info(`Successfully setup proxy.${EOL}`); + this.$logger.info(await this.$proxyService.getInfo()); await this.tryTrackUsage(); } diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 823adfe9fc..7959c0ad8f 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -1281,20 +1281,6 @@ interface IServiceContractGenerator { generate(definitionsPath?: string): Promise; } -/** - * Used to show indication that a process is running - */ -interface IProgressIndicator { - /** - * Prints indication that a process is running - * @param {Promise} promise process - * @param {number} timeout time interval for printing indication - * @param {boolean} options whether to surpress the trailing new line printed after the process ends - * @return {Promise} - */ - showProgressIndicator(promise: Promise, timeout: number, options?: { surpressTrailingNewLine?: boolean }): Promise; -} - /** * Describes project file that should be livesynced */ diff --git a/lib/common/definitions/extensibility.d.ts b/lib/common/definitions/extensibility.d.ts index 778b36d24d..cf7c261f40 100644 --- a/lib/common/definitions/extensibility.d.ts +++ b/lib/common/definitions/extensibility.d.ts @@ -130,6 +130,11 @@ interface IExtensibilityService { * @returns {IExtensionCommandInfo} Information about the extension and the registered command. */ getExtensionNameWhereCommandIsRegistered(inputOpts: IGetExtensionCommandInfoParams): Promise; + + /** + * Defines the path where CLI will search for extensions. + */ + pathToExtensions: string; } /** diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index a719108d6c..6077c31d7d 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -1,19 +1,37 @@ -interface ILogger { - getLevel(): string; - fatal(formatStr?: any, ...args: any[]): void; - error(formatStr?: any, ...args: any[]): void; - warn(formatStr?: any, ...args: any[]): void; - warnWithLabel(formatStr?: any, ...args: any[]): void; - info(formatStr?: any, ...args: any[]): void; - debug(formatStr?: any, ...args: any[]): void; - trace(formatStr?: any, ...args: any[]): void; - printMarkdown(...args: any[]): void; +import { Layout, LoggingEvent, Configuration, Level } from "log4js"; +import { EventEmitter } from "events"; +import { LoggerLevel } from "../../constants"; - out(formatStr?: any, ...args: any[]): void; - write(...args: any[]): void; +declare global { + interface IAppenderOptions extends IDictionary { + type: string; + layout?: Layout; + } - prepare(item: any): string; - printInfoMessageOnSameLine(message: string): void; - printMsgWithTimeout(message: string, timeout: number): Promise; - printOnStderr(formatStr?: any, ...args: any[]): void; + interface ILoggerOptions { + level?: LoggerLevel; + appenderOptions?: IAppenderOptions; + } + + interface ILogger { + initialize(opts?: ILoggerOptions): void; + initializeCliLogger(): void; + getLevel(): string; + fatal(formatStr?: any, ...args: any[]): void; + error(formatStr?: any, ...args: any[]): void; + warn(formatStr?: any, ...args: any[]): void; + info(formatStr?: any, ...args: any[]): void; + debug(formatStr?: any, ...args: any[]): void; + trace(formatStr?: any, ...args: any[]): void; + printMarkdown(...args: any[]): void; + prepare(item: any): string; + } + + interface Log4JSAppenderConfiguration extends Configuration { + layout: Layout; + } + + interface Log4JSEmitAppenderConfiguration extends Log4JSAppenderConfiguration { + emitter: EventEmitter; + } } diff --git a/lib/common/dispatchers.ts b/lib/common/dispatchers.ts index 4a33a299ba..db126ca6bc 100644 --- a/lib/common/dispatchers.ts +++ b/lib/common/dispatchers.ts @@ -61,7 +61,7 @@ export class CommandDispatcher implements ICommandDispatcher { if (json && json.buildVersion) { version = `${version}-${json.buildVersion}`; } - this.$logger.out(version); + this.$logger.info(version); } } $injector.register("commandDispatcher", CommandDispatcher); diff --git a/lib/common/logger.ts b/lib/common/logger.ts deleted file mode 100644 index aebd47b2a0..0000000000 --- a/lib/common/logger.ts +++ /dev/null @@ -1,167 +0,0 @@ -import * as log4js from "log4js"; -import * as util from "util"; -import * as stream from "stream"; -import * as marked from "marked"; -const TerminalRenderer = require("marked-terminal"); -const chalk = require("chalk"); - -export class Logger implements ILogger { - private log4jsLogger: log4js.Logger = null; - private passwordRegex = /(password=).*?(['&,]|$)|(password["']?\s*:\s*["']).*?(["'])/i; - private passwordReplacement = "$1$3*******$2$4"; - private static LABEL = "[WARNING]:"; - - constructor($config: Config.IConfig, - private $options: IOptions) { - const appenders: IDictionary = {}; - const categories: IDictionary<{ appenders: string[]; level: string; }> = {}; - let level: string = null; - if (this.$options.log) { - level = this.$options.log; - } else { - level = $config.DEBUG ? "TRACE" : "INFO"; - } - - appenders["out"] = { - type: "console", - layout: { - type: "messagePassThrough" - } - }; - categories["default"] = { - appenders: ['out'], - level - }; - - log4js.configure({ appenders, categories }); - - this.log4jsLogger = log4js.getLogger(); - } - - getLevel(): string { - return this.log4jsLogger.level.toString(); - } - - fatal(...args: string[]): void { - this.log4jsLogger.fatal.apply(this.log4jsLogger, args); - } - - error(...args: string[]): void { - const message = util.format.apply(null, args); - const colorizedMessage = message.red; - - this.log4jsLogger.error.apply(this.log4jsLogger, [colorizedMessage]); - } - - warn(...args: string[]): void { - const message = util.format.apply(null, args); - const colorizedMessage = message.yellow; - - this.log4jsLogger.warn.apply(this.log4jsLogger, [colorizedMessage]); - } - - warnWithLabel(...args: string[]): void { - const message = util.format.apply(null, args); - this.warn(`${Logger.LABEL} ${message}`); - } - - info(...args: string[]): void { - this.log4jsLogger.info.apply(this.log4jsLogger, args); - } - - debug(...args: string[]): void { - const encodedArgs: string[] = this.getPasswordEncodedArguments(args); - this.log4jsLogger.debug.apply(this.log4jsLogger, encodedArgs); - } - - trace(...args: string[]): void { - const encodedArgs: string[] = this.getPasswordEncodedArguments(args); - this.log4jsLogger.trace.apply(this.log4jsLogger, encodedArgs); - } - - out(...args: string[]): void { - console.log(util.format.apply(null, args)); - } - - write(...args: string[]): void { - process.stdout.write(util.format.apply(null, args)); - } - - prepare(item: any): string { - if (typeof item === "undefined" || item === null) { - return "[no content]"; - } - if (typeof item === "string") { - return item; - } - // do not try to read streams, because they may not be rewindable - if (item instanceof stream.Readable) { - return "[ReadableStream]"; - } - - // There's no point in printing buffers - if (item instanceof Buffer) { - return "[Buffer]"; - } - - return JSON.stringify(item); - } - - public printInfoMessageOnSameLine(message: string): void { - if (!this.$options.log || this.$options.log === "info") { - this.write(message); - } - } - - public printMsgWithTimeout(message: string, timeout: number): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => { - this.printInfoMessageOnSameLine(message); - resolve(); - }, timeout); - - }); - } - - public printMarkdown(...args: string[]): void { - const opts = { - unescape: true, - link: chalk.red, - strong: chalk.green.bold, - firstHeading: chalk.blue.bold, - tableOptions: { - chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - style: { - 'padding-left': 1, - 'padding-right': 1, - head: ['green', 'bold'], - border: ['grey'], - compact: false - } - } - }; - - marked.setOptions({ renderer: new TerminalRenderer(opts) }); - - const formattedMessage = marked(util.format.apply(null, args)); - this.write(formattedMessage); - } - - public printOnStderr(...args: string[]): void { - if (process.stderr) { - process.stderr.write(util.format.apply(null, args)); - } - } - - private getPasswordEncodedArguments(args: string[]): string[] { - return _.map(args, argument => { - if (typeof argument === 'string' && !!argument.match(/password/i)) { - argument = argument.replace(this.passwordRegex, this.passwordReplacement); - } - - return argument; - }); - } -} - -$injector.register("logger", Logger); diff --git a/lib/common/logger/appenders/cli-appender.ts b/lib/common/logger/appenders/cli-appender.ts new file mode 100644 index 0000000000..bf8c2355c6 --- /dev/null +++ b/lib/common/logger/appenders/cli-appender.ts @@ -0,0 +1,29 @@ +import { LoggingEvent } from "log4js"; +import { LoggerConfigData } from "../../../constants"; + +function cliAppender(layout: Function) { + const appender = (loggingEvent: LoggingEvent) => { + if (loggingEvent && loggingEvent.data) { + const stream = loggingEvent.context[LoggerConfigData.useStderr] ? process.stderr : process.stdout; + const preparedData = layout(loggingEvent); + stream.write(preparedData); + } + }; + + return appender; +} + +function configure(config: Log4JSAppenderConfiguration, layouts: any) { + // the default layout for the appender + let layout = layouts.messagePassThroughLayout; + + // check if there is another layout specified + if (config.layout) { + layout = layouts.layout(config.layout.type, config.layout); + } + + // create a new appender instance + return cliAppender(layout); +} + +exports.configure = configure; diff --git a/lib/common/logger/appenders/emit-appender.ts b/lib/common/logger/appenders/emit-appender.ts new file mode 100644 index 0000000000..9902579a67 --- /dev/null +++ b/lib/common/logger/appenders/emit-appender.ts @@ -0,0 +1,38 @@ +import { LoggingEvent } from "log4js"; +import { EventEmitter } from "events"; +import { EMIT_APPENDER_EVENT_NAME } from "../../../constants"; + +function emitAppender(layout: Function, emitter: EventEmitter) { + const appender = (loggingEvent: LoggingEvent) => { + emitter.emit(EMIT_APPENDER_EVENT_NAME, { loggingEvent, formattedMessage: layout(loggingEvent) }); + }; + + appender.shutdown = () => { + emitter.removeAllListeners(EMIT_APPENDER_EVENT_NAME); + }; + + return appender; +} + +function configure(config: Log4JSEmitAppenderConfiguration, layouts: any) { + if (!config.emitter) { + throw new Error("Emitter must be passed to emit-appender"); + } + + if (!config.emitter.emit || typeof config.emitter.emit !== "function") { + throw new Error("The passed emitter must be instance of EventEmitter"); + } + + // the default layout for the appender + let layout = layouts.messagePassThroughLayout; + + // check if there is another layout specified + if (config.layout) { + layout = layouts.layout(config.layout.type, config.layout); + } + + // create a new appender instance + return emitAppender(layout, config.emitter); +} + +exports.configure = configure; diff --git a/lib/common/logger/layouts/cli-layout.ts b/lib/common/logger/layouts/cli-layout.ts new file mode 100644 index 0000000000..60595a3aeb --- /dev/null +++ b/lib/common/logger/layouts/cli-layout.ts @@ -0,0 +1,33 @@ +import { format } from "util"; +import { getMessageWithBorders } from "../../helpers"; +import { LoggingEvent } from "log4js"; +import { LoggerConfigData, LoggerLevel } from "../../../constants"; +import { EOL } from "os"; + +export function layout(config: any) { + return function (logEvent: LoggingEvent): string { + let msg = format.apply(null, logEvent.data); + + if (logEvent.context[LoggerConfigData.wrapMessageWithBorders]) { + msg = getMessageWithBorders(msg); + } + + if (!logEvent.context[LoggerConfigData.skipNewLine]) { + msg += EOL; + } + + if (logEvent.level.isEqualTo(LoggerLevel.INFO)) { + return msg; + } + + if (logEvent.level.isEqualTo(LoggerLevel.ERROR)) { + return msg.red.bold; + } + + if (logEvent.level.isEqualTo(LoggerLevel.WARN)) { + return msg.yellow; + } + + return msg; + }; +} diff --git a/lib/common/logger/logger.ts b/lib/common/logger/logger.ts new file mode 100644 index 0000000000..9047bbbbb0 --- /dev/null +++ b/lib/common/logger/logger.ts @@ -0,0 +1,195 @@ +import * as log4js from "log4js"; +import * as util from "util"; +import * as stream from "stream"; +import * as marked from "marked"; +import { cache } from "../decorators"; +import { layout } from "./layouts/cli-layout"; +import { LoggerConfigData, LoggerLevel, LoggerAppenders } from "../../constants"; +const TerminalRenderer = require("marked-terminal"); +const chalk = require("chalk"); + +export class Logger implements ILogger { + private log4jsLogger: log4js.Logger = null; + private passwordRegex = /(password=).*?(['&,]|$)|(password["']?\s*:\s*["']).*?(["'])/i; + private passwordReplacement = "$1$3*******$2$4"; + + constructor(private $config: Config.IConfig, + private $options: IOptions) { + } + + @cache() + public initialize(opts?: ILoggerOptions): void { + opts = opts || {}; + const { appenderOptions: appenderOpts, level } = opts; + + const appender: any = { + type: "console", + layout: { + type: "messagePassThrough" + } + }; + + if (appenderOpts) { + _.merge(appender, appenderOpts); + } + + const appenders: IDictionary = { + out: appender + }; + + const categories: IDictionary<{ appenders: string[]; level: string; }> = { + default: { + appenders: ['out'], + level: level || (this.$config.DEBUG ? "TRACE" : "INFO") + } + }; + + log4js.configure({ appenders, categories }); + + this.log4jsLogger = log4js.getLogger(); + } + + public initializeCliLogger(): void { + log4js.addLayout("cli", layout); + + this.initialize({ + appenderOptions: { type: LoggerAppenders.cliAppender, layout: { type: "cli" } }, + level: this.$options.log + }); + } + + getLevel(): string { + this.initialize(); + + return this.log4jsLogger.level.toString(); + } + + fatal(...args: any[]): void { + this.logMessage(args, LoggerLevel.FATAL); + } + + error(...args: any[]): void { + args.push({ [LoggerConfigData.useStderr]: true }); + this.logMessage(args, LoggerLevel.ERROR); + } + + warn(...args: any[]): void { + this.logMessage(args, LoggerLevel.WARN); + } + + info(...args: any[]): void { + this.logMessage(args, LoggerLevel.INFO); + } + + debug(...args: any[]): void { + const encodedArgs: string[] = this.getPasswordEncodedArguments(args); + this.logMessage(encodedArgs, LoggerLevel.DEBUG); + } + + trace(...args: any[]): void { + const encodedArgs: string[] = this.getPasswordEncodedArguments(args); + this.logMessage(encodedArgs, LoggerLevel.TRACE); + } + + prepare(item: any): string { + if (typeof item === "undefined" || item === null) { + return "[no content]"; + } + if (typeof item === "string") { + return item; + } + // do not try to read streams, because they may not be rewindable + if (item instanceof stream.Readable) { + return "[ReadableStream]"; + } + + // There's no point in printing buffers + if (item instanceof Buffer) { + return "[Buffer]"; + } + + return JSON.stringify(item); + } + + public printMarkdown(...args: string[]): void { + const opts = { + unescape: true, + link: chalk.red, + strong: chalk.green.bold, + firstHeading: chalk.blue.bold, + tableOptions: { + chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, + style: { + 'padding-left': 1, + 'padding-right': 1, + head: ['green', 'bold'], + border: ['grey'], + compact: false + } + } + }; + + marked.setOptions({ renderer: new TerminalRenderer(opts) }); + + const formattedMessage = marked(util.format.apply(null, args)); + this.info(formattedMessage, { [LoggerConfigData.skipNewLine]: true }); + } + + private logMessage(inputData: any[], logMethod: string): void { + this.initialize(); + + const logOpts = this.getLogOptionsForMessage(inputData); + const data = logOpts.data; + delete logOpts.data; + + for (const prop in logOpts) { + this.log4jsLogger.addContext(prop, logOpts[prop]); + } + + (>this.log4jsLogger)[logMethod.toLowerCase()].apply(this.log4jsLogger, data); + + for (const prop in logOpts) { + this.log4jsLogger.removeContext(prop); + } + } + + private getLogOptionsForMessage(data: any[]): { data: any[], [key: string]: any } { + const opts = _.keys(LoggerConfigData); + + const result: any = {}; + const cleanedData = _.cloneDeep(data); + + const dataToCheck = data.filter(el => typeof el === "object"); + + for (const element of dataToCheck) { + if (opts.length === 0) { + break; + } + + const remainingOpts = _.cloneDeep(opts); + for (const prop of remainingOpts) { + const hasProp = element && element.hasOwnProperty(prop); + if (hasProp) { + opts.splice(opts.indexOf(prop), 1); + result[prop] = element[prop]; + cleanedData.splice(cleanedData.indexOf(element), 1); + } + } + } + + result.data = cleanedData; + return result; + } + + private getPasswordEncodedArguments(args: string[]): string[] { + return _.map(args, argument => { + if (typeof argument === 'string' && !!argument.match(/password/i)) { + argument = argument.replace(this.passwordRegex, this.passwordReplacement); + } + + return argument; + }); + } +} + +$injector.register("logger", Logger); diff --git a/lib/common/mobile/android/android-emulator-services.ts b/lib/common/mobile/android/android-emulator-services.ts index 0226a03a73..c9f2f20c07 100644 --- a/lib/common/mobile/android/android-emulator-services.ts +++ b/lib/common/mobile/android/android-emulator-services.ts @@ -1,6 +1,7 @@ import { AndroidVirtualDevice } from "../../constants"; import { getCurrentEpochTime, sleep } from "../../helpers"; import { EOL } from "os"; +import { LoggerConfigData } from "../../../constants"; export class AndroidEmulatorServices implements Mobile.IEmulatorPlatformService { constructor(private $androidGenymotionService: Mobile.IAndroidVirtualDeviceService, @@ -65,7 +66,7 @@ export class AndroidEmulatorServices implements Mobile.IEmulatorPlatformService this.$androidVirtualDeviceService.detach(deviceInfo); } - private async startEmulatorCore(options: Mobile.IAndroidStartEmulatorOptions): Promise<{runningEmulator: Mobile.IDeviceInfo, errors: string[], endTimeEpoch: number}> { + private async startEmulatorCore(options: Mobile.IAndroidStartEmulatorOptions): Promise<{ runningEmulator: Mobile.IDeviceInfo, errors: string[], endTimeEpoch: number }> { const timeout = options.timeout || AndroidVirtualDevice.TIMEOUT_SECONDS; const endTimeEpoch = getCurrentEpochTime() + this.$utils.getMilliSecondsTimeout(timeout); @@ -146,21 +147,21 @@ export class AndroidEmulatorServices implements Mobile.IEmulatorPlatformService return (best && best.version >= AndroidVirtualDevice.MIN_ANDROID_VERSION) ? best : null; } - private async waitForEmulatorBootToComplete(emulator: Mobile.IDeviceInfo, endTimeEpoch: number, timeout: number): Promise<{runningEmulator: Mobile.IDeviceInfo, errors: string[]}> { - this.$logger.printInfoMessageOnSameLine("Waiting for emulator device initialization..."); + private async waitForEmulatorBootToComplete(emulator: Mobile.IDeviceInfo, endTimeEpoch: number, timeout: number): Promise<{ runningEmulator: Mobile.IDeviceInfo, errors: string[] }> { + this.$logger.info("Waiting for emulator device initialization...", { [LoggerConfigData.skipNewLine]: true }); const isInfiniteWait = this.$utils.getMilliSecondsTimeout(timeout || AndroidVirtualDevice.TIMEOUT_SECONDS) === 0; while (getCurrentEpochTime() < endTimeEpoch || isInfiniteWait) { const isEmulatorBootCompleted = await this.isEmulatorBootCompleted(emulator.identifier); if (isEmulatorBootCompleted) { - this.$logger.printInfoMessageOnSameLine(EOL); + this.$logger.info(EOL, { [LoggerConfigData.skipNewLine]: true }); return { runningEmulator: emulator, errors: [] }; } - this.$logger.printInfoMessageOnSameLine("."); + this.$logger.info(".", { [LoggerConfigData.skipNewLine]: true }); await sleep(10000); } diff --git a/lib/common/mobile/device-log-provider.ts b/lib/common/mobile/device-log-provider.ts index 18261a35b8..ab2da4345a 100644 --- a/lib/common/mobile/device-log-provider.ts +++ b/lib/common/mobile/device-log-provider.ts @@ -1,5 +1,6 @@ import { DeviceLogProviderBase } from "./device-log-provider-base"; import { DEVICE_LOG_EVENT_NAME } from "../constants"; +import { LoggerConfigData } from "../../constants"; export class DeviceLogProvider extends DeviceLogProviderBase { constructor(protected $logFilter: Mobile.ILogFilter, @@ -21,7 +22,7 @@ export class DeviceLogProvider extends DeviceLogProviderBase { } private logDataCore(data: string): void { - this.$logger.write(data); + this.$logger.info(data, { [LoggerConfigData.skipNewLine]: true }); } } $injector.register("deviceLogProvider", DeviceLogProvider); diff --git a/lib/common/mobile/ios/device/ios-device-file-system.ts b/lib/common/mobile/ios/device/ios-device-file-system.ts index d5637751cf..ffc9a0b238 100644 --- a/lib/common/mobile/ios/device/ios-device-file-system.ts +++ b/lib/common/mobile/ios/device/ios-device-file-system.ts @@ -19,7 +19,7 @@ export class IOSDeviceFileSystem implements Mobile.IDeviceFileSystem { let children: string[] = []; const result = await this.$iosDeviceOperations.listDirectory([{ deviceId: deviceIdentifier, path: devicePath, appId: appIdentifier }]); children = result[deviceIdentifier][0].response; - this.$logger.out(children.join(EOL)); + this.$logger.info(children.join(EOL)); } public async getFile(deviceFilePath: string, appIdentifier: string, outputFilePath?: string): Promise { @@ -29,7 +29,7 @@ export class IOSDeviceFileSystem implements Mobile.IDeviceFileSystem { } const fileContent = await this.getFileContent(deviceFilePath, appIdentifier); - this.$logger.out(fileContent); + this.$logger.info(fileContent); } public async getFileContent(deviceFilePath: string, appIdentifier: string): Promise { diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index 72ebf1b517..00858d4510 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -606,7 +606,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi return; } - this.$logger.out("Searching for devices..."); + this.$logger.info("Searching for devices..."); deviceInitOpts = deviceInitOpts || {}; this._data = deviceInitOpts; diff --git a/lib/common/progress-indicator.ts b/lib/common/progress-indicator.ts deleted file mode 100644 index ee48daa699..0000000000 --- a/lib/common/progress-indicator.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { isInteractive } from './helpers'; - -export class ProgressIndicator implements IProgressIndicator { - constructor(private $logger: ILogger) { } - - public async showProgressIndicator(promise: Promise, timeout: number, options?: { surpressTrailingNewLine?: boolean }): Promise { - const surpressTrailingNewLine = options && options.surpressTrailingNewLine; - - let isFulfilled = false; - - const tempPromise = new Promise((resolve, reject) => { - promise - .then(res => { - isFulfilled = true; - resolve(res); - }) - .catch(err => { - isFulfilled = true; - reject(err); - }); - }); - - if (!isInteractive()) { - while (!isFulfilled) { - await this.$logger.printMsgWithTimeout(".", timeout); - } - } - - if (!surpressTrailingNewLine) { - this.$logger.out(); - } - - return tempPromise; - } -} -$injector.register("progressIndicator", ProgressIndicator); diff --git a/lib/common/services/auto-completion-service.ts b/lib/common/services/auto-completion-service.ts index 8cf5b56b84..46a5e322de 100644 --- a/lib/common/services/auto-completion-service.ts +++ b/lib/common/services/auto-completion-service.ts @@ -98,7 +98,7 @@ export class AutoCompletionService implements IAutoCompletionService { this.removeObsoleteAutoCompletion(); if (this.scriptsOk && this.scriptsUpdated) { - this.$logger.out("Restart your shell to disable command auto-completion."); + this.$logger.info("Restart your shell to disable command auto-completion."); } } @@ -108,7 +108,7 @@ export class AutoCompletionService implements IAutoCompletionService { this.removeObsoleteAutoCompletion(); if (this.scriptsOk && this.scriptsUpdated) { - this.$logger.out("Restart your shell to enable command auto-completion."); + this.$logger.info("Restart your shell to enable command auto-completion."); } } @@ -155,11 +155,11 @@ export class AutoCompletionService implements IAutoCompletionService { this.scriptsUpdated = true; } } catch (err) { - this.$logger.out("Unable to update %s. Command-line completion might not work.", fileName); + this.$logger.info("Unable to update %s. Command-line completion might not work.", fileName); // When npm is installed with sudo, in some cases the installation cannot write to shell profiles // Advise the user how to enable autocompletion after the installation is completed. if ((err.code === "EPERM" || err.code === "EACCES") && !this.$hostInfo.isWindows && process.env.SUDO_USER) { - this.$logger.out("To enable command-line completion, run '$ %s autocomplete enable'.", this.$staticConfig.CLIENT_NAME); + this.$logger.info("To enable command-line completion, run '$ %s autocomplete enable'.", this.$staticConfig.CLIENT_NAME); } this.$logger.trace(err); @@ -179,8 +179,8 @@ export class AutoCompletionService implements IAutoCompletionService { } catch (err) { // If file does not exist, autocompletion was not working for it, so ignore this error. if (err.code !== "ENOENT") { - this.$logger.out("Failed to update %s. Auto-completion may still work or work incorrectly. ", fileName); - this.$logger.out(err); + this.$logger.info("Failed to update %s. Auto-completion may still work or work incorrectly. ", fileName); + this.$logger.info(err); this.scriptsOk = false; } } @@ -211,7 +211,7 @@ export class AutoCompletionService implements IAutoCompletionService { this.$fs.chmod(filePath, "0644"); } } catch (err) { - this.$logger.out("Failed to update %s. Auto-completion may not work. ", filePath); + this.$logger.info("Failed to update %s. Auto-completion may not work. ", filePath); this.$logger.trace(err); this.scriptsOk = false; } diff --git a/lib/common/test/unit-tests/logger.ts b/lib/common/test/unit-tests/logger.ts index a9b8c0f7fa..058329fb68 100644 --- a/lib/common/test/unit-tests/logger.ts +++ b/lib/common/test/unit-tests/logger.ts @@ -1,5 +1,5 @@ import { Yok } from "../../yok"; -import { Logger } from "../../logger"; +import { Logger } from "../../logger/logger"; import * as path from "path"; import { assert } from "chai"; import * as fileSystemFile from "../../file-system"; @@ -45,6 +45,8 @@ describe("logger", () => { } }; + // Initialize the logger manually, so we can overwrite the log4jsLogger property + logger.initialize(); logger.log4jsLogger = log4jsLogger; }); diff --git a/lib/common/test/unit-tests/mobile/android-device-file-system.ts b/lib/common/test/unit-tests/mobile/android-device-file-system.ts index afe7fbd854..20e8d0d70d 100644 --- a/lib/common/test/unit-tests/mobile/android-device-file-system.ts +++ b/lib/common/test/unit-tests/mobile/android-device-file-system.ts @@ -1,7 +1,7 @@ import { AndroidDeviceFileSystem } from "../../../mobile/android/android-device-file-system"; import { Yok } from "../../../yok"; import { Errors } from "../../../errors"; -import { Logger } from "../../../logger"; +import { Logger } from "../../../logger/logger"; import { MobileHelper } from "../../../mobile/mobile-helper"; import { DevicePlatformsConstants } from "../../../mobile/device-platforms-constants"; diff --git a/lib/common/test/unit-tests/mobile/project-files-manager.ts b/lib/common/test/unit-tests/mobile/project-files-manager.ts index 4df6e5d759..b94972c78d 100644 --- a/lib/common/test/unit-tests/mobile/project-files-manager.ts +++ b/lib/common/test/unit-tests/mobile/project-files-manager.ts @@ -7,7 +7,7 @@ import { HostInfo } from "../../../host-info"; import { LocalToDevicePathDataFactory } from "../../../mobile/local-to-device-path-data-factory"; import { MobileHelper } from "../../../mobile/mobile-helper"; import { ProjectFilesManager } from "../../../services/project-files-manager"; -import { Logger } from "../../../logger"; +import { Logger } from "../../../logger/logger"; import * as path from "path"; import { Yok } from "../../../yok"; import { ProjectFilesProviderBase } from "../../../services/project-files-provider-base"; diff --git a/lib/common/test/unit-tests/stubs.ts b/lib/common/test/unit-tests/stubs.ts index ba0609a2b8..b0fb842f02 100644 --- a/lib/common/test/unit-tests/stubs.ts +++ b/lib/common/test/unit-tests/stubs.ts @@ -2,6 +2,7 @@ import * as util from "util"; import { EventEmitter } from "events"; +import { LoggerConfigData } from "../../../constants"; export class LockServiceStub implements ILockService { public async lock(lockFilePath?: string, lockOpts?: ILockOptions): Promise<() => void> { @@ -18,46 +19,32 @@ export class LockServiceStub implements ILockService { } export class CommonLoggerStub implements ILogger { + initialize(opts?: ILoggerOptions): void { } + initializeCliLogger(): void { } getLevel(): string { return undefined; } - fatal(...args: string[]): void { } - error(...args: string[]): void { } - warn(...args: string[]): void { - this.out.apply(this, args); + fatal(...args: any[]): void { } + error(...args: any[]): void { } + warn(...args: any[]): void { + this.output += util.format.apply(null, args) + "\n"; } - warnWithLabel(...args: string[]): void { } - info(...args: string[]): void { - this.out.apply(this, args); + info(...args: any[]): void { + this.output += util.format.apply(null, args) + "\n"; } - debug(...args: string[]): void { } - trace(...args: string[]): void { + debug(...args: any[]): void { } + trace(...args: any[]): void { this.traceOutput += util.format.apply(null, args) + "\n"; } public output = ""; public traceOutput = ""; - out(...args: string[]): void { - this.output += util.format.apply(null, args) + "\n"; - } - - write(...args: string[]): void { } - prepare(item: any): string { return ""; } - printInfoMessageOnSameLine(message: string): void { } - async printMsgWithTimeout(message: string, timeout: number): Promise { - return null; - } - printMarkdown(message: string): void { this.output += message; } - - printOnStderr(...args: string[]): void { - // nothing to do here - } } export class ErrorsStub implements IErrors { @@ -170,7 +157,7 @@ export class DeviceLogProviderStub extends EventEmitter implements Mobile.IDevic public currentDeviceProjectNames: IStringDictionary = {}; logData(line: string, platform: string, deviceIdentifier: string): void { - this.logger.write(line, platform, deviceIdentifier); + this.logger.info(line, platform, deviceIdentifier, { [LoggerConfigData.skipNewLine]: true }); } setLogLevel(level: string, deviceIdentifier?: string): void { diff --git a/lib/common/verify-node-version.ts b/lib/common/verify-node-version.ts index f95db5578a..7e14722c55 100644 --- a/lib/common/verify-node-version.ts +++ b/lib/common/verify-node-version.ts @@ -53,38 +53,43 @@ export function verifyNodeVersion(): void { } } +var isGetNodeWarningCalled = false; export function getNodeWarning(): ISystemWarning { - var verificationOpts = getNodeVersionOpts(); - var cliName = verificationOpts.cliName; - var supportedVersionsRange = verificationOpts.supportedVersionsRange; - var deprecatedVersions = verificationOpts.deprecatedVersions; - var nodeVer = verificationOpts.nodeVer; + if (!isGetNodeWarningCalled) { + isGetNodeWarningCalled = true; + + var verificationOpts = getNodeVersionOpts(); + var cliName = verificationOpts.cliName; + var supportedVersionsRange = verificationOpts.supportedVersionsRange; + var deprecatedVersions = verificationOpts.deprecatedVersions; + var nodeVer = verificationOpts.nodeVer; - var warningMessage = ""; - if (deprecatedVersions) { - deprecatedVersions.forEach(function (version) { - if (semver.satisfies(nodeVer, version)) { - warningMessage = "Support for Node.js " + version + " is deprecated and will be removed in one of the next releases of " + cliName + - ". Please, upgrade to the latest Node.js LTS version. "; - return warningMessage; + var warningMessage = ""; + if (deprecatedVersions) { + deprecatedVersions.forEach(function (version) { + if (semver.satisfies(nodeVer, version)) { + warningMessage = "Support for Node.js " + version + " is deprecated and will be removed in one of the next releases of " + cliName + + ". Please, upgrade to the latest Node.js LTS version. "; + return warningMessage; + } + }); + } + + if (!warningMessage) { + var checkSatisfied = semver.satisfies(nodeVer, supportedVersionsRange); + if (!checkSatisfied) { + warningMessage = "Support for Node.js " + nodeVer + " is not verified. " + cliName + " CLI might not install or run properly."; } - }); - } + } - if (!warningMessage) { - var checkSatisfied = semver.satisfies(nodeVer, supportedVersionsRange); - if (!checkSatisfied) { - warningMessage = "Support for Node.js " + nodeVer + " is not verified. " + cliName + " CLI might not install or run properly."; + if (warningMessage) { + return { + message: warningMessage, + severity: SystemWarningsSeverity.medium + }; } - } - if (warningMessage) { - return { - message: warningMessage, - severity: SystemWarningsSeverity.medium - }; + return null; } - - return null; } /* tslint:enable */ diff --git a/lib/constants.ts b/lib/constants.ts index 7443a4632a..e70a951c17 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,5 +1,6 @@ require("colors"); import { PreviewAppLiveSyncEvents } from "./services/livesync/playground/preview-app-constants"; +import { join } from "path"; export const APP_FOLDER_NAME = "app"; export const APP_RESOURCES_FOLDER_NAME = "App_Resources"; @@ -299,3 +300,75 @@ export enum IOSNativeTargetTypes { watchExtension = "watch_extension", appExtension = "app_extension" } + +const pathToLoggerAppendersDir = join(__dirname, "common", "logger", "appenders"); +export const LoggerAppenders = { + emitAppender: join(pathToLoggerAppendersDir, "emit-appender"), + cliAppender: join(pathToLoggerAppendersDir, "cli-appender") +}; + +export enum LoggerLevel { + /** + * Show all log messages. + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + ALL = "ALL", + + /** + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + TRACE = "TRACE", + + /** + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + DEBUG = "DEBUG", + + /** + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + INFO = "INFO", + + /** + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + WARN = "WARN", + + /** + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + ERROR = "ERROR", + + /** + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + FATAL = "FATAL", + + /** + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + MARK = "MARK", + + /** + * Disable all logging. + * Log levels are used to assign importance to log messages, with the integer value being used to sort them. + * If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF) + */ + OFF = "OFF" +} + +export enum LoggerConfigData { + useStderr = "useStderr", + wrapMessageWithBorders = "wrapMessageWithBorders", + skipNewLine = "skipNewLine" +} + +export const EMIT_APPENDER_EVENT_NAME = "logData"; diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e8dfe938e0..883943db2a 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -603,7 +603,7 @@ interface IUpdatePlatformOptions extends IPlatformTemplate { canUpdate: boolean; } -interface IInitService { +interface IProjectInitService { initialize(): Promise; } diff --git a/lib/definitions/initialize-service.d.ts b/lib/definitions/initialize-service.d.ts new file mode 100644 index 0000000000..c2b7b22c09 --- /dev/null +++ b/lib/definitions/initialize-service.d.ts @@ -0,0 +1,9 @@ +interface IInitializeOptions { + loggerOptions?: ILoggerOptions; + settingsServiceOptions?: IConfigurationSettings; + extensibilityOptions?: { pathToExtensions: string }; +} + +interface IInitializeService { + initialize(initOpts?: IInitializeOptions): Promise; +} diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts index 431413ca28..39b6479e60 100644 --- a/lib/nativescript-cli.ts +++ b/lib/nativescript-cli.ts @@ -1,6 +1,5 @@ require("./bootstrap"); -import { EOL } from "os"; import * as shelljs from "shelljs"; shelljs.config.silent = true; shelljs.config.fatal = true; @@ -25,6 +24,9 @@ process.on = (event: string, listener: any): any => { const err: IErrors = $injector.resolve("$errors"); err.printCallStack = config.DEBUG; + const $initializeService = $injector.resolve("initializeService"); + await $initializeService.initialize(); + const extensibilityService: IExtensibilityService = $injector.resolve("extensibilityService"); try { await settlePromises(extensibilityService.loadExtensions()); @@ -32,17 +34,6 @@ process.on = (event: string, listener: any): any => { logger.trace("Unable to load extensions. Error is: ", err); } - const $sysInfo = $injector.resolve("sysInfo"); - const macOSWarning = await $sysInfo.getMacOSWarningMessage(); - if (macOSWarning) { - const message = `${EOL}${macOSWarning.message}${EOL}`; - if (macOSWarning.severity === SystemWarningsSeverity.high) { - logger.printOnStderr(message.red.bold); - } else { - logger.warn(message); - } - } - const commandDispatcher: ICommandDispatcher = $injector.resolve("commandDispatcher"); const messages: IMessagesService = $injector.resolve("$messagesService"); diff --git a/lib/package-installation-manager.ts b/lib/package-installation-manager.ts index b2e62e66c4..62ebf0a5f1 100644 --- a/lib/package-installation-manager.ts +++ b/lib/package-installation-manager.ts @@ -91,7 +91,7 @@ export class PackageInstallationManager implements IPackageInstallationManager { await this.$childProcess.exec(`npm install ${inspectorNpmPackageName}@${version} --prefix ${cachePath}`, { maxBuffer: 250 * 1024 }); } - this.$logger.out("Using inspector from cache."); + this.$logger.info("Using inspector from cache."); return pathToPackageInCache; } @@ -139,7 +139,7 @@ export class PackageInstallationManager implements IPackageInstallationManager { } private async npmInstall(packageName: string, pathToSave: string, version: string, dependencyType: string): Promise { - this.$logger.out(`Installing ${packageName}`); + this.$logger.info(`Installing ${packageName}`); packageName = packageName + (version ? `@${version}` : ""); diff --git a/lib/project-data.ts b/lib/project-data.ts index 0e3106b7e7..538651f0db 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -260,7 +260,7 @@ export class ProjectData implements IProjectData { @cache() private warnProjectId(): void { - this.$logger.warnWithLabel("IProjectData.projectId is deprecated. Please use IProjectData.projectIdentifiers[platform]."); + this.$logger.warn("[WARNING]: IProjectData.projectId is deprecated. Please use IProjectData.projectIdentifiers[platform]."); } } $injector.register("projectData", ProjectData, true); diff --git a/lib/services/analytics/analytics-service.ts b/lib/services/analytics/analytics-service.ts index f160359193..a620f2c209 100644 --- a/lib/services/analytics/analytics-service.ts +++ b/lib/services/analytics/analytics-service.ts @@ -33,7 +33,7 @@ export class AnalyticsService implements IAnalyticsService, IDisposable { let trackFeatureUsage = initialTrackFeatureUsageStatus === AnalyticsStatus.enabled; if (await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME) && isInteractive()) { - this.$logger.out("Do you want to help us improve " + this.$logger.info("Do you want to help us improve " + this.$analyticsSettingsService.getClientName() + " by automatically sending anonymous usage statistics? We will not use this information to identify or contact you." + " You can read our official Privacy Policy at"); diff --git a/lib/services/android-resources-migration-service.ts b/lib/services/android-resources-migration-service.ts index 2aac57b327..a565bacc22 100644 --- a/lib/services/android-resources-migration-service.ts +++ b/lib/services/android-resources-migration-service.ts @@ -27,14 +27,14 @@ export class AndroidResourcesMigrationService implements IAndroidResourcesMigrat try { await this.tryMigrate(originalAppResources, appResourcesDestination, appResourcesBackup); - this.$logger.out(`Successfully updated your project's application resources '/Android' directory structure.${EOL}The previous version of your Android application resources has been renamed to '/${AndroidResourcesMigrationService.ANDROID_DIR_OLD}'`); + this.$logger.info(`Successfully updated your project's application resources '/Android' directory structure.${EOL}The previous version of your Android application resources has been renamed to '/${AndroidResourcesMigrationService.ANDROID_DIR_OLD}'`); } catch (error) { try { this.recover(originalAppResources, appResourcesDestination, appResourcesBackup); - this.$logger.out("Failed to update resources. They should be in their initial state."); + this.$logger.info("Failed to update resources. They should be in their initial state."); } catch (err) { this.$logger.trace(err); - this.$logger.out(`Failed to update resources.${EOL} Backup of original content is inside "${appResourcesBackup}".${EOL}If "${originalAppResources} is missing copy from backup folder."`); + this.$logger.info(`Failed to update resources.${EOL} Backup of original content is inside "${appResourcesBackup}".${EOL}If "${originalAppResources} is missing copy from backup folder."`); } finally { this.$errors.failWithoutHelp(error.message); } diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index e408876713..feda360ebf 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -40,7 +40,7 @@ export class DoctorService implements IDoctorService { if (hasWarnings) { this.$logger.info("There seem to be issues with your configuration."); } else { - this.$logger.out("No issues were detected.".bold); + this.$logger.info("No issues were detected.".bold); this.printInfosCore(infos); } @@ -74,7 +74,7 @@ export class DoctorService implements IDoctorService { return; } - this.$logger.out("Running the setup script to try and automatically configure your environment."); + this.$logger.info("Running the setup script to try and automatically configure your environment."); if (this.$hostInfo.isDarwin) { await this.runSetupScriptCore(DoctorService.DarwinSetupScriptLocation, []); @@ -186,9 +186,9 @@ export class DoctorService implements IDoctorService { private printPackageManagerTip() { if (this.$hostInfo.isWindows) { - this.$logger.out("TIP: To avoid setting up the necessary environment variables, you can use the chocolatey package manager to install the Android SDK and its dependencies." + EOL); + this.$logger.info("TIP: To avoid setting up the necessary environment variables, you can use the chocolatey package manager to install the Android SDK and its dependencies." + EOL); } else if (this.$hostInfo.isDarwin) { - this.$logger.out("TIP: To avoid setting up the necessary environment variables, you can use the Homebrew package manager to install the Android SDK and its dependencies." + EOL); + this.$logger.info("TIP: To avoid setting up the necessary environment variables, you can use the Homebrew package manager to install the Android SDK and its dependencies." + EOL); } } @@ -199,7 +199,7 @@ export class DoctorService implements IDoctorService { if (info.type === constants.WARNING_TYPE_NAME) { message = `WARNING: ${info.message.yellow} ${EOL} ${info.additionalInformation} ${EOL}`; } - this.$logger.out(message); + this.$logger.info(message); }); } diff --git a/lib/services/initialize-service.ts b/lib/services/initialize-service.ts new file mode 100644 index 0000000000..117da1b4c1 --- /dev/null +++ b/lib/services/initialize-service.ts @@ -0,0 +1,46 @@ +import { EOL } from "os"; + +export class InitializeService implements IInitializeService { + // NOTE: Do not inject anything here, use $injector.resolve in the code + // Injecting something may lead to logger initialization, but we want to initialize it from here. + constructor(private $injector: IInjector) { } + + public async initialize(initOpts?: IInitializeOptions): Promise { + initOpts = initOpts || {}; + const $logger = this.$injector.resolve("logger"); + if (initOpts.loggerOptions) { + $logger.initialize(initOpts.loggerOptions); + } else { + $logger.initializeCliLogger(); + } + + if (initOpts.settingsServiceOptions) { + const $settingsService = this.$injector.resolve("settingsService"); + $settingsService.setSettings(initOpts.settingsServiceOptions); + } + + if (initOpts.extensibilityOptions) { + if (initOpts.extensibilityOptions.pathToExtensions) { + const $extensibilityService = this.$injector.resolve("extensibilityService"); + $extensibilityService.pathToExtensions = initOpts.extensibilityOptions.pathToExtensions; + } + } + + await this.showWarnings($logger); + } + + private async showWarnings($logger: ILogger): Promise { + const $sysInfo = $injector.resolve("sysInfo"); + const systemWarnings = await $sysInfo.getSystemWarnings(); + _.each(systemWarnings, systemWarning => { + const message = `${EOL}${systemWarning.message}${EOL}`; + if (systemWarning.severity === SystemWarningsSeverity.high) { + $logger.error(message); + } else { + $logger.warn(message); + } + }); + } +} + +$injector.register("initializeService", InitializeService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 7aca88f31e..2fcf6e9144 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -1424,7 +1424,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f const mergedPlist = plist.parse(this.$fs.readText(mergedPlistPath)); if (infoPlist.CFBundleIdentifier && infoPlist.CFBundleIdentifier !== mergedPlist.CFBundleIdentifier) { - this.$logger.warnWithLabel("The CFBundleIdentifier key inside the 'Info.plist' will be overriden by the 'id' inside 'package.json'."); + this.$logger.warn("[WARNING]: The CFBundleIdentifier key inside the 'Info.plist' will be overriden by the 'id' inside 'package.json'."); } } diff --git a/lib/services/ios-provision-service.ts b/lib/services/ios-provision-service.ts index 19c0ba380c..1df007ac6a 100644 --- a/lib/services/ios-provision-service.ts +++ b/lib/services/ios-provision-service.ts @@ -55,16 +55,16 @@ export class IOSProvisionService { } match.eligable.forEach(prov => pushProvision(prov)); - this.$logger.out(table.toString()); - this.$logger.out(); - this.$logger.out("There are also " + match.nonEligable.length + " non-eligable provisioning profiles."); - this.$logger.out(); + this.$logger.info(table.toString()); + this.$logger.info(); + this.$logger.info("There are also " + match.nonEligable.length + " non-eligable provisioning profiles."); + this.$logger.info(); } public async listTeams(): Promise { const teams = await this.getDevelopmentTeams(); const table = createTable(["Team Name", "Team ID"], teams.map(team => [quoteString(team.name), team.id])); - this.$logger.out(table.toString()); + this.$logger.info(table.toString()); } private async queryProvisioningProfilesAndDevices(projectId: string): Promise<{ devices: string[], match: mobileprovision.provision.Result }> { diff --git a/lib/services/nativescript-cloud-extension-service.ts b/lib/services/nativescript-cloud-extension-service.ts index 3ff6c90a24..04c99c8bdc 100644 --- a/lib/services/nativescript-cloud-extension-service.ts +++ b/lib/services/nativescript-cloud-extension-service.ts @@ -11,7 +11,7 @@ export class NativeScriptCloudExtensionService implements INativeScriptCloudExte return this.$extensibilityService.installExtension(constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); } - this.$logger.out(`Extension ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} is already installed.`); + this.$logger.info(`Extension ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} is already installed.`); } public isInstalled(): boolean { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 4589674cbd..500ca4df7a 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -97,7 +97,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.trace("Package: %s", projectData.projectIdentifiers[platform]); this.$logger.trace("Name: %s", projectData.projectName); - this.$logger.out("Copying template files..."); + this.$logger.info("Copying template files..."); let packageToInstall = ""; if (frameworkPath) { @@ -136,7 +136,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } this.$fs.ensureDirectoryExists(platformPath); - this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); + this.$logger.info(`Platform ${platform} successfully added. v${installedPlatformVersion}`); } private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, nativePrepare?: INativePrepare): Promise { @@ -249,7 +249,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { ); this.$projectChangesService.savePrepareInfo(platformInfo.platform, platformInfo.projectData); } else { - this.$logger.out("Skipping prepare."); + this.$logger.info("Skipping prepare."); } return true; @@ -316,7 +316,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { filesToRemove?: string[], nativePrepare?: INativePrepare): Promise { - this.$logger.out("Preparing project..."); + this.$logger.info("Preparing project..."); const platformData = this.$platformsData.getPlatformData(platform, projectData); const frameworkVersion = this.getCurrentPlatformVersion(platform, projectData); @@ -361,7 +361,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, projectFilesConfig, excludedDirs); - this.$logger.out(`Project successfully prepared (${platform})`); + this.$logger.info(`Project successfully prepared (${platform})`); } public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig, outputPath?: string): Promise { @@ -404,7 +404,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { @performanceLog() public async buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise { - this.$logger.out("Building project..."); + this.$logger.info("Building project..."); const action = constants.TrackActionNames.Build; const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildConfig && buildConfig.buildForDevice; @@ -424,7 +424,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const handler = (data: any) => { this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); - this.$logger.printInfoMessageOnSameLine(data.data.toString()); + this.$logger.info(data.data.toString(), { [constants.LoggerConfigData.skipNewLine]: true }); }; await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig)); @@ -432,7 +432,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const buildInfoFilePath = this.getBuildOutputPath(platform, platformData, buildConfig); this.saveBuildInfoFile(platform, projectData.projectDir, buildInfoFilePath); - this.$logger.out("Project successfully built."); + this.$logger.info("Project successfully built."); return this.lastOutputPath(platform, buildConfig, projectData); } @@ -480,7 +480,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData, packageFile?: string, outputFilePath?: string): Promise { - this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); + this.$logger.info(`Installing on device ${device.deviceInfo.identifier}...`); await this.$analyticsService.trackEventActionInGoogleAnalytics({ action: constants.TrackActionNames.Deploy, @@ -519,7 +519,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); } - this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); + this.$logger.info(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } private async updateHashesOnDevice(data: { device: Mobile.IDevice, appIdentifier: string, outputFilePath: string, platformData: IPlatformData }): Promise { @@ -573,13 +573,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (shouldBuild) { installPackageFile = await deployInfo.buildPlatform(deployInfo.platform, buildConfig, deployInfo.projectData); } else { - this.$logger.out("Skipping package build. No changes detected on the native side. This will be fast!"); + this.$logger.info("Skipping package build. No changes detected on the native side. This will be fast!"); } if (deployInfo.deployOptions.forceInstall || shouldBuild || (await this.shouldInstall(device, deployInfo.projectData, buildConfig))) { await this.installApplication(device, buildConfig, deployInfo.projectData, installPackageFile, deployInfo.outputPath); } else { - this.$logger.out("Skipping install."); + this.$logger.info("Skipping install."); } }; @@ -593,11 +593,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async startApplication(platform: string, runOptions: IRunPlatformOptions, appData: Mobile.IStartApplicationData): Promise { - this.$logger.out("Starting..."); + this.$logger.info("Starting..."); const action = async (device: Mobile.IDevice) => { await device.applicationManager.startApplication(appData); - this.$logger.out(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`); + this.$logger.info(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`); }; await this.$devicesService.initialize({ platform: platform, deviceId: runOptions.device }); @@ -713,7 +713,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$fs.deleteDirectory(platformDir); this.$projectDataService.removeNSProperty(projectData.projectDir, platformData.frameworkPackageName); - this.$logger.out(`Platform ${platform} successfully removed.`); + this.$logger.info(`Platform ${platform} successfully removed.`); } catch (err) { this.$logger.error(`Failed to remove ${platform} platform with errors:`); if (gradleErrorMessage) { @@ -948,7 +948,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; await this.addPlatform(packageName, updateOptions.platformTemplate, projectData, config); - this.$logger.out("Successfully updated to version ", updateOptions.newVersion); + this.$logger.info("Successfully updated to version ", updateOptions.newVersion); } // TODO: Remove this method from here. It has nothing to do with platform diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index f18a9e8e7f..64dc3c38cf 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -70,7 +70,7 @@ export class PluginsService implements IPluginsService { throw err; } - this.$logger.out(`Successfully installed plugin ${realNpmPackageJson.name}.`); + this.$logger.info(`Successfully installed plugin ${realNpmPackageJson.name}.`); } else { await this.$packageManager.uninstall(realNpmPackageJson.name, { save: true }, projectData.projectDir); this.$errors.failWithoutHelp(`${plugin} is not a valid NativeScript plugin. Verify that the plugin package.json file contains a nativescript key and try again.`); @@ -93,14 +93,14 @@ export class PluginsService implements IPluginsService { const action = async (modulesDestinationPath: string, platform: string, platformData: IPlatformData): Promise => { shelljs.rm("-rf", path.join(modulesDestinationPath, pluginName)); - this.$logger.out(`Successfully removed plugin ${pluginName} for ${platform}.`); + this.$logger.info(`Successfully removed plugin ${pluginName} for ${platform}.`); showMessage = false; }; await this.executeForAllInstalledPlatforms(action, projectData); if (showMessage) { - this.$logger.out(`Successfully removed plugin ${pluginName}`); + this.$logger.info(`Successfully removed plugin ${pluginName}`); } } @@ -119,7 +119,7 @@ export class PluginsService implements IPluginsService { await this.preparePluginNativeCode(pluginData, platform, projectData); // Show message - this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); + this.$logger.info(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); } } diff --git a/lib/services/init-service.ts b/lib/services/project-init-service.ts similarity index 94% rename from lib/services/init-service.ts rename to lib/services/project-init-service.ts index 8a35b0af0f..24937a1f5c 100644 --- a/lib/services/init-service.ts +++ b/lib/services/project-init-service.ts @@ -3,7 +3,7 @@ import * as helpers from "../common/helpers"; import * as path from "path"; import * as semver from "semver"; -export class InitService implements IInitService { +export class ProjectInitService implements IProjectInitService { private static MIN_SUPPORTED_FRAMEWORK_VERSIONS: IStringDictionary = { "tns-ios": "1.1.0", "tns-android": "1.1.0", @@ -74,7 +74,7 @@ export class InitService implements IInitService { throw err; } - this.$logger.out("Project successfully initialized."); + this.$logger.info("Project successfully initialized."); } private get projectFilePath(): string { @@ -107,7 +107,7 @@ export class InitService implements IInitService { } const allVersions: any = await this.$packageManager.view(packageName, { "versions": true }); - const versions = _.filter(allVersions, (v: string) => semver.gte(v, InitService.MIN_SUPPORTED_FRAMEWORK_VERSIONS[packageName])); + const versions = _.filter(allVersions, (v: string) => semver.gte(v, ProjectInitService.MIN_SUPPORTED_FRAMEWORK_VERSIONS[packageName])); if (versions.length === 1) { this.$logger.info(`Only ${versions[0]} version is available for ${packageName}.`); return this.buildVersionData(versions[0]); @@ -121,7 +121,7 @@ export class InitService implements IInitService { private buildVersionData(version: string): IStringDictionary { const result: IStringDictionary = {}; - result[InitService.VERSION_KEY_NAME] = version; + result[ProjectInitService.VERSION_KEY_NAME] = version; return result; } @@ -130,4 +130,4 @@ export class InitService implements IInitService { return !helpers.isInteractive() || this.$options.force; } } -$injector.register("initService", InitService); +$injector.register("projectInitService", ProjectInitService); diff --git a/lib/services/versions-service.ts b/lib/services/versions-service.ts index e5d566e2cd..9f5ef644a2 100644 --- a/lib/services/versions-service.ts +++ b/lib/services/versions-service.ts @@ -135,7 +135,7 @@ class VersionsService implements IVersionsService { }, () => this.getAllComponentsVersions()); if (!helpers.isInteractive()) { - versionsInformation.map(componentInformation => this.$logger.out(componentInformation.message)); + versionsInformation.map(componentInformation => this.$logger.info(componentInformation.message)); } _.forEach(versionsInformation, componentInformation => { diff --git a/lib/services/workflow-service.ts b/lib/services/workflow-service.ts index 210dcbea35..3cbba6a5e8 100644 --- a/lib/services/workflow-service.ts +++ b/lib/services/workflow-service.ts @@ -2,6 +2,7 @@ import * as helpers from "../common/helpers"; import * as path from "path"; import * as semver from "semver"; import { EOL } from "os"; +import { LoggerConfigData } from "../constants"; export class WorkflowService implements IWorkflowService { private legacyWorkflowDeprecationMessage = `With the upcoming NativeScript 6.0 the Webpack workflow will become the only way of building apps. @@ -70,16 +71,14 @@ __Improve your project by switching to the Webpack workflow.__ private showLegacyWorkflowWarning() { const legacyWorkflowWarning = `You are using the Legacy Workflow.${EOL}${EOL}${this.legacyWorkflowDeprecationMessage}`; - const warningWithBorders = helpers.getMessageWithBorders(legacyWorkflowWarning); - this.$logger.warn(warningWithBorders); + this.$logger.warn(legacyWorkflowWarning, { [LoggerConfigData.wrapMessageWithBorders]: true }); } private showNoBundleWarning() { const legacyWorkflowWarning = `You are using the '--no-bundle' flag which is switching to the Legacy Workflow.${EOL}${EOL}${this.legacyWorkflowDeprecationMessage}`; - const warningWithBorders = helpers.getMessageWithBorders(legacyWorkflowWarning); - this.$logger.warn(warningWithBorders); + this.$logger.warn(legacyWorkflowWarning, { [LoggerConfigData.wrapMessageWithBorders]: true }); } private async ensureWebpackPluginInstalled(projectData: IProjectData) { diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index 55bcaaedc4..afcd8ccbf4 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -184,7 +184,7 @@ export class NpmPluginPrepare { if (appFolderExists) { this.$pluginsService.preparePluginScripts(pluginData, platform, projectData, projectFilesConfig); // Show message - this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); + this.$logger.info(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); } } } diff --git a/lib/xml-validator.ts b/lib/xml-validator.ts index 6d33e1ca28..08970a745e 100644 --- a/lib/xml-validator.ts +++ b/lib/xml-validator.ts @@ -15,7 +15,7 @@ export class XmlValidator implements IXmlValidator { xmlHasErrors = xmlHasErrors || hasErrors; if (hasErrors) { this.$logger.info(`${file} has syntax errors.`.red.bold); - this.$logger.out(errorOutput.yellow); + this.$logger.info(errorOutput.yellow); } }); return !xmlHasErrors; diff --git a/test/commands/post-install.ts b/test/commands/post-install.ts index 6be24a78bc..39640112ea 100644 --- a/test/commands/post-install.ts +++ b/test/commands/post-install.ts @@ -35,7 +35,7 @@ const createTestInjector = (): IInjector => { }); testInjector.register("logger", { - out: (formatStr?: any, ...args: any[]): void => undefined, + info: (formatStr?: any, ...args: any[]): void => undefined, printMarkdown: (...args: any[]): void => undefined }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 28fdcf890e..0fad626fd0 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -9,7 +9,7 @@ import * as iOSProjectServiceLib from "../lib/services/ios-project-service"; import { IOSProjectService } from "../lib/services/ios-project-service"; import { IOSEntitlementsService } from "../lib/services/ios-entitlements-service"; import { XcconfigService } from "../lib/services/xcconfig-service"; -import * as LoggerLib from "../lib/common/logger"; +import * as LoggerLib from "../lib/common/logger/logger"; import * as OptionsLib from "../lib/options"; import * as yok from "../lib/common/yok"; import { DevicesService } from "../lib/common/mobile/mobile-core/devices-service"; diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index f32f35f779..33b08b031c 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -23,7 +23,7 @@ describe("nativescript-cli-lib", () => { "getIOSAssetsStructure", "getAndroidAssetsStructure" ], - constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME"], + constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME", "LoggerLevel", "LoggerAppenders"], localBuildService: ["build"], deviceLogProvider: null, packageManager: ["install", "uninstall", "view", "search"], @@ -62,6 +62,14 @@ describe("nativescript-cli-lib", () => { ], cleanupService: [ "setCleanupLogFile" + ], + logger: [ + "initialize", + "getLevel", + "info" + ], + initializeService: [ + "initialize" ] }; diff --git a/test/package-installation-manager.ts b/test/package-installation-manager.ts index bec00a5ff7..242324f26e 100644 --- a/test/package-installation-manager.ts +++ b/test/package-installation-manager.ts @@ -3,7 +3,7 @@ import * as ConfigLib from "../lib/config"; import * as ErrorsLib from "../lib/common/errors"; import * as FsLib from "../lib/common/file-system"; import * as HostInfoLib from "../lib/common/host-info"; -import * as LoggerLib from "../lib/common/logger"; +import * as LoggerLib from "../lib/common/logger/logger"; import * as NpmLib from "../lib/node-package-manager"; import * as YarnLib from "../lib/yarn-package-manager"; import * as PackageManagerLib from "../lib/package-manager"; diff --git a/test/plugins-service.ts b/test/plugins-service.ts index bb2b011cd1..630a577b5f 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -107,13 +107,6 @@ function createTestInjector() { showCommandLineHelp: async (): Promise => (undefined) }); testInjector.register("settingsService", SettingsService); - testInjector.register("progressIndicator", { - getSpinner: (msg: string) => ({ - start: (): void => undefined, - stop: (): void => undefined, - message: (): void => undefined - }) - }); testInjector.register("httpClient", {}); testInjector.register("extensibilityService", {}); testInjector.register("androidPluginBuildService", stubs.AndroidPluginBuildServiceStub); diff --git a/test/services/subscription-service.ts b/test/services/subscription-service.ts index c4cbb9b529..30effd389c 100644 --- a/test/services/subscription-service.ts +++ b/test/services/subscription-service.ts @@ -135,7 +135,7 @@ describe("subscriptionService", () => { subscriptionService.shouldAskForEmailResult = false; const logger = testInjector.resolve("logger"); let loggerOutput = ""; - logger.out = (...args: string[]): void => { + logger.info = (...args: string[]): void => { loggerOutput += args.join(" "); }; @@ -151,7 +151,7 @@ describe("subscriptionService", () => { const logger = testInjector.resolve("logger"); let loggerOutput = ""; - logger.out = (...args: string[]): void => { + logger.info = (...args: string[]): void => { loggerOutput += args.join(" "); }; diff --git a/test/stubs.ts b/test/stubs.ts index 7556081a47..5763c9d84c 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -11,12 +11,16 @@ import { HostInfo } from "./../lib/common/host-info"; import { DevicePlatformsConstants } from "./../lib/common/mobile/device-platforms-constants"; export class LoggerStub implements ILogger { + initialize(opts?: ILoggerOptions): void { } + initializeCliLogger(): void { } getLevel(): string { return undefined; } fatal(...args: string[]): void { } error(...args: string[]): void { } warn(...args: string[]): void { } - warnWithLabel(...args: string[]): void { } - info(...args: string[]): void { } + info(...args: string[]): void { + this.output += util.format.apply(null, args) + "\n"; + } + debug(...args: string[]): void { } trace(...args: string[]): void { this.traceOutput += util.format.apply(null, args) + "\n"; @@ -25,26 +29,11 @@ export class LoggerStub implements ILogger { public output = ""; public traceOutput = ""; - out(...args: string[]): void { - this.output += util.format.apply(null, args) + "\n"; - } - - write(...args: string[]): void { } - prepare(item: any): string { return ""; } - printInfoMessageOnSameLine(message: string): void { } - async printMsgWithTimeout(message: string, timeout: number): Promise { - return null; - } - printMarkdown(message: string): void { } - - printOnStderr(...args: string[]): void { - // nothing to do here - } } export class FileSystemStub implements IFileSystem {