diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index ded728e3cb..6190e71356 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -15,6 +15,7 @@ interface IPluginsService { validate(platformData: IPlatformData, projectData: IProjectData): Promise; preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise; convertToPluginData(cacheData: any, projectDir: string): IPluginData; + isNativeScriptPlugin(pluginName: string, projectData: IProjectData): boolean; } interface IPackageJsonDepedenciesResult { diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts index eb70396d9f..582a723d63 100644 --- a/lib/definitions/preview-app-livesync.d.ts +++ b/lib/definitions/preview-app-livesync.d.ts @@ -18,7 +18,7 @@ declare global { } interface IPreviewAppPluginsService { - comparePluginsOnDevice(device: Device): Promise; + comparePluginsOnDevice(data: IPreviewAppLiveSyncData, device: Device): Promise; getExternalPlugins(device: Device): string[]; } diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index a56b31641d..1b6f68ae41 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -49,7 +49,7 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService { startSyncFilesTimeout: startSyncFilesTimeout.bind(this) } }); - await this.$previewAppPluginsService.comparePluginsOnDevice(device); + await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); const payloads = await this.syncFilesForPlatformSafe(data, device.platform); return payloads; @@ -60,7 +60,7 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService { this.showWarningsForNativeFiles(files); for (const device of this.$previewSdkService.connectedDevices) { - await this.$previewAppPluginsService.comparePluginsOnDevice(device); + await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); } const platforms = _(this.$previewSdkService.connectedDevices) diff --git a/lib/services/livesync/playground/preview-app-plugins-service.ts b/lib/services/livesync/playground/preview-app-plugins-service.ts index 9a486ceefe..5a94769413 100644 --- a/lib/services/livesync/playground/preview-app-plugins-service.ts +++ b/lib/services/livesync/playground/preview-app-plugins-service.ts @@ -3,15 +3,18 @@ import * as semver from "semver"; import * as util from "util"; import { Device } from "nativescript-preview-sdk"; import { PluginComparisonMessages } from "./preview-app-constants"; +import { NODE_MODULES_DIR_NAME } from "../../../common/constants"; +import { PLATFORMS_DIR_NAME } from "../../../constants"; export class PreviewAppPluginsService implements IPreviewAppPluginsService { private previewAppVersionWarnings: IDictionary = {}; constructor(private $fs: IFileSystem, private $logger: ILogger, + private $pluginsService: IPluginsService, private $projectData: IProjectData) { } - public async comparePluginsOnDevice(device: Device): Promise { + public async comparePluginsOnDevice(data: IPreviewAppLiveSyncData, device: Device): Promise { if (!this.previewAppVersionWarnings[device.previewAppVersion]) { const devicePlugins = this.getDevicePlugins(device); const localPlugins = this.getLocalPlugins(); @@ -19,7 +22,7 @@ export class PreviewAppPluginsService implements IPreviewAppPluginsService { .map(localPlugin => { const localPluginVersion = localPlugins[localPlugin]; const devicePluginVersion = devicePlugins[localPlugin]; - return this.getWarningForPlugin(localPlugin, localPluginVersion, devicePluginVersion, device.id); + return this.getWarningForPlugin(data, localPlugin, localPluginVersion, devicePluginVersion, device); }) .filter(item => !!item); this.previewAppVersionWarnings[device.previewAppVersion] = warnings; @@ -62,7 +65,15 @@ export class PreviewAppPluginsService implements IPreviewAppPluginsService { } } - private getWarningForPlugin(localPlugin: string, localPluginVersion: string, devicePluginVersion: string, deviceId: string): string { + private getWarningForPlugin(data: IPreviewAppLiveSyncData, localPlugin: string, localPluginVersion: string, devicePluginVersion: string, device: Device): string { + if (data && data.appFilesUpdaterOptions && data.appFilesUpdaterOptions.bundle && this.isNativeScriptPluginWithoutNativeCode(localPlugin, device.platform)) { + return null; + } + + return this.getWarningForPluginCore(localPlugin, localPluginVersion, devicePluginVersion, device.id); + } + + private getWarningForPluginCore(localPlugin: string, localPluginVersion: string, devicePluginVersion: string, deviceId: string): string { this.$logger.trace(`Comparing plugin ${localPlugin} with localPluginVersion ${localPluginVersion} and devicePluginVersion ${devicePluginVersion}`); if (devicePluginVersion) { @@ -80,5 +91,14 @@ export class PreviewAppPluginsService implements IPreviewAppPluginsService { return util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, localPlugin, deviceId); } + + private isNativeScriptPluginWithoutNativeCode(localPlugin: string, platform: string): boolean { + return this.$pluginsService.isNativeScriptPlugin(localPlugin, this.$projectData) && !this.hasNativeCode(localPlugin, platform); + } + + private hasNativeCode(localPlugin: string, platform: string): boolean { + const nativeFolderPath = path.join(this.$projectData.projectDir, NODE_MODULES_DIR_NAME, localPlugin, PLATFORMS_DIR_NAME, platform.toLowerCase()); + return this.$fs.exists(nativeFolderPath) && !this.$fs.isEmptyDir(nativeFolderPath); + } } $injector.register("previewAppPluginsService", PreviewAppPluginsService); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 68cd5d7c5f..a3ca25297e 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -2,6 +2,7 @@ import * as path from "path"; import * as shelljs from "shelljs"; import * as semver from "semver"; import * as constants from "../constants"; +import { NODE_MODULES_DIR_NAME } from "../common/constants"; export class PluginsService implements IPluginsService { private static INSTALL_COMMAND_NAME = "install"; @@ -207,6 +208,12 @@ export class PluginsService implements IPluginsService { }; } + public isNativeScriptPlugin(pluginName: string, projectData: IProjectData): boolean { + const pluginPackageJsonPath = path.join(projectData.projectDir, NODE_MODULES_DIR_NAME, pluginName, "package.json"); + const pluginPackageJsonContent = this.$fs.readJson(pluginPackageJsonPath); + return pluginPackageJsonContent && pluginPackageJsonContent.nativescript; + } + private getBasicPluginInformation(dependencies: any): IBasePluginData[] { return _.map(dependencies, (version: string, key: string) => ({ name: key, diff --git a/test/services/playground/preview-app-plugins-service.ts b/test/services/playground/preview-app-plugins-service.ts index 093769c4b3..8b71c887d5 100644 --- a/test/services/playground/preview-app-plugins-service.ts +++ b/test/services/playground/preview-app-plugins-service.ts @@ -8,14 +8,35 @@ import { PluginComparisonMessages } from "../../../lib/services/livesync/playgro let readJsonParams: string[] = []; let warnParams: string[] = []; -function createTestInjector(localPlugins: IStringDictionary): IInjector { +const deviceId = "myTestDeviceId"; +const projectDir = "testProjectDir"; + +function createTestInjector(localPlugins: IStringDictionary, options?: { isNativeScriptPlugin?: boolean, hasPluginNativeCode?: boolean }): IInjector { + options = options || {}; const injector = new Yok(); injector.register("fs", { readJson: (filePath: string) => { readJsonParams.push(filePath); - return { + const result: any = { dependencies: localPlugins }; + + if (options.isNativeScriptPlugin) { + result.nativescript = {}; + } + + return result; + }, + exists: (filePath: string) => { + return options.hasPluginNativeCode; + }, + isEmptyDir: (filePath: string) => { + return !options.hasPluginNativeCode; + } + }); + injector.register("pluginsService", { + isNativeScriptPlugin: () => { + return options.isNativeScriptPlugin; } }); injector.register("logger", { @@ -23,14 +44,12 @@ function createTestInjector(localPlugins: IStringDictionary): IInjector { warn: (message: string) => warnParams.push(message) }); injector.register("projectData", { - projectDir: "testProjectDir" + projectDir }); injector.register("previewAppPluginsService", PreviewAppPluginsService); return injector; } -const deviceId = "myTestDeviceId"; - function createDevice(plugins: string): Device { return { id: deviceId, @@ -45,8 +64,20 @@ function createDevice(plugins: string): Device { }; } -function setup(localPlugins: IStringDictionary, previewAppPlugins: IStringDictionary): any { - const injector = createTestInjector(localPlugins); +function createPreviewLiveSyncData(options?: { bundle: boolean }) { + return { + projectDir, + appFilesUpdaterOptions: { + release: false, + bundle: options.bundle + }, + env: {} + }; +} + +function setup(localPlugins: IStringDictionary, previewAppPlugins: IStringDictionary, + options?: { isNativeScriptPlugin: boolean, hasPluginNativeCode: boolean }): any { + const injector = createTestInjector(localPlugins, options); const previewAppPluginsService = injector.resolve("previewAppPluginsService"); const device = createDevice(JSON.stringify(previewAppPlugins)); @@ -57,7 +88,7 @@ function setup(localPlugins: IStringDictionary, previewAppPlugins: IStringDictio } describe("previewAppPluginsService", () => { - describe("comparePluginsOnDevice", () => { + describe("comparePluginsOnDevice without bundle", () => { it("should persist warnings per preview app's version", async () => { const localPlugins = { "nativescript-facebook": "2.2.3", @@ -84,7 +115,9 @@ describe("previewAppPluginsService", () => { return originalGetLocalPlugins.apply(previewAppPluginsService); }; - await previewAppPluginsService.comparePluginsOnDevice(createDevice(JSON.stringify(previewAppPlugins))); + const previewLiveSyncData = createPreviewLiveSyncData({ bundle: false }); + + await previewAppPluginsService.comparePluginsOnDevice(previewLiveSyncData, createDevice(JSON.stringify(previewAppPlugins))); const expectedWarnings = [ util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-facebook", deviceId), @@ -98,7 +131,7 @@ describe("previewAppPluginsService", () => { isGetLocalPluginsCalled = false; warnParams = []; - await previewAppPluginsService.comparePluginsOnDevice(createDevice(JSON.stringify(previewAppPlugins))); + await previewAppPluginsService.comparePluginsOnDevice(previewLiveSyncData, createDevice(JSON.stringify(previewAppPlugins))); assert.isFalse(isGetDevicePluginsCalled); assert.isFalse(isGetLocalPluginsCalled); @@ -225,13 +258,171 @@ describe("previewAppPluginsService", () => { it(`${testCase.name}`, async () => { const { previewAppPluginsService, device } = setup(testCase.localPlugins, testCase.previewAppPlugins); - await previewAppPluginsService.comparePluginsOnDevice(device); + await previewAppPluginsService.comparePluginsOnDevice(createPreviewLiveSyncData({ bundle: false }), device); assert.equal(warnParams.length, testCase.expectedWarnings.length); testCase.expectedWarnings.forEach(warning => assert.include(warnParams, warning)); }); } }); + describe("comparePluginsOnDevice with bundle", () => { + const testCases = [ + { + name: "should show warning for non nativescript plugin that has lower major version", + localPlugins: { + lodash: "1.2.3" + }, + previewAppPlugins: { + lodash: "2.3.3" + }, + isNativeScriptPlugin: false, + hasPluginNativeCode: false, + expectedWarnings: [ + util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "lodash", "1.2.3", "2.3.3") + ] + }, + { + name: "should show warning for non nativescript plugin that has greather major version", + localPlugins: { + lodash: "3.2.3" + }, + previewAppPlugins: { + lodash: "2.3.3" + }, + isNativeScriptPlugin: false, + hasPluginNativeCode: false, + expectedWarnings: [ + util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "lodash", "3.2.3", "2.3.3") + ] + }, + { + name: "should show warning for non nativescript plugin that has greather minor version", + localPlugins: { + lodash: "3.4.5" + }, + previewAppPlugins: { + lodash: "3.3.0" + }, + isNativeScriptPlugin: false, + hasPluginNativeCode: false, + expectedWarnings: [ + util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_GREATHER_MINOR_VERSION, "lodash", "3.4.5", "3.3.0") + ] + }, + { + name: "should not show warning for non nativescript plugin that has the same version", + localPlugins: { + lodash: "3.4.5" + }, + previewAppPlugins: { + lodash: "3.4.5" + }, + isNativeScriptPlugin: false, + hasPluginNativeCode: false, + expectedWarnings: [] + }, + { + name: "should not show warning for nativescript plugin without native code that has lower major version", + localPlugins: { + "nativescript-theme-core": "2.4.5" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.4.5" + }, + isNativeScriptPlugin: true, + hasPluginNativeCode: false, + expectedWarnings: [] + }, + { + name: "should not show warning for nativescript plugin without native code that has greather major version", + localPlugins: { + "nativescript-theme-core": "4.4.5" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.4.5" + }, + isNativeScriptPlugin: true, + hasPluginNativeCode: false, + expectedWarnings: [] + }, + { + name: "should not show warning for nativescript plugin without native code that has greather minor version", + localPlugins: { + "nativescript-theme-core": "4.6.5" + }, + previewAppPlugins: { + "nativescript-theme-core": "4.4.5" + }, + isNativeScriptPlugin: true, + hasPluginNativeCode: false, + expectedWarnings: [] + }, + { + name: "should not show warning for nativescript plugin without native code that has the same version", + localPlugins: { + "nativescript-theme-core": "4.6.5" + }, + previewAppPlugins: { + "nativescript-theme-core": "4.6.5" + }, + isNativeScriptPlugin: true, + hasPluginNativeCode: false, + expectedWarnings: [] + }, + { + name: "should show warning for nativescript plugin with native code that has lower major version", + localPlugins: { + "nativescript-theme-core": "2.4.5" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.4.5" + }, + isNativeScriptPlugin: true, + hasPluginNativeCode: true, + expectedWarnings: [util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "nativescript-theme-core", "2.4.5", "3.4.5")] + }, + { + name: "should show warning for nativescript plugin with native code that has greather major version", + localPlugins: { + "nativescript-theme-core": "4.4.5" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.4.5" + }, + isNativeScriptPlugin: true, + hasPluginNativeCode: true, + expectedWarnings: [util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "nativescript-theme-core", "4.4.5", "3.4.5")] + }, + { + name: "should show warning for nativescript plugin with native code that has greather minor version", + localPlugins: { + "nativescript-theme-core": "4.4.5" + }, + previewAppPlugins: { + "nativescript-theme-core": "4.3.5" + }, + isNativeScriptPlugin: true, + hasPluginNativeCode: true, + expectedWarnings: [util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_GREATHER_MINOR_VERSION, "nativescript-theme-core", "4.4.5", "4.3.5")] + }, + ]; + + afterEach(() => { + warnParams = []; + readJsonParams = []; + }); + + _.each(testCases, testCase => { + it(`${testCase.name}`, async () => { + const { previewAppPluginsService, device } = setup(testCase.localPlugins, testCase.previewAppPlugins, { isNativeScriptPlugin: testCase.isNativeScriptPlugin, hasPluginNativeCode: testCase.hasPluginNativeCode }); + + await previewAppPluginsService.comparePluginsOnDevice(createPreviewLiveSyncData({ bundle: true }), device); + + assert.equal(warnParams.length, testCase.expectedWarnings.length); + testCase.expectedWarnings.forEach(warning => assert.include(warnParams, warning)); + }); + }); + }); describe("getExternalPlugins", () => { const testCases = [ {