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; }