diff --git a/docs/man_pages/project/creation/create.md b/docs/man_pages/project/creation/create.md index 152788c977..39e8c71bf0 100644 --- a/docs/man_pages/project/creation/create.md +++ b/docs/man_pages/project/creation/create.md @@ -16,4 +16,13 @@ Creates a new project for native development with NativeScript from the default * `` is the name of project. The specified name must meet the requirements of all platforms that you want to target. <% if(isConsole) { %>For more information about the `` requirements, run `$ tns help create`<% } %><% if(isHtml) { %>For projects that target Android, you can use uppercase or lowercase letters, numbers, and underscores. The name must start with a letter. For projects that target iOS, you can use uppercase or lowercase letters, numbers, and hyphens.<% } %> * `` is the application identifier for your project. It must be a domain name in reverse and must meet the requirements of all platforms that you want to target. If not specified, the application identifier is set to `org.nativescript.` <% if(isConsole) { %>For more information about the `` requirements, run `$ tns help create`<% } %><% if(isHtml) { %>For projects that target Android, you can use uppercase or lowercase letters, numbers, and underscores in the strings of the reversed domain name, separated by a dot. Strings must be separated by a dot and must start with a letter. For example: `com.nativescript.My_Andro1d_App` -For projects that target iOS, you can use uppercase or lowercase letters, numbers, and hyphens in the strings of the reversed domain name. Strings must be separated by a dot. For example: `com.nativescript.My-i0s-App`.<% } %> \ No newline at end of file +For projects that target iOS, you can use uppercase or lowercase letters, numbers, and hyphens in the strings of the reversed domain name. Strings must be separated by a dot. For example: `com.nativescript.My-i0s-App`.<% } %> + +<% if(isHtml) { %> +### Related Commands + +Command | Description +----------|---------- +[init](init.html) | Initializes a project for development. The command prompts you to provide your project configuration interactively and uses the information to create a new package.json file or update the existing one. +[install](install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory. +<% } %> \ No newline at end of file diff --git a/docs/man_pages/project/creation/init.md b/docs/man_pages/project/creation/init.md new file mode 100644 index 0000000000..1e05429b4e --- /dev/null +++ b/docs/man_pages/project/creation/init.md @@ -0,0 +1,21 @@ +init +========== + +Usage | Synopsis +---|--- +General | `$ tns init [--path ] [--force]` + +Initializes a project for development. The command prompts you to provide your project configuration interactively and uses the information to create a new `package.json` file or update the existing one. + +### Options +* `--path` - Specifies the directory where you want to initialize the project, if different from the current directory. The directory must be empty. +* `--force` - If set, applies the default project configuration and does not show the interactive prompt. The default project configuration targets the latest official runtimes and sets `org.nativescript.` for application identifier. + +<% if(isHtml) { %> +### Related Commands + +Command | Description +----------|---------- +[create](create.html) | Creates a new project for native development with NativeScript from the default template or from an existing NativeScript project. +[install](install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory. +<% } %> \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 81657d844f..251c7b8cf3 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -61,4 +61,7 @@ $injector.requireCommand("plugin|add", "./commands/plugin/add-plugin"); $injector.requireCommand("plugin|remove", "./commands/plugin/remove-plugin"); $injector.requireCommand("install", "./commands/install"); +$injector.require("initService", "./services/init-service"); +$injector.requireCommand("init", "./commands/init"); + $injector.require("projectFilesManager", "./services/project-files-manager"); diff --git a/lib/commands/init.ts b/lib/commands/init.ts new file mode 100644 index 0000000000..52a2098f71 --- /dev/null +++ b/lib/commands/init.ts @@ -0,0 +1,16 @@ +/// +"use strict"; + +import Future = require("fibers/future"); + +export class InitCommand implements ICommand { + constructor(private $initService: IInitService) { } + + public allowedParameters: ICommandParameter[] = []; + public enableHooks = false; + + public execute(args: string[]): IFuture { + return this.$initService.initialize(); + } +} +$injector.registerCommand("init", InitCommand); \ No newline at end of file diff --git a/lib/common b/lib/common index 0e7c0aea84..c04a5ebac1 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 0e7c0aea84f2cba602bef98c7fb8ca418b63a40a +Subproject commit c04a5ebac1bf0aa7a53c6cd6bc9752ebcf9ef36d diff --git a/lib/constants.ts b/lib/constants.ts index a7c5e247e5..7d86bfbc49 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -7,6 +7,7 @@ export var NATIVESCRIPT_KEY_NAME = "nativescript"; export var NODE_MODULES_FOLDER_NAME = "node_modules"; export var PACKAGE_JSON_FILE_NAME = "package.json"; export var NODE_MODULE_CACHE_PATH_KEY_NAME = "node-modules-cache-path"; +export var DEFAULT_APP_IDENTIFIER_PREFIX = "org.nativescript"; export class ReleaseType { static MAJOR = "major"; diff --git a/lib/declarations.ts b/lib/declarations.ts index 5f7abb2e7d..e07aa0d439 100644 --- a/lib/declarations.ts +++ b/lib/declarations.ts @@ -50,6 +50,8 @@ interface IOpener { interface IOptions extends ICommonOptions { frameworkPath: string; + frameworkName: string; + frameworkVersion: string; copyFrom: string; linkTo: string; release: boolean; @@ -67,4 +69,8 @@ interface IOptions extends ICommonOptions { interface IProjectFilesManager { processPlatformSpecificFiles(directoryPath: string, platform: string, excludedDirs?: string[]): IFuture; +} + +interface IInitService { + initialize(): IFuture; } \ No newline at end of file diff --git a/lib/options.ts b/lib/options.ts index fc0b19069f..73e46c2c8d 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -13,6 +13,8 @@ export class Options extends commonOptionsLibPath.OptionsBase { $hostInfo: IHostInfo) { super({ frameworkPath: { type: OptionType.String }, + frameworkName: { type: OptionType.String }, + frameworkVersion: { type: OptionType.String }, copyFrom: { type: OptionType.String }, linkTo: { type: OptionType.String }, release: { type: OptionType.Boolean }, diff --git a/lib/services/init-service.ts b/lib/services/init-service.ts new file mode 100644 index 0000000000..e30f5963cd --- /dev/null +++ b/lib/services/init-service.ts @@ -0,0 +1,120 @@ +/// +"use strict"; + +import constants = require("./../constants"); +import helpers = require("./../common/helpers"); +import path = require("path"); +import semver = require("semver"); + +export class InitService implements IInitService { + private static MIN_SUPPORTED_FRAMEWORK_VERSIONS: IStringDictionary = { + "tns-ios": "1.1.0", + "tns-android": "1.1.0" + }; + + private _projectFilePath: string; + + constructor(private $fs: IFileSystem, + private $errors: IErrors, + private $logger: ILogger, + private $options: IOptions, + private $injector: IInjector, + private $staticConfig: IStaticConfig, + private $projectHelper: IProjectHelper, + private $prompter: IPrompter, + private $npm: INodePackageManager, + private $npmInstallationManager: INpmInstallationManager) { } + + public initialize(): IFuture { + return (() => { + let projectData: any = { }; + + if(this.$fs.exists(this.projectFilePath).wait()) { + projectData = this.$fs.readJson(this.projectFilePath).wait(); + } + + let projectDataBackup = _.extend({}, projectData); + + if(!projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]) { + projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE] = { }; + this.$fs.writeJson(this.projectFilePath, projectData).wait(); // We need to create package.json file here in order to prevent "No project found at or above and neither was a --path specified." when resolving platformsData + } + + try { + + projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]["id"] = this.getProjectId().wait(); + + if(this.$options.frameworkName && this.$options.frameworkVersion) { + projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] = this.buildVersionData(this.$options.frameworkVersion); + } else { + let $platformsData = this.$injector.resolve("platformsData"); + _.each($platformsData.platformsNames, platform => { + let platformData: IPlatformData = $platformsData.getPlatformData(platform); + if(!platformData.targetedOS || (platformData.targetedOS && _.contains(platformData.targetedOS, process.platform))) { + projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] = this.getVersionData(platformData.frameworkPackageName).wait(); + } + }); + } + + this.$fs.writeJson(this.projectFilePath, projectData).wait(); + } catch(err) { + this.$fs.writeJson(this.projectFilePath, projectDataBackup).wait(); + throw err; + } + + this.$logger.out("Project successfully initialized."); + }).future()(); + } + + private get projectFilePath(): string { + if(!this._projectFilePath) { + let projectDir = path.resolve(this.$options.path || "."); + this._projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME); + } + + return this._projectFilePath; + } + + private getProjectId(): IFuture { + return (() => { + if(this.$options.appid) { + return this.$options.appid; + } + + let defaultAppId = this.$projectHelper.generateDefaultAppId(path.basename(path.dirname(this.projectFilePath)), constants.DEFAULT_APP_IDENTIFIER_PREFIX); + if(this.useDefaultValue) { + return defaultAppId; + } + + return this.$prompter.getString("Id:", () => defaultAppId).wait(); + }).future()(); + } + + private getVersionData(packageName: string): IFuture { + return (() => { + let latestVersion = this.$npmInstallationManager.getLatestVersion(packageName).wait(); + if(this.useDefaultValue) { + return this.buildVersionData(latestVersion); + } + + let data = this.$npm.view(packageName, "versions").wait(); + let versions = _.filter(data[latestVersion].versions, (version: string) => semver.gte(version, InitService.MIN_SUPPORTED_FRAMEWORK_VERSIONS[packageName])); + if(versions.length === 1) { + this.$logger.info(`Only ${versions[0]} version is available for ${packageName} framework.`); + return this.buildVersionData(versions[0]); + } + let sortedVersions = versions.sort(helpers.versionCompare).reverse(); + let version = this.$prompter.promptForChoice(`${packageName} version:`, sortedVersions).wait(); + return this.buildVersionData(version); + }).future()(); + } + + private buildVersionData(version: string): IStringDictionary { + return { "version": version }; + } + + private get useDefaultValue(): boolean { + return !helpers.isInteractive() || this.$options.force; + } +} +$injector.register("initService", InitService); \ No newline at end of file diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts index e27398cd8f..4aa73a2670 100644 --- a/lib/services/project-service.ts +++ b/lib/services/project-service.ts @@ -9,7 +9,6 @@ import shell = require("shelljs"); import util = require("util"); export class ProjectService implements IProjectService { - private static DEFAULT_APP_IDENTIFIER_PREFIX = "org.nativescript"; constructor(private $errors: IErrors, private $fs: IFileSystem, @@ -27,7 +26,7 @@ export class ProjectService implements IProjectService { } this.$projectNameValidator.validate(projectName); - var projectId = this.$options.appid || this.$projectHelper.generateDefaultAppId(projectName, ProjectService.DEFAULT_APP_IDENTIFIER_PREFIX); + var projectId = this.$options.appid || this.$projectHelper.generateDefaultAppId(projectName, constants.DEFAULT_APP_IDENTIFIER_PREFIX); var projectDir = path.join(path.resolve(this.$options.path || "."), projectName); this.$fs.createDirectory(projectDir).wait();