diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts
index 8cc63b833d..7cf338d9b1 100644
--- a/lib/bootstrap.ts
+++ b/lib/bootstrap.ts
@@ -3,8 +3,17 @@ require("./common/bootstrap");
$injector.require("nativescript-cli", "./nativescript-cli");
$injector.require("projectService", "./services/project-service");
+$injector.require("androidProjectService", "./services/project-service");
+$injector.require("iOSProjectService", "./services/project-service");
$injector.require("projectTemplatesService", "./services/project-templates-service");
+$injector.require("platformService", "./services/platform-service");
-$injector.requireCommand("create", "./commands/create-project-command");
+$injector.requireCommand("create", "./commands/create-project");
+$injector.requireCommand("platform|*list", "./commands/list-platforms");
+$injector.requireCommand("platform|add", "./commands/add-platform");
+$injector.requireCommand("run", "./commands/run");
+$injector.requireCommand("prepare", "./commands/prepare");
+$injector.requireCommand("build", "./commands/build");
$injector.require("npm", "./node-package-manager");
+$injector.require("propertiesParser", "./properties-parser");
diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts
new file mode 100644
index 0000000000..85923e452a
--- /dev/null
+++ b/lib/commands/add-platform.ts
@@ -0,0 +1,12 @@
+///
+
+export class AddPlatformCommand implements ICommand {
+ constructor(private $platformService: IPlatformService) { }
+
+ execute(args: string[]): IFuture {
+ return (() => {
+ this.$platformService.addPlatforms(args).wait();
+ }).future()();
+ }
+}
+$injector.registerCommand("platform|add", AddPlatformCommand);
\ No newline at end of file
diff --git a/lib/commands/build.ts b/lib/commands/build.ts
new file mode 100644
index 0000000000..d483add028
--- /dev/null
+++ b/lib/commands/build.ts
@@ -0,0 +1,12 @@
+///
+
+export class BuildCommand implements ICommand {
+ constructor(private $platformService: IPlatformService) { }
+
+ execute(args: string[]): IFuture {
+ return (() => {
+ this.$platformService.buildPlatform(args[0]).wait();
+ }).future()();
+ }
+}
+$injector.registerCommand("build", BuildCommand);
\ No newline at end of file
diff --git a/lib/commands/create-project-command.ts b/lib/commands/create-project.ts
similarity index 100%
rename from lib/commands/create-project-command.ts
rename to lib/commands/create-project.ts
diff --git a/lib/commands/list-platforms.ts b/lib/commands/list-platforms.ts
new file mode 100644
index 0000000000..855d384f13
--- /dev/null
+++ b/lib/commands/list-platforms.ts
@@ -0,0 +1,20 @@
+///
+import helpers = require("./../common/helpers");
+
+export class ListPlatformsCommand implements ICommand {
+ constructor(private $platformService: IPlatformService,
+ private $logger: ILogger) { }
+
+ execute(args: string[]): IFuture {
+ return (() => {
+ var availablePlatforms = this.$platformService.getAvailablePlatforms().wait();
+ this.$logger.out("Available platforms: %s", helpers.formatListOfNames(availablePlatforms));
+
+ var installedPlatforms = this.$platformService.getInstalledPlatforms().wait();
+ this.$logger.out("Installed platforms %s", helpers.formatListOfNames(installedPlatforms));
+ }).future()();
+ }
+}
+$injector.registerCommand("platform|*list", ListPlatformsCommand);
+
+
diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts
new file mode 100644
index 0000000000..4821ff66e9
--- /dev/null
+++ b/lib/commands/prepare.ts
@@ -0,0 +1,12 @@
+///
+
+export class PrepareCommand implements ICommand {
+ constructor(private $platformService: IPlatformService) { }
+
+ execute(args: string[]): IFuture {
+ return (() => {
+ this.$platformService.preparePlatform(args[0]).wait();
+ }).future()();
+ }
+}
+$injector.registerCommand("prepare", PrepareCommand);
diff --git a/lib/commands/run.ts b/lib/commands/run.ts
new file mode 100644
index 0000000000..39292de7c5
--- /dev/null
+++ b/lib/commands/run.ts
@@ -0,0 +1,12 @@
+///
+
+export class RunCommand implements ICommand {
+ constructor(private $platformService: IPlatformService) { }
+
+ execute(args: string[]): IFuture {
+ return (() => {
+ this.$platformService.runPlatform(args[0]).wait();
+ }).future()();
+ }
+}
+$injector.registerCommand("run", RunCommand);
diff --git a/lib/common b/lib/common
index 33c883f1ab..789a82292b 160000
--- a/lib/common
+++ b/lib/common
@@ -1 +1 @@
-Subproject commit 33c883f1abae2a5e5486a87f11df1d75e00e1dc8
+Subproject commit 789a82292b66159981e7e02791f4ba89a32c74a1
diff --git a/lib/declarations.ts b/lib/declarations.ts
index f8bbfab6e1..ea88f0bcdd 100644
--- a/lib/declarations.ts
+++ b/lib/declarations.ts
@@ -1,5 +1,9 @@
interface INodePackageManager {
- cache: string;
- load(config?: any): IFuture;
- install(where: string, what: string): IFuture;
+ cache: string;
+ load(config?: any): IFuture;
+ install(where: string, what: string): IFuture;
+}
+
+interface IPropertiesParser {
+ createEditor(filePath: string): IFuture;
}
\ No newline at end of file
diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts
new file mode 100644
index 0000000000..0a774170bb
--- /dev/null
+++ b/lib/definitions/platform.d.ts
@@ -0,0 +1,12 @@
+interface IPlatformService {
+ addPlatforms(platforms: string[]): IFuture;
+ getInstalledPlatforms(): IFuture;
+ getAvailablePlatforms(): IFuture;
+ runPlatform(platform: string): IFuture;
+ preparePlatform(platform: string): IFuture;
+ buildPlatform(platform: string): IFuture;
+}
+
+interface IPlatformCapabilities {
+ targetedOS?: string[];
+}
\ No newline at end of file
diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts
index 20934af393..a0eb4d8cfb 100644
--- a/lib/definitions/project.d.ts
+++ b/lib/definitions/project.d.ts
@@ -1,7 +1,26 @@
interface IProjectService {
createProject(projectName: string, projectId: string): IFuture;
+ createPlatformSpecificProject(platform: string): IFuture;
+ prepareProject(normalizedPlatformName: string, platforms: string[]): IFuture;
+ buildProject(platform: string): IFuture;
+ ensureProject(): void;
+ projectData: IProjectData;
+}
+
+interface IPlatformProjectService {
+ createProject(projectData: IProjectData): IFuture;
+ buildProject(projectData: IProjectData): IFuture;
+}
+
+interface IProjectData {
+ projectDir: string;
+ platformsDir: string;
+ projectFilePath: string;
+ projectId?: string;
+ projectName?: string;
}
interface IProjectTemplatesService {
defaultTemplatePath: IFuture;
+ installAndroidFramework(whereToInstall: string): IFuture
}
\ No newline at end of file
diff --git a/lib/definitions/properties-parser.d.ts b/lib/definitions/properties-parser.d.ts
new file mode 100644
index 0000000000..e214062da3
--- /dev/null
+++ b/lib/definitions/properties-parser.d.ts
@@ -0,0 +1,3 @@
+declare module "properties-parser" {
+ function createEditor(path: string, callback: (err: IErrors, data: any) => void);
+}
\ No newline at end of file
diff --git a/lib/definitions/shelljs.d.ts b/lib/definitions/shelljs.d.ts
index 99ea4dc36e..0b84163555 100644
--- a/lib/definitions/shelljs.d.ts
+++ b/lib/definitions/shelljs.d.ts
@@ -2,4 +2,5 @@ declare module "shelljs" {
function cp(arg: string, sourcePath: string, destinationPath: string): void;
function sed(arg: string, oldValue: any, newValue: string, filePath: string): void;
function mv(source: string[], destination: string);
+ function grep(what: any, where: string): any;
}
\ No newline at end of file
diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts
index e2600aa613..2c58c2c502 100644
--- a/lib/nativescript-cli.ts
+++ b/lib/nativescript-cli.ts
@@ -1,16 +1,16 @@
///
+"use strict";
-import Fiber = require("fibers");
-import Future = require("fibers/future");
import path = require("path");
+require("./common/extensions");
require("./bootstrap");
require("./options");
import errors = require("./common/errors");
errors.installUncaughtExceptionListener();
-$injector.register("config", {"CI_LOGGER": false});
+$injector.register("config", {"CI_LOGGER": false, PROJECT_FILE_NAME: ".tnsproject", "DEBUG": process.env.NATIVESCRIPT_DEBUG});
var dispatcher = $injector.resolve("dispatcher");
-dispatcher.runMainFiber();
+dispatcher.runMainFiber();
\ No newline at end of file
diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts
index 4a12295114..1f0c815e71 100644
--- a/lib/node-package-manager.ts
+++ b/lib/node-package-manager.ts
@@ -33,4 +33,4 @@ export class NodePackageManager implements INodePackageManager {
return future;
}
}
-$injector.register("npm", NodePackageManager);
\ No newline at end of file
+$injector.register("npm", NodePackageManager);
diff --git a/lib/options.ts b/lib/options.ts
index 56a48f873d..20f58e75e7 100644
--- a/lib/options.ts
+++ b/lib/options.ts
@@ -10,6 +10,7 @@ var knownOpts:any = {
"path" : String,
"copy-from": String,
"link-to": String,
+ "release": String,
"version": Boolean,
"help": Boolean
},
diff --git a/lib/properties-parser.ts b/lib/properties-parser.ts
new file mode 100644
index 0000000000..9b77eeaf4f
--- /dev/null
+++ b/lib/properties-parser.ts
@@ -0,0 +1,20 @@
+///
+
+import propertiesParser = require("properties-parser");
+import Future = require("fibers/future");
+
+export class PropertiesParser implements IPropertiesParser {
+ public createEditor(filePath: string) {
+ var future = new Future();
+ propertiesParser.createEditor(filePath, (err, data) => {
+ if(err) {
+ future.throw(err);
+ } else {
+ future.return(data);
+ }
+ });
+
+ return future;
+ }
+}
+$injector.register("propertiesParser", PropertiesParser);
\ No newline at end of file
diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts
new file mode 100644
index 0000000000..39214865dc
--- /dev/null
+++ b/lib/services/platform-service.ts
@@ -0,0 +1,147 @@
+///
+
+import path = require("path");
+import util = require("util");
+import helpers = require("./../common/helpers");
+
+export class PlatformService implements IPlatformService {
+ private platformCapabilities: { [key: string]: IPlatformCapabilities } = {
+ ios: {
+ targetedOS: ['darwin']
+ },
+ android: {
+ }
+ };
+
+ private platformNames = [];
+
+ constructor(private $errors: IErrors,
+ private $fs: IFileSystem,
+ private $projectService: IProjectService) {
+ this.platformNames = Object.keys(this.platformCapabilities);
+ }
+
+ public getCapabilities(platform: string): IPlatformCapabilities {
+ return this.platformCapabilities[platform];
+ }
+
+ public addPlatforms(platforms: string[]): IFuture {
+ return (() => {
+ if(!platforms || platforms.length === 0) {
+ this.$errors.fail("No platform specified. Please specify a platform to add");
+ }
+
+ this.$projectService.ensureProject();
+
+ var platformsDir = this.$projectService.projectData.platformsDir;
+ this.$fs.ensureDirectoryExists(platformsDir).wait();
+
+ _.each(platforms, platform => {
+ this.addPlatform(platform.toLowerCase()).wait();
+ });
+
+ }).future()();
+ }
+
+ private addPlatform(platform: string): IFuture {
+ return(() => {
+ platform = platform.split("@")[0];
+
+ this.validatePlatform(platform);
+
+ var platformPath = path.join(this.$projectService.projectData.platformsDir, platform);
+
+ // TODO: Check for version compatability if the platform is in format platform@version. This should be done in PR for semanting versioning
+
+ if (this.$fs.exists(platformPath).wait()) {
+ this.$errors.fail("Platform %s already added", platform);
+ }
+
+ // Copy platform specific files in platforms dir
+ this.$projectService.createPlatformSpecificProject(platform).wait();
+
+ }).future()();
+ }
+
+ public getInstalledPlatforms(): IFuture {
+ return(() => {
+ if(!this.$fs.exists(this.$projectService.projectData.platformsDir).wait()) {
+ return [];
+ }
+
+ var subDirs = this.$fs.readDirectory(this.$projectService.projectData.platformsDir).wait();
+ return _.filter(subDirs, p => { return this.platformNames.indexOf(p) > -1; });
+ }).future()();
+ }
+
+ public getAvailablePlatforms(): IFuture {
+ return (() => {
+ var installedPlatforms = this.getInstalledPlatforms().wait();
+ return _.filter(this.platformNames, p => {
+ return installedPlatforms.indexOf(p) < 0 && this.isPlatformSupportedForOS(p); // Only those not already installed
+ });
+ }).future()();
+ }
+
+ public runPlatform(platform: string): IFuture {
+ return (() => {
+
+ }).future()();
+ }
+
+ public preparePlatform(platform: string): IFuture {
+ return (() => {
+ platform = platform.toLowerCase();
+ this.validatePlatform(platform);
+ var normalizedPlatformName = this.normalizePlatformName(platform);
+
+ this.$projectService.prepareProject(normalizedPlatformName, this.platformNames).wait();
+ }).future()();
+ }
+
+ public buildPlatform(platform: string): IFuture {
+ return (() => {
+ platform = platform.toLocaleLowerCase();
+ this.validatePlatform(platform);
+
+ this.$projectService.buildProject(platform).wait();
+ }).future()();
+ }
+
+ private validatePlatform(platform: string): void {
+ if (!this.isValidPlatform(platform)) {
+ this.$errors.fail("Invalid platform %s. Valid platforms are %s.", platform, helpers.formatListOfNames(this.platformNames));
+ }
+
+ if (!this.isPlatformSupportedForOS(platform)) {
+ this.$errors.fail("Applications for platform %s can not be built on this OS - %s", platform, process.platform);
+ }
+ }
+
+ private isValidPlatform(platform: string) {
+ return this.platformCapabilities[platform];
+ }
+
+ private isPlatformSupportedForOS(platform: string): boolean {
+ var platformCapabilities = this.getCapabilities(platform);
+ var targetedOS = platformCapabilities.targetedOS;
+
+ if(!targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private normalizePlatformName(platform: string): string {
+ switch(platform) {
+ case "android":
+ return "Android";
+ case "ios":
+ return "iOS";
+ }
+
+ return undefined;
+ }
+}
+$injector.register("platformService", PlatformService);
\ No newline at end of file
diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts
index 5511e20d97..1aee2c152e 100644
--- a/lib/services/project-service.ts
+++ b/lib/services/project-service.ts
@@ -4,23 +4,59 @@ import path = require("path");
import options = require("./../options");
import shell = require("shelljs");
import osenv = require("osenv");
+import util = require("util");
+import helpers = require("./../common/helpers");
export class ProjectService implements IProjectService {
- private static DEFAULT_ID = "com.telerik.tns.HelloWorld";
- private static DEFAULT_NAME = "HelloNativescript";
- private static APP_FOLDER_NAME = "app";
+ private static DEFAULT_PROJECT_ID = "com.telerik.tns.HelloWorld";
+ private static DEFAULT_PROJECT_NAME = "HelloNativescript";
+ public static APP_FOLDER_NAME = "app";
+ private static APP_RESOURCES_FOLDER_NAME = "App_Resources";
+ public static PROJECT_FRAMEWORK_DIR = "framework";
+
+ public projectData: IProjectData = null;
constructor(private $logger: ILogger,
private $errors: IErrors,
private $fs: IFileSystem,
- private $projectTemplatesService: IProjectTemplatesService) { }
+ private $projectTemplatesService: IProjectTemplatesService,
+ private $androidProjectService: IPlatformProjectService,
+ private $iOSProjectService: IPlatformProjectService,
+ private $projectHelper: IProjectHelper,
+ private $config) {
+ this.projectData = this.getProjectData().wait();
+ }
+
+ private getProjectData(): IFuture {
+ return(() => {
+ var projectData: IProjectData = null;
+ var projectDir = this.$projectHelper.projectDir;
+
+ if(projectDir) {
+ projectData = {
+ projectDir: projectDir,
+ platformsDir: path.join(projectDir, "platforms"),
+ projectFilePath: path.join(projectDir, this.$config.PROJECT_FILE_NAME)
+ };
+ var projectFilePath = path.join(projectDir, this.$config.PROJECT_FILE_NAME);
+
+ if (this.$fs.exists(projectFilePath).wait()) {
+ var fileContent = this.$fs.readJson(projectFilePath).wait();
+ projectData.projectId = fileContent.id;
+ projectData.projectName = path.basename(projectDir);
+ }
+ }
+
+ return projectData;
+ }).future()();
+ }
public createProject(projectName: string, projectId: string): IFuture {
return(() => {
var projectDir = path.resolve(options.path || ".");
- projectId = projectId || ProjectService.DEFAULT_ID;
- projectName = projectName || ProjectService.DEFAULT_NAME;
+ projectId = projectId || ProjectService.DEFAULT_PROJECT_ID;
+ projectName = projectName || ProjectService.DEFAULT_PROJECT_NAME;
projectDir = path.join(projectDir, projectName);
this.$fs.createDirectory(projectDir).wait();
@@ -34,7 +70,7 @@ export class ProjectService implements IProjectService {
this.$errors.fail("Path already exists and is not empty %s", projectDir);
}
- this.$logger.trace("Creating a new NativeScript project with name %s and id at location", projectName, projectId, projectDir);
+ this.$logger.trace("Creating a new NativeScript project with name %s and id %s at location %s", projectName, projectId, projectDir);
var appDirectory = path.join(projectDir, ProjectService.APP_FOLDER_NAME);
var appPath: string = null;
@@ -58,11 +94,11 @@ export class ProjectService implements IProjectService {
appPath = defaultTemplatePath;
}
- this.createProjectCore(projectDir, appPath, false).wait();
+ this.createProjectCore(projectDir, appPath, projectId, false).wait();
}).future()();
}
- private createProjectCore(projectDir: string, appPath: string, symlink?: boolean): IFuture {
+ private createProjectCore(projectDir: string, appPath: string, projectId: string, symlink?: boolean): IFuture {
return (() => {
if(!this.$fs.exists(projectDir).wait()) {
this.$fs.createDirectory(projectDir).wait();
@@ -74,18 +110,100 @@ export class ProjectService implements IProjectService {
this.$fs.createDirectory(appDir).wait();
shell.cp('-R', path.join(appPath, "*"), appDir);
}
- this.createBasicProjectStructure(projectDir).wait();
+ this.createBasicProjectStructure(projectDir, projectId).wait();
}).future()();
}
- private createBasicProjectStructure(projectDir: string): IFuture {
+ private createBasicProjectStructure(projectDir: string, projectId: string): IFuture {
return (() => {
this.$fs.createDirectory(path.join(projectDir, "platforms")).wait();
this.$fs.createDirectory(path.join(projectDir, "tns_modules")).wait();
this.$fs.createDirectory(path.join(projectDir, "hooks")).wait();
+
+ var projectData = { id: projectId };
+ this.$fs.writeFile(path.join(projectDir, this.$config.PROJECT_FILE_NAME), JSON.stringify(projectData)).wait();
+ }).future()();
+ }
+
+ public createPlatformSpecificProject(platform: string): IFuture {
+ return(() => {
+ this.executePlatformSpecificAction(platform, "createProject").wait();
+ }).future()();
+ }
+
+ public prepareProject(normalizedPlatformName: string, platforms: string[]): IFuture {
+ return (() => {
+ var platform = normalizedPlatformName.toLowerCase();
+ var assetsDirectoryPath = path.join(this.projectData.platformsDir, platform, "assets");
+ var appResourcesDirectoryPath = path.join(assetsDirectoryPath, ProjectService.APP_FOLDER_NAME, ProjectService.APP_RESOURCES_FOLDER_NAME);
+ shell.cp("-r", path.join(this.projectData.projectDir, ProjectService.APP_FOLDER_NAME), assetsDirectoryPath);
+
+ if(this.$fs.exists(appResourcesDirectoryPath).wait()) {
+ shell.cp("-r", path.join(appResourcesDirectoryPath, normalizedPlatformName, "*"), path.join(this.projectData.platformsDir, platform, "res"));
+ this.$fs.deleteDirectory(appResourcesDirectoryPath).wait();
+ }
+
+ var files = helpers.enumerateFilesInDirectorySync(path.join(assetsDirectoryPath, ProjectService.APP_FOLDER_NAME));
+ var platformsAsString = platforms.join("|");
+
+ _.each(files, fileName => {
+ var platformInfo = ProjectService.parsePlatformSpecificFileName(path.basename(fileName), platformsAsString);
+ var shouldExcludeFile = platformInfo && platformInfo.platform !== platform;
+ if(shouldExcludeFile) {
+ this.$fs.deleteFile(fileName).wait();
+ } else if(platformInfo && platformInfo.onDeviceName) {
+ this.$fs.rename(fileName, path.join(path.dirname(fileName), platformInfo.onDeviceName)).wait();
+ }
+ });
+
+ }).future()();
+ }
+
+ private static parsePlatformSpecificFileName(fileName: string, platforms: string): any {
+ var regex = util.format("^(.+?)\.(%s)(\..+?)$", platforms);
+ var parsed = fileName.toLowerCase().match(new RegExp(regex, "i"));
+ if (parsed) {
+ return {
+ platform: parsed[2],
+ onDeviceName: parsed[1] + parsed[3]
+ };
+ }
+ return undefined;
+ }
+
+ public buildProject(platform: string): IFuture {
+ return (() => {
+ this.executePlatformSpecificAction(platform, "buildProject").wait();
+ }).future()();
+ }
+
+ private executePlatformSpecificAction(platform, functionName: string): IFuture {
+ return (() => {
+ var platformProjectService = null;
+ switch (platform) {
+ case "android":
+ platformProjectService = this.$androidProjectService;
+ break;
+ case "ios":
+ platformProjectService = this.$iOSProjectService;
+ break;
+ }
+
+ this.executeFunctionByName(functionName, platformProjectService, [this.projectData]).wait();
}).future()();
}
+ private executeFunctionByName(functionName, context , args: any[] ): IFuture {
+ return (() => {
+ var namespaces = functionName.split(".");
+ var func = namespaces.pop();
+ for (var i = 0; i < namespaces.length; i++) {
+ context = context[namespaces[i]];
+ }
+ return context[func].apply(context, args).wait();
+ }).future()();
+ }
+
private getCustomAppPath(): string {
var customAppPath = options["copy-from"] || options["link-to"];
if(customAppPath) {
@@ -100,5 +218,226 @@ export class ProjectService implements IProjectService {
return customAppPath;
}
+
+ public ensureProject() {
+ if (this.projectData.projectDir === "" || !this.$fs.exists(this.projectData.projectFilePath).wait()) {
+ this.$errors.fail("No project found at or above '%s' and neither was a --path specified.", process.cwd());
+ }
+ }
+}
+$injector.register("projectService", ProjectService);
+
+class AndroidProjectService implements IPlatformProjectService {
+ private frameworkDir: string = null;
+
+ constructor(private $fs: IFileSystem,
+ private $errors: IErrors,
+ private $logger: ILogger,
+ private $childProcess: IChildProcess,
+ private $projectTemplatesService: IProjectTemplatesService,
+ private $propertiesParser: IPropertiesParser) { }
+
+ public createProject(projectData: IProjectData): IFuture {
+ return (() => {
+ this.frameworkDir = this.getFrameworkDir(projectData).wait();
+
+ var packageName = projectData.projectId;
+ var packageAsPath = packageName.replace(/\./g, path.sep);
+ var projectDir = path.join(projectData.projectDir, "platforms", "android");
+
+ this.validatePackageName(packageName);
+ this.validateProjectName(projectData.projectName);
+
+ this.checkRequirements().wait();
+
+ var targetApi = this.getTarget().wait();
+
+ // Log the values for project
+ this.$logger.trace("Creating NativeScript project for the Android platform");
+ this.$logger.trace("Path: %s", projectDir);
+ this.$logger.trace("Package: %s", projectData.projectId);
+ this.$logger.trace("Name: %s", projectData.projectName);
+ this.$logger.trace("Android target: %s", targetApi);
+
+ this.$logger.out("Copying template files...");
+
+ shell.cp("-r", path.join(this.frameworkDir, "assets"), projectDir);
+ shell.cp("-r", path.join(this.frameworkDir, "gen"), projectDir);
+ shell.cp("-r", path.join(this.frameworkDir, "libs"), projectDir);
+ shell.cp("-r", path.join(this.frameworkDir, "res"), projectDir);
+
+ shell.cp("-f", path.join(this.frameworkDir, ".project"), projectDir);
+ shell.cp("-f", path.join(this.frameworkDir, "AndroidManifest.xml"), projectDir);
+
+ // Create src folder
+ var activityDir = path.join(projectDir, 'src', packageAsPath);
+ this.$fs.createDirectory(activityDir).wait();
+
+ this.$fs.deleteDirectory(path.join(projectData.platformsDir, "android", "node_modules")).wait();
+
+ // Interpolate the activity name and package
+ var stringsFilePath = path.join(projectDir, 'res', 'values', 'strings.xml');
+ shell.sed('-i', /__NAME__/, projectData.projectName, stringsFilePath);
+ shell.sed('-i', /__TITLE_ACTIVITY__/, projectData.projectName, stringsFilePath);
+ shell.sed('-i', /__NAME__/, projectData.projectName, path.join(projectDir, '.project'));
+ shell.sed('-i', /__PACKAGE__/, packageName, path.join(projectDir, "AndroidManifest.xml"));
+
+ this.runAndroidUpdate(projectDir, targetApi).wait();
+
+ this.$logger.out("Project successfully created.");
+
+ }).future()();
+ }
+
+ public buildProject(projectData: IProjectData): IFuture {
+ return (() => {
+ var projectRoot = path.join(projectData.platformsDir, "android");
+ var buildConfiguration = options.release || "--debug";
+ var args = this.getAntArgs(buildConfiguration, projectRoot);
+
+ switch(buildConfiguration) {
+ case "--debug":
+ args[0] = "debug";
+ break;
+ case "--release":
+ args[0] = "release";
+ break;
+ default:
+ this.$errors.fail("Build option %s not recognized", buildConfiguration);
+ }
+
+ this.spawn('ant', args);
+
+ }).future()();
+ }
+
+ private spawn(command: string, args: string[], options?: any): void {
+ if(helpers.isWindows()) {
+ args.unshift('/s', '/c', command);
+ command = 'cmd';
+ }
+
+ this.$childProcess.spawn(command, args, {cwd: options, stdio: 'inherit'});
+ }
+
+ private getAntArgs(configuration: string, projectRoot: string): string[] {
+ var args = [configuration, "-f", path.join(projectRoot, "build.xml")];
+ return args;
+ }
+
+ private runAndroidUpdate(projectPath: string, targetApi: string): IFuture {
+ return (() => {
+ this.$childProcess.exec("android update project --subprojects --path " + projectPath + " --target " + targetApi).wait();
+ }).future()();
+ }
+
+ private validatePackageName(packageName: string): void {
+ //Make the package conform to Java package types
+ //Enforce underscore limitation
+ if (!/^[a-zA-Z]+(\.[a-zA-Z0-9][a-zA-Z0-9_]*)+$/.test(packageName)) {
+ this.$errors.fail("Package name must look like: com.company.Name");
+ }
+
+ //Class is a reserved word
+ if(/\b[Cc]lass\b/.test(packageName)) {
+ this.$errors.fail("class is a reserved word");
+ }
+ }
+
+ private validateProjectName(projectName: string): void {
+ if (projectName === '') {
+ this.$errors.fail("Project name cannot be empty");
+ }
+
+ //Classes in Java don't begin with numbers
+ if (/^[0-9]/.test(projectName)) {
+ this.$errors.fail("Project name must not begin with a number");
+ }
+ }
+
+ private getFrameworkDir(projectData: IProjectData): IFuture {
+ return(() => {
+ var androidFrameworkPath = this.$projectTemplatesService.installAndroidFramework(path.join(projectData.platformsDir, "android")).wait();
+ return path.join(androidFrameworkPath, "framework");
+ }).future()();
+ }
+
+ private getTarget(): IFuture {
+ return (() => {
+ var projectPropertiesFilePath = path.join(this.frameworkDir, "project.properties");
+
+ if (this.$fs.exists(projectPropertiesFilePath).wait()) {
+ var properties = this.$propertiesParser.createEditor(projectPropertiesFilePath).wait();
+ return properties.get("target");
+ }
+
+ return "";
+ }).future()();
+ }
+
+ private checkAnt(): IFuture {
+ return (() => {
+ try {
+ this.$childProcess.exec("ant -version").wait();
+ } catch(error) {
+ this.$errors.fail("Error executing commands 'ant', make sure you have ant installed and added to your PATH.")
+ }
+ return true;
+ }).future()();
+ }
+
+ private checkJava(): IFuture {
+ return (() => {
+ try {
+ this.$childProcess.exec("java -version").wait();
+ } catch(error) {
+ this.$errors.fail("%s\n Failed to run 'java', make sure your java environment is set up.\n Including JDK and JRE.\n Your JAVA_HOME variable is %s", error, process.env.JAVA_HOME);
+ }
+ return true;
+ }).future()();
+ }
+
+ private checkAndroid(): IFuture {
+ return (() => {
+ var validTarget = this.getTarget().wait();
+ try {
+ var output = this.$childProcess.exec('android list targets').wait();
+ } catch(error) {
+ if (error.match(/command\snot\sfound/)) {
+ this.$errors.fail("The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path.");
+ } else {
+ this.$errors.fail("An error occurred while listing Android targets");
+ }
+ }
+
+ if (!output.match(validTarget)) {
+ this.$errors.fail("Please install Android target %s the Android newest SDK). Make sure you have the latest Android tools installed as well. Run \"android\" from your command-line to install/update any missing SDKs or tools.",
+ validTarget.split('-')[1]);
+ }
+
+ return true;
+ }).future()();
+ }
+
+ private checkRequirements(): IFuture {
+ return (() => {
+ return this.checkAnt().wait() && this.checkAndroid().wait() && this.checkJava().wait();
+ }).future()();
+ }
+ }
+$injector.register("androidProjectService", AndroidProjectService);
+
+class iOSProjectService implements IPlatformProjectService {
+ public createProject(projectData: IProjectData): IFuture {
+ return (() => {
+
+ }).future()();
+ }
+
+ public buildProject(projectData: IProjectData): IFuture {
+ return (() => {
+
+ }).future()();
+ }
}
-$injector.register("projectService", ProjectService);
\ No newline at end of file
+$injector.register("iOSProjectService", iOSProjectService);
\ No newline at end of file
diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts
index 4ec6dbbec4..6e142c49cb 100644
--- a/lib/services/project-templates-service.ts
+++ b/lib/services/project-templates-service.ts
@@ -12,25 +12,33 @@ export class ProjectTemplatesService implements IProjectTemplatesService {
private static NPM_DEFAULT_TEMPLATE_NAME = "tns-template-hello-world";
private static NPM_LOAD_FAILED = "Failed to retrieve nativescript hello world application. Please try again a little bit later.";
+ private static NPM_ANDROID_BRIDGE_NAME = "tns-android";
+
public constructor(private $errors: IErrors,
private $logger: ILogger,
private $npm: INodePackageManager) { }
public get defaultTemplatePath(): IFuture {
- return this.getDefaultTemplatePath();
+ return this.installNpmPackage(ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME);
+ }
+
+ public installAndroidFramework(where?: string): IFuture {
+ return this.installNpmPackage(ProjectTemplatesService.NPM_ANDROID_BRIDGE_NAME, where);
}
- private getDefaultTemplatePath(): IFuture {
+ private installNpmPackage(packageName: string, where?: string): IFuture {
return (() => {
try {
this.$npm.load().wait();
- this.$npm.install(npm.cache, ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME).wait();
+ var location = where || npm.cache;
+ this.$npm.install(location, packageName).wait();
} catch (error) {
this.$logger.debug(error);
this.$errors.fail(ProjectTemplatesService.NPM_LOAD_FAILED);
}
- return path.join(npm.cache, "node_modules", ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME);
+ return path.join(location, "node_modules", packageName);
+
}).future()();
}
}
diff --git a/package.json b/package.json
index a71e0f49d6..5f38af64b9 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,8 @@
"underscore": "1.5.2",
"unzip": "0.1.9",
"yargs": "1.2.2",
- "npm": "1.4.10"
+ "npm": "1.4.10",
+ "properties-parser": "0.2.3"
},
"analyze": true,
"devDependencies": {