Skip to content

Commit 0646ab3

Browse files
committed
Merge pull request #5 from NativeScript/fatme/create-project-command
Create project command
2 parents 127dd7b + b056dba commit 0646ab3

15 files changed

+268
-16
lines changed

.gitignore

+1-8
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,8 @@ pids
2323
logs
2424
results
2525
scratch/
26-
.idea/workspace.xml
27-
.idea/tasks.xml
28-
.idea/watcherTasks.xml
29-
26+
.idea/
3027
test-reports.xml
3128

3229
npm-debug.log
3330
node_modules
34-
resources/App_Resources
35-
resources/Cordova
36-
resources/ItemTemplates
37-
resources/ProjectTemplates

lib/bootstrap.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
global._ = require("underscore");
2-
global.$injector = require("./common/lib/yok").injector;
1+
require("./common/bootstrap");
32

43
$injector.require("nativescript-cli", "./nativescript-cli");
4+
5+
$injector.require("projectService", "./services/project-service");
6+
$injector.require("projectTemplatesService", "./services/project-templates-service");
7+
8+
$injector.requireCommand("create", "./commands/create-project-command");
9+
10+
$injector.require("npm", "./node-package-manager");
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
///<reference path="../.d.ts"/>
2+
3+
export class CreateProjectCommand implements ICommand {
4+
constructor(private $projectService: IProjectService) { }
5+
6+
execute(args: string[]): IFuture<void> {
7+
return (() => {
8+
this.$projectService.createProject(args[0], args[1]).wait();
9+
}).future<void>()();
10+
}
11+
}
12+
$injector.registerCommand("create", CreateProjectCommand);

lib/declarations.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
interface INodePackageManager {
2+
cache: string;
3+
load(config?: any): IFuture<void>;
4+
install(where: string, what: string): IFuture<any>;
5+
}

lib/definitions/npm.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module "npm" {
2+
var cache: string;
3+
var commands: any[];
4+
function load(config: Object, callback: (err: any, data: any) => void);
5+
}

lib/definitions/osenv.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare module "osenv" {
2+
function home();
3+
}

lib/definitions/project.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface IProjectService {
2+
createProject(projectName: string, projectId: string): IFuture<void>;
3+
}
4+
5+
interface IProjectTemplatesService {
6+
defaultTemplatePath: IFuture<string>;
7+
}

lib/definitions/shelljs.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module "shelljs" {
2+
function cp(arg: string, sourcePath: string, destinationPath: string): void;
3+
function sed(arg: string, oldValue: any, newValue: string, filePath: string): void;
4+
function mv(source: string[], destination: string);
5+
}

lib/nativescript-cli.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
///<reference path=".d.ts"/>
22

33
import Fiber = require("fibers");
4+
import Future = require("fibers/future");
5+
import path = require("path");
46

57
require("./bootstrap");
8+
require("./options");
69

7-
var fiber = Fiber(() => {
8-
});
9-
global.__main_fiber__ = fiber; // leak fiber to prevent it from being GC'd and thus corrupting V8
10-
fiber.run();
10+
import errors = require("./common/errors");
11+
errors.installUncaughtExceptionListener();
12+
13+
$injector.register("config", {"CI_LOGGER": false});
14+
15+
var dispatcher = $injector.resolve("dispatcher");
16+
dispatcher.runMainFiber();

lib/node-package-manager.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
///<reference path=".d.ts"/>
2+
3+
import npm = require("npm");
4+
import Future = require("fibers/future");
5+
import shell = require("shelljs");
6+
7+
export class NodePackageManager implements INodePackageManager {
8+
public get cache(): string {
9+
return npm.cache;
10+
}
11+
12+
public load(config?: any): IFuture<void> {
13+
var future = new Future<void>();
14+
npm.load(config, (err) => {
15+
if(err) {
16+
future.throw(err);
17+
} else {
18+
future.return();
19+
}
20+
});
21+
return future;
22+
}
23+
24+
public install(where: string, what: string): IFuture<any> {
25+
var future = new Future<any>();
26+
npm.commands["install"](where, what, (err, data) => {
27+
if(err) {
28+
future.throw(err);
29+
} else {
30+
future.return(data);
31+
}
32+
});
33+
return future;
34+
}
35+
}
36+
$injector.register("npm", NodePackageManager);

lib/options.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
///<reference path=".d.ts"/>
2+
3+
import path = require("path");
4+
import helpers = require("./common/helpers");
5+
import osenv = require("osenv");
6+
7+
var knownOpts:any = {
8+
"log" : String,
9+
"verbose" : Boolean,
10+
"path" : String,
11+
"copy-from": String,
12+
"link-to": String,
13+
"version": Boolean,
14+
"help": Boolean
15+
},
16+
shorthands = {
17+
"v" : "verbose",
18+
"p" : "path"
19+
};
20+
21+
var defaultProfileDir = path.join(osenv.home(), ".nativescript-cli");
22+
var parsed = helpers.getParsedOptions(knownOpts, shorthands, defaultProfileDir);
23+
24+
Object.keys(parsed).forEach((opt) => exports[opt] = parsed[opt]);
25+
26+
exports.knownOpts = knownOpts;
27+
28+
declare var exports:any;
29+
export = exports;

lib/services/project-service.ts

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
///<reference path="../.d.ts"/>
2+
3+
import path = require("path");
4+
import options = require("./../options");
5+
import shell = require("shelljs");
6+
import osenv = require("osenv");
7+
8+
export class ProjectService implements IProjectService {
9+
private static DEFAULT_ID = "com.telerik.tns.HelloWorld";
10+
private static DEFAULT_NAME = "HelloNativescript";
11+
private static APP_FOLDER_NAME = "app";
12+
13+
constructor(private $logger: ILogger,
14+
private $errors: IErrors,
15+
private $fs: IFileSystem,
16+
private $projectTemplatesService: IProjectTemplatesService) { }
17+
18+
public createProject(projectName: string, projectId: string): IFuture<void> {
19+
return(() => {
20+
var projectDir = path.resolve(options.path || ".");
21+
22+
projectId = projectId || ProjectService.DEFAULT_ID;
23+
projectName = projectName || ProjectService.DEFAULT_NAME;
24+
25+
projectDir = path.join(projectDir, projectName);
26+
this.$fs.createDirectory(projectDir).wait();
27+
28+
var customAppPath = this.getCustomAppPath();
29+
if(customAppPath) {
30+
customAppPath = path.resolve(customAppPath);
31+
}
32+
33+
if(this.$fs.exists(projectDir).wait() && !this.$fs.isEmptyDir(projectDir).wait()) {
34+
this.$errors.fail("Path already exists and is not empty %s", projectDir);
35+
}
36+
37+
this.$logger.trace("Creating a new NativeScript project with name %s and id at location", projectName, projectId, projectDir);
38+
39+
var appDirectory = path.join(projectDir, ProjectService.APP_FOLDER_NAME);
40+
var appPath: string = null;
41+
42+
if(customAppPath) {
43+
this.$logger.trace("Using custom app from %s", customAppPath);
44+
45+
// Make sure that the source app/ is not a direct ancestor of a target app/
46+
var relativePathFromSourceToTarget = path.relative(customAppPath, appDirectory);
47+
var doesRelativePathGoUpAtLeastOneDir = relativePathFromSourceToTarget.split(path.sep)[0] == "..";
48+
if(!doesRelativePathGoUpAtLeastOneDir) {
49+
this.$errors.fail("Project dir %s must not be created at/inside the template used to create the project %s.", projectDir, customAppPath);
50+
}
51+
this.$logger.trace("Copying custom app into %s", appDirectory);
52+
appPath = customAppPath;
53+
} else {
54+
// No custom app - use nativescript hello world application
55+
this.$logger.trace("Using NativeScript hello world application");
56+
var defaultTemplatePath = this.$projectTemplatesService.defaultTemplatePath.wait();
57+
this.$logger.trace("Copying Nativescript hello world application into %s", appDirectory);
58+
appPath = defaultTemplatePath;
59+
}
60+
61+
this.createProjectCore(projectDir, appPath, false).wait();
62+
}).future<void>()();
63+
}
64+
65+
private createProjectCore(projectDir: string, appPath: string, symlink?: boolean): IFuture<void> {
66+
return (() => {
67+
if(!this.$fs.exists(projectDir).wait()) {
68+
this.$fs.createDirectory(projectDir).wait();
69+
}
70+
if(symlink) {
71+
// TODO: Implement support for symlink the app folder instead of copying
72+
} else {
73+
var appDir = path.join(projectDir, ProjectService.APP_FOLDER_NAME);
74+
this.$fs.createDirectory(appDir).wait();
75+
shell.cp('-R', path.join(appPath, "*"), appDir);
76+
}
77+
this.createBasicProjectStructure(projectDir).wait();
78+
}).future<void>()();
79+
}
80+
81+
private createBasicProjectStructure(projectDir: string): IFuture<void> {
82+
return (() => {
83+
this.$fs.createDirectory(path.join(projectDir, "platforms")).wait();
84+
this.$fs.createDirectory(path.join(projectDir, "tns_modules")).wait();
85+
this.$fs.createDirectory(path.join(projectDir, "hooks")).wait();
86+
}).future<void>()();
87+
}
88+
89+
private getCustomAppPath(): string {
90+
var customAppPath = options["copy-from"] || options["link-to"];
91+
if(customAppPath) {
92+
if(customAppPath.indexOf("http") >= 0) {
93+
this.$errors.fail("Only local paths for custom app are supported.");
94+
}
95+
96+
if(customAppPath.substr(0, 1) === '~') {
97+
customAppPath = path.join(osenv.home(), customAppPath.substr(1));
98+
}
99+
}
100+
101+
return customAppPath;
102+
}
103+
}
104+
$injector.register("projectService", ProjectService);
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
///<reference path="../.d.ts"/>
2+
3+
import util = require("util");
4+
import path = require("path");
5+
import shell = require("shelljs");
6+
import npm = require("npm");
7+
var options = require("./../options");
8+
var helpers = require("./../common/helpers");
9+
import Future = require("fibers/future");
10+
11+
export class ProjectTemplatesService implements IProjectTemplatesService {
12+
private static NPM_DEFAULT_TEMPLATE_NAME = "tns-template-hello-world";
13+
private static NPM_LOAD_FAILED = "Failed to retrieve nativescript hello world application. Please try again a little bit later.";
14+
15+
public constructor(private $errors: IErrors,
16+
private $logger: ILogger,
17+
private $npm: INodePackageManager) { }
18+
19+
public get defaultTemplatePath(): IFuture<string> {
20+
return this.getDefaultTemplatePath();
21+
}
22+
23+
private getDefaultTemplatePath(): IFuture<string> {
24+
return (() => {
25+
try {
26+
this.$npm.load().wait();
27+
this.$npm.install(npm.cache, ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME).wait();
28+
} catch (error) {
29+
this.$logger.debug(error);
30+
this.$errors.fail(ProjectTemplatesService.NPM_LOAD_FAILED);
31+
}
32+
33+
return path.join(npm.cache, "node_modules", ProjectTemplatesService.NPM_DEFAULT_TEMPLATE_NAME);
34+
}).future<string>()();
35+
}
36+
}
37+
$injector.register("projectTemplatesService", ProjectTemplatesService);

package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
],
2424
"dependencies": {
2525
"fibers": "https://github.com/icenium/node-fibers/tarball/master",
26+
"filesize": "2.0.3",
27+
"progress-stream": "0.5.0",
2628
"log4js": "0.6.9",
29+
"osenv": "0.1.0",
2730
"tabtab": "https://github.com/tailsu/node-tabtab/tarball/master",
2831
"underscore": "1.5.2",
2932
"unzip": "0.1.9",
30-
"yargs": "1.2.2"
33+
"yargs": "1.2.2",
34+
"npm": "1.4.10"
3135
},
3236
"analyze": true,
3337
"devDependencies": {

0 commit comments

Comments
 (0)