Skip to content

Create project command #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 8 additions & 2 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -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("nodePackageManager", "./node-package-manager");
12 changes: 12 additions & 0 deletions lib/commands/create-project-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
///<reference path="../.d.ts"/>

export class CreateProjectCommand implements ICommand {
constructor(private $projectService: IProjectService) { }

execute(args: string[]): IFuture<void> {
return (() => {
this.$projectService.createProject(args[0], args[1]).wait();
}).future<void>()();
}
}
$injector.registerCommand("create", CreateProjectCommand);
5 changes: 5 additions & 0 deletions lib/declarations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface INodePackageManager {
load(config: any): IFuture<void>;
executeCommand(command: string, arguments: string[]): IFuture<any>;
cache: string;
}
5 changes: 5 additions & 0 deletions lib/definitions/npm.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module "npm" {
var cache: string;
var commands: any[];
function load(config: Object, callback: (err: any, data: any) => void);
}
3 changes: 3 additions & 0 deletions lib/definitions/osenv.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module "osenv" {
function home();
}
7 changes: 7 additions & 0 deletions lib/definitions/project.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface IProjectService {
createProject(projectName: string, projectId: string): IFuture<void>;
}

interface IProjectTemplatesService {
defaultTemplatePath: IFuture<string>;
}
5 changes: 5 additions & 0 deletions lib/definitions/shelljs.d.ts
Original file line number Diff line number Diff line change
@@ -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);
}
3 changes: 3 additions & 0 deletions lib/definitions/tarball-extract.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module "tarball-extract" {
function extractTarball(sourceFile: string, destinationDir: string, callback: (err: any, data: any) => void);
}
18 changes: 18 additions & 0 deletions lib/nativescript-cli.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
///<reference path=".d.ts"/>

import Fiber = require("fibers");
import Future = require("fibers/future");
import path = require("path");

require("./bootstrap");
require("./options");

import errors = require("./common/errors");
errors.installUncaughtExceptionListener();

$injector.register("config", {"CI_LOGGER": false});

var fiber = Fiber(() => {
var commandDispatcher = $injector.resolve("commandDispatcher");

if (process.argv[2] === "completion") {
commandDispatcher.completeCommand();
} else {
commandDispatcher.dispatchCommand({}).wait();
}

$injector.dispose();
Future.assertNoFutureLeftBehind();
});
global.__main_fiber__ = fiber; // leak fiber to prevent it from being GC'd and thus corrupting V8
fiber.run();
36 changes: 36 additions & 0 deletions lib/node-package-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
///<reference path=".d.ts"/>

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<void> {
var future = new Future<void>();
npm.load(config, (err) => {
if(err) {
future.throw(err);
} else {
future.return();
}
});
return future;
}

public executeCommand(command: string, arguments: string[]): IFuture<any> {
var future = new Future<any>();
npm.commands[command](arguments, (err, data) => {
if(err) {
future.throw(err);
} else {
future.return(data);
}
});
return future;
}
}
$injector.register("nodePackageManager", NodePackageManager);
50 changes: 50 additions & 0 deletions lib/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
///<reference path=".d.ts"/>

import path = require("path");

var yargs: any = require("yargs");

var knownOpts:any = {
"log" : String,
"verbose" : Boolean,
"path" : String,
"copy-from": String,
"link-to": String,
"version": Boolean,
"help": Boolean
},
shorthands = {
"v" : "verbose",
"p" : "path"
};

Object.keys(knownOpts).forEach((opt) => {
var type = knownOpts[opt];
if (type === String) {
yargs.string(opt);
} else if (type === Boolean) {
yargs.boolean(opt);
}
});

Object.keys(shorthands).forEach((key) => {
yargs.alias(key, shorthands[key]);
});

var parsed = yargs.argv,
defaultProfileDir = path.join(process.env.USERPROFILE || process.env.HOME || process.env.HOMEPATH, ".nativescript-cli");

Object.keys(parsed).forEach((opt) => {
if (knownOpts[opt] !== Boolean && typeof(parsed[opt]) === 'boolean') {
delete parsed[opt];
}
});

parsed["profile-dir"] = parsed["profile-dir"] || defaultProfileDir;

Object.keys(parsed).forEach((opt) => exports[opt] = parsed[opt]);

exports.knownOpts = knownOpts;

declare var exports:any;
export = exports;
111 changes: 111 additions & 0 deletions lib/services/project-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
///<reference path="../.d.ts"/>

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<void> {
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.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<void>()();
}

private createProjectCore(projectDir: string, appPath: string, symlink?: boolean): IFuture<void> {
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<void>()();
}

private createBasicProjectStructure(projectDir: string): IFuture<void> {
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<void>()();
}

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;
}

private isEmptyDir(directoryPath: string): IFuture<boolean> {
return(() => {
var directoryContent = this.$fs.readDirectory(directoryPath).wait();
return directoryContent.length === 0;
}).future<boolean>()();
}
}
$injector.register("projectService", ProjectService);
43 changes: 43 additions & 0 deletions lib/services/project-templates-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
///<reference path="../.d.ts"/>

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_URL = "http://registry.npmjs.org/tns-template-hello-world/-/tns-template-hello-world-0.1.0.tgz";
private static DEFAULT_TEMPLATE_DOWNLOAD_FAILED = "Failed to retrieve nativescript hello world application. Please try again a little bit later.";

public constructor(private $fs: IFileSystem,
private $errors: IErrors,
private $logger: ILogger,
private $nodePackageManager: INodePackageManager) { }

public get defaultTemplatePath(): IFuture<string> {
return this.getDefaultTemplatePath();
}

private getDefaultTemplatePath(): IFuture<string> {
return (() => {
try {
this.$nodePackageManager.load({"cache": path.join(options["profile-dir"], "npm_cache")}).wait();
var info = this.$nodePackageManager.executeCommand("cache", ['add', ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_URL]).wait();
} catch (error) {
this.$logger.debug(error);
this.$errors.fail(ProjectTemplatesService.DEFAULT_TEMPLATE_DOWNLOAD_FAILED);
}

var packagePath = path.join(npm.cache, info.name, info.version);
var packageFileName = path.join(packagePath, "package.tgz");

this.$fs.unzipTarball(packageFileName, packagePath).wait();

return path.join(packagePath, "package");
}).future<string>()();
}
}
$injector.register("projectTemplatesService", ProjectTemplatesService);
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@
],
"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.5.0-alpha-1",
"tarball-extract": "0.0.2"
},
"analyze": true,
"devDependencies": {
Expand Down