• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-cli / 27742220407

18 Jun 2026 06:53AM UTC coverage: 87.817% (-0.07%) from 87.891%
27742220407

Pull #1719

github

web-flow
Merge a437b108a into ffc46000e
Pull Request #1719: feat: add Blazor project scaffolding options and templates

1160 of 1494 branches covered (77.64%)

Branch coverage included in aggregate %.

103 of 120 new or added lines in 7 files covered. (85.83%)

15 existing lines in 1 file now uncovered.

5832 of 6468 relevant lines covered (90.17%)

86.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.59
/packages/core/prompt/BasePromptSession.ts
1
import * as path from "path";
12✔
2
import { Separator } from "@inquirer/prompts";
12✔
3
import { BaseTemplateManager } from "../templates";
4
import {
5
        BaseTemplate, Component, Config, ControlExtraConfigType, ControlExtraConfiguration, Framework,
6
        FrameworkId, ProjectLibrary, ProjectTemplate, Template
7
} from "../types";
8
import { App, ChoiceItem, GoogleAnalytics, ProjectConfig, Util } from "../util";
12✔
9
import { TEMPLATE_MANAGER } from "../util/GlobalConstants";
12✔
10
import { Task, TaskRunner, WIZARD_BACK_OPTION } from "./TaskRunner";
12✔
11
import { InquirerWrapper } from "./InquirerWrapper";
12✔
12

13
export abstract class BasePromptSession {
12✔
14
        protected config: Config;
15

16
        protected get templateManager(): BaseTemplateManager {
17
                return App.container.get<BaseTemplateManager>(TEMPLATE_MANAGER);
26✔
18
        }
19

20
        /**
21
         * Start questions session for project creation
22
         */
23
        public async start() {
24
                GoogleAnalytics.post({
7✔
25
                        t: "screenview",
26
                        cd: "Wizard"
27
                });
28

29
                let projLibrary: ProjectLibrary;
30
                let theme: string;
31
                this.config = ProjectConfig.getConfig();
7✔
32
                const defaultProjName = "IG Project";
7✔
33

34
                if (ProjectConfig.hasLocalConfig() && !this.config.project.isShowcase) {
7✔
35
                        projLibrary = this.templateManager.getProjectLibrary(this.config.project.framework, this.config.project.projectType);
1✔
36
                        theme = this.config.project.theme;
1✔
37
                } else {
38
                        Util.log(""); /* new line */
6✔
39
                        const projectName = await this.getUserInput({
6✔
40
                                type: "input",
41
                                name: "projectName",
42
                                message: "Enter a name for your project:",
43
                                default: Util.getAvailableName(defaultProjName, true),
44
                                validate: this.nameIsValid
45
                        });
46

47
                        const frameRes: string = await this.getUserInput({
6✔
48
                                type: "select",
49
                                name: "framework",
50
                                message: "Choose framework:",
51
                                choices: this.getFrameworkNames(),
52
                                default: "Angular"
53
                        });
54

55
                        const framework = this.templateManager.getFrameworkByName(frameRes);
6✔
56
                        // app name validation???
57
                        projLibrary = await this.getProjectLibrary(framework);
6✔
58

59
                        const projTemplate = await this.getProjectTemplate(projLibrary);
6✔
60
                        // project options:
61
                        theme = await this.getTheme(projLibrary);
6✔
62

63
                        if (projTemplate.hasExtraConfiguration) {
6✔
64
                                await this.customizeTemplateTask(projTemplate);
2✔
65
                        }
66

67
                        if (typeof projTemplate.scaffold === "function") {
6✔
68
                                Util.log("  Generating project structure.");
1✔
69
                                const success = await projTemplate.scaffold({
1✔
70
                                        name: projectName,
71
                                        theme,
72
                                        skipInstall: false,
73
                                        skipGit: this.config.skipGit
74
                                });
75
                                if (!success) {
1!
NEW
76
                                        return;
×
77
                                }
78
                                // the scaffold service never touches git, so initialize git here when requested
79
                                if (!this.config.skipGit) {
1✔
80
                                        Util.gitInit(process.cwd(), projectName);
1✔
81
                                }
82
                                // move cwd to project folder
83
                                process.chdir(projectName);
1✔
84
                                await this.configureAI(framework.id);
1✔
85
                        } else {
86
                                Util.log("  Generating project structure.");
5✔
87
                                const config = projTemplate.generateConfig(projectName, theme);
5✔
88
                                for (const templatePath of projTemplate.templatePaths) {
5✔
89
                                        await Util.processTemplates(templatePath, path.join(process.cwd(), projectName),
9✔
90
                                        config, projTemplate.delimiters, false);
91
                                }
92

93
                                Util.log(Util.greenCheck() + " Project structure generated.");
5✔
94
                                if (!this.config.skipGit) {
5✔
95
                                        Util.gitInit(process.cwd(), projectName);
4✔
96
                                }
97
                                // move cwd to project folder
98
                                process.chdir(projectName);
5✔
99
                                await this.configureAI(framework.id);
5✔
100
                        }
101
                }
102
                await this.chooseActionLoop(projLibrary);
7✔
103
                //TODO: restore cwd?
104
        }
105

106
        /**
107
         * Starts a loop of 'Choose an action' questions
108
         * @param projectLibrary The framework to use
109
         * @param theme Theme to use
110
         */
111
        public async chooseActionLoop(projectLibrary: ProjectLibrary) {
112
                const taskContext: PromptTaskContext = { projectLibrary };
6✔
113
                const runner = new TaskRunner(taskContext);
6✔
114

115
                runner.addTask(this.chooseActionTask);
6✔
116

117
                while (!runner.done) {
6✔
118
                        await runner.run();
14✔
119
                }
120
        }
121

122
        /** Install packages and run project */
123
        protected abstract completeAndRun(port?: number);
124

125
        /** Upgrade packages to use private Infragistics feed */
126
        protected abstract upgradePackages();
127

128
        /** Configure Ignite UI AI tooling (MCP servers and AI coding skills) for the project */
129
        protected abstract configureAI(frameworkId: string): Promise<void>;
130

131
        /**
132
         * Get user name and set template's extra configurations if any
133
         * @param projectLibrary to add component to
134
         * @param component to get template for
135
         */
136
        protected abstract templateSelectedTask(type?: "component" | "view"): Task<PromptTaskContext>;
137

138
        /**
139
         * Gets the user input according to provided `options`.Returns directly if single choice is provided.
140
         * @param options to use for the user input
141
         * @param withBackChoice Add a "Back" option to choices list
142
         */
143
        protected async getUserInput(
144
                options: Exclude<UserInputOptions, { type: "checkbox" }>,
63✔
145
                withBackChoice: boolean = false,
48✔
146
        ): Promise<string> {
147

148
                if ("choices" in options) {
63✔
149
                        if (options.choices.length < 2) {
50✔
150
                                // single choice to return:
151
                                let choice = options.choices[0];
13✔
152
                                choice = choice.value || choice;
13✔
153
                                this.logAutoSelected(options, choice);
13✔
154
                                return choice;
13✔
155
                        }
156
                        if (withBackChoice) {
37✔
157
                                options.choices.push(WIZARD_BACK_OPTION);
14✔
158
                        }
159
                        options.choices = this.addSeparators(options.choices);
37✔
160
                }
161

162
                let result = "";
50✔
163
                if (options.type === "select") {
50✔
164
                        result = await InquirerWrapper.select(options);
37✔
165
                } else {
166
                        result = await InquirerWrapper.input(options);
13✔
167
                }
168

169
                // post to GA everything but 'Back' user choice
170
                if (!withBackChoice || result !== WIZARD_BACK_OPTION) {
50✔
171
                        GoogleAnalytics.post({
45✔
172
                                t: "event",
173
                                ec: "$ig wizard",
174
                                el: options.message,
175
                                ea: `${options.name}: ${result}`
176
                        });
177
                } else {
178
                        GoogleAnalytics.post({
5✔
179
                                t: "event",
180
                                ec: "$ig wizard",
181
                                el: result,
182
                                ea: `Back from ${options.name}`
183
                        });
184
                }
185

186
                return result;
50✔
187
        }
188

189
        /**
190
         * Check if provided @param name is valid for project name
191
         * @param name the name to check
192
         * @param checkFolder check if folder with this name already exists
193
         */
194
        protected nameIsValid(name: string, checkFolder = true): boolean {
2✔
195
                if (!Util.isAlphanumericExt(name)) {
4✔
196
                        Util.log(""); /* new line */
1✔
197
                        Util.error(`Name '${name}' is not valid. `
1✔
198
                                + "Name should start with a letter and can also contain numbers, dashes and spaces.",
199
                                "red");
200
                        return false;
1✔
201
                }
202

203
                if (checkFolder && Util.directoryExists(name)) {
3!
204
                        Util.log(""); /* new line */
×
205
                        Util.error(`Folder "${name}" already exists!`, "red");
×
206
                        return false;
×
207
                }
208

209
                return true;
3✔
210
        }
211

212
        /** Returns the framework names, potentially filtered by config */
213
        protected getFrameworkNames(): string[] {
214
                let frameworksNames: string[] = [];
6✔
215
                if (
6✔
216
                        this.config.stepByStep &&
8✔
217
                        this.config.stepByStep.frameworks &&
218
                        this.config.stepByStep.frameworks.length
219
                ) {
220
                        this.config.stepByStep.frameworks.forEach(x => {
1✔
221
                                const framework = this.templateManager.getFrameworkById(x);
1✔
222
                                if (framework) {
1✔
223
                                        frameworksNames.push(framework.name);
1✔
224
                                }
225
                        });
226
                }
227
                if (!frameworksNames.length) {
6✔
228
                        // no config or wrong projTypes array:
229
                        frameworksNames = this.templateManager.getFrameworkNames();
5✔
230
                }
231
                return frameworksNames;
6✔
232
        }
233

234
        /**
235
         * Gets the project library from the user input, or default if provided @param framework has single project library
236
         * @param framework to get project library for
237
         */
238
        protected async getProjectLibrary(framework: Framework): Promise<ProjectLibrary> {
239
                const projectLibraries = this.getProjectLibNames(framework);
7✔
240

241
                const projectRes = await this.getUserInput({
7✔
242
                        type: "select",
243
                        name: "projectType",
244
                        message: "Choose the type of project:",
245
                        choices: projectLibraries
246
                });
247
                return this.templateManager.getProjectLibraryByName(framework, projectRes);
7✔
248
        }
249

250
        /**
251
         * Gets project template from user input, or default if provided `projectLibrary` has a single template
252
         * @param projectLibrary to get theme for
253
         */
254
        protected async getProjectTemplate(projectLibrary: ProjectLibrary): Promise<ProjectTemplate> {
255
                const visibleProjects = projectLibrary.projects.filter(p => !p.isHidden);
21✔
256
                const componentNameRes = await this.getUserInput({
7✔
257
                        type: "select",
258
                        name: "projTemplate",
259
                        message: "Choose project template:",
260
                        choices: Util.formatChoices(visibleProjects)
261
                });
262
                const selected = visibleProjects.find(x => x.name === componentNameRes);
9✔
263
                if (!selected) {
7!
264
                        throw new Error(`Project template '${componentNameRes}' not found.`);
×
265
                }
266

267
                // If the selected template has an auth variant (id: "<template-id>-auth"), offer it
268
                const authVariant = projectLibrary.getProject(`${selected.id}-auth`);
7✔
269
                if (authVariant) {
7✔
270
                        const wantsAuth = await InquirerWrapper.confirm({
2✔
271
                                message: "Would you like to add authentication (login, register, social login)?",
272
                                default: false
273
                        });
274
                        GoogleAnalytics.post({
2✔
275
                                t: "event",
276
                                ec: "$ig wizard",
277
                                el: "Include authentication?",
278
                                ea: `projTemplate: ${selected.id}; auth: ${wantsAuth}`
279
                        });
280
                        if (wantsAuth) {
2!
281
                                return authVariant;
×
282
                        }
283
                }
284

285
                return selected;
7✔
286
        }
287

288
        /**
289
         * Gets the theme from the user input, or default if provided @param projectLibrary has a single theme
290
         * @param projectLibrary to get theme for
291
         */
292
        protected async getTheme(projectLibrary: ProjectLibrary): Promise<string> {
293
                const theme = await this.getUserInput({
6✔
294
                        type: "select",
295
                        name: "theme",
296
                        message: "Choose the theme for the project:",
297
                        choices: projectLibrary.themes,
298
                        default: projectLibrary.themes[0]
299
                });
300
                return theme;
6✔
301
        }
302

303
        /**
304
         * Prompt user for template name with appropriate default
305
         * @param template template to get name for
306
         * @param type type of the name question
307
         */
308
        protected async chooseTemplateName(template: Template, type: "component" | "view" = "component") {
3!
309
                const config = ProjectConfig.getConfig();
3✔
310
                const availableDefaultName = Util.getAvailableName(template.name, false,
3✔
311
                        config.project.framework, config.project.projectType);
312

313
                const templateName = await this.getUserInput({
3✔
314
                        type: "input",
315
                        name: `${type === "component" ? type : "customView"}Name`,
3✔
316
                        message: `Name your ${type}:`,
317
                        default: availableDefaultName,
318
                        validate: (input: string) => {
319
                                // TODO: GA post?
320
                                const name = Util.nameFromPath(input);
×
321
                                return this.nameIsValid(name, false);
×
322
                        }
323
                });
324

325
                return templateName;
3✔
326
        }
327

328
        /** Create prompts from template extra configuration and assign user answers to the template */
329
        protected async customizeTemplateTask(template: BaseTemplate) {
330
                const extraPrompt = this.createQuestions(template.getExtraConfiguration());
5✔
331
                const extraConfigAnswers = [];
5✔
332
                for (const question of extraPrompt) {
5✔
333
                        switch (question.type) {
6✔
334
                                case "input":
335
                                        extraConfigAnswers.push(await InquirerWrapper.input(question));
2✔
336
                                        break;
2✔
337
                                case "select":
338
                                        extraConfigAnswers.push(await InquirerWrapper.select(question));
3✔
339
                                        break;
3✔
340
                                case "checkbox":
341
                                        extraConfigAnswers.push(await InquirerWrapper.checkbox(question));
1✔
342
                                        break;
1✔
343
                        }
344
                }
345

346
                const extraConfig = this.parseAnswers(extraConfigAnswers);
5✔
347
                GoogleAnalytics.post({
5✔
348
                        t: "event",
349
                        ec: "$ig wizard",
350
                        el: "Extra configuration:",
351
                        ea: `extra configuration: ${JSON.stringify(extraConfig)}`
352
                });
353

354
                template.setExtraConfiguration(extraConfig);
5✔
355
        }
356

357
        /**
358
         * Returns a new array with inquirer.Separator() added between items
359
         * @param array The original array to add separator to
360
         */
361
        private addSeparators(array: any[]): any[] {
362
                const newArray = [];
37✔
363
                for (let i = 0; i < array.length; i++) {
37✔
364
                        newArray.push(array[i]);
109✔
365
                        if (i + 1 < array.length) {
109✔
366
                                newArray.push(new Separator());
72✔
367
                        }
368
                }
369
                if (array.length > 4) {
37✔
370
                        // additional separator after last item for lists that wrap around
371
                        newArray.push(new Separator(new Array(15).join("=")));
2✔
372
                }
373
                return newArray;
37✔
374
        }
375

376
        /**
377
         * Generate questions from extra configuration array
378
         * @param extraConfig
379
         */
380
        private createQuestions(extraConfig: ControlExtraConfiguration[]): UserInputOptions[] {
381
                const result: UserInputOptions[] = [];
5✔
382
                for (const element of extraConfig) {
5✔
383
                        const base = {
6✔
384
                                default: element.default,
385
                                message: element.message,
386
                                name: element.key,
387
                        };
388
                        switch (element.type) {
6✔
389
                                case ControlExtraConfigType.Choice:
390
                                        result.push({ ...base, type: "select", choices: element.choices ?? [] });
3!
391
                                        break;
3✔
392
                                case ControlExtraConfigType.MultiChoice:
393
                                        result.push({ ...base, type: "checkbox", choices: element.choices ?? [] });
1!
394
                                        break;
1✔
395
                                case ControlExtraConfigType.Value:
396
                                default:
397
                                        result.push({ ...base, type: "input" });
2✔
398
                                        break;
2✔
399
                        }
400
                }
401
                return result;
5✔
402
        }
403
        /**
404
         * Conversion placeholder
405
         * @param answers
406
         */
407
        private parseAnswers(answers: {}): {} {
408
                return answers;
5✔
409
        }
410

411
        /**
412
         * Task to pick action and load consecutive tasks
413
         * @param projectLibrary to add component to
414
         */
415
        private chooseActionTask: Task<PromptTaskContext> = async (runner, context) => {
20✔
416
                Util.log(""); /* new line */
12✔
417
                const action: string = await this.getUserInput({
12✔
418
                        type: "select",
419
                        name: "action",
420
                        message: "Choose an action:",
421
                        choices: this.generateActionChoices(context.projectLibrary),
422
                        default: "Complete & Run"
423
                });
424

425
                runner.clearPending();
12✔
426
                switch (action) {
12✔
427
                /* istanbul ignore next */
428
                case "Add all":
429
                        // internal testing only
430
                        runner.addTask(async (_runner, _context) => {
431
                                const templateTask = this.templateSelectedTask();
432
                                for (const template of _context.projectLibrary.templates) {
433
                                        _context.template = template;
434
                                        await templateTask(_runner, _context);
435
                                }
436
                                return true;
437
                        });
438
                        runner.addTask(run => Promise.resolve(run.resetTasks()));
439
                        break;
440
                case "Add component":
441
                        runner.addTask(this.getComponentGroupTask);
4✔
442
                        runner.addTask(this.getComponentTask);
4✔
443
                        runner.addTask(this.getTemplateTask);
4✔
444
                        runner.addTask(this.templateSelectedTask());
4✔
445
                        runner.addTask(run => Promise.resolve(run.resetTasks()));
4✔
446
                        break;
4✔
447
                case "Add scenario":
448
                        runner.addTask(this.getCustomViewTask);
2✔
449
                        runner.addTask(this.templateSelectedTask("view"));
2✔
450
                        runner.addTask(run => Promise.resolve(run.resetTasks()));
2✔
451
                        break;
2✔
452
                case "Complete & Run":
453
                        const config = ProjectConfig.localConfig();
6✔
454

455
                        if (!config.project) {
6✔
456
                                // Blazor (scaffolded via dotnet) has no cli-config — print next-steps instead of
457
                                // routing through completeAndRun (npm + start.start, which requires a cli-config).
458
                                const projectName = path.basename(process.cwd());
1✔
459
                                if (Util.canPrompt() && await InquirerWrapper.confirm({
1!
460
                                        message: "Run the app now (dotnet run)?",
461
                                        default: false
462
                                })) {
NEW
463
                                        const result = Util.spawnSync("dotnet", ["run", "--project", projectName], { stdio: "inherit" });
×
NEW
464
                                        if (result.error || result.status !== 0) {
×
NEW
465
                                                Util.error("dotnet run failed (see dotnet output above).", "red");
×
466
                                        }
467
                                } else {
468
                                        Util.log("");
1✔
469
                                        Util.log("Next Steps:");
1✔
470
                                        Util.log(`  cd ${projectName}`);
1✔
471
                                        Util.log(`  dotnet run --project ${projectName}`);
1✔
472
                                }
473
                                break;
1✔
474
                        }
475

476
                        if (config.project.framework === "angular" &&
5✔
477
                                config.project.projectType === "igx-ts" &&
478
                                !config.packagesInstalled) {
479
                                // TODO: should we add check if there are paid components at all
480
                                Util.log("The project will be created using a Trial version of Ignite UI for Angular.");
1✔
481
                                Util.log("You can always run the upgrade-packages command once it's created.");
1✔
482
                                const shouldUpgrade = await this.getUserInput({
1✔
483
                                        type: "select",
484
                                        name: "shouldUpgrade",
485
                                        message: "Would you like to upgrade to the licensed feed now?",
486
                                        choices: [
487
                                                { value: "yes", name: "Yes (requires active subscription)", short: "Yes" },
488
                                                { value: "no", name: "Skip for now", short: "Skip" }
489
                                        ],
490
                                        default: "yes"
491
                                });
492

493
                                if (shouldUpgrade === "yes") {
1✔
494
                                        await this.upgradePackages();
1✔
495
                                }
496
                        }
497

498
                        const defaultPort = config.project.defaultPort;
5✔
499
                        const port = await this.getUserInput({
5✔
500
                                type: "input",
501
                                name: "port",
502
                                message: "Choose app host port:",
503
                                default: defaultPort,
504
                                validate: (input: string) => {
505
                                        if (!Number(input)) {
3✔
506
                                                Util.log(""); /* new line */
2✔
507
                                                Util.error(`port should be a number. Input valid port or use the suggested default port`, "red");
2✔
508
                                                return false;
2✔
509
                                        }
510
                                        return true;
1✔
511
                                }
512
                        });
513
                        config.project.defaultPort = parseInt(port, 10);
5✔
514
                        ProjectConfig.setConfig(config);
5✔
515

516
                        await this.completeAndRun(config.project.defaultPort);
5✔
517
                        break;
5✔
518
                }
519
                return true;
12✔
520
        }
521

522
        /**
523
         * Get component group from user input
524
         * @param projectLibrary to add component to
525
         */
526
        private getComponentGroupTask: Task<PromptTaskContext> = async (_runner, context) => {
20✔
527
                const groups = context.projectLibrary.getComponentGroupNames();
6✔
528
                const groupRes: string = await this.getUserInput({
6✔
529
                        type: "select",
530
                        name: "componentGroup",
531
                        message: "Choose a group:",
532
                        choices: Util.formatChoices(context.projectLibrary.getComponentGroups()),
533
                        default: groups.find(x => x.includes("Grids")) || groups[0]
12✔
534
                }, true);
535

536
                if (groupRes === WIZARD_BACK_OPTION) {
6✔
537
                        return WIZARD_BACK_OPTION;
2✔
538
                }
539
                context.group = groupRes;
4✔
540
                return true;
4✔
541
        }
542

543
        /**
544
         * Get component in the selected components group
545
         * @param projectLibrary to add component to
546
         * @param groupName to chose components from
547
         */
548
        private getComponentTask: Task<PromptTaskContext> = async (_runner, context) => {
20✔
549
                const componentNameRes = await this.getUserInput({
4✔
550
                        type: "select",
551
                        name: "component",
552
                        message: "Choose a component:",
553
                        choices: Util.formatChoices(context.projectLibrary.getComponentsByGroup(context.group))
554
                }, true);
555

556
                if (componentNameRes === WIZARD_BACK_OPTION) {
4✔
557
                        return WIZARD_BACK_OPTION;
2✔
558
                }
559

560
                context.component = context.projectLibrary.getComponentByName(componentNameRes);
2✔
561
                return true;
2✔
562
        }
563

564
        /**
565
         * Get template for selected component
566
         * @param projectLibrary to add component to
567
         * @param component to get template for
568
         */
569
        private getTemplateTask: Task<PromptTaskContext> = async (_runner, context) => {
20✔
570
                const templates: Template[] = context.component.templates;
2✔
571

572
                const templateRes = await this.getUserInput({
2✔
573
                        type: "select",
574
                        name: "template",
575
                        message: "Choose one:",
576
                        choices: Util.formatChoices(templates)
577
                }, true);
578

579
                if (templateRes === WIZARD_BACK_OPTION) {
2!
580
                        return WIZARD_BACK_OPTION;
×
581
                }
582

583
                const selectedTemplate = templates.find((value, i, obj) => {
2✔
584
                        return value.name === templateRes;
2✔
585
                });
586

587
                if (selectedTemplate) {
2✔
588
                        context.template = selectedTemplate;
2✔
589
                        return true;
2✔
590
                }
591

592
                return false;
×
593
        }
594

595
        /**
596
         * Get template for custom view from user input
597
         * @param projectLibrary to add component to
598
         * @param theme to use to style the project
599
         */
600
        private getCustomViewTask: Task<PromptTaskContext> = async (_runner, context) => {
20✔
601
                const customTemplates: Template[] = context.projectLibrary.getCustomTemplates();
2✔
602

603
                const customTemplateNameRes = await this.getUserInput({
2✔
604
                        type: "select",
605
                        name: "customTemplate",
606
                        message: "Choose custom view:",
607
                        choices: Util.formatChoices(customTemplates)
608
                }, true);
609

610
                if (customTemplateNameRes === WIZARD_BACK_OPTION) {
2✔
611
                        return WIZARD_BACK_OPTION;
1✔
612
                }
613
                const selectedTemplate = customTemplates.find((value, i, obj) => {
1✔
614
                        return customTemplateNameRes === value.name;
1✔
615
                });
616

617
                if (selectedTemplate) {
1✔
618
                        context.template = selectedTemplate;
1✔
619
                        return true;
1✔
620
                }
621
                return false;
×
622
        }
623

624
        private logAutoSelected(options: UserInputOptions, choice: any) {
625
                let text;
626
                switch (options.name) {
13!
627
                        case "framework":
628
                                text = `  Framework`;
2✔
629
                                break;
2✔
630
                        case "projectType":
631
                                text = `  Project type`;
5✔
632
                                break;
5✔
633
                        case "projTemplate":
634
                                text = `  Proj Template`;
4✔
635
                                break;
4✔
636
                        case "theme":
637
                                text = `  Theme`;
2✔
638
                                break;
2✔
639
                        default:
640
                                return;
×
641
                                //TODO: text = `  ${options.name}`;
642
                }
643
                GoogleAnalytics.post({
13✔
644
                        t: "event",
645
                        ec: "$ig wizard",
646
                        el: options.message,
647
                        ea: `${options.name}: ${choice}`
648
                });
649
                Util.log(`${text}: ${choice}`);
13✔
650
        }
651

652
        /** Returns the projectLibraries names, potentially filtered by config */
653
        private getProjectLibNames(framework: Framework): string[] {
654
                let projectLibraries: string[] = [];
7✔
655
                const frameworkConfig = this.config.stepByStep && this.config.stepByStep[framework.id as FrameworkId];
7✔
656

657
                if (frameworkConfig && frameworkConfig.projTypes && frameworkConfig.projTypes.length) {
7✔
658
                        frameworkConfig.projTypes.forEach(x => {
1✔
659
                                const projLib = framework.projectLibraries.find(p => p.projectType === x);
2✔
660
                                if (projLib) {
1✔
661
                                        projectLibraries.push(projLib.name);
1✔
662
                                }
663
                        });
664
                }
665

666
                if (!projectLibraries.length) {
7✔
667
                        // no config or wrong projTypes array:
668
                        projectLibraries = this.templateManager.getProjectLibraryNames(framework.id);
6✔
669
                }
670
                return projectLibraries;
7✔
671
        }
672

673
        /**
674
         * Generates a list of options for chooseActionLoop
675
         * @param projectLibrary to generate options for
676
         */
677
        private generateActionChoices(projectLibrary: ProjectLibrary): Array<{}> {
678
                const actionChoices: ChoiceItem[] = [
10✔
679
                        { name: "Complete & Run", description: "install packages and run in the default browser" }
680
                ];
681

682
                /* istanbul ignore next */
683
                if (App.testMode) {
684
                        // internal testing only
685
                        actionChoices.push({ name: "Add all", description: "add all components/views" });
686
                }
687

688
                if (projectLibrary.components.length > 0) {
10✔
689
                        actionChoices.push({ name: "Add component", description: "add a specific component view (e.g a grid)" });
10✔
690
                }
691

692
                if (projectLibrary.getCustomTemplateNames().length > 0) {
10✔
693
                        actionChoices.push({ name: "Add scenario", description: "add a predefined scenario view (e.g grid or dashboard)" });
9✔
694
                }
695
                return Util.formatChoices(actionChoices, 10);
10✔
696
        }
697
}
698

699
type InputOptions = {
700
        type: "input";
701
        name: string;
702
        message: string;
703
        default?: any;
704
        validate?: (input: string) => string | boolean;
705
}
706

707
type SelectOptions = Omit<InputOptions, "type"> & {
708
        type: "select";
709
        // TODO: Expand type:
710
        choices: any[];
711
}
712

713
type CheckboxOptions = Omit<SelectOptions, "type"> & {
714
        type: "checkbox";
715
        required?: boolean;
716
}
717

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

721
/** Context type for prompt tasks */
722
export interface PromptTaskContext {
723
        projectLibrary: ProjectLibrary;
724
        group?: string;
725
        component?: Component;
726
        template?: Template;
727
        name?: string;
728
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc