Skip to content

Commit 2c851e3

Browse files
committed
refactor(prompt): improve input types, unify 'list' -> 'select'
1 parent 09b6482 commit 2c851e3

5 files changed

Lines changed: 60 additions & 51 deletions

File tree

packages/core/prompt/BasePromptSession.ts

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,11 @@ export abstract class BasePromptSession {
3838
name: "projectName",
3939
message: "Enter a name for your project:",
4040
default: Util.getAvailableName(defaultProjName, true),
41-
choices: null,
4241
validate: this.nameIsValid
4342
});
4443

4544
const frameRes: string = await this.getUserInput({
46-
type: "list",
45+
type: "select",
4746
name: "framework",
4847
message: "Choose framework:",
4948
choices: this.getFrameworkNames(),
@@ -114,9 +113,12 @@ export abstract class BasePromptSession {
114113
* @param options to use for the user input
115114
* @param withBackChoice Add a "Back" option to choices list
116115
*/
117-
protected async getUserInput(options: IUserInputOptions, withBackChoice: boolean = false): Promise<string> {
116+
protected async getUserInput(
117+
options: Exclude<UserInputOptions, { type: "checkbox" }>,
118+
withBackChoice: boolean = false,
119+
): Promise<string> {
118120

119-
if (options.choices) {
121+
if ("choices" in options) {
120122
if (options.choices.length < 2) {
121123
// single choice to return:
122124
let choice = options.choices[0];
@@ -130,8 +132,8 @@ export abstract class BasePromptSession {
130132
options.choices = this.addSeparators(options.choices);
131133
}
132134

133-
let result: string = null;
134-
if (options.type === "list") {
135+
let result = "";
136+
if (options.type === "select") {
135137
result = await InquirerWrapper.select(options);
136138
} else {
137139
result = await InquirerWrapper.input(options);
@@ -210,7 +212,7 @@ export abstract class BasePromptSession {
210212
const projectLibraries = this.getProjectLibNames(framework);
211213

212214
const projectRes = await this.getUserInput({
213-
type: "list",
215+
type: "select",
214216
name: "projectType",
215217
message: "Choose the type of project:",
216218
choices: projectLibraries
@@ -225,7 +227,7 @@ export abstract class BasePromptSession {
225227
protected async getProjectTemplate(projectLibrary: ProjectLibrary): Promise<ProjectTemplate> {
226228
const visibleProjects = projectLibrary.projects.filter(p => !p.isHidden);
227229
const componentNameRes = await this.getUserInput({
228-
type: "list",
230+
type: "select",
229231
name: "projTemplate",
230232
message: "Choose project template:",
231233
choices: Util.formatChoices(visibleProjects)
@@ -239,7 +241,7 @@ export abstract class BasePromptSession {
239241
*/
240242
protected async getTheme(projectLibrary: ProjectLibrary): Promise<string> {
241243
const theme = await this.getUserInput({
242-
type: "list",
244+
type: "select",
243245
name: "theme",
244246
message: "Choose the theme for the project:",
245247
choices: projectLibrary.themes,
@@ -263,7 +265,6 @@ export abstract class BasePromptSession {
263265
name: `${type === "component" ? type : "customView"}Name`,
264266
message: `Name your ${type}:`,
265267
default: availableDefaultName,
266-
choices: null,
267268
validate: (input: string) => {
268269
// TODO: GA post?
269270
const name = Util.nameFromPath(input);
@@ -326,27 +327,26 @@ export abstract class BasePromptSession {
326327
* Generate questions from extra configuration array
327328
* @param extraConfig
328329
*/
329-
private createQuestions(extraConfig: ControlExtraConfiguration[]): { type: string; name: string; message: string; choices: any[]; default: any; }[] {
330-
const result = [];
330+
private createQuestions(extraConfig: ControlExtraConfiguration[]): UserInputOptions[] {
331+
const result: UserInputOptions[] = [];
331332
for (const element of extraConfig) {
332-
const currExtraConfig = {};
333+
const base = {
334+
default: element.default,
335+
message: element.message,
336+
name: element.key,
337+
};
333338
switch (element.type) {
334339
case ControlExtraConfigType.Choice:
335-
currExtraConfig["type"] = "select"; // formerly list
340+
result.push({ ...base, type: "select", choices: element.choices ?? [] });
336341
break;
337342
case ControlExtraConfigType.MultiChoice:
338-
currExtraConfig["type"] = "checkbox";
343+
result.push({ ...base, type: "checkbox", choices: element.choices ?? [] });
339344
break;
340345
case ControlExtraConfigType.Value:
341346
default:
342-
currExtraConfig["type"] = "input";
347+
result.push({ ...base, type: "input" });
343348
break;
344349
}
345-
currExtraConfig["default"] = element.default;
346-
currExtraConfig["message"] = element.message;
347-
currExtraConfig["name"] = element.key;
348-
currExtraConfig["choices"] = element.choices;
349-
result.push(currExtraConfig);
350350
}
351351
return result;
352352
}
@@ -365,7 +365,7 @@ export abstract class BasePromptSession {
365365
private chooseActionTask: Task<PromptTaskContext> = async (runner, context) => {
366366
Util.log(""); /* new line */
367367
const action: string = await this.getUserInput({
368-
type: "list",
368+
type: "select",
369369
name: "action",
370370
message: "Choose an action:",
371371
choices: this.generateActionChoices(context.projectLibrary),
@@ -409,7 +409,7 @@ export abstract class BasePromptSession {
409409
Util.log("The project will be created using a Trial version of Ignite UI for Angular.");
410410
Util.log("You can always run the upgrade-packages command once it's created.");
411411
const shouldUpgrade = await this.getUserInput({
412-
type: "list",
412+
type: "select",
413413
name: "shouldUpgrade",
414414
message: "Would you like to upgrade to the licensed feed now?",
415415
choices: [
@@ -430,7 +430,6 @@ export abstract class BasePromptSession {
430430
name: "port",
431431
message: "Choose app host port:",
432432
default: defaultPort,
433-
choices: null,
434433
validate: (input: string) => {
435434
if (!Number(input)) {
436435
Util.log(""); /* new line */
@@ -456,7 +455,7 @@ export abstract class BasePromptSession {
456455
private getComponentGroupTask: Task<PromptTaskContext> = async (_runner, context) => {
457456
const groups = context.projectLibrary.getComponentGroupNames();
458457
const groupRes: string = await this.getUserInput({
459-
type: "list",
458+
type: "select",
460459
name: "componentGroup",
461460
message: "Choose a group:",
462461
choices: Util.formatChoices(context.projectLibrary.getComponentGroups()),
@@ -477,7 +476,7 @@ export abstract class BasePromptSession {
477476
*/
478477
private getComponentTask: Task<PromptTaskContext> = async (_runner, context) => {
479478
const componentNameRes = await this.getUserInput({
480-
type: "list",
479+
type: "select",
481480
name: "component",
482481
message: "Choose a component:",
483482
choices: Util.formatChoices(context.projectLibrary.getComponentsByGroup(context.group))
@@ -500,7 +499,7 @@ export abstract class BasePromptSession {
500499
const templates: Template[] = context.component.templates;
501500

502501
const templateRes = await this.getUserInput({
503-
type: "list",
502+
type: "select",
504503
name: "template",
505504
message: "Choose one:",
506505
choices: Util.formatChoices(templates)
@@ -531,7 +530,7 @@ export abstract class BasePromptSession {
531530
const customTemplates: Template[] = context.projectLibrary.getCustomTemplates();
532531

533532
const customTemplateNameRes = await this.getUserInput({
534-
type: "list",
533+
type: "select",
535534
name: "customTemplate",
536535
message: "Choose custom view:",
537536
choices: Util.formatChoices(customTemplates)
@@ -551,7 +550,7 @@ export abstract class BasePromptSession {
551550
return false;
552551
}
553552

554-
private logAutoSelected(options: IUserInputOptions, choice: any) {
553+
private logAutoSelected(options: UserInputOptions, choice: any) {
555554
let text;
556555
switch (options.name) {
557556
case "framework":
@@ -626,16 +625,28 @@ export abstract class BasePromptSession {
626625
}
627626
}
628627

629-
/** Options for User Input */
630-
export interface IUserInputOptions {
631-
type: string;
628+
type InputOptions = {
629+
type: "input";
632630
name: string;
633631
message: string;
634-
choices: any[];
635632
default?: any;
636633
validate?: (input: string) => string | boolean;
637634
}
638635

636+
type SelectOptions = Omit<InputOptions, "type"> & {
637+
type: "select";
638+
// TODO: Expand type:
639+
choices: any[];
640+
}
641+
642+
type CheckboxOptions = Omit<SelectOptions, "type"> & {
643+
type: "checkbox";
644+
required?: boolean;
645+
}
646+
647+
/** Options for User Input */
648+
export type UserInputOptions = InputOptions | SelectOptions | CheckboxOptions;
649+
639650
/** Context type for prompt tasks */
640651
export interface PromptTaskContext {
641652
projectLibrary: ProjectLibrary;

packages/core/prompt/InquirerWrapper.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,22 @@ import { Context } from '@inquirer/type';
33

44
// ref - node_modules\@inquirer\input\dist\cjs\types\index.d.ts - bc for some reason this is not publicly exported
55
type InputConfig = {
6-
message: string;
7-
default?: string;
8-
required?: boolean;
6+
message: string;
7+
default?: string;
8+
required?: boolean;
99
type?: string;
1010
name?: string;
11-
choices?: (string | Separator)[] | ({ value: string; name?: string; checked?: boolean } | Separator)[];
12-
transformer?: (value: string, { isFinal }: {
13-
isFinal: boolean;
14-
}) => string;
11+
transformer?: (value: string, { isFinal }: {
12+
isFinal: boolean;
13+
}) => string;
1514

1615
// TODO: consider typing these by extracting the types from the inquirer package
17-
validate?: any;
18-
theme?: any;
16+
validate?: any;
17+
theme?: any;
1918
};
2019

21-
type InputChoicesConfig = InputConfig & {
22-
choices: (string | Separator)[] | ({ value: string; name?: string; checked?: boolean } | Separator)[];
20+
type InputChoicesConfig = Omit<InputConfig, "transformer"> & {
21+
choices: (string | Separator)[] | ({ value: string; name?: string; checked?: boolean } | Separator)[];
2322
};
2423

2524
export class InquirerWrapper {

packages/core/types/ControlExtraConfiguration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { ControlExtraConfigType } from "./enumerations/ControlExtraConfigType";
66
*/
77
export interface ControlExtraConfiguration {
88
/** Prompt session works with choices except value - where the user should enter value */
9-
choices: string[];
9+
choices?: string[];
1010

1111
/** default value used when the user is prompted to choose or enter */
1212
default: any;

packages/ng-schematics/src/prompt/SchematicsPromptSession.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SchematicContext, Tree } from "@angular-devkit/schematics";
22
import { IgniteUIForAngularTemplate } from "@igniteui/angular-templates";
33
import {
44
BasePromptSession, BaseTemplateManager, Framework,
5-
IUserInputOptions, ProjectConfig, ProjectLibrary, ProjectTemplate, PromptTaskContext, Task
5+
type UserInputOptions, ProjectConfig, ProjectLibrary, ProjectTemplate, PromptTaskContext, Task
66
} from "@igniteui/cli-core";
77
import { of } from "rxjs";
88
import { TemplateOptions } from "../component/schema";
@@ -27,7 +27,10 @@ export class SchematicsPromptSession extends BasePromptSession {
2727
this.userAnswers = new Map<string, any>();
2828
}
2929

30-
public async getUserInput(options: IUserInputOptions, withBackChoice: boolean = false): Promise<string> {
30+
public async getUserInput(
31+
options: Exclude<UserInputOptions, { type: "checkbox" }>,
32+
withBackChoice: boolean = false,
33+
): Promise<string> {
3134
return super.getUserInput(options, withBackChoice);
3235
}
3336

spec/unit/PromptSession-spec.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,11 @@ describe("Unit - PromptSession", () => {
405405
});
406406
it("chooseActionLoop - should run through properly - Add Component", async () => {
407407
const mockExtraConfigurations = [{
408-
choices: [],
409408
default: "Choice 1",
410409
message: "Please enter a value",
411410
key: "customValue1",
412411
type: ControlExtraConfigType.Value
413412
}, {
414-
choices: [],
415413
default: "Choice 1",
416414
message: "Please enter a value",
417415
key: "customValue2",
@@ -514,14 +512,12 @@ describe("Unit - PromptSession", () => {
514512
default: "Choice 1",
515513
message: "Please enter a value",
516514
name: "customValue1",
517-
choices: []
518515
});
519516
expect(InquirerWrapper.input).toHaveBeenCalledWith({
520517
type: "input",
521518
default: "Choice 1",
522519
message: "Please enter a value",
523520
name: "customValue2",
524-
choices: []
525521
});
526522
});
527523
it("chooseActionLoop - should run through properly - Add scenario", async () => {

0 commit comments

Comments
 (0)