From 9e529870efc4083c7afc3648ebc1106218dc8589 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 14 Mar 2018 19:30:16 +0200 Subject: [PATCH] fix: Plugin remove command fails in case plugin name has dot In case plugin name has dot (`.`) in its name, the `plugin remove` command fails. The reason is the way we are trying to remove plugin related data from the `nativescript` key of application's package.json. We are using `.` to concatenate the keys inside nativescript key and later we are trying to split by `.` in order to find which key should be deleted. This works in most of the cases, but when the plugin name has `.` in it, our logic fails as the split operation finds the dot. Fix this by using some symbols that are not allowed in npm for plugins names. Use symbols that will rarely be used in the same sequence. --- lib/constants.ts | 5 ++ lib/services/project-data-service.ts | 9 ++-- test/services/project-data-service.ts | 66 +++++++++++++++------------ 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/lib/constants.ts b/lib/constants.ts index 3857c72542..be62b65a9b 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -139,3 +139,8 @@ export const enum BuildStates { } export const NATIVESCRIPT_CLOUD_EXTENSION_NAME = "nativescript-cloud"; + +/** + * Used in ProjectDataService to concatenate the names of the properties inside nativescript key of package.json. + */ +export const NATIVESCRIPT_PROPS_INTERNAL_DELIMITER = "**|__**"; diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index ce0962e871..a93368f29a 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,6 +1,7 @@ import * as path from "path"; import { ProjectData } from "../project-data"; import { exported } from "../common/decorators"; +import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER } from "../constants"; interface IProjectFileData { projectData: any; @@ -65,11 +66,11 @@ export class ProjectDataService implements IProjectDataService { } private getNativeScriptPropertyName(propertyName: string) { - return `${this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE}.${propertyName}`; + return `${this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE}${NATIVESCRIPT_PROPS_INTERNAL_DELIMITER}${propertyName}`; } private getPropertyValueFromJson(jsonData: any, dottedPropertyName: string): any { - const props = dottedPropertyName.split("."); + const props = dottedPropertyName.split(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); let result = jsonData[props.shift()]; for (const prop of props) { @@ -82,7 +83,7 @@ export class ProjectDataService implements IProjectDataService { private setValue(projectDir: string, key: string, value: any): void { const projectFileInfo = this.getProjectFileData(projectDir); - const props = key.split("."); + const props = key.split(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); const data: any = projectFileInfo.projectData; let currentData = data; @@ -103,7 +104,7 @@ export class ProjectDataService implements IProjectDataService { const projectFileInfo = this.getProjectFileData(projectDir); const data: any = projectFileInfo.projectData; let currentData = data; - const props = propertyName.split("."); + const props = propertyName.split(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); const propertyToDelete = props.splice(props.length - 1, 1)[0]; _.each(props, (prop) => { diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index 71112f4abb..447dad0413 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -2,34 +2,40 @@ import { Yok } from "../../lib/common/yok"; import { assert } from "chai"; import { ProjectDataService } from "../../lib/services/project-data-service"; import { LoggerStub } from "../stubs"; +import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER } from '../../lib/constants'; const CLIENT_NAME_KEY_IN_PROJECT_FILE = "nativescript"; -const testData: any = [{ - "propertyValue": 1, - "propertyName": "root", - "description": "returns correct result when a single propertyName is passed and the value of it is number" -}, -{ - "propertyValue": "expectedData", - "propertyName": "root", - "description": "returns correct result when a single propertyName is passed and the value of it is string" -}, -{ - "propertyValue": "expectedData", - "propertyName": "root.prop1", - "description": "returns correct result when inner propertyName is passed and the value of it is string" -}, -{ - "propertyValue": 1234, - "propertyName": "root.prop1", - "description": "returns correct result when inner propertyName is passed and the value of it is number" -}, -{ - "propertyValue": "expectedData", - "propertyName": "root.prop1.prop2.prop3.prop4", - "description": "returns correct result when really inner propertyName is passed and the value of it is string" -} +const getPropertyName = (props: string[]): string => { + return props.join(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); +}; + +const testData: any = [ + { + "propertyValue": 1, + "propertyName": "root", + "description": "returns correct result when a single propertyName is passed and the value of it is number" + }, + { + "propertyValue": "expectedData", + "propertyName": "root", + "description": "returns correct result when a single propertyName is passed and the value of it is string" + }, + { + "propertyValue": "expectedData", + "propertyName": getPropertyName(["root", "prop1"]), + "description": "returns correct result when inner propertyName is passed and the value of it is string" + }, + { + "propertyValue": 1234, + "propertyName": getPropertyName(["root", "prop1"]), + "description": "returns correct result when inner propertyName is passed and the value of it is number" + }, + { + "propertyValue": "expectedData", + "propertyName": getPropertyName(["root", "prop1", "prop2", "prop3", "prop4"]), + "description": "returns correct result when really inner propertyName is passed and the value of it is string" + } ]; const createTestInjector = (readTextData?: string): IInjector => { @@ -60,7 +66,7 @@ const createTestInjector = (readTextData?: string): IInjector => { describe("projectDataService", () => { const generateJsonDataFromTestData = (currentTestData: any, skipNativeScriptKey?: boolean) => { - const props = currentTestData.propertyName.split("."); + const props = currentTestData.propertyName.split(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); const data: any = {}; let currentData: any = skipNativeScriptKey ? data : (data[CLIENT_NAME_KEY_IN_PROJECT_FILE] = {}); @@ -140,7 +146,7 @@ describe("projectDataService", () => { }; const projectDataService: IProjectDataService = testInjector.resolve("projectDataService"); - projectDataService.setNSValue("projectDir", "root.id", "2"); + projectDataService.setNSValue("projectDir", getPropertyName(["root", "id"]), "2"); const expectedData = _.cloneDeep(initialData); expectedData[CLIENT_NAME_KEY_IN_PROJECT_FILE].root.id = "2"; assert.isTrue(!!dataPassedToWriteJson[CLIENT_NAME_KEY_IN_PROJECT_FILE], "Data passed to write JSON must contain nativescript key."); @@ -154,7 +160,7 @@ describe("projectDataService", () => { describe("removeNSProperty", () => { const generateExpectedDataFromTestData = (currentTestData: any) => { - const props = currentTestData.propertyName.split("."); + const props = currentTestData.propertyName.split(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); props.splice(props.length - 1, 1); const data: any = {}; @@ -207,7 +213,7 @@ describe("projectDataService", () => { }; const projectDataService: IProjectDataService = testInjector.resolve("projectDataService"); - projectDataService.removeNSProperty("projectDir", "root.id"); + projectDataService.removeNSProperty("projectDir", getPropertyName(["root", "id"])); assert.deepEqual(dataPassedToWriteJson, { nativescript: { root: { constantItem: "myValue" } } }); }); }); @@ -215,7 +221,7 @@ describe("projectDataService", () => { describe("removeDependency", () => { it("removes specified dependency from project file", () => { const currentTestData = { - propertyName: "dependencies.myDeps", + propertyName: getPropertyName(["dependencies", "myDeps"]), propertyValue: "1.0.0" };