diff --git a/.gitignore b/.gitignore
index 69dce8499d..73461007f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,15 +23,8 @@ pids
logs
results
scratch/
-.idea/workspace.xml
-.idea/tasks.xml
-.idea/watcherTasks.xml
-
+.idea/
test-reports.xml
npm-debug.log
node_modules
-resources/App_Resources
-resources/Cordova
-resources/ItemTemplates
-resources/ProjectTemplates
diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts
index e7a290a099..8cc63b833d 100644
--- a/lib/bootstrap.ts
+++ b/lib/bootstrap.ts
@@ -1,4 +1,10 @@
-global._ = require("underscore");
-global.$injector = require("./common/lib/yok").injector;
+require("./common/bootstrap");
$injector.require("nativescript-cli", "./nativescript-cli");
+
+$injector.require("projectService", "./services/project-service");
+$injector.require("projectTemplatesService", "./services/project-templates-service");
+
+$injector.requireCommand("create", "./commands/create-project-command");
+
+$injector.require("npm", "./node-package-manager");
diff --git a/lib/commands/create-project-command.ts b/lib/commands/create-project-command.ts
new file mode 100644
index 0000000000..4fe0df3b2c
--- /dev/null
+++ b/lib/commands/create-project-command.ts
@@ -0,0 +1,12 @@
+///
+
+export class CreateProjectCommand implements ICommand {
+ constructor(private $projectService: IProjectService) { }
+
+ execute(args: string[]): IFuture {
+ return (() => {
+ this.$projectService.createProject(args[0], args[1]).wait();
+ }).future()();
+ }
+}
+$injector.registerCommand("create", CreateProjectCommand);
diff --git a/lib/common b/lib/common
index a4294787f9..33c883f1ab 160000
--- a/lib/common
+++ b/lib/common
@@ -1 +1 @@
-Subproject commit a4294787f9ce49b5de608ed02cfc08124594ab32
+Subproject commit 33c883f1abae2a5e5486a87f11df1d75e00e1dc8
diff --git a/lib/declarations.ts b/lib/declarations.ts
new file mode 100644
index 0000000000..f8bbfab6e1
--- /dev/null
+++ b/lib/declarations.ts
@@ -0,0 +1,5 @@
+interface INodePackageManager {
+ cache: string;
+ load(config?: any): IFuture;
+ install(where: string, what: string): IFuture;
+}
\ No newline at end of file
diff --git a/lib/definitions/npm.d.ts b/lib/definitions/npm.d.ts
new file mode 100644
index 0000000000..ce70940eab
--- /dev/null
+++ b/lib/definitions/npm.d.ts
@@ -0,0 +1,5 @@
+ declare module "npm" {
+ var cache: string;
+ var commands: any[];
+ function load(config: Object, callback: (err: any, data: any) => void);
+}
\ No newline at end of file
diff --git a/lib/definitions/osenv.d.ts b/lib/definitions/osenv.d.ts
new file mode 100644
index 0000000000..e02de9be32
--- /dev/null
+++ b/lib/definitions/osenv.d.ts
@@ -0,0 +1,3 @@
+declare module "osenv" {
+ function home();
+}
\ No newline at end of file
diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts
new file mode 100644
index 0000000000..20934af393
--- /dev/null
+++ b/lib/definitions/project.d.ts
@@ -0,0 +1,7 @@
+interface IProjectService {
+ createProject(projectName: string, projectId: string): IFuture;
+}
+
+interface IProjectTemplatesService {
+ defaultTemplatePath: IFuture;
+}
\ No newline at end of file
diff --git a/lib/definitions/shelljs.d.ts b/lib/definitions/shelljs.d.ts
new file mode 100644
index 0000000000..99ea4dc36e
--- /dev/null
+++ b/lib/definitions/shelljs.d.ts
@@ -0,0 +1,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);
+}
\ No newline at end of file
diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts
index bbb1df0206..e2600aa613 100644
--- a/lib/nativescript-cli.ts
+++ b/lib/nativescript-cli.ts
@@ -1,10 +1,16 @@
///
import Fiber = require("fibers");
+import Future = require("fibers/future");
+import path = require("path");
require("./bootstrap");
+require("./options");
-var fiber = Fiber(() => {
-});
-global.__main_fiber__ = fiber; // leak fiber to prevent it from being GC'd and thus corrupting V8
-fiber.run();
\ No newline at end of file
+import errors = require("./common/errors");
+errors.installUncaughtExceptionListener();
+
+$injector.register("config", {"CI_LOGGER": false});
+
+var dispatcher = $injector.resolve("dispatcher");
+dispatcher.runMainFiber();
diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts
new file mode 100644
index 0000000000..4a12295114
--- /dev/null
+++ b/lib/node-package-manager.ts
@@ -0,0 +1,36 @@
+///
+
+import npm = require("npm");
+import Future = require("fibers/future");
+import shell = require("shelljs");
+
+export class NodePackageManager implements INodePackageManager {
+ public get cache(): string {
+ return npm.cache;
+ }
+
+ public load(config?: any): IFuture {
+ var future = new Future();
+ npm.load(config, (err) => {
+ if(err) {
+ future.throw(err);
+ } else {
+ future.return();
+ }
+ });
+ return future;
+ }
+
+ public install(where: string, what: string): IFuture {
+ var future = new Future();
+ npm.commands["install"](where, what, (err, data) => {
+ if(err) {
+ future.throw(err);
+ } else {
+ future.return(data);
+ }
+ });
+ return future;
+ }
+}
+$injector.register("npm", NodePackageManager);
\ No newline at end of file
diff --git a/lib/options.ts b/lib/options.ts
new file mode 100644
index 0000000000..56a48f873d
--- /dev/null
+++ b/lib/options.ts
@@ -0,0 +1,29 @@
+///
+
+import path = require("path");
+import helpers = require("./common/helpers");
+import osenv = require("osenv");
+
+var knownOpts:any = {
+ "log" : String,
+ "verbose" : Boolean,
+ "path" : String,
+ "copy-from": String,
+ "link-to": String,
+ "version": Boolean,
+ "help": Boolean
+ },
+ shorthands = {
+ "v" : "verbose",
+ "p" : "path"
+ };
+
+var defaultProfileDir = path.join(osenv.home(), ".nativescript-cli");
+var parsed = helpers.getParsedOptions(knownOpts, shorthands, defaultProfileDir);
+
+Object.keys(parsed).forEach((opt) => exports[opt] = parsed[opt]);
+
+exports.knownOpts = knownOpts;
+
+declare var exports:any;
+export = exports;
\ No newline at end of file
diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts
new file mode 100644
index 0000000000..5511e20d97
--- /dev/null
+++ b/lib/services/project-service.ts
@@ -0,0 +1,104 @@
+///
+
+import path = require("path");
+import options = require("./../options");
+import shell = require("shelljs");
+import osenv = require("osenv");
+
+export class ProjectService implements IProjectService {
+ private static DEFAULT_ID = "com.telerik.tns.HelloWorld";
+ private static DEFAULT_NAME = "HelloNativescript";
+ private static APP_FOLDER_NAME = "app";
+
+ constructor(private $logger: ILogger,
+ private $errors: IErrors,
+ private $fs: IFileSystem,
+ private $projectTemplatesService: IProjectTemplatesService) { }
+
+ public createProject(projectName: string, projectId: string): IFuture {
+ return(() => {
+ var projectDir = path.resolve(options.path || ".");
+
+ projectId = projectId || ProjectService.DEFAULT_ID;
+ projectName = projectName || ProjectService.DEFAULT_NAME;
+
+ projectDir = path.join(projectDir, projectName);
+ this.$fs.createDirectory(projectDir).wait();
+
+ var customAppPath = this.getCustomAppPath();
+ if(customAppPath) {
+ customAppPath = path.resolve(customAppPath);
+ }
+
+ if(this.$fs.exists(projectDir).wait() && !this.$fs.isEmptyDir(projectDir).wait()) {
+ 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);
+
+ var appDirectory = path.join(projectDir, ProjectService.APP_FOLDER_NAME);
+ var appPath: string = null;
+
+ if(customAppPath) {
+ this.$logger.trace("Using custom app from %s", customAppPath);
+
+ // Make sure that the source app/ is not a direct ancestor of a target app/
+ var relativePathFromSourceToTarget = path.relative(customAppPath, appDirectory);
+ var doesRelativePathGoUpAtLeastOneDir = relativePathFromSourceToTarget.split(path.sep)[0] == "..";
+ if(!doesRelativePathGoUpAtLeastOneDir) {
+ this.$errors.fail("Project dir %s must not be created at/inside the template used to create the project %s.", projectDir, customAppPath);
+ }
+ this.$logger.trace("Copying custom app into %s", appDirectory);
+ appPath = customAppPath;
+ } else {
+ // No custom app - use nativescript hello world application
+ this.$logger.trace("Using NativeScript hello world application");
+ var defaultTemplatePath = this.$projectTemplatesService.defaultTemplatePath.wait();
+ this.$logger.trace("Copying Nativescript hello world application into %s", appDirectory);
+ appPath = defaultTemplatePath;
+ }
+
+ this.createProjectCore(projectDir, appPath, false).wait();
+ }).future()();
+ }
+
+ private createProjectCore(projectDir: string, appPath: string, symlink?: boolean): IFuture {
+ return (() => {
+ if(!this.$fs.exists(projectDir).wait()) {
+ this.$fs.createDirectory(projectDir).wait();
+ }
+ if(symlink) {
+ // TODO: Implement support for symlink the app folder instead of copying
+ } else {
+ var appDir = path.join(projectDir, ProjectService.APP_FOLDER_NAME);
+ this.$fs.createDirectory(appDir).wait();
+ shell.cp('-R', path.join(appPath, "*"), appDir);
+ }
+ this.createBasicProjectStructure(projectDir).wait();
+ }).future()();
+ }
+
+ private createBasicProjectStructure(projectDir: 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();
+ }).future()();
+ }
+
+ private getCustomAppPath(): string {
+ var customAppPath = options["copy-from"] || options["link-to"];
+ if(customAppPath) {
+ if(customAppPath.indexOf("http") >= 0) {
+ this.$errors.fail("Only local paths for custom app are supported.");
+ }
+
+ if(customAppPath.substr(0, 1) === '~') {
+ customAppPath = path.join(osenv.home(), customAppPath.substr(1));
+ }
+ }
+
+ return customAppPath;
+ }
+}
+$injector.register("projectService", ProjectService);
\ No newline at end of file
diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts
new file mode 100644
index 0000000000..4ec6dbbec4
--- /dev/null
+++ b/lib/services/project-templates-service.ts
@@ -0,0 +1,37 @@
+///
+
+import util = require("util");
+import path = require("path");
+import shell = require("shelljs");
+import npm = require("npm");
+var options = require("./../options");
+var helpers = require("./../common/helpers");
+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.";
+
+ public constructor(private $errors: IErrors,
+ private $logger: ILogger,
+ private $npm: INodePackageManager) { }
+
+ public get defaultTemplatePath(): IFuture {
+ return this.getDefaultTemplatePath();
+ }
+
+ private getDefaultTemplatePath(): IFuture {
+ return (() => {
+ try {
+ this.$npm.load().wait();
+ this.$npm.install(npm.cache, ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME).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);
+ }).future()();
+ }
+}
+$injector.register("projectTemplatesService", ProjectTemplatesService);
\ No newline at end of file
diff --git a/package.json b/package.json
index 7723e21b9b..a71e0f49d6 100644
--- a/package.json
+++ b/package.json
@@ -23,11 +23,15 @@
],
"dependencies": {
"fibers": "https://github.com/icenium/node-fibers/tarball/master",
+ "filesize": "2.0.3",
+ "progress-stream": "0.5.0",
"log4js": "0.6.9",
+ "osenv": "0.1.0",
"tabtab": "https://github.com/tailsu/node-tabtab/tarball/master",
"underscore": "1.5.2",
"unzip": "0.1.9",
- "yargs": "1.2.2"
+ "yargs": "1.2.2",
+ "npm": "1.4.10"
},
"analyze": true,
"devDependencies": {