From 2ce4b751ae3999793f999757c895a33c8ff6c0ac Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Sat, 20 Feb 2016 16:30:39 +0200 Subject: [PATCH] Add support for custom platform templates Add new option when adding platform: `--platform-template`. When it is used, CLI will use the specified template instead of the default template from the runtime. The path to the specified template will be saved in project's package.json, so next time when platform is added, the same template will be used. In case when `--platform-template` is not passed, CLI will check the package.json and if there's value for the template, it will be used. Otherwise the default template from runtime will be used. --- .../project/configuration/platform-add.md | 24 +++++---- lib/declarations.ts | 1 + lib/definitions/project.d.ts | 2 +- lib/nativescript-cli.ts | 1 + lib/options.ts | 3 +- lib/services/android-project-service.ts | 10 +++- lib/services/ios-project-service.ts | 11 ++-- lib/services/platform-service.ts | 50 +++++++++++++++++-- test/platform-commands.ts | 1 + test/platform-service.ts | 1 + 10 files changed, 82 insertions(+), 22 deletions(-) diff --git a/docs/man_pages/project/configuration/platform-add.md b/docs/man_pages/project/configuration/platform-add.md index 8e7458f310..470a245ef3 100644 --- a/docs/man_pages/project/configuration/platform-add.md +++ b/docs/man_pages/project/configuration/platform-add.md @@ -3,26 +3,28 @@ platform add Usage | Synopsis ------|------- -Android latest runtime | `$ tns platform add android [--frameworkPath ] [--symlink] [--sdk ]` -Android selected runtime | `$ tns platform add android[@] [--frameworkPath ] [--symlink] [--sdk ]` -<% if (isMacOS) { %>iOS latest runtime | `$ tns platform add ios [--frameworkPath ] [--symlink]` -iOS selected runtime | `$ tns platform add ios[@] [--frameworkPath ] [--symlink]`<% } %> +Android latest runtime | `$ tns platform add android [--framework-path ] [--symlink] [--sdk ] [--platform-template ]` +Android selected runtime | `$ tns platform add android[@] [--framework-path ] [--symlink] [--sdk ] [--platform-template ]` +<% if (isMacOS) { %>iOS latest runtime | `$ tns platform add ios [--framework-path ] [--symlink]` +iOS selected runtime | `$ tns platform add ios[@] [--framework-path ] [--symlink] [--platform-template ]`<% } %> -Configures the current project to target the selected platform. <% if(isHtml) { %>When you add a target platform, the NativeScript CLI adds a corresponding platform-specific subdirectory under the platforms directory. This platform-specific directory contains the necessary files to let you build your project for the target platform.<% } %> +Configures the current project to target the selected platform. <% if(isHtml) { %>When you add a target platform, the NativeScript CLI creates a corresponding platform-specific subdirectory under the platforms directory. This platform-specific directory contains the necessary files to let you build your project for the target platform.<% } %> ### Options -* `--frameworkPath` - Sets the path to a NativeScript runtime for the specified platform that you want to use instead of the default runtime. If `--symlink` is specified, `` must point to directory in which the runtime is already extracted. If `--symlink` is not specified, `` must point to a valid npm package. +* `--framework-path` - Sets the path to a NativeScript runtime for the specified platform that you want to use instead of the default runtime. If `--symlink` is specified, `` must point to directory in which the runtime is already extracted. If `--symlink` is not specified, `` must point to a valid npm package. * `--symlink` - Creates a symlink to a NativeScript runtime for the specified platform that you want to use instead of the default runtime. If `--frameworkPath` is specified, creates a symlink to the specified directory. If `--frameworkPath` is not specified, creates a symlink to platform runtime installed with your current version of NativeScript. * `--sdk` - Sets the target Android SDK for the project. +* `--platform-template` - Sets the platform template that will be used for the native application. ### Attributes * `` is a valid Android API level. For example: 17, 19, MNC.<% if(isHtml) { %> For a complete list of the Android API levels and their corresponding Android versions, click [here](http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#platform).<% } %> * `` is the complete path to a valid npm package or a directory that contains a NativeScript runtime for the selected platform. -* `` is any available version of the respective platform runtime published in npm. <% if(isHtml) { %>If `@` is not specified, the NativeScript CLI installs the latest stable runtime for the selected platform. -To list all available versions for Android, run `$ npm view tns-android versions` -To list only experimental versions for Android, run `$ npm view tns-android dist-tags` -To list all available versions for iOS, run `$ npm view tns-ios versions` -To list only experimental versions for iOS, run `$ npm view tns-ios dist-tags` +* `` is a valid npm package, path to directory, .tgz or GitHub URL that contains a native Android or iOS template. +* `` is any available version of the respective platform runtime published in npm. <% if(isHtml) { %>If `@` is not specified, the NativeScript CLI installs the latest stable runtime for the selected platform. +To list all available versions for Android, run `$ npm view tns-android versions` +To list only experimental versions for Android, run `$ npm view tns-android dist-tags` +To list all available versions for iOS, run `$ npm view tns-ios versions` +To list only experimental versions for iOS, run `$ npm view tns-ios dist-tags` ### Command Limitations diff --git a/lib/declarations.ts b/lib/declarations.ts index 581830a2be..9fab8767ca 100644 --- a/lib/declarations.ts +++ b/lib/declarations.ts @@ -83,6 +83,7 @@ interface IOptions extends ICommonOptions { port: Number; copyTo: string; baseConfig: string; + platformTemplate: string; } interface IInitService { diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index f282b8ad1b..563a47cb65 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -69,7 +69,7 @@ interface IiOSBuildConfig extends IBuildConfig { interface IPlatformProjectService { platformData: IPlatformData; validate(): IFuture; - createProject(frameworkDir: string, frameworkVersion: string): IFuture; + createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture; interpolateData(): IFuture; interpolateConfigurationFile(configurationFilePath?: string): IFuture; afterCreateProject(projectRoot: string): IFuture; diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts index a06792c38f..3e7d989aee 100644 --- a/lib/nativescript-cli.ts +++ b/lib/nativescript-cli.ts @@ -9,6 +9,7 @@ import * as fiber from "fibers"; import Future = require("fibers/future"); import * as shelljs from "shelljs"; shelljs.config.silent = true; +shelljs.config.fatal = true; import {installUncaughtExceptionListener} from "./common/errors"; installUncaughtExceptionListener(process.exit); diff --git a/lib/options.ts b/lib/options.ts index 26b8857c29..2b9311b4b1 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -34,7 +34,8 @@ export class Options extends commonOptionsLibPath.OptionsBase { compileSdk: {type: OptionType.Number }, port: { type: OptionType.Number }, copyTo: { type: OptionType.String }, - baseConfig: { type: OptionType.String } + baseConfig: { type: OptionType.String }, + platformTemplate: { type: OptionType.String } }, path.join($hostInfo.isWindows ? process.env.AppData : path.join(osenv.home(), ".local/share"), ".nativescript-cli"), $errors, $staticConfig); diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index ebd521ed9f..b5aa5d3b2a 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -92,7 +92,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject }).future()(); } - public createProject(frameworkDir: string, frameworkVersion: string): IFuture { + public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture { return (() => { if(semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) { this.$errors.failWithoutHelp(`The NativeScript CLI requires Android runtime ${AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE} or later to work properly.`); @@ -111,7 +111,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } // These files and directories should not be symlinked as CLI is modifying them and we'll change the original values as well. - this.copy(this.platformData.projectRoot, frameworkDir, "src", "-R"); + if(pathToTemplate) { + let mainPath = path.join(this.platformData.projectRoot, "src", "main"); + this.$fs.createDirectory(mainPath).wait(); + shell.cp("-R", path.join(path.resolve(pathToTemplate), "*"), mainPath); + } else { + this.copy(this.platformData.projectRoot, frameworkDir, "src", "-R"); + } this.copy(this.platformData.projectRoot, frameworkDir, "build.gradle settings.gradle gradle.properties", "-f"); if (this.useGradleWrapper(frameworkDir)) { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 6489528c74..345535ae5b 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -104,10 +104,16 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ }).future()(); } - public createProject(frameworkDir: string, frameworkVersion: string): IFuture { + public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture { return (() => { this.$fs.ensureDirectoryExists(path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)).wait(); - if(this.$options.symlink) { + if(pathToTemplate) { + // Copy everything except the template from the runtime + this.$fs.readDirectory(frameworkDir).wait() + .filter(dirName => dirName.indexOf(IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER) === -1) + .forEach(dirName => shell.cp("-R", path.join(frameworkDir, dirName), this.platformData.projectRoot)); + shell.cp("-rf", path.join(pathToTemplate, "*"), this.platformData.projectRoot); + } else if(this.$options.symlink) { let xcodeProjectName = util.format("%s.xcodeproj", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER); shell.cp("-R", path.join(frameworkDir, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, "*"), path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)); @@ -118,7 +124,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ _.each(frameworkFiles, (file: string) => { this.$fs.symlink(path.join(frameworkDir, file), path.join(this.platformData.projectRoot, file)).wait(); }); - } else { shell.cp("-R", path.join(frameworkDir, "*"), this.platformData.projectRoot); } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 1f89838719..c673395bf9 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -8,6 +8,8 @@ import * as helpers from "../common/helpers"; import * as semver from "semver"; import * as minimatch from "minimatch"; import Future = require("fibers/future"); +import * as temp from "temp"; +temp.track(); let clui = require("clui"); export class PlatformService implements IPlatformService { @@ -29,7 +31,8 @@ export class PlatformService implements IPlatformService { private $projectFilesManager: IProjectFilesManager, private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, - private $xmlValidator: IXmlValidator) { } + private $xmlValidator: IXmlValidator, + private $npm: INodePackageManager) { } public addPlatforms(platforms: string[]): IFuture { return (() => { @@ -114,7 +117,10 @@ export class PlatformService implements IPlatformService { } let sourceFrameworkDir = isFrameworkPathDirectory && this.$options.symlink ? path.join(this.$options.frameworkPath, "framework") : frameworkDir; - platformData.platformProjectService.createProject(path.resolve(sourceFrameworkDir), installedVersion).wait(); + this.$projectDataService.initialize(this.$projectData.projectDir); + let customTemplateOptions = this.getPathToPlatformTemplate(this.$options.platformTemplate, platformData.frameworkPackageName).wait(); + let pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; + platformData.platformProjectService.createProject(path.resolve(sourceFrameworkDir), installedVersion, pathToTemplate).wait(); if(isFrameworkPathDirectory || isFrameworkPathNotSymlinkedFile) { // Need to remove unneeded node_modules folder @@ -132,12 +138,48 @@ export class PlatformService implements IPlatformService { this.$fs.copyFile(newConfigFile, platformData.configurationFilePath).wait(); } - this.$projectDataService.initialize(this.$projectData.projectDir); - this.$projectDataService.setValue(platformData.frameworkPackageName, {version: installedVersion}).wait(); + let frameworkPackageNameData: any = {version: installedVersion}; + if(customTemplateOptions) { + frameworkPackageNameData.template = customTemplateOptions.selectedTemplate; + } + this.$projectDataService.setValue(platformData.frameworkPackageName, frameworkPackageNameData).wait(); }).future()(); } + private getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string): IFuture { + return (() => { + if(!selectedTemplate) { + // read data from package.json's nativescript key + // check the nativescript.tns-.template value + let nativescriptPlatformData = this.$projectDataService.getValue(frameworkPackageName).wait(); + selectedTemplate = nativescriptPlatformData && nativescriptPlatformData.template; + } + + if(selectedTemplate) { + let tempDir = temp.mkdirSync("platform-template"); + try { + /* + * Output of npm.install is array of arrays. For example: + * [ [ 'test-android-platform-template@0.0.1', + * 'C:\\Users\\~1\\AppData\\Local\\Temp\\1\\platform-template11627-15560-rm3ngx\\node_modules\\test-android-platform-template', + * undefined, + * undefined, + * '..\\..\\..\\android-platform-template' ] ] + * Project successfully created. + */ + let pathToTemplate = this.$npm.install(selectedTemplate, tempDir).wait()[0][1]; + return { selectedTemplate, pathToTemplate }; + } catch(err) { + this.$logger.trace("Error while trying to install specified template: ", err); + this.$errors.failWithoutHelp(`Unable to install platform template ${selectedTemplate}. Make sure the specified value is valid.`); + } + } + + return null; + }).future()(); + } + public getInstalledPlatforms(): IFuture { return(() => { if(!this.$fs.exists(this.$projectData.platformsDir).wait()) { diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 8871fff37c..98fd5e0bdc 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -137,6 +137,7 @@ function createTestInjector() { testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); + testInjector.register("npm", {}); return testInjector; } diff --git a/test/platform-service.ts b/test/platform-service.ts index 65f721d059..4ed9a18a8b 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -71,6 +71,7 @@ function createTestInjector() { testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); + testInjector.register("npm", {}); return testInjector; }