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