Skip to content

Commit a39cf09

Browse files
Fix creating multiple projects in a single process (#2573)
In case the CLI is `required` as a library (used in a long living process, not as a command-line interface), trying to create a project multiple times leads ot error. The problem is in the `projectDataService` which caches the path to first created project file (package.json) and uses it for all consecutive operations. In other cases, when CLI had to use the projectDataService, it always had to call `projectDataService.initialize` method. Instead of relying on a magically called initialize method, modify the service methods to work with any project dir and execute the required operation for it. This will fix the creation of multiple projects as all operations will work with currently passed project directory. Also remove the code that creates a `package.json` file during `loadProjectFile` in projectDataService - it's required only when a project is created, so move it there. Rename the methods getValue, setValue and removeProperty to getNSValue, setNSValue, removeNSProperty as they are intended to modify the nativescript key in package.json. We can later expose getValue, setValue and removeProperty (they are currently private). Remove the indentation detection as `$fs.writeJson` file already does this.
1 parent 587b816 commit a39cf09

13 files changed

+357
-92
lines changed

lib/commands/install.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ export class InstallCommand implements ICommand {
2323

2424
await this.$pluginsService.ensureAllDependenciesAreInstalled();
2525

26-
this.$projectDataService.initialize(this.$projectData.projectDir);
2726
for (let platform of this.$platformsData.platformsNames) {
2827
let platformData = this.$platformsData.getPlatformData(platform);
29-
let frameworkPackageData = this.$projectDataService.getValue(platformData.frameworkPackageName);
28+
const frameworkPackageData = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName);
3029
if (frameworkPackageData && frameworkPackageData.version) {
3130
try {
3231
await this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`]);

lib/commands/update.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,12 @@ export class UpdateCommand implements ICommand {
6060
let availablePlatforms = this.$platformService.getAvailablePlatforms();
6161
let packagePlatforms: string[] = [];
6262

63-
this.$projectDataService.initialize(this.$projectData.projectDir);
6463
for (let platform of availablePlatforms) {
6564
let platformData = this.$platformsData.getPlatformData(platform);
66-
let platformVersion = this.$projectDataService.getValue(platformData.frameworkPackageName);
65+
const platformVersion = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName);
6766
if (platformVersion) {
6867
packagePlatforms.push(platform);
69-
this.$projectDataService.removeProperty(platformData.frameworkPackageName);
68+
this.$projectDataService.removeNSProperty(this.$projectData.projectDir, platformData.frameworkPackageName);
7069
}
7170
}
7271

lib/definitions/project.d.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -60,36 +60,38 @@ interface IProjectData {
6060
}
6161

6262
interface IProjectDataService {
63-
initialize(projectDir: string): void;
64-
6563
/**
6664
* Returns a value from `nativescript` key in project's package.json.
65+
* @param {string} projectDir The project directory - the place where the root package.json is located.
6766
* @param {string} propertyName The name of the property to be checked in `nativescript` key.
6867
* @returns {any} The value of the property.
6968
*/
70-
getValue(propertyName: string): any;
69+
getNSValue(projectDir: string, propertyName: string): any;
7170

7271
/**
7372
* Sets a value in the `nativescript` key in a project's package.json.
73+
* @param {string} projectDir The project directory - the place where the root package.json is located.
7474
* @param {string} key Key to be added to `nativescript` key in project's package.json.
7575
* @param {any} value Value of the key to be added to `nativescript` key in project's package.json.
7676
* @returns {void}
7777
*/
78-
setValue(key: string, value: any): void;
78+
setNSValue(projectDir: string, key: string, value: any): void;
7979

8080
/**
8181
* Removes a property from `nativescript` key in project's package.json.
82+
* @param {string} projectDir The project directory - the place where the root package.json is located.
8283
* @param {string} propertyName The name of the property to be removed from `nativescript` key.
8384
* @returns {void}
8485
*/
85-
removeProperty(propertyName: string): void;
86+
removeNSProperty(projectDir: string, propertyName: string): void;
8687

8788
/**
8889
* Removes dependency from package.json
90+
* @param {string} projectDir The project directory - the place where the root package.json is located.
8991
* @param {string} dependencyName Name of the dependency that has to be removed.
9092
* @returns {void}
9193
*/
92-
removeDependency(dependencyName: string): void;
94+
removeDependency(projectDir: string, dependencyName: string): void;
9395
}
9496

9597
/**

lib/services/android-project-service.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
426426
private canUseGradle(frameworkVersion?: string): boolean {
427427
if (!this._canUseGradle) {
428428
if (!frameworkVersion) {
429-
this.$projectDataService.initialize(this.$projectData.projectDir);
430-
let frameworkInfoInProjectFile = this.$projectDataService.getValue(this.platformData.frameworkPackageName);
429+
const frameworkInfoInProjectFile = this.$projectDataService.getNSValue(this.$projectData.projectDir, this.platformData.frameworkPackageName);
431430
frameworkVersion = frameworkInfoInProjectFile && frameworkInfoInProjectFile.version;
432431
}
433432

lib/services/livesync/livesync-service.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ class LiveSyncService implements ILiveSyncService {
2626
private $processService: IProcessService) { }
2727

2828
private async ensureAndroidFrameworkVersion(platformData: IPlatformData): Promise<void> { // TODO: this can be moved inside command or canExecute function
29-
this.$projectDataService.initialize(this.$projectData.projectDir);
30-
let frameworkVersion = this.$projectDataService.getValue(platformData.frameworkPackageName).version;
29+
const frameworkVersion = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName).version;
3130

3231
if (platformData.normalizedPlatformName.toLowerCase() === this.$devicePlatformsConstants.Android.toLowerCase()) {
3332
if (semver.lt(frameworkVersion, "1.2.1")) {

lib/services/platform-project-service-base.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ export class PlatformProjectServiceBase implements IPlatformProjectServiceBase {
2323
}
2424

2525
protected getFrameworkVersion(runtimePackageName: string): string {
26-
this.$projectDataService.initialize(this.$projectData.projectDir);
27-
let frameworkVersion = this.$projectDataService.getValue(runtimePackageName).version;
28-
return frameworkVersion;
26+
return this.$projectDataService.getNSValue(this.$projectData.projectDir, runtimePackageName).version;
2927
}
3028
}

lib/services/platform-service.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ export class PlatformService implements IPlatformService {
116116
let installedVersion = coreModuleData.version;
117117
let coreModuleName = coreModuleData.name;
118118

119-
this.$projectDataService.initialize(this.$projectData.projectDir);
120119
let customTemplateOptions = await this.getPathToPlatformTemplate(this.$options.platformTemplate, platformData.frameworkPackageName);
121120
let pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate;
122121
await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, pathToTemplate);
@@ -130,7 +129,8 @@ export class PlatformService implements IPlatformService {
130129
if (customTemplateOptions) {
131130
frameworkPackageNameData.template = customTemplateOptions.selectedTemplate;
132131
}
133-
this.$projectDataService.setValue(platformData.frameworkPackageName, frameworkPackageNameData);
132+
133+
this.$projectDataService.setNSValue(this.$projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData);
134134

135135
return coreModuleName;
136136

@@ -140,7 +140,7 @@ export class PlatformService implements IPlatformService {
140140
if (!selectedTemplate) {
141141
// read data from package.json's nativescript key
142142
// check the nativescript.tns-<platform>.template value
143-
let nativescriptPlatformData = this.$projectDataService.getValue(frameworkPackageName);
143+
const nativescriptPlatformData = this.$projectDataService.getNSValue(this.$projectData.projectDir, frameworkPackageName);
144144
selectedTemplate = nativescriptPlatformData && nativescriptPlatformData.template;
145145
}
146146

@@ -582,8 +582,6 @@ export class PlatformService implements IPlatformService {
582582
}
583583

584584
public async removePlatforms(platforms: string[]): Promise<void> {
585-
this.$projectDataService.initialize(this.$projectData.projectDir);
586-
587585
for (let platform of platforms) {
588586
this.validatePlatformInstalled(platform);
589587
let platformData = this.$platformsData.getPlatformData(platform);
@@ -592,7 +590,7 @@ export class PlatformService implements IPlatformService {
592590

593591
let platformDir = path.join(this.$projectData.platformsDir, platform);
594592
this.$fs.deleteDirectory(platformDir);
595-
this.$projectDataService.removeProperty(platformData.frameworkPackageName);
593+
this.$projectDataService.removeNSProperty(this.$projectData.projectDir, platformData.frameworkPackageName);
596594

597595
this.$logger.out(`Platform ${platform} successfully removed.`);
598596
}
@@ -724,8 +722,7 @@ export class PlatformService implements IPlatformService {
724722
private async updatePlatform(platform: string, version: string): Promise<void> {
725723
let platformData = this.$platformsData.getPlatformData(platform);
726724

727-
this.$projectDataService.initialize(this.$projectData.projectDir);
728-
let data = this.$projectDataService.getValue(platformData.frameworkPackageName);
725+
let data = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName);
729726
let currentVersion = data && data.version ? data.version : "0.2.0";
730727

731728
let newVersion = version === constants.PackageVersion.NEXT ?

lib/services/plugin-variables-service.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ export class PluginVariablesService implements IPluginVariablesService {
2323
});
2424

2525
if (!_.isEmpty(values)) {
26-
this.$projectDataService.initialize(this.$projectData.projectDir);
27-
this.$projectDataService.setValue(this.getPluginVariablePropertyName(pluginData.name), values);
26+
this.$projectDataService.setNSValue(this.$projectData.projectDir, this.getPluginVariablePropertyName(pluginData.name), values);
2827
}
2928
}
3029

3130
public removePluginVariablesFromProjectFile(pluginName: string): void {
32-
this.$projectDataService.initialize(this.$projectData.projectDir);
33-
this.$projectDataService.removeProperty(this.getPluginVariablePropertyName(pluginName));
31+
this.$projectDataService.removeNSProperty(this.$projectData.projectDir, this.getPluginVariablePropertyName(pluginName));
3432
}
3533

3634
public async interpolatePluginVariables(pluginData: IPluginData, pluginConfigurationFilePath: string): Promise<void> {
@@ -97,8 +95,7 @@ export class PluginVariablesService implements IPluginVariablesService {
9795

9896
variableData.name = pluginVariableName;
9997

100-
this.$projectDataService.initialize(this.$projectData.projectDir);
101-
let pluginVariableValues = this.$projectDataService.getValue(this.getPluginVariablePropertyName(pluginData.name));
98+
const pluginVariableValues = this.$projectDataService.getNSValue(this.$projectData.projectDir, this.getPluginVariablePropertyName(pluginData.name));
10299
variableData.value = pluginVariableValues ? pluginVariableValues[pluginVariableName] : undefined;
103100

104101
return variableData;

lib/services/plugins-service.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ export class PluginsService implements IPluginsService {
5656
await this.$pluginVariablesService.savePluginVariablesInProjectFile(pluginData);
5757
} catch (err) {
5858
// Revert package.json
59-
this.$projectDataService.initialize(this.$projectData.projectDir);
60-
this.$projectDataService.removeProperty(this.$pluginVariablesService.getPluginVariablePropertyName(pluginData.name));
59+
this.$projectDataService.removeNSProperty(this.$projectData.projectDir, this.$pluginVariablesService.getPluginVariablePropertyName(pluginData.name));
6160
await this.$npm.uninstall(plugin, PluginsService.NPM_CONFIG, this.$projectData.projectDir);
6261

6362
throw err;
@@ -275,8 +274,7 @@ export class PluginsService implements IPluginsService {
275274

276275
private getInstalledFrameworkVersion(platform: string): string {
277276
let platformData = this.$platformsData.getPlatformData(platform);
278-
this.$projectDataService.initialize(this.$projectData.projectDir);
279-
let frameworkData = this.$projectDataService.getValue(platformData.frameworkPackageName);
277+
const frameworkData = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName);
280278
return frameworkData.version;
281279
}
282280

lib/services/project-data-service.ts

+81-47
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,109 @@
11
import * as path from "path";
2-
import * as assert from "assert";
2+
3+
interface IProjectFileData {
4+
projectData: any;
5+
projectFilePath: string;
6+
}
37

48
export class ProjectDataService implements IProjectDataService {
59
private static DEPENDENCIES_KEY_NAME = "dependencies";
610

7-
private projectFilePath: string;
8-
private projectData: IDictionary<any>;
9-
private projectFileIndent: string;
10-
1111
constructor(private $fs: IFileSystem,
12-
private $staticConfig: IStaticConfig) {
12+
private $staticConfig: IStaticConfig,
13+
private $logger: ILogger) {
1314
}
1415

15-
public initialize(projectDir: string): void {
16-
if (!this.projectFilePath) {
17-
this.projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME);
18-
}
16+
public getNSValue(projectDir: string, propertyName: string): any {
17+
return this.getValue(projectDir, this.getNativeScriptPropertyName(propertyName));
1918
}
2019

21-
public getValue(propertyName: string): any {
22-
this.loadProjectFile();
23-
return this.projectData ? this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][propertyName] : null;
20+
public setNSValue(projectDir: string, key: string, value: any): void {
21+
this.setValue(projectDir, this.getNativeScriptPropertyName(key), value);
2422
}
2523

26-
public setValue(key: string, value: any): void {
27-
this.loadProjectFile();
28-
if (!this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]) {
29-
this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE] = Object.create(null);
30-
}
31-
this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][key] = value;
32-
this.$fs.writeJson(this.projectFilePath, this.projectData, this.projectFileIndent);
24+
public removeNSProperty(projectDir: string, propertyName: string): void {
25+
this.removeProperty(projectDir, this.getNativeScriptPropertyName(propertyName));
3326
}
3427

35-
public removeProperty(propertyName: string): void {
36-
this.loadProjectFile();
37-
delete this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][propertyName];
38-
this.$fs.writeJson(this.projectFilePath, this.projectData, this.projectFileIndent);
28+
public removeDependency(projectDir: string, dependencyName: string): void {
29+
const projectFileInfo = this.getProjectFileData(projectDir);
30+
delete projectFileInfo.projectData[ProjectDataService.DEPENDENCIES_KEY_NAME][dependencyName];
31+
this.$fs.writeJson(projectFileInfo.projectFilePath, projectFileInfo.projectData);
32+
}
33+
34+
private getValue(projectDir: string, propertyName: string): any {
35+
const projectData = this.getProjectFileData(projectDir).projectData;
36+
37+
if (projectData) {
38+
try {
39+
return this.getPropertyValueFromJson(projectData, propertyName);
40+
} catch (err) {
41+
this.$logger.trace(`Error while trying to get property ${propertyName} from ${projectDir}. Error is:`, err);
42+
}
43+
}
44+
45+
return null;
3946
}
4047

41-
public removeDependency(dependencyName: string): void {
42-
this.loadProjectFile();
43-
delete this.projectData[ProjectDataService.DEPENDENCIES_KEY_NAME][dependencyName];
44-
this.$fs.writeJson(this.projectFilePath, this.projectData, this.projectFileIndent);
48+
private getNativeScriptPropertyName(propertyName: string) {
49+
return `${this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE}.${propertyName}`;
4550
}
4651

47-
private loadProjectFile(): void {
48-
assert.ok(this.projectFilePath, "Initialize method of projectDataService is not called.");
52+
private getPropertyValueFromJson(jsonData: any, dottedPropertyName: string): any {
53+
const props = dottedPropertyName.split(".");
54+
let result = jsonData[props.shift()];
4955

50-
if (!this.$fs.exists(this.projectFilePath)) {
51-
this.$fs.writeJson(this.projectFilePath, {
52-
"description": "NativeScript Application",
53-
"license": "SEE LICENSE IN <your-license-filename>",
54-
"readme": "NativeScript Application",
55-
"repository": "<fill-your-repository-here>"
56-
});
56+
for (let prop of props) {
57+
result = result[prop];
5758
}
5859

59-
// Detect indent and use it later to write JSON.
60-
let projectFileContent = this.$fs.readText(this.projectFilePath);
60+
return result;
61+
}
62+
63+
private setValue(projectDir: string, key: string, value: any): void {
64+
const projectFileInfo = this.getProjectFileData(projectDir);
65+
66+
const props = key.split(".");
67+
let data: any = projectFileInfo.projectData;
68+
let currentData = data;
6169

62-
this.projectFileIndent = projectFileContent ? this.detectIndent(projectFileContent) : "\t";
70+
_.each(props, (prop, index: number) => {
71+
if (index === (props.length - 1)) {
72+
currentData[prop] = value;
73+
} else {
74+
currentData[prop] = currentData[prop] || Object.create(null);
75+
}
6376

64-
this.projectData = projectFileContent ? JSON.parse(projectFileContent) : Object.create(null);
77+
currentData = currentData[prop];
78+
});
79+
80+
this.$fs.writeJson(projectFileInfo.projectFilePath, data);
6581
}
6682

67-
private detectIndent(content: string): any {
68-
const leadingSpace = content.match(/(^[ ]+)\S/m);
69-
if (leadingSpace) {
70-
return leadingSpace[1].length;
71-
}
72-
return "\t";
83+
private removeProperty(projectDir: string, propertyName: string): void {
84+
const projectFileInfo = this.getProjectFileData(projectDir);
85+
let data: any = projectFileInfo.projectData;
86+
let currentData = data;
87+
const props = propertyName.split(".");
88+
const propertyToDelete = props.splice(props.length - 1, 1)[0];
89+
90+
_.each(props, (prop) => {
91+
currentData = currentData[prop];
92+
});
93+
94+
delete currentData[propertyToDelete];
95+
this.$fs.writeJson(projectFileInfo.projectFilePath, data);
96+
}
97+
98+
private getProjectFileData(projectDir: string): IProjectFileData {
99+
const projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME);
100+
const projectFileContent = this.$fs.readText(projectFilePath);
101+
const projectData = projectFileContent ? JSON.parse(projectFileContent) : Object.create(null);
102+
103+
return {
104+
projectData,
105+
projectFilePath
106+
};
73107
}
74108
}
75109
$injector.register("projectDataService", ProjectDataService);

0 commit comments

Comments
 (0)