Skip to content

Commit 50b6685

Browse files
Merge pull request #1588 from NativeScript/vladimirov/custom-native-templates
Add support for custom platform templates
2 parents 2ac0444 + 2ce4b75 commit 50b6685

File tree

10 files changed

+82
-22
lines changed

10 files changed

+82
-22
lines changed

docs/man_pages/project/configuration/platform-add.md

+13-11
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,28 @@ platform add
33

44
Usage | Synopsis
55
------|-------
6-
Android latest runtime | `$ tns platform add android [--frameworkPath <File Path>] [--symlink] [--sdk <API Level>]`
7-
Android selected runtime | `$ tns platform add android[@<Version>] [--frameworkPath <File Path>] [--symlink] [--sdk <API Level>]`
8-
<% if (isMacOS) { %>iOS latest runtime | `$ tns platform add ios [--frameworkPath <File Path>] [--symlink]`
9-
iOS selected runtime | `$ tns platform add ios[@<Version>] [--frameworkPath <File Path>] [--symlink]`<% } %>
6+
Android latest runtime | `$ tns platform add android [--framework-path <File Path>] [--symlink] [--sdk <API Level>] [--platform-template <Platform Template>]`
7+
Android selected runtime | `$ tns platform add android[@<Version>] [--framework-path <File Path>] [--symlink] [--sdk <API Level>] [--platform-template <Platform Template>]`
8+
<% if (isMacOS) { %>iOS latest runtime | `$ tns platform add ios [--framework-path <File Path>] [--symlink]`
9+
iOS selected runtime | `$ tns platform add ios[@<Version>] [--framework-path <File Path>] [--symlink] [--platform-template <Platform Template>]`<% } %>
1010

11-
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.<% } %>
11+
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.<% } %>
1212

1313
### Options
14-
* `--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, `<File Path>` must point to directory in which the runtime is already extracted. If `--symlink` is not specified, `<File Path>` must point to a valid npm package.
14+
* `--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, `<File Path>` must point to directory in which the runtime is already extracted. If `--symlink` is not specified, `<File Path>` must point to a valid npm package.
1515
* `--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.
1616
* `--sdk` - Sets the target Android SDK for the project.
17+
* `--platform-template` - Sets the platform template that will be used for the native application.
1718

1819
### Attributes
1920
* `<API Level>` 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).<% } %>
2021
* `<File Path>` is the complete path to a valid npm package or a directory that contains a NativeScript runtime for the selected platform.
21-
* `<Version>` is any available version of the respective platform runtime published in npm. <% if(isHtml) { %>If `@<Version>` is not specified, the NativeScript CLI installs the latest stable runtime for the selected platform.
22-
To list all available versions for Android, run `$ npm view tns-android versions`
23-
To list only experimental versions for Android, run `$ npm view tns-android dist-tags`
24-
To list all available versions for iOS, run `$ npm view tns-ios versions`
25-
To list only experimental versions for iOS, run `$ npm view tns-ios dist-tags`
22+
* `<Platform Template>` is a valid npm package, path to directory, .tgz or GitHub URL that contains a native Android or iOS template.
23+
* `<Version>` is any available version of the respective platform runtime published in npm. <% if(isHtml) { %>If `@<Version>` is not specified, the NativeScript CLI installs the latest stable runtime for the selected platform.
24+
To list all available versions for Android, run `$ npm view tns-android versions`
25+
To list only experimental versions for Android, run `$ npm view tns-android dist-tags`
26+
To list all available versions for iOS, run `$ npm view tns-ios versions`
27+
To list only experimental versions for iOS, run `$ npm view tns-ios dist-tags`
2628

2729
### Command Limitations
2830

lib/declarations.ts

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ interface IOptions extends ICommonOptions {
8383
port: Number;
8484
copyTo: string;
8585
baseConfig: string;
86+
platformTemplate: string;
8687
}
8788

8889
interface IInitService {

lib/definitions/project.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ interface IiOSBuildConfig extends IBuildConfig {
6969
interface IPlatformProjectService {
7070
platformData: IPlatformData;
7171
validate(): IFuture<void>;
72-
createProject(frameworkDir: string, frameworkVersion: string): IFuture<void>;
72+
createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture<void>;
7373
interpolateData(): IFuture<void>;
7474
interpolateConfigurationFile(configurationFilePath?: string): IFuture<void>;
7575
afterCreateProject(projectRoot: string): IFuture<void>;

lib/nativescript-cli.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as fiber from "fibers";
99
import Future = require("fibers/future");
1010
import * as shelljs from "shelljs";
1111
shelljs.config.silent = true;
12+
shelljs.config.fatal = true;
1213
import {installUncaughtExceptionListener} from "./common/errors";
1314
installUncaughtExceptionListener(process.exit);
1415

lib/options.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export class Options extends commonOptionsLibPath.OptionsBase {
3434
compileSdk: {type: OptionType.Number },
3535
port: { type: OptionType.Number },
3636
copyTo: { type: OptionType.String },
37-
baseConfig: { type: OptionType.String }
37+
baseConfig: { type: OptionType.String },
38+
platformTemplate: { type: OptionType.String }
3839
},
3940
path.join($hostInfo.isWindows ? process.env.AppData : path.join(osenv.home(), ".local/share"), ".nativescript-cli"),
4041
$errors, $staticConfig);

lib/services/android-project-service.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
9292
}).future<void>()();
9393
}
9494

95-
public createProject(frameworkDir: string, frameworkVersion: string): IFuture<void> {
95+
public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture<void> {
9696
return (() => {
9797
if(semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
9898
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
111111
}
112112

113113
// These files and directories should not be symlinked as CLI is modifying them and we'll change the original values as well.
114-
this.copy(this.platformData.projectRoot, frameworkDir, "src", "-R");
114+
if(pathToTemplate) {
115+
let mainPath = path.join(this.platformData.projectRoot, "src", "main");
116+
this.$fs.createDirectory(mainPath).wait();
117+
shell.cp("-R", path.join(path.resolve(pathToTemplate), "*"), mainPath);
118+
} else {
119+
this.copy(this.platformData.projectRoot, frameworkDir, "src", "-R");
120+
}
115121
this.copy(this.platformData.projectRoot, frameworkDir, "build.gradle settings.gradle gradle.properties", "-f");
116122

117123
if (this.useGradleWrapper(frameworkDir)) {

lib/services/ios-project-service.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,16 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
104104
}).future<void>()();
105105
}
106106

107-
public createProject(frameworkDir: string, frameworkVersion: string): IFuture<void> {
107+
public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture<void> {
108108
return (() => {
109109
this.$fs.ensureDirectoryExists(path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)).wait();
110-
if(this.$options.symlink) {
110+
if(pathToTemplate) {
111+
// Copy everything except the template from the runtime
112+
this.$fs.readDirectory(frameworkDir).wait()
113+
.filter(dirName => dirName.indexOf(IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER) === -1)
114+
.forEach(dirName => shell.cp("-R", path.join(frameworkDir, dirName), this.platformData.projectRoot));
115+
shell.cp("-rf", path.join(pathToTemplate, "*"), this.platformData.projectRoot);
116+
} else if(this.$options.symlink) {
111117
let xcodeProjectName = util.format("%s.xcodeproj", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER);
112118

113119
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
118124
_.each(frameworkFiles, (file: string) => {
119125
this.$fs.symlink(path.join(frameworkDir, file), path.join(this.platformData.projectRoot, file)).wait();
120126
});
121-
122127
} else {
123128
shell.cp("-R", path.join(frameworkDir, "*"), this.platformData.projectRoot);
124129
}

lib/services/platform-service.ts

+46-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import * as helpers from "../common/helpers";
88
import * as semver from "semver";
99
import * as minimatch from "minimatch";
1010
import Future = require("fibers/future");
11+
import * as temp from "temp";
12+
temp.track();
1113
let clui = require("clui");
1214

1315
export class PlatformService implements IPlatformService {
@@ -29,7 +31,8 @@ export class PlatformService implements IPlatformService {
2931
private $projectFilesManager: IProjectFilesManager,
3032
private $mobileHelper: Mobile.IMobileHelper,
3133
private $hostInfo: IHostInfo,
32-
private $xmlValidator: IXmlValidator) { }
34+
private $xmlValidator: IXmlValidator,
35+
private $npm: INodePackageManager) { }
3336

3437
public addPlatforms(platforms: string[]): IFuture<void> {
3538
return (() => {
@@ -114,7 +117,10 @@ export class PlatformService implements IPlatformService {
114117
}
115118

116119
let sourceFrameworkDir = isFrameworkPathDirectory && this.$options.symlink ? path.join(this.$options.frameworkPath, "framework") : frameworkDir;
117-
platformData.platformProjectService.createProject(path.resolve(sourceFrameworkDir), installedVersion).wait();
120+
this.$projectDataService.initialize(this.$projectData.projectDir);
121+
let customTemplateOptions = this.getPathToPlatformTemplate(this.$options.platformTemplate, platformData.frameworkPackageName).wait();
122+
let pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate;
123+
platformData.platformProjectService.createProject(path.resolve(sourceFrameworkDir), installedVersion, pathToTemplate).wait();
118124

119125
if(isFrameworkPathDirectory || isFrameworkPathNotSymlinkedFile) {
120126
// Need to remove unneeded node_modules folder
@@ -132,12 +138,48 @@ export class PlatformService implements IPlatformService {
132138
this.$fs.copyFile(newConfigFile, platformData.configurationFilePath).wait();
133139
}
134140

135-
this.$projectDataService.initialize(this.$projectData.projectDir);
136-
this.$projectDataService.setValue(platformData.frameworkPackageName, {version: installedVersion}).wait();
141+
let frameworkPackageNameData: any = {version: installedVersion};
142+
if(customTemplateOptions) {
143+
frameworkPackageNameData.template = customTemplateOptions.selectedTemplate;
144+
}
145+
this.$projectDataService.setValue(platformData.frameworkPackageName, frameworkPackageNameData).wait();
137146

138147
}).future<void>()();
139148
}
140149

150+
private getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string): IFuture<any> {
151+
return (() => {
152+
if(!selectedTemplate) {
153+
// read data from package.json's nativescript key
154+
// check the nativescript.tns-<platform>.template value
155+
let nativescriptPlatformData = this.$projectDataService.getValue(frameworkPackageName).wait();
156+
selectedTemplate = nativescriptPlatformData && nativescriptPlatformData.template;
157+
}
158+
159+
if(selectedTemplate) {
160+
let tempDir = temp.mkdirSync("platform-template");
161+
try {
162+
/*
163+
* Output of npm.install is array of arrays. For example:
164+
165+
* 'C:\\Users\\<USER>~1\\AppData\\Local\\Temp\\1\\platform-template11627-15560-rm3ngx\\node_modules\\test-android-platform-template',
166+
* undefined,
167+
* undefined,
168+
* '..\\..\\..\\android-platform-template' ] ]
169+
* Project successfully created.
170+
*/
171+
let pathToTemplate = this.$npm.install(selectedTemplate, tempDir).wait()[0][1];
172+
return { selectedTemplate, pathToTemplate };
173+
} catch(err) {
174+
this.$logger.trace("Error while trying to install specified template: ", err);
175+
this.$errors.failWithoutHelp(`Unable to install platform template ${selectedTemplate}. Make sure the specified value is valid.`);
176+
}
177+
}
178+
179+
return null;
180+
}).future<any>()();
181+
}
182+
141183
public getInstalledPlatforms(): IFuture<string[]> {
142184
return(() => {
143185
if(!this.$fs.exists(this.$projectData.platformsDir).wait()) {

test/platform-commands.ts

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ function createTestInjector() {
137137
testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities);
138138
testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
139139
testInjector.register("xmlValidator", XmlValidator);
140+
testInjector.register("npm", {});
140141

141142
return testInjector;
142143
}

test/platform-service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ function createTestInjector() {
7171
testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities);
7272
testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
7373
testInjector.register("xmlValidator", XmlValidator);
74+
testInjector.register("npm", {});
7475

7576
return testInjector;
7677
}

0 commit comments

Comments
 (0)