Skip to content

Commit 2ce4b75

Browse files
Add support for custom platform templates
Add new option when adding platform: `--platform-template`. When it is used, CLI will use the specified template instead of the default template from the runtime. The path to the specified template will be saved in project's package.json, so next time when platform is added, the same template will be used. In case when `--platform-template` is not passed, CLI will check the package.json and if there's value for the template, it will be used. Otherwise the default template from runtime will be used.
1 parent 62b1dfb commit 2ce4b75

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)