Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 17 additions & 82 deletions packages/cli/lib/PromptSession.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
BasePromptSession, GoogleAnalytics, InquirerWrapper, PackageManager, ProjectConfig,
BasePromptSession, Framework, InquirerWrapper, PackageManager,
ProjectLibrary, PromptTaskContext, Task, Util
} from "@igniteui/cli-core";
import * as path from "path";
Expand All @@ -15,112 +15,47 @@ export class PromptSession extends BasePromptSession {
super(templateManager);
}

public static async chooseTerm() {
public static async chooseTerm(): Promise<string> {
const answer = await InquirerWrapper.input({
default: null,
message: "Enter a search term",
});
if (answer) {
return answer;
} else {
const retProm = await this.chooseTerm();
return retProm;
return this.chooseTerm();
}
}

/**
* Start questions session for project creation
*/
public async start() {
GoogleAnalytics.post({
t: "screenview",
cd: "Wizard"
});

let projLibrary: ProjectLibrary;
let theme: string;
add.templateManager = this.templateManager as TemplateManager;
this.config = ProjectConfig.getConfig();
const defaultProjName = "IG Project";

if (ProjectConfig.hasLocalConfig() && !this.config.project.isShowcase) {
projLibrary = this.templateManager.getProjectLibrary(this.config.project.framework, this.config.project.projectType);
theme = this.config.project.theme;
} else {
Util.log(""); /* new line */
const projectName = await this.getUserInput({
type: "input",
name: "projectName",
message: "Enter a name for your project:",
default: Util.getAvailableName(defaultProjName, true),
choices: null,
validate: this.nameIsValid
});

const frameRes: string = await this.getUserInput({
type: "list",
name: "framework",
message: "Choose framework:",
choices: this.getFrameworkNames(),
default: "Angular"
});

const framework = this.templateManager.getFrameworkByName(frameRes);
// app name validation???
projLibrary = await this.getProjectLibrary(framework);
if (frameRes === "Angular" && projLibrary.projectType === "igx-ts") {
Util.log("Psst! Did you know you can also use our schematics package with Angular CLI to create and modify your projects?", "yellow");
Util.log("Read more at: https://www.infragistics.com/products/ignite-ui-angular/angular/components/general/cli-overview", "yellow");
}
const projTemplate = await this.getProjectTemplate(projLibrary);
// project options:
theme = await this.getTheme(projLibrary);

Util.log(" Generating project structure.");
const config = projTemplate.generateConfig(projectName, theme);
for (const templatePath of projTemplate.templatePaths) {
await Util.processTemplates(templatePath, path.join(process.cwd(), projectName),
config, projTemplate.delimiters, false);
}

Util.log(Util.greenCheck() + " Project structure generated.");
if (!this.config.skipGit) {
Util.gitInit(process.cwd(), projectName);
}
// move cwd to project folder
process.chdir(projectName);
await this.configureAI();
protected override async getProjectLibrary(framework: Framework): Promise<ProjectLibrary> {
const result = await super.getProjectLibrary(framework);
if (framework.name === "Angular" && result.projectType === "igx-ts") {
Util.log("Psst! Did you know you can also use our schematics package with Angular CLI to create and modify your projects?", "yellow");
Util.log("Read more at: https://www.infragistics.com/products/ignite-ui-angular/angular/components/general/cli-overview", "yellow");
}
await this.chooseActionLoop(projLibrary);
//TODO: restore cwd?
return result;
}

protected async completeAndRun(port?: number) {
protected override async completeAndRun(port?: number) {
await PackageManager.flushQueue(true);
await start.start({ port });
}

protected async upgradePackages() {
protected override async upgradePackages() {
upgrade.templateManager = this.templateManager as TemplateManager;
await upgrade.upgrade({ skipInstall: true, _: ["upgrade"], $0: "upgrade" });
}

protected async configureAI(): Promise<void> {
protected override async configureAI(): Promise<void> {
await aiConfigure();
}

/**
* Get user name and set template's extra configurations if any
* @param projectLibrary to add component to
* @param component to get template for
*/
protected templateSelectedTask(type: "component" | "view" = "component"): Task<PromptTaskContext> {
protected override templateSelectedTask(type: "component" | "view" = "component"): Task<PromptTaskContext> {
return async (_runner, context) => {
const name = await this.chooseTemplateName(context.template, type);
if (context.template.hasExtraConfiguration) {
await this.customizeTemplateTask(context.template);
const name = await this.chooseTemplateName(context.template!, type);
if (context.template!.hasExtraConfiguration) {
await this.customizeTemplateTask(context.template!);
}
const res = await add.addTemplate(name, context.template);
const res = await add.addTemplate(name, context.template!);
return res;
};
}
Expand Down
77 changes: 44 additions & 33 deletions packages/core/prompt/BasePromptSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,11 @@ export abstract class BasePromptSession {
name: "projectName",
message: "Enter a name for your project:",
default: Util.getAvailableName(defaultProjName, true),
choices: null,
validate: this.nameIsValid
});

const frameRes: string = await this.getUserInput({
type: "list",
type: "select",
name: "framework",
message: "Choose framework:",
choices: this.getFrameworkNames(),
Expand Down Expand Up @@ -114,9 +113,12 @@ export abstract class BasePromptSession {
* @param options to use for the user input
* @param withBackChoice Add a "Back" option to choices list
*/
protected async getUserInput(options: IUserInputOptions, withBackChoice: boolean = false): Promise<string> {
protected async getUserInput(
options: Exclude<UserInputOptions, { type: "checkbox" }>,
withBackChoice: boolean = false,
): Promise<string> {

if (options.choices) {
if ("choices" in options) {
if (options.choices.length < 2) {
// single choice to return:
let choice = options.choices[0];
Expand All @@ -130,8 +132,8 @@ export abstract class BasePromptSession {
options.choices = this.addSeparators(options.choices);
}

let result: string = null;
if (options.type === "list") {
let result = "";
if (options.type === "select") {
result = await InquirerWrapper.select(options);
} else {
result = await InquirerWrapper.input(options);
Expand Down Expand Up @@ -210,7 +212,7 @@ export abstract class BasePromptSession {
const projectLibraries = this.getProjectLibNames(framework);

const projectRes = await this.getUserInput({
type: "list",
type: "select",
name: "projectType",
message: "Choose the type of project:",
choices: projectLibraries
Expand All @@ -225,7 +227,7 @@ export abstract class BasePromptSession {
protected async getProjectTemplate(projectLibrary: ProjectLibrary): Promise<ProjectTemplate> {
const visibleProjects = projectLibrary.projects.filter(p => !p.isHidden);
const componentNameRes = await this.getUserInput({
type: "list",
type: "select",
name: "projTemplate",
message: "Choose project template:",
choices: Util.formatChoices(visibleProjects)
Expand All @@ -239,7 +241,7 @@ export abstract class BasePromptSession {
*/
protected async getTheme(projectLibrary: ProjectLibrary): Promise<string> {
const theme = await this.getUserInput({
type: "list",
type: "select",
name: "theme",
message: "Choose the theme for the project:",
choices: projectLibrary.themes,
Expand All @@ -263,7 +265,6 @@ export abstract class BasePromptSession {
name: `${type === "component" ? type : "customView"}Name`,
message: `Name your ${type}:`,
default: availableDefaultName,
choices: null,
validate: (input: string) => {
// TODO: GA post?
const name = Util.nameFromPath(input);
Expand Down Expand Up @@ -326,27 +327,26 @@ export abstract class BasePromptSession {
* Generate questions from extra configuration array
* @param extraConfig
*/
private createQuestions(extraConfig: ControlExtraConfiguration[]): { type: string; name: string; message: string; choices: any[]; default: any; }[] {
const result = [];
private createQuestions(extraConfig: ControlExtraConfiguration[]): UserInputOptions[] {
const result: UserInputOptions[] = [];
for (const element of extraConfig) {
const currExtraConfig = {};
const base = {
default: element.default,
message: element.message,
name: element.key,
};
switch (element.type) {
case ControlExtraConfigType.Choice:
currExtraConfig["type"] = "select"; // formerly list
result.push({ ...base, type: "select", choices: element.choices ?? [] });
break;
case ControlExtraConfigType.MultiChoice:
currExtraConfig["type"] = "checkbox";
result.push({ ...base, type: "checkbox", choices: element.choices ?? [] });
break;
case ControlExtraConfigType.Value:
default:
currExtraConfig["type"] = "input";
result.push({ ...base, type: "input" });
break;
}
currExtraConfig["default"] = element.default;
currExtraConfig["message"] = element.message;
currExtraConfig["name"] = element.key;
currExtraConfig["choices"] = element.choices;
result.push(currExtraConfig);
}
return result;
}
Expand All @@ -365,7 +365,7 @@ export abstract class BasePromptSession {
private chooseActionTask: Task<PromptTaskContext> = async (runner, context) => {
Util.log(""); /* new line */
const action: string = await this.getUserInput({
type: "list",
type: "select",
name: "action",
message: "Choose an action:",
choices: this.generateActionChoices(context.projectLibrary),
Expand Down Expand Up @@ -409,7 +409,7 @@ export abstract class BasePromptSession {
Util.log("The project will be created using a Trial version of Ignite UI for Angular.");
Util.log("You can always run the upgrade-packages command once it's created.");
const shouldUpgrade = await this.getUserInput({
type: "list",
type: "select",
name: "shouldUpgrade",
message: "Would you like to upgrade to the licensed feed now?",
choices: [
Expand All @@ -430,7 +430,6 @@ export abstract class BasePromptSession {
name: "port",
message: "Choose app host port:",
default: defaultPort,
choices: null,
validate: (input: string) => {
if (!Number(input)) {
Util.log(""); /* new line */
Expand All @@ -456,7 +455,7 @@ export abstract class BasePromptSession {
private getComponentGroupTask: Task<PromptTaskContext> = async (_runner, context) => {
const groups = context.projectLibrary.getComponentGroupNames();
const groupRes: string = await this.getUserInput({
type: "list",
type: "select",
name: "componentGroup",
message: "Choose a group:",
choices: Util.formatChoices(context.projectLibrary.getComponentGroups()),
Expand All @@ -477,7 +476,7 @@ export abstract class BasePromptSession {
*/
private getComponentTask: Task<PromptTaskContext> = async (_runner, context) => {
const componentNameRes = await this.getUserInput({
type: "list",
type: "select",
name: "component",
message: "Choose a component:",
choices: Util.formatChoices(context.projectLibrary.getComponentsByGroup(context.group))
Expand All @@ -500,7 +499,7 @@ export abstract class BasePromptSession {
const templates: Template[] = context.component.templates;

const templateRes = await this.getUserInput({
type: "list",
type: "select",
name: "template",
message: "Choose one:",
choices: Util.formatChoices(templates)
Expand Down Expand Up @@ -531,7 +530,7 @@ export abstract class BasePromptSession {
const customTemplates: Template[] = context.projectLibrary.getCustomTemplates();

const customTemplateNameRes = await this.getUserInput({
type: "list",
type: "select",
name: "customTemplate",
message: "Choose custom view:",
choices: Util.formatChoices(customTemplates)
Expand All @@ -551,7 +550,7 @@ export abstract class BasePromptSession {
return false;
}

private logAutoSelected(options: IUserInputOptions, choice: any) {
private logAutoSelected(options: UserInputOptions, choice: any) {
let text;
switch (options.name) {
case "framework":
Expand Down Expand Up @@ -626,16 +625,28 @@ export abstract class BasePromptSession {
}
}

/** Options for User Input */
export interface IUserInputOptions {
type: string;
type InputOptions = {
type: "input";
name: string;
message: string;
choices: any[];
default?: any;
validate?: (input: string) => string | boolean;
}

type SelectOptions = Omit<InputOptions, "type"> & {
type: "select";
// TODO: Expand type:
choices: any[];
}

type CheckboxOptions = Omit<SelectOptions, "type"> & {
type: "checkbox";
required?: boolean;
}

/** Options for User Input */
export type UserInputOptions = InputOptions | SelectOptions | CheckboxOptions;

/** Context type for prompt tasks */
export interface PromptTaskContext {
projectLibrary: ProjectLibrary;
Expand Down
21 changes: 10 additions & 11 deletions packages/core/prompt/InquirerWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@ import { Context } from '@inquirer/type';

// ref - node_modules\@inquirer\input\dist\cjs\types\index.d.ts - bc for some reason this is not publicly exported
type InputConfig = {
message: string;
default?: string;
required?: boolean;
message: string;
default?: string;
required?: boolean;
type?: string;
name?: string;
choices?: (string | Separator)[] | ({ value: string; name?: string; checked?: boolean } | Separator)[];
transformer?: (value: string, { isFinal }: {
isFinal: boolean;
}) => string;
transformer?: (value: string, { isFinal }: {
isFinal: boolean;
}) => string;

// TODO: consider typing these by extracting the types from the inquirer package
validate?: any;
theme?: any;
validate?: any;
theme?: any;
};

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

export class InquirerWrapper {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/types/ControlExtraConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ControlExtraConfigType } from "./enumerations/ControlExtraConfigType";
*/
export interface ControlExtraConfiguration {
/** Prompt session works with choices except value - where the user should enter value */
choices: string[];
choices?: string[];

/** default value used when the user is prompted to choose or enter */
default: any;
Expand Down
Loading
Loading