From d17998b0aea3d3648d715dac27e07b413080629a Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 15 Apr 2019 09:07:47 +0300 Subject: [PATCH 01/62] chore: add changelog for 5.3.2 release --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8715ce074..83fcdccb7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ NativeScript CLI Changelog ================ +5.3.2 (2019, April 12) +== + +### Fixed +* [Fixed #1798](https://github.com/NativeScript/nativescript-cli/issues/1798): Test init command doesn't add a sample test in TypeScript for TypeScript/Angular projects +* [Fixed #4498](https://github.com/NativeScript/nativescript-cli/pull/4498): API: Change the AppStore ids for kinvey scanner and preview app +* [Fixed #4504](https://github.com/NativeScript/nativescript-cli/issues/4504): Custom tagged versions of android runtime are not supported +* [Fixed #4510](https://github.com/NativeScript/nativescript-cli/pull/4510): Handle HTTP 304 response status code + 5.3.0 (2019, March 27) == From d255b97ac3e41699ebe5e4fb05abfed4491ba2d6 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 18 Apr 2019 11:03:50 +0300 Subject: [PATCH 02/62] feat: deprecate the legacy workflow and recommend the new one --- lib/bootstrap.ts | 1 + lib/commands/preview.ts | 8 +- lib/commands/test.ts | 8 +- lib/commands/update.ts | 9 +- lib/declarations.d.ts | 7 ++ lib/definitions/platform.d.ts | 10 ++ lib/definitions/project.d.ts | 10 +- lib/helpers/bundle-validator-helper.ts | 20 ++-- lib/options.ts | 1 + lib/project-data.ts | 2 +- .../preview-app-livesync-service.ts | 10 +- lib/services/platform-service.ts | 4 +- lib/services/project-data-service.ts | 72 +++++++++++-- lib/services/workflow-service.ts | 102 ++++++++++++++++++ test/stubs.ts | 3 + 15 files changed, 239 insertions(+), 28 deletions(-) create mode 100644 lib/services/workflow-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index e359fa3f35..a3a6c22a12 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -190,6 +190,7 @@ $injector.require("hmrStatusService", "./services/hmr-status-service"); $injector.require("pacoteService", "./services/pacote-service"); $injector.require("qrCodeTerminalService", "./services/qr-code-terminal-service"); $injector.require("testInitializationService", "./services/test-initialization-service"); +$injector.require("workflowService", "./services/workflow-service"); $injector.require("networkConnectivityValidator", "./helpers/network-connectivity-validator"); $injector.requirePublic("cleanupService", "./services/cleanup-service"); diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index bb2aab8c35..4a77d6a294 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -14,12 +14,14 @@ export class PreviewCommand implements ICommand { private $options: IOptions, private $previewAppLogProvider: IPreviewAppLogProvider, private $previewQrCodeService: IPreviewQrCodeService, + protected $workflowService: IWorkflowService, $cleanupService: ICleanupService) { - this.$analyticsService.setShouldDispose(false); - $cleanupService.setShouldDispose(false); - } + this.$analyticsService.setShouldDispose(false); + $cleanupService.setShouldDispose(false); + } public async execute(): Promise { + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options); this.$previewAppLogProvider.on(DEVICE_LOG_EVENT_NAME, (deviceId: string, message: string) => { this.$logger.info(message); }); diff --git a/lib/commands/test.ts b/lib/commands/test.ts index e96d981793..f855972076 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -11,8 +11,10 @@ abstract class TestCommandBase { protected abstract $platformEnvironmentRequirements: IPlatformEnvironmentRequirements; protected abstract $errors: IErrors; protected abstract $cleanupService: ICleanupService; + protected abstract $workflowService: IWorkflowService; async execute(args: string[]): Promise { + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options); await this.$testExecutionService.startKarmaServer(this.platform, this.$projectData, this.projectFilesConfig); } @@ -54,7 +56,8 @@ class TestAndroidCommand extends TestCommandBase implements ICommand { protected $options: IOptions, protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, - protected $cleanupService: ICleanupService) { + protected $cleanupService: ICleanupService, + protected $workflowService: IWorkflowService) { super(); } @@ -69,7 +72,8 @@ class TestIosCommand extends TestCommandBase implements ICommand { protected $options: IOptions, protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, - protected $cleanupService: ICleanupService) { + protected $cleanupService: ICleanupService, + protected $workflowService: IWorkflowService) { super(); } diff --git a/lib/commands/update.ts b/lib/commands/update.ts index 1649284c41..07054ac7be 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -12,7 +12,8 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma private $pluginsService: IPluginsService, private $projectDataService: IProjectDataService, private $fs: IFileSystem, - private $logger: ILogger) { + private $logger: ILogger, + private $workflowService: IWorkflowService) { super($options, $platformsData, $platformService, $projectData); this.$projectData.initializeProjectData(); } @@ -28,6 +29,12 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma static readonly backupFailMessage: string = "Could not backup project folders!"; public async execute(args: string[]): Promise { + if (this.$options.workflow) { + const forceWebpackWorkflow = true; + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, forceWebpackWorkflow); + return; + } + const tmpDir = path.join(this.$projectData.projectDir, UpdateCommand.tempFolder); try { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e048c14ba4..aef6e0fd86 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -571,6 +571,7 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai performance: Object; setupOptions(projectData: IProjectData): void; cleanupLogFile: string; + workflow: boolean; } interface IEnvOptions { @@ -940,6 +941,12 @@ interface IBundleValidatorHelper { * @return {void} */ validate(minSupportedVersion?: string): void; + + /** + * Returns the installed bundler version. + * @return {string} + */ + getBundlerDependencyVersion(bundlerName?: string): string; } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 6a02997efc..e9f29a99c8 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -14,6 +14,16 @@ interface IBuildPlatformAction { buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise; } +interface IWorkflowService { + handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, force?: boolean): Promise; +} + +interface IWebpackWorkflowSettings { + bundle?: boolean | string; + useHotModuleReload?: boolean; + release?: boolean; +} + interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, framework?: string): Promise; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 8de8d95e18..37be0d1158 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -142,6 +142,14 @@ interface IProjectDataService { */ setNSValue(projectDir: string, key: string, value: any): void; + /** + * Sets a value in the `useLegacyWorkflow` key in a project's nsconfig.json. + * @param {string} projectDir The project directory - the place where the root package.json is located. + * @param {any} value Value of the key to be set to `useLegacyWorkflow` key in project's nsconfig.json. + * @returns {void} + */ + setUseLegacyWorkflow(projectDir: string, value: any): void; + /** * Removes a property from `nativescript` key in project's package.json. * @param {string} projectDir The project directory - the place where the root package.json is located. @@ -584,7 +592,7 @@ interface IIOSExtensionsService { removeExtensions(options: IRemoveExtensionsOptions): void; } -interface IAddExtensionsFromPathOptions{ +interface IAddExtensionsFromPathOptions { extensionsFolderPath: string; projectData: IProjectData; platformData: IPlatformData; diff --git a/lib/helpers/bundle-validator-helper.ts b/lib/helpers/bundle-validator-helper.ts index 4eedac652b..2caf345a16 100644 --- a/lib/helpers/bundle-validator-helper.ts +++ b/lib/helpers/bundle-validator-helper.ts @@ -16,20 +16,28 @@ export class BundleValidatorHelper extends VersionValidatorHelper implements IBu public validate(minSupportedVersion?: string): void { if (this.$options.bundle) { - const bundlePluginName = this.bundlersMap[this.$options.bundle]; - const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName]; - const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName]; - if (!bundlePluginName || (!bundlerVersionInDependencies && !bundlerVersionInDevDependencies)) { + const currentVersion = this.getBundlerDependencyVersion(); + if (!currentVersion) { this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin); } - const currentVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies; const shouldThrowError = minSupportedVersion && this.isValidVersion(currentVersion) && this.isVersionLowerThan(currentVersion, minSupportedVersion); if (shouldThrowError) { - this.$errors.failWithoutHelp(util.format(BundleValidatorMessages.NotSupportedVersion, minSupportedVersion)); + this.$errors.failWithoutHelp(util.format(BundleValidatorMessages.NotSupportedVersion, minSupportedVersion)); } } } + + public getBundlerDependencyVersion(bundlerName?: string): string { + let dependencyVersion = null; + const bundlePluginName = bundlerName || this.bundlersMap[this.$options.bundle]; + const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName]; + const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName]; + dependencyVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies; + + return dependencyVersion; + + } } $injector.register("bundleValidatorHelper", BundleValidatorHelper); diff --git a/lib/options.ts b/lib/options.ts index b75042a669..44793f0fcb 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -136,6 +136,7 @@ export class Options { file: { type: OptionType.String, hasSensitiveValue: true }, force: { type: OptionType.Boolean, alias: "f", hasSensitiveValue: false }, // remove legacy + workflow: { type: OptionType.Boolean, hasSensitiveValue: false }, companion: { type: OptionType.Boolean, hasSensitiveValue: false }, emulator: { type: OptionType.Boolean, hasSensitiveValue: false }, sdk: { type: OptionType.String, hasSensitiveValue: false }, diff --git a/lib/project-data.ts b/lib/project-data.ts index 328d8a6198..0e3106b7e7 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -263,4 +263,4 @@ export class ProjectData implements IProjectData { this.$logger.warnWithLabel("IProjectData.projectId is deprecated. Please use IProjectData.projectIdentifiers[platform]."); } } -$injector.register("projectData", ProjectData); +$injector.register("projectData", ProjectData, true); diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index ad59828b55..b7db6b59ba 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -22,13 +22,15 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA private $previewAppFilesService: IPreviewAppFilesService, private $previewAppPluginsService: IPreviewAppPluginsService, private $previewDevicesService: IPreviewDevicesService, - private $hmrStatusService: IHmrStatusService) { - super(); - } + private $hmrStatusService: IHmrStatusService, + protected $workflowService: IWorkflowService) { + super(); + } @performanceLog() public async initialize(data: IPreviewAppLiveSyncData): Promise { await this.$previewSdkService.initialize(data.projectDir, async (device: Device) => { + await this.$workflowService.handleLegacyWorkflow(data.projectDir, data); try { if (!device) { this.$errors.failWithoutHelp("Sending initial preview files without a specified device is not supported."); @@ -177,7 +179,7 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA if (status === HmrConstants.HMR_ERROR_STATUS) { const originalUseHotModuleReload = data.useHotModuleReload; data.useHotModuleReload = false; - await this.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id ); + await this.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id); data.useHotModuleReload = originalUseHotModuleReload; } })); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 4129447144..b5d8a20885 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -36,7 +36,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $terminalSpinnerService: ITerminalSpinnerService, private $pacoteService: IPacoteService, private $usbLiveSyncService: any, - public $hooksService: IHooksService + public $hooksService: IHooksService, + public $workflowService: IWorkflowService ) { super(); } @@ -221,6 +222,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { @performanceLog() public async preparePlatform(platformInfo: IPreparePlatformInfo): Promise { + await this.$workflowService.handleLegacyWorkflow(platformInfo.projectData.projectDir, platformInfo.appFilesUpdaterOptions); const changesInfo = await this.getChangesInfo(platformInfo); const shouldPrepare = await this.shouldPrepare({ platformInfo, changesInfo }); diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 6a4cb20bbd..34e5f3e389 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,5 +1,7 @@ import * as path from "path"; +import * as constants from "../constants"; import { ProjectData } from "../project-data"; +import { parseJson } from "../common/helpers"; import { exported } from "../common/decorators"; import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, @@ -17,14 +19,21 @@ interface IProjectFileData { } export class ProjectDataService implements IProjectDataService { + private defaultProjectDir = ""; private static DEPENDENCIES_KEY_NAME = "dependencies"; + private projectDataCache: IDictionary = {}; constructor(private $fs: IFileSystem, private $staticConfig: IStaticConfig, private $logger: ILogger, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $androidResourcesMigrationService: IAndroidResourcesMigrationService, - private $injector: IInjector) { + private $injector: IInjector, + $projectData: IProjectData) { + // add the ProjectData of the default projectDir to the projectData cache + $projectData.initializeProjectData(); + this.defaultProjectDir = $projectData.projectDir; + this.projectDataCache[this.defaultProjectDir] = $projectData; } public getNSValue(projectDir: string, propertyName: string): any { @@ -49,16 +58,19 @@ export class ProjectDataService implements IProjectDataService { // TODO: Remove $projectData and replace it with $projectDataService.getProjectData @exported("projectDataService") public getProjectData(projectDir: string): IProjectData { - const projectDataInstance = this.$injector.resolve(ProjectData); - projectDataInstance.initializeProjectData(projectDir); - return projectDataInstance; + projectDir = projectDir || this.defaultProjectDir; + this.projectDataCache[projectDir] = this.projectDataCache[projectDir] || this.$injector.resolve(ProjectData); + this.projectDataCache[projectDir].initializeProjectData(projectDir); + + return this.projectDataCache[projectDir]; } @exported("projectDataService") public getProjectDataFromContent(packageJsonContent: string, nsconfigContent: string, projectDir?: string): IProjectData { - const projectDataInstance = this.$injector.resolve(ProjectData); - projectDataInstance.initializeProjectDataFromContent(packageJsonContent, nsconfigContent, projectDir); - return projectDataInstance; + projectDir = projectDir || this.defaultProjectDir; + this.projectDataCache[projectDir] = this.projectDataCache[projectDir] || this.$injector.resolve(ProjectData); + this.projectDataCache[projectDir].initializeProjectDataFromContent(packageJsonContent, nsconfigContent, projectDir); + return this.projectDataCache[projectDir]; } @exported("projectDataService") @@ -124,6 +136,14 @@ export class ProjectDataService implements IProjectDataService { }; } + public setUseLegacyWorkflow(projectDir: string, value: any): void { + // TODO: use trace + this.$logger.info(`useLegacyWorkflow will be set to ${value}`); + this.updateNsConfigValue(projectDir, { useLegacyWorkflow: value }); + this.refreshProjectData(projectDir); + this.$logger.info(`useLegacyWorkflow was set to ${value}`); + } + public getAppExecutableFiles(projectDir: string): string[] { const projectData = this.getProjectData(projectDir); @@ -157,6 +177,34 @@ export class ProjectDataService implements IProjectDataService { return files; } + private refreshProjectData(projectDir: string) { + if (this.projectDataCache[projectDir]) { + this.projectDataCache[projectDir].initializeProjectData(projectDir); + } + } + + private updateNsConfigValue(projectDir: string, updateObject: INsConfig): void { + const nsConfigPath = path.join(projectDir, constants.CONFIG_NS_FILE_NAME); + const currentNsConfig = this.getNsConfig(nsConfigPath); + const newNsConfig = Object.assign(currentNsConfig, updateObject); + + this.$fs.writeJson(nsConfigPath, newNsConfig); + } + + private getNsConfig(nsConfigPath: string): INsConfig { + let result = this.getNsConfigDefaultObject(); + if (this.$fs.exists(nsConfigPath)) { + const nsConfigContent = this.$fs.readText(nsConfigPath); + try { + result = parseJson(nsConfigContent); + } catch (e) { + // default + } + } + + return result; + } + private getImageDefinitions(): IImageDefinitionsStructure { const pathToImageDefinitions = path.join(__dirname, "..", "..", CLI_RESOURCES_DIR_NAME, AssetConstants.assets, AssetConstants.imageDefinitionsFileName); const imageDefinitions = this.$fs.readJson(pathToImageDefinitions); @@ -308,10 +356,16 @@ export class ProjectDataService implements IProjectDataService { }; } + private getNsConfigDefaultObject(data?: Object): INsConfig { + const config: INsConfig = { useLegacyWorkflow: false }; + Object.assign(config, data); + + return config; + } + @exported("projectDataService") public getNsConfigDefaultContent(data?: Object): string { - const config: INsConfig = {}; - Object.assign(config, data); + const config = this.getNsConfigDefaultObject(data); return JSON.stringify(config); } diff --git a/lib/services/workflow-service.ts b/lib/services/workflow-service.ts new file mode 100644 index 0000000000..bc3a023b72 --- /dev/null +++ b/lib/services/workflow-service.ts @@ -0,0 +1,102 @@ +import * as helpers from "../common/helpers"; +import * as path from "path"; +import * as semver from "semver"; + +export class WorkflowService implements IWorkflowService { + constructor(private $bundleValidatorHelper: IBundleValidatorHelper, + private $fs: IFileSystem, + private $logger: ILogger, + private $packageManager: INodePackageManager, + private $projectDataService: IProjectDataService, + private $prompter: IPrompter, + private $options: IOptions + ) { + } + + public async handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, force?: boolean): Promise { + if (!settings.bundle || force) { + const projectData = this.$projectDataService.getProjectData(projectDir); + if (projectData.useLegacyWorkflow === null || projectData.useLegacyWorkflow === undefined || force) { + const hasSwitched = await this.handleWebpackWorkflowSwitch(projectData, force); + if (hasSwitched) { + this.$options.bundle = "webpack"; + this.$options.hmr = !settings.release; + if (typeof (settings.bundle) === "boolean") { + settings.bundle = true; + } else { + settings.bundle = this.$options.bundle; + } + settings.useHotModuleReload = this.$options.hmr; + } + } else if (projectData.useLegacyWorkflow === true) { + this.showLegacyWorkflowWarning(); + } else { + this.showNoBundleWarning(); + } + } + } + + private async handleWebpackWorkflowSwitch(projectData: IProjectData, force: boolean): Promise { + let hasSwitched = false; + if (force || helpers.isInteractive()) { + hasSwitched = force || await this.$prompter.confirm("Please use webpack!", () => true); + if (hasSwitched) { + this.$projectDataService.setUseLegacyWorkflow(projectData.projectDir, false); + await this.ensureWebpackPluginInstalled(projectData); + } else { + this.$projectDataService.setUseLegacyWorkflow(projectData.projectDir, true); + await this.showLegacyWorkflowWarning(); + } + } else { + await this.showLegacyWorkflowWarning(); + } + + return hasSwitched; + } + + private async showLegacyWorkflowWarning() { + this.$logger.warn("WARNINGGGGG LEGACY TRUE!!!"); + } + + private showNoBundleWarning() { + this.$logger.warn("WARNINGGGGG NO BUNDLE!!!"); + } + + private async ensureWebpackPluginInstalled(projectData: IProjectData) { + const hmrOutOfBetaWebpackPluginVersion = "0.21.0"; + const webpackPluginName = "nativescript-dev-webpack"; + const webpackConfigFileName = "webpack.config.js"; + const validWebpackPluginTags = ["*", "latest", "next", "rc"]; + + let isInstalledVersionSupported = true; + const installedVersion = this.$bundleValidatorHelper.getBundlerDependencyVersion(webpackPluginName); + // TODO: use trace + this.$logger.info(`Updating to webpack workflow: Found ${webpackPluginName} v${installedVersion}`); + if (validWebpackPluginTags.indexOf(installedVersion) === -1) { + const isInstalledVersionValid = !!semver.valid(installedVersion) || !!semver.coerce(installedVersion); + isInstalledVersionSupported = + isInstalledVersionValid && semver.gte(semver.coerce(installedVersion), hmrOutOfBetaWebpackPluginVersion); + this.$logger.info(`Updating to webpack workflow: Is installedVersion valid: ${isInstalledVersionValid}`); + } + + this.$logger.info(`Updating to webpack workflow: Is installedVersion supported: ${isInstalledVersionSupported}`); + if (!isInstalledVersionSupported) { + const webpackConfigPath = path.join(projectData.projectDir, webpackConfigFileName); + if (this.$fs.exists(webpackConfigPath)) { + this.$logger.info(`Your Webpack config was stored to .bak!!`); + this.$fs.rename(webpackConfigPath, `${webpackConfigPath}.bak`); + } + + const installResult = await this.$packageManager.install(`${webpackPluginName}@latest`, projectData.projectDir, { + 'save-dev': true, + 'save-exact': true, + disableNpmInstall: false, + frameworkPath: null, + ignoreScripts: false, + }); + this.$logger.info(`Updating to webpack workflow: The ${webpackPluginName} was updated to v${installResult.version}`); + } + } +} + +$injector.register("workflowService", WorkflowService); diff --git a/test/stubs.ts b/test/stubs.ts index 828fcc1646..1f775f69f8 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -497,6 +497,9 @@ export class PlatformsDataStub extends EventEmitter implements IPlatformsData { } export class ProjectDataService implements IProjectDataService { + setUseLegacyWorkflow(projectDir: string, value: any): Promise { + return; + } getNSValue(propertyName: string): any { return {}; } From dfbc0872c6a3d17984343568904f304ea248156c Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 18 Apr 2019 11:57:01 +0300 Subject: [PATCH 03/62] fix: recommend the new workflow before executing run/test/debug/prepare/build/preview --- lib/commands/build.ts | 22 +++++++++++-------- lib/commands/debug.ts | 4 +++- lib/commands/prepare.ts | 8 ++++--- lib/commands/preview.ts | 2 +- lib/commands/run.ts | 5 ++++- lib/commands/test.ts | 2 +- lib/definitions/platform.d.ts | 2 +- lib/services/project-data-service.ts | 5 ++--- lib/services/workflow-service.ts | 32 +++++++++++++--------------- 9 files changed, 45 insertions(+), 37 deletions(-) diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 33d7db7639..bdad8cfe02 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -9,12 +9,14 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService, private $bundleValidatorHelper: IBundleValidatorHelper, - protected $logger: ILogger) { - super($options, $platformsData, $platformService, $projectData); - this.$projectData.initializeProjectData(); + protected $logger: ILogger, + protected $workflowService: IWorkflowService) { + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } public async executeCore(args: string[]): Promise { + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); const platform = args[0].toLowerCase(); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: !!this.$options.bundle, @@ -94,8 +96,9 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService, $bundleValidatorHelper: IBundleValidatorHelper, - $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $logger); + $logger: ILogger, + $workflowService: IWorkflowService) { + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $logger, $workflowService); } public async execute(args: string[]): Promise { @@ -107,7 +110,7 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { super.validatePlatform(platform); - let result = await super.canExecuteCommandBase(platform, { notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true }}); + let result = await super.canExecuteCommandBase(platform, { notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true } }); if (result.canExecute) { result = await super.validateArgs(args, platform); } @@ -129,8 +132,9 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $platformService: IPlatformService, $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, - protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $logger); + protected $logger: ILogger, + $workflowService: IWorkflowService) { + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $logger, $workflowService); } public async execute(args: string[]): Promise { @@ -149,7 +153,7 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { const platform = this.$devicePlatformsConstants.Android; super.validatePlatform(platform); this.$androidBundleValidatorHelper.validateRuntimeVersion(this.$projectData); - let result = await super.canExecuteCommandBase(platform, { notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true }}); + let result = await super.canExecuteCommandBase(platform, { notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true } }); if (result.canExecute) { if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index ab62e8ac56..f53a40bc2a 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -18,11 +18,13 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements private $debugDataService: IDebugDataService, private $liveSyncService: IDebugLiveSyncService, private $liveSyncCommandHelper: ILiveSyncCommandHelper, - private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { + private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, + private $workflowService: IWorkflowService) { super($options, $platformsData, $platformService, $projectData); } public async execute(args: string[]): Promise { + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); await this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device, diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 409ddf16c6..91e8734411 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -7,12 +7,14 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm $platformService: IPlatformService, $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, - $platformsData: IPlatformsData) { - super($options, $platformsData, $platformService, $projectData); - this.$projectData.initializeProjectData(); + $platformsData: IPlatformsData, + private $workflowService: IWorkflowService) { + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: !!this.$options.bundle, release: this.$options.release, diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 4a77d6a294..47520544b0 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -21,7 +21,7 @@ export class PreviewCommand implements ICommand { } public async execute(): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options); + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); this.$previewAppLogProvider.on(DEVICE_LOG_EVENT_NAME, (deviceId: string, message: string) => { this.$logger.info(message); }); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 2e87e58376..af0846f9e2 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -13,10 +13,13 @@ export class RunCommandBase implements ICommand { private $errors: IErrors, private $hostInfo: IHostInfo, private $liveSyncCommandHelper: ILiveSyncCommandHelper, - private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { } + private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, + private $options: IOptions, + private $workflowService: IWorkflowService) { } public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); await this.$analyticsService.trackPreviewAppData(this.platform, this.$projectData.projectDir); return this.$liveSyncCommandHelper.executeCommandLiveSync(this.platform, this.liveSyncCommandHelperAdditionalOptions); } diff --git a/lib/commands/test.ts b/lib/commands/test.ts index f855972076..50b4e3a110 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -14,7 +14,7 @@ abstract class TestCommandBase { protected abstract $workflowService: IWorkflowService; async execute(args: string[]): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options); + await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); await this.$testExecutionService.startKarmaServer(this.platform, this.$projectData, this.projectFilesConfig); } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index e9f29a99c8..c1a1973bed 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -15,7 +15,7 @@ interface IBuildPlatformAction { } interface IWorkflowService { - handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, force?: boolean): Promise; + handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, skipWarnings?: boolean, force?: boolean): Promise; } interface IWebpackWorkflowSettings { diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 34e5f3e389..2a22bf4f98 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -137,11 +137,10 @@ export class ProjectDataService implements IProjectDataService { } public setUseLegacyWorkflow(projectDir: string, value: any): void { - // TODO: use trace - this.$logger.info(`useLegacyWorkflow will be set to ${value}`); + this.$logger.trace(`useLegacyWorkflow will be set to ${value}`); this.updateNsConfigValue(projectDir, { useLegacyWorkflow: value }); this.refreshProjectData(projectDir); - this.$logger.info(`useLegacyWorkflow was set to ${value}`); + this.$logger.trace(`useLegacyWorkflow was set to ${value}`); } public getAppExecutableFiles(projectDir: string): string[] { diff --git a/lib/services/workflow-service.ts b/lib/services/workflow-service.ts index bc3a023b72..680bf6d7c0 100644 --- a/lib/services/workflow-service.ts +++ b/lib/services/workflow-service.ts @@ -13,11 +13,11 @@ export class WorkflowService implements IWorkflowService { ) { } - public async handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, force?: boolean): Promise { + public async handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, skipWarnings?: boolean, force?: boolean): Promise { if (!settings.bundle || force) { const projectData = this.$projectDataService.getProjectData(projectDir); - if (projectData.useLegacyWorkflow === null || projectData.useLegacyWorkflow === undefined || force) { - const hasSwitched = await this.handleWebpackWorkflowSwitch(projectData, force); + if (typeof (projectData.useLegacyWorkflow) !== "boolean" || force) { + const hasSwitched = await this.handleWebpackWorkflowSwitch(projectData, skipWarnings, force); if (hasSwitched) { this.$options.bundle = "webpack"; this.$options.hmr = !settings.release; @@ -28,15 +28,15 @@ export class WorkflowService implements IWorkflowService { } settings.useHotModuleReload = this.$options.hmr; } - } else if (projectData.useLegacyWorkflow === true) { + } else if (!skipWarnings && projectData.useLegacyWorkflow === true) { this.showLegacyWorkflowWarning(); - } else { + } else if (!skipWarnings && projectData.useLegacyWorkflow === false) { this.showNoBundleWarning(); } } } - private async handleWebpackWorkflowSwitch(projectData: IProjectData, force: boolean): Promise { + private async handleWebpackWorkflowSwitch(projectData: IProjectData, skipWarnings: boolean, force: boolean): Promise { let hasSwitched = false; if (force || helpers.isInteractive()) { hasSwitched = force || await this.$prompter.confirm("Please use webpack!", () => true); @@ -45,21 +45,20 @@ export class WorkflowService implements IWorkflowService { await this.ensureWebpackPluginInstalled(projectData); } else { this.$projectDataService.setUseLegacyWorkflow(projectData.projectDir, true); - await this.showLegacyWorkflowWarning(); } - } else { + } else if (!skipWarnings) { await this.showLegacyWorkflowWarning(); } return hasSwitched; } - private async showLegacyWorkflowWarning() { - this.$logger.warn("WARNINGGGGG LEGACY TRUE!!!"); + private showLegacyWorkflowWarning() { + this.$logger.warn("TODO: "); } private showNoBundleWarning() { - this.$logger.warn("WARNINGGGGG NO BUNDLE!!!"); + this.$logger.warn("TODO: "); } private async ensureWebpackPluginInstalled(projectData: IProjectData) { @@ -70,20 +69,19 @@ export class WorkflowService implements IWorkflowService { let isInstalledVersionSupported = true; const installedVersion = this.$bundleValidatorHelper.getBundlerDependencyVersion(webpackPluginName); - // TODO: use trace - this.$logger.info(`Updating to webpack workflow: Found ${webpackPluginName} v${installedVersion}`); + this.$logger.trace(`Updating to webpack workflow: Found ${webpackPluginName} v${installedVersion}`); if (validWebpackPluginTags.indexOf(installedVersion) === -1) { const isInstalledVersionValid = !!semver.valid(installedVersion) || !!semver.coerce(installedVersion); isInstalledVersionSupported = isInstalledVersionValid && semver.gte(semver.coerce(installedVersion), hmrOutOfBetaWebpackPluginVersion); - this.$logger.info(`Updating to webpack workflow: Is installedVersion valid: ${isInstalledVersionValid}`); + this.$logger.trace(`Updating to webpack workflow: Is installed version valid?: ${isInstalledVersionValid}`); } - this.$logger.info(`Updating to webpack workflow: Is installedVersion supported: ${isInstalledVersionSupported}`); + this.$logger.trace(`Updating to webpack workflow: Is installed version supported?: ${isInstalledVersionSupported}`); if (!isInstalledVersionSupported) { const webpackConfigPath = path.join(projectData.projectDir, webpackConfigFileName); if (this.$fs.exists(webpackConfigPath)) { - this.$logger.info(`Your Webpack config was stored to .bak!!`); + this.$logger.info(``); this.$fs.rename(webpackConfigPath, `${webpackConfigPath}.bak`); } @@ -94,7 +92,7 @@ export class WorkflowService implements IWorkflowService { frameworkPath: null, ignoreScripts: false, }); - this.$logger.info(`Updating to webpack workflow: The ${webpackPluginName} was updated to v${installResult.version}`); + this.$logger.trace(`Updating to webpack workflow: The ${webpackPluginName} was updated to v${installResult.version}`); } } } From 28390c1291e2f5f02fa46255b7c20fadcccc35e4 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 18 Apr 2019 11:58:58 +0300 Subject: [PATCH 04/62] fix: fix upload to appstore for accounts without 2 factor authentication --- lib/bootstrap.ts | 4 + lib/commands/appstore-list.ts | 18 +- lib/commands/appstore-upload.ts | 11 +- lib/declarations.d.ts | 11 +- .../apple-portal-application-service.ts | 54 +++++ .../apple-portal-cookie-service.ts | 41 ++++ .../apple-portal-session-service.ts | 95 +++++++++ lib/services/apple-portal/definitions.d.ts | 72 +++++++ lib/services/itmstransporter-service.ts | 197 +++++------------- npm-shrinkwrap.json | 2 +- package.json | 2 +- 11 files changed, 346 insertions(+), 161 deletions(-) create mode 100644 lib/services/apple-portal/apple-portal-application-service.ts create mode 100644 lib/services/apple-portal/apple-portal-cookie-service.ts create mode 100644 lib/services/apple-portal/apple-portal-session-service.ts create mode 100644 lib/services/apple-portal/definitions.d.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index cc08b41de5..7acde30d4a 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -192,3 +192,7 @@ $injector.require("qrCodeTerminalService", "./services/qr-code-terminal-service" $injector.require("testInitializationService", "./services/test-initialization-service"); $injector.require("networkConnectivityValidator", "./helpers/network-connectivity-validator"); + +$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"); diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 6313a2f64b..c88bcaee82 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -5,7 +5,7 @@ export class ListiOSApps implements ICommand { public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)]; constructor(private $injector: IInjector, - private $itmsTransporterService: IITMSTransporterService, + private $applePortalApplicationService: IApplePortalApplicationService, private $logger: ILogger, private $projectData: IProjectData, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, @@ -31,18 +31,26 @@ export class ListiOSApps implements ICommand { password = await this.$prompter.getPassword("Apple ID password"); } - const iOSApplications = await this.$itmsTransporterService.getiOSApplications({ username, password }); + const applications = await this.$applePortalApplicationService.getApplications({ username, password }); - if (!iOSApplications || !iOSApplications.length) { + if (!applications || !applications.length) { this.$logger.out("Seems you don't have any applications yet."); } else { - const table: any = createTable(["Application Name", "Bundle Identifier", "Version"], iOSApplications.map(element => { - return [element.name, element.bundleId, element.version]; + const table: any = createTable(["Application Name", "Bundle Identifier", "In Flight Version"], applications.map(application => { + return [application.name, application.bundleId, this.getVersion(application)]; })); this.$logger.out(table.toString()); } } + + private getVersion(application: IApplePortalApplicationSummary): string { + if (application && application.versionSets && application.versionSets.length && application.versionSets[0].inFlightVersion) { + return application.versionSets[0].inFlightVersion.version; + } + + return ""; + } } $injector.registerCommand("appstore|*list", ListiOSApps); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 03168cde65..70f21385a3 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -6,14 +6,16 @@ export class PublishIOS implements ICommand { public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)]; - constructor(private $errors: IErrors, + constructor( private $injector: IInjector, private $itmsTransporterService: IITMSTransporterService, private $logger: ILogger, private $projectData: IProjectData, private $options: IOptions, private $prompter: IPrompter, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $hostInfo: IHostInfo, + private $errors: IErrors) { this.$projectData.initializeProjectData(); } @@ -107,11 +109,16 @@ export class PublishIOS implements ICommand { username, password, ipaFilePath, + ipaFileOption: !!this.$options.ipa, verboseLogging: this.$logger.getLevel() === "TRACE" }); } public async canExecute(args: string[]): Promise { + if (!this.$hostInfo.isDarwin) { + this.$errors.failWithoutHelp("iOS publishing is only available on Mac OS X."); + } + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e7c4a4456d..25f6ed06f9 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -622,6 +622,11 @@ interface IITMSData extends ICredentials { * @type {string} */ ipaFilePath: string; + + /** + * Specified if the ipa options is provided + */ + ipaFileOption: boolean; /** * Specifies whether the logging level of the itmstransporter command-line tool should be set to verbose. * @type {string} @@ -639,12 +644,6 @@ interface IITMSTransporterService { * @return {Promise} */ upload(data: IITMSData): Promise; - /** - * Queries Apple's content delivery API to get the user's registered iOS applications. - * @param {ICredentials} credentials Credentials for authentication with iTunes Connect. - * @return {Promise} The user's iOS applications. - */ - getiOSApplications(credentials: ICredentials): Promise; } /** diff --git a/lib/services/apple-portal/apple-portal-application-service.ts b/lib/services/apple-portal/apple-portal-application-service.ts new file mode 100644 index 0000000000..791e420bb1 --- /dev/null +++ b/lib/services/apple-portal/apple-portal-application-service.ts @@ -0,0 +1,54 @@ +export class ApplePortalApplicationService implements IApplePortalApplicationService { + constructor( + private $applePortalSessionService: IApplePortalSessionService, + private $errors: IErrors, + private $httpClient: Server.IHttpClient + ) { } + + public async getApplications(credentials: ICredentials): Promise { + let result: IApplePortalApplicationSummary[] = []; + + const user = await this.$applePortalSessionService.createUserSession(credentials); + for (const account of user.associatedAccounts) { + const contentProviderId = account.contentProvider.contentProviderId; + const dsId = user.sessionToken.dsId; + const applications = await this.getApplicationsByProvider(contentProviderId, dsId); + result = result.concat(applications.summaries); + } + + return result; + } + + public async getApplicationsByProvider(contentProviderId: number, dsId: string): Promise { + const webSessionCookie = await this.$applePortalSessionService.createWebSession(contentProviderId, dsId); + const response = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/manageyourapps/summary/v2", + method: "GET", + body: JSON.stringify({ + contentProviderId + }), + headers: { + 'Content-Type': 'application/json', + 'Cookie': webSessionCookie + } + }); + + return JSON.parse(response.body).data; + } + + public async getApplicationByBundleId(credentials: ICredentials, bundleId: string): Promise { + const applications = await this.getApplications(credentials); + if (!applications || !applications.length) { + this.$errors.failWithoutHelp(`Cannot find any registered applications for Apple ID ${credentials.username} in iTunes Connect.`); + } + + const application = _.find(applications, app => app.bundleId === bundleId); + + if (!application) { + this.$errors.failWithoutHelp(`Cannot find registered applications that match the specified identifier ${bundleId} in iTunes Connect.`); + } + + return application; + } +} +$injector.register("applePortalApplicationService", ApplePortalApplicationService); diff --git a/lib/services/apple-portal/apple-portal-cookie-service.ts b/lib/services/apple-portal/apple-portal-cookie-service.ts new file mode 100644 index 0000000000..241652cd82 --- /dev/null +++ b/lib/services/apple-portal/apple-portal-cookie-service.ts @@ -0,0 +1,41 @@ +export class ApplePortalCookieService implements IApplePortalCookieService { + private userSessionCookies: IStringDictionary = {}; + private validUserSessionCookieNames = ["myacinfo", "dqsid", "itctx", "itcdq", "acn01"]; + private validWebSessionCookieNames = ["wosid", "woinst", "itctx"]; + + public getWebSessionCookie(cookiesData: string[]): string { + const webSessionCookies = _.cloneDeep(this.userSessionCookies); + + const parsedCookies = this.parseCookiesData(cookiesData, this.validWebSessionCookieNames); + _.each(parsedCookies, parsedCookie => webSessionCookies[parsedCookie.key] = parsedCookie.cookie); + + return _.values(webSessionCookies).join("; "); + } + + public getUserSessionCookie(): string { + return _.values(this.userSessionCookies).join("; "); + } + + public updateUserSessionCookie(cookiesData: string[]): void { + const parsedCookies = this.parseCookiesData(cookiesData, this.validUserSessionCookieNames); + _.each(parsedCookies, parsedCookie => this.userSessionCookies[parsedCookie.key] = parsedCookie.cookie); + } + + private parseCookiesData(cookiesData: string[], validCookieNames: string[]): IDictionary<{key: string, value: string, cookie: string}> { + const result: IDictionary<{key: string, value: string, cookie: string}> = {}; + + for (const c of cookiesData) { + const parts = c.split(";"); + for (const cookie of parts) { + const trimmedCookie = cookie.trim(); + const [cookieKey, cookieValue] = trimmedCookie.split("="); + if (_.includes(validCookieNames, cookieKey)) { + result[cookieKey] = { key: cookieKey, value: cookieValue, cookie: trimmedCookie }; + } + } + } + + return result; + } +} +$injector.register("applePortalCookieService", ApplePortalCookieService); diff --git a/lib/services/apple-portal/apple-portal-session-service.ts b/lib/services/apple-portal/apple-portal-session-service.ts new file mode 100644 index 0000000000..83adf60272 --- /dev/null +++ b/lib/services/apple-portal/apple-portal-session-service.ts @@ -0,0 +1,95 @@ +export class ApplePortalSessionService implements IApplePortalSessionService { + private loginConfigEndpoint = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com"; + private defaultLoginConfig = { + authServiceUrl : "https://idmsa.apple.com/appleautcodh", + authServiceKey : "e0b80c3bf78523bfe80974d320935bfa30add02e1bff88ec2166c6bd5a706c42" + }; + + constructor( + private $applePortalCookieService: IApplePortalCookieService, + private $httpClient: Server.IHttpClient, + private $logger: ILogger + ) { } + + public async createUserSession(credentials: ICredentials): Promise { + const loginConfig = await this.getLoginConfig(); + const loginUrl = `${loginConfig.authServiceUrl}/auth/signin`; + const loginResponse = await this.$httpClient.httpRequest({ + url: loginUrl, + method: "POST", + body: JSON.stringify({ + accountName: credentials.username, + password: credentials.password, + rememberMe: true + }), + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + 'X-Apple-Widget-Key': loginConfig.authServiceKey, + 'Accept': 'application/json, text/javascript' + } + }); + + this.$applePortalCookieService.updateUserSessionCookie(loginResponse.headers["set-cookie"]); + + const sessionResponse = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/olympus/v1/session", + method: "GET", + headers: { + 'Cookie': this.$applePortalCookieService.getUserSessionCookie() + } + }); + + this.$applePortalCookieService.updateUserSessionCookie(sessionResponse.headers["set-cookie"]); + + const userDetailResponse = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail", + method: "GET", + headers: { + 'Content-Type': 'application/json', + 'Cookie': this.$applePortalCookieService.getUserSessionCookie(), + } + }); + + this.$applePortalCookieService.updateUserSessionCookie(userDetailResponse.headers["set-cookie"]); + + return JSON.parse(userDetailResponse.body).data; + } + + public async createWebSession(contentProviderId: number, dsId: string): Promise { + const webSessionResponse = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/v1/session/webSession", + method: "POST", + body: JSON.stringify({ + contentProviderId, + dsId, + ipAddress: null + }), + headers: { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Encoding': 'gzip, deflate, br', + 'X-Csrf-Itc': 'itc', + 'Content-Type': 'application/json;charset=UTF-8', + 'Cookie': this.$applePortalCookieService.getUserSessionCookie() + } + }); + + const webSessionCookie = this.$applePortalCookieService.getWebSessionCookie(webSessionResponse.headers["set-cookie"]); + + return webSessionCookie; + } + + private async getLoginConfig(): Promise<{authServiceUrl: string, authServiceKey: string}> { + let config = null; + + try { + const response = await this.$httpClient.httpRequest({ url: this.loginConfigEndpoint, method: "GET" }); + config = JSON.parse(response.body); + } catch (err) { + this.$logger.trace(`Error while executing request to ${this.loginConfigEndpoint}. More info: ${err}`); + } + + return config || this.defaultLoginConfig; + } +} +$injector.register("applePortalSessionService", ApplePortalSessionService); diff --git a/lib/services/apple-portal/definitions.d.ts b/lib/services/apple-portal/definitions.d.ts new file mode 100644 index 0000000000..fdaf16968e --- /dev/null +++ b/lib/services/apple-portal/definitions.d.ts @@ -0,0 +1,72 @@ +interface IApplePortalSessionService { + createUserSession(credentials: ICredentials): Promise; + createWebSession(contentProviderId: number, dsId: string): Promise; +} + +interface IApplePortalCookieService { + getWebSessionCookie(cookiesData: string[]): string; + getUserSessionCookie(): string; + updateUserSessionCookie(cookie: string[]): void; +} + +interface IApplePortalApplicationService { + getApplications(credentials: ICredentials): Promise + getApplicationsByProvider(contentProviderId: number, dsId: string): Promise; + getApplicationByBundleId(credentials: ICredentials, bundleId: string): Promise; +} + +interface IApplePortalUserDetail { + associatedAccounts: IApplePortalAssociatedAccountData[]; + sessionToken: { + dsId: string; + contentProviderId: number; + ipAddress: string; + } + contentProviderFeatures: string[]; + contentProviderId: number; + firstname: string; + displayName: string; + userName: string; + userId: string; + contentProvider: string; + visibility: boolean; + DYCVisibility: boolean; +} + +interface IApplePortalAssociatedAccountData { + contentProvider: { + name: string; + contentProviderId: number; + contentProviderPublicId: string; + contentProviderTypes: string[]; + }; + roles: string[]; + lastLogin: number; +} + +interface IApplePortalApplication { + summaries: IApplePortalApplicationSummary[]; + showSharedSecret: boolean; + macBundlesEnabled: boolean; + canCreateMacApps: boolean; + cloudStorageEnabled: boolean; + sharedSecretLink: string; + gameCenterGroupLink: string; + enabledPlatforms: string[]; + cloudStorageLink: string; + catalogReportsLink: string; + canCreateIOSApps: boolean; +} + +interface IApplePortalApplicationSummary { + name: string; + adamId: string; + vendorId: string; + bundleId: string; + appType: any; + versionSets: any[]; + lastModifiedDate: number; + iconUrl: string; + issuesCount: number; + priceTier: string; +} \ No newline at end of file diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index 1536c4d1d8..607081bf41 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -1,23 +1,18 @@ import * as path from "path"; import * as temp from "temp"; -import { EOL } from "os"; import { ITMSConstants, INFO_PLIST_FILE_NAME } from "../constants"; -import { ItunesConnectApplicationTypes } from "../constants"; -import { quoteString, versionCompare } from "../common/helpers"; +import { quoteString } from "../common/helpers"; +import { cache } from "../common/decorators"; export class ITMSTransporterService implements IITMSTransporterService { - private _itmsTransporterPath: string = null; - private _itunesConnectApplications: IiTunesConnectApplication[] = null; - private _bundleIdentifier: string = null; - - constructor(private $plistParser: IPlistParser, + constructor( + private $applePortalApplicationService: IApplePortalApplicationService, private $childProcess: IChildProcess, private $errors: IErrors, private $fs: IFileSystem, - private $hostInfo: IHostInfo, - private $httpClient: Server.IHttpClient, private $injector: IInjector, private $logger: ILogger, + private $plistParser: IPlistParser, private $xcodeSelectService: IXcodeSelectService) { } private get $projectData(): IProjectData { @@ -25,171 +20,81 @@ export class ITMSTransporterService implements IITMSTransporterService { } public async upload(data: IITMSData): Promise { - if (!this.$hostInfo.isDarwin) { - this.$errors.failWithoutHelp("iOS publishing is only available on Mac OS X."); - } - temp.track(); - const itmsTransporterPath = await this.getITMSTransporterPath(), - ipaFileName = "app.ipa", - itmsDirectory = temp.mkdirSync("itms-"), - innerDirectory = path.join(itmsDirectory, "mybundle.itmsp"), - ipaFileLocation = path.join(innerDirectory, ipaFileName), - loggingLevel = data.verboseLogging ? ITMSConstants.VerboseLoggingLevels.Verbose : ITMSConstants.VerboseLoggingLevels.Informational, - bundleId = await this.getBundleIdentifier(data.ipaFilePath), - iOSApplication = await this.getiOSApplication(data.username, data.password, bundleId); + const itmsTransporterPath = await this.getITMSTransporterPath(); + const ipaFileName = "app.ipa"; + const itmsDirectory = temp.mkdirSync("itms-"); + const innerDirectory = path.join(itmsDirectory, "mybundle.itmsp"); + const ipaFileLocation = path.join(innerDirectory, ipaFileName); + const loggingLevel = data.verboseLogging ? ITMSConstants.VerboseLoggingLevels.Verbose : ITMSConstants.VerboseLoggingLevels.Informational; + const bundleId = await this.getBundleIdentifier(data); + const application = await this.$applePortalApplicationService.getApplicationByBundleId(data, bundleId); this.$fs.createDirectory(innerDirectory); this.$fs.copyFile(data.ipaFilePath, ipaFileLocation); - const ipaFileHash = await this.$fs.getFileShasum(ipaFileLocation, { algorithm: "md5" }), - ipaFileSize = this.$fs.getFileSize(ipaFileLocation), - metadata = this.getITMSMetadataXml(iOSApplication.adamId, ipaFileName, ipaFileHash, ipaFileSize); + const ipaFileHash = await this.$fs.getFileShasum(ipaFileLocation, { algorithm: "md5" }); + const ipaFileSize = this.$fs.getFileSize(ipaFileLocation); + const metadata = this.getITMSMetadataXml(application.adamId, ipaFileName, ipaFileHash, ipaFileSize); this.$fs.writeFile(path.join(innerDirectory, ITMSConstants.ApplicationMetadataFile), metadata); await this.$childProcess.spawnFromEvent(itmsTransporterPath, ["-m", "upload", "-f", itmsDirectory, "-u", quoteString(data.username), "-p", quoteString(data.password), "-v", loggingLevel], "close", { stdio: "inherit" }); } - public async getiOSApplications(credentials: ICredentials): Promise { - if (!this._itunesConnectApplications) { - const requestBody = this.getContentDeliveryRequestBody(credentials), - contentDeliveryResponse = await this.$httpClient.httpRequest({ - url: "https://contentdelivery.itunes.apple.com/WebObjects/MZLabelService.woa/json/MZITunesProducerService", - method: "POST", - body: requestBody, - headers: { - "Content-Length": requestBody.length - } - }), - contentDeliveryBody: IContentDeliveryBody = JSON.parse(contentDeliveryResponse.body); - - if (!contentDeliveryBody.result.Success || !contentDeliveryBody.result.Applications) { - let errorMessage = ["Unable to connect to iTunes Connect"]; - if (contentDeliveryBody.result.Errors && contentDeliveryBody.result.Errors.length) { - errorMessage = errorMessage.concat(contentDeliveryBody.result.Errors); - } - - this.$errors.failWithoutHelp(errorMessage.join(EOL)); + private async getBundleIdentifier(data: IITMSData): Promise { + const { ipaFileOption, ipaFilePath } = data; + + if (ipaFileOption) { + if (!this.$fs.exists(ipaFilePath) || path.extname(ipaFilePath) !== ".ipa") { + this.$errors.failWithoutHelp(`Cannot use specified ipa file ${ipaFilePath}. File either does not exist or is not an ipa file.`); } - this._itunesConnectApplications = contentDeliveryBody.result.Applications.filter(app => app.type === ItunesConnectApplicationTypes.iOS); - } + this.$logger.trace("--ipa set - extracting .ipa file to get app's bundle identifier"); + temp.track(); + const destinationDir = temp.mkdirSync("ipa-"); + await this.$fs.unzip(ipaFilePath, destinationDir); - return this._itunesConnectApplications; - } + const payloadDir = path.join(destinationDir, "Payload"); + let allFiles = this.$fs.readDirectory(payloadDir); - /** - * Gets iTunes Connect application corresponding to the given bundle identifier. - * @param {string} username For authentication with iTunes Connect. - * @param {string} password For authentication with iTunes Connect. - * @param {string} bundleId Application's Bundle Identifier - * @return {IFuture} The iTunes Connect application. - */ - private async getiOSApplication(username: string, password: string, bundleId: string): Promise { - const iOSApplications = await this.getiOSApplications({ username, password }); - if (!iOSApplications || !iOSApplications.length) { - this.$errors.failWithoutHelp(`Cannot find any registered applications for Apple ID ${username} in iTunes Connect.`); - } + this.$logger.debug("ITMSTransporter .ipa Payload files:"); + allFiles.forEach(f => this.$logger.debug(" - " + f)); - const iOSApplication = _.find(iOSApplications, app => app.bundleId === bundleId); + allFiles = allFiles.filter(f => path.extname(f).toLowerCase() === ".app"); + if (allFiles.length > 1) { + this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there is more than one .app file. We don't know which one to upload."); + } else if (allFiles.length <= 0) { + this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there must be at least one .app file."); + } + const appFile = path.join(payloadDir, allFiles[0]); - if (!iOSApplication) { - this.$errors.failWithoutHelp(`Cannot find registered applications that match the specified identifier ${bundleId} in iTunes Connect.`); - } + const plistObject = await this.$plistParser.parseFile(path.join(appFile, INFO_PLIST_FILE_NAME)); + const bundleId = plistObject && plistObject.CFBundleIdentifier; + if (!bundleId) { + this.$errors.failWithoutHelp(`Unable to determine bundle identifier from ${ipaFilePath}.`); + } - return iOSApplication; - } + this.$logger.trace(`bundle identifier determined to be ${bundleId}`); - /** - * Gets the application's bundle identifier. If ipaFileFullPath is provided will extract the bundle identifier from the .ipa file. - * @param {string} ipaFileFullPath Optional full path to .ipa file - * @return {IFuture} Application's bundle identifier. - */ - private async getBundleIdentifier(ipaFileFullPath?: string): Promise { - if (!this._bundleIdentifier) { - if (!ipaFileFullPath) { - this._bundleIdentifier = this.$projectData.projectIdentifiers.ios; - } else { - if (!this.$fs.exists(ipaFileFullPath) || path.extname(ipaFileFullPath) !== ".ipa") { - this.$errors.failWithoutHelp(`Cannot use specified ipa file ${ipaFileFullPath}. File either does not exist or is not an ipa file.`); - } - - this.$logger.trace("--ipa set - extracting .ipa file to get app's bundle identifier"); - temp.track(); - const destinationDir = temp.mkdirSync("ipa-"); - await this.$fs.unzip(ipaFileFullPath, destinationDir); - - const payloadDir = path.join(destinationDir, "Payload"); - let allApps = this.$fs.readDirectory(payloadDir); - - this.$logger.debug("ITMSTransporter .ipa Payload files:"); - allApps.forEach(f => this.$logger.debug(" - " + f)); - - allApps = allApps.filter(f => path.extname(f).toLowerCase() === ".app"); - if (allApps.length > 1) { - this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there is more than one .app file. We don't know which one to upload."); - } else if (allApps.length <= 0) { - this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there must be at least one .app file."); - } - const appFile = path.join(payloadDir, allApps[0]); - - const plistObject = await this.$plistParser.parseFile(path.join(appFile, INFO_PLIST_FILE_NAME)); - const bundleId = plistObject && plistObject.CFBundleIdentifier; - if (!bundleId) { - this.$errors.failWithoutHelp(`Unable to determine bundle identifier from ${ipaFileFullPath}.`); - } - - this.$logger.trace(`bundle identifier determined to be ${bundleId}`); - this._bundleIdentifier = bundleId; - } + return bundleId; } - return this._bundleIdentifier; + return this.$projectData.projectIdentifiers.ios; } + @cache() private async getITMSTransporterPath(): Promise { - if (!this._itmsTransporterPath) { - const xcodePath = await this.$xcodeSelectService.getContentsDirectoryPath(); - const xcodeVersion = await this.$xcodeSelectService.getXcodeVersion(); - let result = path.join(xcodePath, "Applications", "Application Loader.app", "Contents"); - - xcodeVersion.patch = xcodeVersion.patch || "0"; - // iTMS Transporter's path has been modified in Xcode 6.3 - // https://github.com/nomad/shenzhen/issues/243 - if (xcodeVersion.major && xcodeVersion.minor && - versionCompare(xcodeVersion, "6.3.0") < 0) { - result = path.join(result, "MacOS"); - } + const xcodePath = await this.$xcodeSelectService.getContentsDirectoryPath(); + const loaderAppContentsPath = path.join(xcodePath, "Applications", "Application Loader.app", "Contents"); + const itmsTransporterPath = path.join(loaderAppContentsPath, ITMSConstants.iTMSDirectoryName, "bin", ITMSConstants.iTMSExecutableName); - this._itmsTransporterPath = path.join(result, ITMSConstants.iTMSDirectoryName, "bin", ITMSConstants.iTMSExecutableName); - } - - if (!this.$fs.exists(this._itmsTransporterPath)) { + if (!this.$fs.exists(itmsTransporterPath)) { this.$errors.failWithoutHelp('iTMS Transporter not found on this machine - make sure your Xcode installation is not damaged.'); } - return this._itmsTransporterPath; - } - - private getContentDeliveryRequestBody(credentials: ICredentials): Buffer { - // All of those values except credentials are hardcoded - // Apple's content delivery API is very picky with handling requests - // and if only one of these ends up missing the API returns - // a response with 200 status code and an error - return Buffer.from(JSON.stringify({ - id: "1", // magic number - jsonrpc: "2.0", - method: "lookupSoftwareApplications", - params: { - Username: credentials.username, - Password: credentials.password, - Version: "2.9.1 (441)", - Application: "Application Loader", - OSIdentifier: "Mac OS X 10.8.5 (x86_64)" - } - }), "utf8"); + return itmsTransporterPath; } private getITMSMetadataXml(appleId: string, ipaFileName: string, ipaFileHash: string, ipaFileSize: number): string { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b32d662fac..7d4916d98d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.3.1", + "version": "5.3.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c1d433b63f..05ddfb92a6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.3.2", + "version": "5.3.3", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From ace53e535bdc33db6ddf839bfb2f2857bbfef588 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Fri, 19 Apr 2019 10:35:47 +0300 Subject: [PATCH 05/62] fix: handle CLI usage as a library --- lib/commands/build.ts | 2 +- lib/commands/debug.ts | 2 +- lib/commands/deploy.ts | 6 +++--- lib/commands/preview.ts | 2 +- lib/declarations.d.ts | 4 ++-- lib/helpers/bundle-validator-helper.ts | 14 ++++++------- lib/helpers/livesync-command-helper.ts | 2 +- lib/services/project-data-service.ts | 18 +++++++++------- lib/services/workflow-service.ts | 2 +- test/debug.ts | 1 + test/helpers/bundle-validator-helper.ts | 21 ++++++++++--------- test/package-installation-manager.ts | 3 +++ test/platform-commands.ts | 2 ++ test/platform-service.ts | 1 + .../preview-app-livesync-service.ts | 3 ++- test/services/project-data-service.ts | 4 +++- test/stubs.ts | 7 +++++++ test/update.ts | 13 ++++++------ 18 files changed, 64 insertions(+), 43 deletions(-) diff --git a/lib/commands/build.ts b/lib/commands/build.ts index bdad8cfe02..a748b2d2d3 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -65,7 +65,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { this.$errors.fail(`Applications for platform ${platform} can not be built on this OS`); } - this.$bundleValidatorHelper.validate(); + this.$bundleValidatorHelper.validate(this.$projectData); } protected async validateArgs(args: string[], platform: string): Promise { diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index f53a40bc2a..1ce3e40830 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -69,7 +69,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements } const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null; - this.$bundleValidatorHelper.validate(minSupportedWebpackVersion); + this.$bundleValidatorHelper.validate(this.$projectData, minSupportedWebpackVersion); const result = await super.canExecuteCommandBase(this.platform, { validateOptions: true, notConfiguredEnvOptions: { hideCloudBuildOption: true, hideSyncToPreviewAppOption: true } }); return result; diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index c83b357ca0..ad6fffc62b 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -14,8 +14,8 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement $platformsData: IPlatformsData, private $bundleValidatorHelper: IBundleValidatorHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { - super($options, $platformsData, $platformService, $projectData); - this.$projectData.initializeProjectData(); + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { @@ -26,7 +26,7 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement public async canExecute(args: string[]): Promise { this.$androidBundleValidatorHelper.validateNoAab(); - this.$bundleValidatorHelper.validate(); + this.$bundleValidatorHelper.validate(this.$projectData); if (!args || !args.length || args.length > 1) { return false; } diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 47520544b0..7e03dc87a8 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -46,7 +46,7 @@ export class PreviewCommand implements ICommand { } await this.$networkConnectivityValidator.validate(); - this.$bundleValidatorHelper.validate(PreviewCommand.MIN_SUPPORTED_WEBPACK_VERSION); + this.$bundleValidatorHelper.validate(this.$projectData, PreviewCommand.MIN_SUPPORTED_WEBPACK_VERSION); return true; } } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index aef6e0fd86..3c2458cba6 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -940,13 +940,13 @@ interface IBundleValidatorHelper { * @param {string} minSupportedVersion the minimum supported version of nativescript-dev-webpack * @return {void} */ - validate(minSupportedVersion?: string): void; + validate(projectData: IProjectData, minSupportedVersion?: string): void; /** * Returns the installed bundler version. * @return {string} */ - getBundlerDependencyVersion(bundlerName?: string): string; + getBundlerDependencyVersion(projectData: IProjectData, bundlerName?: string): string; } diff --git a/lib/helpers/bundle-validator-helper.ts b/lib/helpers/bundle-validator-helper.ts index 2caf345a16..d5d8e6de0d 100644 --- a/lib/helpers/bundle-validator-helper.ts +++ b/lib/helpers/bundle-validator-helper.ts @@ -7,16 +7,14 @@ export class BundleValidatorHelper extends VersionValidatorHelper implements IBu webpack: "nativescript-dev-webpack" }; - constructor(protected $projectData: IProjectData, - protected $errors: IErrors, + constructor(protected $errors: IErrors, protected $options: IOptions) { super(); - this.$projectData.initializeProjectData(); } - public validate(minSupportedVersion?: string): void { + public validate(projectData: IProjectData, minSupportedVersion?: string): void { if (this.$options.bundle) { - const currentVersion = this.getBundlerDependencyVersion(); + const currentVersion = this.getBundlerDependencyVersion(projectData); if (!currentVersion) { this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin); } @@ -28,11 +26,11 @@ export class BundleValidatorHelper extends VersionValidatorHelper implements IBu } } - public getBundlerDependencyVersion(bundlerName?: string): string { + public getBundlerDependencyVersion(projectData: IProjectData, bundlerName?: string): string { let dependencyVersion = null; const bundlePluginName = bundlerName || this.bundlersMap[this.$options.bundle]; - const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName]; - const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName]; + const bundlerVersionInDependencies = projectData.dependencies && projectData.dependencies[bundlePluginName]; + const bundlerVersionInDevDependencies = projectData.devDependencies && projectData.devDependencies[bundlePluginName]; dependencyVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies; return dependencyVersion; diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 8b5c648298..2760220cdb 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -152,7 +152,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null; - this.$bundleValidatorHelper.validate(minSupportedWebpackVersion); + this.$bundleValidatorHelper.validate(this.$projectData, minSupportedWebpackVersion); return result; } diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 2a22bf4f98..4d1544bf13 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -19,7 +19,7 @@ interface IProjectFileData { } export class ProjectDataService implements IProjectDataService { - private defaultProjectDir = ""; + private defaultProjectDir: string; private static DEPENDENCIES_KEY_NAME = "dependencies"; private projectDataCache: IDictionary = {}; @@ -28,12 +28,16 @@ export class ProjectDataService implements IProjectDataService { private $logger: ILogger, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $androidResourcesMigrationService: IAndroidResourcesMigrationService, - private $injector: IInjector, - $projectData: IProjectData) { - // add the ProjectData of the default projectDir to the projectData cache - $projectData.initializeProjectData(); - this.defaultProjectDir = $projectData.projectDir; - this.projectDataCache[this.defaultProjectDir] = $projectData; + private $injector: IInjector) { + try { + // add the ProjectData of the default projectDir to the projectData cache + const projectData = this.$injector.resolve("projectData"); + projectData.initializeProjectData(); + this.defaultProjectDir = projectData.projectDir; + this.projectDataCache[this.defaultProjectDir] = projectData; + } catch (e) { + // the CLI is required as a lib from a non-project folder + } } public getNSValue(projectDir: string, propertyName: string): any { diff --git a/lib/services/workflow-service.ts b/lib/services/workflow-service.ts index 680bf6d7c0..5b5490ef56 100644 --- a/lib/services/workflow-service.ts +++ b/lib/services/workflow-service.ts @@ -68,7 +68,7 @@ export class WorkflowService implements IWorkflowService { const validWebpackPluginTags = ["*", "latest", "next", "rc"]; let isInstalledVersionSupported = true; - const installedVersion = this.$bundleValidatorHelper.getBundlerDependencyVersion(webpackPluginName); + const installedVersion = this.$bundleValidatorHelper.getBundlerDependencyVersion(projectData, webpackPluginName); this.$logger.trace(`Updating to webpack workflow: Found ${webpackPluginName} v${installedVersion}`); if (validWebpackPluginTags.indexOf(installedVersion) === -1) { const isInstalledVersionValid = !!semver.valid(installedVersion) || !!semver.coerce(installedVersion); diff --git a/test/debug.ts b/test/debug.ts index a3de9409d4..e41315dacc 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -15,6 +15,7 @@ import { SettingsService } from "../lib/common/test/unit-tests/stubs"; function createTestInjector(): IInjector { const testInjector: IInjector = new yok.Yok(); + testInjector.register("workflowService", stubs.WorkflowServiceStub); testInjector.register("debug|android", DebugAndroidCommand); testInjector.register("config", Configuration); testInjector.register("staticConfig", StaticConfig); diff --git a/test/helpers/bundle-validator-helper.ts b/test/helpers/bundle-validator-helper.ts index 2edd2568b2..3feee9c4c2 100644 --- a/test/helpers/bundle-validator-helper.ts +++ b/test/helpers/bundle-validator-helper.ts @@ -90,17 +90,18 @@ describe("BundleValidatorHelper", () => { ]); }); - _.each(testCases, (testCase: any) => { - const deps = { - "nativescript-dev-webpack": testCase.currentWebpackVersion - }; + _.each(testCases, (testCase: any) => { + const deps = { + "nativescript-dev-webpack": testCase.currentWebpackVersion + }; - it(`${testCase.name}`, async () => { - const injector = createTestInjector({ dependencies: testCase.isDependency ? deps : null, devDependencies: !testCase.isDependency ? deps : null }); - const bundleValidatorHelper = injector.resolve("bundleValidatorHelper"); - bundleValidatorHelper.validate(testCase.minSupportedWebpackVersion); + it(`${testCase.name}`, async () => { + const injector = createTestInjector({ dependencies: testCase.isDependency ? deps : null, devDependencies: !testCase.isDependency ? deps : null }); + const bundleValidatorHelper = injector.resolve("bundleValidatorHelper"); + const projectData = injector.resolve("projectData"); + bundleValidatorHelper.validate(projectData, testCase.minSupportedWebpackVersion); - assert.deepEqual(error, testCase.expectedError); - }); + assert.deepEqual(error, testCase.expectedError); }); + }); }); diff --git a/test/package-installation-manager.ts b/test/package-installation-manager.ts index fa35296680..bec00a5ff7 100644 --- a/test/package-installation-manager.ts +++ b/test/package-installation-manager.ts @@ -14,10 +14,13 @@ import * as yok from "../lib/common/yok"; import ChildProcessLib = require("../lib/common/child-process"); import { SettingsService } from "../lib/common/test/unit-tests/stubs"; import { ProjectDataService } from "../lib/services/project-data-service"; +import { WorkflowServiceStub, ProjectDataStub } from "./stubs"; function createTestInjector(): IInjector { const testInjector = new yok.Yok(); + testInjector.register("workflowService", WorkflowServiceStub); + testInjector.register("projectData", ProjectDataStub); testInjector.register("config", ConfigLib.Configuration); testInjector.register("logger", LoggerLib.Logger); testInjector.register("errors", ErrorsLib.Errors); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 5bba06ea46..9b593a1b73 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -20,6 +20,7 @@ import * as ChildProcessLib from "../lib/common/child-process"; import ProjectChangesLib = require("../lib/services/project-changes-service"); import { Messages } from "../lib/common/messages/messages"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; +import { WorkflowServiceStub } from "./stubs"; let isCommandExecuted = true; @@ -97,6 +98,7 @@ function createTestInjector() { const testInjector = new yok.Yok(); testInjector.register("injector", testInjector); + testInjector.register("workflowService", WorkflowServiceStub); testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("staticConfig", StaticConfigLib.StaticConfig); testInjector.register("nodeModulesDependenciesBuilder", {}); diff --git a/test/platform-service.ts b/test/platform-service.ts index 1f61e9fc77..e7039b33f1 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -32,6 +32,7 @@ temp.track(); function createTestInjector() { const testInjector = new yok.Yok(); + testInjector.register("workflowService", stubs.WorkflowServiceStub); testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('errors', stubs.ErrorsStub); testInjector.register('logger', stubs.LoggerStub); diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index 4706a00439..8eab7ee01e 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -1,6 +1,6 @@ import { Yok } from "../../../lib/common/yok"; import * as _ from 'lodash'; -import { LoggerStub, ErrorsStub } from "../../stubs"; +import { LoggerStub, ErrorsStub, WorkflowServiceStub } from "../../stubs"; import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk"; import { PreviewAppLiveSyncService } from "../../../lib/services/livesync/playground/preview-app-livesync-service"; import * as chai from "chai"; @@ -101,6 +101,7 @@ function createTestInjector(options?: { options = options || {}; const injector = new Yok(); + injector.register("workflowService", WorkflowServiceStub); injector.register("logger", LoggerMock); injector.register("hmrStatusService", {}); injector.register("errors", ErrorsStub); diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index 414276b1e8..72f1db2b95 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -1,7 +1,7 @@ import { Yok } from "../../lib/common/yok"; import { assert } from "chai"; import { ProjectDataService } from "../../lib/services/project-data-service"; -import { LoggerStub } from "../stubs"; +import { LoggerStub, WorkflowServiceStub, ProjectDataStub } from "../stubs"; import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, PACKAGE_JSON_FILE_NAME, AssetConstants, ProjectTypes } from '../../lib/constants'; import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; import { basename, join } from "path"; @@ -43,6 +43,8 @@ const testData: any = [ const createTestInjector = (readTextData?: string): IInjector => { const testInjector = new Yok(); + testInjector.register("workflowService", WorkflowServiceStub); + testInjector.register("projectData", ProjectDataStub); testInjector.register("staticConfig", { CLIENT_NAME_KEY_IN_PROJECT_FILE: CLIENT_NAME_KEY_IN_PROJECT_FILE, PROJECT_FILE_NAME: "package.json" diff --git a/test/stubs.ts b/test/stubs.ts index 1f775f69f8..da6e615e55 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -916,6 +916,12 @@ export class PerformanceService implements IPerformanceService { processExecutionData() { } } +export class WorkflowServiceStub implements IWorkflowService { + handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, skipWarnings?: boolean, force?: boolean): Promise { + return; + } +} + export class InjectorStub extends Yok implements IInjector { constructor() { super(); @@ -954,5 +960,6 @@ export class InjectorStub extends Yok implements IInjector { getDevice: (): Mobile.IDevice => undefined, getDeviceByIdentifier: (): Mobile.IDevice => undefined }); + this.register("workflowService", WorkflowServiceStub); } } diff --git a/test/update.ts b/test/update.ts index 65b162c48a..f6a2052017 100644 --- a/test/update.ts +++ b/test/update.ts @@ -25,6 +25,7 @@ function createTestInjector( ): IInjector { const testInjector: IInjector = new yok.Yok(); testInjector.register("logger", stubs.LoggerStub); + testInjector.register("workflowService", stubs.WorkflowServiceStub); testInjector.register("options", Options); testInjector.register('fs', stubs.FileSystemStub); testInjector.register("analyticsService", { @@ -49,10 +50,10 @@ function createTestInjector( }); testInjector.register("pluginVariablesService", {}); testInjector.register("platformService", { - getInstalledPlatforms: function(): string[] { + getInstalledPlatforms: function (): string[] { return installedPlatforms; }, - getAvailablePlatforms: function(): string[] { + getAvailablePlatforms: function (): string[] { return availablePlatforms; }, removePlatforms: async (): Promise => undefined, @@ -66,9 +67,9 @@ function createTestInjector( getPlatformData: () => { return { platformProjectService: { - validate - } - }; + validate + } + }; } }); testInjector.register("settingsService", SettingsService); @@ -161,7 +162,7 @@ describe("update command method tests", () => { const fs = testInjector.resolve("fs"); const copyFileStub = sandbox.stub(fs, "copyFile"); const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute(["3.3.0"]).then( () => { + return updateCommand.execute(["3.3.0"]).then(() => { assert.isTrue(copyFileStub.calledWith(path.join(projectFolder, "package.json"))); for (const folder of UpdateCommand.folders) { assert.isTrue(copyFileStub.calledWith(path.join(projectFolder, folder))); From b2bd5b4f540948f856351fbf99281211ba361f90 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 19 Apr 2019 10:51:11 +0300 Subject: [PATCH 06/62] chore: fix PR comments --- lib/commands/appstore-list.ts | 11 ++--------- lib/commands/appstore-upload.ts | 2 +- lib/declarations.d.ts | 4 ++-- lib/services/itmstransporter-service.ts | 4 ++-- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index c88bcaee82..40391c7047 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -37,20 +37,13 @@ export class ListiOSApps implements ICommand { this.$logger.out("Seems you don't have any applications yet."); } else { const table: any = createTable(["Application Name", "Bundle Identifier", "In Flight Version"], applications.map(application => { - return [application.name, application.bundleId, this.getVersion(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()); } } - - private getVersion(application: IApplePortalApplicationSummary): string { - if (application && application.versionSets && application.versionSets.length && application.versionSets[0].inFlightVersion) { - return application.versionSets[0].inFlightVersion.version; - } - - return ""; - } } $injector.registerCommand("appstore|*list", ListiOSApps); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 70f21385a3..8b40694abf 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -109,7 +109,7 @@ export class PublishIOS implements ICommand { username, password, ipaFilePath, - ipaFileOption: !!this.$options.ipa, + shouldExtractIpa: !!this.$options.ipa, verboseLogging: this.$logger.getLevel() === "TRACE" }); } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 25f6ed06f9..e3e75e604b 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -624,9 +624,9 @@ interface IITMSData extends ICredentials { ipaFilePath: string; /** - * Specified if the ipa options is provided + * Specify if the service should extract the `.ipa` file into `temp` directory in order to get bundleIdentifier from info.plist */ - ipaFileOption: boolean; + shouldExtractIpa: boolean; /** * Specifies whether the logging level of the itmstransporter command-line tool should be set to verbose. * @type {string} diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index 607081bf41..c129e9332f 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -44,9 +44,9 @@ export class ITMSTransporterService implements IITMSTransporterService { } private async getBundleIdentifier(data: IITMSData): Promise { - const { ipaFileOption, ipaFilePath } = data; + const { shouldExtractIpa, ipaFilePath } = data; - if (ipaFileOption) { + if (shouldExtractIpa) { if (!this.$fs.exists(ipaFilePath) || path.extname(ipaFilePath) !== ".ipa") { this.$errors.failWithoutHelp(`Cannot use specified ipa file ${ipaFilePath}. File either does not exist or is not an ipa file.`); } From f2d59a7e2d967122809b483be1e057cba7f519e8 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Fri, 19 Apr 2019 16:33:08 +0300 Subject: [PATCH 07/62] fix: add the initial version of the Legacy Workflow warnings and recommendations --- lib/common/helpers.ts | 25 +++++++++++++++++++++++++ lib/services/workflow-service.ts | 30 +++++++++++++++++++++++++----- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/lib/common/helpers.ts b/lib/common/helpers.ts index f3c22b4430..9d188e2a36 100644 --- a/lib/common/helpers.ts +++ b/lib/common/helpers.ts @@ -384,6 +384,31 @@ export function createTable(headers: string[], data: string[][]): any { return table; } +export function getMessageWithBorders(message: string, spanLength = 3): string { + const longestRowLength = message.split(EOL).sort(function (a, b) { return b.length - a.length; })[0].length; + let border = "*".repeat(longestRowLength + 2 * spanLength); // * 2 for both sides + if (border.length % 2 === 0) { + border += "*"; // the * should always be an odd number in order to get * in each edge (we will remove the even *s below) + } + border = border.replace(/\*\*/g, "* "); // ***** => * * * in order to have similar padding to the side borders + const formatRow = function (row: string) { + return _.padEnd("*", spanLength) + _.padEnd(row, border.length - (2 * spanLength)) + _.padStart("*", spanLength) + EOL; + }; + const emptyRow = formatRow(""); + + const messageWithBorders = []; + messageWithBorders.push( + EOL, + border + EOL, + emptyRow, + ...message.split(EOL).map(row => formatRow(row)), + emptyRow, + border + EOL, + EOL + ); + return messageWithBorders.join(""); +} + export function remove(array: T[], predicate: (element: T) => boolean, numberOfElements?: number): T[] { numberOfElements = numberOfElements || 1; const index = _.findIndex(array, predicate); diff --git a/lib/services/workflow-service.ts b/lib/services/workflow-service.ts index 5b5490ef56..5cd4082b7e 100644 --- a/lib/services/workflow-service.ts +++ b/lib/services/workflow-service.ts @@ -1,8 +1,14 @@ import * as helpers from "../common/helpers"; import * as path from "path"; import * as semver from "semver"; +import { EOL } from "os"; export class WorkflowService implements IWorkflowService { + private legacyWorkflowDeprecationMessage = `With the upcoming NativeScript 6.0 the Webpack workflow will become the only way of build apps. +More info about the reason for this change and how to migrate your project can be found in the link below: +`; + private webpackWorkflowConfirmMessage = `Do you want to switch your app to the Webpack workflow?`; + constructor(private $bundleValidatorHelper: IBundleValidatorHelper, private $fs: IFileSystem, private $logger: ILogger, @@ -37,9 +43,17 @@ export class WorkflowService implements IWorkflowService { } private async handleWebpackWorkflowSwitch(projectData: IProjectData, skipWarnings: boolean, force: boolean): Promise { - let hasSwitched = false; + let hasSwitched = force; if (force || helpers.isInteractive()) { - hasSwitched = force || await this.$prompter.confirm("Please use webpack!", () => true); + if (!force) { + this.$logger.info(); + this.$logger.printMarkdown(` +__Improve your project by switching to the Webpack workflow.__ + +\`${this.legacyWorkflowDeprecationMessage}\``); + hasSwitched = await this.$prompter.confirm(this.webpackWorkflowConfirmMessage, () => true); + } + if (hasSwitched) { this.$projectDataService.setUseLegacyWorkflow(projectData.projectDir, false); await this.ensureWebpackPluginInstalled(projectData); @@ -54,11 +68,17 @@ export class WorkflowService implements IWorkflowService { } private showLegacyWorkflowWarning() { - this.$logger.warn("TODO: "); + const legacyWorkflowWarning = `You are using the Legacy Workflow.${EOL}${EOL}${this.legacyWorkflowDeprecationMessage}`; + const warningWithBorders = helpers.getMessageWithBorders(legacyWorkflowWarning); + + this.$logger.warn(warningWithBorders); } private showNoBundleWarning() { - this.$logger.warn("TODO: "); + 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); } private async ensureWebpackPluginInstalled(projectData: IProjectData) { @@ -81,8 +101,8 @@ export class WorkflowService implements IWorkflowService { if (!isInstalledVersionSupported) { const webpackConfigPath = path.join(projectData.projectDir, webpackConfigFileName); if (this.$fs.exists(webpackConfigPath)) { - this.$logger.info(``); this.$fs.rename(webpackConfigPath, `${webpackConfigPath}.bak`); + this.$logger.warn(`The 'nativescript-dev-webpack' plugin was updated and your '${webpackConfigFileName}' was replaced. You can find your old '${webpackConfigPath}' in '${webpackConfigPath}.bak'.`); } const installResult = await this.$packageManager.install(`${webpackPluginName}@latest`, projectData.projectDir, { From afdb0213a0ea291d22cba5b3be2247cf8e143d6c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 23 Apr 2019 09:47:35 +0300 Subject: [PATCH 08/62] fix: prepare xcconfig files for all conigurations Currently CLI merges and prepares xcconfig files only for the current build configuration, i.e. debug or release. However, the `pod install` command works with both configurations. This leads to problem when some property is set in the project's (plugin's) build.xcconfig file as CLI will prepare the native iOS project only for one configuration. When `pod install` is executed, if the Pods project uses the property set in the build.xcconfig file, it will fail with error, as the value for debug and release will be different (i.e. it will be set only for one of them). Pods project does not support this behavior, so the build operation fails. So, ensure CLI merges all xcconfig files for both debug and release configurations. This way, the Pods project will be in sync, so it should continue its execution. This is important when using Cocoapods 1.6.0+ where some changes are applied for Swift support, which can be workarounded by setting SWIFT_VERSION in project's build.xcconfig file. --- lib/declarations.d.ts | 7 ++-- lib/services/cocoapods-service.ts | 12 ++++--- lib/services/ios-project-service.ts | 53 ++++++++++++++++----------- lib/services/xcconfig-service.ts | 12 +++---- test/ios-project-service.ts | 56 +++++++++++++++-------------- 5 files changed, 77 insertions(+), 63 deletions(-) diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e3e75e604b..7972c678dc 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -890,12 +890,11 @@ interface IXcprojInfo { interface IXcconfigService { /** - * Returns the path to the xcconfig file + * Returns the paths to the xcconfig files for build configuration (debug/release) * @param projectRoot The path to root folder of native project (platforms/ios) - * @param opts - * @returns {string} + * @returns {IStringDictionary} */ - getPluginsXcconfigFilePath(projectRoot: string, opts: IRelease): string; + getPluginsXcconfigFilePaths(projectRoot: string): IStringDictionary; /** * Returns the value of a property from a xcconfig file. diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index b25a7daa13..da08ec1252 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -55,14 +55,16 @@ export class CocoaPodsService implements ICocoaPodsService { return podInstallResult; } - public async mergePodXcconfigFile(projectData: IProjectData, platformData: IPlatformData, opts: IRelease) { + public async mergePodXcconfigFile(projectData: IProjectData, platformData: IPlatformData): Promise { const podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`); const podFolder = path.join(platformData.projectRoot, podFilesRootDirName); if (this.$fs.exists(podFolder)) { - const podXcconfigFilePath = opts && opts.release ? path.join(podFolder, `Pods-${projectData.projectName}.release.xcconfig`) - : path.join(podFolder, `Pods-${projectData.projectName}.debug.xcconfig`); - const pluginsXcconfigFilePath = this.$xcconfigService.getPluginsXcconfigFilePath(platformData.projectRoot, opts); - await this.$xcconfigService.mergeFiles(podXcconfigFilePath, pluginsXcconfigFilePath); + const pluginsXcconfigFilePaths = this.$xcconfigService.getPluginsXcconfigFilePaths(platformData.projectRoot); + for (const configuration in pluginsXcconfigFilePaths) { + const pluginsXcconfigFilePath = pluginsXcconfigFilePaths[configuration]; + const podXcconfigFilePath = path.join(podFolder, `Pods-${projectData.projectName}.${configuration}.xcconfig`); + await this.$xcconfigService.mergeFiles(podXcconfigFilePath, pluginsXcconfigFilePath); + } } } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 85960e4ec2..820677b4d6 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -799,7 +799,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: IRelease): Promise { await this.mergeInfoPlists(projectData, opts); await this.$iOSEntitlementsService.merge(projectData); - await this.mergeProjectXcconfigFiles(projectData, opts); + await this.mergeProjectXcconfigFiles(projectData); for (const pluginData of await this.getAllInstalledPlugins(projectData)) { await this.$pluginVariablesService.interpolatePluginVariables(pluginData, this.getPlatformData(projectData).configurationFilePath, projectData.projectDir); } @@ -1216,10 +1216,13 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.writeFile(path.join(headersFolderPath, "module.modulemap"), modulemap); } - private async mergeProjectXcconfigFiles(projectData: IProjectData, opts: IRelease): Promise { + private async mergeProjectXcconfigFiles(projectData: IProjectData): Promise { const platformData = this.getPlatformData(projectData); - const pluginsXcconfigFilePath = this.$xcconfigService.getPluginsXcconfigFilePath(platformData.projectRoot, opts); - this.$fs.deleteFile(pluginsXcconfigFilePath); + const pluginsXcconfigFilePaths = _.values(this.$xcconfigService.getPluginsXcconfigFilePaths(platformData.projectRoot)); + + for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) { + this.$fs.deleteFile(pluginsXcconfigFilePath); + } const pluginsService = this.$injector.resolve("pluginsService"); const allPlugins: IPluginData[] = await pluginsService.getAllInstalledPlugins(projectData); @@ -1227,32 +1230,40 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f const pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); const pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, BUILD_XCCONFIG_FILE_NAME); if (this.$fs.exists(pluginXcconfigFilePath)) { - await this.$xcconfigService.mergeFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath); + for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) { + await this.$xcconfigService.mergeFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath); + } } } const appResourcesXcconfigPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, BUILD_XCCONFIG_FILE_NAME); if (this.$fs.exists(appResourcesXcconfigPath)) { - await this.$xcconfigService.mergeFiles(appResourcesXcconfigPath, pluginsXcconfigFilePath); + for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) { + await this.$xcconfigService.mergeFiles(appResourcesXcconfigPath, pluginsXcconfigFilePath); + } } - if (!this.$fs.exists(pluginsXcconfigFilePath)) { - // We need the pluginsXcconfig file to exist in platforms dir as it is required in the native template: - // https://github.com/NativeScript/ios-runtime/blob/9c2b7b5f70b9bee8452b7a24aa6b646214c7d2be/build/project-template/__PROJECT_NAME__/build-debug.xcconfig#L3 - // From Xcode 10 in case the file is missing, this include fails and the build itself fails (was a warning in previous Xcode versions). - this.$fs.writeFile(pluginsXcconfigFilePath, ""); + for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) { + if (!this.$fs.exists(pluginsXcconfigFilePath)) { + // We need the pluginsXcconfig file to exist in platforms dir as it is required in the native template: + // https://github.com/NativeScript/ios-runtime/blob/9c2b7b5f70b9bee8452b7a24aa6b646214c7d2be/build/project-template/__PROJECT_NAME__/build-debug.xcconfig#L3 + // From Xcode 10 in case the file is missing, this include fails and the build itself fails (was a warning in previous Xcode versions). + this.$fs.writeFile(pluginsXcconfigFilePath, ""); + } } - // Set Entitlements Property to point to default file if not set explicitly by the user. - const entitlementsPropertyValue = this.$xcconfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); - if (entitlementsPropertyValue === null && this.$fs.exists(this.$iOSEntitlementsService.getPlatformsEntitlementsPath(projectData))) { - temp.track(); - const tempEntitlementsDir = temp.mkdirSync("entitlements"); - const tempEntitlementsFilePath = path.join(tempEntitlementsDir, "set-entitlements.xcconfig"); - const entitlementsRelativePath = this.$iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); - this.$fs.writeFile(tempEntitlementsFilePath, `CODE_SIGN_ENTITLEMENTS = ${entitlementsRelativePath}${EOL}`); - - await this.$xcconfigService.mergeFiles(tempEntitlementsFilePath, pluginsXcconfigFilePath); + for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) { + // Set Entitlements Property to point to default file if not set explicitly by the user. + const entitlementsPropertyValue = this.$xcconfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); + if (entitlementsPropertyValue === null && this.$fs.exists(this.$iOSEntitlementsService.getPlatformsEntitlementsPath(projectData))) { + temp.track(); + const tempEntitlementsDir = temp.mkdirSync("entitlements"); + const tempEntitlementsFilePath = path.join(tempEntitlementsDir, "set-entitlements.xcconfig"); + const entitlementsRelativePath = this.$iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); + this.$fs.writeFile(tempEntitlementsFilePath, `CODE_SIGN_ENTITLEMENTS = ${entitlementsRelativePath}${EOL}`); + + await this.$xcconfigService.mergeFiles(tempEntitlementsFilePath, pluginsXcconfigFilePath); + } } } diff --git a/lib/services/xcconfig-service.ts b/lib/services/xcconfig-service.ts index 59a33353ec..896778536f 100644 --- a/lib/services/xcconfig-service.ts +++ b/lib/services/xcconfig-service.ts @@ -1,4 +1,5 @@ import * as path from "path"; +import { Configurations } from "../common/constants"; export class XcconfigService implements IXcconfigService { constructor( @@ -6,12 +7,11 @@ export class XcconfigService implements IXcconfigService { private $fs: IFileSystem, private $xcprojService: IXcprojService) { } - public getPluginsXcconfigFilePath(projectRoot: string, opts: IRelease): string { - if (opts && opts.release) { - return this.getPluginsReleaseXcconfigFilePath(projectRoot); - } - - return this.getPluginsDebugXcconfigFilePath(projectRoot); + public getPluginsXcconfigFilePaths(projectRoot: string): IStringDictionary { + return { + [Configurations.Debug.toLowerCase()]: this.getPluginsDebugXcconfigFilePath(projectRoot), + [Configurations.Release.toLowerCase()]: this.getPluginsReleaseXcconfigFilePath(projectRoot) + }; } private getPluginsDebugXcconfigFilePath(projectRoot: string): string { diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 74468e6e6b..dc6fbc249a 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -1178,17 +1178,19 @@ describe("Merge Project XCConfig files", () => { // run merge for all release: debug|release for (const release in [true, false]) { - await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); + await (iOSProjectService).mergeProjectXcconfigFiles(projectData); - const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); + const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths(projectRoot); - assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); - const expected = { - 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', - 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', - 'CODE_SIGN_IDENTITY': 'iPhone Distribution' - }; - assertPropertyValues(expected, destinationFilePath, testInjector); + _.each(destinationFilePaths, destinationFilePath => { + assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); + const expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', + 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', + 'CODE_SIGN_IDENTITY': 'iPhone Distribution' + }; + assertPropertyValues(expected, destinationFilePath, testInjector); + }); } }); @@ -1206,13 +1208,15 @@ describe("Merge Project XCConfig files", () => { await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); - const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); + const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths(projectRoot); - assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); - const expected = { - 'CODE_SIGN_ENTITLEMENTS': iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData) - }; - assertPropertyValues(expected, destinationFilePath, testInjector); + _.each(destinationFilePaths, destinationFilePath => { + assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); + const expected = { + 'CODE_SIGN_ENTITLEMENTS': iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData) + }; + assertPropertyValues(expected, destinationFilePath, testInjector); + }); } }); @@ -1222,13 +1226,12 @@ describe("Merge Project XCConfig files", () => { const xcconfigEntitlements = appResourceXCConfigContent + `${EOL}CODE_SIGN_ENTITLEMENTS = ${expectedEntitlementsFile}`; fs.writeFile(appResourcesXcconfigPath, xcconfigEntitlements); - // run merge for all release: debug|release - for (const release in [true, false]) { - await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); + await (iOSProjectService).mergeProjectXcconfigFiles(projectData); - const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); + const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths(projectRoot); - assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); + _.each(destinationFilePaths, destinationFilePath => { + assert.isTrue(fs.exists(destinationFilePath), `Target build xcconfig ${destinationFilePath} is missing.`); const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', @@ -1236,20 +1239,19 @@ describe("Merge Project XCConfig files", () => { 'CODE_SIGN_ENTITLEMENTS': expectedEntitlementsFile }; assertPropertyValues(expected, destinationFilePath, testInjector); - } + }); }); it("creates empty plugins-.xcconfig in case there are no build.xcconfig in App_Resources and in plugins", async () => { - // run merge for all release: debug|release - for (const release in [true, false]) { - await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); + await (iOSProjectService).mergeProjectXcconfigFiles(projectData); - const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); + const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths(projectRoot); - assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); + _.each(destinationFilePaths, destinationFilePath => { + assert.isTrue(fs.exists(destinationFilePath), `Target build xcconfig ${destinationFilePath} is missing.` ); const content = fs.readFile(destinationFilePath).toString(); assert.equal(content, ""); - } + }); }); }); From f9be51d0fce6b1f7fed7ef6147ebda9a126dd584 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Tue, 23 Apr 2019 10:46:24 +0300 Subject: [PATCH 09/62] chore: fix PR comments --- lib/commands/build.ts | 2 +- lib/commands/debug.ts | 2 +- lib/commands/prepare.ts | 2 +- lib/commands/preview.ts | 2 +- lib/commands/run.ts | 2 +- lib/commands/test.ts | 2 +- lib/commands/update.ts | 3 +-- lib/common/helpers.ts | 6 +++++- lib/definitions/platform.d.ts | 9 ++++++++- .../livesync/playground/preview-app-livesync-service.ts | 2 +- lib/services/platform-service.ts | 2 +- lib/services/project-data-service.ts | 4 +--- lib/services/workflow-service.ts | 7 ++++--- test/stubs.ts | 2 +- 14 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/commands/build.ts b/lib/commands/build.ts index a748b2d2d3..79ba67eeff 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -16,7 +16,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { } public async executeCore(args: string[]): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); + await this.$workflowService.handleLegacyWorkflow({ projectDir: this.$projectData.projectDir, settings: this.$options, skipWarnings: true }); const platform = args[0].toLowerCase(); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: !!this.$options.bundle, diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 1ce3e40830..734b546b5b 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -24,7 +24,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements } public async execute(args: string[]): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); + await this.$workflowService.handleLegacyWorkflow({ projectDir: this.$projectData.projectDir, settings: this.$options, skipWarnings: true }); await this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device, diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 91e8734411..d0f821c808 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -14,7 +14,7 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm } public async execute(args: string[]): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); + await this.$workflowService.handleLegacyWorkflow({ projectDir: this.$projectData.projectDir, settings: this.$options, skipWarnings: true }); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: !!this.$options.bundle, release: this.$options.release, diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 7e03dc87a8..59cf02ad7e 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -21,7 +21,7 @@ export class PreviewCommand implements ICommand { } public async execute(): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); + await this.$workflowService.handleLegacyWorkflow({ projectDir: this.$projectData.projectDir, settings: this.$options, skipWarnings: true }); this.$previewAppLogProvider.on(DEVICE_LOG_EVENT_NAME, (deviceId: string, message: string) => { this.$logger.info(message); }); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index af0846f9e2..cf83c86dd5 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -19,7 +19,7 @@ export class RunCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); + await this.$workflowService.handleLegacyWorkflow({ projectDir: this.$projectData.projectDir, settings: this.$options, skipWarnings: true }); await this.$analyticsService.trackPreviewAppData(this.platform, this.$projectData.projectDir); return this.$liveSyncCommandHelper.executeCommandLiveSync(this.platform, this.liveSyncCommandHelperAdditionalOptions); } diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 50b4e3a110..51fd1efd64 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -14,7 +14,7 @@ abstract class TestCommandBase { protected abstract $workflowService: IWorkflowService; async execute(args: string[]): Promise { - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, true); + await this.$workflowService.handleLegacyWorkflow({ projectDir: this.$projectData.projectDir, settings: this.$options, skipWarnings: true }); await this.$testExecutionService.startKarmaServer(this.platform, this.$projectData, this.projectFilesConfig); } diff --git a/lib/commands/update.ts b/lib/commands/update.ts index 07054ac7be..bcdc250e2e 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -30,8 +30,7 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma public async execute(args: string[]): Promise { if (this.$options.workflow) { - const forceWebpackWorkflow = true; - await this.$workflowService.handleLegacyWorkflow(this.$projectData.projectDir, this.$options, forceWebpackWorkflow); + await this.$workflowService.handleLegacyWorkflow({ projectDir: this.$projectData.projectDir, settings: this.$options, force: true }); return; } diff --git a/lib/common/helpers.ts b/lib/common/helpers.ts index 9d188e2a36..7283ecb764 100644 --- a/lib/common/helpers.ts +++ b/lib/common/helpers.ts @@ -385,7 +385,11 @@ export function createTable(headers: string[], data: string[][]): any { } export function getMessageWithBorders(message: string, spanLength = 3): string { - const longestRowLength = message.split(EOL).sort(function (a, b) { return b.length - a.length; })[0].length; + if (!message) { + return ""; + } + + const longestRowLength = message.split(EOL).sort((a, b) => { return b.length - a.length; })[0].length; let border = "*".repeat(longestRowLength + 2 * spanLength); // * 2 for both sides if (border.length % 2 === 0) { border += "*"; // the * should always be an odd number in order to get * in each edge (we will remove the even *s below) diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index c1a1973bed..2ded89ea0f 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -15,7 +15,14 @@ interface IBuildPlatformAction { } interface IWorkflowService { - handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, skipWarnings?: boolean, force?: boolean): Promise; + handleLegacyWorkflow(options: IHandleLegacyWorkflowOptions): Promise; +} + +interface IHandleLegacyWorkflowOptions { + projectDir: string; + settings: IWebpackWorkflowSettings; + skipWarnings?: boolean; + force?: boolean; } interface IWebpackWorkflowSettings { diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index b7db6b59ba..1b18380916 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -30,7 +30,7 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA @performanceLog() public async initialize(data: IPreviewAppLiveSyncData): Promise { await this.$previewSdkService.initialize(data.projectDir, async (device: Device) => { - await this.$workflowService.handleLegacyWorkflow(data.projectDir, data); + await this.$workflowService.handleLegacyWorkflow({ projectDir: data.projectDir, settings: data }); try { if (!device) { this.$errors.failWithoutHelp("Sending initial preview files without a specified device is not supported."); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index b5d8a20885..ed8a29c189 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -222,7 +222,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { @performanceLog() public async preparePlatform(platformInfo: IPreparePlatformInfo): Promise { - await this.$workflowService.handleLegacyWorkflow(platformInfo.projectData.projectDir, platformInfo.appFilesUpdaterOptions); + await this.$workflowService.handleLegacyWorkflow({ projectDir: platformInfo.projectData.projectDir, settings: platformInfo.appFilesUpdaterOptions }); const changesInfo = await this.getChangesInfo(platformInfo); const shouldPrepare = await this.shouldPrepare({ platformInfo, changesInfo }); diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 4d1544bf13..9a443bbc12 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,7 +1,6 @@ import * as path from "path"; import * as constants from "../constants"; import { ProjectData } from "../project-data"; -import { parseJson } from "../common/helpers"; import { exported } from "../common/decorators"; import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, @@ -197,9 +196,8 @@ export class ProjectDataService implements IProjectDataService { private getNsConfig(nsConfigPath: string): INsConfig { let result = this.getNsConfigDefaultObject(); if (this.$fs.exists(nsConfigPath)) { - const nsConfigContent = this.$fs.readText(nsConfigPath); try { - result = parseJson(nsConfigContent); + result = this.$fs.readJson(nsConfigPath); } catch (e) { // default } diff --git a/lib/services/workflow-service.ts b/lib/services/workflow-service.ts index 5cd4082b7e..210dcbea35 100644 --- a/lib/services/workflow-service.ts +++ b/lib/services/workflow-service.ts @@ -4,8 +4,8 @@ import * as semver from "semver"; import { EOL } from "os"; export class WorkflowService implements IWorkflowService { - private legacyWorkflowDeprecationMessage = `With the upcoming NativeScript 6.0 the Webpack workflow will become the only way of build apps. -More info about the reason for this change and how to migrate your project can be found in the link below: + private legacyWorkflowDeprecationMessage = `With the upcoming NativeScript 6.0 the Webpack workflow will become the only way of building apps. +More info about the reasons for this change and how to migrate your project can be found in the link below: `; private webpackWorkflowConfirmMessage = `Do you want to switch your app to the Webpack workflow?`; @@ -19,7 +19,8 @@ More info about the reason for this change and how to migrate your project can b ) { } - public async handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, skipWarnings?: boolean, force?: boolean): Promise { + public async handleLegacyWorkflow(options: IHandleLegacyWorkflowOptions): Promise { + const { projectDir, settings, skipWarnings, force } = options; if (!settings.bundle || force) { const projectData = this.$projectDataService.getProjectData(projectDir); if (typeof (projectData.useLegacyWorkflow) !== "boolean" || force) { diff --git a/test/stubs.ts b/test/stubs.ts index da6e615e55..7556081a47 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -917,7 +917,7 @@ export class PerformanceService implements IPerformanceService { } export class WorkflowServiceStub implements IWorkflowService { - handleLegacyWorkflow(projectDir: string, settings: IWebpackWorkflowSettings, skipWarnings?: boolean, force?: boolean): Promise { + handleLegacyWorkflow(options: IHandleLegacyWorkflowOptions): Promise { return; } } From a2d109896b5f0459f2edd1c3eb76c651caa9d03b Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 23 Apr 2019 12:29:09 +0300 Subject: [PATCH 10/62] chore: set version to 5.3.4 --- npm-shrinkwrap.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7d4916d98d..a79a750442 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.3.3", + "version": "5.3.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 05ddfb92a6..cf27bebdd3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.3.3", + "version": "5.3.4", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 5b35f2ff169e9c95ecf22086bb67faaf7127b2e3 Mon Sep 17 00:00:00 2001 From: NickIliev Date: Wed, 24 Apr 2019 10:55:53 +0300 Subject: [PATCH 11/62] docs: remove obsolete HMR steps --- docs/man_pages/project/testing/debug-android.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man_pages/project/testing/debug-android.md b/docs/man_pages/project/testing/debug-android.md index 02492c5212..a6670c46f0 100644 --- a/docs/man_pages/project/testing/debug-android.md +++ b/docs/man_pages/project/testing/debug-android.md @@ -9,7 +9,7 @@ position: 4 Initiates a debugging session for your project on a connected Android device or Android emulator. When necessary, the command will prepare, build, deploy and launch the app before starting the debug session. While debugging, the output from the application is printed in the console and any changes made to your code are synchronizes with the deployed app. -To enable Hot Module Replacement (HMR) in Angular projects, follow the steps outlined in this wiki: https://github.com/NativeScript/nativescript-angular/wiki/HMR. +To enable Hot Module Replacement (HMR) in Angular projects, follow the steps outlined in [the HMR documentation section](https://docs.nativescript.org/performance-optimizations/bundling-with-webpack#hot-module-replacement). ### Commands From 59576574c7c47ae7c743597003c9d944d3fd887f Mon Sep 17 00:00:00 2001 From: NickIliev Date: Wed, 24 Apr 2019 11:12:11 +0300 Subject: [PATCH 12/62] docs: remove BETA statement for HMR status --- docs/man_pages/project/testing/debug-android.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man_pages/project/testing/debug-android.md b/docs/man_pages/project/testing/debug-android.md index a6670c46f0..2553e63ace 100644 --- a/docs/man_pages/project/testing/debug-android.md +++ b/docs/man_pages/project/testing/debug-android.md @@ -31,12 +31,12 @@ Attach the debug tools to a running app in the native emulator | `$ tns debug an * `--no-watch` - If set, changes in your code will not be reflected during the execution of this command. * `--clean` - If set, forces the complete rebuild of the native application. * `--bundle` - Specifies that the `webpack` bundler will be used to bundle the application. -* `--hmr` - (Beta) Enables the hot module replacement (HMR) feature. HMR depends on `webpack` and adding the `--hmr` flag to the command will automatically enable the `--bundle` option as well.<% if(isConsole) { %> The HMR feature is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/NativeScript/issues/6398.<% } %> +* `--hmr` - Enables the hot module replacement (HMR) feature. HMR depends on `webpack` and adding the `--hmr` flag to the command will automatically enable the `--bundle` option as well.<% if(isConsole) { %> For more information about the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/NativeScript/issues/6398.<% } %> * `--syncAllFiles` - Watches all production dependencies inside node_modules for changes. Triggers project rebuild if necessary! <% if(isHtml) { %> ->Note: Hot Module Replacement (HMR) is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/NativeScript/issues/6398. +>Note: For more information about HMR, the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/NativeScript/issues/6398. ### Command Limitations From ed965e7b63075f05386a7890bb6903cdea4ff5d1 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 24 Apr 2019 19:52:46 +0300 Subject: [PATCH 13/62] chore: add changelog for 5.3.3 and 5.3.4 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83fcdccb7f..03b0810185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ NativeScript CLI Changelog ================ +5.3.4 (2019, April 24) +== + +### Fixed +* [Fixed #4561](https://github.com/NativeScript/nativescript-cli/issues/4561): CLI merges xcconfig files only for specified build configuration + +5.3.3 (2019, April 23) +== + +### Fixed +* [Fixed #4527](https://github.com/NativeScript/nativescript-cli/issues/4527): Unable to upload applications to App Store + 5.3.2 (2019, April 12) == From 61d77ac765ad432ed1d655846218f8f0d875c759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Martin?= Date: Wed, 24 Apr 2019 19:00:16 +0200 Subject: [PATCH 14/62] fix: use proxy only for remote requests Do not use the proxy for requests to localhost or 127.0.0.1 when NativeScript is configured to use the proxy. Partially fixes issue #2313, since requests to localhost will be possible in corporate proxy environments. --- lib/common/http-client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/common/http-client.ts b/lib/common/http-client.ts index 6aaec22c5d..5c970b7eea 100644 --- a/lib/common/http-client.ts +++ b/lib/common/http-client.ts @@ -289,7 +289,9 @@ private defaultUserAgent: string; * @param {string} requestProto The protocol used for the current request - http or https. */ private async useProxySettings(proxySettings: IProxySettings, cliProxySettings: IProxySettings, options: any, headers: any, requestProto: string): Promise { - if (proxySettings || cliProxySettings) { + const isLocalRequest = options.host === "localhost" || options.host === "127.0.0.1"; + // don't use the proxy for requests to localhost + if (!isLocalRequest && (proxySettings || cliProxySettings)) { const proto = (proxySettings && proxySettings.protocol) || cliProxySettings.protocol || "http:"; const host = (proxySettings && proxySettings.hostname) || cliProxySettings.hostname; const port = (proxySettings && proxySettings.port) || cliProxySettings.port; From 8f9178f9c1bb7f5f8961a63d19e56ef07f0b8567 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 25 Apr 2019 11:57:32 +0300 Subject: [PATCH 15/62] fix: fix the workflow warnings split on Windows (always split by "\n" as the string literals are always generating "/n" terminating lines based on the below-mentioned ES specification) More details here: http://exploringjs.com/es6/ch_template-literals.html#_line-terminators-in-template-literals-are-always-lf-n 8.2.3 Line terminators in template literals are always LF (\n) # Common ways of terminating lines are: Line feed (LF, \n, U+000A): used by Unix (incl. current macOS) Carriage return (CR, \r, U+000D): used by the old Mac OS. CRLF (\r\n): used by Windows. `All of these line terminators are normalized to LF in template literals.` That is, the following code logs true on all platforms: const str = `BEFORE AFTER`; console.log(str === 'BEFORE\nAFTER'); // true --- lib/common/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/common/helpers.ts b/lib/common/helpers.ts index 7283ecb764..5ca4bb5d3f 100644 --- a/lib/common/helpers.ts +++ b/lib/common/helpers.ts @@ -389,7 +389,7 @@ export function getMessageWithBorders(message: string, spanLength = 3): string { return ""; } - const longestRowLength = message.split(EOL).sort((a, b) => { return b.length - a.length; })[0].length; + const longestRowLength = message.split("\n").sort((a, b) => { return b.length - a.length; })[0].length; let border = "*".repeat(longestRowLength + 2 * spanLength); // * 2 for both sides if (border.length % 2 === 0) { border += "*"; // the * should always be an odd number in order to get * in each edge (we will remove the even *s below) @@ -405,7 +405,7 @@ export function getMessageWithBorders(message: string, spanLength = 3): string { EOL, border + EOL, emptyRow, - ...message.split(EOL).map(row => formatRow(row)), + ...message.split("\n").map(row => formatRow(row.trim())), emptyRow, border + EOL, EOL From 7fdb7b784eec37fa41f54c7394dd75bb3428d39d Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Thu, 18 Apr 2019 17:51:22 +0300 Subject: [PATCH 16/62] feat: initial work on watch app integration --- lib/bootstrap.ts | 1 + lib/common/definitions/mobile.d.ts | 4 + .../ios/simulator/ios-simulator-device.ts | 1 + lib/definitions/xcode.d.ts | 3 +- lib/services/ios-project-service.ts | 32 +++-- lib/services/ios-watch-app-service.ts | 117 ++++++++++++++++++ 6 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 lib/services/ios-watch-app-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 0cdbeb89bd..d99ca32d44 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -13,6 +13,7 @@ $injector.require("androidProjectService", "./services/android-project-service") $injector.require("androidPluginBuildService", "./services/android-plugin-build-service"); $injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); $injector.require("iOSExtensionsService", "./services/ios-extensions-service"); +$injector.require("iOSWatchAppService", "./services/ios-watch-app-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); $injector.require("iOSProvisionService", "./services/ios-provision-service"); $injector.require("xcconfigService", "./services/xcconfig-service"); diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 1ce531beef..229c23a939 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -81,6 +81,10 @@ declare module Mobile { imageIdentifier?: string; } + interface IIOSWatchSimulatorDevice extends IDeviceInfo{ + + } + interface IDeviceError extends Error, IDeviceIdentifier { } interface IDeviceIdentifier { diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index 50a178d4f3..489712a140 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -10,6 +10,7 @@ export class IOSSimulator extends IOSDeviceBase implements Mobile.IiOSDevice { public applicationManager: Mobile.IDeviceApplicationManager; public fileSystem: Mobile.IDeviceFileSystem; public deviceInfo: Mobile.IDeviceInfo; + public watchSimulator: Mobile.IIOSWatchSimulatorDevice; constructor(private simulator: Mobile.IiSimDevice, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, diff --git a/lib/definitions/xcode.d.ts b/lib/definitions/xcode.d.ts index 065fbe2067..01c561dab3 100644 --- a/lib/definitions/xcode.d.ts +++ b/lib/definitions/xcode.d.ts @@ -28,7 +28,7 @@ declare module "nativescript-dev-xcode" { pbxXCBuildConfigurationSection(): any; - addTarget(targetName: string, targetType: string, targetPath?: string): target; + addTarget(targetName: string, targetType: string, targetPath?: string, parentTarget?: string): target; addBuildPhase(filePathsArray: string[], buildPhaseType: string, comment: string, @@ -47,6 +47,7 @@ declare module "nativescript-dev-xcode" { addBuildProperty(prop: string, value: any, build_name?: string, productName?: string): void; addToHeaderSearchPaths(file: string|Object, productName?: string): void; removeTargetsByProductType(targetType: string): void + getFirstTarget(): {uuid: string} } class target { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 3a68c26b91..45d451fb06 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -53,7 +53,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $plistParser: IPlistParser, private $sysInfo: ISysInfo, private $xcconfigService: IXcconfigService, - private $iOSExtensionsService: IIOSExtensionsService) { + private $iOSExtensionsService: IIOSExtensionsService, + private $iOSWatchAppService: IIOSExtensionsService) { super($fs, $projectDataService); } @@ -414,7 +415,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ args = args.concat((buildConfig && buildConfig.architectures) || this.getBuildArchitectures(projectData, buildConfig, ["armv7", "arm64"])); args = args.concat([ - "-sdk", DevicePlatformSdkName, "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR) ]); @@ -503,6 +503,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ xcode.setAutomaticSigningStyle(projectData.projectName, teamId); xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.app-extension", teamId); + xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.watchkit2-extension", teamId); + xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.application.watchapp2", teamId); xcode.save(); this.$logger.trace(`Set Automatic signing style and team id ${teamId}.`); @@ -584,12 +586,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ .concat([ "build", "-configuration", getConfigurationName(buildConfig.release), - "-sdk", SimulatorPlatformSdkName, "ONLY_ACTIVE_ARCH=NO", "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR), - "CODE_SIGN_IDENTITY=", + "CODE_SIGNING_ALLOWED=NO", + "-destination", + "generic/platform=iOS Simulator" ]) - .concat(this.xcbuildProjectArgs(projectRoot, projectData)); + .concat(this.xcbuildProjectArgs(projectRoot, projectData, "scheme")); await this.xcodebuild(args, projectRoot, buildConfig.buildOutputStdio); } @@ -781,15 +784,18 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f _.each(imagesToRemove, image => project.removeResourceFile(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image))); this.savePbxProj(project, projectData); - - const resourcesNativeCodePath = path.join( - projectData.getAppResourcesDirectoryPath(), - this.getPlatformData(projectData).normalizedPlatformName, - constants.NATIVE_SOURCE_FOLDER - ); - - await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); } + + const platformData = this.getPlatformData(projectData); + const resourlcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); + const pbxProjPath = this.getPbxProjPath(projectData); + const resourcesNativeCodePath = path.join( + projectData.getAppResourcesDirectoryPath(), + platformData.normalizedPlatformName, + constants.NATIVE_SOURCE_FOLDER + ); + await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); + await this.$iOSWatchAppService.addExtensionsFromPath({ extensionsFolderPath: path.join(resourlcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); } diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts new file mode 100644 index 0000000000..5d90ebedac --- /dev/null +++ b/lib/services/ios-watch-app-service.ts @@ -0,0 +1,117 @@ +import * as path from "path"; + +export class IOSWatchAppService implements IIOSExtensionsService { + constructor(private $fs: IFileSystem, + private $pbxprojDomXcode: IPbxprojDomXcode, + private $xcode: IXcode) { + } + + public async addExtensionsFromPath({extensionsFolderPath, projectData, platformData, pbxProjPath}: IAddExtensionsFromPathOptions): Promise { + const targetUuids: string[] = []; + let addedExtensions = false; + if (!this.$fs.exists(extensionsFolderPath)) { + return false; + } + const project = new this.$xcode.project(pbxProjPath); + const appPath = path.join(extensionsFolderPath, "watchapp"); + const extensionPath = path.join(extensionsFolderPath, "watchextension"); + project.parseSync(); + const appFolder = this.$fs.readDirectory(appPath) + .filter(fileName => { + const filePath = path.join(appPath, fileName); + const stats = this.$fs.getFsStats(filePath); + + return stats.isDirectory() && !fileName.startsWith("."); + })[0]; + + const extensionFolder = this.$fs.readDirectory(extensionPath) + .filter(fileName => { + const filePath = path.join(extensionPath, fileName); + const stats = this.$fs.getFsStats(filePath); + + return stats.isDirectory() && !fileName.startsWith("."); + })[0]; + + let targetUuid = this.addExtensionToProject(appPath, appFolder, project, projectData, platformData, "watch_app", `${projectData.projectIdentifiers.ios}.watchkitapp`, project.getFirstTarget().uuid); + targetUuids.push(targetUuid); + targetUuid = this.addExtensionToProject(extensionPath, extensionFolder, project, projectData, platformData, "watch_extension", `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, targetUuid); + targetUuids.push(targetUuid); + addedExtensions = true; + + this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); + this.prepareExtensionSigning(targetUuids, projectData, pbxProjPath); + + return addedExtensions; + } + + private addExtensionToProject(extensionsFolderPath: string, extensionFolder: string, project: IXcode.project, projectData: IProjectData, platformData: IPlatformData, targetType: string, identifier: string, parentTarget: string): string { + const extensionPath = path.join(extensionsFolderPath, extensionFolder); + const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath); + const files = this.$fs.readDirectory(extensionPath) + .filter(filePath => !filePath.startsWith(".")) + .map(filePath => path.join(extensionPath, filePath)); + const target = project.addTarget(extensionFolder, targetType, extensionRelativePath, parentTarget); + project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid); + project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid); + project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid); + + const extJsonPath = path.join(extensionsFolderPath, extensionFolder, "extension.json"); + if (this.$fs.exists(extJsonPath)) { + const extensionJson = this.$fs.readJson(extJsonPath); + _.forEach(extensionJson.frameworks, framework => { + project.addFramework( + framework, + { target: target.uuid } + ); + }); + if (extensionJson.assetcatalogCompilerAppiconName) { + project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", extensionJson.assetcatalogCompilerAppiconName, target.uuid); + } + } + const identifierParts = identifier.split("."); + identifierParts.pop(); + const wkAppBundleIdentifier = identifierParts.join("."); + project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true }); + project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Debug", extensionFolder); + project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Release", extensionFolder); + project.addBuildProperty("SDKROOT", "watchos", "Debug", extensionFolder); + project.addBuildProperty("SDKROOT", "watchos", "Release", extensionFolder); + project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Debug", extensionFolder); + project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Release", extensionFolder); + project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Debug", extensionFolder); + project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Release", extensionFolder); + project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Debug", extensionFolder); + project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Release", extensionFolder); + project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName); + + return target.uuid; + } + + private prepareExtensionSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) { + const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath); + const signing = xcode.getSigning(projectData.projectName); + if (signing !== undefined) { + _.forEach(targetUuids, targetUuid => { + if (signing.style === "Automatic") { + xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team); + } else { + for (const config in signing.configurations) { + const signingConfiguration = signing.configurations[config]; + xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration); + break; + } + } + }); + } + xcode.save(); + } + + public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void { + const project = new this.$xcode.project(pbxProjPath); + project.parseSync(); + project.removeTargetsByProductType("com.apple.product-type.app-extension"); + this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); + } +} + +$injector.register("iOSWatchAppService", IOSWatchAppService); From 25bf41a627a3f85ffe907acf3a913aeab4ad45cb Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Fri, 19 Apr 2019 16:28:44 +0300 Subject: [PATCH 17/62] refactor: extract common logic for watch app and extensions --- lib/definitions/project.d.ts | 29 ++++- lib/services/ios-extensions-service.ts | 58 +++------- .../ios-native-target-service-base.ts | 43 +++++++ lib/services/ios-project-service.ts | 11 +- lib/services/ios-watch-app-service.ts | 106 ++++++------------ 5 files changed, 124 insertions(+), 123 deletions(-) create mode 100644 lib/services/ios-native-target-service-base.ts diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 37be0d1158..e5d4254a94 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -592,17 +592,42 @@ interface IIOSExtensionsService { removeExtensions(options: IRemoveExtensionsOptions): void; } -interface IAddExtensionsFromPathOptions { - extensionsFolderPath: string; +interface IIOSNativeTargetServiceBase { +} + +/** + * Describes a service used to add and remove iOS extension + */ +interface IIOSExtensionsService { + addExtensionsFromPath(options: IAddExtensionsFromPathOptions): Promise; + removeExtensions(options: IRemoveExtensionsOptions): void; +} + +interface IIOSWatchAppService { + addWatchAppFromPath(options: IAddWatchAppFromPathOptions): Promise; + removeWatchApp(options: IRemoveWatchAppOptions): void; +} + +interface IAddTargetFromPathOptions { projectData: IProjectData; platformData: IPlatformData; pbxProjPath: string; } +interface IAddExtensionsFromPathOptions extends IAddTargetFromPathOptions { + extensionsFolderPath: string; +} + +interface IAddWatchAppFromPathOptions extends IAddTargetFromPathOptions { + watchAppFolderPath: string; +} + interface IRemoveExtensionsOptions { pbxProjPath: string } +interface IRemoveWatchAppOptions extends IRemoveExtensionsOptions{} + interface IRubyFunction { functionName: string; functionParameters?: string; diff --git a/lib/services/ios-extensions-service.ts b/lib/services/ios-extensions-service.ts index e54420b635..bd8daa362d 100644 --- a/lib/services/ios-extensions-service.ts +++ b/lib/services/ios-extensions-service.ts @@ -1,9 +1,11 @@ import * as path from "path"; +import { NativeTargetServiceBase } from "./ios-native-target-service-base"; -export class IOSExtensionsService implements IIOSExtensionsService { - constructor(private $fs: IFileSystem, - private $pbxprojDomXcode: IPbxprojDomXcode, - private $xcode: IXcode) { +export class IOSExtensionsService extends NativeTargetServiceBase implements IIOSExtensionsService { + constructor(protected $fs: IFileSystem, + protected $pbxprojDomXcode: IPbxprojDomXcode, + protected $xcode: IXcode) { + super($fs, $pbxprojDomXcode, $xcode); } public async addExtensionsFromPath({extensionsFolderPath, projectData, platformData, pbxProjPath}: IAddExtensionsFromPathOptions): Promise { @@ -22,29 +24,20 @@ export class IOSExtensionsService implements IIOSExtensionsService { return stats.isDirectory() && !fileName.startsWith("."); }) .forEach(extensionFolder => { - const targetUuid = this.addExtensionToProject(extensionsFolderPath, extensionFolder, project, projectData, platformData); - targetUuids.push(targetUuid); + const target = this.addTargetToProject(extensionsFolderPath, extensionFolder, 'app_extension', project, platformData); + this.configureTarget(extensionFolder, path.join(extensionsFolderPath, extensionFolder), target, project, projectData); + targetUuids.push(target.uuid); addedExtensions = true; }); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); - this.prepareExtensionSigning(targetUuids, projectData, pbxProjPath); + this.prepareSigning(targetUuids, projectData, pbxProjPath); return addedExtensions; } - private addExtensionToProject(extensionsFolderPath: string, extensionFolder: string, project: IXcode.project, projectData: IProjectData, platformData: IPlatformData): string { - const extensionPath = path.join(extensionsFolderPath, extensionFolder); - const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath); - const files = this.$fs.readDirectory(extensionPath) - .filter(filePath => !filePath.startsWith(".")) - .map(filePath => path.join(extensionPath, filePath)); - const target = project.addTarget(extensionFolder, 'app_extension', extensionRelativePath); - project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid); - project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid); - project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid); - - const extJsonPath = path.join(extensionsFolderPath, extensionFolder, "extension.json"); + private configureTarget(extensionName: string, extensionPath: string, target: IXcode.target, project: IXcode.project, projectData: IProjectData) { + const extJsonPath = path.join(extensionPath, "extension.json"); if (this.$fs.exists(extJsonPath)) { const extensionJson = this.$fs.readJson(extJsonPath); _.forEach(extensionJson.frameworks, framework => { @@ -58,31 +51,8 @@ export class IOSExtensionsService implements IIOSExtensionsService { } } - project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true }); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionFolder}`, "Debug", extensionFolder); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionFolder}`, "Release", extensionFolder); - project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName); - - return target.uuid; - } - - private prepareExtensionSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) { - const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath); - const signing = xcode.getSigning(projectData.projectName); - if (signing !== undefined) { - _.forEach(targetUuids, targetUuid => { - if (signing.style === "Automatic") { - xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team); - } else { - for (const config in signing.configurations) { - const signingConfiguration = signing.configurations[config]; - xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration); - break; - } - } - }); - } - xcode.save(); + project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionName}`, "Debug", extensionName); + project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionName}`, "Release", extensionName); } public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void { diff --git a/lib/services/ios-native-target-service-base.ts b/lib/services/ios-native-target-service-base.ts new file mode 100644 index 0000000000..48d1d94779 --- /dev/null +++ b/lib/services/ios-native-target-service-base.ts @@ -0,0 +1,43 @@ +import * as path from "path"; + +export abstract class NativeTargetServiceBase implements IIOSNativeTargetServiceBase { + constructor(protected $fs: IFileSystem, + protected $pbxprojDomXcode: IPbxprojDomXcode, + protected $xcode: IXcode) { + } + + protected addTargetToProject(extensionsFolderPath: string, extensionFolder: string, targetType: string, project: IXcode.project, platformData: IPlatformData, parentTarget?: string): IXcode.target { + const extensionPath = path.join(extensionsFolderPath, extensionFolder); + const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath); + const files = this.$fs.readDirectory(extensionPath) + .filter(filePath => !filePath.startsWith(".")) + .map(filePath => path.join(extensionPath, filePath)); + const target = project.addTarget(extensionFolder, targetType, extensionRelativePath, parentTarget); + project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid); + project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid); + project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid); + + project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true }); + project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName); + return target; + } + + protected prepareSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) { + const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath); + const signing = xcode.getSigning(projectData.projectName); + if (signing !== undefined) { + _.forEach(targetUuids, targetUuid => { + if (signing.style === "Automatic") { + xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team); + } else { + for (const config in signing.configurations) { + const signingConfiguration = signing.configurations[config]; + xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration); + break; + } + } + }); + } + xcode.save(); + } +} diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 45d451fb06..0dab214a99 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -54,7 +54,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $sysInfo: ISysInfo, private $xcconfigService: IXcconfigService, private $iOSExtensionsService: IIOSExtensionsService, - private $iOSWatchAppService: IIOSExtensionsService) { + private $iOSWatchAppService: IIOSWatchAppService) { super($fs, $projectDataService); } @@ -785,9 +785,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.savePbxProj(project, projectData); } - + const platformData = this.getPlatformData(projectData); - const resourlcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); + const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); const pbxProjPath = this.getPbxProjPath(projectData); const resourcesNativeCodePath = path.join( projectData.getAppResourcesDirectoryPath(), @@ -795,7 +795,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f constants.NATIVE_SOURCE_FOLDER ); await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); - await this.$iOSWatchAppService.addExtensionsFromPath({ extensionsFolderPath: path.join(resourlcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); + this.$iOSWatchAppService.removeWatchApp({ pbxProjPath }); + await this.$iOSWatchAppService.addWatchAppFromPath({ watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); } @@ -809,6 +810,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f // src folder should not be copied as the pbxproject will have references to its files this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER)); this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_EXTENSION_FOLDER)); + this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchapp")); + this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchextension")); this.$fs.deleteDirectory(this.getAppResourcesDestinationDirectoryPath(projectData)); } diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts index 5d90ebedac..54d8d15374 100644 --- a/lib/services/ios-watch-app-service.ts +++ b/lib/services/ios-watch-app-service.ts @@ -1,20 +1,22 @@ import * as path from "path"; +import { NativeTargetServiceBase } from "./ios-native-target-service-base"; -export class IOSWatchAppService implements IIOSExtensionsService { - constructor(private $fs: IFileSystem, - private $pbxprojDomXcode: IPbxprojDomXcode, - private $xcode: IXcode) { +export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSWatchAppService { + constructor(protected $fs: IFileSystem, + protected $pbxprojDomXcode: IPbxprojDomXcode, + protected $xcode: IXcode) { + super($fs, $pbxprojDomXcode, $xcode); } - public async addExtensionsFromPath({extensionsFolderPath, projectData, platformData, pbxProjPath}: IAddExtensionsFromPathOptions): Promise { + public async addWatchAppFromPath({watchAppFolderPath, projectData, platformData, pbxProjPath}: IAddWatchAppFromPathOptions): Promise { const targetUuids: string[] = []; let addedExtensions = false; - if (!this.$fs.exists(extensionsFolderPath)) { + if (!this.$fs.exists(watchAppFolderPath)) { return false; } const project = new this.$xcode.project(pbxProjPath); - const appPath = path.join(extensionsFolderPath, "watchapp"); - const extensionPath = path.join(extensionsFolderPath, "watchextension"); + const appPath = path.join(watchAppFolderPath, "watchapp"); + const extensionPath = path.join(watchAppFolderPath, "watchextension"); project.parseSync(); const appFolder = this.$fs.readDirectory(appPath) .filter(fileName => { @@ -32,84 +34,42 @@ export class IOSWatchAppService implements IIOSExtensionsService { return stats.isDirectory() && !fileName.startsWith("."); })[0]; - let targetUuid = this.addExtensionToProject(appPath, appFolder, project, projectData, platformData, "watch_app", `${projectData.projectIdentifiers.ios}.watchkitapp`, project.getFirstTarget().uuid); - targetUuids.push(targetUuid); - targetUuid = this.addExtensionToProject(extensionPath, extensionFolder, project, projectData, platformData, "watch_extension", `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, targetUuid); - targetUuids.push(targetUuid); - addedExtensions = true; + const watchApptarget = this.addTargetToProject(appPath, appFolder, "watch_app", project, platformData, project.getFirstTarget().uuid); + this.configureTarget(appFolder, path.join(appPath, appFolder), `${projectData.projectIdentifiers.ios}.watchkitapp`, watchApptarget, project); + targetUuids.push(watchApptarget.uuid); + const watchExtensionTarget = this.addTargetToProject(extensionPath, extensionFolder, "watch_extension", project, platformData, watchApptarget.uuid); + this.configureTarget(extensionFolder, path.join(extensionPath, extensionFolder), `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, watchExtensionTarget, project); + targetUuids.push(watchExtensionTarget.uuid); + addedExtensions = true; this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); - this.prepareExtensionSigning(targetUuids, projectData, pbxProjPath); + this.prepareSigning(targetUuids, projectData, pbxProjPath); return addedExtensions; } - private addExtensionToProject(extensionsFolderPath: string, extensionFolder: string, project: IXcode.project, projectData: IProjectData, platformData: IPlatformData, targetType: string, identifier: string, parentTarget: string): string { - const extensionPath = path.join(extensionsFolderPath, extensionFolder); - const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath); - const files = this.$fs.readDirectory(extensionPath) - .filter(filePath => !filePath.startsWith(".")) - .map(filePath => path.join(extensionPath, filePath)); - const target = project.addTarget(extensionFolder, targetType, extensionRelativePath, parentTarget); - project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid); - project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid); - project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid); - - const extJsonPath = path.join(extensionsFolderPath, extensionFolder, "extension.json"); - if (this.$fs.exists(extJsonPath)) { - const extensionJson = this.$fs.readJson(extJsonPath); - _.forEach(extensionJson.frameworks, framework => { - project.addFramework( - framework, - { target: target.uuid } - ); - }); - if (extensionJson.assetcatalogCompilerAppiconName) { - project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", extensionJson.assetcatalogCompilerAppiconName, target.uuid); - } - } + private configureTarget(targetName: string, targetPath: string, identifier: string, target: IXcode.target, project: IXcode.project) { const identifierParts = identifier.split("."); identifierParts.pop(); const wkAppBundleIdentifier = identifierParts.join("."); - project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true }); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Debug", extensionFolder); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Release", extensionFolder); - project.addBuildProperty("SDKROOT", "watchos", "Debug", extensionFolder); - project.addBuildProperty("SDKROOT", "watchos", "Release", extensionFolder); - project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Debug", extensionFolder); - project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Release", extensionFolder); - project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Debug", extensionFolder); - project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Release", extensionFolder); - project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Debug", extensionFolder); - project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Release", extensionFolder); - project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName); - - return target.uuid; - } - - private prepareExtensionSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) { - const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath); - const signing = xcode.getSigning(projectData.projectName); - if (signing !== undefined) { - _.forEach(targetUuids, targetUuid => { - if (signing.style === "Automatic") { - xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team); - } else { - for (const config in signing.configurations) { - const signingConfiguration = signing.configurations[config]; - xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration); - break; - } - } - }); - } - xcode.save(); + project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Debug", targetName); + project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Release", targetName); + project.addBuildProperty("SDKROOT", "watchos", "Debug", targetName); + project.addBuildProperty("SDKROOT", "watchos", "Release", targetName); + project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Debug", targetName); + project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Release", targetName); + project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Debug", targetName); + project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Release", targetName); + project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Debug", targetName); + project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Release", targetName); + project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName); } - public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void { + public removeWatchApp({pbxProjPath}: IRemoveWatchAppOptions): void { const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - project.removeTargetsByProductType("com.apple.product-type.app-extension"); + project.removeTargetsByProductType("com.apple.product-type.application.watchapp2"); + project.removeTargetsByProductType("com.apple.product-type.watchkit2-extension"); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); } } From 57a97a390df38acd4e379744c6aba55953d4ad19 Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Tue, 23 Apr 2019 16:59:06 +0300 Subject: [PATCH 18/62] fix: code signing for apps with watchapp --- lib/services/ios-project-service.ts | 44 +++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 0dab214a99..b7be03e2ac 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -21,6 +21,11 @@ interface INativeSourceCodeGroup { files: string[]; } +enum ProductArgs { + target = "target", + scheme = "scheme" +} + const DevicePlatformSdkName = "iphoneos"; const SimulatorPlatformSdkName = "iphonesimulator"; @@ -215,7 +220,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ const archivePath = options && options.archivePath ? path.resolve(options.archivePath) : path.join(platformData.getBuildOutputPath(buildConfig), projectData.projectName + ".xcarchive"); let args = ["archive", "-archivePath", archivePath, "-configuration", getConfigurationName(!buildConfig || buildConfig.release)] - .concat(this.xcbuildProjectArgs(projectRoot, projectData, "scheme")); + .concat(this.xcbuildProjectArgs(projectRoot, projectData, ProductArgs.scheme)); if (options && options.additionalArgs) { args = args.concat(options.additionalArgs); @@ -340,7 +345,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return exportFile; } - private xcbuildProjectArgs(projectRoot: string, projectData: IProjectData, product?: "scheme" | "target"): string[] { + private xcbuildProjectArgs(projectRoot: string, projectData: IProjectData, product?: ProductArgs): string[] { const xcworkspacePath = path.join(projectRoot, projectData.projectName + ".xcworkspace"); if (this.$fs.exists(xcworkspacePath)) { return ["-workspace", xcworkspacePath, product ? "-" + product : "-scheme", projectData.projectName]; @@ -414,6 +419,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ args = args.concat((buildConfig && buildConfig.architectures) || this.getBuildArchitectures(projectData, buildConfig, ["armv7", "arm64"])); + if (!this.hasWatchApp(projectData)) { + args = args.concat([ + "-sdk", DevicePlatformSdkName + ]); + } + args = args.concat([ "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR) ]); @@ -580,6 +591,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private async buildForSimulator(projectRoot: string, args: string[], projectData: IProjectData, buildConfig?: IBuildConfig): Promise { const architectures = this.getBuildArchitectures(projectData, buildConfig, ["i386", "x86_64"]); + let product = ProductArgs.target; args = args .concat(architectures) @@ -588,11 +600,16 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ "-configuration", getConfigurationName(buildConfig.release), "ONLY_ACTIVE_ARCH=NO", "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR), - "CODE_SIGNING_ALLOWED=NO", - "-destination", - "generic/platform=iOS Simulator" - ]) - .concat(this.xcbuildProjectArgs(projectRoot, projectData, "scheme")); + ]); + + if (this.hasWatchApp(projectData)) { + product = ProductArgs.scheme; + args = args.concat(["-destination", "generic/platform=iOS Simulator", "CODE_SIGNING_ALLOWED=NO"]); + } else { + args = args.concat(["-sdk", SimulatorPlatformSdkName, "CODE_SIGN_IDENTITY="]); + } + + args = args.concat(this.xcbuildProjectArgs(projectRoot, projectData, product)); await this.xcodebuild(args, projectRoot, buildConfig.buildOutputStdio); } @@ -790,7 +807,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); const pbxProjPath = this.getPbxProjPath(projectData); const resourcesNativeCodePath = path.join( - projectData.getAppResourcesDirectoryPath(), + resourcesDirectoryPath, platformData.normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER ); @@ -1398,6 +1415,17 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f "Enterprise": "enterprise" }[provision.Type]; } + + private hasWatchApp(projectData: IProjectData) { + const platformData = this.getPlatformData(projectData); + const watchAppPath = path.join( + projectData.getAppResourcesDirectoryPath(), + platformData.normalizedPlatformName, + constants.IOS_WATCHAPP_FOLDER + ); + + return this.$fs.exists(watchAppPath); + } } $injector.register("iOSProjectService", IOSProjectService); From 3c08232152ffa98cd55081f0effe9961ecac1a78 Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Tue, 23 Apr 2019 17:00:25 +0300 Subject: [PATCH 19/62] refactor: extract more common logic for extension and watchapp --- lib/constants.ts | 2 + lib/services/ios-extensions-service.ts | 27 ++----- .../ios-native-target-service-base.ts | 45 +++++++++++ lib/services/ios-watch-app-service.ts | 81 ++++++++++--------- 4 files changed, 96 insertions(+), 59 deletions(-) diff --git a/lib/constants.ts b/lib/constants.ts index a1a8961018..38713eb2d9 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -49,6 +49,8 @@ export const TNS_NATIVE_SOURCE_GROUP_NAME = "TNSNativeSource"; export const NATIVE_SOURCE_FOLDER = "src"; export const APPLICATION_RESPONSE_TIMEOUT_SECONDS = 60; export const NATIVE_EXTENSION_FOLDER = "extensions"; +export const IOS_WATCHAPP_FOLDER = "watchapp"; +export const IOS_WATCHAPP_EXTENSION_FOLDER = "watchextension"; export class PackageVersion { static NEXT = "next"; diff --git a/lib/services/ios-extensions-service.ts b/lib/services/ios-extensions-service.ts index bd8daa362d..b92475f712 100644 --- a/lib/services/ios-extensions-service.ts +++ b/lib/services/ios-extensions-service.ts @@ -16,13 +16,7 @@ export class IOSExtensionsService extends NativeTargetServiceBase implements IIO } const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - this.$fs.readDirectory(extensionsFolderPath) - .filter(fileName => { - const filePath = path.join(extensionsFolderPath, fileName); - const stats = this.$fs.getFsStats(filePath); - - return stats.isDirectory() && !fileName.startsWith("."); - }) + this.getTargetDirectories(extensionsFolderPath) .forEach(extensionFolder => { const target = this.addTargetToProject(extensionsFolderPath, extensionFolder, 'app_extension', project, platformData); this.configureTarget(extensionFolder, path.join(extensionsFolderPath, extensionFolder), target, project, projectData); @@ -38,21 +32,12 @@ export class IOSExtensionsService extends NativeTargetServiceBase implements IIO private configureTarget(extensionName: string, extensionPath: string, target: IXcode.target, project: IXcode.project, projectData: IProjectData) { const extJsonPath = path.join(extensionPath, "extension.json"); - if (this.$fs.exists(extJsonPath)) { - const extensionJson = this.$fs.readJson(extJsonPath); - _.forEach(extensionJson.frameworks, framework => { - project.addFramework( - framework, - { target: target.uuid } - ); - }); - if (extensionJson.assetcatalogCompilerAppiconName) { - project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", extensionJson.assetcatalogCompilerAppiconName, target.uuid); - } - } + this.setConfigurationsFromJsonFile(extJsonPath, target.uuid, project); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionName}`, "Debug", extensionName); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionName}`, "Release", extensionName); + this.setXcodeTargetBuildConfigurationProperties( + [{name: "PRODUCT_BUNDLE_IDENTIFIER", value: `${projectData.projectIdentifiers.ios}.${extensionName}`}], + extensionName, + project); } public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void { diff --git a/lib/services/ios-native-target-service-base.ts b/lib/services/ios-native-target-service-base.ts index 48d1d94779..8fbe505924 100644 --- a/lib/services/ios-native-target-service-base.ts +++ b/lib/services/ios-native-target-service-base.ts @@ -1,5 +1,16 @@ import * as path from "path"; +export enum BuildNames { + debug = "Debug", + release = "Release" +} + +export interface IXcodeTargetBuildConfigurationProperty { + name: string; + value: any; + buildNames?: BuildNames[]; +} + export abstract class NativeTargetServiceBase implements IIOSNativeTargetServiceBase { constructor(protected $fs: IFileSystem, protected $pbxprojDomXcode: IPbxprojDomXcode, @@ -40,4 +51,38 @@ export abstract class NativeTargetServiceBase implements IIOSNativeTargetService } xcode.save(); } + + protected getTargetDirectories(folderPath: string): string[] { + return this.$fs.readDirectory(folderPath) + .filter(fileName => { + const filePath = path.join(folderPath, fileName); + const stats = this.$fs.getFsStats(filePath); + + return stats.isDirectory() && !fileName.startsWith("."); + }); + } + + protected setXcodeTargetBuildConfigurationProperties(properties: IXcodeTargetBuildConfigurationProperty[], targetName: string, project: IXcode.project): void { + properties.forEach(property => { + const buildNames = property.buildNames || [BuildNames.debug, BuildNames.release]; + buildNames.forEach((buildName) => { + project.addBuildProperty(property.name, property.value, buildName, targetName); + }); + }); + } + + protected setConfigurationsFromJsonFile(jsonPath: string, targetUuid: string, project: IXcode.project) { + if (this.$fs.exists(jsonPath)) { + const configurationJson = this.$fs.readJson(jsonPath) || {}; + _.forEach(configurationJson.frameworks, framework => { + project.addFramework( + framework, + { target: targetUuid } + ); + }); + if (configurationJson.assetcatalogCompilerAppiconName) { + project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", configurationJson.assetcatalogCompilerAppiconName, targetUuid); + } + } + } } diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts index 54d8d15374..095ac2c105 100644 --- a/lib/services/ios-watch-app-service.ts +++ b/lib/services/ios-watch-app-service.ts @@ -10,59 +10,45 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW public async addWatchAppFromPath({watchAppFolderPath, projectData, platformData, pbxProjPath}: IAddWatchAppFromPathOptions): Promise { const targetUuids: string[] = []; - let addedExtensions = false; + if (!this.$fs.exists(watchAppFolderPath)) { return false; } - const project = new this.$xcode.project(pbxProjPath); - const appPath = path.join(watchAppFolderPath, "watchapp"); - const extensionPath = path.join(watchAppFolderPath, "watchextension"); - project.parseSync(); - const appFolder = this.$fs.readDirectory(appPath) - .filter(fileName => { - const filePath = path.join(appPath, fileName); - const stats = this.$fs.getFsStats(filePath); - return stats.isDirectory() && !fileName.startsWith("."); - })[0]; + const appPath = path.join(watchAppFolderPath, "watchapp"); + const appFolder = this.getTargetDirectories(appPath)[0]; - const extensionFolder = this.$fs.readDirectory(extensionPath) - .filter(fileName => { - const filePath = path.join(extensionPath, fileName); - const stats = this.$fs.getFsStats(filePath); + const extensionPath = path.join(watchAppFolderPath, "watchextension"); + const extensionFolder = this.getTargetDirectories(extensionPath)[0]; - return stats.isDirectory() && !fileName.startsWith("."); - })[0]; + const project = new this.$xcode.project(pbxProjPath); + project.parseSync(); const watchApptarget = this.addTargetToProject(appPath, appFolder, "watch_app", project, platformData, project.getFirstTarget().uuid); - this.configureTarget(appFolder, path.join(appPath, appFolder), `${projectData.projectIdentifiers.ios}.watchkitapp`, watchApptarget, project); + this.configureTarget( + appFolder, + path.join(appPath, appFolder), + `${projectData.projectIdentifiers.ios}.watchkitapp`, + "watchapp.json", + watchApptarget, + project + ); targetUuids.push(watchApptarget.uuid); + const watchExtensionTarget = this.addTargetToProject(extensionPath, extensionFolder, "watch_extension", project, platformData, watchApptarget.uuid); - this.configureTarget(extensionFolder, path.join(extensionPath, extensionFolder), `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, watchExtensionTarget, project); + this.configureTarget( + extensionFolder, + path.join(extensionPath, extensionFolder), + `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, + "extension.json", + watchExtensionTarget, + project); targetUuids.push(watchExtensionTarget.uuid); - addedExtensions = true; this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); this.prepareSigning(targetUuids, projectData, pbxProjPath); - return addedExtensions; - } - - private configureTarget(targetName: string, targetPath: string, identifier: string, target: IXcode.target, project: IXcode.project) { - const identifierParts = identifier.split("."); - identifierParts.pop(); - const wkAppBundleIdentifier = identifierParts.join("."); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Debug", targetName); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Release", targetName); - project.addBuildProperty("SDKROOT", "watchos", "Debug", targetName); - project.addBuildProperty("SDKROOT", "watchos", "Release", targetName); - project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Debug", targetName); - project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Release", targetName); - project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Debug", targetName); - project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Release", targetName); - project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Debug", targetName); - project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Release", targetName); - project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName); + return true; } public removeWatchApp({pbxProjPath}: IRemoveWatchAppOptions): void { @@ -72,6 +58,25 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW project.removeTargetsByProductType("com.apple.product-type.watchkit2-extension"); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); } + + private configureTarget(targetName: string, targetPath: string, identifier: string, configurationFileName: string, target: IXcode.target, project: IXcode.project) { + const targetConfigurationJsonPath = path.join(targetPath, configurationFileName); + this.setConfigurationsFromJsonFile(targetConfigurationJsonPath, target.uuid, project); + + const identifierParts = identifier.split("."); + identifierParts.pop(); + const wkAppBundleIdentifier = identifierParts.join("."); + + this.setXcodeTargetBuildConfigurationProperties([ + {name: "PRODUCT_BUNDLE_IDENTIFIER", value: identifier}, + {name: "SDKROOT", value: "watchos"}, + {name: "TARGETED_DEVICE_FAMILY", value: 4}, + {name: "WATCHOS_DEPLOYMENT_TARGET", value: 4.1}, + {name: "WK_APP_BUNDLE_IDENTIFIER", value: wkAppBundleIdentifier} + ], targetName, project); + + project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName); + } } $injector.register("iOSWatchAppService", IOSWatchAppService); From 1723a67ac9977c2aaec0a4aea3bf6787bb8a6c43 Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Wed, 24 Apr 2019 20:03:26 +0300 Subject: [PATCH 20/62] refactor: extract some constants --- lib/constants.ts | 17 +++++++++++++++++ lib/services/ios-extensions-service.ts | 5 +++-- lib/services/ios-project-service.ts | 24 ++++++++++++++++++------ lib/services/ios-watch-app-service.ts | 23 +++++++++++++---------- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/lib/constants.ts b/lib/constants.ts index 38713eb2d9..7443a4632a 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -282,3 +282,20 @@ export const LiveSyncEvents = { liveSyncStarted: "liveSyncStarted", liveSyncNotification: "notify" }; + +export enum IOSDeviceTargets { + ios = "1,2", + watchos = 4 +} + +export enum IOSNativeTargetProductTypes { + watchApp = "com.apple.product-type.application.watchapp2", + watchExtension = "com.apple.product-type.watchkit2-extension", + appExtension = "com.apple.product-type.app-extension" +} + +export enum IOSNativeTargetTypes { + watchApp = "watch_app", + watchExtension = "watch_extension", + appExtension = "app_extension" +} diff --git a/lib/services/ios-extensions-service.ts b/lib/services/ios-extensions-service.ts index b92475f712..3e55c184db 100644 --- a/lib/services/ios-extensions-service.ts +++ b/lib/services/ios-extensions-service.ts @@ -1,5 +1,6 @@ import * as path from "path"; import { NativeTargetServiceBase } from "./ios-native-target-service-base"; +import { IOSNativeTargetProductTypes, IOSNativeTargetTypes } from "../constants"; export class IOSExtensionsService extends NativeTargetServiceBase implements IIOSExtensionsService { constructor(protected $fs: IFileSystem, @@ -18,7 +19,7 @@ export class IOSExtensionsService extends NativeTargetServiceBase implements IIO project.parseSync(); this.getTargetDirectories(extensionsFolderPath) .forEach(extensionFolder => { - const target = this.addTargetToProject(extensionsFolderPath, extensionFolder, 'app_extension', project, platformData); + const target = this.addTargetToProject(extensionsFolderPath, extensionFolder, IOSNativeTargetTypes.appExtension, project, platformData); this.configureTarget(extensionFolder, path.join(extensionsFolderPath, extensionFolder), target, project, projectData); targetUuids.push(target.uuid); addedExtensions = true; @@ -43,7 +44,7 @@ export class IOSExtensionsService extends NativeTargetServiceBase implements IIO public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void { const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - project.removeTargetsByProductType("com.apple.product-type.app-extension"); + project.removeTargetsByProductType(IOSNativeTargetProductTypes.appExtension); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); } } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index b7be03e2ac..3ba70d90b3 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -13,7 +13,7 @@ import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import * as mobileProvisionFinder from "ios-mobileprovision-finder"; -import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants"; +import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants, IOSNativeTargetProductTypes } from "../constants"; interface INativeSourceCodeGroup { name: string; @@ -513,9 +513,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } xcode.setAutomaticSigningStyle(projectData.projectName, teamId); - xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.app-extension", teamId); - xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.watchkit2-extension", teamId); - xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.application.watchapp2", teamId); + xcode.setAutomaticSigningStyleByTargetProductTypesList([ + IOSNativeTargetProductTypes.appExtension, + IOSNativeTargetProductTypes.watchApp, + IOSNativeTargetProductTypes.watchExtension + ], + teamId); xcode.save(); this.$logger.trace(`Set Automatic signing style and team id ${teamId}.`); @@ -557,7 +560,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ identity: mobileprovision.Type === "Development" ? "iPhone Developer" : "iPhone Distribution" }; xcode.setManualSigningStyle(projectData.projectName, configuration); - xcode.setManualSigningStyleByTargetProductType("com.apple.product-type.app-extension", configuration); + xcode.setManualSigningStyleByTargetProductTypesList([ + IOSNativeTargetProductTypes.appExtension, + IOSNativeTargetProductTypes.watchApp, + IOSNativeTargetProductTypes.watchExtension + ], + configuration); xcode.save(); // this.cache(uuid); @@ -813,7 +821,11 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f ); await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); this.$iOSWatchAppService.removeWatchApp({ pbxProjPath }); - await this.$iOSWatchAppService.addWatchAppFromPath({ watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); + const addedWatchApp = await this.$iOSWatchAppService.addWatchAppFromPath({ watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); + + if (addedWatchApp) { + this.$logger.warn("The support for Apple Watch App is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: ISSUE LINK"); + } } diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts index 095ac2c105..8d679302d9 100644 --- a/lib/services/ios-watch-app-service.ts +++ b/lib/services/ios-watch-app-service.ts @@ -1,7 +1,10 @@ import * as path from "path"; import { NativeTargetServiceBase } from "./ios-native-target-service-base"; +import { IOSDeviceTargets, IOS_WATCHAPP_FOLDER, IOS_WATCHAPP_EXTENSION_FOLDER, IOSNativeTargetProductTypes, IOSNativeTargetTypes } from "../constants"; export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSWatchAppService { + private static WATCH_APP_IDENTIFIER = "watchkitapp"; + private static WACTCH_EXTENSION_IDENTIFIER = "watchkitextension"; constructor(protected $fs: IFileSystem, protected $pbxprojDomXcode: IPbxprojDomXcode, protected $xcode: IXcode) { @@ -15,31 +18,31 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW return false; } - const appPath = path.join(watchAppFolderPath, "watchapp"); + const appPath = path.join(watchAppFolderPath, IOS_WATCHAPP_FOLDER); const appFolder = this.getTargetDirectories(appPath)[0]; - const extensionPath = path.join(watchAppFolderPath, "watchextension"); + const extensionPath = path.join(watchAppFolderPath, IOS_WATCHAPP_EXTENSION_FOLDER); const extensionFolder = this.getTargetDirectories(extensionPath)[0]; const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - const watchApptarget = this.addTargetToProject(appPath, appFolder, "watch_app", project, platformData, project.getFirstTarget().uuid); + const watchApptarget = this.addTargetToProject(appPath, appFolder, IOSNativeTargetTypes.watchApp, project, platformData, project.getFirstTarget().uuid); this.configureTarget( appFolder, path.join(appPath, appFolder), - `${projectData.projectIdentifiers.ios}.watchkitapp`, + `${projectData.projectIdentifiers.ios}.${IOSWatchAppService.WATCH_APP_IDENTIFIER}`, "watchapp.json", watchApptarget, project ); targetUuids.push(watchApptarget.uuid); - const watchExtensionTarget = this.addTargetToProject(extensionPath, extensionFolder, "watch_extension", project, platformData, watchApptarget.uuid); + const watchExtensionTarget = this.addTargetToProject(extensionPath, extensionFolder, IOSNativeTargetTypes.watchExtension, project, platformData, watchApptarget.uuid); this.configureTarget( extensionFolder, path.join(extensionPath, extensionFolder), - `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, + `${projectData.projectIdentifiers.ios}.${IOSWatchAppService.WATCH_APP_IDENTIFIER}.${IOSWatchAppService.WACTCH_EXTENSION_IDENTIFIER}`, "extension.json", watchExtensionTarget, project); @@ -54,8 +57,8 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW public removeWatchApp({pbxProjPath}: IRemoveWatchAppOptions): void { const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - project.removeTargetsByProductType("com.apple.product-type.application.watchapp2"); - project.removeTargetsByProductType("com.apple.product-type.watchkit2-extension"); + project.removeTargetsByProductType(IOSNativeTargetProductTypes.watchApp); + project.removeTargetsByProductType(IOSNativeTargetProductTypes.watchExtension); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); } @@ -70,8 +73,8 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW this.setXcodeTargetBuildConfigurationProperties([ {name: "PRODUCT_BUNDLE_IDENTIFIER", value: identifier}, {name: "SDKROOT", value: "watchos"}, - {name: "TARGETED_DEVICE_FAMILY", value: 4}, - {name: "WATCHOS_DEPLOYMENT_TARGET", value: 4.1}, + {name: "TARGETED_DEVICE_FAMILY", value: IOSDeviceTargets.watchos}, + {name: "WATCHOS_DEPLOYMENT_TARGET", value: 4.1}, //TODO consider WATCHOS_DEPLOYMENT_TARGET in json configuration {name: "WK_APP_BUNDLE_IDENTIFIER", value: wkAppBundleIdentifier} ], targetName, project); From ac311ecaac4f552dfd6298e8240f7b1240409e6a Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Thu, 25 Apr 2019 17:21:21 +0300 Subject: [PATCH 21/62] chore: update pbxproj libraries --- npm-shrinkwrap.json | 11 +++++------ package.json | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2c6008a7fe..46a9da7799 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -5316,9 +5316,8 @@ } }, "nativescript-dev-xcode": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/nativescript-dev-xcode/-/nativescript-dev-xcode-0.1.0.tgz", - "integrity": "sha512-auQd2/qVdOwD/8v+rHKLflYE8jpT4o1wMwtEFkFGp7sewLkBQz7y9pH79oNM7ztyw0gnL/cFAhLT/ViGLWdw6w==", + "version": "https://github.com/NativeScript/nativescript-dev-xcode/tarball/ec70f5d6032a72b65298ad56fa6ec0c4b2ef03a3", + "integrity": "sha512-CIZGo20Mulu/0u5Im6MYfh/gt2jldK3zt/HtWjUBogV/0QVQNKry2ewC6BudpjetUw4otjmqTRlUhY6wXpZ9Bg==", "requires": { "simple-plist": "^1.0.0", "uuid": "^3.3.2" @@ -6066,9 +6065,9 @@ "dev": true }, "pbxproj-dom": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pbxproj-dom/-/pbxproj-dom-1.1.0.tgz", - "integrity": "sha512-ti2ZuXEptfYbkkUweP6sBtLBlRjTcWkxWG450lbJ+PwNMk8nvhhAtV7lIuQPHR29h7hPUHOIvUBAIljIx8ygxg==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pbxproj-dom/-/pbxproj-dom-1.2.0.tgz", + "integrity": "sha512-K2czrWqA68AR0q1UXz5EBi/zoxcljrkO4RSJX0jPnVn3iyE0HYnYOzaEEDYMpueczkT/Vtdm3SCc3NM+12kMaQ==" }, "pend": { "version": "1.2.0", diff --git a/package.json b/package.json index ad975a9576..4ea1444a73 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "minimatch": "3.0.2", "mkdirp": "0.5.1", "mute-stream": "0.0.5", - "nativescript-dev-xcode": "0.1.0", + "nativescript-dev-xcode": "https://github.com/NativeScript/nativescript-dev-xcode/tarball/ec70f5d6032a72b65298ad56fa6ec0c4b2ef03a3", "nativescript-doctor": "1.9.2", "nativescript-preview-sdk": "0.3.4", "open": "0.0.5", @@ -64,7 +64,7 @@ "osenv": "0.1.3", "pacote": "8.1.6", "pako": "1.0.6", - "pbxproj-dom": "1.1.0", + "pbxproj-dom": "1.2.0", "plist": "1.1.0", "plist-merge-patch": "0.1.1", "proper-lockfile": "3.2.0", From 4b69bb3d949ff610ba665d5515c1d13a91e47796 Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Thu, 25 Apr 2019 18:03:13 +0300 Subject: [PATCH 22/62] fix: no way to control watchos deployment target --- lib/services/ios-extensions-service.ts | 3 ++- lib/services/ios-native-target-service-base.ts | 12 ++++++++++-- lib/services/ios-watch-app-service.ts | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/services/ios-extensions-service.ts b/lib/services/ios-extensions-service.ts index 3e55c184db..fc83e100cf 100644 --- a/lib/services/ios-extensions-service.ts +++ b/lib/services/ios-extensions-service.ts @@ -33,12 +33,13 @@ export class IOSExtensionsService extends NativeTargetServiceBase implements IIO private configureTarget(extensionName: string, extensionPath: string, target: IXcode.target, project: IXcode.project, projectData: IProjectData) { const extJsonPath = path.join(extensionPath, "extension.json"); - this.setConfigurationsFromJsonFile(extJsonPath, target.uuid, project); this.setXcodeTargetBuildConfigurationProperties( [{name: "PRODUCT_BUNDLE_IDENTIFIER", value: `${projectData.projectIdentifiers.ios}.${extensionName}`}], extensionName, project); + + this.setConfigurationsFromJsonFile(extJsonPath, target.uuid, extensionName, project); } public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void { diff --git a/lib/services/ios-native-target-service-base.ts b/lib/services/ios-native-target-service-base.ts index 8fbe505924..d409adcfaf 100644 --- a/lib/services/ios-native-target-service-base.ts +++ b/lib/services/ios-native-target-service-base.ts @@ -71,17 +71,25 @@ export abstract class NativeTargetServiceBase implements IIOSNativeTargetService }); } - protected setConfigurationsFromJsonFile(jsonPath: string, targetUuid: string, project: IXcode.project) { + protected setConfigurationsFromJsonFile(jsonPath: string, targetUuid: string, targetName: string, project: IXcode.project) { if (this.$fs.exists(jsonPath)) { const configurationJson = this.$fs.readJson(jsonPath) || {}; + _.forEach(configurationJson.frameworks, framework => { project.addFramework( framework, { target: targetUuid } ); }); + if (configurationJson.assetcatalogCompilerAppiconName) { - project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", configurationJson.assetcatalogCompilerAppiconName, targetUuid); + project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", configurationJson.assetcatalogCompilerAppiconName, targetUuid); + } + + if (configurationJson.targetBuildConfigurationProperties) { + const properties: IXcodeTargetBuildConfigurationProperty[] = []; + _.forEach(configurationJson.targetBuildConfigurationProperties, (value, name: string) => properties.push({value, name})); + this.setXcodeTargetBuildConfigurationProperties(properties, targetName, project); } } } diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts index 8d679302d9..0a3bbbe8d3 100644 --- a/lib/services/ios-watch-app-service.ts +++ b/lib/services/ios-watch-app-service.ts @@ -64,7 +64,6 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW private configureTarget(targetName: string, targetPath: string, identifier: string, configurationFileName: string, target: IXcode.target, project: IXcode.project) { const targetConfigurationJsonPath = path.join(targetPath, configurationFileName); - this.setConfigurationsFromJsonFile(targetConfigurationJsonPath, target.uuid, project); const identifierParts = identifier.split("."); identifierParts.pop(); @@ -74,10 +73,11 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW {name: "PRODUCT_BUNDLE_IDENTIFIER", value: identifier}, {name: "SDKROOT", value: "watchos"}, {name: "TARGETED_DEVICE_FAMILY", value: IOSDeviceTargets.watchos}, - {name: "WATCHOS_DEPLOYMENT_TARGET", value: 4.1}, //TODO consider WATCHOS_DEPLOYMENT_TARGET in json configuration + {name: "WATCHOS_DEPLOYMENT_TARGET", value: 5.2}, {name: "WK_APP_BUNDLE_IDENTIFIER", value: wkAppBundleIdentifier} ], targetName, project); + this.setConfigurationsFromJsonFile(targetConfigurationJsonPath, target.uuid, targetName, project); project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName); } } From be2ee2ae20ff503f60d43c65776197995195ae7f Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Thu, 25 Apr 2019 18:27:27 +0300 Subject: [PATCH 23/62] chore: remove unnecessary code --- lib/common/definitions/mobile.d.ts | 4 ---- lib/common/mobile/ios/simulator/ios-simulator-device.ts | 1 - lib/definitions/project.d.ts | 3 --- lib/services/ios-native-target-service-base.ts | 2 +- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 229c23a939..1ce531beef 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -81,10 +81,6 @@ declare module Mobile { imageIdentifier?: string; } - interface IIOSWatchSimulatorDevice extends IDeviceInfo{ - - } - interface IDeviceError extends Error, IDeviceIdentifier { } interface IDeviceIdentifier { diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index 489712a140..50a178d4f3 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -10,7 +10,6 @@ export class IOSSimulator extends IOSDeviceBase implements Mobile.IiOSDevice { public applicationManager: Mobile.IDeviceApplicationManager; public fileSystem: Mobile.IDeviceFileSystem; public deviceInfo: Mobile.IDeviceInfo; - public watchSimulator: Mobile.IIOSWatchSimulatorDevice; constructor(private simulator: Mobile.IiSimDevice, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index e5d4254a94..1b52116980 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -592,9 +592,6 @@ interface IIOSExtensionsService { removeExtensions(options: IRemoveExtensionsOptions): void; } -interface IIOSNativeTargetServiceBase { -} - /** * Describes a service used to add and remove iOS extension */ diff --git a/lib/services/ios-native-target-service-base.ts b/lib/services/ios-native-target-service-base.ts index d409adcfaf..5f203d405b 100644 --- a/lib/services/ios-native-target-service-base.ts +++ b/lib/services/ios-native-target-service-base.ts @@ -11,7 +11,7 @@ export interface IXcodeTargetBuildConfigurationProperty { buildNames?: BuildNames[]; } -export abstract class NativeTargetServiceBase implements IIOSNativeTargetServiceBase { +export abstract class NativeTargetServiceBase { constructor(protected $fs: IFileSystem, protected $pbxprojDomXcode: IPbxprojDomXcode, protected $xcode: IXcode) { From 2e5a7e9caf32fc0ff1ac00274b1929c14d83a2ed Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Thu, 25 Apr 2019 19:30:24 +0300 Subject: [PATCH 24/62] chore: fix tests --- lib/services/ios-project-service.ts | 17 +++++++++-------- test/ios-project-service.ts | 8 ++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 3ba70d90b3..e2a4d85b96 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -773,6 +773,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f public async prepareProject(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { const projectRoot = path.join(projectData.platformsDir, "ios"); + const platformData = this.getPlatformData(projectData); + const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); const provision = platformSpecificData && platformSpecificData.provision; const teamId = platformSpecificData && platformSpecificData.teamId; @@ -809,17 +811,16 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f _.each(imagesToRemove, image => project.removeResourceFile(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image))); this.savePbxProj(project, projectData); + + const resourcesNativeCodePath = path.join( + resourcesDirectoryPath, + platformData.normalizedPlatformName, + constants.NATIVE_SOURCE_FOLDER + ); + await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); } - const platformData = this.getPlatformData(projectData); - const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); const pbxProjPath = this.getPbxProjPath(projectData); - const resourcesNativeCodePath = path.join( - resourcesDirectoryPath, - platformData.normalizedPlatformName, - constants.NATIVE_SOURCE_FOLDER - ); - await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); this.$iOSWatchAppService.removeWatchApp({ pbxProjPath }); const addedWatchApp = await this.$iOSWatchAppService.addWatchAppFromPath({ watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index d01f8df6c0..71c1fd3061 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -159,6 +159,10 @@ function createTestInjector(projectPath: string, projectName: string, xCode?: IX removeExtensions: () => { /* */ }, addExtensionsFromPath: () => Promise.resolve() }); + testInjector.register("iOSWatchAppService", { + removeWatchApp: () => { /* */ }, + addWatchAppFromPath: () => Promise.resolve() + }); return testInjector; } @@ -1064,6 +1068,7 @@ describe("iOS Project Service Signing", () => { stack.push({ targetName, manualSigning }); }, setManualSigningStyleByTargetProductType: () => ({}), + setManualSigningStyleByTargetProductTypesList: () => ({}), setManualSigningStyleByTargetKey: () => ({}) }; }; @@ -1085,6 +1090,7 @@ describe("iOS Project Service Signing", () => { stack.push({ targetName, manualSigning }); }, setManualSigningStyleByTargetProductType: () => ({}), + setManualSigningStyleByTargetProductTypesList: () => ({}), setManualSigningStyleByTargetKey: () => ({}) }; }; @@ -1106,6 +1112,7 @@ describe("iOS Project Service Signing", () => { stack.push({ targetName, manualSigning }); }, setManualSigningStyleByTargetProductType: () => ({}), + setManualSigningStyleByTargetProductTypesList: () => ({}), setManualSigningStyleByTargetKey: () => ({}) }; }; @@ -1296,6 +1303,7 @@ describe("buildProject", () => { getSigning: () => ({}), setAutomaticSigningStyle: () => ({}), setAutomaticSigningStyleByTargetProductType: () => ({}), + setAutomaticSigningStyleByTargetProductTypesList: () => ({}), setAutomaticSigningStyleByTargetKey: () => ({}), save: () => ({}) }) From 74ace6bba7c2815bab61d176c0c59c64a61f2425 Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Thu, 25 Apr 2019 20:28:21 +0300 Subject: [PATCH 25/62] fix: wrong check for watchapp folder --- lib/services/ios-watch-app-service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts index 0a3bbbe8d3..dae287e9fb 100644 --- a/lib/services/ios-watch-app-service.ts +++ b/lib/services/ios-watch-app-service.ts @@ -13,15 +13,14 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW public async addWatchAppFromPath({watchAppFolderPath, projectData, platformData, pbxProjPath}: IAddWatchAppFromPathOptions): Promise { const targetUuids: string[] = []; + const appPath = path.join(watchAppFolderPath, IOS_WATCHAPP_FOLDER); + const extensionPath = path.join(watchAppFolderPath, IOS_WATCHAPP_EXTENSION_FOLDER); - if (!this.$fs.exists(watchAppFolderPath)) { + if (!this.$fs.exists(appPath) || !this.$fs.exists(extensionPath)) { return false; } - const appPath = path.join(watchAppFolderPath, IOS_WATCHAPP_FOLDER); const appFolder = this.getTargetDirectories(appPath)[0]; - - const extensionPath = path.join(watchAppFolderPath, IOS_WATCHAPP_EXTENSION_FOLDER); const extensionFolder = this.getTargetDirectories(extensionPath)[0]; const project = new this.$xcode.project(pbxProjPath); From e260a5988419ffab531b11beafd99fd054199099 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 29 Apr 2019 22:18:28 +0300 Subject: [PATCH 26/62] chore: add changelog for 5.3.1 release --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b0810185..f386d37ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,17 @@ NativeScript CLI Changelog * [Fixed #4504](https://github.com/NativeScript/nativescript-cli/issues/4504): Custom tagged versions of android runtime are not supported * [Fixed #4510](https://github.com/NativeScript/nativescript-cli/pull/4510): Handle HTTP 304 response status code +5.3.1 (2019, April 03) +== + +### Implemented +* [Implemented #4492](https://github.com/NativeScript/nativescript-cli/pull/4492): API(kinvey): provide correct data to preview-sdk based on the schema + +### Fixed +* [Fixed #4370](https://github.com/NativeScript/nativescript-cli/issues/4370): NativeScript CLI installation fails on linux +* [Fixed #4451](https://github.com/NativeScript/nativescript-cli/issues/4451): Error while trying to start application on Android emulator with API level Q +* [Fixed #4483](https://github.com/NativeScript/nativescript-cli/pull/4483): Detection fixes for emulator/device + 5.3.0 (2019, March 27) == From 0468853cab2cba264829b64835f6fd41a6013529 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 30 Apr 2019 16:01:11 +0300 Subject: [PATCH 27/62] fix: logcat process is not restarted in some cases In case you have `keepSingleProcess` passed to logcat Stream (this option is passed when using CLI as a library - in this case we keep a long living logcat process alive and change only the filter for it by specifying different PIDs), in case the logcat process dies (for example device is disconnected), the process is not cleaned from CLI's internal cache. This way, in case you try to start the logs again (in the example above - just attach the device again), they are not started as CLI thinks it has a process for this case. So, in case the logcat stream process dies, ensure we clean all the resources related to it. Add unit tests for this case and remove calls to `type`/`cat` from the tests - there's no need to spawn actual child process in the tests. --- lib/common/mobile/android/logcat-helper.ts | 15 ++-- .../mobile/android/logcat-helper.ts | 69 +++++++++++++------ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/lib/common/mobile/android/logcat-helper.ts b/lib/common/mobile/android/logcat-helper.ts index 9c7fb9b05c..f95117a47a 100644 --- a/lib/common/mobile/android/logcat-helper.ts +++ b/lib/common/mobile/android/logcat-helper.ts @@ -39,7 +39,8 @@ export class LogcatHelper implements Mobile.ILogcatHelper { logcatStream.on("close", (code: number) => { try { - this.stop(deviceIdentifier); + this.forceStop(deviceIdentifier); + if (code !== 0) { this.$logger.trace("ADB process exited with code " + code.toString()); } @@ -76,13 +77,17 @@ export class LogcatHelper implements Mobile.ILogcatHelper { */ public stop(deviceIdentifier: string): void { if (this.mapDevicesLoggingData[deviceIdentifier] && !this.mapDevicesLoggingData[deviceIdentifier].keepSingleProcess) { - this.mapDevicesLoggingData[deviceIdentifier].loggingProcess.removeAllListeners(); - this.mapDevicesLoggingData[deviceIdentifier].loggingProcess.kill("SIGINT"); - this.mapDevicesLoggingData[deviceIdentifier].lineStream.removeAllListeners(); - delete this.mapDevicesLoggingData[deviceIdentifier]; + this.forceStop(deviceIdentifier); } } + private forceStop(deviceIdentifier: string): void { + this.mapDevicesLoggingData[deviceIdentifier].loggingProcess.removeAllListeners(); + this.mapDevicesLoggingData[deviceIdentifier].loggingProcess.kill("SIGINT"); + this.mapDevicesLoggingData[deviceIdentifier].lineStream.removeAllListeners(); + delete this.mapDevicesLoggingData[deviceIdentifier]; + } + private async getLogcatStream(deviceIdentifier: string, pid?: string) { const device = await this.$devicesService.getDevice(deviceIdentifier); const minAndroidWithLogcatPidSupport = "7.0.0"; diff --git a/lib/common/test/unit-tests/mobile/android/logcat-helper.ts b/lib/common/test/unit-tests/mobile/android/logcat-helper.ts index 561a7b1114..01f8e24426 100644 --- a/lib/common/test/unit-tests/mobile/android/logcat-helper.ts +++ b/lib/common/test/unit-tests/mobile/android/logcat-helper.ts @@ -3,34 +3,39 @@ import { Yok } from "../../../../yok"; import { assert } from "chai"; import * as path from "path"; import * as childProcess from "child_process"; +import * as fileSystem from "fs"; +import { EventEmitter } from "events"; +import stream = require("stream"); + +class ChildProcessMockInstance extends EventEmitter { + public stdout: stream.Readable; + public stderr: any; + public processKillCallCount = 0; + + constructor(pathToSample: string) { + super(); + this.stdout = fileSystem.createReadStream(pathToSample); + this.stderr = new EventEmitter(); + } + + public kill(): void { + this.processKillCallCount++; + } +} class ChildProcessStub { public processSpawnCallCount = 0; - public processKillCallCount = 0; public adbProcessArgs: string[] = []; - private isWin = /^win/.test(process.platform); + public lastSpawnedProcess: ChildProcessMockInstance = null; public spawn(command: string, args?: string[], options?: any): childProcess.ChildProcess { this.adbProcessArgs = args; this.processSpawnCallCount++; - let pathToExecutable = ""; - let shell = ""; - if (this.isWin) { - pathToExecutable = "type"; - shell = "cmd"; - } else { - pathToExecutable = "cat"; - } - pathToExecutable = path.join(pathToExecutable); const pathToSample = path.join(__dirname, "valid-sample.txt"); - const spawnedProcess = childProcess.spawn(pathToExecutable, [pathToSample], { shell }); - const spawnedProcessKill = spawnedProcess.kill; - spawnedProcess.kill = (signal: string) => { - this.processKillCallCount++; - spawnedProcessKill.call(spawnedProcessKill, signal); - }; - - return spawnedProcess; + + this.lastSpawnedProcess = new ChildProcessMockInstance(pathToSample); + + return this.lastSpawnedProcess; } } @@ -207,7 +212,7 @@ describe("logcat-helper", () => { await logcatHelper.stop(validIdentifier); await logcatHelper.stop(validIdentifier); - assert.equal(childProcessStub.processKillCallCount, 1); + assert.equal(childProcessStub.lastSpawnedProcess.processKillCallCount, 1); }); it("should not kill the process if started with keepSingleProcess", async () => { @@ -220,7 +225,7 @@ describe("logcat-helper", () => { await logcatHelper.stop(validIdentifier); await logcatHelper.stop(validIdentifier); - assert.equal(childProcessStub.processKillCallCount, 0); + assert.equal(childProcessStub.lastSpawnedProcess.processKillCallCount, 0); }); it("should do nothing if called without start", async () => { @@ -229,7 +234,27 @@ describe("logcat-helper", () => { await logcatHelper.stop(validIdentifier); assert.equal(childProcessStub.processSpawnCallCount, 0); - assert.equal(childProcessStub.processKillCallCount, 0); }); + + for (const keepSingleProcess of [false, true]) { + it(`should clear the device identifier cache when the logcat process is killed manually and keepSingleProcess is ${keepSingleProcess}`, async () => { + const logcatHelper = injector.resolve("logcatHelper"); + + await logcatHelper.start({ + deviceIdentifier: validIdentifier, + keepSingleProcess + }); + + assert.equal(childProcessStub.processSpawnCallCount, 1); + + childProcessStub.lastSpawnedProcess.emit("close"); + + await logcatHelper.start({ + deviceIdentifier: validIdentifier + }); + + assert.equal(childProcessStub.processSpawnCallCount, 2); + }); + } }); }); From 8e6f70e50cfda78c11edfbb080a966484055963d Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 2 May 2019 17:24:07 +0300 Subject: [PATCH 28/62] fix: fix the order of applying Podfiles Currently when CLI adds plugin's Podfile, it places its content at the top of the newly generated Podfile. Same happens for the post_install hook of the plugin. The last Podfile that is applied, is the one from App_Resources/iOS, but its content is also placed at the top of the merged Podfile and its post_install action (in case it has such) is executed first. The idea of having Podfile in App_Resources/iOS was to be able to overwrite some settings from plugins Podfiles, for example SWIFT_VERSION. To handle this, append each Podfile at the end of the merged Podfile. Same is valid for the post_install hook coming from a Podfile - it will be appended at end of the merged post_install function. --- lib/services/cocoapods-platform-manager.ts | 6 +- lib/services/cocoapods-service.ts | 11 +- test/cocoapods-service.ts | 256 ++++++++++----------- test/ios-project-service.ts | 20 +- 4 files changed, 148 insertions(+), 145 deletions(-) diff --git a/lib/services/cocoapods-platform-manager.ts b/lib/services/cocoapods-platform-manager.ts index 6b5bfb3771..4427895418 100644 --- a/lib/services/cocoapods-platform-manager.ts +++ b/lib/services/cocoapods-platform-manager.ts @@ -13,10 +13,10 @@ export class CocoaPodsPlatformManager implements ICocoaPodsPlatformManager { if (shouldReplacePlatformSection) { this.$logger.warn(`Multiple identical platforms with different versions have been detected during the processing of podfiles. The current platform's content "${platformSectionData.podfilePlatformData.content}" from ${platformSectionData.podfilePlatformData.path} will be replaced with "${podfilePlatformData.content}" from ${podfilePlatformData.path}`); const newSection = this.buildPlatformSection(podfilePlatformData); - projectPodfileContent = projectPodfileContent.replace(platformSectionData.platformSectionContent, newSection); + projectPodfileContent = projectPodfileContent.replace(platformSectionData.platformSectionContent, newSection.trim()); } } else { - projectPodfileContent += this.buildPlatformSection(podfilePlatformData); + projectPodfileContent = projectPodfileContent.trim() + EOL + EOL + this.buildPlatformSection(podfilePlatformData); } return projectPodfileContent; @@ -29,7 +29,7 @@ export class CocoaPodsPlatformManager implements ICocoaPodsPlatformManager { const allPodfiles = projectPodfileContent.match(podfileContentRegExp) || []; const selectedPlatformData = this.selectPlatformDataFromProjectPodfile(allPodfiles); const newPlatformSection = selectedPlatformData ? this.buildPlatformSection(selectedPlatformData) : ""; - const regExp = new RegExp(`\\r?\\n${platformSectionData.platformSectionContent}\\r?\\n`, "mg"); + const regExp = new RegExp(`${platformSectionData.platformSectionContent}\\r?\\n`, "mg"); projectPodfileContent = projectPodfileContent.replace(regExp, newPlatformSection); } diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index da08ec1252..129941d913 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -1,6 +1,7 @@ import { EOL } from "os"; import * as path from "path"; import { PluginNativeDirNames, PODFILE_NAME, NS_BASE_PODFILE } from "../constants"; +import { regExpEscape } from "../common/helpers"; export class CocoaPodsService implements ICocoaPodsService { private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install"; @@ -100,7 +101,7 @@ export class CocoaPodsService implements ICocoaPodsService { finalPodfileContent = this.$cocoaPodsPlatformManager.addPlatformSection(projectData, podfilePlatformData, finalPodfileContent); } - finalPodfileContent = `${podfileContent}${EOL}${finalPodfileContent}`; + finalPodfileContent = `${finalPodfileContent.trim()}${EOL}${EOL}${podfileContent.trim()}${EOL}`; this.saveProjectPodfile(projectData, finalPodfileContent, nativeProjectPath); } } @@ -115,8 +116,8 @@ export class CocoaPodsService implements ICocoaPodsService { projectPodFileContent = this.$cocoaPodsPlatformManager.removePlatformSection(moduleName, projectPodFileContent, podfilePath); const defaultPodfileBeginning = this.getPodfileHeader(projectData.projectName); - const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${EOL}${this.getPostInstallHookHeader()}end${EOL}end`; - const defaultContentWithoutPostInstallHook = `${defaultPodfileBeginning}end`; + const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${this.getPostInstallHookHeader()}end${EOL}end`; + const defaultContentWithoutPostInstallHook = `${defaultPodfileBeginning}${EOL}end`; const trimmedProjectPodFileContent = projectPodFileContent.trim(); if (!trimmedProjectPodFileContent || trimmedProjectPodFileContent === defaultContentWithPostInstallHook || trimmedProjectPodFileContent === defaultContentWithoutPostInstallHook) { this.$fs.deleteFile(this.getProjectPodfilePath(projectRoot)); @@ -147,7 +148,9 @@ export class CocoaPodsService implements ICocoaPodsService { if (postInstallHookContent) { const index = finalPodfileContent.indexOf(postInstallHookStart); if (index !== -1) { - finalPodfileContent = finalPodfileContent.replace(postInstallHookStart, `${postInstallHookStart}${postInstallHookContent}`); + const regExp = new RegExp(`(${regExpEscape(postInstallHookStart)}[\\s\\S]*?)(\\bend\\b)`, "m"); + finalPodfileContent = finalPodfileContent.replace(regExp, `$1${postInstallHookContent.trimRight()}${EOL}$2`); + } else { if (finalPodfileContent.length > 0) { finalPodfileContent += `${EOL}${EOL}`; diff --git a/test/cocoapods-service.ts b/test/cocoapods-service.ts index 03deeef262..c511e70804 100644 --- a/test/cocoapods-service.ts +++ b/test/cocoapods-service.ts @@ -119,6 +119,10 @@ end`, output: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_with_special_symbols_0 installer +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -135,10 +139,6 @@ def post_installplugin1_with_special_symbols_0 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1_with_special_symbols_0 installer -end end`, projectPodfileContent: "", pluginData: { @@ -165,6 +165,10 @@ end`, output: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_with_special_symbols___and___underscore_0 installer +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -181,10 +185,6 @@ def post_installplugin1_with_special_symbols___and___underscore_0 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1_with_special_symbols___and___underscore_0 installer -end end`, projectPodfileContent: "", pluginData: { @@ -207,6 +207,10 @@ end`, output: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1___plugin_0 installer +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -219,14 +223,13 @@ def post_installplugin1___plugin_0 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1___plugin_0 installer -end end`, projectPodfileContent: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1___plugin_0 installer +end # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -239,10 +242,6 @@ def post_installplugin1___plugin_0 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1___plugin_0 installer -end end`, pluginData: { name: "plugin1_plugin", @@ -268,6 +267,10 @@ end`, output: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_0 installer +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -284,14 +287,13 @@ def post_installplugin1_0 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1_0 installer -end end`, projectPodfileContent: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_0 installer +end # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -308,10 +310,6 @@ def post_installplugin1_0 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1_0 installer -end end`, }, { @@ -329,6 +327,14 @@ end`, output: `use_frameworks! target "projectName" do +# Begin Podfile - secondPluginPlatformsFolderPath/Podfile +pod 'OCMock', '~> 2.0.1' +# End Podfile + +post_install do |installer| + post_installplugin1_0 installer +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -341,14 +347,6 @@ def post_installplugin1_0 (installer) end end # End Podfile - -# Begin Podfile - secondPluginPlatformsFolderPath/Podfile -pod 'OCMock', '~> 2.0.1' -# End Podfile - -post_install do |installer| - post_installplugin1_0 installer -end end`, projectPodfileContent: `use_frameworks! @@ -387,6 +385,12 @@ end`, output: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_0 installer + post_installplugin1_1 installer + post_installplugin1_2 installer +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -413,12 +417,6 @@ def post_installplugin1_2 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1_0 installer - post_installplugin1_1 installer - post_installplugin1_2 installer -end end`, }, { @@ -443,6 +441,11 @@ end`, output: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_0 installer + post_installplugin1_1 +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -462,11 +465,6 @@ target 'MyApp' do end end # End Podfile - -post_install do |installer| - post_installplugin1_0 installer - post_installplugin1_1 -end end`, }, { @@ -489,6 +487,10 @@ end`, projectPodfileContent: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_0 installer +end + # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -505,10 +507,6 @@ def post_installplugin1_0 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1_0 installer -end end`, } ]; @@ -540,6 +538,7 @@ target "projectName" do pod 'GoogleAnalytics', '~> 3.1' # End Podfile + end` }, { @@ -562,6 +561,9 @@ end`, projectPodfileContent: `use_frameworks! target "projectName" do +post_install do |installer| +post_installplugin1_0 installer +end # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -578,10 +580,6 @@ def post_installplugin1_0 (installer) end end # End Podfile - -post_install do |installer| -post_installplugin1_0 installer -end end` }, { @@ -614,6 +612,11 @@ end`, projectPodfileContent: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_0 installer + post_installplugin1_1 installer + post_installplugin1_2 installer +end # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -640,12 +643,6 @@ def post_installplugin1_2 (installer) end end # End Podfile - -post_install do |installer| - post_installplugin1_0 installer - post_installplugin1_1 installer - post_installplugin1_2 installer -end end` }, { @@ -667,6 +664,9 @@ end`, output: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin2_0 installer +end # Begin Podfile - pluginPlatformsFolderPath1/Podfile @@ -678,14 +678,14 @@ def post_installplugin2_0 (installer) end end # End Podfile - -post_install do |installer| -post_installplugin2_0 installer -end end`, projectPodfileContent: `use_frameworks! target "projectName" do +post_install do |installer| + post_installplugin1_0 installer + post_installplugin2_0 installer +end # Begin Podfile - pluginPlatformsFolderPath/Podfile target 'MyApp' do @@ -713,11 +713,6 @@ def post_installplugin2_0 (installer) end end # End Podfile - -post_install do |installer| -post_installplugin1_0 installer -post_installplugin2_0 installer -end end` } ]; @@ -975,44 +970,45 @@ end` expectedProjectPodfileContentAfterApply: `use_frameworks! target "projectName" do -# Begin Podfile - my/full/path/to/app/App_Resources/iOS/Podfile -# platform :ios, '8.0' -# End Podfile +# NativeScriptPlatformSection my/full/path/to/app/App_Resources/iOS/Podfile with 8.0 +platform :ios, '8.0' +# End NativeScriptPlatformSection -# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile -# platform :ios, '11.0' +# Begin Podfile - node_modules/ mypath with spaces/mySecondPluginWithPlatform/Podfile +# platform :ios, '10.0' # End Podfile # Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile pod 'myPod' ~> 0.3.4 # End Podfile -# Begin Podfile - node_modules/ mypath with spaces/mySecondPluginWithPlatform/Podfile -# platform :ios, '10.0' +# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile +# platform :ios, '11.0' # End Podfile -# NativeScriptPlatformSection my/full/path/to/app/App_Resources/iOS/Podfile with 8.0 -platform :ios, '8.0' -# End NativeScriptPlatformSection +# Begin Podfile - my/full/path/to/app/App_Resources/iOS/Podfile +# platform :ios, '8.0' +# End Podfile end`, expectedProjectPodfileContentAfterRemove: `use_frameworks! target "projectName" do +# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with 11.0 +platform :ios, '11.0' +# End NativeScriptPlatformSection -# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile -# platform :ios, '11.0' +# Begin Podfile - node_modules/ mypath with spaces/mySecondPluginWithPlatform/Podfile +# platform :ios, '10.0' # End Podfile # Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile pod 'myPod' ~> 0.3.4 # End Podfile -# Begin Podfile - node_modules/ mypath with spaces/mySecondPluginWithPlatform/Podfile -# platform :ios, '10.0' +# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile +# platform :ios, '11.0' # End Podfile -# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with 11.0 -platform :ios, '11.0' -# End NativeScriptPlatformSection + end` } ]; @@ -1061,13 +1057,13 @@ end` expectedProjectPodfileContent: `use_frameworks! target "projectName" do -# Begin Podfile - path/to/my/plugin2/platforms/ios/Podfile -# platform :ios, '9.0' -# End Podfile - # NativeScriptPlatformSection path/to/my/plugin2/platforms/ios/Podfile with 9.0 platform :ios, '9.0' # End NativeScriptPlatformSection + +# Begin Podfile - path/to/my/plugin2/platforms/ios/Podfile +# platform :ios, '9.0' +# End Podfile end` }, { @@ -1088,21 +1084,21 @@ end` expectedProjectPodfileContent: `use_frameworks! target "projectName" do -# Begin Podfile - my/full/path/to/app/App_Resources/iOS/Podfile -# platform :ios, '9.0' +# NativeScriptPlatformSection my/full/path/to/app/App_Resources/iOS/Podfile with 9.0 +platform :ios, '9.0' +# End NativeScriptPlatformSection + +# Begin Podfile - my/full/path/to/plugin1/platforms/ios/Podfile +# platform :ios, '10.0' # End Podfile # Begin Podfile - my/full/path/to/plugin2/platforms/ios/Podfile pod 'myPod' ~> 0.3.4 # End Podfile -# Begin Podfile - my/full/path/to/plugin1/platforms/ios/Podfile -# platform :ios, '10.0' +# Begin Podfile - my/full/path/to/app/App_Resources/iOS/Podfile +# platform :ios, '9.0' # End Podfile - -# NativeScriptPlatformSection my/full/path/to/app/App_Resources/iOS/Podfile with 9.0 -platform :ios, '9.0' -# End NativeScriptPlatformSection end` }, { @@ -1123,21 +1119,21 @@ end` expectedProjectPodfileContent: `use_frameworks! target "projectName" do -# Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile -pod 'myPod' ~> 0.3.4 +# NativeScriptPlatformSection node_modules/mySecondPluginWithPlatform/Podfile with 10.0 +platform :ios, '10.0' +# End NativeScriptPlatformSection + +# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile +# platform :ios, '9.0' # End Podfile # Begin Podfile - node_modules/mySecondPluginWithPlatform/Podfile # platform :ios, '10.0' # End Podfile -# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile -# platform :ios, '9.0' +# Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile +pod 'myPod' ~> 0.3.4 # End Podfile - -# NativeScriptPlatformSection node_modules/mySecondPluginWithPlatform/Podfile with 10.0 -platform :ios, '10.0' -# End NativeScriptPlatformSection end` }, { @@ -1158,19 +1154,21 @@ end` expectedProjectPodfileContent: `use_frameworks! target "projectName" do -# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile -# platform :ios +# Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile +pod 'myPod' ~> 0.3.4 # End Podfile +# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with +platform :ios +# End NativeScriptPlatformSection + # Begin Podfile - node_modules/mySecondPluginWithPlatform/Podfile # platform :ios, '10.0' # End Podfile -# Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile -pod 'myPod' ~> 0.3.4 -# End Podfile# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with -platform :ios -# End NativeScriptPlatformSection +# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile +# platform :ios +# End Podfile end` }, { @@ -1191,19 +1189,21 @@ end` expectedProjectPodfileContent: `use_frameworks! target "projectName" do -# Begin Podfile - node_modules/mySecondPluginWithPlatform/Podfile -# platform :ios, '10.0' +# Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile +pod 'myPod' ~> 0.3.4 # End Podfile +# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with +platform :ios +# End NativeScriptPlatformSection + # Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile # platform :ios # End Podfile -# Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile -pod 'myPod' ~> 0.3.4 -# End Podfile# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with -platform :ios -# End NativeScriptPlatformSection +# Begin Podfile - node_modules/mySecondPluginWithPlatform/Podfile +# platform :ios, '10.0' +# End Podfile end` }, { @@ -1228,26 +1228,26 @@ end` expectedProjectPodfileContent: `use_frameworks! target "projectName" do -# Begin Podfile - my/full/path/to/app/App_Resources/iOS/Podfile -pod: 'mySecondPlatformPod' ~> 2.0.0 -pod: 'platformKit' ~> 1.0 -# End Podfile +# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with 11.0 +platform :ios, '11.0' +# End NativeScriptPlatformSection -# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile -# platform :ios, '11.0' +# Begin Podfile - node_modules/ mypath with spaces/mySecondPluginWithPlatform/Podfile +# platform :ios, '10.0' # End Podfile # Begin Podfile - node_modules/myPluginWithoutPlatform/Podfile pod 'myPod' ~> 0.3.4 # End Podfile -# Begin Podfile - node_modules/ mypath with spaces/mySecondPluginWithPlatform/Podfile -# platform :ios, '10.0' +# Begin Podfile - node_modules/myFirstPluginWithPlatform/Podfile +# platform :ios, '11.0' # End Podfile -# NativeScriptPlatformSection node_modules/myFirstPluginWithPlatform/Podfile with 11.0 -platform :ios, '11.0' -# End NativeScriptPlatformSection +# Begin Podfile - my/full/path/to/app/App_Resources/iOS/Podfile +pod: 'mySecondPlatformPod' ~> 2.0.0 +pod: 'platformKit' ~> 1.0 +# End Podfile end` } ]; diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index b419ff9e7a..9a75634b04 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -405,14 +405,14 @@ describe("Cocoapods support", () => { const expectedPlatformSection = [ `# NativeScriptPlatformSection ${basePodfilePath} with 8.1`, "platform :ios, '8.1'", - "# End NativeScriptPlatformSection", + "# End NativeScriptPlatformSection\n" ].join("\n"); const expectedProjectPodfileContent = ["use_frameworks!\n", `target "${projectName}" do`, + expectedPlatformSection, `# Begin Podfile - ${basePodfilePath}`, expectedPluginPodfileContent, - "# End Podfile\n", - expectedPlatformSection, + "# End Podfile", "end"] .join("\n"); assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); @@ -483,14 +483,14 @@ describe("Cocoapods support", () => { const expectedPlatformSection = [ `# NativeScriptPlatformSection ${pluginPodfilePath} with 8.1`, "platform :ios, '8.1'", - "# End NativeScriptPlatformSection", + "# End NativeScriptPlatformSection\n", ].join("\n"); const expectedProjectPodfileContent = ["use_frameworks!\n", `target "${projectName}" do`, + expectedPlatformSection, `# Begin Podfile - ${pluginPodfilePath}`, expectedPluginPodfileContent, - "# End Podfile\n", - expectedPlatformSection, + "# End Podfile", "end"] .join("\n"); assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); @@ -565,14 +565,14 @@ describe("Cocoapods support", () => { const expectedPlatformSection = [ `# NativeScriptPlatformSection ${pluginPodfilePath} with 8.1`, "platform :ios, '8.1'", - "# End NativeScriptPlatformSection", + "# End NativeScriptPlatformSection\n", ].join("\n"); const expectedProjectPodfileContent = ["use_frameworks!\n", `target "${projectName}" do`, + expectedPlatformSection, `# Begin Podfile - ${pluginPodfilePath}`, expectedPluginPodfileContent, - "# End Podfile\n", - expectedPlatformSection, + "# End Podfile", "end"] .join("\n"); assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); @@ -1246,7 +1246,7 @@ describe("Merge Project XCConfig files", () => { const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths(projectRoot); _.each(destinationFilePaths, destinationFilePath => { - assert.isTrue(fs.exists(destinationFilePath), `Target build xcconfig ${destinationFilePath} is missing.` ); + assert.isTrue(fs.exists(destinationFilePath), `Target build xcconfig ${destinationFilePath} is missing.`); const content = fs.readFile(destinationFilePath).toString(); assert.equal(content, ""); }); From 3e586f2eb0b5671e91c654b3a25ee31e2252df9e Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Sun, 21 Apr 2019 12:55:54 +0300 Subject: [PATCH 29/62] feat: allow executing JS funcitons on cleanup In some cases it is required to execute some JS functions to clean used resources by CLI. Add an easy way to specify JS files which should be executed by the cleanup process. Each JS action is defined by a JS File, which should be required, data that will be passed to the default exported function in the file and timeout - if the action cannot be executed for specified time (3 seconds is the default), the child process in which the JS action is executed will be killed. --- lib/definitions/cleanup-service.d.ts | 16 +++++ .../cleanup-js-subprocess.ts | 58 +++++++++++++++ .../cleanup-process-definitions.d.ts | 34 ++++++--- lib/detached-processes/cleanup-process.ts | 70 +++++++++++++++++-- .../detached-process-enums.d.ts | 11 ++- lib/services/cleanup-service.ts | 14 +++- 6 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 lib/detached-processes/cleanup-js-subprocess.ts diff --git a/lib/definitions/cleanup-service.d.ts b/lib/definitions/cleanup-service.d.ts index 59e56a350f..abec414790 100644 --- a/lib/definitions/cleanup-service.d.ts +++ b/lib/definitions/cleanup-service.d.ts @@ -40,4 +40,20 @@ interface ICleanupService extends IShouldDispose, IDisposable { * @returns {Promise} */ removeCleanupDeleteAction(filePath: string): Promise; + + /** + * Adds JS file to be required and executed during cleanup. + * NOTE: The JS file will be required in a new child process, so you can pass timeout for the execution. + * In the child process you can use all injected dependencies of CLI. + * @param {IJSCommand} jsCommand Information about the JS file to be required and the data that should be passed to it. + * @returns {Promise} + */ + addCleanupJS(jsCommand: IJSCommand): Promise; + + /** + * Removes JS file to be required and executed during cleanup. + * @param {IJSCommand} filePath jsCommand Information about the JS file to be required and the data that should not be passed to it. + * @returns {Promise} + */ + removeCleanupJS(jsCommand: IJSCommand): Promise; } diff --git a/lib/detached-processes/cleanup-js-subprocess.ts b/lib/detached-processes/cleanup-js-subprocess.ts new file mode 100644 index 0000000000..c1c5adbd54 --- /dev/null +++ b/lib/detached-processes/cleanup-js-subprocess.ts @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +// NOTE: This file is used to call JS functions when cleaning resources used by CLI, after the CLI is killed. +// The instances here are not shared with the ones in main CLI process. +import * as fs from "fs"; +import * as uuid from "uuid"; +import { FileLogService } from "./file-log-service"; + +const pathToBootstrap = process.argv[2]; +if (!pathToBootstrap || !fs.existsSync(pathToBootstrap)) { + throw new Error("Invalid path to bootstrap."); +} + +// After requiring the bootstrap we can use $injector +require(pathToBootstrap); + +const logFile = process.argv[3]; +const jsFilePath = process.argv[4]; + +const fileLogService = $injector.resolve(FileLogService, { logFile }); +const uniqueId = uuid.v4(); +fileLogService.logData({ message: `Initializing Cleanup process for path: ${jsFilePath} Unique id: ${uniqueId}` }); + +if (!fs.existsSync(jsFilePath)) { + throw new Error(`Unable to find file ${jsFilePath}. Ensure it exists.`); +} + +let data: any; +try { + data = process.argv[5] && JSON.parse(process.argv[5]); +} catch (err) { + throw new Error(`Unable to parse data from argv ${process.argv[5]}.`); +} + +const logMessage = (msg: string, type?: FileLogMessageType): void => { + fileLogService.logData({ message: `[${uniqueId}] ${msg}`, type }); +}; + +/* tslint:disable:no-floating-promises */ +(async () => { + try { + logMessage(`Requiring file ${jsFilePath}`); + + const func = require(jsFilePath); + if (func && typeof func === "function") { + try { + logMessage(`Passing data: ${JSON.stringify(data)} to the default function exported by currently required file ${jsFilePath}`); + await func(data); + logMessage(`Finished execution with data: ${JSON.stringify(data)} to the default function exported by currently required file ${jsFilePath}`); + } catch (err) { + logMessage(`Unable to execute action of file ${jsFilePath} when passed data is ${JSON.stringify(data)}. Error is: ${err}.`, FileLogMessageType.Error); + } + } + } catch (err) { + logMessage(`Unable to require file: ${jsFilePath}. Error is: ${err}.`, FileLogMessageType.Error); + } +})(); +/* tslint:enable:no-floating-promises */ diff --git a/lib/detached-processes/cleanup-process-definitions.d.ts b/lib/detached-processes/cleanup-process-definitions.d.ts index 72ed39579a..24eab593a4 100644 --- a/lib/detached-processes/cleanup-process-definitions.d.ts +++ b/lib/detached-processes/cleanup-process-definitions.d.ts @@ -1,4 +1,18 @@ -interface ISpawnCommandInfo { +interface ITimeout { + /** + * Timeout to execute the action. + */ + timeout?: number; +} + +interface IFilePath { + /** + * Path to file/directory to be deleted or required + */ + filePath: string; +} + +interface ISpawnCommandInfo extends ITimeout { /** * Executable to be started. */ @@ -8,11 +22,6 @@ interface ISpawnCommandInfo { * Arguments that will be passed to the child process */ args: string[]; - - /** - * Timeout to execute the action. - */ - timeout?: number; } interface ICleanupMessageBase { @@ -29,9 +38,12 @@ interface ISpawnCommandCleanupMessage extends ICleanupMessageBase { commandInfo: ISpawnCommandInfo; } -interface IDeleteFileCleanupMessage extends ICleanupMessageBase { - /** - * Path to file/directory to be deleted. - */ - filePath: string; +interface IFileCleanupMessage extends ICleanupMessageBase, IFilePath { } + +interface IJSCommand extends ITimeout, IFilePath { + data: IDictionary; } + +interface IJSCleanupMessage extends ICleanupMessageBase { + jsCommand: IJSCommand; + } diff --git a/lib/detached-processes/cleanup-process.ts b/lib/detached-processes/cleanup-process.ts index 705c266c6f..825b48d613 100644 --- a/lib/detached-processes/cleanup-process.ts +++ b/lib/detached-processes/cleanup-process.ts @@ -19,9 +19,29 @@ fileLogService.logData({ message: "Initializing Cleanup process." }); const commandsInfos: ISpawnCommandInfo[] = []; const filesToDelete: string[] = []; +const jsCommands: IJSCommand[] = []; + +const executeJSCleanup = async (jsCommand: IJSCommand) => { + const $childProcess = $injector.resolve("childProcess"); + + try { + fileLogService.logData({ message: `Start executing action for file: ${jsCommand.filePath} and data ${JSON.stringify(jsCommand.data)}` }); + + await $childProcess.trySpawnFromCloseEvent(process.execPath, [path.join(__dirname, "cleanup-js-subprocess.js"), pathToBootstrap, logFile, jsCommand.filePath, JSON.stringify(jsCommand.data)], {}, { throwError: true, timeout: jsCommand.timeout || 3000 }); + fileLogService.logData({ message: `Finished xecuting action for file: ${jsCommand.filePath} and data ${JSON.stringify(jsCommand.data)}` }); + + } catch (err) { + fileLogService.logData({ message: `Unable to execute action for file ${jsCommand.filePath} with data ${JSON.stringify(jsCommand.data)}. Error is: ${err}.`, type: FileLogMessageType.Error }); + } +}; const executeCleanup = async () => { const $childProcess = $injector.resolve("childProcess"); + + for (const jsCommand of jsCommands) { + await executeJSCleanup(jsCommand); + } + for (const commandInfo of commandsInfos) { try { fileLogService.logData({ message: `Start executing command: ${JSON.stringify(commandInfo)}` }); @@ -29,13 +49,17 @@ const executeCleanup = async () => { await $childProcess.trySpawnFromCloseEvent(commandInfo.command, commandInfo.args, {}, { throwError: true, timeout: commandInfo.timeout || 3000 }); fileLogService.logData({ message: `Successfully executed command: ${JSON.stringify(commandInfo)}` }); } catch (err) { - fileLogService.logData({ message: `Unable to execute command: ${JSON.stringify(commandInfo)}`, type: FileLogMessageType.Error }); + fileLogService.logData({ message: `Unable to execute command: ${JSON.stringify(commandInfo)}. Error is: ${err}.`, type: FileLogMessageType.Error }); } } if (filesToDelete.length) { - fileLogService.logData({ message: `Deleting files ${filesToDelete.join(" ")}` }); - shelljs.rm("-Rf", filesToDelete); + try { + fileLogService.logData({ message: `Deleting files ${filesToDelete.join(" ")}` }); + shelljs.rm("-Rf", filesToDelete); + } catch (err) { + fileLogService.logData({ message: `Unable to delete files: ${JSON.stringify(filesToDelete)}. Error is: ${err}.`, type: FileLogMessageType.Error }); + } } fileLogService.logData({ message: `cleanup-process finished` }); @@ -56,7 +80,7 @@ const removeCleanupAction = (commandInfo: ISpawnCommandInfo): void => { _.remove(commandsInfos, currentCommandInfo => _.isEqual(currentCommandInfo, commandInfo)); fileLogService.logData({ message: `cleanup-process removed command for execution: ${JSON.stringify(commandInfo)}` }); } else { - fileLogService.logData({ message: `cleanup-process cannot remove command for execution as it has note been added before: ${JSON.stringify(commandInfo)}` }); + fileLogService.logData({ message: `cleanup-process cannot remove command for execution as it has not been added before: ${JSON.stringify(commandInfo)}` }); } }; @@ -82,6 +106,32 @@ const removeDeleteAction = (filePath: string): void => { } }; +const addJSFile = (jsCommand: IJSCommand): void => { + const fullPath = path.resolve(jsCommand.filePath); + + jsCommand.filePath = fullPath; + + if (_.some(jsCommands, currentJSCommand => _.isEqual(currentJSCommand, jsCommand))) { + fileLogService.logData({ message: `cleanup-process will not add JS file for execution as it has been added already: ${JSON.stringify(jsCommand)}` }); + } else { + fileLogService.logData({ message: `cleanup-process added JS file for execution: ${JSON.stringify(jsCommand)}` }); + jsCommands.push(jsCommand); + } +}; + +const removeJSFile = (jsCommand: IJSCommand): void => { + const fullPath = path.resolve(jsCommand.filePath); + + jsCommand.filePath = fullPath; + + if (_.some(jsCommands, currentJSCommand => _.isEqual(currentJSCommand, jsCommand))) { + _.remove(jsCommands, currentJSCommand => _.isEqual(currentJSCommand, jsCommand)); + fileLogService.logData({ message: `cleanup-process removed JS action for execution: ${JSON.stringify(jsCommand)}` }); + } else { + fileLogService.logData({ message: `cleanup-process cannot remove JS action for execution as it has not been added before: ${JSON.stringify(jsCommand)}` }); + } +}; + process.on("message", async (cleanupProcessMessage: ICleanupMessageBase) => { fileLogService.logData({ message: `cleanup-process received message of type: ${JSON.stringify(cleanupProcessMessage)}` }); @@ -93,10 +143,18 @@ process.on("message", async (cleanupProcessMessage: ICleanupMessageBase) => { removeCleanupAction((cleanupProcessMessage).commandInfo); break; case CleanupProcessMessage.AddDeleteFileAction: - addDeleteAction((cleanupProcessMessage).filePath); + addDeleteAction((cleanupProcessMessage).filePath); break; case CleanupProcessMessage.RemoveDeleteFileAction: - removeDeleteAction((cleanupProcessMessage).filePath); + removeDeleteAction((cleanupProcessMessage).filePath); + break; + case CleanupProcessMessage.AddJSFileToRequire: + const jsCleanupMessage = cleanupProcessMessage; + addJSFile(jsCleanupMessage.jsCommand); + break; + case CleanupProcessMessage.RemoveJSFileToRequire: + const msgToRemove = cleanupProcessMessage; + removeJSFile(msgToRemove.jsCommand); break; default: fileLogService.logData({ message: `Unable to handle message of type ${cleanupProcessMessage.messageType}. Full message is ${JSON.stringify(cleanupProcessMessage)}`, type: FileLogMessageType.Error }); diff --git a/lib/detached-processes/detached-process-enums.d.ts b/lib/detached-processes/detached-process-enums.d.ts index 1e3a16f5d6..c8e27edede 100644 --- a/lib/detached-processes/detached-process-enums.d.ts +++ b/lib/detached-processes/detached-process-enums.d.ts @@ -40,8 +40,17 @@ declare const enum CleanupProcessMessage { AddDeleteFileAction = "AddDeleteFileAction", /** - * This type of message defines the cleanup procedure should not delete previously specified file. + * This type of message defines that the cleanup procedure should not delete previously specified file. */ RemoveDeleteFileAction = "RemoveDeleteFileAction", + /** + * This type of message defines that the cleanup procedure will require the specified JS file, which should execute some action. + */ + AddJSFileToRequire = "AddJSFileToRequire", + + /** + * This type of message defines that the cleanup procedure will not require the previously specified JS file. + */ + RemoveJSFileToRequire = "RemoveJSFileToRequire", } diff --git a/lib/services/cleanup-service.ts b/lib/services/cleanup-service.ts index 671f6afed6..953f330e25 100644 --- a/lib/services/cleanup-service.ts +++ b/lib/services/cleanup-service.ts @@ -27,12 +27,22 @@ export class CleanupService implements ICleanupService { public async addCleanupDeleteAction(filePath: string): Promise { const cleanupProcess = await this.getCleanupProcess(); - cleanupProcess.send({ messageType: CleanupProcessMessage.AddDeleteFileAction, filePath }); + cleanupProcess.send({ messageType: CleanupProcessMessage.AddDeleteFileAction, filePath }); } public async removeCleanupDeleteAction(filePath: string): Promise { const cleanupProcess = await this.getCleanupProcess(); - cleanupProcess.send({ messageType: CleanupProcessMessage.RemoveDeleteFileAction, filePath }); + cleanupProcess.send({ messageType: CleanupProcessMessage.RemoveDeleteFileAction, filePath }); + } + + public async addCleanupJS(jsCommand: IJSCommand): Promise { + const cleanupProcess = await this.getCleanupProcess(); + cleanupProcess.send({ messageType: CleanupProcessMessage.AddJSFileToRequire, jsCommand }); + } + + public async removeCleanupJS(jsCommand: IJSCommand): Promise { + const cleanupProcess = await this.getCleanupProcess(); + cleanupProcess.send({ messageType: CleanupProcessMessage.RemoveJSFileToRequire, jsCommand}); } @exported("cleanupService") From 395e476a791ee3546ec4e81d766d63f54faa8adf Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 30 Apr 2019 11:37:49 +0300 Subject: [PATCH 30/62] feat: show deprecated messages for things that will be dropped as of NativeScript v.6.0.0 https://github.com/NativeScript/nativescript-cli/issues/4518 --- lib/commands/clean-app.ts | 15 ++++++++++----- lib/common/services/commands-service.ts | 1 + lib/declarations.d.ts | 3 ++- lib/options.ts | 14 ++++++++++++++ lib/services/platform-service.ts | 5 +++++ test/platform-service.ts | 2 ++ 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index e22c1a4c64..5f97252d0a 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -10,12 +10,15 @@ export class CleanAppCommandBase extends ValidatePlatformCommandBase implements $platformService: IPlatformService, protected $errors: IErrors, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformsData: IPlatformsData) { + $platformsData: IPlatformsData, + private $logger: ILogger) { super($options, $platformsData, $platformService, $projectData); this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { + this.$logger.warn(`"tns clean-app ${this.platform.toLowerCase()}" command has been deprecated and will be removed in the upcoming NativeScript CLI v.6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: !!this.$options.bundle, release: this.$options.release, @@ -49,8 +52,9 @@ export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand protected $platformsData: IPlatformsData, protected $errors: IErrors, $platformService: IPlatformService, - $projectData: IProjectData) { - super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData); + $projectData: IProjectData, + $logger: ILogger) { + super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData, $logger); } protected get platform(): string { @@ -66,8 +70,9 @@ export class CleanAppAndroidCommand extends CleanAppCommandBase implements IComm protected $platformsData: IPlatformsData, protected $errors: IErrors, $platformService: IPlatformService, - $projectData: IProjectData) { - super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData); + $projectData: IProjectData, + $logger: ILogger) { + super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData, $logger); } protected get platform(): string { diff --git a/lib/common/services/commands-service.ts b/lib/common/services/commands-service.ts index d441a2959a..315bbfa6e1 100644 --- a/lib/common/services/commands-service.ts +++ b/lib/common/services/commands-service.ts @@ -37,6 +37,7 @@ export class CommandsService implements ICommandsService { } this.$options.setupOptions(projectData); + this.$options.printMessagesForDeprecatedOptions(this.$logger); } public allCommands(opts: { includeDevCommands: boolean }): string[] { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index dfaf0d258f..e1c56e57be 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -569,9 +569,10 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai link: boolean; analyticsLogFile: string; performance: Object; - setupOptions(projectData: IProjectData): void; cleanupLogFile: string; workflow: boolean; + setupOptions(projectData: IProjectData): void; + printMessagesForDeprecatedOptions(logger: ILogger): void; } interface IEnvOptions { diff --git a/lib/options.ts b/lib/options.ts index 44793f0fcb..eeee8c0eed 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -204,6 +204,20 @@ export class Options { }); } + public printMessagesForDeprecatedOptions($logger: ILogger) { + if (this.argv.platformTemplate) { + $logger.warn(`"--platformTemplate" option has been deprecated and will be removed in the upcoming NativeScript CLI v.6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + } + + if (this.argv.syncAllFiles) { + $logger.warn(`"--syncAllFiles" option has been deprecated and will be removed in the upcoming NativeScript CLI v.6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + } + + if (this.argv.bundle) { + $logger.warn(`"--bundle" option has been deprecated and as of NativeScript CLI v.6.0.0 Webpack workflow will become the only way of building apps.`); + } + } + private getCorrectOptionName(optionName: string): string { const secondaryOptionName = this.getNonDashedOptionName(optionName); return _.includes(this.optionNames, secondaryOptionName) ? secondaryOptionName : optionName; diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index ed8a29c189..2c6fe89d66 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -319,6 +319,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Preparing project..."); const platformData = this.$platformsData.getPlatformData(platform, projectData); + const frameworkVersion = this.getCurrentPlatformVersion(platform, projectData); + if (semver.lt(semver.coerce(frameworkVersion), semver.coerce('5.1.0'))) { + this.$logger.warn(`Runtime versions lower than 5.1.0 have been deprecated and will not be supported as of v.6.0.0 of NativeScript CLI. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + } + const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: appFilesUpdaterOptions.release }); await this.$preparePlatformJSService.preparePlatform({ platform, diff --git a/test/platform-service.ts b/test/platform-service.ts index e7039b33f1..5ae8e12f63 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -516,6 +516,7 @@ describe('Platform Service Tests', () => { platformService = testInjector.resolve("platformService"); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: release, useHotModuleReload: false }; + platformService.getCurrentPlatformVersion = () => "5.1.1"; await platformService.preparePlatform({ platform: platformToTest, appFilesUpdaterOptions, @@ -950,6 +951,7 @@ describe('Platform Service Tests', () => { projectData.appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); platformService = testInjector.resolve("platformService"); + platformService.getCurrentPlatformVersion = () => "5.1.1"; const oldLoggerWarner = testInjector.resolve("$logger").warn; let warnings: string = ""; try { From d5725e0efcb84a3f831901a4672b02419acf89b1 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 7 May 2019 09:21:27 +0300 Subject: [PATCH 31/62] fix PR comments --- lib/commands/clean-app.ts | 2 +- lib/options.ts | 7 ++++--- lib/services/platform-service.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index 5f97252d0a..e95daf8a9e 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -17,7 +17,7 @@ export class CleanAppCommandBase extends ValidatePlatformCommandBase implements } public async execute(args: string[]): Promise { - this.$logger.warn(`"tns clean-app ${this.platform.toLowerCase()}" command has been deprecated and will be removed in the upcoming NativeScript CLI v.6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + this.$logger.warn(`"tns clean-app ${this.platform.toLowerCase()}" command has been deprecated and will be removed in the upcoming NativeScript CLI v6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: !!this.$options.bundle, diff --git a/lib/options.ts b/lib/options.ts index eeee8c0eed..0e5f1045bf 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -206,15 +206,16 @@ export class Options { public printMessagesForDeprecatedOptions($logger: ILogger) { if (this.argv.platformTemplate) { - $logger.warn(`"--platformTemplate" option has been deprecated and will be removed in the upcoming NativeScript CLI v.6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + $logger.warn(`"--platformTemplate" option has been deprecated and will be removed in the upcoming NativeScript CLI v6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); } if (this.argv.syncAllFiles) { - $logger.warn(`"--syncAllFiles" option has been deprecated and will be removed in the upcoming NativeScript CLI v.6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + $logger.warn(`"--syncAllFiles" option has been deprecated and will be removed in the upcoming NativeScript CLI v6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); } if (this.argv.bundle) { - $logger.warn(`"--bundle" option has been deprecated and as of NativeScript CLI v.6.0.0 Webpack workflow will become the only way of building apps.`); + $logger.warn(`"--bundle" option has been deprecated and as of NativeScript CLI v6.0.0 Webpack workflow will become the only way of building apps. + More info about the reasons for this change and how to migrate your project can be found in the link below: `); } } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 2c6fe89d66..4589674cbd 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -321,7 +321,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const platformData = this.$platformsData.getPlatformData(platform, projectData); const frameworkVersion = this.getCurrentPlatformVersion(platform, projectData); if (semver.lt(semver.coerce(frameworkVersion), semver.coerce('5.1.0'))) { - this.$logger.warn(`Runtime versions lower than 5.1.0 have been deprecated and will not be supported as of v.6.0.0 of NativeScript CLI. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); + this.$logger.warn(`Runtime versions lower than 5.1.0 have been deprecated and will not be supported as of v6.0.0 of NativeScript CLI. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); } const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: appFilesUpdaterOptions.release }); From 9ad0ac8dd19967f4fffbf884ddf3d4f5150eb053 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 7 May 2019 12:58:46 +0300 Subject: [PATCH 32/62] chore: remove the deprecation message for `--bundle` option --- lib/options.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/options.ts b/lib/options.ts index 0e5f1045bf..63c0f9bb74 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -212,11 +212,6 @@ export class Options { if (this.argv.syncAllFiles) { $logger.warn(`"--syncAllFiles" option has been deprecated and will be removed in the upcoming NativeScript CLI v6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); } - - if (this.argv.bundle) { - $logger.warn(`"--bundle" option has been deprecated and as of NativeScript CLI v6.0.0 Webpack workflow will become the only way of building apps. - More info about the reasons for this change and how to migrate your project can be found in the link below: `); - } } private getCorrectOptionName(optionName: string): string { From b606213b242557f7135d27e93c2f12704be8894d Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Tue, 7 May 2019 15:08:59 +0300 Subject: [PATCH 33/62] chore: update watchapp beta warning --- lib/services/ios-project-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index fc6b6017e2..925b953315 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -825,7 +825,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f const addedWatchApp = await this.$iOSWatchAppService.addWatchAppFromPath({ watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); if (addedWatchApp) { - this.$logger.warn("The support for Apple Watch App is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: ISSUE LINK"); + this.$logger.warn("The support for Apple Watch App is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/nativescript-cli/issues/4589"); } } From bd7365723a6c608e64bb9232e3ea359d21b4e928 Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Wed, 8 May 2019 11:43:56 +0300 Subject: [PATCH 34/62] fix: target argument passed to xcodebuild when using workspace --- lib/services/ios-project-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 925b953315..7aca88f31e 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -599,7 +599,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private async buildForSimulator(projectRoot: string, args: string[], projectData: IProjectData, buildConfig?: IBuildConfig): Promise { const architectures = this.getBuildArchitectures(projectData, buildConfig, ["i386", "x86_64"]); - let product = ProductArgs.target; + let product; args = args .concat(architectures) From 82078341e1ac944990cbb08c3154fdcdd1eed16e Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 8 May 2019 16:58:54 +0300 Subject: [PATCH 35/62] fix (test command): generate source maps when debug-brk is set --- lib/services/test-execution-service.ts | 3 ++- resources/test/karma.conf.js | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 2a7a9d7689..b8fc54f05b 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -125,7 +125,7 @@ export class TestExecutionService implements ITestExecutionService { await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); }; - karmaRunner.on("message", (karmaData: any) => { + karmaRunner.on("message", (karmaData: any) => { this.$logger.trace(`The received message from karma is: `, karmaData); if (!karmaData.launcherConfig && !karmaData.url) { return; @@ -219,6 +219,7 @@ export class TestExecutionService implements ITestExecutionService { karmaConfig.projectDir = projectData.projectDir; karmaConfig.bundle = this.$options.bundle; + karmaConfig.debugBrk = this.$options.debugBrk; karmaConfig.platform = platform.toLowerCase(); this.$logger.debug(JSON.stringify(karmaConfig, null, 4)); diff --git a/resources/test/karma.conf.js b/resources/test/karma.conf.js index 918a6cee93..705b5a713e 100644 --- a/resources/test/karma.conf.js +++ b/resources/test/karma.conf.js @@ -1,4 +1,4 @@ -module.exports = function(config) { +module.exports = function (config) { const options = { // base path that will be used to resolve all patterns (eg. files, exclude) @@ -11,7 +11,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser - files: [ ${ testFiles } ], + files: [${ testFiles }], // list of files to exclude @@ -75,7 +75,7 @@ module.exports = function(config) { setWebpackPreprocessor(config, options); setWebpack(config, options); - + config.set(options); } @@ -98,6 +98,7 @@ function setWebpack(config, options) { if (config && config.bundle) { const env = {}; env[config.platform] = true; + env.sourceMap = config.debugBrk; options.webpack = require('./webpack.config')(env); delete options.webpack.entry; delete options.webpack.output.libraryTarget; From f8443f8960d706a2afd8b34714a8a0d4c8b474b5 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 8 May 2019 17:42:40 +0300 Subject: [PATCH 36/62] fix: ensure analytics and cleanup processes are not leaking When CLI dies, the `analyticsBroker` and `cleanupProcess` processes receive disconnect event. At this point they have to finish their work and die silently after that. However, this does not happen for `analyticsBroker` process as in the `disconnect` handler we call `process.disconnect` method. It fails, as the process is already disconnected. So the process hangs in undefined state and it leaks. The stareted cleanup process from it also leaks. To fix this remove the disconnect call from the handler. Also ensure injector is disposed before calling process.exit in the detached process's handler. --- lib/detached-processes/cleanup-process.ts | 2 ++ lib/services/analytics/analytics-broker-process.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/detached-processes/cleanup-process.ts b/lib/detached-processes/cleanup-process.ts index 825b48d613..a6bd0c7975 100644 --- a/lib/detached-processes/cleanup-process.ts +++ b/lib/detached-processes/cleanup-process.ts @@ -166,6 +166,8 @@ process.on("message", async (cleanupProcessMessage: ICleanupMessageBase) => { process.on("disconnect", async () => { fileLogService.logData({ message: "cleanup-process received process.disconnect event" }); await executeCleanup(); + $injector.dispose(); + process.exit(); }); fileLogService.logData({ message: `cleanup-process will send ${DetachedProcessMessages.ProcessReadyToReceive} message` }); diff --git a/lib/services/analytics/analytics-broker-process.ts b/lib/services/analytics/analytics-broker-process.ts index 9fee55b3a5..5e7439af26 100644 --- a/lib/services/analytics/analytics-broker-process.ts +++ b/lib/services/analytics/analytics-broker-process.ts @@ -29,7 +29,7 @@ const finishTracking = async (data?: ITrackingInformation) => { analyticsLoggingService.logData({ message: `analytics-broker-process finish tracking started` }); await trackingQueue; analyticsLoggingService.logData({ message: `analytics-broker-process tracking finished` }); - process.disconnect(); + $injector.dispose(); process.exit(); }; From 8e108ead35a0e43790ada4bf064833c4c3b6ab24 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 8 May 2019 18:04:19 +0300 Subject: [PATCH 37/62] fix: do not send hot updates when debugBrk is set as we will restart the app on each LiveSync anyway --- lib/options.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/options.ts b/lib/options.ts index 63c0f9bb74..0094c9e31a 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -48,6 +48,12 @@ export class Options { this.argv.bundle = undefined; this.argv.hmr = false; } + + if (this.argv.debugBrk) { + // we cannot use HMR along with debug-brk because we have to restart the app + // on each livesync in order to stop and allow debugging on app start + this.argv.hmr = false; + } } constructor(private $errors: IErrors, From a3850dc106ec6053b1202e97bbbb6f4daebd9b3f Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 1 May 2019 14:10:54 +0300 Subject: [PATCH 38/62] chore: delete unused progress-indicator --- lib/common/bootstrap.ts | 1 - lib/common/declarations.d.ts | 14 ------------- lib/common/progress-indicator.ts | 36 -------------------------------- test/plugins-service.ts | 7 ------- 4 files changed, 58 deletions(-) delete mode 100644 lib/common/progress-indicator.ts 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/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/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/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); From 02d6dfbfa385e49b261b0e5858c65f24a6a71ff2 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 1 May 2019 14:12:18 +0300 Subject: [PATCH 39/62] chore: remove `printMsgWithTimeout` from logger --- lib/common/definitions/logger.d.ts | 1 - lib/common/logger.ts | 10 ---------- lib/common/test/unit-tests/stubs.ts | 3 --- test/stubs.ts | 3 --- 4 files changed, 17 deletions(-) diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index a719108d6c..3a3a1e44b6 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -14,6 +14,5 @@ interface ILogger { prepare(item: any): string; printInfoMessageOnSameLine(message: string): void; - printMsgWithTimeout(message: string, timeout: number): Promise; printOnStderr(formatStr?: any, ...args: any[]): void; } diff --git a/lib/common/logger.ts b/lib/common/logger.ts index aebd47b2a0..d3228d34f3 100644 --- a/lib/common/logger.ts +++ b/lib/common/logger.ts @@ -113,16 +113,6 @@ export class Logger implements ILogger { } } - 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, diff --git a/lib/common/test/unit-tests/stubs.ts b/lib/common/test/unit-tests/stubs.ts index ba0609a2b8..3e97787eb8 100644 --- a/lib/common/test/unit-tests/stubs.ts +++ b/lib/common/test/unit-tests/stubs.ts @@ -47,9 +47,6 @@ export class CommonLoggerStub implements ILogger { } printInfoMessageOnSameLine(message: string): void { } - async printMsgWithTimeout(message: string, timeout: number): Promise { - return null; - } printMarkdown(message: string): void { this.output += message; diff --git a/test/stubs.ts b/test/stubs.ts index 7556081a47..eecca957b6 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -36,9 +36,6 @@ export class LoggerStub implements ILogger { } printInfoMessageOnSameLine(message: string): void { } - async printMsgWithTimeout(message: string, timeout: number): Promise { - return null; - } printMarkdown(message: string): void { } From 822173a5bf0ded08b947905b463012254dd18d74 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 3 May 2019 10:32:59 +0300 Subject: [PATCH 40/62] chore: rename initService to projectInitService Rename `initService` to `projectInitService` as the new name has more sense. --- lib/bootstrap.ts | 2 +- lib/commands/init.ts | 8 ++++---- lib/declarations.d.ts | 2 +- lib/services/{init-service.ts => project-init-service.ts} | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) rename lib/services/{init-service.ts => project-init-service.ts} (95%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index d99ca32d44..76de985005 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -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"); 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/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/services/init-service.ts b/lib/services/project-init-service.ts similarity index 95% rename from lib/services/init-service.ts rename to lib/services/project-init-service.ts index 8a35b0af0f..edb8bea6f9 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", @@ -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); From 516982913d460a2fc366eb5eb44d0a7716b1bb2b Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 3 May 2019 14:37:54 +0300 Subject: [PATCH 41/62] feat: expose logger and enable configuring it Expose `$logger` for using when CLI is required as a library. Also introduce `initialize` method in it, that allows full configuration of the log4js instance. Implement a new `emit-appender` logger, that emits `logData` event with information for the logging instead of printing it to the stdout. This emitter can be used with the following code: ```JavaScript const tns = require("nativescript"); const { LoggerAppenders } = tns.constants; const { EventEmitter } = require("events"); const emitter = new EventEmitter(); // IMPORTANT: Due to current log4js behavior, you must set the event handler BEFORE calling initialize of the logger. emitter.on("logData", logData => { // logData contains two properties: loggingEvent and formattedMessage // loggingEvent contains information about the level of the message and the raw message itself // formattedMessage is the message with specified layout (i.e. it is string). Default layout is `messagePassThrough`. }); tns.logger.initialize({ appenderOptions: { type: LoggerAppenders.emitAppender, emitter }); ``` This is the easiest way to use the new appender. You can also use LoggerLevel from constants and specify different logging levels. You can pass whatever layout you need for the message. --- lib/bootstrap.ts | 2 +- lib/common/definitions/logger.d.ts | 52 +++++++++++----- lib/common/logger-appenders/emit-appender.ts | 38 ++++++++++++ lib/common/logger.ts | 52 ++++++++++++---- lib/common/test/unit-tests/logger.ts | 2 + lib/common/test/unit-tests/stubs.ts | 1 + lib/constants.ts | 63 ++++++++++++++++++++ test/nativescript-cli-lib.ts | 7 ++- test/stubs.ts | 1 + 9 files changed, 188 insertions(+), 30 deletions(-) create mode 100644 lib/common/logger-appenders/emit-appender.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 76de985005..cafc649a96 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"); $injector.require("config", "./config"); $injector.require("options", "./options"); // note: order above is important! diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index 3a3a1e44b6..6d203e3a0e 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -1,18 +1,40 @@ -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; + } - prepare(item: any): string; - printInfoMessageOnSameLine(message: string): void; - printOnStderr(formatStr?: any, ...args: any[]): void; + interface ILoggerOptions { + level?: LoggerLevel; + appenderOptions?: IAppenderOptions; + } + + interface ILogger { + initialize(opts?: ILoggerOptions): void; + 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; + + out(formatStr?: any, ...args: any[]): void; + write(...args: any[]): void; + + prepare(item: any): string; + printInfoMessageOnSameLine(message: string): void; + printOnStderr(formatStr?: any, ...args: any[]): void; + } + + + interface Log4JSEmitAppenderConfiguration extends Configuration { + layout: Layout; + emitter: EventEmitter; + } } diff --git a/lib/common/logger-appenders/emit-appender.ts b/lib/common/logger-appenders/emit-appender.ts new file mode 100644 index 0000000000..23939ceae2 --- /dev/null +++ b/lib/common/logger-appenders/emit-appender.ts @@ -0,0 +1,38 @@ +import { LoggingEvent } from "log4js"; +import { EventEmitter } from "events"; + +const logDataEventName = "logData"; +function emitAppender(layout: Function, emitter: EventEmitter) { + const appender = (loggingEvent: LoggingEvent) => { + emitter.emit(logDataEventName, { loggingEvent, formattedMessage: layout(loggingEvent) }); + }; + + appender.shutdown = () => { + emitter.removeAllListeners(logDataEventName); + }; + + return appender; +} + +function configure(config: Log4JSEmitAppenderConfiguration, 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); + } + + 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"); + } + + // create a new appender instance + return emitAppender(layout, config.emitter); +} + +exports.configure = configure; diff --git a/lib/common/logger.ts b/lib/common/logger.ts index d3228d34f3..17cb539a9c 100644 --- a/lib/common/logger.ts +++ b/lib/common/logger.ts @@ -2,6 +2,7 @@ import * as log4js from "log4js"; import * as util from "util"; import * as stream from "stream"; import * as marked from "marked"; +import { cache } from "./decorators"; const TerminalRenderer = require("marked-terminal"); const chalk = require("chalk"); @@ -11,26 +12,35 @@ export class Logger implements ILogger { private passwordReplacement = "$1$3*******$2$4"; private static LABEL = "[WARNING]:"; - constructor($config: Config.IConfig, + constructor(private $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"; - } + } + + @cache() + public initialize(opts?: ILoggerOptions): void { + opts = opts || {}; + const { appenderOptions: appenderOpts, level } = opts; - appenders["out"] = { + const appender: any = { type: "console", layout: { type: "messagePassThrough" } }; - categories["default"] = { - appenders: ['out'], - level + + 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 }); @@ -39,14 +49,20 @@ export class Logger implements ILogger { } getLevel(): string { + this.initialize(); + return this.log4jsLogger.level.toString(); } fatal(...args: string[]): void { + this.initialize(); + this.log4jsLogger.fatal.apply(this.log4jsLogger, args); } error(...args: string[]): void { + this.initialize(); + const message = util.format.apply(null, args); const colorizedMessage = message.red; @@ -54,6 +70,8 @@ export class Logger implements ILogger { } warn(...args: string[]): void { + this.initialize(); + const message = util.format.apply(null, args); const colorizedMessage = message.yellow; @@ -61,20 +79,28 @@ export class Logger implements ILogger { } warnWithLabel(...args: string[]): void { + this.initialize(); + const message = util.format.apply(null, args); this.warn(`${Logger.LABEL} ${message}`); } info(...args: string[]): void { + this.initialize(); + this.log4jsLogger.info.apply(this.log4jsLogger, args); } debug(...args: string[]): void { + this.initialize(); + const encodedArgs: string[] = this.getPasswordEncodedArguments(args); this.log4jsLogger.debug.apply(this.log4jsLogger, encodedArgs); } trace(...args: string[]): void { + this.initialize(); + const encodedArgs: string[] = this.getPasswordEncodedArguments(args); this.log4jsLogger.trace.apply(this.log4jsLogger, encodedArgs); } diff --git a/lib/common/test/unit-tests/logger.ts b/lib/common/test/unit-tests/logger.ts index a9b8c0f7fa..6ddbef0685 100644 --- a/lib/common/test/unit-tests/logger.ts +++ b/lib/common/test/unit-tests/logger.ts @@ -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/stubs.ts b/lib/common/test/unit-tests/stubs.ts index 3e97787eb8..567d637684 100644 --- a/lib/common/test/unit-tests/stubs.ts +++ b/lib/common/test/unit-tests/stubs.ts @@ -18,6 +18,7 @@ export class LockServiceStub implements ILockService { } export class CommonLoggerStub implements ILogger { + initialize(opts?: ILoggerOptions): void { } getLevel(): string { return undefined; } fatal(...args: string[]): void { } error(...args: string[]): void { } diff --git a/lib/constants.ts b/lib/constants.ts index 7443a4632a..bcf374bdd7 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,65 @@ export enum IOSNativeTargetTypes { watchExtension = "watch_extension", appExtension = "app_extension" } + +export const LoggerAppenders = { + emitAppender: join(__dirname, "common", "logger-appenders", "emit-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" +} diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index f32f35f779..70f55bdc2a 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,11 @@ describe("nativescript-cli-lib", () => { ], cleanupService: [ "setCleanupLogFile" + ], + logger: [ + "initialize", + "getLevel", + "info" ] }; diff --git a/test/stubs.ts b/test/stubs.ts index eecca957b6..bfdd8efda5 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -11,6 +11,7 @@ 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 { } getLevel(): string { return undefined; } fatal(...args: string[]): void { } error(...args: string[]): void { } From 86dd22a8ad64d7284e05487a691a91423b43f859 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 3 May 2019 15:13:53 +0300 Subject: [PATCH 42/62] feat: introduce initializeService Introduce initializeService which can be used to setup some specifics of CLI (currently the logger) and to print all required warnings. At the moment, whenever we want to introduce new warnings, we need to introduce new methods that we call from CLI and when it is used as a library. With the new solution all such warnings can be placed in the initialize method and it will automatically show those warnings. --- lib/bootstrap.ts | 2 ++ lib/definitions/initialize-service.d.ts | 3 +++ lib/nativescript-cli.ts | 17 +++++--------- lib/services/initialize-service.ts | 30 +++++++++++++++++++++++++ test/nativescript-cli-lib.ts | 3 +++ 5 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 lib/definitions/initialize-service.d.ts create mode 100644 lib/services/initialize-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index cafc649a96..090c7c01ae 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -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/definitions/initialize-service.d.ts b/lib/definitions/initialize-service.d.ts new file mode 100644 index 0000000000..e4cab0db93 --- /dev/null +++ b/lib/definitions/initialize-service.d.ts @@ -0,0 +1,3 @@ +interface IInitializeService { + initialize(initOpts?: { loggerOptions?: ILoggerOptions }): Promise; +} diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts index 431413ca28..2620b33018 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,11 @@ process.on = (event: string, listener: any): any => { const err: IErrors = $injector.resolve("$errors"); err.printCallStack = config.DEBUG; + const $options = $injector.resolve("options"); + + const $initializeService = $injector.resolve("initializeService"); + await $initializeService.initialize({ loggerOptions: { level: $options.log } }); + const extensibilityService: IExtensibilityService = $injector.resolve("extensibilityService"); try { await settlePromises(extensibilityService.loadExtensions()); @@ -32,17 +36,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/services/initialize-service.ts b/lib/services/initialize-service.ts new file mode 100644 index 0000000000..55deb76228 --- /dev/null +++ b/lib/services/initialize-service.ts @@ -0,0 +1,30 @@ +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?: { loggerOptions?: ILoggerOptions }): Promise { + initOpts = initOpts || {}; + const $logger = this.$injector.resolve("logger"); + $logger.initialize(initOpts.loggerOptions); + + await this.showWarnings($logger); + } + + private async showWarnings($logger: ILogger): Promise { + 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); + } + } + } +} + +$injector.register("initializeService", InitializeService); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 70f55bdc2a..33b08b031c 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -67,6 +67,9 @@ describe("nativescript-cli-lib", () => { "initialize", "getLevel", "info" + ], + initializeService: [ + "initialize" ] }; From 449e8a7341a08484b048e5d0c9cb9ac67b4b4038 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 3 May 2019 15:39:02 +0300 Subject: [PATCH 43/62] fix: use correct method for showing all warnings on initialize --- lib/common/verify-node-version.ts | 57 ++++++++++++++++-------------- lib/services/initialize-service.ts | 10 +++--- 2 files changed, 36 insertions(+), 31 deletions(-) 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/services/initialize-service.ts b/lib/services/initialize-service.ts index 55deb76228..83fd10913a 100644 --- a/lib/services/initialize-service.ts +++ b/lib/services/initialize-service.ts @@ -15,15 +15,15 @@ export class InitializeService implements IInitializeService { private async showWarnings($logger: ILogger): Promise { const $sysInfo = $injector.resolve("sysInfo"); - const macOSWarning = await $sysInfo.getMacOSWarningMessage(); - if (macOSWarning) { - const message = `${EOL}${macOSWarning.message}${EOL}`; - if (macOSWarning.severity === SystemWarningsSeverity.high) { + const systemWarnings = await $sysInfo.getSystemWarnings(); + _.each(systemWarnings, systemWarning => { + const message = `${EOL}${systemWarning.message}${EOL}`; + if (systemWarning.severity === SystemWarningsSeverity.high) { $logger.printOnStderr(message.red.bold); } else { $logger.warn(message); } - } + }); } } From b28f5be0ac332a43f2e019badb8a255c01ce8c4c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 7 May 2019 21:15:57 +0300 Subject: [PATCH 44/62] feat: use only log4js methods in CLI logger Refactor the logic in CLI's logger to use log4js methods only - currently we have some methods that use `console.log`, others use `process.stdout`, `process.stderr`, etc. Also, there are some methods that apply console colorization for the message, but in fact CLI may be used as a library or in CI, where these colorization does not make sense. To resolve this, introduce a new appender and new layout that CLI will pass to `log4js`. The appender (called `cli-appender`) will control if the message will be shown on `stdout` or `stderr`. The layout (called `cli-layout`) will control the presentation of the message on the terminal, i.e. if we should add new line at the end of the message, if we should wrap it in borders (with `*` symbols, etc.) and the colorization of the message. The layout will be applied on all methods, so you can use it with any log4js method, i.e. you can do: ```TypeScript this.$logger.warn("This is my message", { useStderr: true }); this.$logger.info("This is my message", { useStderr: true }); ``` Before passing the data to `log4js`, CLI will set required properties in the context of the logging event, so the appender and the layout can read them. This way, in case CLI is used as a library, the custom properties and CLI specific layout, will not be shown. Also delete some methods from logger API, so it is streamlined. Also change logger.error to print on stderr instead of stdout. Move logger.ts to logger directory, so all logger logic will be on a single place. --- lib/bootstrap.ts | 2 +- lib/common/definitions/logger.d.ts | 8 +- lib/common/logger/appenders/cli-appender.ts | 29 +++++ .../appenders}/emit-appender.ts | 6 +- lib/common/logger/layouts/cli-layout.ts | 33 +++++ lib/common/{ => logger}/logger.ts | 122 ++++++++++-------- lib/common/test/unit-tests/logger.ts | 2 +- lib/common/test/unit-tests/stubs.ts | 33 ++--- lib/constants.ts | 12 +- lib/nativescript-cli.ts | 4 +- lib/services/initialize-service.ts | 8 +- test/stubs.ts | 19 +-- 12 files changed, 171 insertions(+), 107 deletions(-) create mode 100644 lib/common/logger/appenders/cli-appender.ts rename lib/common/{logger-appenders => logger/appenders}/emit-appender.ts (81%) create mode 100644 lib/common/logger/layouts/cli-layout.ts rename lib/common/{ => logger}/logger.ts (57%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 090c7c01ae..3b77b4214b 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -1,5 +1,5 @@ require("./common/bootstrap"); -$injector.requirePublicClass("logger", "./common/logger"); +$injector.requirePublicClass("logger", "./common/logger/logger"); $injector.require("config", "./config"); $injector.require("options", "./options"); // note: order above is important! diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index 6d203e3a0e..fcbb21053d 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -14,22 +14,16 @@ declare global { 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; - 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; - - out(formatStr?: any, ...args: any[]): void; - write(...args: any[]): void; - prepare(item: any): string; - printInfoMessageOnSameLine(message: string): void; - printOnStderr(formatStr?: any, ...args: any[]): void; } diff --git a/lib/common/logger/appenders/cli-appender.ts b/lib/common/logger/appenders/cli-appender.ts new file mode 100644 index 0000000000..19f5cbee0d --- /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: Log4JSEmitAppenderConfiguration, 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 similarity index 81% rename from lib/common/logger-appenders/emit-appender.ts rename to lib/common/logger/appenders/emit-appender.ts index 23939ceae2..f0b907174e 100644 --- a/lib/common/logger-appenders/emit-appender.ts +++ b/lib/common/logger/appenders/emit-appender.ts @@ -1,14 +1,14 @@ import { LoggingEvent } from "log4js"; import { EventEmitter } from "events"; +import { EmitAppenderEventName } from "../../../constants"; -const logDataEventName = "logData"; function emitAppender(layout: Function, emitter: EventEmitter) { const appender = (loggingEvent: LoggingEvent) => { - emitter.emit(logDataEventName, { loggingEvent, formattedMessage: layout(loggingEvent) }); + emitter.emit(EmitAppenderEventName, { loggingEvent, formattedMessage: layout(loggingEvent) }); }; appender.shutdown = () => { - emitter.removeAllListeners(logDataEventName); + emitter.removeAllListeners(EmitAppenderEventName); }; return appender; 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.ts b/lib/common/logger/logger.ts similarity index 57% rename from lib/common/logger.ts rename to lib/common/logger/logger.ts index 17cb539a9c..dcd0418b0c 100644 --- a/lib/common/logger.ts +++ b/lib/common/logger/logger.ts @@ -2,7 +2,9 @@ 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 { cache } from "../decorators"; +import { layout } from "./layouts/cli-layout"; +import { LoggerConfigData, LoggerLevel, LoggerAppenders } from "../../constants"; const TerminalRenderer = require("marked-terminal"); const chalk = require("chalk"); @@ -10,7 +12,6 @@ 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(private $config: Config.IConfig, private $options: IOptions) { @@ -48,68 +49,49 @@ export class Logger implements ILogger { this.log4jsLogger = log4js.getLogger(); } - getLevel(): string { - this.initialize(); + public initializeCliLogger(): void { + log4js.addLayout("cli", layout); - return this.log4jsLogger.level.toString(); + this.initialize({ + appenderOptions: { type: LoggerAppenders.cliAppender, layout: { type: "cli" } }, + level: this.$options.log + }); } - fatal(...args: string[]): void { + getLevel(): string { this.initialize(); - this.log4jsLogger.fatal.apply(this.log4jsLogger, args); + return this.log4jsLogger.level.toString(); } - error(...args: string[]): void { - this.initialize(); - - const message = util.format.apply(null, args); - const colorizedMessage = message.red; - - this.log4jsLogger.error.apply(this.log4jsLogger, [colorizedMessage]); + fatal(...args: any[]): void { + this.logMessage(args, LoggerLevel.FATAL); } - warn(...args: string[]): void { - this.initialize(); - - const message = util.format.apply(null, args); - const colorizedMessage = message.yellow; - - this.log4jsLogger.warn.apply(this.log4jsLogger, [colorizedMessage]); + error(...args: any[]): void { + args.push({ [LoggerConfigData.useStderr]: true }); + this.logMessage(args, LoggerLevel.ERROR); } - warnWithLabel(...args: string[]): void { - this.initialize(); - - const message = util.format.apply(null, args); - this.warn(`${Logger.LABEL} ${message}`); + warn(...args: any[]): void { + this.logMessage(args, LoggerLevel.WARN); } - info(...args: string[]): void { - this.initialize(); - - this.log4jsLogger.info.apply(this.log4jsLogger, args); + info(...args: any[]): void { + this.logMessage(args, LoggerLevel.INFO); } - debug(...args: string[]): void { - this.initialize(); - + debug(...args: any[]): void { const encodedArgs: string[] = this.getPasswordEncodedArguments(args); - this.log4jsLogger.debug.apply(this.log4jsLogger, encodedArgs); + this.logMessage(encodedArgs, LoggerLevel.DEBUG); } - trace(...args: string[]): void { - this.initialize(); - + trace(...args: any[]): 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)); + this.logMessage(encodedArgs, LoggerLevel.TRACE); } - write(...args: string[]): void { + write(...args: any[]): void { process.stdout.write(util.format.apply(null, args)); } @@ -133,12 +115,6 @@ export class Logger implements ILogger { return JSON.stringify(item); } - public printInfoMessageOnSameLine(message: string): void { - if (!this.$options.log || this.$options.log === "info") { - this.write(message); - } - } - public printMarkdown(...args: string[]): void { const opts = { unescape: true, @@ -160,13 +136,53 @@ export class Logger implements ILogger { marked.setOptions({ renderer: new TerminalRenderer(opts) }); const formattedMessage = marked(util.format.apply(null, args)); - this.write(formattedMessage); + this.info(formattedMessage, { [LoggerConfigData.skipNewLine]: true }); } - public printOnStderr(...args: string[]): void { - if (process.stderr) { - process.stderr.write(util.format.apply(null, args)); + 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[] { diff --git a/lib/common/test/unit-tests/logger.ts b/lib/common/test/unit-tests/logger.ts index 6ddbef0685..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"; diff --git a/lib/common/test/unit-tests/stubs.ts b/lib/common/test/unit-tests/stubs.ts index 567d637684..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> { @@ -19,43 +20,31 @@ 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 { } - printMarkdown(message: string): void { this.output += message; } - - printOnStderr(...args: string[]): void { - // nothing to do here - } } export class ErrorsStub implements IErrors { @@ -168,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/constants.ts b/lib/constants.ts index bcf374bdd7..e70a951c17 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -301,8 +301,10 @@ export enum IOSNativeTargetTypes { appExtension = "app_extension" } +const pathToLoggerAppendersDir = join(__dirname, "common", "logger", "appenders"); export const LoggerAppenders = { - emitAppender: join(__dirname, "common", "logger-appenders", "emit-appender"), + emitAppender: join(pathToLoggerAppendersDir, "emit-appender"), + cliAppender: join(pathToLoggerAppendersDir, "cli-appender") }; export enum LoggerLevel { @@ -362,3 +364,11 @@ export enum LoggerLevel { */ OFF = "OFF" } + +export enum LoggerConfigData { + useStderr = "useStderr", + wrapMessageWithBorders = "wrapMessageWithBorders", + skipNewLine = "skipNewLine" +} + +export const EMIT_APPENDER_EVENT_NAME = "logData"; diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts index 2620b33018..39b6479e60 100644 --- a/lib/nativescript-cli.ts +++ b/lib/nativescript-cli.ts @@ -24,10 +24,8 @@ process.on = (event: string, listener: any): any => { const err: IErrors = $injector.resolve("$errors"); err.printCallStack = config.DEBUG; - const $options = $injector.resolve("options"); - const $initializeService = $injector.resolve("initializeService"); - await $initializeService.initialize({ loggerOptions: { level: $options.log } }); + await $initializeService.initialize(); const extensibilityService: IExtensibilityService = $injector.resolve("extensibilityService"); try { diff --git a/lib/services/initialize-service.ts b/lib/services/initialize-service.ts index 83fd10913a..d57e2969b2 100644 --- a/lib/services/initialize-service.ts +++ b/lib/services/initialize-service.ts @@ -8,7 +8,11 @@ export class InitializeService implements IInitializeService { public async initialize(initOpts?: { loggerOptions?: ILoggerOptions }): Promise { initOpts = initOpts || {}; const $logger = this.$injector.resolve("logger"); - $logger.initialize(initOpts.loggerOptions); + if (initOpts.loggerOptions) { + $logger.initialize(initOpts.loggerOptions); + } else { + $logger.initializeCliLogger(); + } await this.showWarnings($logger); } @@ -19,7 +23,7 @@ export class InitializeService implements IInitializeService { _.each(systemWarnings, systemWarning => { const message = `${EOL}${systemWarning.message}${EOL}`; if (systemWarning.severity === SystemWarningsSeverity.high) { - $logger.printOnStderr(message.red.bold); + $logger.error(message); } else { $logger.warn(message); } diff --git a/test/stubs.ts b/test/stubs.ts index bfdd8efda5..5763c9d84c 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -12,12 +12,15 @@ import { DevicePlatformsConstants } from "./../lib/common/mobile/device-platform 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"; @@ -26,23 +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 { } - printMarkdown(message: string): void { } - - printOnStderr(...args: string[]): void { - // nothing to do here - } } export class FileSystemStub implements IFileSystem { From e874d335f2ee654842e7953d6a11734f1f8a0571 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 7 May 2019 21:24:22 +0300 Subject: [PATCH 45/62] chore: handle changes in logger API There are several changes in logger API that need to be changed in the code - they are mostly renaming of methods. --- lib/commands/appstore-list.ts | 4 +-- lib/commands/extensibility/list-extensions.ts | 2 +- lib/commands/list-platforms.ts | 10 +++--- lib/commands/plugin/list-plugins.ts | 10 +++--- lib/commands/post-install.ts | 2 +- lib/common/commands/analytics.ts | 2 +- lib/common/commands/autocompletion.ts | 2 +- .../commands/device/list-applications.ts | 2 +- lib/common/commands/device/list-devices.ts | 10 +++--- lib/common/commands/proxy/proxy-clear.ts | 2 +- lib/common/commands/proxy/proxy-get.ts | 2 +- lib/common/commands/proxy/proxy-set.ts | 4 +-- lib/common/dispatchers.ts | 2 +- lib/common/logger/appenders/emit-appender.ts | 6 ++-- .../android/android-emulator-services.ts | 11 ++++--- lib/common/mobile/device-log-provider.ts | 3 +- .../ios/device/ios-device-file-system.ts | 4 +-- .../mobile/mobile-core/devices-service.ts | 2 +- .../services/auto-completion-service.ts | 14 ++++---- .../mobile/android-device-file-system.ts | 2 +- .../mobile/project-files-manager.ts | 2 +- lib/package-installation-manager.ts | 4 +-- lib/project-data.ts | 2 +- lib/services/analytics/analytics-service.ts | 2 +- .../android-resources-migration-service.ts | 6 ++-- lib/services/doctor-service.ts | 10 +++--- lib/services/ios-project-service.ts | 2 +- lib/services/ios-provision-service.ts | 10 +++--- .../nativescript-cloud-extension-service.ts | 2 +- lib/services/platform-service.ts | 32 +++++++++---------- lib/services/plugins-service.ts | 8 ++--- lib/services/project-init-service.ts | 2 +- lib/services/versions-service.ts | 2 +- lib/services/workflow-service.ts | 7 ++-- .../node-modules/node-modules-dest-copy.ts | 2 +- lib/xml-validator.ts | 2 +- test/commands/post-install.ts | 2 +- test/ios-project-service.ts | 2 +- test/package-installation-manager.ts | 2 +- test/services/subscription-service.ts | 4 +-- 40 files changed, 101 insertions(+), 100 deletions(-) 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/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/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/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/appenders/emit-appender.ts b/lib/common/logger/appenders/emit-appender.ts index f0b907174e..d64c0d9294 100644 --- a/lib/common/logger/appenders/emit-appender.ts +++ b/lib/common/logger/appenders/emit-appender.ts @@ -1,14 +1,14 @@ import { LoggingEvent } from "log4js"; import { EventEmitter } from "events"; -import { EmitAppenderEventName } from "../../../constants"; +import { EMIT_APPENDER_EVENT_NAME } from "../../../constants"; function emitAppender(layout: Function, emitter: EventEmitter) { const appender = (loggingEvent: LoggingEvent) => { - emitter.emit(EmitAppenderEventName, { loggingEvent, formattedMessage: layout(loggingEvent) }); + emitter.emit(EMIT_APPENDER_EVENT_NAME, { loggingEvent, formattedMessage: layout(loggingEvent) }); }; appender.shutdown = () => { - emitter.removeAllListeners(EmitAppenderEventName); + emitter.removeAllListeners(EMIT_APPENDER_EVENT_NAME); }; return appender; 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/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/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/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/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/project-init-service.ts b/lib/services/project-init-service.ts index edb8bea6f9..24937a1f5c 100644 --- a/lib/services/project-init-service.ts +++ b/lib/services/project-init-service.ts @@ -74,7 +74,7 @@ export class ProjectInitService implements IProjectInitService { throw err; } - this.$logger.out("Project successfully initialized."); + this.$logger.info("Project successfully initialized."); } private get projectFilePath(): string { 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/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/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(" "); }; From 4f1d7a80b8f9304e00c9c72ebed871873a5c7b09 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 7 May 2019 21:31:28 +0300 Subject: [PATCH 46/62] chore: remove write method from logger --- lib/common/logger/logger.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/common/logger/logger.ts b/lib/common/logger/logger.ts index dcd0418b0c..9047bbbbb0 100644 --- a/lib/common/logger/logger.ts +++ b/lib/common/logger/logger.ts @@ -91,10 +91,6 @@ export class Logger implements ILogger { this.logMessage(encodedArgs, LoggerLevel.TRACE); } - write(...args: any[]): void { - process.stdout.write(util.format.apply(null, args)); - } - prepare(item: any): string { if (typeof item === "undefined" || item === null) { return "[no content]"; From 8b6527215c4922a34d575ae6c600ff63c43aca94 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 8 May 2019 02:33:41 +0300 Subject: [PATCH 47/62] chore: use correct interfaces for appenders --- lib/common/definitions/logger.d.ts | 7 +++++-- lib/common/logger/appenders/cli-appender.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index fcbb21053d..6077c31d7d 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -5,6 +5,7 @@ import { LoggerLevel } from "../../constants"; declare global { interface IAppenderOptions extends IDictionary { type: string; + layout?: Layout; } interface ILoggerOptions { @@ -26,9 +27,11 @@ declare global { prepare(item: any): string; } - - interface Log4JSEmitAppenderConfiguration extends Configuration { + interface Log4JSAppenderConfiguration extends Configuration { layout: Layout; + } + + interface Log4JSEmitAppenderConfiguration extends Log4JSAppenderConfiguration { emitter: EventEmitter; } } diff --git a/lib/common/logger/appenders/cli-appender.ts b/lib/common/logger/appenders/cli-appender.ts index 19f5cbee0d..bf8c2355c6 100644 --- a/lib/common/logger/appenders/cli-appender.ts +++ b/lib/common/logger/appenders/cli-appender.ts @@ -13,7 +13,7 @@ function cliAppender(layout: Function) { return appender; } -function configure(config: Log4JSEmitAppenderConfiguration, layouts: any) { +function configure(config: Log4JSAppenderConfiguration, layouts: any) { // the default layout for the appender let layout = layouts.messagePassThroughLayout; From 9509b7435e9bd3630a5822bd983d0ccd58446a2d Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 8 May 2019 02:34:13 +0300 Subject: [PATCH 48/62] docs: add information for logger in Public API --- PublicAPI.md | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/PublicAPI.md b/PublicAPI.md index 066640eb0d..4dcc34fc07 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,155 @@ const tns = require("nativescript"); tns.cleanupService.setCleanupLogFile("/Users/username/cleanup-logs.txt"); ``` +## initializeService +The `initializeService` is used to initialize CLI's configuration and the beginning and print all warnings related to current environment. + +### initialize +This method inits CLI's logger and prints all system warnings. + +* Definition +```TypeScript +initialize(initOpts?: { loggerOptions?: ILoggerOptions }): Promise +``` + +> NOTE: For more information about loggerOptions, you can check `logger`. + +* Usage +```JavaScript +const tns = require("nativescript"); +tns.initializeService.initialize(); +``` + +## 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`. From 9bff5a02654463b4534e9a9fadde331aae5e2745 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 8 May 2019 19:17:55 +0300 Subject: [PATCH 49/62] chore: handle PR comments --- PublicAPI.md | 2 +- lib/common/logger/appenders/emit-appender.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 4dcc34fc07..6246904fb6 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -1525,7 +1525,7 @@ tns.cleanupService.setCleanupLogFile("/Users/username/cleanup-logs.txt"); ``` ## initializeService -The `initializeService` is used to initialize CLI's configuration and the beginning and print all warnings related to current environment. +The `initializeService` is used to initialize CLI's configuration at the beginning and print all warnings related to current environment. ### initialize This method inits CLI's logger and prints all system warnings. diff --git a/lib/common/logger/appenders/emit-appender.ts b/lib/common/logger/appenders/emit-appender.ts index d64c0d9294..9902579a67 100644 --- a/lib/common/logger/appenders/emit-appender.ts +++ b/lib/common/logger/appenders/emit-appender.ts @@ -15,14 +15,6 @@ function emitAppender(layout: Function, emitter: EventEmitter) { } function configure(config: Log4JSEmitAppenderConfiguration, 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); - } - if (!config.emitter) { throw new Error("Emitter must be passed to emit-appender"); } @@ -31,6 +23,14 @@ function configure(config: Log4JSEmitAppenderConfiguration, layouts: any) { 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); } From 194106c47906bf9942071ffd377eba6eda79a4a4 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 8 May 2019 19:18:53 +0300 Subject: [PATCH 50/62] feat: improve initialization service Improve initialization service, so it can be used to initialize all settings required at the beginning of CLI process. --- PublicAPI.md | 33 ++++++++++++++++++----- lib/common/definitions/extensibility.d.ts | 5 ++++ lib/definitions/initialize-service.d.ts | 8 +++++- lib/services/initialize-service.ts | 14 +++++++++- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 6246904fb6..3fdbba0e30 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -1528,20 +1528,41 @@ tns.cleanupService.setCleanupLogFile("/Users/username/cleanup-logs.txt"); The `initializeService` is used to initialize CLI's configuration at the beginning and print all warnings related to current environment. ### initialize -This method inits CLI's logger and prints all system warnings. +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 -initialize(initOpts?: { loggerOptions?: ILoggerOptions }): Promise +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 -```JavaScript -const tns = require("nativescript"); -tns.initializeService.initialize(); -``` + * 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 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/definitions/initialize-service.d.ts b/lib/definitions/initialize-service.d.ts index e4cab0db93..c2b7b22c09 100644 --- a/lib/definitions/initialize-service.d.ts +++ b/lib/definitions/initialize-service.d.ts @@ -1,3 +1,9 @@ +interface IInitializeOptions { + loggerOptions?: ILoggerOptions; + settingsServiceOptions?: IConfigurationSettings; + extensibilityOptions?: { pathToExtensions: string }; +} + interface IInitializeService { - initialize(initOpts?: { loggerOptions?: ILoggerOptions }): Promise; + initialize(initOpts?: IInitializeOptions): Promise; } diff --git a/lib/services/initialize-service.ts b/lib/services/initialize-service.ts index d57e2969b2..117da1b4c1 100644 --- a/lib/services/initialize-service.ts +++ b/lib/services/initialize-service.ts @@ -5,7 +5,7 @@ export class InitializeService implements IInitializeService { // Injecting something may lead to logger initialization, but we want to initialize it from here. constructor(private $injector: IInjector) { } - public async initialize(initOpts?: { loggerOptions?: ILoggerOptions }): Promise { + public async initialize(initOpts?: IInitializeOptions): Promise { initOpts = initOpts || {}; const $logger = this.$injector.resolve("logger"); if (initOpts.loggerOptions) { @@ -14,6 +14,18 @@ export class InitializeService implements IInitializeService { $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); } From ae4abbd76151ac3e7c07bafa5e1bb478994f5278 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 9 May 2019 08:50:17 +0300 Subject: [PATCH 51/62] fix: logger changes broke trace and some hooks `--log trace` is broken as we are tring to iterate over object created with `Object.create(null)`. It uses the prototype of null, so it does not have `hasOwnProperty` method. Filter such objects from our checks in logger. Also nativescript-plugin-firebase uses logger.out, which has been deleted, get it back for backwards compatibility and delete it in 6.0.0 release. --- lib/common/logger/logger.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/common/logger/logger.ts b/lib/common/logger/logger.ts index 9047bbbbb0..4768e61e7e 100644 --- a/lib/common/logger/logger.ts +++ b/lib/common/logger/logger.ts @@ -81,6 +81,15 @@ export class Logger implements ILogger { this.logMessage(args, LoggerLevel.INFO); } + /** + * DEPRECATED + * Present only for backwards compatibility as some plugins (nativescript-plugin-firebase) + * use $logger.out in their hooks + */ + out(...args: any[]): void { + this.info(args); + } + debug(...args: any[]): void { const encodedArgs: string[] = this.getPasswordEncodedArguments(args); this.logMessage(encodedArgs, LoggerLevel.DEBUG); @@ -159,7 +168,8 @@ export class Logger implements ILogger { const result: any = {}; const cleanedData = _.cloneDeep(data); - const dataToCheck = data.filter(el => typeof el === "object"); + // objects created with Object.create(null) do not have `hasOwnProperty` function + const dataToCheck = data.filter(el => typeof el === "object" && el.hasOwnProperty && typeof el.hasOwnProperty === "function"); for (const element of dataToCheck) { if (opts.length === 0) { From f63195af494469f8f5a6a4649aa7be7248508f89 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 9 May 2019 17:34:27 +0300 Subject: [PATCH 52/62] chore: set version to 6.0.0 --- npm-shrinkwrap.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 46a9da7799..a8fe72fd72 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.4.0", + "version": "6.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4ea1444a73..34b323c8bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.4.0", + "version": "6.0.0", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From f9424dc7e7bb38fd9335d9cf008b45d32b878b92 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 9 May 2019 17:37:49 +0300 Subject: [PATCH 53/62] chore: set version to 5.4.1 --- npm-shrinkwrap.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 46a9da7799..d97b5c17ad 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.4.0", + "version": "5.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4ea1444a73..f4d61d2b64 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.4.0", + "version": "5.4.1", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 6ddcc2f7d4289d6276b640a12fc21a8125de3bf9 Mon Sep 17 00:00:00 2001 From: Todor Petrov Date: Fri, 10 May 2019 11:19:23 +0300 Subject: [PATCH 54/62] style: misleading warning message --- lib/services/plugins-service.ts | 8 ++++---- test/plugins-service.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 64dc3c38cf..857e05dbbc 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -318,12 +318,12 @@ export class PluginsService implements IPluginsService { const installedFrameworkVersion = this.getInstalledFrameworkVersion(platform, projectData); const pluginPlatformsData = pluginData.platformsData; if (pluginPlatformsData) { - const pluginVersion = (pluginPlatformsData)[platform]; - if (!pluginVersion) { + const versionRequiredByPlugin = (pluginPlatformsData)[platform]; + if (!versionRequiredByPlugin) { this.$logger.warn(`${pluginData.name} is not supported for ${platform}.`); isValid = false; - } else if (semver.gt(pluginVersion, installedFrameworkVersion)) { - this.$logger.warn(`${pluginData.name} ${pluginVersion} for ${platform} is not compatible with the currently installed framework version ${installedFrameworkVersion}.`); + } else if (semver.gt(versionRequiredByPlugin, installedFrameworkVersion)) { + this.$logger.warn(`${pluginData.name} requires at least version ${versionRequiredByPlugin} of platform ${platform}. Currently installed version is ${installedFrameworkVersion}.`); isValid = false; } } diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 630a577b5f..e36966e1aa 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -272,7 +272,7 @@ describe("Plugins service", () => { }); it("fails when the plugin does not support the installed framework", async () => { let isWarningMessageShown = false; - const expectedWarningMessage = "mySamplePlugin 1.5.0 for android is not compatible with the currently installed framework version 1.4.0."; + const expectedWarningMessage = "mySamplePlugin requires at least version 1.5.0 of platform android. Currently installed version is 1.4.0."; // Creates plugin in temp folder const pluginName = "mySamplePlugin"; From 7d3302f140c878d7cc007e0a1b2465ed5a21ca98 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 10 May 2019 19:22:17 +0300 Subject: [PATCH 55/62] fix: make logger backwards compatible Several plugin use deleted logger methods in their hooks. Same is valid for `nativescript-cloud` extension. To resolve this, get back the deleted methods, we'll delete them in 6.0.0 release. Also, `nativescript-cloud` has an option `--workflow`, that is an object, while ours is boolean. This breaks its transpilation, so set ours to `any`. --- lib/common/definitions/logger.d.ts | 30 ++++++++++++++++++++++ lib/common/logger/logger.ts | 40 ++++++++++++++++++++++------- lib/common/test/unit-tests/stubs.ts | 6 +++++ lib/declarations.d.ts | 2 +- test/stubs.ts | 6 +++++ 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index 6077c31d7d..6e098bc4c1 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -25,6 +25,36 @@ declare global { trace(formatStr?: any, ...args: any[]): void; printMarkdown(...args: any[]): void; prepare(item: any): string; + + /** + * DEPRECATED + * Do not use it. + */ + out(formatStr?: any, ...args: any[]): void; + + /** + * DEPRECATED + * Do not use it. + */ + write(...args: any[]): void; + + /** + * DEPRECATED + * Do not use it. + */ + printInfoMessageOnSameLine(message: string): void; + + /** + * DEPRECATED + * Do not use it. + */ + printMsgWithTimeout(message: string, timeout: number): Promise; + + /** + * DEPRECATED + * Do not use it. + */ + printOnStderr(formatStr?: any, ...args: any[]): void; } interface Log4JSAppenderConfiguration extends Configuration { diff --git a/lib/common/logger/logger.ts b/lib/common/logger/logger.ts index 4768e61e7e..ca80f80747 100644 --- a/lib/common/logger/logger.ts +++ b/lib/common/logger/logger.ts @@ -81,15 +81,6 @@ export class Logger implements ILogger { this.logMessage(args, LoggerLevel.INFO); } - /** - * DEPRECATED - * Present only for backwards compatibility as some plugins (nativescript-plugin-firebase) - * use $logger.out in their hooks - */ - out(...args: any[]): void { - this.info(args); - } - debug(...args: any[]): void { const encodedArgs: string[] = this.getPasswordEncodedArguments(args); this.logMessage(encodedArgs, LoggerLevel.DEBUG); @@ -200,6 +191,37 @@ export class Logger implements ILogger { return argument; }); } + + /******************************************************************************************* + * Metods below are deprecated. Delete them in 6.0.0 release: * + * Present only for backwards compatibility as some plugins (nativescript-plugin-firebase) * + * use these methods in their hooks * + *******************************************************************************************/ + + out(...args: any[]): void { + this.info(args); + } + + write(...args: any[]): void { + this.info(args, { [LoggerConfigData.skipNewLine]: true }); + } + + printOnStderr(...args: string[]): void { + this.error(args); + } + + printInfoMessageOnSameLine(message: string): void { + this.info(message, { [LoggerConfigData.skipNewLine]: true }); + } + + printMsgWithTimeout(message: string, timeout: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + this.printInfoMessageOnSameLine(message); + resolve(); + }, timeout); + }); + } } $injector.register("logger", Logger); diff --git a/lib/common/test/unit-tests/stubs.ts b/lib/common/test/unit-tests/stubs.ts index b0fb842f02..075dcb4806 100644 --- a/lib/common/test/unit-tests/stubs.ts +++ b/lib/common/test/unit-tests/stubs.ts @@ -45,6 +45,12 @@ export class CommonLoggerStub implements ILogger { printMarkdown(message: string): void { this.output += message; } + + out(formatStr?: any, ...args: any[]): void { } + write(...args: any[]): void { } + printInfoMessageOnSameLine(message: string): void { } + async printMsgWithTimeout(message: string, timeout: number): Promise { } + printOnStderr(formatStr?: any, ...args: any[]): void { } } export class ErrorsStub implements IErrors { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 883943db2a..2b2ee41c09 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -570,7 +570,7 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai analyticsLogFile: string; performance: Object; cleanupLogFile: string; - workflow: boolean; + workflow: any; setupOptions(projectData: IProjectData): void; printMessagesForDeprecatedOptions(logger: ILogger): void; } diff --git a/test/stubs.ts b/test/stubs.ts index 5763c9d84c..4a0dde08c2 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -34,6 +34,12 @@ export class LoggerStub implements ILogger { } printMarkdown(message: string): void { } + + out(formatStr?: any, ...args: any[]): void { } + write(...args: any[]): void { } + printInfoMessageOnSameLine(message: string): void { } + async printMsgWithTimeout(message: string, timeout: number): Promise { } + printOnStderr(formatStr?: any, ...args: any[]): void { } } export class FileSystemStub implements IFileSystem { From beb69e9c6ff867c3f3720c74afe103e4ec571ace Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Mon, 13 May 2019 17:01:18 +0300 Subject: [PATCH 56/62] chore: update nativescript-dev-xcode to 0.2.0 --- npm-shrinkwrap.json | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 46a9da7799..9f31adf20c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -5316,8 +5316,9 @@ } }, "nativescript-dev-xcode": { - "version": "https://github.com/NativeScript/nativescript-dev-xcode/tarball/ec70f5d6032a72b65298ad56fa6ec0c4b2ef03a3", - "integrity": "sha512-CIZGo20Mulu/0u5Im6MYfh/gt2jldK3zt/HtWjUBogV/0QVQNKry2ewC6BudpjetUw4otjmqTRlUhY6wXpZ9Bg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nativescript-dev-xcode/-/nativescript-dev-xcode-0.2.0.tgz", + "integrity": "sha512-UKX+AS3rF/HNQ7777GunT9SaVQJXmcYnol9yKxVXWM1uRNXiQUgLaUmhjYUkHw7voqNN+FCqqRZQlB8mHUPA9g==", "requires": { "simple-plist": "^1.0.0", "uuid": "^3.3.2" diff --git a/package.json b/package.json index 4ea1444a73..b203321248 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "minimatch": "3.0.2", "mkdirp": "0.5.1", "mute-stream": "0.0.5", - "nativescript-dev-xcode": "https://github.com/NativeScript/nativescript-dev-xcode/tarball/ec70f5d6032a72b65298ad56fa6ec0c4b2ef03a3", + "nativescript-dev-xcode": "0.2.0", "nativescript-doctor": "1.9.2", "nativescript-preview-sdk": "0.3.4", "open": "0.0.5", From 3be9e815b96f7d142590565ab1bd02b99702f052 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 13 May 2019 17:32:06 +0300 Subject: [PATCH 57/62] fix: set correct link for legacy workflow warning --- lib/services/workflow-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/workflow-service.ts b/lib/services/workflow-service.ts index 3cbba6a5e8..c9d36308a4 100644 --- a/lib/services/workflow-service.ts +++ b/lib/services/workflow-service.ts @@ -7,7 +7,7 @@ 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. More info about the reasons for this change and how to migrate your project can be found in the link below: -`; +https://www.nativescript.org/blog/the-future-of-building-nativescript-apps`; private webpackWorkflowConfirmMessage = `Do you want to switch your app to the Webpack workflow?`; constructor(private $bundleValidatorHelper: IBundleValidatorHelper, From 936cdff0813c4e6074ef1b2f3e5ec144c1070fcf Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 13 May 2019 16:37:46 +0300 Subject: [PATCH 58/62] fix: plugin create command needs more args for plugin seed There are two new prompts in the plugin seed which require new command line options to be passed from CLI. Handle the new prompts and pass the new options. They can be used in automation scripts. In order to prevent such issue in the future, ensure the spawned process's stdout and stdin are inherited, so if there's a prompt in the plugin seed, CLI will show it to the user. --- .../man_pages/lib-management/plugin-create.md | 2 + lib/commands/plugin/create-plugin.ts | 33 +++++++-- lib/declarations.d.ts | 2 + lib/options.ts | 2 + test/plugin-create.ts | 67 +++++++++++++++++-- test/stubs.ts | 12 +++- 6 files changed, 103 insertions(+), 15 deletions(-) diff --git a/docs/man_pages/lib-management/plugin-create.md b/docs/man_pages/lib-management/plugin-create.md index bfa6d6f7ad..b267887ade 100644 --- a/docs/man_pages/lib-management/plugin-create.md +++ b/docs/man_pages/lib-management/plugin-create.md @@ -28,6 +28,8 @@ Create from a custom plugin seed | `$ tns plugin create * `--path` - Specifies the directory where you want to create the project, if different from the current directory. * `--username` - Specifies the Github username, which will be used to build the URLs in the plugin's package.json file. * `--pluginName` - Used to set the default file and class names in the plugin source. +* `--includeTypeScriptDemo` - Specifies if TypeScript demo should be created. Default value is `y` (i.e. `demo` will be created), in case you do not want to create this demo, pass `--includeTypeScriptDemo=n` +* `--includeAngularDemo` - Specifies if Angular demo should be created. Default value is `y` (i.e. `demo-angular` will be created), in case you do not want to create this demo, pass `--includeAngularDemo=n` * `--template` - Specifies the custom seed archive, which you want to use to create your plugin. If `--template` is not set, the NativeScript CLI creates the plugin from the default NativeScript Plugin Seed. `