diff --git a/lib/common b/lib/common index 3f8ec96802..c539660037 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 3f8ec96802f3ed1b518e22dc7cd4c25a595ebc3e +Subproject commit c5396600371891c19088d022ce70b435e27324ce diff --git a/lib/config.ts b/lib/config.ts index 6ee2c069cc..e33d9173b6 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -21,7 +21,8 @@ export class Configuration extends configBaseLib.ConfigBase implements IConfigur $injector.register("config", Configuration); export class StaticConfig extends staticConfigBaseLibPath.StaticConfigBase implements IStaticConfig { - public PROJECT_FILE_NAME = ".tnsproject"; + public PROJECT_FILE_NAME = "package.json"; + public CLIENT_NAME_KEY_IN_PROJECT_FILE = "nativescript"; public CLIENT_NAME = "tns"; public CLIENT_NAME_ALIAS = "NativeScript"; public ANALYTICS_API_KEY = "5752dabccfc54c4ab82aea9626b7338e"; diff --git a/lib/project-data.ts b/lib/project-data.ts index ee60487504..365e74b612 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -1,10 +1,14 @@ /// "use strict"; +import constants = require("./constants"); import path = require("path"); import os = require("os"); +import options = require("./common/options"); export class ProjectData implements IProjectData { + private static OLD_PROJECT_FILE_NAME = ".tnsproject"; + public projectDir: string; public platformsDir: string; public projectFilePath: string; @@ -13,6 +17,7 @@ export class ProjectData implements IProjectData { constructor(private $fs: IFileSystem, private $errors: IErrors, + private $logger: ILogger, private $projectHelper: IProjectHelper, private $staticConfig: IStaticConfig) { this.initializeProjectData().wait(); @@ -20,18 +25,16 @@ export class ProjectData implements IProjectData { private initializeProjectData(): IFuture { return(() => { - var projectDir = this.$projectHelper.projectDir; + let projectDir = this.$projectHelper.projectDir; // If no project found, projectDir should be null if(projectDir) { - this.projectDir = projectDir; - this.projectName = this.$projectHelper.sanitizeName(path.basename(projectDir)); - this.platformsDir = path.join(projectDir, "platforms"); - this.projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); + this.initializeProjectDataCore(projectDir); + let data: any = null; if (this.$fs.exists(this.projectFilePath).wait()) { try { - var fileContent = this.$fs.readJson(this.projectFilePath).wait(); - this.projectId = fileContent.id; + let fileContent = this.$fs.readJson(this.projectFilePath).wait(); + data = fileContent[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]; } catch (err) { this.$errors.fail({formatStr: "The project file %s is corrupted." + os.EOL + "Consider restoring an earlier version from your source control or backup." + os.EOL + @@ -39,11 +42,62 @@ export class ProjectData implements IProjectData { suppressCommandHelp: true}, this.projectFilePath, err.toString()); } - } + + if(data) { + this.projectId = data.id; + } else { // This is the case when we have package.json file but nativescipt key is not presented in it + this.tryToUpgradeProject().wait(); + } + } + } else { // This is the case when no project file found + this.tryToUpgradeProject().wait(); + } + }).future()(); + } + + private throwNoProjectFoundError(): void { + this.$errors.fail("No project found at or above '%s' and neither was a --path specified.", options.path || path.resolve(".")); + } + + private tryToUpgradeProject(): IFuture { + return (() => { + let projectDir = this.projectDir || path.resolve(options.path || "."); + let oldProjectFilePath = path.join(projectDir, ProjectData.OLD_PROJECT_FILE_NAME); + if(this.$fs.exists(oldProjectFilePath).wait()) { + this.upgrade(projectDir, oldProjectFilePath).wait(); } else { - this.$errors.fail("No project found at or above '%s' and neither was a --path specified.", process.cwd()); + this.throwNoProjectFoundError(); } }).future()(); } + + private upgrade(projectDir: string, oldProjectFilePath: string): IFuture { + return (() => { + try { + let oldProjectData = this.$fs.readJson(oldProjectFilePath).wait(); + + let newProjectFilePath = this.projectFilePath || path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); + let newProjectData = this.$fs.exists(newProjectFilePath).wait() ? this.$fs.readJson(newProjectFilePath).wait() : {}; + newProjectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE] = oldProjectData; + this.$fs.writeJson(newProjectFilePath, newProjectData).wait(); + + this.$fs.deleteFile(oldProjectFilePath).wait(); + } catch(err) { + this.$logger.out("An error occurred while upgrading your project."); + throw err; + } + + this.initializeProjectDataCore(projectDir); + + this.$logger.out("Successfully upgraded your project file."); + }).future()(); + } + + private initializeProjectDataCore(projectDir: string): void { + this.projectDir = projectDir; + this.projectName = this.$projectHelper.sanitizeName(path.basename(projectDir)); + this.platformsDir = path.join(projectDir, "platforms"); + this.projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); + } } $injector.register("projectData", ProjectData); \ No newline at end of file diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index c99eb1d3ef..2f6a00a837 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,48 +1,54 @@ /// "use strict"; +import constants = require("./../constants"); import path = require("path"); import assert = require("assert"); export class ProjectDataService implements IProjectDataService { - private projectFileName: string; + private projectFilePath: string; private projectData: IDictionary; constructor(private $fs: IFileSystem, - private $staticConfig: IStaticConfig) { + private $staticConfig: IStaticConfig, + private $errors: IErrors, + private $logger: ILogger) { } public initialize(projectDir: string): void { - if(!this.projectFileName) { - this.projectFileName = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); + if(!this.projectFilePath) { + this.projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); } } public getValue(propertyName: string): IFuture { return (() => { this.loadProjectFile().wait(); - return this.projectData ? this.projectData[propertyName] : null; + return this.projectData ? this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][propertyName] : null; }).future()(); } public setValue(key: string, value: any): IFuture { return (() => { this.loadProjectFile().wait(); - this.projectData[key] = value; - this.$fs.writeJson(this.projectFileName, this.projectData, "\t").wait(); + if(!this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]) { + this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE] = Object.create(null); + } + this.projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][key] = value; + this.$fs.writeJson(this.projectFilePath, this.projectData, "\t").wait(); }).future()(); } private loadProjectFile(): IFuture { return (() => { - assert.ok(this.projectFileName, "Initialize method of projectDataService is not called"); + assert.ok(this.projectFilePath, "Initialize method of projectDataService is not called"); if(!this.projectData) { - if(!this.$fs.exists(this.projectFileName).wait()) { - this.$fs.writeFile(this.projectFileName, null).wait(); + if(!this.$fs.exists(this.projectFilePath).wait()) { + this.$fs.writeFile(this.projectFilePath, null).wait(); } - this.projectData = this.$fs.readJson(this.projectFileName).wait() || Object.create(null); + this.projectData = this.$fs.readJson(this.projectFilePath).wait() || Object.create(null); } }).future()(); } diff --git a/test/project-service.ts b/test/project-service.ts index 3042110ba1..27008d25ea 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -6,11 +6,13 @@ import stubs = require('./stubs'); import ProjectServiceLib = require("../lib/services/project-service"); import ProjectDataServiceLib = require("../lib/services/project-data-service"); +import ProjectDataLib = require("../lib/project-data"); import ProjectHelperLib = require("../lib/common/project-helper"); import StaticConfigLib = require("../lib/config"); import NpmLib = require("../lib/node-package-manager"); import HttpClientLib = require("../lib/common/http-client"); import fsLib = require("../lib/common/file-system"); +import platformServiceLib = require("../lib/services/platform-service"); import path = require("path"); import temp = require("temp"); @@ -64,7 +66,7 @@ class ProjectIntegrationTest { var projectDir = path.join(tempFolder, projectName); var appDirectoryPath = path.join(projectDir, "app"); var platformsDirectoryPath = path.join(projectDir, "platforms"); - var tnsProjectFilePath = path.join(projectDir, ".tnsproject"); + let tnsProjectFilePath = path.join(projectDir, "package.json"); assert.isTrue(fs.exists(appDirectoryPath).wait()); assert.isTrue(fs.exists(platformsDirectoryPath).wait()); @@ -73,7 +75,7 @@ class ProjectIntegrationTest { assert.isFalse(fs.isEmptyDir(appDirectoryPath).wait()); assert.isTrue(fs.isEmptyDir(platformsDirectoryPath).wait()); - var actualAppId = fs.readJson(tnsProjectFilePath).wait().id; + var actualAppId = fs.readJson(tnsProjectFilePath).wait()["nativescript"].id; var expectedAppId = appId; assert.equal(actualAppId, expectedAppId); @@ -139,4 +141,135 @@ describe("Project Service Tests", () => { projectIntegrationTest.assertProject(tempFolder, projectName, options.appid).wait(); }); }); -}); \ No newline at end of file +}); + +function createTestInjector() { + var testInjector = new yok.Yok(); + + testInjector.register("errors", stubs.ErrorsStub); + testInjector.register('logger', stubs.LoggerStub); + testInjector.register("projectService", ProjectServiceLib.ProjectService); + testInjector.register("projectHelper", ProjectHelperLib.ProjectHelper); + testInjector.register("projectTemplatesService", stubs.ProjectTemplatesService); + testInjector.register("projectNameValidator", mockProjectNameValidator); + + testInjector.register("fs", fsLib.FileSystem); + testInjector.register("projectDataService", ProjectDataServiceLib.ProjectDataService); + + testInjector.register("staticConfig", StaticConfigLib.StaticConfig); + + testInjector.register("npm", NpmLib.NodePackageManager); + testInjector.register("httpClient", HttpClientLib.HttpClient); + testInjector.register("config", {}); + testInjector.register("lockfile", stubs.LockFile); + + testInjector.register('projectData', ProjectDataLib.ProjectData); + + return testInjector; +} + +describe("project upgrade procedure tests", () => { + it("should throw error when no nativescript project folder specified", () => { + var testInjector = createTestInjector(); + var tempFolder = temp.mkdirSync("project upgrade"); + options.path = tempFolder; + var isErrorThrown = false; + + try { + testInjector.resolve("projectData"); // This should trigger upgrade procedure + } catch(err) { + isErrorThrown = true; + var expectedErrorMessage = "No project found at or above '%s' and neither was a --path specified.," + tempFolder; + assert.equal(expectedErrorMessage, err.toString()); + } + + assert.isTrue(isErrorThrown); + }); + it("should upgrade project when .tnsproject file exists but package.json file doesn't exist", () => { + var testInjector = createTestInjector(); + var fs: IFileSystem = testInjector.resolve("fs"); + + var tempFolder = temp.mkdirSync("projectUpgradeTest2"); + options.path = tempFolder; + var tnsProjectData = { + "id": "org.nativescript.Test", + "tns-ios": { + "version": "1.0.0" + } + }; + var tnsProjectFilePath = path.join(tempFolder, ".tnsproject"); + fs.writeJson(tnsProjectFilePath, tnsProjectData).wait(); + + testInjector.resolve("projectData"); // This should trigger upgrade procedure + + var packageJsonFilePath = path.join(tempFolder, "package.json"); + var packageJsonFileContent = require(packageJsonFilePath); + assert.isTrue(fs.exists(packageJsonFilePath).wait()); + assert.isFalse(fs.exists(tnsProjectFilePath).wait()); + assert.deepEqual(tnsProjectData, packageJsonFileContent["nativescript"]); + }); + it("should upgrade project when .tnsproject and package.json exist but nativescript key is not presented in package.json file", () => { + var testInjector = createTestInjector(); + var fs: IFileSystem = testInjector.resolve("fs"); + + var tempFolder = temp.mkdirSync("projectUpgradeTest3"); + options.path = tempFolder; + var tnsProjectData = { + "id": "org.nativescript.Test", + "tns-ios": { + "version": "1.0.1" + } + }; + var packageJsonData = { + "name": "testModuleName", + "version": "0.0.0", + "dependencies": { + "myFirstDep": "0.0.1" + } + } + let tnsProjectFilePath = path.join(tempFolder, ".tnsproject"); + fs.writeJson(tnsProjectFilePath, tnsProjectData).wait(); + + var packageJsonFilePath = path.join(tempFolder, "package.json"); + fs.writeJson(packageJsonFilePath, packageJsonData).wait(); + + testInjector.resolve("projectData"); // This should trigger upgrade procedure + + var packageJsonFileContent = require(packageJsonFilePath); + var expectedPackageJsonContent: any = packageJsonData; + expectedPackageJsonContent["nativescript"] = tnsProjectData; + assert.deepEqual(expectedPackageJsonContent, packageJsonFileContent); + }); + it("shouldn't upgrade project when .tnsproject and package.json exist and nativescript key is presented in package.json file", () => { + var testInjector = createTestInjector(); + var fs: IFileSystem = testInjector.resolve("fs"); + + var tempFolder = temp.mkdirSync("projectUpgradeTest4"); + options.path = tempFolder; + var tnsProjectData = { + + }; + var packageJsonData = { + "name": "testModuleName", + "version": "0.0.0", + "dependencies": { + "myFirstDep": "0.0.2" + }, + "nativescript": { + "id": "org.nativescript.Test", + "tns-ios": { + "version": "1.0.2" + } + } + } + + fs.writeJson(path.join(tempFolder, ".tnsproject"), tnsProjectData).wait(); + fs.writeJson(path.join(tempFolder, "package.json"), packageJsonData).wait(); + testInjector.resolve("projectData"); // This should trigger upgrade procedure + + var packageJsonFilePath = path.join(tempFolder, "package.json"); + var packageJsonFileContent = require(packageJsonFilePath); + + assert.deepEqual(packageJsonData, packageJsonFileContent); + }); +}); \ No newline at end of file