diff --git a/CHANGELOG.md b/CHANGELOG.md index 291586b657..32ae3f35d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,43 @@ NativeScript CLI Changelog ================ +6.1.2 (2019, September 18) +== + +### Fixed +* [Fixed #5018](https://github.com/NativeScript/nativescript-cli/issues/5018): Track runtime versions on add and on build, run, deploy + + +6.1.1 (2019, September 17) +== + +### Fixed + +* [Fixed #5015](https://github.com/NativeScript/nativescript-cli/pull/5015): CLI passes `--preserve-symlinks` to the webpack itself, not to the Node.js +* [Fixed #4893](https://github.com/NativeScript/nativescript-cli/issues/4893): `tns preview` crashes when scanning on devices with different platforms +* [Fixed #4939](https://github.com/NativeScript/nativescript-cli/issues/4939): Xcode 11 warning: `CFBundleIdentifier value must be the same as PRODUCT_BUNDLE_IDENTIFIER` + +6.1.0 (2019, September 04) +== + +### New + +* [Implemented #4229](https://github.com/NativeScript/nativescript-cli/issues/4229): Do not display command usage help after execution is started +* [Implemented #4909](https://github.com/NativeScript/nativescript-cli/issues/4909): Support for Xcode 11 and iOS 13 +* [Implemented #4926](https://github.com/NativeScript/nativescript-cli/issues/4926): Android SDK 29 support +* [Implemented #4947](https://github.com/NativeScript/nativescript-cli/issues/4947): Add tracking for both React NativeScript and Svelte Native projects +* [Implemented #4966](https://github.com/NativeScript/nativescript-cli/issues/4966): Support LiveSync to iOS Wi-Fi devices +* [Implemented #4974](https://github.com/NativeScript/nativescript-cli/issues/4974): Ask the users why they've uninstalled NativeScript CLI +* [Implemented #4976](https://github.com/NativeScript/nativescript-cli/issues/4976): Handle changes in iOS and Android Runtime 6.1.0 logging +* [Implemented #4980](https://github.com/NativeScript/nativescript-cli/issues/4980): Update message for subscribing to NativeScript newsletter +* [Implemented #4992](https://github.com/NativeScript/nativescript-cli/pull/4992): Allow tns to be able to use npm configuration properly + +### Fixed + +* [Fixed #4936](https://github.com/NativeScript/nativescript-cli/issues/4936): HMR not recovering after exception in Angular lazy routes +* [Fixed #4958](https://github.com/NativeScript/nativescript-cli/issues/4958): `tns doctor` fails when setup is not correct and user selects to fix it manually +* [Fixed #4971](https://github.com/NativeScript/nativescript-cli/issues/4971): Not needed checks are executed on `pod install` + 6.0.3 (2019, August 05) == * [Fixed #4914](https://github.com/NativeScript/nativescript-cli/issues/4914): livesync not working with command tns test android diff --git a/README.md b/README.md index 37062115da..2d27145292 100644 --- a/README.md +++ b/README.md @@ -143,9 +143,10 @@ The --insecure flag allows you to perform insecure SSL connections #### Limitations * You can provide the `` and `` attributes only on Windows systems. -* Proxy settings for the npm and the Android Gradle need to be configured separately. For more information, see the following articles: +* Proxy settings for the npm, the Android Gradle and (optional) Docker need to be configured separately. For more information, see the following articles: * [Configure the npm proxy](https://docs.npmjs.com/misc/config#https-proxy) * [Configure the Android Gradle proxy](https://docs.gradle.org/3.3/userguide/build_environment.html#sec:accessing_the_web_via_a_proxy) + * [Configure the Docker proxy](https://docs.docker.com/network/proxy/) ### Display Current Proxy Settings diff --git a/docs/man_pages/general/proxy-set.md b/docs/man_pages/general/proxy-set.md index ec14d3a1fa..d894039bd0 100644 --- a/docs/man_pages/general/proxy-set.md +++ b/docs/man_pages/general/proxy-set.md @@ -31,9 +31,10 @@ General | `$ tns proxy set [ <% if(isWindows) {%>[ []]< ### Command Limitations * You can set credentials only on Windows systems. -* Proxy settings for npm and (Android) Gradle need to be set separately. +* Proxy settings for npm, (Android) Gradle and (optional) Docker need to be set separately. * configuring `npm` proxy - https://docs.npmjs.com/misc/config#https-proxy * (Android) configuring Gradle proxy - set global configuration in the user directory - _/.gradle/gradle.properties_ - https://docs.gradle.org/3.3/userguide/build_environment.html#sec:accessing_the_web_via_a_proxy + * configuring Docker proxy - https://docs.docker.com/network/proxy/ ### Related Commands diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 56d552ba6e..39393ad64c 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -15,6 +15,7 @@ $injector.require("gradleCommandService", "./services/android/gradle-command-ser $injector.require("gradleBuildService", "./services/android/gradle-build-service"); $injector.require("gradleBuildArgsService", "./services/android/gradle-build-args-service"); $injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); +$injector.require("iOSNativeTargetService", "./services/ios-native-target-service"); $injector.require("iOSExtensionsService", "./services/ios-extensions-service"); $injector.require("iOSWatchAppService", "./services/ios-watch-app-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); diff --git a/lib/constants.ts b/lib/constants.ts index 1ce1a04016..877328afd0 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -186,7 +186,9 @@ export const enum TrackActionNames { AcceptTracking = "Accept Tracking", Performance = "Performance", PreviewAppData = "Preview App Data", - UninstallCLI = "Uninstall CLI" + UninstallCLI = "Uninstall CLI", + UsingRuntimeVersion = "Using Runtime Version", + AddPlatform = "Add Platform" } export const AnalyticsEventLabelDelimiter = "__"; diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index 8c51ac20c3..bffb70425a 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -11,7 +11,11 @@ export class DeployController { const executeAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.$prepareController.prepare(deviceDescriptor.buildData); + const prepareData = { + ...deviceDescriptor.buildData, + nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare } + }; + await this.$prepareController.prepare(prepareData); const packageFilePath = await deviceDescriptor.buildAction(); await this.$deviceInstallAppService.installOnDevice(device, { ...deviceDescriptor.buildData, buildForDevice: !device.isEmulator }, packageFilePath); }; diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 9d6a85195d..fe0ff9460a 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -1,10 +1,9 @@ import * as choki from "chokidar"; import { hook } from "../common/helpers"; -import { performanceLog } from "../common/decorators"; +import { performanceLog, cache } from "../common/decorators"; import { EventEmitter } from "events"; import * as path from "path"; -import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE, PACKAGE_JSON_FILE_NAME, PLATFORMS_DIR_NAME } from "../constants"; - +import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE, PACKAGE_JSON_FILE_NAME, PLATFORMS_DIR_NAME, TrackActionNames, AnalyticsEventLabelDelimiter } from "../constants"; interface IPlatformWatcherData { hasWebpackCompilerProcess: boolean; nativeFilesWatcher: choki.FSWatcher; @@ -27,12 +26,14 @@ export class PrepareController extends EventEmitter { private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, private $webpackCompilerService: IWebpackCompilerService, - private $watchIgnoreListService: IWatchIgnoreListService + private $watchIgnoreListService: IWatchIgnoreListService, + private $analyticsService: IAnalyticsService ) { super(); } public async prepare(prepareData: IPrepareData): Promise { const projectData = this.$projectDataService.getProjectData(prepareData.projectDir); + await this.trackRuntimeVersion(prepareData.platform, projectData); await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); return this.prepareCore(prepareData, projectData); @@ -127,6 +128,21 @@ export class PrepareController extends EventEmitter { } private async startNativeWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { + let newNativeWatchStarted = false; + let hasNativeChanges = false; + + if (prepareData.watchNative) { + newNativeWatchStarted = await this.startNativeWatcher(platformData, projectData); + } + + if (newNativeWatchStarted) { + hasNativeChanges = await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + } + + return hasNativeChanges; + } + + private async startNativeWatcher(platformData: IPlatformData, projectData: IProjectData): Promise { if (this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) { return false; } @@ -155,9 +171,7 @@ export class PrepareController extends EventEmitter { this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher; - const hasNativeChanges = await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - - return hasNativeChanges; + return true; } @hook('watchPatterns') @@ -186,5 +200,24 @@ export class PrepareController extends EventEmitter { this.persistedData.push(filesChangeEventData); } } + + @cache() + private async trackRuntimeVersion(platform: string, projectData: IProjectData): Promise { + let runtimeVersion: string = null; + try { + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); + const runtimeVersionData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + runtimeVersion = runtimeVersionData && runtimeVersionData.version; + } catch (err) { + this.$logger.trace(`Unable to get runtime version for project directory: ${projectData.projectDir} and platform ${platform}. Error is: `, err); + } + + if (runtimeVersion) { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.UsingRuntimeVersion, + additionalData: `${platform.toLowerCase()}${AnalyticsEventLabelDelimiter}${runtimeVersion}` + }); + } + } } $injector.register("prepareController", PrepareController); diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index 447e86f429..79fb137586 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -99,7 +99,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon data.env = data.env || {}; data.env.externals = this.$previewAppPluginsService.getExternalPlugins(device); - const prepareData = this.$prepareDataService.getPrepareData(data.projectDir, device.platform.toLowerCase(), { ...data, nativePrepare: { skipNativePrepare: true }, watch: true }); + const prepareData = this.$prepareDataService.getPrepareData(data.projectDir, device.platform.toLowerCase(), { ...data, nativePrepare: { skipNativePrepare: true }, watch: true, watchNative: false }); await this.$prepareController.prepare(prepareData); try { diff --git a/lib/data/prepare-data.ts b/lib/data/prepare-data.ts index c24f6fcc0f..d13ed4d01a 100644 --- a/lib/data/prepare-data.ts +++ b/lib/data/prepare-data.ts @@ -5,6 +5,7 @@ export class PrepareData extends ControllerDataBase { public hmr: boolean; public env: any; public watch?: boolean; + public watchNative: boolean = true; constructor(public projectDir: string, public platform: string, data: any) { super(projectDir, platform, data); @@ -16,6 +17,9 @@ export class PrepareData extends ControllerDataBase { hmr: data.hmr || data.useHotModuleReload }; this.watch = data.watch; + if (_.isBoolean(data.watchNative)) { + this.watchNative = data.watchNative; + } } } diff --git a/lib/definitions/prepare.d.ts b/lib/definitions/prepare.d.ts index 43f93ae225..9ad8f0a3c1 100644 --- a/lib/definitions/prepare.d.ts +++ b/lib/definitions/prepare.d.ts @@ -7,6 +7,7 @@ declare global { hmr: boolean; env: any; watch?: boolean; + watchNative: boolean } interface IiOSCodeSigningData { diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 56c9e23ee4..aae148252b 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -458,12 +458,26 @@ interface ICocoaPodsPlatformManager { replacePlatformRow(podfileContent: string, podfilePath: string): { replacedContent: string, podfilePlatformData: IPodfilePlatformData }; } +declare const enum BuildNames { + debug = "Debug", + release = "Release" +} + +interface IXcodeTargetBuildConfigurationProperty { + name: string; + value: any; + buildNames?: BuildNames[]; +} + /** * Describes a service used to add and remove iOS extension */ -interface IIOSExtensionsService { - addExtensionsFromPath(options: IAddExtensionsFromPathOptions): Promise; - removeExtensions(options: IRemoveExtensionsOptions): void; +interface IIOSNativeTargetService { + addTargetToProject(targetRootPath: string, targetFolder: string, targetType: string, project: IXcode.project, platformData: IPlatformData, parentTarget?: string): IXcode.target; + prepareSigning(targetUuids: string[], projectData:IProjectData, projectPath: string): void; + getTargetDirectories(folderPath: string): string[]; + setXcodeTargetBuildConfigurationProperties(properties: IXcodeTargetBuildConfigurationProperty[], targetName: string, project: IXcode.project): void + setConfigurationsFromJsonFile(jsonPath: string, targetUuid: string, targetName: string, project: IXcode.project): void } /** diff --git a/lib/services/ios-extensions-service.ts b/lib/services/ios-extensions-service.ts index fc83e100cf..4786e1dbd8 100644 --- a/lib/services/ios-extensions-service.ts +++ b/lib/services/ios-extensions-service.ts @@ -1,12 +1,11 @@ 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 { +export class IOSExtensionsService implements IIOSExtensionsService { constructor(protected $fs: IFileSystem, protected $pbxprojDomXcode: IPbxprojDomXcode, - protected $xcode: IXcode) { - super($fs, $pbxprojDomXcode, $xcode); + protected $xcode: IXcode, + private $iOSNativeTargetService: IIOSNativeTargetService) { } public async addExtensionsFromPath({extensionsFolderPath, projectData, platformData, pbxProjPath}: IAddExtensionsFromPathOptions): Promise { @@ -17,16 +16,16 @@ export class IOSExtensionsService extends NativeTargetServiceBase implements IIO } const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - this.getTargetDirectories(extensionsFolderPath) + this.$iOSNativeTargetService.getTargetDirectories(extensionsFolderPath) .forEach(extensionFolder => { - const target = this.addTargetToProject(extensionsFolderPath, extensionFolder, IOSNativeTargetTypes.appExtension, project, platformData); + const target = this.$iOSNativeTargetService.addTargetToProject(extensionsFolderPath, extensionFolder, IOSNativeTargetTypes.appExtension, 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.prepareSigning(targetUuids, projectData, pbxProjPath); + this.$iOSNativeTargetService.prepareSigning(targetUuids, projectData, pbxProjPath); return addedExtensions; } @@ -34,12 +33,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"); - this.setXcodeTargetBuildConfigurationProperties( + this.$iOSNativeTargetService.setXcodeTargetBuildConfigurationProperties( [{name: "PRODUCT_BUNDLE_IDENTIFIER", value: `${projectData.projectIdentifiers.ios}.${extensionName}`}], extensionName, project); - this.setConfigurationsFromJsonFile(extJsonPath, target.uuid, extensionName, project); + this.$iOSNativeTargetService.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.ts similarity index 59% rename from lib/services/ios-native-target-service-base.ts rename to lib/services/ios-native-target-service.ts index 5f203d405b..b8be774f6e 100644 --- a/lib/services/ios-native-target-service-base.ts +++ b/lib/services/ios-native-target-service.ts @@ -1,39 +1,27 @@ 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 { +export class IOSNativeTargetService implements IIOSNativeTargetService { constructor(protected $fs: IFileSystem, - protected $pbxprojDomXcode: IPbxprojDomXcode, - protected $xcode: IXcode) { + protected $pbxprojDomXcode: IPbxprojDomXcode) { } - 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) + public addTargetToProject(targetRootPath: string, targetFolder: string, targetType: string, project: IXcode.project, platformData: IPlatformData, parentTarget?: string): IXcode.target { + const targetPath = path.join(targetRootPath, targetFolder); + const targetRelativePath = path.relative(platformData.projectRoot, targetPath); + const files = this.$fs.readDirectory(targetPath) .filter(filePath => !filePath.startsWith(".")) - .map(filePath => path.join(extensionPath, filePath)); - const target = project.addTarget(extensionFolder, targetType, extensionRelativePath, parentTarget); + .map(filePath => path.join(targetPath, filePath)); + const target = project.addTarget(targetFolder, targetType, targetRelativePath, 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); + project.addPbxGroup(files, targetFolder, targetPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true }); + project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName); return target; } - protected prepareSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) { + public prepareSigning(targetUuids: string[], projectData: IProjectData, projectPath: string): void { const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath); const signing = xcode.getSigning(projectData.projectName); if (signing !== undefined) { @@ -52,7 +40,7 @@ export abstract class NativeTargetServiceBase { xcode.save(); } - protected getTargetDirectories(folderPath: string): string[] { + public getTargetDirectories(folderPath: string): string[] { return this.$fs.readDirectory(folderPath) .filter(fileName => { const filePath = path.join(folderPath, fileName); @@ -62,7 +50,7 @@ export abstract class NativeTargetServiceBase { }); } - protected setXcodeTargetBuildConfigurationProperties(properties: IXcodeTargetBuildConfigurationProperty[], targetName: string, project: IXcode.project): void { + public setXcodeTargetBuildConfigurationProperties(properties: IXcodeTargetBuildConfigurationProperty[], targetName: string, project: IXcode.project): void { properties.forEach(property => { const buildNames = property.buildNames || [BuildNames.debug, BuildNames.release]; buildNames.forEach((buildName) => { @@ -71,7 +59,7 @@ export abstract class NativeTargetServiceBase { }); } - protected setConfigurationsFromJsonFile(jsonPath: string, targetUuid: string, targetName: string, project: IXcode.project) { + public setConfigurationsFromJsonFile(jsonPath: string, targetUuid: string, targetName: string, project: IXcode.project): void { if (this.$fs.exists(jsonPath)) { const configurationJson = this.$fs.readJson(jsonPath) || {}; @@ -94,3 +82,5 @@ export abstract class NativeTargetServiceBase { } } } + +$injector.register("iOSNativeTargetService", IOSNativeTargetService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index a7d93a4f5d..784fd90388 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -52,7 +52,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $xcconfigService: IXcconfigService, private $xcodebuildService: IXcodebuildService, private $iOSExtensionsService: IIOSExtensionsService, - private $iOSWatchAppService: IIOSWatchAppService) { + private $iOSWatchAppService: IIOSWatchAppService, + private $iOSNativeTargetService: IIOSNativeTargetService) { super($fs, $projectDataService); } @@ -432,7 +433,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ CFBundleIdentifier - ${projectData.projectIdentifiers.ios} + $(PRODUCT_BUNDLE_IDENTIFIER) ` }); @@ -529,6 +530,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ public async handleNativeDependenciesChange(projectData: IProjectData, opts: IRelease): Promise { const platformData = this.getPlatformData(projectData); + + this.setProductBundleIdentifier(projectData); await this.$cocoapodsService.applyPodfileFromAppResources(projectData, platformData); const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(platformData.projectRoot); @@ -603,6 +606,17 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return target; } + private setProductBundleIdentifier(projectData: IProjectData): void { + const project = this.createPbxProj(projectData); + this.$iOSNativeTargetService.setXcodeTargetBuildConfigurationProperties([ + { + name: "PRODUCT_BUNDLE_IDENTIFIER", + value: `"${projectData.projectIdentifiers.ios}"` + } + ], projectData.projectName, project); + this.savePbxProj(project, projectData); + } + private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string | string[]): string[] { const fileExtensions = _.isArray(fileExtension) ? fileExtension : [fileExtension]; const filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts index d5b6886e45..3c4dad7e5c 100644 --- a/lib/services/ios-watch-app-service.ts +++ b/lib/services/ios-watch-app-service.ts @@ -1,14 +1,13 @@ 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 { +export class IOSWatchAppService implements IIOSWatchAppService { private static WATCH_APP_IDENTIFIER = "watchkitapp"; private static WACTCH_EXTENSION_IDENTIFIER = "watchkitextension"; constructor(protected $fs: IFileSystem, protected $pbxprojDomXcode: IPbxprojDomXcode, - protected $xcode: IXcode) { - super($fs, $pbxprojDomXcode, $xcode); + protected $xcode: IXcode, + private $iOSNativeTargetService: IIOSNativeTargetService) { } public async addWatchAppFromPath({watchAppFolderPath, projectData, platformData, pbxProjPath}: IAddWatchAppFromPathOptions): Promise { @@ -20,13 +19,13 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW return false; } - const appFolder = this.getTargetDirectories(appPath)[0]; - const extensionFolder = this.getTargetDirectories(extensionPath)[0]; + const appFolder = this.$iOSNativeTargetService.getTargetDirectories(appPath)[0]; + const extensionFolder = this.$iOSNativeTargetService.getTargetDirectories(extensionPath)[0]; const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - const watchApptarget = this.addTargetToProject(appPath, appFolder, IOSNativeTargetTypes.watchApp, project, platformData, project.getFirstTarget().uuid); + const watchApptarget = this.$iOSNativeTargetService.addTargetToProject(appPath, appFolder, IOSNativeTargetTypes.watchApp, project, platformData, project.getFirstTarget().uuid); this.configureTarget( appFolder, path.join(appPath, appFolder), @@ -37,7 +36,7 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW ); targetUuids.push(watchApptarget.uuid); - const watchExtensionTarget = this.addTargetToProject(extensionPath, extensionFolder, IOSNativeTargetTypes.watchExtension, project, platformData, watchApptarget.uuid); + const watchExtensionTarget = this.$iOSNativeTargetService.addTargetToProject(extensionPath, extensionFolder, IOSNativeTargetTypes.watchExtension, project, platformData, watchApptarget.uuid); this.configureTarget( extensionFolder, path.join(extensionPath, extensionFolder), @@ -48,7 +47,7 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW targetUuids.push(watchExtensionTarget.uuid); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); - this.prepareSigning(targetUuids, projectData, pbxProjPath); + this.$iOSNativeTargetService.prepareSigning(targetUuids, projectData, pbxProjPath); return true; } @@ -78,7 +77,7 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW identifierParts.pop(); const wkAppBundleIdentifier = identifierParts.join("."); - this.setXcodeTargetBuildConfigurationProperties([ + this.$iOSNativeTargetService.setXcodeTargetBuildConfigurationProperties([ {name: "PRODUCT_BUNDLE_IDENTIFIER", value: identifier}, {name: "SDKROOT", value: "watchos"}, {name: "TARGETED_DEVICE_FAMILY", value: IOSDeviceTargets.watchos}, @@ -86,7 +85,7 @@ export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSW {name: "WK_APP_BUNDLE_IDENTIFIER", value: wkAppBundleIdentifier} ], targetName, project); - this.setConfigurationsFromJsonFile(targetConfigurationJsonPath, target.uuid, targetName, project); + this.$iOSNativeTargetService.setConfigurationsFromJsonFile(targetConfigurationJsonPath, target.uuid, targetName, project); project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName); } } diff --git a/lib/services/ip-service.ts b/lib/services/ip-service.ts index d1a5d9e17d..9815c9bebd 100644 --- a/lib/services/ip-service.ts +++ b/lib/services/ip-service.ts @@ -1,9 +1,12 @@ +import { cache } from "../common/decorators"; + export class IPService implements IIPService { private static GET_IP_TIMEOUT = 1000; constructor(private $config: IConfiguration, private $httpClient: Server.IHttpClient, private $logger: ILogger) { } + @cache() public async getCurrentIPv4Address(): Promise { const ipAddress = await this.getIPAddressFromServiceReturningJSONWithIPProperty(this.$config.WHOAMI_URL_ENDPOINT) || await this.getIPAddressFromServiceReturningJSONWithIPProperty("https://api.myip.com") || diff --git a/lib/services/platform/add-platform-service.ts b/lib/services/platform/add-platform-service.ts index 9185127290..4f795e3076 100644 --- a/lib/services/platform/add-platform-service.ts +++ b/lib/services/platform/add-platform-service.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as temp from "temp"; -import { PROJECT_FRAMEWORK_FOLDER_NAME } from "../../constants"; +import { PROJECT_FRAMEWORK_FOLDER_NAME, TrackActionNames, AnalyticsEventLabelDelimiter } from "../../constants"; import { performanceLog } from "../../common/decorators"; export class AddPlatformService implements IAddPlatformService { @@ -8,7 +8,8 @@ export class AddPlatformService implements IAddPlatformService { private $fs: IFileSystem, private $pacoteService: IPacoteService, private $projectDataService: IProjectDataService, - private $terminalSpinnerService: ITerminalSpinnerService + private $terminalSpinnerService: ITerminalSpinnerService, + private $analyticsService: IAnalyticsService ) { } public async addPlatformSafe(projectData: IProjectData, platformData: IPlatformData, packageToInstall: string, nativePrepare: INativePrepare): Promise { @@ -22,6 +23,7 @@ export class AddPlatformService implements IAddPlatformService { const frameworkVersion = frameworkPackageJsonContent.version; await this.setPlatformVersion(platformData, projectData, frameworkVersion); + await this.trackPlatformVersion(frameworkVersion, platformData); if (!nativePrepare || !nativePrepare.skipNativePrepare) { await this.addNativePlatform(platformData, projectData, frameworkDirPath, frameworkVersion); @@ -61,5 +63,12 @@ export class AddPlatformService implements IAddPlatformService { await platformData.platformProjectService.interpolateData(projectData); platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); } + + private async trackPlatformVersion(frameworkVersion: string, platformData: IPlatformData): Promise { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.AddPlatform, + additionalData: `${platformData.platformNameLowerCase}${AnalyticsEventLabelDelimiter}${frameworkVersion}` + }); + } } $injector.register("addPlatformService", AddPlatformService); diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 29f6c4c7f8..80457719b3 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -139,8 +139,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const args = [ ...additionalNodeArgs, - path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), "--preserve-symlinks", + path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, ...envParams ]; @@ -174,11 +174,18 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp Object.assign(envData, appPath && { appPath }, - appResourcesPath && { appResourcesPath }, + appResourcesPath && { appResourcesPath } ); envData.verbose = envData.verbose || this.$logger.isVerbose(); envData.production = envData.production || prepareData.release; + // The snapshot generation is wrongly located in the Webpack plugin. + // It should be moved in the Native Prepare of the CLI or a Gradle task in the Runtime. + // As a workaround, we skip the mksnapshot, xxd and android-ndk calls based on skipNativePrepare. + // In this way the plugin will prepare only the snapshot JS entry without any native prepare and + // we will able to execute cloud builds with snapshot without having any local snapshot or Docker setup. + // TODO: Remove this flag when we remove the native part from the plugin. + envData.skipSnapshotTools = prepareData.nativePrepare && prepareData.nativePrepare.skipNativePrepare; if (prepareData.env && (prepareData.env.sourceMap === false || prepareData.env.sourceMap === 'false')) { delete envData.sourceMap; diff --git a/test/controllers/add-platform-controller.ts b/test/controllers/add-platform-controller.ts index 4da557ea4c..2af133929d 100644 --- a/test/controllers/add-platform-controller.ts +++ b/test/controllers/add-platform-controller.ts @@ -16,6 +16,9 @@ function createInjector(data?: { latestFrameworkVersion: string }) { injector.register("platformController", PlatformController); injector.register("addPlatformService", AddPlatformService); injector.register("pacoteService", PacoteServiceStub); + injector.register("analyticsService", { + trackEventActionInGoogleAnalytics: () => ({}) + }); injector.register("pacoteService", { extractPackage: async (name: string): Promise => { extractedPackageFromPacote = name; } diff --git a/test/controllers/prepare-controller.ts b/test/controllers/prepare-controller.ts index ef0b4a0fd0..741da72bdb 100644 --- a/test/controllers/prepare-controller.ts +++ b/test/controllers/prepare-controller.ts @@ -9,7 +9,8 @@ const prepareData = { release: false, hmr: false, env: {}, - watch: true + watch: true, + watchNative: true }; let isCompileWithWatchCalled = false; @@ -54,6 +55,10 @@ function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { isFileInIgnoreList: () => false }); + injector.register("analyticsService", { + trackEventActionInGoogleAnalytics: () => ({}) + }); + const prepareController: PrepareController = injector.resolve("prepareController"); prepareController.emit = (eventName: string, eventData: any) => { emittedEventNames.push(eventName); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index f980f15267..fd1b2ed35c 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -174,6 +174,9 @@ function createTestInjector(projectPath: string, projectName: string, xCode?: IX testInjector.register("logSourceMapService", { replaceWithOriginalFileLocations: (platform: string, message: string) => message }); + testInjector.register("iOSNativeTargetService", { + setXcodeTargetBuildConfigurationProperties: () => { /* */ } + }); return testInjector; } diff --git a/test/services/ip-service.ts b/test/services/ip-service.ts index 0d91ebe0d5..c2750f6ebf 100644 --- a/test/services/ip-service.ts +++ b/test/services/ip-service.ts @@ -135,5 +135,25 @@ describe("ipService", () => { assert.isTrue(logger.traceOutput.indexOf(errMsgForMyipCom) !== -1, `Trace output\n'${logger.traceOutput}'\ndoes not contain expected message:\n${errMsgForMyipCom}`); assert.isTrue(logger.traceOutput.indexOf(errMsgForIpifyOrg) !== -1, `Trace output\n'${logger.traceOutput}'\ndoes not contain expected message:\n${errMsgForMyipCom}`); }); + + it("is called only once per process", async () => { + const testInjector = createTestInjector(); + const httpClient = testInjector.resolve("httpClient"); + let httpRequestCounter = 0; + httpClient.httpRequest = async (options: any, proxySettings?: IProxySettings): Promise => { + httpRequestCounter++; + return { body: JSON.stringify({ ip }) }; + }; + + const ipService = testInjector.resolve("ipService"); + + const ipAddress = await ipService.getCurrentIPv4Address(); + assert.equal(httpRequestCounter, 1); + assert.equal(ipAddress, ip); + + const ipAddress2 = await ipService.getCurrentIPv4Address(); + assert.equal(httpRequestCounter, 1); + assert.equal(ipAddress2, ip); + }); }); }); diff --git a/test/services/platform/add-platform-service.ts b/test/services/platform/add-platform-service.ts index 8a2dcc9810..6d4945f7ba 100644 --- a/test/services/platform/add-platform-service.ts +++ b/test/services/platform/add-platform-service.ts @@ -19,6 +19,9 @@ function createTestInjector() { } }); injector.register("addPlatformService", AddPlatformService); + injector.register("analyticsService", { + trackEventActionInGoogleAnalytics: () => ({}) + }); const fs = injector.resolve("fs"); fs.exists = () => false;