diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts
index 7cf338d9b1..e7084481c1 100644
--- a/lib/bootstrap.ts
+++ b/lib/bootstrap.ts
@@ -2,10 +2,14 @@ require("./common/bootstrap");
$injector.require("nativescript-cli", "./nativescript-cli");
+$injector.require("projectData", "./services/project-service");
$injector.require("projectService", "./services/project-service");
-$injector.require("androidProjectService", "./services/project-service");
-$injector.require("iOSProjectService", "./services/project-service");
+$injector.require("androidProjectService", "./services/android-project-service");
+$injector.require("iOSProjectService", "./services/ios-project-service");
+
$injector.require("projectTemplatesService", "./services/project-templates-service");
+
+$injector.require("platformsData", "./services/platform-service");
$injector.require("platformService", "./services/platform-service");
$injector.requireCommand("create", "./commands/create-project");
diff --git a/lib/constants.ts b/lib/constants.ts
new file mode 100644
index 0000000000..804e955644
--- /dev/null
+++ b/lib/constants.ts
@@ -0,0 +1,8 @@
+///
+
+export var APP_FOLDER_NAME = "app";
+export var DEFAULT_PROJECT_ID = "com.telerik.tns.HelloWorld";
+export var DEFAULT_PROJECT_NAME = "HelloNativescript";
+export var APP_RESOURCES_FOLDER_NAME = "App_Resources";
+export var PROJECT_FRAMEWORK_FOLDER_NAME = "framework";
+
diff --git a/lib/declarations.ts b/lib/declarations.ts
index ea88f0bcdd..55adf5b3db 100644
--- a/lib/declarations.ts
+++ b/lib/declarations.ts
@@ -1,7 +1,7 @@
interface INodePackageManager {
cache: string;
load(config?: any): IFuture;
- install(where: string, what: string): IFuture;
+ install(packageName: string, pathToSave?: string): IFuture;
}
interface IPropertiesParser {
diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts
index 0a774170bb..a0fce8fbe9 100644
--- a/lib/definitions/platform.d.ts
+++ b/lib/definitions/platform.d.ts
@@ -7,6 +7,16 @@ interface IPlatformService {
buildPlatform(platform: string): IFuture;
}
-interface IPlatformCapabilities {
+interface IPlatformData {
+ frameworkPackageName: string;
+ platformProjectService: IPlatformProjectService;
+ projectRoot: string;
+ normalizedPlatformName: string;
targetedOS?: string[];
-}
\ No newline at end of file
+}
+
+interface IPlatformsData {
+ platformsNames: string[];
+ getPlatformData(platform: string): IPlatformData;
+}
+
diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts
index a0eb4d8cfb..ed7d44936b 100644
--- a/lib/definitions/project.d.ts
+++ b/lib/definitions/project.d.ts
@@ -1,26 +1,25 @@
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;
+ projectName: string;
platformsDir: string;
projectFilePath: string;
projectId?: string;
- projectName?: string;
}
interface IProjectTemplatesService {
defaultTemplatePath: IFuture;
- installAndroidFramework(whereToInstall: string): IFuture
+}
+
+interface IPlatformProjectService {
+ validate(): IFuture;
+ createProject(projectRoot: string, frameworkDir: string): IFuture;
+ interpolateData(projectRoot: string): void;
+ afterCreateProject(projectRoot: string): void;
+ prepareProject(normalizedPlatformName: string, platforms: string[]): IFuture;
+ buildProject(projectRoot: string): IFuture;
}
\ No newline at end of file
diff --git a/lib/definitions/shelljs.d.ts b/lib/definitions/shelljs.d.ts
index 0b84163555..518e2063e2 100644
--- a/lib/definitions/shelljs.d.ts
+++ b/lib/definitions/shelljs.d.ts
@@ -1,5 +1,6 @@
declare module "shelljs" {
function cp(arg: string, sourcePath: string, destinationPath: string): void;
+ 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;
diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts
index 1f0c815e71..a13132516e 100644
--- a/lib/node-package-manager.ts
+++ b/lib/node-package-manager.ts
@@ -3,8 +3,14 @@
import npm = require("npm");
import Future = require("fibers/future");
import shell = require("shelljs");
+import path = require("path");
export class NodePackageManager implements INodePackageManager {
+ private static NPM_LOAD_FAILED = "Failed to retrieve data from npm. Please try again a little bit later.";
+
+ constructor(private $logger: ILogger,
+ private $errors: IErrors) { }
+
public get cache(): string {
return npm.cache;
}
@@ -21,7 +27,21 @@ export class NodePackageManager implements INodePackageManager {
return future;
}
- public install(where: string, what: string): IFuture {
+ public install(packageName: string, pathToSave?: string): IFuture {
+ return (() => {
+ var action = (packageName: string) => {
+ pathToSave = pathToSave || npm.cache;
+ this.installCore(pathToSave, packageName).wait();
+ };
+
+ this.tryExecuteAction(action, packageName).wait();
+
+ return path.join(pathToSave, "node_modules", packageName);
+
+ }).future()();
+ }
+
+ private installCore(where: string, what: string): IFuture {
var future = new Future();
npm.commands["install"](where, what, (err, data) => {
if(err) {
@@ -32,5 +52,17 @@ export class NodePackageManager implements INodePackageManager {
});
return future;
}
+
+ private tryExecuteAction(action: (...args: any[]) => void, ...args: any[]): IFuture {
+ return (() => {
+ try {
+ this.load().wait(); // It's obligatory to execute load before whatever npm function
+ action.apply(null, args);
+ } catch(error) {
+ this.$logger.debug(error);
+ this.$errors.fail(NodePackageManager.NPM_LOAD_FAILED);
+ }
+ }).future()();
+ }
}
$injector.register("npm", NodePackageManager);
diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts
new file mode 100644
index 0000000000..d289d7cf07
--- /dev/null
+++ b/lib/services/android-project-service.ts
@@ -0,0 +1,212 @@
+///
+import path = require("path");
+import shell = require("shelljs");
+import util = require("util");
+import options = require("./../options");
+import helpers = require("./../common/helpers");
+import constants = require("./../constants");
+
+class AndroidProjectService implements IPlatformProjectService {
+ constructor(private $fs: IFileSystem,
+ private $errors: IErrors,
+ private $logger: ILogger,
+ private $childProcess: IChildProcess,
+ private $projectData: IProjectData,
+ private $propertiesParser: IPropertiesParser) { }
+
+ public validate(): IFuture {
+ return (() => {
+ this.validatePackageName(this.$projectData.projectId);
+ this.validateProjectName(this.$projectData.projectName);
+
+ this.checkAnt().wait() && this.checkAndroid().wait() && this.checkJava().wait();
+ }).future()();
+ }
+
+ public createProject(projectRoot: string, frameworkDir: string): IFuture {
+ return (() => {
+ this.validateAndroidTarget(frameworkDir); // We need framework to be installed to validate android target so we can't call this method in validate()
+
+ var paths = "assets gen libs res".split(' ').map(p => path.join(frameworkDir, p));
+ shell.cp("-r", paths, projectRoot);
+
+ paths = ".project AndroidManifest.xml project.properties".split(' ').map(p => path.join(frameworkDir, p));
+ shell.cp("-f", paths, projectRoot);
+
+ // Create src folder
+ var packageName = this.$projectData.projectId;
+ var packageAsPath = packageName.replace(/\./g, path.sep);
+ var activityDir = path.join(projectRoot, 'src', packageAsPath);
+ this.$fs.createDirectory(activityDir).wait();
+
+ }).future()();
+ }
+
+ public interpolateData(projectRoot: string): void {
+ // Interpolate the activity name and package
+ var stringsFilePath = path.join(projectRoot, 'res', 'values', 'strings.xml');
+ shell.sed('-i', /__NAME__/, this.$projectData.projectName, stringsFilePath);
+ shell.sed('-i', /__TITLE_ACTIVITY__/, this.$projectData.projectName, stringsFilePath);
+ shell.sed('-i', /__NAME__/, this.$projectData.projectName, path.join(projectRoot, '.project'));
+ shell.sed('-i', /__PACKAGE__/, this.$projectData.projectId, path.join(projectRoot, "AndroidManifest.xml"));
+ }
+
+ public afterCreateProject(projectRoot: string) {
+ var targetApi = this.getTarget(projectRoot).wait();
+ this.$logger.trace("Android target: %s", targetApi);
+ this.runAndroidUpdate(projectRoot, targetApi).wait();
+ }
+
+ 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, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME);
+ shell.cp("-r", path.join(this.$projectData.projectDir, constants.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, constants.APP_FOLDER_NAME));
+ var platformsAsString = platforms.join("|");
+
+ _.each(files, fileName => {
+ var platformInfo = AndroidProjectService.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()();
+ }
+
+ public buildProject(projectRoot: string): IFuture {
+ return (() => {
+ var buildConfiguration = options.release ? "release" : "debug";
+ var args = this.getAntArgs(buildConfiguration, projectRoot);
+ 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 (() => {
+ var args = [
+ "--path", projectPath,
+ "--target", targetApi
+ ];
+
+ this.spawn("android update project", args);
+ }).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 validateAndroidTarget(frameworkDir: string) {
+ var validTarget = this.getTarget(frameworkDir).wait();
+ var output = this.$childProcess.exec('android list targets').wait();
+ 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]);
+ }
+ }
+
+ private getTarget(projectRoot: string): IFuture {
+ return (() => {
+ var projectPropertiesFilePath = path.join(projectRoot, "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.")
+ }
+ }).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);
+ }
+ }).future()();
+ }
+
+ private checkAndroid(): IFuture {
+ return (() => {
+ try {
+ 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");
+ }
+ }
+ }).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;
+ }
+}
+$injector.register("androidProjectService", AndroidProjectService);
\ No newline at end of file
diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts
new file mode 100644
index 0000000000..6aacd045b7
--- /dev/null
+++ b/lib/services/ios-project-service.ts
@@ -0,0 +1,35 @@
+///
+
+class IOSProjectService implements IPlatformProjectService {
+ public validate(): IFuture {
+ return (() => {
+ }).future()();
+ }
+
+ public interpolateData(): void {
+
+ }
+
+ public afterCreateProject(): void {
+
+ }
+
+ public createProject(): IFuture {
+ return (() => {
+
+ }).future()();
+ }
+
+ public prepareProject(normalizedPlatformName: string, platforms: string[]): IFuture {
+ return (() => {
+
+ }).future()();
+ }
+
+ public buildProject(): IFuture {
+ return (() => {
+
+ }).future()();
+ }
+}
+$injector.register("iOSProjectService", IOSProjectService);
\ No newline at end of file
diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts
index 39214865dc..065b99f6b1 100644
--- a/lib/services/platform-service.ts
+++ b/lib/services/platform-service.ts
@@ -1,28 +1,53 @@
///
import path = require("path");
+import shell = require("shelljs");
import util = require("util");
+import constants = require("./../constants");
import helpers = require("./../common/helpers");
-export class PlatformService implements IPlatformService {
- private platformCapabilities: { [key: string]: IPlatformCapabilities } = {
- ios: {
- targetedOS: ['darwin']
- },
- android: {
+class PlatformsData implements IPlatformsData {
+ private platformsData = {};
+
+ constructor($projectData: IProjectData,
+ $androidProjectService: IPlatformProjectService,
+ $iOSProjectService: IPlatformProjectService) {
+
+ this.platformsData = {
+ ios: {
+ frameworkPackageName: "tns-ios",
+ normalizedPlatformName: "iOS",
+ platformProjectService: $iOSProjectService,
+ projectRoot: "",
+ targetedOS: ['darwin']
+ },
+ android: {
+ frameworkPackageName: "tns-android",
+ normalizedPlatformName: "Android",
+ platformProjectService: $androidProjectService,
+ projectRoot: path.join($projectData.platformsDir, "android")
+ }
}
- };
+ }
- private platformNames = [];
+ public get platformsNames() {
+ return Object.keys(this.platformsData);
+ }
- constructor(private $errors: IErrors,
- private $fs: IFileSystem,
- private $projectService: IProjectService) {
- this.platformNames = Object.keys(this.platformCapabilities);
+ public getPlatformData(platform): IPlatformData {
+ return this.platformsData[platform];
}
+}
+$injector.register("platformsData", PlatformsData);
- public getCapabilities(platform: string): IPlatformCapabilities {
- return this.platformCapabilities[platform];
+export class PlatformService implements IPlatformService {
+ constructor(private $errors: IErrors,
+ private $fs: IFileSystem,
+ private $logger: ILogger,
+ private $npm: INodePackageManager,
+ private $projectService: IProjectService,
+ private $projectData: IProjectData,
+ private $platformsData: IPlatformsData) {
}
public addPlatforms(platforms: string[]): IFuture {
@@ -33,7 +58,7 @@ export class PlatformService implements IPlatformService {
this.$projectService.ensureProject();
- var platformsDir = this.$projectService.projectData.platformsDir;
+ var platformsDir = this.$projectData.platformsDir;
this.$fs.ensureDirectoryExists(platformsDir).wait();
_.each(platforms, platform => {
@@ -49,7 +74,7 @@ export class PlatformService implements IPlatformService {
this.validatePlatform(platform);
- var platformPath = path.join(this.$projectService.projectData.platformsDir, platform);
+ var platformPath = path.join(this.$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
@@ -58,44 +83,66 @@ export class PlatformService implements IPlatformService {
}
// Copy platform specific files in platforms dir
- this.$projectService.createPlatformSpecificProject(platform).wait();
+ var platformData = this.$platformsData.getPlatformData(platform);
+ var platformProjectService = platformData.platformProjectService;
+
+ platformProjectService.validate().wait();
+
+ // Log the values for project
+ this.$logger.trace("Creating NativeScript project for the %s platform", platform);
+ this.$logger.trace("Path: %s", platformData.projectRoot);
+ this.$logger.trace("Package: %s", this.$projectData.projectId);
+ this.$logger.trace("Name: %s", this.$projectData.projectName);
+
+ this.$logger.out("Copying template files...");
+
+ // get path to downloaded framework package
+ var frameworkDir = this.$npm.install(this.$platformsData.getPlatformData(platform).frameworkPackageName,
+ path.join(this.$projectData.platformsDir, platform)).wait();
+ frameworkDir = path.join(frameworkDir, constants.PROJECT_FRAMEWORK_FOLDER_NAME);
+
+ platformProjectService.createProject(platformData.projectRoot, frameworkDir).wait();
+
+ // Need to remove unneeded node_modules folder
+ this.$fs.deleteDirectory(path.join("../", frameworkDir)).wait();
+
+ platformProjectService.interpolateData(platformData.projectRoot);
+ platformProjectService.afterCreateProject(platformData.projectRoot);
+
+ this.$logger.out("Project successfully created.");
}).future()();
}
public getInstalledPlatforms(): IFuture {
return(() => {
- if(!this.$fs.exists(this.$projectService.projectData.platformsDir).wait()) {
+ if(!this.$fs.exists(this.$projectData.platformsDir).wait()) {
return [];
}
- var subDirs = this.$fs.readDirectory(this.$projectService.projectData.platformsDir).wait();
- return _.filter(subDirs, p => { return this.platformNames.indexOf(p) > -1; });
+ var subDirs = this.$fs.readDirectory(this.$projectData.platformsDir).wait();
+ return _.filter(subDirs, p => this.$platformsData.platformsNames.indexOf(p) > -1);
}).future()();
}
public getAvailablePlatforms(): IFuture {
return (() => {
var installedPlatforms = this.getInstalledPlatforms().wait();
- return _.filter(this.platformNames, p => {
+ return _.filter(this.$platformsData.platformsNames, 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();
+ var platformData = this.$platformsData.getPlatformData(platform);
+ var platformProjectService = platformData.platformProjectService;
+
+ platformProjectService.prepareProject(platformData.normalizedPlatformName, this.$platformsData.platformsNames).wait();
}).future()();
}
@@ -104,13 +151,21 @@ export class PlatformService implements IPlatformService {
platform = platform.toLocaleLowerCase();
this.validatePlatform(platform);
- this.$projectService.buildProject(platform).wait();
+ var platformData = this.$platformsData.getPlatformData(platform);
+ platformData.platformProjectService.buildProject(platformData.projectRoot).wait();
+ this.$logger.out("Project successfully built");
+ }).future()();
+ }
+
+ public runPlatform(platform: string): IFuture {
+ return (() => {
+
}).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));
+ this.$errors.fail("Invalid platform %s. Valid platforms are %s.", platform, helpers.formatListOfNames(this.$platformsData.platformsNames));
}
if (!this.isPlatformSupportedForOS(platform)) {
@@ -119,12 +174,11 @@ export class PlatformService implements IPlatformService {
}
private isValidPlatform(platform: string) {
- return this.platformCapabilities[platform];
+ return this.$platformsData.getPlatformData(platform);
}
private isPlatformSupportedForOS(platform: string): boolean {
- var platformCapabilities = this.getCapabilities(platform);
- var targetedOS = platformCapabilities.targetedOS;
+ var targetedOS = this.$platformsData.getPlatformData(platform).targetedOS;
if(!targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0) {
return true;
@@ -132,16 +186,5 @@ export class PlatformService implements IPlatformService {
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
+$injector.register("platformService", PlatformService);
diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts
index 1aee2c152e..8d709b1a2b 100644
--- a/lib/services/project-service.ts
+++ b/lib/services/project-service.ts
@@ -1,62 +1,61 @@
///
-import path = require("path");
import options = require("./../options");
-import shell = require("shelljs");
import osenv = require("osenv");
+import path = require("path");
+import shell = require("shelljs");
import util = require("util");
+import constants = require("./../constants");
import helpers = require("./../common/helpers");
-export class ProjectService implements IProjectService {
- 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";
+class ProjectData implements IProjectData {
+ public projectDir: string;
+ public platformsDir: string;
+ public projectFilePath: string;
+ public projectId: string;
+ public projectName: string;
- public projectData: IProjectData = null;
-
- constructor(private $logger: ILogger,
- private $errors: IErrors,
- private $fs: IFileSystem,
- private $projectTemplatesService: IProjectTemplatesService,
- private $androidProjectService: IPlatformProjectService,
- private $iOSProjectService: IPlatformProjectService,
+ constructor(private $fs: IFileSystem,
private $projectHelper: IProjectHelper,
private $config) {
- this.projectData = this.getProjectData().wait();
+ this.initializeProjectData().wait();
}
- private getProjectData(): IFuture {
+ private initializeProjectData(): 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);
+ this.projectDir = projectDir;
+ this.projectName = path.basename(projectDir);
+ this.platformsDir = path.join(projectDir, "platforms");
+ this.projectFilePath = path.join(projectDir, this.$config.PROJECT_FILE_NAME);
+
+ if (this.$fs.exists(this.projectFilePath).wait()) {
+ var fileContent = this.$fs.readJson(this.projectFilePath).wait();
+ this.projectId = fileContent.id;
}
}
- return projectData;
- }).future()();
+ }).future()();
}
+}
+$injector.register("projectData", ProjectData);
+
+export class ProjectService implements IProjectService {
+ constructor(private $logger: ILogger,
+ private $errors: IErrors,
+ private $fs: IFileSystem,
+ private $projectTemplatesService: IProjectTemplatesService,
+ private $projectData: IProjectData,
+ private $config) { }
public createProject(projectName: string, projectId: string): IFuture {
return(() => {
var projectDir = path.resolve(options.path || ".");
- projectId = projectId || ProjectService.DEFAULT_PROJECT_ID;
- projectName = projectName || ProjectService.DEFAULT_PROJECT_NAME;
+ projectId = projectId || constants.DEFAULT_PROJECT_ID;
+ projectName = projectName || constants.DEFAULT_PROJECT_NAME;
projectDir = path.join(projectDir, projectName);
this.$fs.createDirectory(projectDir).wait();
@@ -72,7 +71,7 @@ export class ProjectService implements IProjectService {
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 appDirectory = path.join(projectDir, constants.APP_FOLDER_NAME);
var appPath: string = null;
if(customAppPath) {
@@ -106,7 +105,7 @@ export class ProjectService implements IProjectService {
if(symlink) {
// TODO: Implement support for symlink the app folder instead of copying
} else {
- var appDir = path.join(projectDir, ProjectService.APP_FOLDER_NAME);
+ var appDir = path.join(projectDir, constants.APP_FOLDER_NAME);
this.$fs.createDirectory(appDir).wait();
shell.cp('-R', path.join(appPath, "*"), appDir);
}
@@ -125,89 +124,10 @@ export class ProjectService implements IProjectService {
}).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) {
- if(customAppPath.indexOf("http") >= 0) {
+ if(customAppPath.indexOf("http://") === 0) {
this.$errors.fail("Only local paths for custom app are supported.");
}
@@ -220,224 +140,10 @@ export class ProjectService implements IProjectService {
}
public ensureProject() {
- if (this.projectData.projectDir === "" || !this.$fs.exists(this.projectData.projectFilePath).wait()) {
+ 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("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 6e142c49cb..e70bfe69f0 100644
--- a/lib/services/project-templates-service.ts
+++ b/lib/services/project-templates-service.ts
@@ -10,36 +10,11 @@ import Future = require("fibers/future");
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 constructor(private $npm: INodePackageManager) { }
public get defaultTemplatePath(): IFuture {
- return this.installNpmPackage(ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME);
- }
-
- public installAndroidFramework(where?: string): IFuture {
- return this.installNpmPackage(ProjectTemplatesService.NPM_ANDROID_BRIDGE_NAME, where);
- }
-
- private installNpmPackage(packageName: string, where?: string): IFuture {
- return (() => {
- try {
- this.$npm.load().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(location, "node_modules", packageName);
-
- }).future()();
+ return this.$npm.install(ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME);
}
}
$injector.register("projectTemplatesService", ProjectTemplatesService);
\ No newline at end of file
diff --git a/package.json b/package.json
index 5f38af64b9..bdc41a8f67 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,11 @@
"unzip": "0.1.9",
"yargs": "1.2.2",
"npm": "1.4.10",
- "properties-parser": "0.2.3"
+ "properties-parser": "0.2.3",
+ "watchr": "2.4.11",
+ "rimraf": "2.2.6",
+ "mkdirp": "0.3.5",
+ "shelljs": "0.3.0"
},
"analyze": true,
"devDependencies": {