-
-
Notifications
You must be signed in to change notification settings - Fork 197
Refactor project and platform services #7
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
Changes from 8 commits
bef4e47
0284cf8
6828871
0f36cf2
5f12de3
3f42363
0bc9d78
c0b11cf
b962f30
ebf4c97
29a5190
598d079
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
///<reference path=".d.ts"/> | ||
|
||
export var APP_FOLDER_NAME = "app"; | ||
export var DEFAULT_PROJECT_ID = "com.telerik.tns.HelloWorld"; | ||
export var DEFAULT_PROJECT_NAME = "HelloNativescript"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,30 @@ | ||
interface IProjectService { | ||
createProject(projectName: string, projectId: string): IFuture<void>; | ||
createPlatformSpecificProject(platform: string): IFuture<void>; | ||
prepareProject(normalizedPlatformName: string, platforms: string[]): IFuture<void>; | ||
buildProject(platform: string): IFuture<void>; | ||
ensureProject(): void; | ||
projectData: IProjectData; | ||
} | ||
|
||
interface IPlatformProjectService { | ||
createProject(projectData: IProjectData): IFuture<void>; | ||
buildProject(projectData: IProjectData): IFuture<void>; | ||
} | ||
|
||
interface IProjectData { | ||
projectDir: string; | ||
projectName: string; | ||
platformsDir: string; | ||
projectFilePath: string; | ||
projectId?: string; | ||
projectName?: string; | ||
} | ||
|
||
interface IProjectTemplatesService { | ||
defaultTemplatePath: IFuture<string>; | ||
installAndroidFramework(whereToInstall: string): IFuture<string> | ||
} | ||
|
||
interface IPlatformProjectService { | ||
createProject(platform: string): IFuture<void>; | ||
buildProject(platform: string): IFuture<void>; | ||
prepareProject(normalizedPlatformName: string, platforms: string[]): IFuture<void>; | ||
} | ||
|
||
interface IPlatformSpecificProjectService { | ||
validate(): IFuture<void>; | ||
createProject(projectRoot: string, frameworkDir: string): IFuture<void>; | ||
interpolateData(projectRoot: string): void; | ||
executePlatformSpecificAction(projectRoot: string): void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method name is too generic. You can rename it to something like: |
||
buildProject(projectRoot: string): IFuture<void>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,7 @@ export class NodePackageManager implements INodePackageManager { | |
return future; | ||
} | ||
|
||
public install(where: string, what: string): IFuture<any> { | ||
private installCore(where: string, what: string): IFuture<any> { | ||
var future = new Future<any>(); | ||
npm.commands["install"](where, what, (err, data) => { | ||
if(err) { | ||
|
@@ -32,5 +38,30 @@ export class NodePackageManager implements INodePackageManager { | |
}); | ||
return future; | ||
} | ||
|
||
private tryExecuteAction(action: (...args: any[]) => void, ...args: any[]): IFuture<void> { | ||
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<void>()(); | ||
} | ||
|
||
public install(packageName: string, pathToSave?: string): IFuture<string> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can move this after the other public method in this class. |
||
return (() => { | ||
var action = (packageName: string) => { | ||
this.installCore(pathToSave || npm.cache, packageName).wait(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can extract There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I can't 😄 because it's obligatory to call |
||
}; | ||
|
||
this.tryExecuteAction(action, packageName).wait(); | ||
|
||
return path.join(pathToSave || npm.cache, "node_modules", packageName); | ||
|
||
}).future<string>()(); | ||
} | ||
} | ||
$injector.register("npm", NodePackageManager); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
///<reference path="../.d.ts"/> | ||
import path = require("path"); | ||
import shell = require("shelljs"); | ||
import options = require("./../options"); | ||
import helpers = require("./../common/helpers"); | ||
|
||
class AndroidProjectService implements IPlatformSpecificProjectService { | ||
constructor(private $fs: IFileSystem, | ||
private $errors: IErrors, | ||
private $logger: ILogger, | ||
private $childProcess: IChildProcess, | ||
private $projectData: IProjectData, | ||
private $propertiesParser: IPropertiesParser) { } | ||
|
||
public validate(): IFuture<void> { | ||
return (() => { | ||
this.validatePackageName(this.$projectData.projectId); | ||
this.validateProjectName(this.$projectData.projectName); | ||
|
||
this.checkAnt().wait() && this.checkAndroid().wait() && this.checkJava().wait(); | ||
}).future<void>()(); | ||
} | ||
|
||
public createProject(projectRoot: string, frameworkDir: string): IFuture<void> { | ||
return (() => { | ||
var packageName = this.$projectData.projectId; | ||
var packageAsPath = packageName.replace(/\./g, path.sep); | ||
|
||
var validTarget = this.getTarget(frameworkDir).wait(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This validation can be extracted in a separate method. |
||
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]); | ||
} | ||
|
||
shell.cp("-r", path.join(frameworkDir, "assets"), projectRoot); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use an array as a second argument here: https://github.com/arturadib/shelljs#cpoptions--source_array-dest. var paths = "assets gen libs res".split(' ').map(p => path.join(frameworkDir, p)); |
||
shell.cp("-r", path.join(frameworkDir, "gen"), projectRoot); | ||
shell.cp("-r", path.join(frameworkDir, "libs"), projectRoot); | ||
shell.cp("-r", path.join(frameworkDir, "res"), projectRoot); | ||
|
||
shell.cp("-f", path.join(frameworkDir, ".project"), projectRoot); | ||
shell.cp("-f", path.join(frameworkDir, "AndroidManifest.xml"), projectRoot); | ||
shell.cp("-f", path.join(frameworkDir, "project.properties"), projectRoot); | ||
|
||
// Create src folder | ||
var activityDir = path.join(projectRoot, 'src', packageAsPath); | ||
this.$fs.createDirectory(activityDir).wait(); | ||
|
||
}).future<any>()(); | ||
} | ||
|
||
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 executePlatformSpecificAction(projectRoot: string) { | ||
var targetApi = this.getTarget(projectRoot).wait(); | ||
this.$logger.trace("Android target: %s", targetApi); | ||
this.runAndroidUpdate(projectRoot, targetApi).wait(); | ||
} | ||
|
||
public buildProject(projectRoot: string): IFuture<void> { | ||
return (() => { | ||
var buildConfiguration = options.release ? "release" : "debug"; | ||
var args = this.getAntArgs(buildConfiguration, projectRoot); | ||
this.spawn('ant', args); | ||
}).future<void>()(); | ||
} | ||
|
||
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<void> { | ||
return (() => { | ||
var args = [ | ||
"--path", projectPath, | ||
"--target", targetApi | ||
]; | ||
|
||
this.spawn("android update project", args); | ||
}).future<void>()(); | ||
} | ||
|
||
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 getTarget(projectRoot: string): IFuture<string> { | ||
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<string>()(); | ||
} | ||
|
||
private checkAnt(): IFuture<void> { | ||
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<void>()(); | ||
} | ||
|
||
private checkJava(): IFuture<void> { | ||
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<void>()(); | ||
} | ||
|
||
private checkAndroid(): IFuture<void> { | ||
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<void>()(); | ||
} | ||
} | ||
$injector.register("androidProjectService", AndroidProjectService); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
///<reference path="../.d.ts"/> | ||
|
||
class IOSProjectService implements IPlatformSpecificProjectService { | ||
public validate(): IFuture<void> { | ||
return (() => { | ||
}).future<void>()(); | ||
} | ||
|
||
public interpolateData(): void { | ||
|
||
} | ||
|
||
public executePlatformSpecificAction(): void { | ||
|
||
} | ||
|
||
public createProject(): IFuture<void> { | ||
return (() => { | ||
|
||
}).future<any>()(); | ||
} | ||
|
||
public buildProject(): IFuture<void> { | ||
return (() => { | ||
|
||
}).future<void>()(); | ||
} | ||
} | ||
$injector.register("iOSProjectService", IOSProjectService); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the normalized prefix?