diff --git a/CocoaPods.md b/CocoaPods.md
index 6bc2089e0e..57a40e1015 100644
--- a/CocoaPods.md
+++ b/CocoaPods.md
@@ -12,14 +12,14 @@ To work with such libraries, you need to wrap them as a custom NativeScript plug
## Install CocoaPods
You need to install CocoaPods. If you haven't yet, you can do so by running:
-```
+```bash
$ sudo gem install cocoapods
```
> **NOTE:** All operations and code in this article are verified against CocoaPods 0.38.2.
To check your current version, run the following command.
-```
+```bash
$ pod --version
```
@@ -33,8 +33,8 @@ sudo gem install cocoapods
To start, create a project and add the iOS platform.
```bash
-$ tns create MYCocoaPods
-$ cd MYCocoaPods
+$ tns create MYCocoaPodsApp
+$ cd MYCocoaPodsApp
$ tns platform add ios
```
@@ -42,17 +42,17 @@ $ tns platform add ios
For more information about working with NativeScript plugins, click [here](PLUGINS.md).
-```
+```bash
cd ..
mkdir my-plugin
cd my-plugin
```
-Create a package.json file with the following content:
+Create a `package.json` file with the following content:
-```
+```json
{
- "name": "myplugin",
+ "name": "my-plugin",
"version": "0.0.1",
"nativescript": {
"platforms": {
@@ -72,18 +72,31 @@ my-plugin/
└── Podfile
```
+Podfile:
+```
+pod 'GoogleMaps'
+```
+
+## Install the Plugin
+
Next, install the plugin:
-```
+```bash
tns plugin add ../my-plugin
```
-## Build the project
+> **NOTE:** Installing CocoaPods sets the deployment target of your app to iOS 8, if not already set to iOS 8 or later. This change is required because CocoaPods are installed as shared frameworks to ensure that all symbols are available at runtime.
-```
+## Build the Project
+
+```bash
tns build ios
```
-This modifies the `MYCocoaPods.xcodeproj` and creates a workspace with the same name.
+This modifies the `MYCocoaPodsApp.xcodeproj` and creates a workspace with the same name.
+
+> **IMPORTANT:** You will no longer be able to run the `xcodeproj` alone. NativeScript CLI will build the newly created workspace and produce the correct package.
+
+## Troubleshooting
-> **IMPORTANT:** You will no longer be able to run the `xcodeproj` alone. NativeScript CLI will build the newly created workspace and produce the correct .ipa.
\ No newline at end of file
+In case of post-build linker errors, you might need to resolve missing dependencies to native frameworks required by the installed CocoaPod. For more information about how to create the required links, see the [build.xcconfig specification](PLUGINS.md#buildxcconfig-specification).
diff --git a/Gruntfile.js b/Gruntfile.js
index 536061fcab..b432c963b0 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -39,17 +39,17 @@ module.exports = function(grunt) {
},
devlib: {
- src: ["lib/**/*.ts", "!lib/common/node_modules/**/*.ts"],
+ src: ["lib/**/*.ts", "!lib/common/node_modules/**/*.ts", "!lib/common/messages/**/*.ts"],
reference: "lib/.d.ts"
},
devall: {
- src: ["lib/**/*.ts", "test/**/*.ts", "!lib/common/node_modules/**/*.ts", "lib/common/test/unit-tests/**/*.ts", "definitions/**/*.ts", "!lib/common/test/.d.ts"],
+ src: ["lib/**/*.ts", "test/**/*.ts", "!lib/common/node_modules/**/*.ts", "!lib/common/messages/**/*.ts", "lib/common/test/unit-tests/**/*.ts", "definitions/**/*.ts", "!lib/common/test/.d.ts"],
reference: "lib/.d.ts"
},
release_build: {
- src: ["lib/**/*.ts", "test/**/*.ts", "!lib/common/node_modules/**/*.ts"],
+ src: ["lib/**/*.ts", "test/**/*.ts", "!lib/common/node_modules/**/*.ts", "!lib/common/messages/**/*.ts"],
reference: "lib/.d.ts",
options: {
sourceMap: false,
@@ -61,7 +61,7 @@ module.exports = function(grunt) {
tslint: {
build: {
files: {
- src: ["lib/**/*.ts", "test/**/*.ts", "!lib/common/node_modules/**/*.ts", "lib/common/test/unit-tests/**/*.ts", "definitions/**/*.ts", "!**/*.d.ts"]
+ src: ["lib/**/*.ts", "test/**/*.ts", "!lib/common/node_modules/**/*.ts", "!lib/common/messages/**/*.ts", "lib/common/test/unit-tests/**/*.ts", "definitions/**/*.ts", "!**/*.d.ts"]
},
options: {
configuration: grunt.file.readJSON("./tslint.json")
@@ -71,7 +71,7 @@ module.exports = function(grunt) {
watch: {
devall: {
- files: ["lib/**/*.ts", 'test/**/*.ts', "!lib/common/node_modules/**/*.ts"],
+ files: ["lib/**/*.ts", 'test/**/*.ts', "!lib/common/node_modules/**/*.ts", "!lib/common/messages/**/*.ts"],
tasks: [
'ts:devall',
'shell:npm_test'
@@ -184,6 +184,6 @@ module.exports = function(grunt) {
"copy:package_to_qa_drop_folder"
]);
grunt.registerTask("lint", ["tslint:build"]);
-
+ grunt.registerTask("all", ["clean", "test", "lint"]);
grunt.registerTask("default", "ts:devlib");
};
diff --git a/PLUGINS.md b/PLUGINS.md
index abecd6009e..e811271028 100644
--- a/PLUGINS.md
+++ b/PLUGINS.md
@@ -6,8 +6,9 @@ Starting with NativeScript CLI 1.1.0, you can develop or use plugins in your Nat
* [What Are NativeScript Plugins](#what-are-nativescript-plugins)
* [Create a Plugin](#create-a-plugin)
* [Directory Structure](#directory-structure)
- * [Package.json Specification](#packagejson-specification)
- * [Include.gradle Specification](#includegradle-specification)
+ * [`package.json` Specification](#packagejson-specification)
+ * [`include.gradle` Specification](#includegradle-specification)
+ * [`build.xcconfig` Specification](#buildxcconfig-specification)
* [Install a Plugin](#install-a-plugin)
* [Valid Plugin Sources](#valid-plugin-sources)
* [Installation Specifics](#installation-specifics)
@@ -21,11 +22,11 @@ Starting with NativeScript CLI 1.1.0, you can develop or use plugins in your Nat
A NativeScript plugin is any npm package, published or not, that exposes a native API via JavaScript and consists of the following elements.
-* A `package.json` file which contains the following metadata: name, version, supported runtime versions, dependencies and others. For more information, see the [Package.json Specification](#packagejson-specification) section.
+* A `package.json` file which contains the following metadata: name, version, supported runtime versions, dependencies and others. For more information, see the [`package.json` Specification](#packagejson-specification) section.
* One or more CommonJS modules that expose a native API via a unified JavaScript API. For more information about Common JS modules, see the [CommonJS Wiki](http://wiki.commonjs.org/wiki/CommonJS).
-* `AndroidManifest.xml` and `Info.plist` which describe the permissions, features or other configurations required or used by your app for Android and iOS, respectively.
-* (Optional) Native Android libraries and the native Android `include.gradle` configuration file which describes the native dependencies. For more information, see the [Include.gradle Specification](#includegradle-specification) section
-* (Optional) Native iOS dynamic libraries.
+* (Optional) `AndroidManifest.xml` and `Info.plist` which describe the permissions, features or other configurations required or used by your app for Android and iOS, respectively.
+* (Optional) Native Android libraries and the native Android `include.gradle` configuration file which describes the native dependencies. For more information, see the [`include.gradle` Specification](#includegradle-specification) section.
+* (Optional) Native iOS libraries and the native `build.xcconfig` configuration file which describes the native dependencies. For more information, see the [`build.xcconfig` Specification](#buildxcconfig-specification) section.
The plugin must have the directory structure, described in the [Directory Structure](#directory-structure) section.
@@ -35,11 +36,11 @@ If the NativeScript framework does not expose a native API that you need, you ca
* The plugin must be a valid npm package.
* The plugin must expose a built-in native API or a native API available via custom native libraries.
-* The plugin must be written in JavaScript or TypeScript and must comply with the CommonJS specification. If written in TypeScript, make sure to include the compiled `JavaScript` file in your plugin.
+* The plugin must be written in JavaScript and must comply with the CommonJS specification. If you are using a transpiler, make sure to include the transpiled JavaScript files in your plugin.
* The plugin directory structure must comply with the specification described below.
* The plugin must contain a valid `package.json` which complies with the specification described below.
-* If the plugin requires any permissions, features or other configuration specifics, it must contain `AndroidManifest.xml` and `Info.plist` file which describe them.
-* (Android-only) If the plugin depends on native libraries, it must contain a valid `include.gradle file`, which describes the dependencies.
+* If the plugin requires any permissions, features or other configuration specifics, it must contain `AndroidManifest.xml` or `Info.plist` file which describe them.
+* If the plugin depends on native libraries, it must contain a valid `include.gradle` or `build.xcconfig` file, which describes the dependencies.
### Directory Structure
@@ -51,7 +52,7 @@ my-plugin/
├── package.json
└── platforms/
├── android/
- │ └── res/
+ │ ├── res/
│ └── AndroidManifest.xml
└── ios/
└── Info.plist
@@ -61,6 +62,7 @@ NativeScript plugins which consist of multiple CommonJS modules might have the f
```
my-plugin/
+├── index.js
├── package.json
├── MyModule1/
│ ├── index1.js
@@ -70,20 +72,17 @@ my-plugin/
│ └── package.json
└── platforms/
├── android/
+ │ ├── AndroidManifest.xml
│ └── res/
- │ └── AndroidManifest.xml
└── ios/
└── Info.plist
- └── Podfile
```
* `index.js`: This file is the CommonJS module which exposes the native API. You can use platform-specific `*.platform.js` files. For example: `index.ios.js` and `index.android.js`. During the plugin installation, the NativeScript CLI will copy the platform resources to the `tns_modules` subdirectory in the correct platform destination in the `platforms` directory of your project.
Alternatively, you can give any name to this CommonJS module. In this case, however, you need to point to this file by setting the `main` key in the `package.json` for the plugin. For more information, see [Folders as Modules](https://nodejs.org/api/modules.html#modules_folders_as_modules).
* `package.json`: This file contains the metadata for your plugin. It sets the supported runtimes, the plugin name and version and any dependencies. The `package.json` specification is described in detail below.
* `platforms\android\AndroidManifest.xml`: This file describes any specific configuration changes required for your plugin to work. For example: required permissions. For more information about the format of `AndroidManifest.xml`, see [App Manifest](http://developer.android.com/guide/topics/manifest/manifest-intro.html).
During build, gradle will merge the plugin `AndroidManifest.xml` with the `AndroidManifest.xml` for your project. The NativeScript CLI will not resolve any contradicting or duplicate entries during the merge. After the plugin is installed, you need to manually resolve such issues.
-* `platforms\android\include.gradle`: This file modifies the native Android configuration of your NativeScript project such as native dependencies, build types and configurations. For more information about the format of `include.gradle`, see [include.gradle file](#includegradle-specification).
-* `platforms/android/res`: (Optional) This directory contains resources declared by the `AndroidManifest.xml` file. You can look at the folder structure [here](http://developer.android.com/guide/topics/resources/providing-resources.html#ResourceTypes).
-* `platforms/ios/Info.plist`: This file describes any specific configuration changes required for your plugin to work. For example: required permissions. For more information about the format of `Info.plist`, see [About Information Property List Files](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html).
During the plugin installation, the NativeScript CLI will merge the plugin `Info.plist` with the `Info.plist` for your project. The NativeScript CLI will not resolve any contradicting or duplicate entries during the merge. After the plugin is installed, you need to manually resolve such issues.
-* `platforms/ios/Podfile`: This file describes the dependency to the library that you want to use. For more information, see [the CocoaPods article](CocoaPods.md).
+* `platforms\android\res`: (Optional) This directory contains resources declared by the `AndroidManifest.xml` file. You can look at the folder structure [here](http://developer.android.com/guide/topics/resources/providing-resources.html#ResourceTypes).
+* `platforms\ios\Info.plist`: This file describes any specific configuration changes required for your plugin to work. For example, required permissions. For more information about the format of `Info.plist`, see [About Information Property List Files](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html).
During the plugin installation, the NativeScript CLI will merge the plugin `Info.plist` with the `Info.plist` for your project. The NativeScript CLI will not resolve any contradicting or duplicate entries during the merge. After the plugin is installed, you need to manually resolve such issues.
NativeScript plugins which contain both native Android and iOS libraries might have the following directory structure.
@@ -92,20 +91,28 @@ my-plugin/
├── ...
└── platforms/
├── android/
- │ └── res/
- │ └── MyLibrary.jar
- │ └── MyLibrary.aar
- │ └── include.gradle
+ │ ├── res/
+ │ ├── MyLibrary.jar
+ │ ├── MyLibrary.aar
+ │ ├── include.gradle
│ └── AndroidManifest.xml
└── ios/
- ├── MyiOSLibrary.framework
- └── Info.plist
+ ├── MyiOSFramework.framework
+ ├── build.xcconfig
+ ├── Podfile
+ ├── Info.plist
+ ├── MyStaticiOSLibrary.a
+ └── include/
+ └── MyStaticiOSLibrary/
+ └── ...
```
-* `platforms\android`: This directory contains any native Android libraries packaged as `*.jar` and `*.aar` packages. These native libraries can reside in the root of this directory or in a user-created sub-directory. During the plugin installation, the NativeScript CLI will configure the Android project in `platforms\android` to work with the plugin.
+* `platforms\android`: This directory contains any native Android libraries packaged as `*.jar` and `*.aar` packages. These native libraries can reside in the root of this directory or in a user-created sub-directory. During the plugin installation, the NativeScript CLI will configure the Android project in `platforms\android` to work with the plugin.
* `platforms\android\res`: (Optional) This directory contains resources declared by the `AndroidManifest.xml` file. You can look at the folder structure [here](http://developer.android.com/guide/topics/resources/providing-resources.html#ResourceTypes).
-* `platforms\ios`: This directory contains native iOS dynamic libraries (`.framework`). During the plugin installation, the NativeScript CLI will copy these files to `lib\iOS` in your project and will configure the Android project in `platforms\ios` to work with the library.
-* `platforms\android\include.gradle`: This file modifies the native Android configuration of your NativeScript project such as native dependencies, build types and configurations. For more information about the format of `include.gradle`, see [include.gradle file](#includegradle-specification).
+* `platforms\android\include.gradle`: This file modifies the native Android configuration of your NativeScript project such as native dependencies, build types and configurations. For more information about the format of `include.gradle`, see [`include.gradle` file](#includegradle-specification).
+* `platforms\ios`: This directory contains native dynamic iOS Cocoa Touch Frameworks (`.framework`) and Cocoa Touch Static Libraries (`.a`). During the plugin installation, the NativeScript CLI will copy these files to `lib\iOS` in your project and will configure the iOS project in `platforms\ios` to work with the libraries. If the library is written in Swift, only APIs exposed to Objective-C are exposed to NativeScript. In case the plugin contains a Cocoa Touch Static Library (`.a`), you must place all public headers (`.h`) under `include\\`. Make sure that the static libraries are built at least for the following processor architectures - armv7, arm64, i386.
+* `platforms\ios\build.xcconfig`: This file modifies the native iOS configuration of your NativeScript project such as native dependencies and configurations. For more information about the format of `build.xcconfig`, see [`build.xcconfig` file](#buildxcconfig-specification).
+* `platforms\ios\Podfile`: This file describes the dependency to the library that you want to use. For more information, see [the CocoaPods article](CocoaPods.md).
### Package.json Specification
@@ -113,7 +120,7 @@ Every NativeScript plugin should contain a valid `package.json` file in its root
* It must comply with the [npm specification](https://docs.npmjs.com/files/package.json).
The `package.json` must contain at least `name` and `version` pairs. You will later use the plugin in your code by requiring it by its `name`.
* It must contain a `nativescript` section which describes the supported NativeScript runtimes and their versions. This section can be empty. If you want to define supported platforms and runtimes, you can nest a `platforms` section. In this `platforms` section, you can nest `ios` and `android` key-value pairs. The values in these pairs must be valid runtime versions or ranges of values specified by a valid semver(7) syntax.
-* If the plugin depends on other npm modules, it must contain a `dependencies` section as described [here](https://docs.npmjs.com/files/package.json#dependencies).
The NativeScript CLI will resolve the dependencies during the plugin installation.
+* If the plugin depends on other npm modules, it must contain a `dependencies` section as described [here](https://docs.npmjs.com/files/package.json#dependencies).
The NativeScript CLI will resolve the dependencies during the plugin installation.
#### Package.json Example
@@ -141,16 +148,16 @@ Every NativeScript plugin, which contains native Android dependencies, should al
* Any native dependencies should be available in [jcenter](https://bintray.com/bintray/jcenter) or from the Android SDK installed on your machine.
> **IMPORTANT:** If you don't have an `include.gradle` file, at build time, gradle will create a default one containing all default elements.
-
+
#### Include.gradle Example
-```
+```gradle
//default elements
-android {
- productFlavors {
- "my-plugin" {
- dimension "my-plugin"
- }
- }
+android {
+ productFlavors {
+ "my-plugin" {
+ dimension "my-plugin"
+ }
+ }
}
//optional elements
@@ -159,6 +166,14 @@ dependencies {
}
```
+### Build.xcconfig Specification
+Every NativeScript plugin, which contains native iOS dependencies, can also contain a [valid](https://pewpewthespells.com/blog/xcconfig_guide.html) `build.xcconfig` file in the root of its `platforms\ios` directory. This `build.xcconfig` file might contain native dependencies required to build the plugin properly.
+
+#### Build.xcconfig Example
+```
+OTHER_LDFLAGS = $(inherited) -framework "QuartzCore" -l"sqlite3"
+```
+
## Install a Plugin
To install a plugin for your project, inside your project, run the following command.
@@ -185,7 +200,7 @@ The NativeScript CLI takes the plugin and installs it to the `node_modules` dire
If the NativeScript CLI detects any native iOS libraries in the plugin, it copies the library files to the `lib\ios` folder in your project and configures the iOS-specific projects in `platforms\ios` to work with the library.
-Next, the NativeScript CLI runs a partial `prepare` operation for the plugin for all platforms configured for the project. During this operation, the CLI copies only the plugin to the `tns_modules` subdirectories in the `platforms\android` and `platforms\ios` directories in your project. If your plugin contains platform-specific `JS` files, the CLI copies them to the respective platform subdirectory and renames them by removing the platform modifier.
+Next, the NativeScript CLI runs a partial `prepare` operation for the plugin for all platforms configured for the project. During this operation, the CLI copies only the plugin to the `tns_modules` subdirectories in the `platforms\android` and `platforms\ios` directories in your project. If your plugin contains platform-specific `JS` files, the CLI copies them to the respective platform subdirectory and renames them by removing the platform modifier.
> **TIP:** If you have not configured any platforms, when you run `$ tns platform add`, the NativeScript CLI will automatically prepare all installed plugins for the newly added platform.
@@ -202,7 +217,7 @@ The following is an example of a plugin `AndroidManifest`, project `AndroidManif
```XML
-
+
@@ -211,7 +226,7 @@ The following is an example of a plugin `AndroidManifest`, project `AndroidManif
-
+
@@ -229,10 +244,10 @@ The following is an example of a plugin `AndroidManifest`, project `AndroidManif
-
+
-
+
-
+
@@ -259,7 +274,7 @@ The following is an example of a plugin `AndroidManifest`, project `AndroidManif
```XML
-
@@ -271,7 +286,7 @@ The following is an example of a plugin `AndroidManifest`, project `AndroidManif
-
@@ -300,7 +315,7 @@ To use a plugin inside your project, you need to add a `require` in your app.
var myPlugin = require("myplugin");
```
-This will look for a `myplugin` module with a valid `package.json` file in the `tns_modules` directory. Note that you must require the plugin with the value for the `name` key in the plugin `package.json` file.
+This will look for a `myplugin` module with a valid `package.json` file in the `tns_modules` directory. Note that you must require the plugin with the value for the `name` key in the plugin `package.json` file.
## Remove a Plugin
diff --git a/README.md b/README.md
index a3ac6fd200..2753c76b65 100644
--- a/README.md
+++ b/README.md
@@ -570,3 +570,4 @@ This software is licensed under the Apache 2.0 license, quoted [--path ] [--appid ] [--copy-from ]`
+Create from default JavaScript template | `$ tns create [--path ] [--appid ]`
+Create from default TypeScript template | `$ tns create [--path ] [--appid --template typescript` OR `$ tns create [--path ] [--appid --template tsc`
+Copy from existing project | `$ tns create [--path ] [--appid ] --copy-from `
+Create from custom template | `$ tns create [--path ] [--appid ] --template `
-Creates a new project for native development with NativeScript from the default template or from an existing NativeScript project.
+Creates a new project for native development with NativeScript.
### Options
* `--path` - Specifies the directory where you want to create the project, if different from the current directory. The directory must be empty.
-* `--appid` - Sets the application identifier for your project.
-* `--copy-from` - Specifies a directory which contains an existing NativeScript project. If not set, the NativeScript CLI creates the project from the default hello-world template.
+* `--appid` - Sets the application identifier for your project.
+* `--copy-from` - Specifies a directory which contains an existing NativeScript project. If `--copy-from` and `--template` are not set, the NativeScript CLI creates the project from the default JavaScript hello-world template.
+* `--template` - Specifies a valid na in the default hello-world template.
### Attributes
-* `` is the name of project. The specified name must meet the requirements of all platforms that you want to target. <% if(isConsole) { %>For more information about the `` requirements, run `$ tns help create`<% } %><% if(isHtml) { %>For projects that target Android, you can use uppercase or lowercase letters, numbers, and underscores. The name must start with a letter.
-For projects that target iOS, you can use uppercase or lowercase letters, numbers, and hyphens.<% } %>
-* `` is the application identifier for your project. It must be a domain name in reverse and must meet the requirements of all platforms that you want to target. If not specified, the application identifier is set to `org.nativescript.` <% if(isConsole) { %>For more information about the `` requirements, run `$ tns help create`<% } %><% if(isHtml) { %>For projects that target Android, you can use uppercase or lowercase letters, numbers, and underscores in the strings of the reversed domain name, separated by a dot. Strings must be separated by a dot and must start with a letter. For example: `com.nativescript.My_Andro1d_App`
-For projects that target iOS, you can use uppercase or lowercase letters, numbers, and hyphens in the strings of the reversed domain name. Strings must be separated by a dot. For example: `com.nativescript.My-i0s-App`.<% } %>
+* `` is the name of project. The specified name must meet the requirements of all platforms that you want to target. <% if(isConsole) { %>For more information about the `` requirements, run `$ tns help create`<% } %><% if(isHtml) { %>For projects that target Android, you can use uppercase or lowercase letters, numbers, and underscores. The name must start with a letter.
+For projects that target iOS, you can use uppercase or lowercase letters, numbers, and hyphens.<% } %>
+* `` is the application identifier for your project. It must be a domain name in reverse and must meet the requirements of all platforms that you want to target. If not specified, the application identifier is set to `org.nativescript.` <% if(isConsole) { %>For more information about the `` requirements, run `$ tns help create`<% } %><% if(isHtml) { %>For projects that target Android, you can use uppercase or lowercase letters, numbers, and underscores in the strings of the reversed domain name, separated by a dot. Strings must be separated by a dot and must start with a letter. For example: `com.nativescript.My_Andro1d_App`
+For projects that target iOS, you can use uppercase or lowercase letters, numbers, and hyphens in the strings of the reversed domain name. Strings must be separated by a dot. For example: `com.nativescript.My-i0s-App`.
+* `` is a valid npm package which you want to use as template for your app. You can specify the package by name in the npm registry or by local path or GitHub URL to a directory or .tar.gz containing a package.json file. The contents of the package will be copied to the `app` directory of your project.<% } %>
-<% if(isHtml) { %>
+<% if(isHtml) { %>
### Related Commands
Command | Description
----------|----------
[init](init.html) | Initializes a project for development. The command prompts you to provide your project configuration interactively and uses the information to create a new package.json file or update the existing one.
[install](/lib-management/install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory.
-<% } %>
\ No newline at end of file
+<% } %>
diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts
index edf24f1af4..5f13a02e0a 100644
--- a/lib/bootstrap.ts
+++ b/lib/bootstrap.ts
@@ -1,4 +1,5 @@
require("./common/bootstrap");
+$injector.require("logger", "./common/logger");
$injector.require("config", "./config");
$injector.require("options", "./options");
// note: order above is important!
@@ -90,3 +91,4 @@ $injector.require("iOSNotificationService", "./services/ios-notification-service
$injector.require("socketProxyFactory", "./device-sockets/ios/socket-proxy-factory");
$injector.require("iOSNotification", "./device-sockets/ios/notification");
$injector.require("iOSSocketRequestExecutor", "./device-sockets/ios/socket-request-executor");
+$injector.require("messages", "./messages");
diff --git a/lib/commands/create-project.ts b/lib/commands/create-project.ts
index 44c297fa8d..14683a192b 100644
--- a/lib/commands/create-project.ts
+++ b/lib/commands/create-project.ts
@@ -26,13 +26,14 @@ export class CreateProjectCommand implements ICommand {
constructor(private $projectService: IProjectService,
private $errors: IErrors,
private $logger: ILogger,
- private $projectNameValidator: IProjectNameValidator) { }
+ private $projectNameValidator: IProjectNameValidator,
+ private $options: ICommonOptions) { }
public enableHooks = false;
execute(args: string[]): IFuture {
return (() => {
- this.$projectService.createProject(args[0]).wait();
+ this.$projectService.createProject(args[0], this.$options.template).wait();
}).future()();
}
diff --git a/lib/common b/lib/common
index 33ed5a86b4..c337d26e7e 160000
--- a/lib/common
+++ b/lib/common
@@ -1 +1 @@
-Subproject commit 33ed5a86b44f5c9256be1b413f38c2d4d777ba22
+Subproject commit c337d26e7e314d6d8521184a00f53a1b7c51cf3f
diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts
index df3d0022ab..825a99573d 100644
--- a/lib/definitions/project.d.ts
+++ b/lib/definitions/project.d.ts
@@ -1,6 +1,6 @@
interface IProjectService {
- createProject(projectName: string): IFuture;
+ createProject(projectName: string, selectedTemplate?: string): IFuture;
}
interface IProjectData {
@@ -22,8 +22,24 @@ interface IProjectDataService {
removeDependency(dependencyName: string): IFuture;
}
+/**
+ * Describes working with templates.
+ */
interface IProjectTemplatesService {
+ /**
+ * Defines the path where unpacked default template can be found.
+ */
defaultTemplatePath: IFuture;
+
+ /**
+ * Prepares template for project creation.
+ * In case templateName is not provided, use defaultTemplatePath.
+ * In case templateName is a special word, validated from us (for ex. typescript), resolve the real template name and add it to npm cache.
+ * In any other cases try to `npm install` the specified templateName to temp directory.
+ * @param {string} templateName The name of the template.
+ * @return {string} Path to the directory where extracted template can be found.
+ */
+ prepareTemplate(templateName: string): IFuture;
}
interface IPlatformProjectServiceBase {
diff --git a/lib/messages.d.ts b/lib/messages.d.ts
new file mode 100644
index 0000000000..4d4e59f425
--- /dev/null
+++ b/lib/messages.d.ts
@@ -0,0 +1,13 @@
+///
+//
+// automatically generated code; do not edit manually!
+//
+interface IMessages{
+ Devices : {
+ NotFoundDeviceByIdentifierErrorMessage: string;
+ NotFoundDeviceByIdentifierErrorMessageWithIdentifier: string;
+ NotFoundDeviceByIndexErrorMessage: string;
+ };
+
+}
+
diff --git a/lib/messages.ts b/lib/messages.ts
new file mode 100644
index 0000000000..b7dc279a2f
--- /dev/null
+++ b/lib/messages.ts
@@ -0,0 +1,16 @@
+///
+"use strict";
+//
+// automatically generated code; do not edit manually!
+//
+
+export class Messages implements IMessages{
+ Devices = {
+ NotFoundDeviceByIdentifierErrorMessage: "Devices.NotFoundDeviceByIdentifierErrorMessage",
+ NotFoundDeviceByIdentifierErrorMessageWithIdentifier: "Devices.NotFoundDeviceByIdentifierErrorMessageWithIdentifier",
+ NotFoundDeviceByIndexErrorMessage: "Devices.NotFoundDeviceByIndexErrorMessage",
+ };
+
+}
+$injector.register('messages', Messages);
+
diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts
index 7175f6e83d..a06792c38f 100644
--- a/lib/nativescript-cli.ts
+++ b/lib/nativescript-cli.ts
@@ -19,6 +19,9 @@ fiber(() => {
let commandDispatcher: ICommandDispatcher = $injector.resolve("commandDispatcher");
+ let messages: IMessagesService = $injector.resolve("$messagesService");
+ messages.pathsToMessageJsonFiles = [/* Place client-specific json message file paths here */];
+
if (process.argv[2] === "completion") {
commandDispatcher.completeCommand().wait();
} else {
diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts
index 677b4cc258..656f0da30b 100644
--- a/lib/npm-installation-manager.ts
+++ b/lib/npm-installation-manager.ts
@@ -18,7 +18,8 @@ export class NpmInstallationManager implements INpmInstallationManager {
private packageSpecificDirectories: IStringDictionary = {
"tns-android": constants.PROJECT_FRAMEWORK_FOLDER_NAME,
"tns-ios": constants.PROJECT_FRAMEWORK_FOLDER_NAME,
- "tns-template-hello-world": constants.APP_RESOURCES_FOLDER_NAME
+ "tns-template-hello-world": constants.APP_RESOURCES_FOLDER_NAME,
+ "tns-template-hello-world-ts": constants.APP_RESOURCES_FOLDER_NAME
};
constructor(private $npm: INodePackageManager,
diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts
index 0db9290824..d666aef6ee 100644
--- a/lib/services/project-service.ts
+++ b/lib/services/project-service.ts
@@ -4,7 +4,7 @@
import * as constants from "../constants";
import * as osenv from "osenv";
import * as path from "path";
-import * as shell from "shelljs";
+import * as shelljs from "shelljs";
export class ProjectService implements IProjectService {
@@ -18,7 +18,7 @@ export class ProjectService implements IProjectService {
private $projectTemplatesService: IProjectTemplatesService,
private $options: IOptions) { }
- public createProject(projectName: string): IFuture {
+ public createProject(projectName: string, selectedTemplate?: string): IFuture {
return(() => {
if (!projectName) {
this.$errors.fail("You must specify when creating a new project.");
@@ -51,7 +51,6 @@ export class ProjectService implements IProjectService {
let appDirectory = path.join(projectDir, constants.APP_FOLDER_NAME);
let appPath: string = null;
-
if (customAppPath) {
this.$logger.trace("Using custom app from %s", customAppPath);
@@ -68,25 +67,74 @@ export class ProjectService implements IProjectService {
this.$logger.trace("Copying custom app into %s", appDirectory);
appPath = customAppPath;
} else {
- // No custom app - use nativescript hello world application
- this.$logger.trace("Using NativeScript hello world application");
- let defaultTemplatePath = this.$projectTemplatesService.defaultTemplatePath.wait();
- this.$logger.trace("Copying NativeScript hello world application into %s", appDirectory);
+ let defaultTemplatePath = this.$projectTemplatesService.prepareTemplate(selectedTemplate).wait();
+ this.$logger.trace(`Copying application from '${defaultTemplatePath}' into '${appDirectory}'.`);
appPath = defaultTemplatePath;
}
try {
this.createProjectCore(projectDir, appPath, projectId).wait();
+ //update dependencies and devDependencies of newly created project with data from template
+ this.mergeProjectAndTemplateProperties(projectDir, appPath).wait();
+ this.updateAppResourcesDir(appDirectory).wait();
+ this.$npm.install(projectDir, projectDir, { "ignore-scripts": this.$options.ignoreScripts }).wait();
} catch (err) {
this.$fs.deleteDirectory(projectDir).wait();
throw err;
}
-
this.$logger.out("Project %s was successfully created", projectName);
}).future()();
}
+ private mergeProjectAndTemplateProperties(projectDir: string, templatePath: string): IFuture {
+ return (() => {
+ let templatePackageJsonPath = path.join(templatePath, constants.PACKAGE_JSON_FILE_NAME);
+ if(this.$fs.exists(templatePackageJsonPath).wait()) {
+ let projectPackageJsonPath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
+ let projectPackageJsonData = this.$fs.readJson(projectPackageJsonPath).wait();
+ this.$logger.trace("Initial project package.json data: ", projectPackageJsonData);
+ let templatePackageJsonData = this.$fs.readJson(templatePackageJsonPath).wait();
+ if(projectPackageJsonData.dependencies || templatePackageJsonData.dependencies) {
+ projectPackageJsonData.dependencies = this.mergeDependencies(projectPackageJsonData.dependencies, templatePackageJsonData.dependencies);
+ }
+
+ if(projectPackageJsonData.devDependencies || templatePackageJsonData.devDependencies) {
+ projectPackageJsonData.devDependencies = this.mergeDependencies(projectPackageJsonData.devDependencies, templatePackageJsonData.devDependencies);
+ }
+
+ this.$logger.trace("New project package.json data: ", projectPackageJsonData);
+ this.$fs.writeJson(projectPackageJsonPath, projectPackageJsonData).wait();
+ } else {
+ this.$logger.trace(`Template ${templatePath} does not have ${constants.PACKAGE_JSON_FILE_NAME} file.`);
+ }
+ }).future()();
+ }
+
+ private updateAppResourcesDir(appDirectory: string): IFuture {
+ return (() => {
+ let defaultAppResourcesDir = path.join(this.$projectTemplatesService.defaultTemplatePath.wait(), constants.APP_RESOURCES_FOLDER_NAME);
+ let targetAppResourcesDir = path.join(appDirectory, constants.APP_RESOURCES_FOLDER_NAME);
+ this.$logger.trace(`Updating AppResources values from ${defaultAppResourcesDir} to ${targetAppResourcesDir}`);
+ shelljs.cp("-R", path.join(defaultAppResourcesDir, "*"), targetAppResourcesDir);
+ }).future()();
+ }
+
+ private mergeDependencies(projectDependencies: IStringDictionary, templateDependencies: IStringDictionary): IStringDictionary {
+ // Cast to any when logging as logger thinks it can print only string.
+ // Cannot use toString() because we want to print the whole objects, not [Object object]
+ this.$logger.trace("Merging dependencies, projectDependencies are: ", projectDependencies, " templateDependencies are: ", templateDependencies);
+ projectDependencies = projectDependencies || {};
+ _.extend(projectDependencies, templateDependencies || {});
+ let sortedDeps: IStringDictionary = {};
+ let dependenciesNames = _.keys(projectDependencies).sort();
+ _.each(dependenciesNames, (key: string) => {
+ sortedDeps[key] = projectDependencies[key];
+ });
+ this.$logger.trace("Sorted merged dependencies are: ", sortedDeps);
+ return sortedDeps;
+ }
+
private createProjectCore(projectDir: string, appSourcePath: string, projectId: string): IFuture {
return (() => {
this.$fs.ensureDirectoryExists(projectDir).wait();
@@ -97,7 +145,7 @@ export class ProjectService implements IProjectService {
if(this.$options.symlink) {
this.$fs.symlink(appSourcePath, appDestinationPath).wait();
} else {
- shell.cp('-R', path.join(appSourcePath, "*"), appDestinationPath);
+ shelljs.cp('-R', path.join(appSourcePath, "*"), appDestinationPath);
}
this.createBasicProjectStructure(projectDir, projectId).wait();
diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts
index 7d3d11d566..9c23c5c9d7 100644
--- a/lib/services/project-templates-service.ts
+++ b/lib/services/project-templates-service.ts
@@ -1,13 +1,101 @@
///
"use strict";
+import * as path from "path";
+import * as temp from "temp";
+import * as constants from "../constants";
+import {EOL} from "os";
+temp.track();
export class ProjectTemplatesService implements IProjectTemplatesService {
- private static NPM_DEFAULT_TEMPLATE_NAME = "tns-template-hello-world";
+ private static RESERVED_TEMPLATE_NAMES: IStringDictionary = {
+ "default": "tns-template-hello-world",
+ "tsc": "tns-template-hello-world-ts",
+ "typescript": "tns-template-hello-world-ts"
+ };
- public constructor(private $npmInstallationManager: INpmInstallationManager) { }
+ public constructor(private $errors: IErrors,
+ private $fs: IFileSystem,
+ private $logger: ILogger,
+ private $npm: INodePackageManager,
+ private $npmInstallationManager: INpmInstallationManager) { }
public get defaultTemplatePath(): IFuture {
- return this.$npmInstallationManager.install(ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME);
+ return this.prepareNativeScriptTemplate(ProjectTemplatesService.RESERVED_TEMPLATE_NAMES["default"]);
+ }
+
+ public prepareTemplate(originalTemplateName: string): IFuture {
+ return ((): string => {
+ let realTemplatePath: string;
+ if(originalTemplateName) {
+ let templateName = originalTemplateName.toLowerCase();
+
+ // support @ syntax
+ let [name, version] = templateName.split("@");
+ if(ProjectTemplatesService.RESERVED_TEMPLATE_NAMES[name]) {
+ realTemplatePath = this.prepareNativeScriptTemplate(ProjectTemplatesService.RESERVED_TEMPLATE_NAMES[name], version).wait();
+ } else {
+ let tempDir = temp.mkdirSync("nativescript-template-dir");
+ try {
+ // Use the original template name, specified by user as it may be case-sensitive.
+ this.$npm.install(originalTemplateName, tempDir, {production: true, silent: true}).wait();
+ } catch(err) {
+ this.$logger.trace(err);
+ this.$errors.failWithoutHelp(`Unable to use template ${originalTemplateName}. Make sure you've specified valid name, github URL or path to local dir.` +
+ `${EOL}Error is: ${err.message}.`);
+ }
+
+ realTemplatePath = this.getTemplatePathFromTempDir(tempDir).wait();
+ }
+ } else {
+ realTemplatePath = this.defaultTemplatePath.wait();
+ }
+
+ if(realTemplatePath) {
+ this.$fs.deleteDirectory(path.join(realTemplatePath, constants.NODE_MODULES_FOLDER_NAME)).wait();
+ return realTemplatePath;
+ }
+
+ 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.`);
+ }).future()();
+ }
+
+ /**
+ * Install verified NativeScript template in the npm cache.
+ * The "special" here is that npmInstallationManager will check current CLI version and will instal best matching version of the template.
+ * For example in case CLI is version 10.12.8, npmInstallationManager will try to find latest 10.12.x version of the template.
+ * @param {string} templateName The name of the verified NativeScript template.
+ * @param {string} version The version of the template specified by user.
+ * @return {string} Path to the directory where the template is installed.
+ */
+ private prepareNativeScriptTemplate(templateName: string, version?: string): IFuture {
+ this.$logger.trace(`Using NativeScript verified template: ${templateName} with version ${version}.`);
+ return this.$npmInstallationManager.install(templateName, {version: version});
+ }
+
+ private getTemplatePathFromTempDir(tempDir: string): IFuture {
+ return ((): string => {
+ let templatePath: string;
+ let tempDirContents = this.$fs.readDirectory(tempDir).wait();
+ this.$logger.trace(`TempDir contents: ${tempDirContents}.`);
+
+ // We do not know the name of the package that will be installed, so after installation to temp dir,
+ // there should be node_modules dir there and its only subdir should be our package.
+ // In case there's some other dir instead of node_modules, consider it as our package.
+ if(tempDirContents && tempDirContents.length === 1) {
+ let tempDirSubdir = _.first(tempDirContents);
+ if(tempDirSubdir === constants.NODE_MODULES_FOLDER_NAME) {
+ let templateDirName = _.first(this.$fs.readDirectory(path.join(tempDir, constants.NODE_MODULES_FOLDER_NAME)).wait());
+ if(templateDirName) {
+ templatePath = path.join(tempDir, tempDirSubdir, templateDirName);
+ }
+ } else {
+ templatePath = path.join(tempDir, tempDirSubdir);
+ }
+ }
+
+ return templatePath;
+ }).future()();
}
}
$injector.register("projectTemplatesService", ProjectTemplatesService);
diff --git a/lib/services/usb-livesync-service.ts b/lib/services/usb-livesync-service.ts
index 68486e97ff..3a5f0e18fb 100644
--- a/lib/services/usb-livesync-service.ts
+++ b/lib/services/usb-livesync-service.ts
@@ -187,11 +187,13 @@ export class UsbLiveSyncService extends usbLivesyncServiceBaseLib.UsbLiveSyncSer
}).future()();
}
- protected preparePlatformForSync(platform: string) {
- if (!this.$platformService.preparePlatform(platform).wait()) {
- this.$logger.out("Verify that listed files are well-formed and try again the operation.");
- return;
- }
+ protected preparePlatformForSync(platform: string): IFuture {
+ return (() => {
+ if (!this.$platformService.preparePlatform(platform).wait()) {
+ this.$logger.out("Verify that listed files are well-formed and try again the operation.");
+ return;
+ }
+ }).future()();
}
private resolveUsbLiveSyncService(platform: string, device: Mobile.IDevice): IPlatformSpecificUsbLiveSyncService {
diff --git a/package.json b/package.json
index 9ee25740e8..1496e18e85 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "nativescript",
"preferGlobal": true,
- "version": "1.5.2",
+ "version": "1.6.0",
"author": "Telerik ",
"description": "Command-line interface for building NativeScript projects",
"bin": {
diff --git a/setup/native-script.txt b/setup/native-script.ps1
similarity index 56%
rename from setup/native-script.txt
rename to setup/native-script.ps1
index 206d2c5e42..1edb36e7b1 100644
--- a/setup/native-script.txt
+++ b/setup/native-script.ps1
@@ -1,5 +1,9 @@
# A Boxstarter script to set up Windows machine for NativeScript development
+# To run it against RELEASE branch (recommended) use
+# http://boxstarter.org/package/nr/url?https://raw.githubusercontent.com/NativeScript/nativescript-cli/release/setup/native-script.ps1
+# To run it against MASTER branch (usually only developers of NativeScript need to) use
+# http://boxstarter.org/package/nr/url?https://raw.githubusercontent.com/NativeScript/nativescript-cli/master/setup/native-script.ps1
# install dependenciess with Chocolately
@@ -7,7 +11,7 @@ write-host -BackgroundColor Black -ForegroundColor Yellow "Installing Google Chr
cinst googlechrome
write-host -BackgroundColor Black -ForegroundColor Yellow "Installing node.js"
-cinst nodejs.install -version 0.12.7
+cinst nodejs.install -version 5.1.0
write-host -BackgroundColor Black -ForegroundColor Yellow "Installing Java Development Kit"
cinst jdk8
@@ -16,17 +20,17 @@ write-host -BackgroundColor Black -ForegroundColor Yellow "Installing Android SD
cinst android-sdk
# setup android sdk
-echo yes | cmd /c $env:localappdata\Android\android-sdk\tools\android update sdk --filter "tools,platform-tools,android-22,build-tools-22.0.1,sys-img-x86-android-22,extra-android-m2repository,extra-google-m2repository,extra-android-support" --all --no-ui
+echo yes | cmd /c $env:localappdata\Android\android-sdk\tools\android update sdk --filter "tools,platform-tools,android-23,build-tools-23.0.2,extra-android-m2repository" --all --no-ui
# setup environment
if (!$env:ANDROID_HOME) { [Environment]::SetEnvironmentVariable("ANDROID_HOME", "$env:localappdata\Android\android-sdk", "User") }
-$oldPathUser = [Environment]::GetEnvironmentVariable("PATH", "User")
-$pathMachine = [Environment]::GetEnvironmentVariable("PATH", "Machine")
-$myPath = [Environment]::GetEnvironmentVariable("PATH")
-[Environment]::SetEnvironmentVariable("PATH", "$myPath;$oldPathUser;$pathMachine;$env:localappdata\Android\android-sdk\tools;$env:localappdata\Android\android-sdk\platform-tools")
-[Environment]::SetEnvironmentVariable("PATH", "$oldPathUser;$env:localappdata\Android\android-sdk\tools;$env:localappdata\Android\android-sdk\platform-tools", "User")
+if (!$env:JAVA_HOME) {
+ $curVer = (Get-ItemProperty "HKLM:\SOFTWARE\JavaSoft\Java Development Kit").CurrentVersion
+ $javaHome = (Get-ItemProperty "HKLM:\Software\JavaSoft\Java Development Kit\$curVer").JavaHome
+ [Environment]::SetEnvironmentVariable("JAVA_HOME", $javaHome, "User")
+}
# install NativeScript CLI
write-host -BackgroundColor Black -ForegroundColor Yellow "Installing NativeScript CLI"
diff --git a/setup/native-script.rb b/setup/native-script.rb
index 9119062838..a6762a303c 100644
--- a/setup/native-script.rb
+++ b/setup/native-script.rb
@@ -1,5 +1,11 @@
# coding: utf-8
+# A script to setup developer's workstation for developing with NativeScript
+# To run it against RELEASE branch (recommended) use
+# ruby -e "$(curl -fsSL https://raw.githubusercontent.com/NativeScript/nativescript-cli/release/setup/native-script.rb)"
+# To run it against MASTER branch (usually only developers of NativeScript need to) use
+# ruby -e "$(curl -fsSL https://raw.githubusercontent.com/NativeScript/nativescript-cli/master/setup/native-script.rb)"
+
# Only the user can manually download and install Xcode from App Store
puts "NativeScript requires Xcode."
puts "If you do not have Xcode installed, download and install it from App Store and run it once to complete its setup."
@@ -36,41 +42,19 @@
puts "Installing the Java SE Development Kit... This might take some time, please, be patient. (You might need to provide your password.)"
system('brew cask install java')
-system('echo "export JAVA_HOME=$(/usr/libexec/java_home)" >> ~/.bash_profile')
-system('echo "export ANDROID_HOME=/usr/local/opt/android-sdk" >> ~/.bash_profile')
-
-puts "Installing node.js 0.12"
-system('brew install homebrew/versions/node012')
-
-puts "Creating Homebrew formula for NativeScript."
-File.open("/usr/local/Library/Formula/native-script.rb", "w:utf-8") do |f|
- f.write DATA.read
-end
+system('echo "export JAVA_HOME=$(/usr/libexec/java_home)" >> ~/.profile')
-puts "Installing NativeScript formula... This might take some time, please, be patient."
-system('brew install native-script')
+puts "Installing Android SDK"
+system('brew install android-sdk')
+system('echo "export ANDROID_HOME=/usr/local/opt/android-sdk" >> ~/.profile')
-__END__
+puts "Configuring your system for Android development... This might take some time, please, be patient."
+system "echo yes | /usr/local/opt/android-sdk/tools/android update sdk --filter tools,platform-tools,android-23,build-tools-23.0.2,extra-android-m2repository --all --no-ui"
-class NativeScript < Formula
- desc "NativeScript"
- homepage "https://www.nativescript.org"
- version "1.3.0"
- url "https://raw.githubusercontent.com/NativeScript/nativescript-cli/brew/setup/empty.tar.gz"
- sha256 "813e1b809c094d29255191c14892a32a498e2ca298abbf5ce5cb4081faa4e88f"
+puts "Installing Node.js 4"
+system('brew install homebrew/versions/node4-lts')
- depends_on :macos => :yosemite
- depends_on "pkg-config" => :build
-# depends_on "node" # currently we do not work with latest node, and we manually install 0.12 (see above)
- depends_on "android-sdk"
+puts "Installing NativeScript CLI..."
+system "/usr/local/bin/npm install -g nativescript"
- def install
- ohai "Installing NativeScript CLI..."
- system "/usr/local/bin/npm install -g nativescript"
-
- ohai "Configuring your system for Android development... This might take some time, please, be patient."
- system "echo yes | android update sdk --filter tools,platform-tools,android-22,build-tools-22.0.1,sys-img-x86-android-22,extra-android-m2repository,extra-google-m2repository,extra-android-support --all --no-ui"
-
- ohai "The ANDROID_HOME and JAVA_HOME environment variables have been added to your .bash_profile. Restart the terminal to use them."
- end
-end
+puts "The ANDROID_HOME and JAVA_HOME environment variables have been added to your .profile. Restart the terminal to use them."
diff --git a/test/plugin-variables-service.ts b/test/plugin-variables-service.ts
index 800250d4f9..39e8a20bc4 100644
--- a/test/plugin-variables-service.ts
+++ b/test/plugin-variables-service.ts
@@ -13,6 +13,7 @@ import {ProjectData} from "../lib/project-data";
import {ProjectDataService} from "../lib/services/project-data-service";
import {ProjectHelper} from "../lib/common/project-helper";
import {StaticConfig} from "../lib/config";
+import {MessagesService} from "../lib/common/services/messages-service";
import {Yok} from '../lib/common/yok';
import * as stubs from './stubs';
import * as path from "path";
@@ -22,6 +23,7 @@ temp.track();
function createTestInjector(): IInjector {
let testInjector = new Yok();
+ testInjector.register("messagesService", MessagesService);
testInjector.register("errors", Errors);
testInjector.register("fs", FileSystem);
testInjector.register("hostInfo", HostInfo);
diff --git a/test/plugins-service.ts b/test/plugins-service.ts
index aade26ae7a..c0c3959801 100644
--- a/test/plugins-service.ts
+++ b/test/plugins-service.ts
@@ -24,6 +24,7 @@ import {ResourceLoader} from "../lib/common/resource-loader";
import {EOL} from "os";
import {PluginsService} from "../lib/services/plugins-service";
import {AddPluginCommand} from "../lib/commands/plugin/add-plugin";
+import {MessagesService} from "../lib/common/services/messages-service";
import {Builder} from "../lib/tools/broccoli/builder";
import {AndroidProjectService} from "../lib/services/android-project-service";
import {AndroidToolsInfo} from "../lib/android-tools-info";
@@ -37,7 +38,7 @@ let isErrorThrown = false;
function createTestInjector() {
let testInjector = new Yok();
-
+ testInjector.register("messagesService", MessagesService);
testInjector.register("npm", NodePackageManager);
testInjector.register("fs", FileSystem);
testInjector.register("projectData", ProjectData);
diff --git a/test/project-service.ts b/test/project-service.ts
index 5ecc2bce4a..39f3e87f7d 100644
--- a/test/project-service.ts
+++ b/test/project-service.ts
@@ -20,6 +20,7 @@ import * as helpers from "../lib/common/helpers";
import {assert} from "chai";
import {Options} from "../lib/options";
import {HostInfo} from "../lib/common/host-info";
+import {ProjectTemplatesService} from "../lib/services/project-templates-service";
let mockProjectNameValidator = {
validate: () => { return true; }
@@ -34,9 +35,9 @@ class ProjectIntegrationTest {
this.createTestInjector();
}
- public createProject(projectName: string): IFuture {
+ public createProject(projectName: string, template?: string): IFuture {
let projectService = this.testInjector.resolve("projectService");
- return projectService.createProject(projectName);
+ return projectService.createProject(projectName, template);
}
public getNpmPackagePath(packageName: string): IFuture {
@@ -59,7 +60,7 @@ class ProjectIntegrationTest {
}).future()();
}
- public assertProject(tempFolder: string, projectName: string, appId: string): IFuture {
+ public assertProject(tempFolder: string, projectName: string, appId: string, projectSourceDirectory?: string): IFuture {
return (() => {
let fs: IFileSystem = this.testInjector.resolve("fs");
let projectDir = path.join(tempFolder, projectName);
@@ -67,7 +68,7 @@ class ProjectIntegrationTest {
let platformsDirectoryPath = path.join(projectDir, "platforms");
let tnsProjectFilePath = path.join(projectDir, "package.json");
let tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME);
-
+ let packageJsonContent = fs.readJson(tnsProjectFilePath).wait();
let options = this.testInjector.resolve("options");
assert.isTrue(fs.exists(appDirectoryPath).wait());
@@ -78,21 +79,35 @@ class ProjectIntegrationTest {
assert.isFalse(fs.isEmptyDir(appDirectoryPath).wait());
assert.isTrue(fs.isEmptyDir(platformsDirectoryPath).wait());
- let actualAppId = fs.readJson(tnsProjectFilePath).wait()["nativescript"].id;
+ let actualAppId = packageJsonContent["nativescript"].id;
let expectedAppId = appId;
assert.equal(actualAppId, expectedAppId);
- let tnsCoreModulesRecord = fs.readJson(tnsProjectFilePath).wait()["dependencies"][constants.TNS_CORE_MODULES_NAME];
+ let tnsCoreModulesRecord = packageJsonContent["dependencies"][constants.TNS_CORE_MODULES_NAME];
assert.isTrue(tnsCoreModulesRecord !== null);
- let actualFiles = fs.enumerateFilesInDirectorySync(options.copyFrom);
- let expectedFiles = fs.enumerateFilesInDirectorySync(appDirectoryPath);
+ let sourceDir = projectSourceDirectory || options.copyFrom;
- assert.equal(actualFiles.length, expectedFiles.length);
- _.each(actualFiles, file => {
- let relativeToProjectDir = helpers.getRelativeToRootPath(options.copyFrom, file);
- assert.isTrue(fs.exists(path.join(appDirectoryPath, relativeToProjectDir)).wait());
+ let expectedFiles = fs.enumerateFilesInDirectorySync(sourceDir);
+ let actualFiles = fs.enumerateFilesInDirectorySync(appDirectoryPath);
+ assert.isTrue(actualFiles.length >= expectedFiles.length, "Files in created project must be at least as files in app dir.");
+ _.each(expectedFiles, file => {
+ let relativeToProjectDir = helpers.getRelativeToRootPath(sourceDir, file);
+ let filePathInApp = path.join(appDirectoryPath, relativeToProjectDir);
+ assert.isTrue(fs.exists(filePathInApp).wait(), `File ${filePathInApp} does not exist.`);
});
+
+ // assert dependencies and devDependencies are copied from template to real project
+ let sourcePackageJsonContent = fs.readJson(path.join(sourceDir, "package.json")).wait();
+ let missingDeps = _.difference(_.keys(sourcePackageJsonContent.dependencies), _.keys(packageJsonContent.dependencies));
+ let missingDevDeps = _.difference(_.keys(sourcePackageJsonContent.devDependencies), _.keys(packageJsonContent.devDependencies));
+ assert.deepEqual(missingDeps, [], `All dependencies from template must be copied to project's package.json. Missing ones are: ${missingDeps.join(", ")}.`);
+ assert.deepEqual(missingDevDeps, [], `All devDependencies from template must be copied to project's package.json. Missing ones are: ${missingDevDeps.join(", ")}.`);
+
+ // assert App_Resources are prepared correctly
+ let appResourcesDir = path.join(appDirectoryPath, "App_Resources");
+ let appResourcesContents = fs.readDirectory(appResourcesDir).wait();
+ assert.deepEqual(appResourcesContents, ["Android", "iOS"], "Project's app/App_Resources must contain Android and iOS directories.");
}).future()();
}
@@ -107,7 +122,7 @@ class ProjectIntegrationTest {
this.testInjector.register('logger', stubs.LoggerStub);
this.testInjector.register("projectService", ProjectServiceLib.ProjectService);
this.testInjector.register("projectHelper", ProjectHelperLib.ProjectHelper);
- this.testInjector.register("projectTemplatesService", stubs.ProjectTemplatesService);
+ this.testInjector.register("projectTemplatesService", ProjectTemplatesService);
this.testInjector.register("projectNameValidator", mockProjectNameValidator);
this.testInjector.register("fs", FileSystem);
@@ -126,8 +141,15 @@ class ProjectIntegrationTest {
describe("Project Service Tests", () => {
describe("project service integration tests", () => {
+ let pathToDefaultTemplate: string;
+ before(() => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let projectTemplatesService: IProjectTemplatesService = projectIntegrationTest.testInjector.resolve("projectTemplatesService");
+ pathToDefaultTemplate = projectTemplatesService.defaultTemplatePath.wait();
+ });
+
it("creates valid project from default template", () => {
- let projectIntegrationTest = new ProjectIntegrationTest();
+ let projectIntegrationTest = new ProjectIntegrationTest();
let tempFolder = temp.mkdirSync("project");
let projectName = "myapp";
let options = projectIntegrationTest.testInjector.resolve("options");
@@ -138,8 +160,104 @@ describe("Project Service Tests", () => {
projectIntegrationTest.createProject(projectName).wait();
projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp").wait();
});
+
+ it("creates valid project from default template when --template default is specified", () => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let tempFolder = temp.mkdirSync("project");
+ let projectName = "myapp";
+ let options = projectIntegrationTest.testInjector.resolve("options");
+
+ options.path = tempFolder;
+ projectIntegrationTest.createProject(projectName, "default").wait();
+ projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", pathToDefaultTemplate).wait();
+ });
+
+ it("creates valid project from default template when --template default@version is specified", () => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let tempFolder = temp.mkdirSync("project");
+ let projectName = "myapp";
+ let options = projectIntegrationTest.testInjector.resolve("options");
+
+ options.path = tempFolder;
+ let projectTemplatesService: IProjectTemplatesService = projectIntegrationTest.testInjector.resolve("projectTemplatesService");
+ projectIntegrationTest.createProject(projectName, "default@1.4.0").wait();
+ projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", projectTemplatesService.prepareTemplate("default@1.4.0").wait()).wait();
+ });
+
+ it("creates valid project from typescript template", () => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let tempFolder = temp.mkdirSync("projectTypescript");
+ let projectName = "myapp";
+ let options = projectIntegrationTest.testInjector.resolve("options");
+
+ options.path = tempFolder;
+ projectIntegrationTest.createProject(projectName, "typescript").wait();
+
+ let projectTemplatesService: IProjectTemplatesService = projectIntegrationTest.testInjector.resolve("projectTemplatesService");
+ projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", projectTemplatesService.prepareTemplate("typescript").wait()).wait();
+ });
+
+ it("creates valid project from tsc template", () => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let tempFolder = temp.mkdirSync("projectTsc");
+ let projectName = "myapp";
+ let options = projectIntegrationTest.testInjector.resolve("options");
+
+ options.path = tempFolder;
+ projectIntegrationTest.createProject(projectName, "tsc").wait();
+
+ let projectTemplatesService: IProjectTemplatesService = projectIntegrationTest.testInjector.resolve("projectTemplatesService");
+ projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", projectTemplatesService.prepareTemplate("tsc").wait()).wait();
+ });
+
+ it("creates valid project from local directory template", () => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let tempFolder = temp.mkdirSync("projectLocalDir");
+ let projectName = "myapp";
+ let options = projectIntegrationTest.testInjector.resolve("options");
+
+ options.path = tempFolder;
+ let tempDir = temp.mkdirSync("template");
+ let fs: IFileSystem = projectIntegrationTest.testInjector.resolve("fs");
+ fs.writeJson(path.join(tempDir, "package.json"), {
+ name: "myCustomTemplate",
+ version: "1.0.0",
+ dependencies: {
+ "lodash": "3.10.1"
+ },
+ devDependencies: {
+ "minimist": "1.2.0"
+ }
+ }).wait();
+
+ projectIntegrationTest.createProject(projectName, tempDir).wait();
+ projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", tempDir).wait();
+ });
+
+ it("creates valid project from tarball", () => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let tempFolder = temp.mkdirSync("projectLocalDir");
+ let projectName = "myapp";
+ let options = projectIntegrationTest.testInjector.resolve("options");
+
+ options.path = tempFolder;
+ projectIntegrationTest.createProject(projectName, "https://github.com/NativeScript/template-hello-world/tarball/master").wait();
+ projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", pathToDefaultTemplate).wait();
+ });
+
+ it("creates valid project from git url", () => {
+ let projectIntegrationTest = new ProjectIntegrationTest();
+ let tempFolder = temp.mkdirSync("projectLocalDir");
+ let projectName = "myapp";
+ let options = projectIntegrationTest.testInjector.resolve("options");
+
+ options.path = tempFolder;
+ projectIntegrationTest.createProject(projectName, "https://github.com/NativeScript/template-hello-world.git").wait();
+ projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", pathToDefaultTemplate).wait();
+ });
+
it("creates valid project with specified id from default template", () => {
- let projectIntegrationTest = new ProjectIntegrationTest();
+ let projectIntegrationTest = new ProjectIntegrationTest();
let tempFolder = temp.mkdirSync("project1");
let projectName = "myapp";
let options = projectIntegrationTest.testInjector.resolve("options");
diff --git a/test/project-templates-service.ts b/test/project-templates-service.ts
new file mode 100644
index 0000000000..e4c0c6f91b
--- /dev/null
+++ b/test/project-templates-service.ts
@@ -0,0 +1,134 @@
+///
+"use strict";
+
+import {Yok} from "../lib/common/yok";
+import * as stubs from "./stubs";
+import {ProjectTemplatesService} from "../lib/services/project-templates-service";
+import * as assert from "assert";
+import Future = require("fibers/future");
+import * as path from "path";
+
+let isDeleteDirectoryCalledForNodeModulesDir = false;
+let expectedTemplatePath = "templatePath";
+let nativeScriptValidatedTemplatePath = "nsValidatedTemplatePath";
+
+function createTestInjector(configuration?: {shouldNpmInstallThrow: boolean, npmInstallationDirContents: string[], npmInstallationDirNodeModulesContents: string[]}): IInjector {
+ let injector = new Yok();
+ injector.register("errors", stubs.ErrorsStub);
+ injector.register("logger", stubs.LoggerStub);
+ injector.register("fs", {
+ readDirectory: (dirPath: string) => {
+ if(dirPath.toLowerCase().indexOf("node_modules") !== -1) {
+ return Future.fromResult(configuration.npmInstallationDirNodeModulesContents);
+ }
+ return Future.fromResult(configuration.npmInstallationDirContents);
+ },
+
+ deleteDirectory: (directory: string) => {
+ if(directory.indexOf("node_modules") !== -1) {
+ isDeleteDirectoryCalledForNodeModulesDir = true;
+ }
+ return Future.fromResult();
+ }
+
+ });
+ injector.register("npm", {
+ install: (packageName: string, pathToSave: string, config?: any) => {
+ return (() => {
+ if(configuration.shouldNpmInstallThrow) {
+ throw new Error("NPM install throws error.");
+ }
+
+ return "sample result";
+ }).future()();
+ }
+ });
+
+ injector.register("npmInstallationManager", {
+ install: (packageName: string, options?: INpmInstallOptions) => {
+ return Future.fromResult(nativeScriptValidatedTemplatePath);
+ }
+ });
+
+ injector.register("projectTemplatesService", ProjectTemplatesService);
+
+ return injector;
+}
+
+describe("project-templates-service", () => {
+ let testInjector: IInjector;
+ let projectTemplatesService: IProjectTemplatesService;
+ beforeEach(() => {
+ isDeleteDirectoryCalledForNodeModulesDir = false;
+ });
+
+ describe("prepareTemplate", () => {
+ describe("throws error", () =>{
+ it("when npm install fails", () => {
+ testInjector = createTestInjector({shouldNpmInstallThrow: true, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: null});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ assert.throws(() => projectTemplatesService.prepareTemplate("invalidName").wait());
+ });
+
+ it("when after npm install the temp directory does not have any content", () => {
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: null});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ assert.throws(() => projectTemplatesService.prepareTemplate("validName").wait());
+ });
+
+ it("when after npm install the temp directory has more than one subdir", () => {
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: ["dir1", "dir2"], npmInstallationDirNodeModulesContents: []});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ assert.throws(() => projectTemplatesService.prepareTemplate("validName").wait());
+ });
+
+ it("when after npm install the temp directory has only node_modules directory and there's nothing inside node_modules", () => {
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: ["node_modules"], npmInstallationDirNodeModulesContents: []});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ assert.throws(() => projectTemplatesService.prepareTemplate("validName").wait());
+ });
+ });
+
+ describe("returns correct path to template", () => {
+ it("when after npm install the temp directory has only one subdir and it is not node_modules", () =>{
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [expectedTemplatePath], npmInstallationDirNodeModulesContents: []});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ let actualPathToTemplate = projectTemplatesService.prepareTemplate("validName").wait();
+ assert.strictEqual(path.basename(actualPathToTemplate), expectedTemplatePath);
+ assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
+ });
+
+ it("when after npm install the temp directory has only one subdir and it is node_modules", () =>{
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: ["node_modules"], npmInstallationDirNodeModulesContents: [expectedTemplatePath]});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ let actualPathToTemplate = projectTemplatesService.prepareTemplate("validName").wait();
+ assert.strictEqual(path.basename(actualPathToTemplate), expectedTemplatePath);
+ assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
+ });
+
+ it("when reserved template name is used", () =>{
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: []});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ let actualPathToTemplate = projectTemplatesService.prepareTemplate("typescript").wait();
+ assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath);
+ assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
+ });
+
+ it("when reserved template name is used (case-insensitive test)", () =>{
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: []});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ let actualPathToTemplate = projectTemplatesService.prepareTemplate("tYpEsCriPT").wait();
+ assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath);
+ assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
+ });
+
+ it("uses defaultTemplate when undefined is passed as parameter", () =>{
+ testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: []});
+ projectTemplatesService = testInjector.resolve("projectTemplatesService");
+ let actualPathToTemplate = projectTemplatesService.prepareTemplate(undefined).wait();
+ assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath);
+ assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
+ });
+ });
+ });
+});
diff --git a/test/stubs.ts b/test/stubs.ts
index 4d645811c5..be34db8dde 100644
--- a/test/stubs.ts
+++ b/test/stubs.ts
@@ -184,7 +184,7 @@ export class ErrorsStub implements IErrors {
}
failWithoutHelp(message: string, ...args: any[]): void {
- throw new Error();
+ throw new Error(message);
}
beginCommand(action:() => IFuture, printHelpCommand: () => IFuture): IFuture {
@@ -386,6 +386,10 @@ export class ProjectTemplatesService implements IProjectTemplatesService {
get defaultTemplatePath(): IFuture {
return Future.fromResult("");
}
+
+ prepareTemplate(templateName: string): IFuture {
+ return Future.fromResult("");
+ }
}
export class HooksServiceStub implements IHooksService {
diff --git a/test/test-bootstrap.ts b/test/test-bootstrap.ts
index ee807cabce..b90c40cdf7 100644
--- a/test/test-bootstrap.ts
+++ b/test/test-bootstrap.ts
@@ -1,3 +1,5 @@
+import * as shelljs from "shelljs";
+shelljs.config.silent = true;
global._ = require("lodash");
global.$injector = require("../lib/common/yok").injector;