Skip to content

Commit 15e6c64

Browse files
committed
refactor(prompt): improve input types, unify 'list' -> 'select'
fix: schematics prompt call
1 parent b8b19c2 commit 15e6c64

6 files changed

Lines changed: 60 additions & 52 deletions

File tree

packages/core/prompt/BasePromptSession.ts

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,11 @@ export abstract class BasePromptSession {
4141
name: "projectName",
4242
message: "Enter a name for your project:",
4343
default: Util.getAvailableName(defaultProjName, true),
44-
choices: null,
4544
validate: this.nameIsValid
4645
});
4746

4847
const frameRes: string = await this.getUserInput({
49-
type: "list",
48+
type: "select",
5049
name: "framework",
5150
message: "Choose framework:",
5251
choices: this.getFrameworkNames(),
@@ -117,9 +116,12 @@ export abstract class BasePromptSession {
117116
* @param options to use for the user input
118117
* @param withBackChoice Add a "Back" option to choices list
119118
*/
120-
protected async getUserInput(options: IUserInputOptions, withBackChoice: boolean = false): Promise<string> {
119+
protected async getUserInput(
120+
options: Exclude<UserInputOptions, { type: "checkbox" }>,
121+
withBackChoice: boolean = false,
122+
): Promise<string> {
121123

122-
if (options.choices) {
124+
if ("choices" in options) {
123125
if (options.choices.length < 2) {
124126
// single choice to return:
125127
let choice = options.choices[0];
@@ -133,8 +135,8 @@ export abstract class BasePromptSession {
133135
options.choices = this.addSeparators(options.choices);
134136
}
135137

136-
let result: string = null;
137-
if (options.type === "list") {
138+
let result = "";
139+
if (options.type === "select") {
138140
result = await InquirerWrapper.select(options);
139141
} else {
140142
result = await InquirerWrapper.input(options);
@@ -213,7 +215,7 @@ export abstract class BasePromptSession {
213215
const projectLibraries = this.getProjectLibNames(framework);
214216

215217
const projectRes = await this.getUserInput({
216-
type: "list",
218+
type: "select",
217219
name: "projectType",
218220
message: "Choose the type of project:",
219221
choices: projectLibraries
@@ -228,7 +230,7 @@ export abstract class BasePromptSession {
228230
protected async getProjectTemplate(projectLibrary: ProjectLibrary): Promise<ProjectTemplate> {
229231
const visibleProjects = projectLibrary.projects.filter(p => !p.isHidden);
230232
const componentNameRes = await this.getUserInput({
231-
type: "list",
233+
type: "select",
232234
name: "projTemplate",
233235
message: "Choose project template:",
234236
choices: Util.formatChoices(visibleProjects)
@@ -242,7 +244,7 @@ export abstract class BasePromptSession {
242244
*/
243245
protected async getTheme(projectLibrary: ProjectLibrary): Promise<string> {
244246
const theme = await this.getUserInput({
245-
type: "list",
247+
type: "select",
246248
name: "theme",
247249
message: "Choose the theme for the project:",
248250
choices: projectLibrary.themes,
@@ -266,7 +268,6 @@ export abstract class BasePromptSession {
266268
name: `${type === "component" ? type : "customView"}Name`,
267269
message: `Name your ${type}:`,
268270
default: availableDefaultName,
269-
choices: null,
270271
validate: (input: string) => {
271272
// TODO: GA post?
272273
const name = Util.nameFromPath(input);
@@ -329,27 +330,26 @@ export abstract class BasePromptSession {
329330
* Generate questions from extra configuration array
330331
* @param extraConfig
331332
*/
332-
private createQuestions(extraConfig: ControlExtraConfiguration[]): { type: string; name: string; message: string; choices: any[]; default: any; }[] {
333-
const result = [];
333+
private createQuestions(extraConfig: ControlExtraConfiguration[]): UserInputOptions[] {
334+
const result: UserInputOptions[] = [];
334335
for (const element of extraConfig) {
335-
const currExtraConfig = {};
336+
const base = {
337+
default: element.default,
338+
message: element.message,
339+
name: element.key,
340+
};
336341
switch (element.type) {
337342
case ControlExtraConfigType.Choice:
338-
currExtraConfig["type"] = "select"; // formerly list
343+
result.push({ ...base, type: "select", choices: element.choices ?? [] });
339344
break;
340345
case ControlExtraConfigType.MultiChoice:
341-
currExtraConfig["type"] = "checkbox";
346+
result.push({ ...base, type: "checkbox", choices: element.choices ?? [] });
342347
break;
343348
case ControlExtraConfigType.Value:
344349
default:
345-
currExtraConfig["type"] = "input";
350+
result.push({ ...base, type: "input" });
346351
break;
347352
}
348-
currExtraConfig["default"] = element.default;
349-
currExtraConfig["message"] = element.message;
350-
currExtraConfig["name"] = element.key;
351-
currExtraConfig["choices"] = element.choices;
352-
result.push(currExtraConfig);
353353
}
354354
return result;
355355
}
@@ -368,7 +368,7 @@ export abstract class BasePromptSession {
368368
private chooseActionTask: Task<PromptTaskContext> = async (runner, context) => {
369369
Util.log(""); /* new line */
370370
const action: string = await this.getUserInput({
371-
type: "list",
371+
type: "select",
372372
name: "action",
373373
message: "Choose an action:",
374374
choices: this.generateActionChoices(context.projectLibrary),
@@ -412,7 +412,7 @@ export abstract class BasePromptSession {
412412
Util.log("The project will be created using a Trial version of Ignite UI for Angular.");
413413
Util.log("You can always run the upgrade-packages command once it's created.");
414414
const shouldUpgrade = await this.getUserInput({
415-
type: "list",
415+
type: "select",
416416
name: "shouldUpgrade",
417417
message: "Would you like to upgrade to the licensed feed now?",
418418
choices: [
@@ -433,7 +433,6 @@ export abstract class BasePromptSession {
433433
name: "port",
434434
message: "Choose app host port:",
435435
default: defaultPort,
436-
choices: null,
437436
validate: (input: string) => {
438437
if (!Number(input)) {
439438
Util.log(""); /* new line */
@@ -459,7 +458,7 @@ export abstract class BasePromptSession {
459458
private getComponentGroupTask: Task<PromptTaskContext> = async (_runner, context) => {
460459
const groups = context.projectLibrary.getComponentGroupNames();
461460
const groupRes: string = await this.getUserInput({
462-
type: "list",
461+
type: "select",
463462
name: "componentGroup",
464463
message: "Choose a group:",
465464
choices: Util.formatChoices(context.projectLibrary.getComponentGroups()),
@@ -480,7 +479,7 @@ export abstract class BasePromptSession {
480479
*/
481480
private getComponentTask: Task<PromptTaskContext> = async (_runner, context) => {
482481
const componentNameRes = await this.getUserInput({
483-
type: "list",
482+
type: "select",
484483
name: "component",
485484
message: "Choose a component:",
486485
choices: Util.formatChoices(context.projectLibrary.getComponentsByGroup(context.group))
@@ -503,7 +502,7 @@ export abstract class BasePromptSession {
503502
const templates: Template[] = context.component.templates;
504503

505504
const templateRes = await this.getUserInput({
506-
type: "list",
505+
type: "select",
507506
name: "template",
508507
message: "Choose one:",
509508
choices: Util.formatChoices(templates)
@@ -534,7 +533,7 @@ export abstract class BasePromptSession {
534533
const customTemplates: Template[] = context.projectLibrary.getCustomTemplates();
535534

536535
const customTemplateNameRes = await this.getUserInput({
537-
type: "list",
536+
type: "select",
538537
name: "customTemplate",
539538
message: "Choose custom view:",
540539
choices: Util.formatChoices(customTemplates)
@@ -554,7 +553,7 @@ export abstract class BasePromptSession {
554553
return false;
555554
}
556555

557-
private logAutoSelected(options: IUserInputOptions, choice: any) {
556+
private logAutoSelected(options: UserInputOptions, choice: any) {
558557
let text;
559558
switch (options.name) {
560559
case "framework":
@@ -629,16 +628,28 @@ export abstract class BasePromptSession {
629628
}
630629
}
631630

632-
/** Options for User Input */
633-
export interface IUserInputOptions {
634-
type: string;
631+
type InputOptions = {
632+
type: "input";
635633
name: string;
636634
message: string;
637-
choices: any[];
638635
default?: any;
639636
validate?: (input: string) => string | boolean;
640637
}
641638

639+
type SelectOptions = Omit<InputOptions, "type"> & {
640+
type: "select";
641+
// TODO: Expand type:
642+
choices: any[];
643+
}
644+
645+
type CheckboxOptions = Omit<SelectOptions, "type"> & {
646+
type: "checkbox";
647+
required?: boolean;
648+
}
649+
650+
/** Options for User Input */
651+
export type UserInputOptions = InputOptions | SelectOptions | CheckboxOptions;
652+
642653
/** Context type for prompt tasks */
643654
export interface PromptTaskContext {
644655
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/ng-new/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export function newProject(options: OptionsSchema): Rule {
6969
name: "projectName",
7070
message: "Enter a name for your project:",
7171
default: Util.getAvailableName(defaultProjName, true),
72-
choices: null as unknown as string[],
7372
validate: prompt.nameIsValid
7473
});
7574
nameProvided = false;

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, 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";
@@ -26,7 +26,10 @@ export class SchematicsPromptSession extends BasePromptSession {
2626
this.userAnswers = new Map<string, any>();
2727
}
2828

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

spec/unit/PromptSession-spec.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,11 @@ describe("Unit - PromptSession", () => {
410410
});
411411
it("chooseActionLoop - should run through properly - Add Component", async () => {
412412
const mockExtraConfigurations = [{
413-
choices: [],
414413
default: "Choice 1",
415414
message: "Please enter a value",
416415
key: "customValue1",
417416
type: ControlExtraConfigType.Value
418417
}, {
419-
choices: [],
420418
default: "Choice 1",
421419
message: "Please enter a value",
422420
key: "customValue2",
@@ -521,14 +519,12 @@ describe("Unit - PromptSession", () => {
521519
default: "Choice 1",
522520
message: "Please enter a value",
523521
name: "customValue1",
524-
choices: []
525522
});
526523
expect(InquirerWrapper.input).toHaveBeenCalledWith({
527524
type: "input",
528525
default: "Choice 1",
529526
message: "Please enter a value",
530527
name: "customValue2",
531-
choices: []
532528
});
533529
});
534530
it("chooseActionLoop - should run through properly - Add scenario", async () => {

0 commit comments

Comments
 (0)