From 05fdbf1a55b20052129c5335dae7c556f6b01bae Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Fri, 8 May 2026 19:08:33 +0300 Subject: [PATCH 1/3] refactor(prompt): drop duplicate start override for one log, cleanup --- packages/cli/lib/PromptSession.ts | 99 ++++++------------------------- 1 file changed, 17 insertions(+), 82 deletions(-) diff --git a/packages/cli/lib/PromptSession.ts b/packages/cli/lib/PromptSession.ts index ca2f73e65..5fa19293e 100644 --- a/packages/cli/lib/PromptSession.ts +++ b/packages/cli/lib/PromptSession.ts @@ -1,8 +1,7 @@ import { - BasePromptSession, GoogleAnalytics, InquirerWrapper, PackageManager, ProjectConfig, + BasePromptSession, Framework, InquirerWrapper, PackageManager, ProjectLibrary, PromptTaskContext, Task, Util } from "@igniteui/cli-core"; -import * as path from "path"; import { default as add } from "./commands/add"; import { configure as aiConfigure } from "./commands/ai-config"; import { default as start } from "./commands/start"; @@ -10,110 +9,46 @@ import { default as upgrade } from "./commands/upgrade"; export class PromptSession extends BasePromptSession { - public static async chooseTerm() { + public static async chooseTerm(): Promise { 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; - 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 { + 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() { await upgrade.upgrade({ skipInstall: true, _: ["upgrade"], $0: "upgrade" }); } - protected async configureAI(): Promise { + protected override async configureAI(): Promise { 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 { + protected override templateSelectedTask(type: "component" | "view" = "component"): Task { 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; }; } From 132b7dfcd7270b2785c178ef3062cd3a29f1d46f Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Mon, 11 May 2026 10:47:02 +0300 Subject: [PATCH 2/3] refactor(prompt): improve input types, unify 'list' -> 'select' fix: schematics prompt call test: squash me --- packages/core/prompt/BasePromptSession.ts | 77 +++++++++++-------- packages/core/prompt/InquirerWrapper.ts | 21 +++-- .../core/types/ControlExtraConfiguration.ts | 2 +- packages/ng-schematics/src/ng-new/index.ts | 1 - .../src/prompt/SchematicsPromptSession.ts | 7 +- spec/unit/PromptSession-spec.ts | 8 +- 6 files changed, 62 insertions(+), 54 deletions(-) diff --git a/packages/core/prompt/BasePromptSession.ts b/packages/core/prompt/BasePromptSession.ts index 853aa5611..3021ed7f1 100644 --- a/packages/core/prompt/BasePromptSession.ts +++ b/packages/core/prompt/BasePromptSession.ts @@ -41,12 +41,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(), @@ -117,9 +116,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 { + protected async getUserInput( + options: Exclude, + withBackChoice: boolean = false, + ): Promise { - if (options.choices) { + if ("choices" in options) { if (options.choices.length < 2) { // single choice to return: let choice = options.choices[0]; @@ -133,8 +135,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); @@ -213,7 +215,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 @@ -228,7 +230,7 @@ export abstract class BasePromptSession { protected async getProjectTemplate(projectLibrary: ProjectLibrary): Promise { 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) @@ -242,7 +244,7 @@ export abstract class BasePromptSession { */ protected async getTheme(projectLibrary: ProjectLibrary): Promise { const theme = await this.getUserInput({ - type: "list", + type: "select", name: "theme", message: "Choose the theme for the project:", choices: projectLibrary.themes, @@ -266,7 +268,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); @@ -329,27 +330,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; } @@ -368,7 +368,7 @@ export abstract class BasePromptSession { private chooseActionTask: Task = 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), @@ -412,7 +412,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: [ @@ -433,7 +433,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 */ @@ -459,7 +458,7 @@ export abstract class BasePromptSession { private getComponentGroupTask: Task = 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()), @@ -480,7 +479,7 @@ export abstract class BasePromptSession { */ private getComponentTask: Task = 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)) @@ -503,7 +502,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) @@ -534,7 +533,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) @@ -554,7 +553,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": @@ -629,16 +628,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 & { + type: "select"; + // TODO: Expand type: + choices: any[]; +} + +type CheckboxOptions = Omit & { + type: "checkbox"; + required?: boolean; +} + +/** Options for User Input */ +export type UserInputOptions = InputOptions | SelectOptions | CheckboxOptions; + /** Context type for prompt tasks */ export interface PromptTaskContext { projectLibrary: ProjectLibrary; diff --git a/packages/core/prompt/InquirerWrapper.ts b/packages/core/prompt/InquirerWrapper.ts index d0f33279e..d96c59bd8 100644 --- a/packages/core/prompt/InquirerWrapper.ts +++ b/packages/core/prompt/InquirerWrapper.ts @@ -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 & { + choices: (string | Separator)[] | ({ value: string; name?: string; checked?: boolean } | Separator)[]; }; export class InquirerWrapper { diff --git a/packages/core/types/ControlExtraConfiguration.ts b/packages/core/types/ControlExtraConfiguration.ts index e27c933f5..6b62522a5 100644 --- a/packages/core/types/ControlExtraConfiguration.ts +++ b/packages/core/types/ControlExtraConfiguration.ts @@ -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; diff --git a/packages/ng-schematics/src/ng-new/index.ts b/packages/ng-schematics/src/ng-new/index.ts index c512e2725..3403307fd 100644 --- a/packages/ng-schematics/src/ng-new/index.ts +++ b/packages/ng-schematics/src/ng-new/index.ts @@ -69,7 +69,6 @@ export function newProject(options: OptionsSchema): Rule { name: "projectName", message: "Enter a name for your project:", default: Util.getAvailableName(defaultProjName, true), - choices: null as unknown as string[], validate: prompt.nameIsValid }); nameProvided = false; diff --git a/packages/ng-schematics/src/prompt/SchematicsPromptSession.ts b/packages/ng-schematics/src/prompt/SchematicsPromptSession.ts index be7e367d7..6d0ef2221 100644 --- a/packages/ng-schematics/src/prompt/SchematicsPromptSession.ts +++ b/packages/ng-schematics/src/prompt/SchematicsPromptSession.ts @@ -2,7 +2,7 @@ import { SchematicContext, Tree } from "@angular-devkit/schematics"; import { IgniteUIForAngularTemplate } from "@igniteui/angular-templates"; import { BasePromptSession, Framework, - IUserInputOptions, ProjectConfig, ProjectLibrary, ProjectTemplate, PromptTaskContext, Task + type UserInputOptions, ProjectConfig, ProjectLibrary, ProjectTemplate, PromptTaskContext, Task } from "@igniteui/cli-core"; import { of } from "rxjs"; import { TemplateOptions } from "../component/schema"; @@ -26,7 +26,10 @@ export class SchematicsPromptSession extends BasePromptSession { this.userAnswers = new Map(); } - public async getUserInput(options: IUserInputOptions, withBackChoice: boolean = false): Promise { + public async getUserInput( + options: Exclude, + withBackChoice: boolean = false, + ): Promise { return super.getUserInput(options, withBackChoice); } diff --git a/spec/unit/PromptSession-spec.ts b/spec/unit/PromptSession-spec.ts index b209bddea..b739f902d 100644 --- a/spec/unit/PromptSession-spec.ts +++ b/spec/unit/PromptSession-spec.ts @@ -164,7 +164,7 @@ describe("Unit - PromptSession", () => { App.container.set(TEMPLATE_MANAGER, mockTemplate); const mockSession = new PromptSession(); const mockQuestion = { - type: "list", + type: "select", name: "theme", message: "Choose the theme for the project:", choices: ["infragistics", new Separator(), "infragistics.less"], @@ -352,7 +352,7 @@ describe("Unit - PromptSession", () => { App.container.set(TEMPLATE_MANAGER, mockTemplate); const mockSession = new PromptSession(); const mockQuestion = { - type: "list", + type: "select", name: "theme", message: "Choose the theme for the project:", choices: ["infragistics", new Separator(), "infragistics.less"], @@ -410,13 +410,11 @@ describe("Unit - PromptSession", () => { }); it("chooseActionLoop - should run through properly - Add Component", async () => { const mockExtraConfigurations = [{ - choices: [], default: "Choice 1", message: "Please enter a value", key: "customValue1", type: ControlExtraConfigType.Value }, { - choices: [], default: "Choice 1", message: "Please enter a value", key: "customValue2", @@ -521,14 +519,12 @@ describe("Unit - PromptSession", () => { default: "Choice 1", message: "Please enter a value", name: "customValue1", - choices: [] }); expect(InquirerWrapper.input).toHaveBeenCalledWith({ type: "input", default: "Choice 1", message: "Please enter a value", name: "customValue2", - choices: [] }); }); it("chooseActionLoop - should run through properly - Add scenario", async () => { From 66037662dff1a2e19c850d48f9675c054f57b440 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 18:36:00 +0300 Subject: [PATCH 3/3] chore: drop invalid VS Code TS option causing IDE server mismatch --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 663846dcc..c41b10903 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,5 @@ // Place your settings in this file to overwrite default and user settings. { - "typescript.tsdk": "./node_modules/typescript/lib", "editor.insertSpaces": false, "editor.detectIndentation": false, "files.exclude": {