diff --git a/lib/declarations.ts b/lib/declarations.ts index 7227ac6110..90d29d2565 100644 --- a/lib/declarations.ts +++ b/lib/declarations.ts @@ -65,7 +65,6 @@ interface IOptions extends ICommonOptions { baseConfig: string; client: boolean; compileSdk: number; - copyFrom: string; copyTo: string; debugTransport: boolean; emulator: boolean; @@ -81,7 +80,6 @@ interface IOptions extends ICommonOptions { keyStoreAliasPassword: string; keyStorePassword: string; keyStorePath: string; - linkTo: string; ng: boolean; tsc: boolean; androidTypings: boolean; @@ -90,7 +88,6 @@ interface IOptions extends ICommonOptions { port: Number; production: boolean; //npm flag sdk: string; - tnsModulesVersion: string; teamId: string; syncAllFiles: boolean; liveEdit: boolean; diff --git a/lib/options.ts b/lib/options.ts index a8be7a7cb3..0ce53419ab 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -14,8 +14,6 @@ export class Options extends commonOptionsLibPath.OptionsBase { frameworkName: { type: OptionType.String }, framework: { type: OptionType.String }, frameworkVersion: { type: OptionType.String }, - copyFrom: { type: OptionType.String }, - linkTo: { type: OptionType.String }, forDevice: { type: OptionType.Boolean }, provision: { type: OptionType.Object }, client: { type: OptionType.Boolean, default: true }, @@ -27,7 +25,6 @@ export class Options extends commonOptionsLibPath.OptionsBase { keyStoreAliasPassword: { type: OptionType.String }, ignoreScripts: { type: OptionType.Boolean }, disableNpmInstall: { type: OptionType.Boolean }, - tnsModulesVersion: { type: OptionType.String }, compileSdk: { type: OptionType.Number }, port: { type: OptionType.Number }, copyTo: { type: OptionType.String }, diff --git a/lib/services/init-service.ts b/lib/services/init-service.ts index d82129871b..973ce986eb 100644 --- a/lib/services/init-service.ts +++ b/lib/services/init-service.ts @@ -65,7 +65,7 @@ export class InitService implements IInitService { // In case console is interactive and --force is not specified, do not read the version from package.json, show all available versions to the user. let tnsCoreModulesVersionInPackageJson = this.useDefaultValue ? projectData.dependencies[constants.TNS_CORE_MODULES_NAME] : null; - projectData.dependencies[constants.TNS_CORE_MODULES_NAME] = this.$options.tnsModulesVersion || tnsCoreModulesVersionInPackageJson || (await this.getVersionData(constants.TNS_CORE_MODULES_NAME))["version"]; + projectData.dependencies[constants.TNS_CORE_MODULES_NAME] = tnsCoreModulesVersionInPackageJson || (await this.getVersionData(constants.TNS_CORE_MODULES_NAME))["version"]; this.$fs.writeJson(this.projectFilePath, projectData); } catch (err) { diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts index 5a9fb74548..64437baea3 100644 --- a/lib/services/project-service.ts +++ b/lib/services/project-service.ts @@ -1,5 +1,4 @@ import * as constants from "../constants"; -import * as osenv from "osenv"; import * as path from "path"; import * as shelljs from "shelljs"; @@ -19,72 +18,37 @@ export class ProjectService implements IProjectService { if (!projectName) { this.$errors.fail("You must specify when creating a new project."); } - projectName = await this.$projectNameService.ensureValidName(projectName, { force: this.$options.force }); - let projectId = this.$options.appid || this.$projectHelper.generateDefaultAppId(projectName, constants.DEFAULT_APP_IDENTIFIER_PREFIX); - let projectDir = path.join(path.resolve(this.$options.path || "."), projectName); this.$fs.createDirectory(projectDir); if (this.$fs.exists(projectDir) && !this.$fs.isEmptyDir(projectDir)) { this.$errors.fail("Path already exists and is not empty %s", projectDir); } + let projectId = this.$options.appid || this.$projectHelper.generateDefaultAppId(projectName, constants.DEFAULT_APP_IDENTIFIER_PREFIX); this.createPackageJson(projectDir, projectId); - let customAppPath = this.getCustomAppPath(); - if (customAppPath) { - customAppPath = path.resolve(customAppPath); - if (!this.$fs.exists(customAppPath)) { - this.$errors.failWithoutHelp(`The specified path "${customAppPath}" doesn't exist. Check that you specified the path correctly and try again.`); - } - - let customAppContents = this.$fs.enumerateFilesInDirectorySync(customAppPath); - if (customAppContents.length === 0) { - this.$errors.failWithoutHelp(`The specified path "${customAppPath}" is empty directory.`); - } + this.$logger.trace(`Creating a new NativeScript project with name ${projectName} and id ${projectId} at location ${projectDir}`); + if (!selectedTemplate) { + selectedTemplate = constants.RESERVED_TEMPLATE_NAMES["default"]; } - this.$logger.trace("Creating a new NativeScript project with name %s and id %s at location %s", projectName, projectId, projectDir); - - let projectAppDirectory = path.join(projectDir, constants.APP_FOLDER_NAME); - let appPath: string = null; - if (customAppPath) { - this.$logger.trace("Using custom app from %s", customAppPath); - - // Make sure that the source app/ is not a direct ancestor of a target app/ - let relativePathFromSourceToTarget = path.relative(customAppPath, projectAppDirectory); - // path.relative returns second argument if the paths are located on different disks - // so in this case we don't need to make the check for direct ancestor - if (relativePathFromSourceToTarget !== projectAppDirectory) { - let doesRelativePathGoUpAtLeastOneDir = relativePathFromSourceToTarget.split(path.sep)[0] === ".."; - if (!doesRelativePathGoUpAtLeastOneDir) { - this.$errors.fail("Project dir %s must not be created at/inside the template used to create the project %s.", projectDir, customAppPath); - } - } - this.$logger.trace("Copying custom app into %s", projectAppDirectory); - appPath = customAppPath; - } else { + try { let templatePath = await this.$projectTemplatesService.prepareTemplate(selectedTemplate, projectDir); - this.$logger.trace(`Copying application from '${templatePath}' into '${projectAppDirectory}'.`); - let templatePackageJson = this.$fs.readJson(path.join(templatePath, "package.json")); - selectedTemplate = templatePackageJson.name; - appPath = templatePath; - } + await this.extractTemplate(projectDir, templatePath); - try { - //TODO: plamen5kov: move copy of template and npm uninstall in prepareTemplate logic - await this.createProjectCore(projectDir, appPath, projectId); - let templatePackageJsonData = this.getDataFromJson(appPath); + let packageName = constants.TNS_CORE_MODULES_NAME; + await this.$npm.install(packageName, projectDir, { save: true, "save-exact": true }); + + let templatePackageJsonData = this.getDataFromJson(templatePath); this.mergeProjectAndTemplateProperties(projectDir, templatePackageJsonData); //merging dependencies from template (dev && prod) this.removeMergedDependencies(projectDir, templatePackageJsonData); + await this.$npm.install(projectDir, projectDir, { "ignore-scripts": this.$options.ignoreScripts }); - selectedTemplate = selectedTemplate || ""; - let templateName = (constants.RESERVED_TEMPLATE_NAMES[selectedTemplate.toLowerCase()] || selectedTemplate/*user template*/) || constants.RESERVED_TEMPLATE_NAMES["default"]; - await this.$npm.uninstall(selectedTemplate, { save: true }, projectDir); - // TODO: plamen5kov: remove later (put only so tests pass (need to fix tests)) - this.$logger.trace(`Using NativeScript verified template: ${templateName} with version undefined.`); + let templatePackageJson = this.$fs.readJson(path.join(templatePath, "package.json")); + await this.$npm.uninstall(templatePackageJson.name, { save: true }, projectDir); } catch (err) { this.$fs.deleteDirectory(projectDir); throw err; @@ -103,6 +67,18 @@ export class ProjectService implements IProjectService { return null; } + private async extractTemplate(projectDir: string, realTemplatePath: string): Promise { + this.$fs.ensureDirectoryExists(projectDir); + + let appDestinationPath = path.join(projectDir, constants.APP_FOLDER_NAME); + this.$fs.createDirectory(appDestinationPath); + + this.$logger.trace(`Copying application from '${realTemplatePath}' into '${appDestinationPath}'.`); + shelljs.cp('-R', path.join(realTemplatePath, "*"), appDestinationPath); + + this.$fs.createDirectory(path.join(projectDir, "platforms")); + } + private removeMergedDependencies(projectDir: string, templatePackageJsonData: any): void { let extractedTemplatePackageJsonPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.PACKAGE_JSON_FILE_NAME); for (let key in templatePackageJsonData) { @@ -129,6 +105,8 @@ export class ProjectService implements IProjectService { } this.$logger.trace("New project package.json data: ", projectPackageJsonData); this.$fs.writeJson(projectPackageJsonPath, projectPackageJsonData); + } else { + this.$errors.failWithoutHelp(`Couldn't find package.json data in installed template`); } } @@ -147,42 +125,9 @@ export class ProjectService implements IProjectService { return sortedDeps; } - private async createProjectCore(projectDir: string, appSourcePath: string, projectId: string): Promise { - this.$fs.ensureDirectoryExists(projectDir); - - let appDestinationPath = path.join(projectDir, constants.APP_FOLDER_NAME); - this.$fs.createDirectory(appDestinationPath); - - shelljs.cp('-R', path.join(appSourcePath, "*"), appDestinationPath); - - this.$fs.createDirectory(path.join(projectDir, "platforms")); - - let tnsModulesVersion = this.$options.tnsModulesVersion; - let packageName = constants.TNS_CORE_MODULES_NAME; - if (tnsModulesVersion) { - packageName = `${packageName}@${tnsModulesVersion}`; - } - await this.$npm.install(packageName, projectDir, { save: true, "save-exact": true }); - } - private createPackageJson(projectDir: string, projectId: string): void { this.$projectDataService.initialize(projectDir); this.$projectDataService.setValue("id", projectId); } - - private getCustomAppPath(): string { - let customAppPath = this.$options.copyFrom || this.$options.linkTo; - if (customAppPath) { - if (customAppPath.indexOf("http://") === 0) { - this.$errors.fail("Only local paths for custom app are supported."); - } - - if (customAppPath.substr(0, 1) === '~') { - customAppPath = path.join(osenv.home(), customAppPath.substr(1)); - } - } - - return customAppPath; - } } $injector.register("projectService", ProjectService); diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts index 5e6896a297..ba5ac17456 100644 --- a/lib/services/project-templates-service.ts +++ b/lib/services/project-templates-service.ts @@ -5,37 +5,28 @@ temp.track(); export class ProjectTemplatesService implements IProjectTemplatesService { - public constructor(private $errors: IErrors, - private $fs: IFileSystem, + public constructor(private $fs: IFileSystem, private $logger: ILogger, private $npmInstallationManager: INpmInstallationManager) { } public async prepareTemplate(originalTemplateName: string, projectDir: string): Promise { let realTemplatePath: string; - if (originalTemplateName) { - // support @ syntax - let data = originalTemplateName.split("@"), - name = data[0], - version = data[1]; + // support @ syntax + let data = originalTemplateName.split("@"), + name = data[0], + version = data[1]; - if (constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()]) { - realTemplatePath = await this.prepareNativeScriptTemplate(constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()], version, projectDir); - } else { - // Use the original template name, specified by user as it may be case-sensitive. - realTemplatePath = await this.prepareNativeScriptTemplate(name, version, projectDir); - } + if (constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()]) { + realTemplatePath = await this.prepareNativeScriptTemplate(constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()], version, projectDir); } else { - realTemplatePath = await this.prepareNativeScriptTemplate(constants.RESERVED_TEMPLATE_NAMES["default"], null/*version*/, projectDir); + // Use the original template name, specified by user as it may be case-sensitive. + realTemplatePath = await this.prepareNativeScriptTemplate(originalTemplateName, version, projectDir); } - if (realTemplatePath) { - //this removes dependencies from templates so they are not copied to app folder - this.$fs.deleteDirectory(path.join(realTemplatePath, constants.NODE_MODULES_FOLDER_NAME)); - return realTemplatePath; - } + //this removes dependencies from templates so they are not copied to app folder + this.$fs.deleteDirectory(path.join(realTemplatePath, constants.NODE_MODULES_FOLDER_NAME)); - this.$errors.failWithoutHelp("Unable to find the template in temp directory. " + - `Please open an issue at https://github.com/NativeScript/nativescript-cli/issues and send the output of the same command executed with --log trace.`); + return realTemplatePath; } /** diff --git a/test/project-service.ts b/test/project-service.ts index ba8a7ead66..6f9f0d5264 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -39,6 +39,9 @@ class ProjectIntegrationTest { public async createProject(projectName: string, template?: string): Promise { let projectService = this.testInjector.resolve("projectService"); + if (!template) { + template = constants.RESERVED_TEMPLATE_NAMES["default"]; + } return projectService.createProject(projectName, template); } @@ -67,7 +70,7 @@ class ProjectIntegrationTest { let tnsCoreModulesRecord = packageJsonContent["dependencies"][constants.TNS_CORE_MODULES_NAME]; assert.isTrue(tnsCoreModulesRecord !== null); - let sourceDir = projectSourceDirectory || options.copyFrom; + let sourceDir = projectSourceDirectory || options.template; let expectedFiles = fs.enumerateFilesInDirectorySync(sourceDir); let actualFiles = fs.enumerateFilesInDirectorySync(appDirectoryPath); @@ -436,7 +439,7 @@ describe("Project Service Tests", () => { options.path = tempFolder; await projectIntegrationTest.createProject(projectName); - options.copyFrom = defaultTemplatePath; + options.template = defaultTemplatePath; await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, null); }); diff --git a/test/project-templates-service.ts b/test/project-templates-service.ts index edd38ded32..ff7589b4bb 100644 --- a/test/project-templates-service.ts +++ b/test/project-templates-service.ts @@ -4,6 +4,7 @@ import { ProjectTemplatesService } from "../lib/services/project-templates-servi import { assert } from "chai"; import * as path from "path"; import temp = require("temp"); +import * as constants from "../lib/constants"; let isDeleteDirectoryCalledForNodeModulesDir = false; let nativeScriptValidatedTemplatePath = "nsValidatedTemplatePath"; @@ -92,7 +93,7 @@ describe("project-templates-service", () => { testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); let tempFolder = temp.mkdirSync("preparetemplate"); - let actualPathToTemplate = await projectTemplatesService.prepareTemplate(undefined, tempFolder); + let actualPathToTemplate = await projectTemplatesService.prepareTemplate(constants.RESERVED_TEMPLATE_NAMES["default"], tempFolder); assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath); assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted."); });