|
9 | 9 |
|
10 | 10 | // symbol polyfill must go first
|
11 | 11 | import 'symbol-observable';
|
12 |
| -import type { logging, schema } from '@angular-devkit/core'; |
| 12 | +import type { JsonValue, logging, schema } from '@angular-devkit/core'; |
13 | 13 | import { ProcessOutput, createConsoleLogger } from '@angular-devkit/core/node';
|
14 | 14 | import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
|
15 | 15 | import { NodeWorkflow } from '@angular-devkit/schematics/tools';
|
16 | 16 | import ansiColors from 'ansi-colors';
|
17 |
| -import type { Question, QuestionCollection } from 'inquirer'; |
18 | 17 | import { existsSync } from 'node:fs';
|
19 | 18 | import * as path from 'node:path';
|
20 | 19 | import yargsParser, { camelCase, decamelize } from 'yargs-parser';
|
@@ -69,71 +68,94 @@ function _listSchematics(workflow: NodeWorkflow, collectionName: string, logger:
|
69 | 68 |
|
70 | 69 | function _createPromptProvider(): schema.PromptProvider {
|
71 | 70 | return async (definitions) => {
|
72 |
| - const questions: QuestionCollection = definitions.map((definition) => { |
73 |
| - const question: Question = { |
74 |
| - name: definition.id, |
75 |
| - message: definition.message, |
76 |
| - default: definition.default, |
77 |
| - }; |
78 |
| - |
79 |
| - const validator = definition.validator; |
80 |
| - if (validator) { |
81 |
| - question.validate = (input) => validator(input); |
82 |
| - |
83 |
| - // Filter allows transformation of the value prior to validation |
84 |
| - question.filter = async (input) => { |
85 |
| - for (const type of definition.propertyTypes) { |
86 |
| - let value; |
87 |
| - switch (type) { |
88 |
| - case 'string': |
89 |
| - value = String(input); |
90 |
| - break; |
91 |
| - case 'integer': |
92 |
| - case 'number': |
93 |
| - value = Number(input); |
94 |
| - break; |
95 |
| - default: |
96 |
| - value = input; |
97 |
| - break; |
98 |
| - } |
99 |
| - // Can be a string if validation fails |
100 |
| - const isValid = (await validator(value)) === true; |
101 |
| - if (isValid) { |
102 |
| - return value; |
103 |
| - } |
104 |
| - } |
| 71 | + let prompts: typeof import('@inquirer/prompts') | undefined; |
| 72 | + const answers: Record<string, JsonValue> = {}; |
105 | 73 |
|
106 |
| - return input; |
107 |
| - }; |
108 |
| - } |
| 74 | + for (const definition of definitions) { |
| 75 | + // Only load prompt package if needed |
| 76 | + prompts ??= await import('@inquirer/prompts'); |
109 | 77 |
|
110 | 78 | switch (definition.type) {
|
111 | 79 | case 'confirmation':
|
112 |
| - return { ...question, type: 'confirm' }; |
| 80 | + answers[definition.id] = await prompts.confirm({ |
| 81 | + message: definition.message, |
| 82 | + default: definition.default as boolean | undefined, |
| 83 | + }); |
| 84 | + break; |
113 | 85 | case 'list':
|
114 |
| - return { |
115 |
| - ...question, |
116 |
| - type: definition.multiselect ? 'checkbox' : 'list', |
117 |
| - choices: |
118 |
| - definition.items && |
119 |
| - definition.items.map((item) => { |
120 |
| - if (typeof item == 'string') { |
121 |
| - return item; |
122 |
| - } else { |
123 |
| - return { |
124 |
| - name: item.label, |
125 |
| - value: item.value, |
126 |
| - }; |
| 86 | + if (!definition.items?.length) { |
| 87 | + continue; |
| 88 | + } |
| 89 | + |
| 90 | + const choices = definition.items?.map((item) => { |
| 91 | + return typeof item == 'string' |
| 92 | + ? { |
| 93 | + name: item, |
| 94 | + value: item, |
| 95 | + } |
| 96 | + : { |
| 97 | + name: item.label, |
| 98 | + value: item.value, |
| 99 | + }; |
| 100 | + }); |
| 101 | + |
| 102 | + answers[definition.id] = await ( |
| 103 | + definition.multiselect ? prompts.checkbox : prompts.select |
| 104 | + )({ |
| 105 | + message: definition.message, |
| 106 | + default: definition.default, |
| 107 | + choices, |
| 108 | + }); |
| 109 | + break; |
| 110 | + case 'input': |
| 111 | + let finalValue: JsonValue | undefined; |
| 112 | + answers[definition.id] = await prompts.input({ |
| 113 | + message: definition.message, |
| 114 | + default: definition.default as string | undefined, |
| 115 | + async validate(value) { |
| 116 | + if (definition.validator === undefined) { |
| 117 | + return true; |
| 118 | + } |
| 119 | + |
| 120 | + let lastValidation: ReturnType<typeof definition.validator> = false; |
| 121 | + for (const type of definition.propertyTypes) { |
| 122 | + let potential; |
| 123 | + switch (type) { |
| 124 | + case 'string': |
| 125 | + potential = String(value); |
| 126 | + break; |
| 127 | + case 'integer': |
| 128 | + case 'number': |
| 129 | + potential = Number(value); |
| 130 | + break; |
| 131 | + default: |
| 132 | + potential = value; |
| 133 | + break; |
127 | 134 | }
|
128 |
| - }), |
129 |
| - }; |
130 |
| - default: |
131 |
| - return { ...question, type: definition.type }; |
| 135 | + lastValidation = await definition.validator(potential); |
| 136 | + |
| 137 | + // Can be a string if validation fails |
| 138 | + if (lastValidation === true) { |
| 139 | + finalValue = potential; |
| 140 | + |
| 141 | + return true; |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + return lastValidation; |
| 146 | + }, |
| 147 | + }); |
| 148 | + |
| 149 | + // Use validated value if present. |
| 150 | + // This ensures the correct type is inserted into the final schema options. |
| 151 | + if (finalValue !== undefined) { |
| 152 | + answers[definition.id] = finalValue; |
| 153 | + } |
| 154 | + break; |
132 | 155 | }
|
133 |
| - }); |
134 |
| - const { default: inquirer } = await loadEsmModule<typeof import('inquirer')>('inquirer'); |
| 156 | + } |
135 | 157 |
|
136 |
| - return inquirer.prompt(questions); |
| 158 | + return answers; |
137 | 159 | };
|
138 | 160 | }
|
139 | 161 |
|
@@ -487,29 +509,3 @@ if (require.main === module) {
|
487 | 509 | throw e;
|
488 | 510 | });
|
489 | 511 | }
|
490 |
| - |
491 |
| -/** |
492 |
| - * Lazily compiled dynamic import loader function. |
493 |
| - */ |
494 |
| -let load: (<T>(modulePath: string | URL) => Promise<T>) | undefined; |
495 |
| - |
496 |
| -/** |
497 |
| - * This uses a dynamic import to load a module which may be ESM. |
498 |
| - * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript |
499 |
| - * will currently, unconditionally downlevel dynamic import into a require call. |
500 |
| - * require calls cannot load ESM code and will result in a runtime error. To workaround |
501 |
| - * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. |
502 |
| - * Once TypeScript provides support for keeping the dynamic import this workaround can |
503 |
| - * be dropped. |
504 |
| - * |
505 |
| - * @param modulePath The path of the module to load. |
506 |
| - * @returns A Promise that resolves to the dynamically imported module. |
507 |
| - */ |
508 |
| -export function loadEsmModule<T>(modulePath: string | URL): Promise<T> { |
509 |
| - load ??= new Function('modulePath', `return import(modulePath);`) as Exclude< |
510 |
| - typeof load, |
511 |
| - undefined |
512 |
| - >; |
513 |
| - |
514 |
| - return load(modulePath); |
515 |
| -} |
0 commit comments