Skip to content

Commit 0bc9d78

Browse files
committed
Code review changes
1 parent 3f42363 commit 0bc9d78

11 files changed

+334
-328
lines changed

lib/bootstrap.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ $injector.require("nativescript-cli", "./nativescript-cli");
44

55
$injector.require("projectData", "./services/project-service");
66
$injector.require("projectService", "./services/project-service");
7-
$injector.require("androidProjectService", "./services/project-service");
8-
$injector.require("iOSProjectService", "./services/project-service");
7+
$injector.require("androidProjectService", "./services/android-project-service");
8+
$injector.require("iOSProjectService", "./services/ios-project-service");
99

1010
$injector.require("projectTemplatesService", "./services/project-templates-service");
1111

1212
$injector.require("platformsData", "./services/platform-service");
1313
$injector.require("platformService", "./services/platform-service");
14-
$injector.require("platformProjectService", "./services/platform-service");
14+
$injector.require("platformProjectService", "./services/platform-project-service");
1515

1616
$injector.requireCommand("create", "./commands/create-project");
1717
$injector.requireCommand("platform|*list", "./commands/list-platforms");

lib/constants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
///<reference path=".d.ts"/>
2+
3+
export var APP_FOLDER_NAME = "app";
4+
export var DEFAULT_PROJECT_ID = "com.telerik.tns.HelloWorld";
5+
export var DEFAULT_PROJECT_NAME = "HelloNativescript";

lib/declarations.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ interface INodePackageManager {
22
cache: string;
33
load(config?: any): IFuture<void>;
44
install(where: string, what: string): IFuture<any>;
5-
tryExecuteAction(action: any, args?: any[]): IFuture<void>;
6-
downloadNpmPackage(packageName: string, pathToSave?: string): IFuture<string>;
5+
installSafe(packageName: string, pathToSave?: string): IFuture<string>;
76
}
87

98
interface IPropertiesParser {

lib/definitions/project.d.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ interface IPlatformProjectService {
2222
}
2323

2424
interface IPlatformSpecificProjectService {
25-
validate(): void;
26-
checkRequirements(): IFuture<void>;
25+
validate(): IFuture<void>;
2726
createProject(projectRoot: string, frameworkDir: string): IFuture<void>;
2827
interpolateData(projectRoot: string): void;
29-
executePlatformSpecificAction(projectRoot: string, frameworkDir: string): void;
28+
executePlatformSpecificAction(projectRoot: string): void;
3029
buildProject(projectRoot: string): IFuture<void>;
3130
}

lib/node-package-manager.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,25 @@ export class NodePackageManager implements INodePackageManager {
3939
return future;
4040
}
4141

42-
public tryExecuteAction(action: any, args?: any[]): IFuture<void> {
42+
private tryExecuteAction(action: (...args: any[]) => void, ...args: any[]): IFuture<void> {
4343
return (() => {
4444
try {
4545
this.load().wait(); // It's obligatory to execute load before whatever npm function
46-
action(args);
46+
action.apply(null, args);
4747
} catch(error) {
4848
this.$logger.debug(error);
4949
this.$errors.fail(NodePackageManager.NPM_LOAD_FAILED);
5050
}
5151
}).future<void>()();
5252
}
5353

54-
public downloadNpmPackage(packageName: string, pathToSave?: string): IFuture<string> {
54+
public installSafe(packageName: string, pathToSave?: string): IFuture<string> {
5555
return (() => {
5656
var action = (packageName: string) => {
5757
this.install(pathToSave || npm.cache, packageName).wait();
5858
};
5959

60-
this.tryExecuteAction(action, [packageName]).wait();
60+
this.tryExecuteAction(action, packageName).wait();
6161

6262
return path.join(pathToSave || npm.cache, "node_modules", packageName);
6363

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
///<reference path="../.d.ts"/>
2+
import path = require("path");
3+
import shell = require("shelljs");
4+
import options = require("./../options");
5+
import helpers = require("./../common/helpers");
6+
7+
class AndroidProjectService implements IPlatformSpecificProjectService {
8+
constructor(private $fs: IFileSystem,
9+
private $errors: IErrors,
10+
private $logger: ILogger,
11+
private $childProcess: IChildProcess,
12+
private $projectData: IProjectData,
13+
private $propertiesParser: IPropertiesParser) { }
14+
15+
public validate(): IFuture<void> {
16+
return (() => {
17+
this.validatePackageName(this.$projectData.projectId);
18+
this.validateProjectName(this.$projectData.projectName);
19+
20+
this.checkAnt().wait() && this.checkAndroid().wait() && this.checkJava().wait();
21+
}).future<void>()();
22+
}
23+
24+
public createProject(projectRoot: string, frameworkDir: string): IFuture<void> {
25+
return (() => {
26+
var packageName = this.$projectData.projectId;
27+
var packageAsPath = packageName.replace(/\./g, path.sep);
28+
29+
var validTarget = this.getTarget(frameworkDir).wait();
30+
var output = this.$childProcess.exec('android list targets').wait();
31+
if (!output.match(validTarget)) {
32+
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.",
33+
validTarget.split('-')[1]);
34+
}
35+
36+
shell.cp("-r", path.join(frameworkDir, "assets"), projectRoot);
37+
shell.cp("-r", path.join(frameworkDir, "gen"), projectRoot);
38+
shell.cp("-r", path.join(frameworkDir, "libs"), projectRoot);
39+
shell.cp("-r", path.join(frameworkDir, "res"), projectRoot);
40+
41+
shell.cp("-f", path.join(frameworkDir, ".project"), projectRoot);
42+
shell.cp("-f", path.join(frameworkDir, "AndroidManifest.xml"), projectRoot);
43+
shell.cp("-f", path.join(frameworkDir, "project.properties"), projectRoot);
44+
45+
// Create src folder
46+
var activityDir = path.join(projectRoot, 'src', packageAsPath);
47+
this.$fs.createDirectory(activityDir).wait();
48+
49+
}).future<any>()();
50+
}
51+
52+
public interpolateData(projectRoot: string): void {
53+
// Interpolate the activity name and package
54+
var stringsFilePath = path.join(projectRoot, 'res', 'values', 'strings.xml');
55+
shell.sed('-i', /__NAME__/, this.$projectData.projectName, stringsFilePath);
56+
shell.sed('-i', /__TITLE_ACTIVITY__/, this.$projectData.projectName, stringsFilePath);
57+
shell.sed('-i', /__NAME__/, this.$projectData.projectName, path.join(projectRoot, '.project'));
58+
shell.sed('-i', /__PACKAGE__/, this.$projectData.projectId, path.join(projectRoot, "AndroidManifest.xml"));
59+
}
60+
61+
public executePlatformSpecificAction(projectRoot: string) {
62+
var targetApi = this.getTarget(projectRoot).wait();
63+
this.$logger.trace("Android target: %s", targetApi);
64+
this.runAndroidUpdate(projectRoot, targetApi).wait();
65+
}
66+
67+
public buildProject(projectRoot: string): IFuture<void> {
68+
return (() => {
69+
var buildConfiguration = options.release ? "release" : "debug";
70+
var args = this.getAntArgs(buildConfiguration, projectRoot);
71+
this.spawn('ant', args);
72+
}).future<void>()();
73+
}
74+
75+
private spawn(command: string, args: string[], options?: any): void {
76+
if(helpers.isWindows()) {
77+
args.unshift('/s', '/c', command);
78+
command = 'cmd';
79+
}
80+
81+
this.$childProcess.spawn(command, args, {cwd: options, stdio: 'inherit'});
82+
}
83+
84+
private getAntArgs(configuration: string, projectRoot: string): string[] {
85+
var args = [configuration, "-f", path.join(projectRoot, "build.xml")];
86+
return args;
87+
}
88+
89+
private runAndroidUpdate(projectPath: string, targetApi: string): IFuture<void> {
90+
return (() => {
91+
var args = [
92+
"--path", projectPath,
93+
"--target", targetApi
94+
];
95+
96+
this.spawn("android update project", args);
97+
}).future<void>()();
98+
}
99+
100+
private validatePackageName(packageName: string): void {
101+
//Make the package conform to Java package types
102+
//Enforce underscore limitation
103+
if (!/^[a-zA-Z]+(\.[a-zA-Z0-9][a-zA-Z0-9_]*)+$/.test(packageName)) {
104+
this.$errors.fail("Package name must look like: com.company.Name");
105+
}
106+
107+
//Class is a reserved word
108+
if(/\b[Cc]lass\b/.test(packageName)) {
109+
this.$errors.fail("class is a reserved word");
110+
}
111+
}
112+
113+
private validateProjectName(projectName: string): void {
114+
if (projectName === '') {
115+
this.$errors.fail("Project name cannot be empty");
116+
}
117+
118+
//Classes in Java don't begin with numbers
119+
if (/^[0-9]/.test(projectName)) {
120+
this.$errors.fail("Project name must not begin with a number");
121+
}
122+
}
123+
124+
private getTarget(projectRoot: string): IFuture<string> {
125+
return (() => {
126+
var projectPropertiesFilePath = path.join(projectRoot, "project.properties");
127+
128+
if (this.$fs.exists(projectPropertiesFilePath).wait()) {
129+
var properties = this.$propertiesParser.createEditor(projectPropertiesFilePath).wait();
130+
return properties.get("target");
131+
}
132+
133+
return "";
134+
}).future<string>()();
135+
}
136+
137+
private checkAnt(): IFuture<void> {
138+
return (() => {
139+
try {
140+
this.$childProcess.exec("ant -version").wait();
141+
} catch(error) {
142+
this.$errors.fail("Error executing commands 'ant', make sure you have ant installed and added to your PATH.")
143+
}
144+
}).future<void>()();
145+
}
146+
147+
private checkJava(): IFuture<void> {
148+
return (() => {
149+
try {
150+
this.$childProcess.exec("java -version").wait();
151+
} catch(error) {
152+
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);
153+
}
154+
}).future<void>()();
155+
}
156+
157+
private checkAndroid(): IFuture<void> {
158+
return (() => {
159+
try {
160+
this.$childProcess.exec('android list targets').wait();
161+
} catch(error) {
162+
if (error.match(/command\snot\sfound/)) {
163+
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.");
164+
} else {
165+
this.$errors.fail("An error occurred while listing Android targets");
166+
}
167+
}
168+
}).future<void>()();
169+
}
170+
}
171+
$injector.register("androidProjectService", AndroidProjectService);

lib/services/ios-project-service.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
///<reference path="../.d.ts"/>
2+
3+
class IOSProjectService implements IPlatformSpecificProjectService {
4+
public validate(): IFuture<void> {
5+
return (() => {
6+
}).future<void>()();
7+
}
8+
9+
public interpolateData(): void {
10+
11+
}
12+
13+
public executePlatformSpecificAction(): void {
14+
15+
}
16+
17+
public createProject(): IFuture<void> {
18+
return (() => {
19+
20+
}).future<any>()();
21+
}
22+
23+
public buildProject(): IFuture<void> {
24+
return (() => {
25+
26+
}).future<void>()();
27+
}
28+
}
29+
$injector.register("iOSProjectService", IOSProjectService);
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
///<reference path="../.d.ts"/>
2+
3+
import path = require("path");
4+
import shell = require("shelljs");
5+
import util = require("util");
6+
import constants = require("./../constants");
7+
import helpers = require("./../common/helpers");
8+
9+
class PlatformProjectService implements IPlatformProjectService {
10+
private static APP_RESOURCES_FOLDER_NAME = "App_Resources";
11+
private static PROJECT_FRAMEWORK_DIR = "framework";
12+
13+
constructor(private $npm: INodePackageManager,
14+
private $platformsData: IPlatformsData,
15+
private $projectData: IProjectData,
16+
private $logger: ILogger,
17+
private $fs: IFileSystem) { }
18+
19+
public createProject(platform: string): IFuture<void> {
20+
return(() => {
21+
var platformData = this.$platformsData.getPlatformData(platform);
22+
var platformProjectService = platformData.platformProjectService;
23+
24+
platformProjectService.validate().wait();
25+
26+
// Log the values for project
27+
this.$logger.trace("Creating NativeScript project for the %s platform", platform);
28+
this.$logger.trace("Path: %s", platformData.projectRoot);
29+
this.$logger.trace("Package: %s", this.$projectData.projectId);
30+
this.$logger.trace("Name: %s", this.$projectData.projectName);
31+
32+
this.$logger.out("Copying template files...");
33+
34+
// get path to downloaded framework package
35+
var frameworkDir = this.$npm.installSafe(this.$platformsData.getPlatformData(platform).frameworkPackageName,
36+
path.join(this.$projectData.platformsDir, platform)).wait();
37+
frameworkDir = path.join(frameworkDir, PlatformProjectService.PROJECT_FRAMEWORK_DIR);
38+
39+
platformProjectService.createProject(platformData.projectRoot, frameworkDir).wait();
40+
41+
// Need to remove unneeded node_modules folder
42+
this.$fs.deleteDirectory(path.join(this.$projectData.platformsDir, platform, "node_modules")).wait();
43+
44+
platformProjectService.interpolateData(platformData.projectRoot);
45+
platformProjectService.executePlatformSpecificAction(platformData.projectRoot);
46+
47+
this.$logger.out("Project successfully created.");
48+
49+
}).future<void>()();
50+
}
51+
52+
public prepareProject(normalizedPlatformName: string, platforms: string[]): IFuture<void> {
53+
return (() => {
54+
var platform = normalizedPlatformName.toLowerCase();
55+
var assetsDirectoryPath = path.join(this.$projectData.platformsDir, platform, "assets");
56+
var appResourcesDirectoryPath = path.join(assetsDirectoryPath, constants.APP_FOLDER_NAME, PlatformProjectService.APP_RESOURCES_FOLDER_NAME);
57+
shell.cp("-r", path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME), assetsDirectoryPath);
58+
59+
if(this.$fs.exists(appResourcesDirectoryPath).wait()) {
60+
shell.cp("-r", path.join(appResourcesDirectoryPath, normalizedPlatformName, "*"), path.join(this.$projectData.platformsDir, platform, "res"));
61+
this.$fs.deleteDirectory(appResourcesDirectoryPath).wait();
62+
}
63+
64+
var files = helpers.enumerateFilesInDirectorySync(path.join(assetsDirectoryPath, constants.APP_FOLDER_NAME));
65+
var platformsAsString = platforms.join("|");
66+
67+
_.each(files, fileName => {
68+
var platformInfo = PlatformProjectService.parsePlatformSpecificFileName(path.basename(fileName), platformsAsString);
69+
var shouldExcludeFile = platformInfo && platformInfo.platform !== platform;
70+
if(shouldExcludeFile) {
71+
this.$fs.deleteFile(fileName).wait();
72+
} else if(platformInfo && platformInfo.onDeviceName) {
73+
this.$fs.rename(fileName, path.join(path.dirname(fileName), platformInfo.onDeviceName)).wait();
74+
}
75+
});
76+
77+
}).future<void>()();
78+
}
79+
80+
private static parsePlatformSpecificFileName(fileName: string, platforms: string): any {
81+
var regex = util.format("^(.+?)\.(%s)(\..+?)$", platforms);
82+
var parsed = fileName.toLowerCase().match(new RegExp(regex, "i"));
83+
if (parsed) {
84+
return {
85+
platform: parsed[2],
86+
onDeviceName: parsed[1] + parsed[3]
87+
};
88+
}
89+
return undefined;
90+
}
91+
92+
public buildProject(platform: string): IFuture<void> {
93+
return (() => {
94+
var platformData = this.$platformsData.getPlatformData(platform);
95+
platformData.platformProjectService.buildProject(platformData.projectRoot).wait();
96+
this.$logger.out("Project successfully built");
97+
}).future<void>()();
98+
}
99+
}
100+
$injector.register("platformProjectService", PlatformProjectService);

0 commit comments

Comments
 (0)