|
1 | 1 | import * as constants from "../constants";
|
2 | 2 | import * as path from "path";
|
| 3 | +import { isInteractive } from "../common/helpers"; |
3 | 4 |
|
4 | 5 | export class CreateProjectCommand implements ICommand {
|
5 | 6 | public enableHooks = false;
|
6 |
| - public allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("Project name cannot be empty.")]; |
| 7 | + public allowedParameters: ICommandParameter[] = [this.$stringParameter]; |
| 8 | + private static HelloWorldTemplateKey = "Hello World"; |
| 9 | + private static HelloWorldTemplateDescription = "A Hello World app"; |
| 10 | + private static DrawerTemplateKey = "SideDrawer"; |
| 11 | + private static DrawerTemplateDescription = "An app with pre-built pages that uses a drawer for navigation"; |
| 12 | + private static TabsTemplateKey = "Tabs"; |
| 13 | + private static TabsTemplateDescription = "An app with pre-built pages that uses tabs for navigation"; |
| 14 | + private isInteractionIntroShown = false; |
7 | 15 |
|
8 |
| - private createdProjecData: ICreateProjectData; |
| 16 | + private createdProjectData: ICreateProjectData; |
9 | 17 |
|
10 | 18 | constructor(private $projectService: IProjectService,
|
11 | 19 | private $logger: ILogger,
|
12 | 20 | private $errors: IErrors,
|
13 | 21 | private $options: IOptions,
|
14 |
| - private $stringParameterBuilder: IStringParameterBuilder) { } |
| 22 | + private $prompter: IPrompter, |
| 23 | + private $stringParameter: ICommandParameter) { } |
15 | 24 |
|
16 | 25 | public async execute(args: string[]): Promise<void> {
|
17 |
| - if ((this.$options.tsc || this.$options.ng) && this.$options.template) { |
18 |
| - this.$errors.fail("You cannot use --ng or --tsc options together with --template."); |
| 26 | + const interactiveAdverbs = ["First", "Next", "Finally"]; |
| 27 | + const getNextInteractiveAdverb = () => { |
| 28 | + return interactiveAdverbs.shift() || "Next"; |
| 29 | + }; |
| 30 | + |
| 31 | + if ((this.$options.tsc || this.$options.ng || this.$options.vue || this.$options.js) && this.$options.template) { |
| 32 | + this.$errors.fail("You cannot use a flavor option like --ng, --vue, --tsc and --js together with --template."); |
19 | 33 | }
|
20 | 34 |
|
| 35 | + let projectName = args[0]; |
21 | 36 | let selectedTemplate: string;
|
22 |
| - if (this.$options.tsc) { |
| 37 | + if (this.$options.js) { |
| 38 | + selectedTemplate = constants.JAVASCRIPT_NAME; |
| 39 | + } else if (this.$options.tsc) { |
23 | 40 | selectedTemplate = constants.TYPESCRIPT_NAME;
|
24 | 41 | } else if (this.$options.ng) {
|
25 | 42 | selectedTemplate = constants.ANGULAR_NAME;
|
| 43 | + } else if (this.$options.vue) { |
| 44 | + selectedTemplate = constants.VUE_NAME; |
26 | 45 | } else {
|
27 | 46 | selectedTemplate = this.$options.template;
|
28 | 47 | }
|
29 | 48 |
|
30 |
| - this.createdProjecData = await this.$projectService.createProject({ |
31 |
| - projectName: args[0], |
| 49 | + if (!projectName && isInteractive()) { |
| 50 | + this.printInteractiveCreationIntroIfNeeded(); |
| 51 | + projectName = await this.$prompter.getString(`${getNextInteractiveAdverb()}, what will be the name of your app?`, { allowEmpty: false }); |
| 52 | + this.$logger.info(); |
| 53 | + } |
| 54 | + |
| 55 | + projectName = await this.$projectService.validateProjectName({ projectName: projectName, force: this.$options.force, pathToProject: this.$options.path }); |
| 56 | + |
| 57 | + if (!selectedTemplate && isInteractive()) { |
| 58 | + this.printInteractiveCreationIntroIfNeeded(); |
| 59 | + selectedTemplate = await this.interactiveFlavorAndTemplateSelection(getNextInteractiveAdverb(), getNextInteractiveAdverb()); |
| 60 | + } |
| 61 | + |
| 62 | + this.createdProjectData = await this.$projectService.createProject({ |
| 63 | + projectName: projectName, |
32 | 64 | template: selectedTemplate,
|
33 | 65 | appId: this.$options.appid,
|
34 | 66 | pathToProject: this.$options.path,
|
35 |
| - force: this.$options.force, |
| 67 | + // its already validated above |
| 68 | + force: true, |
36 | 69 | ignoreScripts: this.$options.ignoreScripts
|
37 | 70 | });
|
38 | 71 | }
|
39 | 72 |
|
| 73 | + private async interactiveFlavorAndTemplateSelection(flavorAdverb: string, templateAdverb: string) { |
| 74 | + const selectedFlavor = await this.interactiveFlavorSelection(flavorAdverb); |
| 75 | + const selectedTemplate: string = await this.interactiveTemplateSelection(selectedFlavor, templateAdverb); |
| 76 | + |
| 77 | + return selectedTemplate; |
| 78 | + } |
| 79 | + |
| 80 | + private async interactiveFlavorSelection(adverb: string) { |
| 81 | + const flavorSelection = await this.$prompter.promptForDetailedChoice(`${adverb}, which flavor would you like to use?`, [ |
| 82 | + { key: constants.NgFlavorName, description: "Learn more at https://angular.io/" }, |
| 83 | + { key: constants.VueFlavorName, description: "Learn more at https://vuejs.org/" }, |
| 84 | + { key: constants.TsFlavorName, description: "Learn more at https://www.typescriptlang.org/" }, |
| 85 | + { key: constants.JsFlavorName, description: "Learn more at https://www.javascript.com/" }, |
| 86 | + ]); |
| 87 | + return flavorSelection; |
| 88 | + } |
| 89 | + |
| 90 | + private printInteractiveCreationIntroIfNeeded() { |
| 91 | + if (!this.isInteractionIntroShown) { |
| 92 | + this.isInteractionIntroShown = true; |
| 93 | + this.$logger.info(); |
| 94 | + this.$logger.printMarkdown(`# Let’s create a NativeScript app!`); |
| 95 | + this.$logger.printMarkdown(` |
| 96 | +Answer the following questions to help us build the right app for you. (Note: you |
| 97 | +can skip this prompt next time using the --template option, or the --ng, --vue, --ts, |
| 98 | +or --js flags.) |
| 99 | +`); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + private async interactiveTemplateSelection(flavorSelection: string, adverb: string) { |
| 104 | + const selectedFlavorTemplates: { |
| 105 | + key?: string; |
| 106 | + value: string; |
| 107 | + description?: string; |
| 108 | + }[] = []; |
| 109 | + let selectedTemplate: string; |
| 110 | + switch (flavorSelection) { |
| 111 | + case constants.NgFlavorName: { |
| 112 | + selectedFlavorTemplates.push(...this.getNgFlavors()); |
| 113 | + break; |
| 114 | + } |
| 115 | + case constants.VueFlavorName: { |
| 116 | + selectedFlavorTemplates.push({ value: "https://github.com/NativeScript/template-blank-vue/tarball/0.9.0" }); |
| 117 | + break; |
| 118 | + } |
| 119 | + case constants.TsFlavorName: { |
| 120 | + selectedFlavorTemplates.push(...this.getTsTemplates()); |
| 121 | + break; |
| 122 | + } |
| 123 | + case constants.JsFlavorName: { |
| 124 | + selectedFlavorTemplates.push(...this.getJsTemplates()); |
| 125 | + break; |
| 126 | + } |
| 127 | + } |
| 128 | + if (selectedFlavorTemplates.length > 1) { |
| 129 | + this.$logger.info(); |
| 130 | + const templateChoices = selectedFlavorTemplates.map((template) => { |
| 131 | + return { key: template.key, description: template.description }; |
| 132 | + }); |
| 133 | + const selectedTemplateKey = await this.$prompter.promptForDetailedChoice(`${adverb}, which template would you like to start from?`, templateChoices); |
| 134 | + selectedTemplate = selectedFlavorTemplates.find(t => t.key === selectedTemplateKey).value; |
| 135 | + } else { |
| 136 | + selectedTemplate = selectedFlavorTemplates[0].value; |
| 137 | + } |
| 138 | + return selectedTemplate; |
| 139 | + } |
| 140 | + |
| 141 | + private getJsTemplates() { |
| 142 | + const templates = [{ |
| 143 | + key: CreateProjectCommand.HelloWorldTemplateKey, |
| 144 | + value: constants.RESERVED_TEMPLATE_NAMES.javascript, |
| 145 | + description: CreateProjectCommand.HelloWorldTemplateDescription |
| 146 | + }, |
| 147 | + { |
| 148 | + key: CreateProjectCommand.DrawerTemplateKey, |
| 149 | + value: "tns-template-drawer-navigation", |
| 150 | + description: CreateProjectCommand.DrawerTemplateDescription |
| 151 | + }, |
| 152 | + { |
| 153 | + key: CreateProjectCommand.TabsTemplateKey, |
| 154 | + value: "tns-template-tab-navigation", |
| 155 | + description: CreateProjectCommand.TabsTemplateDescription |
| 156 | + }]; |
| 157 | + |
| 158 | + return templates; |
| 159 | + } |
| 160 | + |
| 161 | + private getTsTemplates() { |
| 162 | + const templates = [{ |
| 163 | + key: CreateProjectCommand.HelloWorldTemplateKey, |
| 164 | + value: constants.RESERVED_TEMPLATE_NAMES.typescript, |
| 165 | + description: CreateProjectCommand.HelloWorldTemplateDescription |
| 166 | + }, |
| 167 | + { |
| 168 | + key: CreateProjectCommand.DrawerTemplateKey, |
| 169 | + value: "tns-template-drawer-navigation-ts", |
| 170 | + description: CreateProjectCommand.DrawerTemplateDescription |
| 171 | + }, |
| 172 | + { |
| 173 | + key: CreateProjectCommand.TabsTemplateKey, |
| 174 | + value: "tns-template-tab-navigation-ts", |
| 175 | + description: CreateProjectCommand.TabsTemplateDescription |
| 176 | + }]; |
| 177 | + |
| 178 | + return templates; |
| 179 | + } |
| 180 | + |
| 181 | + private getNgFlavors() { |
| 182 | + const templates = [{ |
| 183 | + key: CreateProjectCommand.HelloWorldTemplateKey, |
| 184 | + value: constants.RESERVED_TEMPLATE_NAMES.angular, |
| 185 | + description: CreateProjectCommand.HelloWorldTemplateDescription |
| 186 | + }, |
| 187 | + { |
| 188 | + key: CreateProjectCommand.DrawerTemplateKey, |
| 189 | + value: "tns-template-drawer-navigation-ng", |
| 190 | + description: CreateProjectCommand.DrawerTemplateDescription |
| 191 | + }, |
| 192 | + { |
| 193 | + key: CreateProjectCommand.TabsTemplateKey, |
| 194 | + value: "tns-template-tab-navigation-ng", |
| 195 | + description: CreateProjectCommand.TabsTemplateDescription |
| 196 | + }]; |
| 197 | + |
| 198 | + return templates; |
| 199 | + } |
| 200 | + |
40 | 201 | public async postCommandAction(args: string[]): Promise<void> {
|
41 |
| - const { projectDir } = this.createdProjecData; |
| 202 | + const { projectDir } = this.createdProjectData; |
42 | 203 | const relativePath = path.relative(process.cwd(), projectDir);
|
43 | 204 | this.$logger.printMarkdown(`Now you can navigate to your project with \`$ cd ${relativePath}\``);
|
44 | 205 | this.$logger.printMarkdown(`After that you can run it on device/emulator by executing \`$ tns run <platform>\``);
|
|
0 commit comments