mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-18 03:23:57 +08:00
refactor(@angular-devkit/schematics-cli): use latest inquirer prompt package
The `inquirer` package has been rewritten with a new set of packages. The rewrite had a focus on reduced package size and improved performance. The main prompt package is now `@inquirer/prompts`. The API is very similar but did require some refactoring to adapt to Angular's usage.
This commit is contained in:
parent
d6aa216553
commit
d36c53bc82
@ -51,11 +51,10 @@ ts_library(
|
|||||||
"//packages/angular_devkit/schematics",
|
"//packages/angular_devkit/schematics",
|
||||||
"//packages/angular_devkit/schematics/tasks",
|
"//packages/angular_devkit/schematics/tasks",
|
||||||
"//packages/angular_devkit/schematics/tools",
|
"//packages/angular_devkit/schematics/tools",
|
||||||
"@npm//@types/inquirer",
|
"@npm//@inquirer/prompts",
|
||||||
"@npm//@types/node",
|
"@npm//@types/node",
|
||||||
"@npm//@types/yargs-parser",
|
"@npm//@types/yargs-parser",
|
||||||
"@npm//ansi-colors",
|
"@npm//ansi-colors",
|
||||||
"@npm//inquirer",
|
|
||||||
"@npm//symbol-observable",
|
"@npm//symbol-observable",
|
||||||
"@npm//yargs-parser",
|
"@npm//yargs-parser",
|
||||||
],
|
],
|
||||||
|
@ -9,12 +9,11 @@
|
|||||||
|
|
||||||
// symbol polyfill must go first
|
// symbol polyfill must go first
|
||||||
import 'symbol-observable';
|
import 'symbol-observable';
|
||||||
import type { logging, schema } from '@angular-devkit/core';
|
import type { JsonValue, logging, schema } from '@angular-devkit/core';
|
||||||
import { ProcessOutput, createConsoleLogger } from '@angular-devkit/core/node';
|
import { ProcessOutput, createConsoleLogger } from '@angular-devkit/core/node';
|
||||||
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
|
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
|
||||||
import { NodeWorkflow } from '@angular-devkit/schematics/tools';
|
import { NodeWorkflow } from '@angular-devkit/schematics/tools';
|
||||||
import ansiColors from 'ansi-colors';
|
import ansiColors from 'ansi-colors';
|
||||||
import type { Question, QuestionCollection } from 'inquirer';
|
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import yargsParser, { camelCase, decamelize } from 'yargs-parser';
|
import yargsParser, { camelCase, decamelize } from 'yargs-parser';
|
||||||
@ -69,71 +68,94 @@ function _listSchematics(workflow: NodeWorkflow, collectionName: string, logger:
|
|||||||
|
|
||||||
function _createPromptProvider(): schema.PromptProvider {
|
function _createPromptProvider(): schema.PromptProvider {
|
||||||
return async (definitions) => {
|
return async (definitions) => {
|
||||||
const questions: QuestionCollection = definitions.map((definition) => {
|
let prompts: typeof import('@inquirer/prompts') | undefined;
|
||||||
const question: Question = {
|
const answers: Record<string, JsonValue> = {};
|
||||||
name: definition.id,
|
|
||||||
message: definition.message,
|
|
||||||
default: definition.default,
|
|
||||||
};
|
|
||||||
|
|
||||||
const validator = definition.validator;
|
for (const definition of definitions) {
|
||||||
if (validator) {
|
// Only load prompt package if needed
|
||||||
question.validate = (input) => validator(input);
|
prompts ??= await import('@inquirer/prompts');
|
||||||
|
|
||||||
// Filter allows transformation of the value prior to validation
|
|
||||||
question.filter = async (input) => {
|
|
||||||
for (const type of definition.propertyTypes) {
|
|
||||||
let value;
|
|
||||||
switch (type) {
|
|
||||||
case 'string':
|
|
||||||
value = String(input);
|
|
||||||
break;
|
|
||||||
case 'integer':
|
|
||||||
case 'number':
|
|
||||||
value = Number(input);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
value = input;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Can be a string if validation fails
|
|
||||||
const isValid = (await validator(value)) === true;
|
|
||||||
if (isValid) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (definition.type) {
|
switch (definition.type) {
|
||||||
case 'confirmation':
|
case 'confirmation':
|
||||||
return { ...question, type: 'confirm' };
|
answers[definition.id] = await prompts.confirm({
|
||||||
|
message: definition.message,
|
||||||
|
default: definition.default as boolean | undefined,
|
||||||
|
});
|
||||||
|
break;
|
||||||
case 'list':
|
case 'list':
|
||||||
return {
|
if (!definition.items?.length) {
|
||||||
...question,
|
continue;
|
||||||
type: definition.multiselect ? 'checkbox' : 'list',
|
}
|
||||||
choices:
|
|
||||||
definition.items &&
|
const choices = definition.items?.map((item) => {
|
||||||
definition.items.map((item) => {
|
return typeof item == 'string'
|
||||||
if (typeof item == 'string') {
|
? {
|
||||||
return item;
|
name: item,
|
||||||
} else {
|
value: item,
|
||||||
return {
|
}
|
||||||
|
: {
|
||||||
name: item.label,
|
name: item.label,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return { ...question, type: definition.type };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const { default: inquirer } = await loadEsmModule<typeof import('inquirer')>('inquirer');
|
|
||||||
|
|
||||||
return inquirer.prompt(questions);
|
answers[definition.id] = await (
|
||||||
|
definition.multiselect ? prompts.checkbox : prompts.select
|
||||||
|
)({
|
||||||
|
message: definition.message,
|
||||||
|
default: definition.default,
|
||||||
|
choices,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'input':
|
||||||
|
let finalValue: JsonValue | undefined;
|
||||||
|
answers[definition.id] = await prompts.input({
|
||||||
|
message: definition.message,
|
||||||
|
default: definition.default as string | undefined,
|
||||||
|
async validate(value) {
|
||||||
|
if (definition.validator === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastValidation: ReturnType<typeof definition.validator> = false;
|
||||||
|
for (const type of definition.propertyTypes) {
|
||||||
|
let potential;
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
potential = String(value);
|
||||||
|
break;
|
||||||
|
case 'integer':
|
||||||
|
case 'number':
|
||||||
|
potential = Number(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
potential = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastValidation = await definition.validator(potential);
|
||||||
|
|
||||||
|
// Can be a string if validation fails
|
||||||
|
if (lastValidation === true) {
|
||||||
|
finalValue = potential;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastValidation;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use validated value if present.
|
||||||
|
// This ensures the correct type is inserted into the final schema options.
|
||||||
|
if (finalValue !== undefined) {
|
||||||
|
answers[definition.id] = finalValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answers;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,29 +509,3 @@ if (require.main === module) {
|
|||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazily compiled dynamic import loader function.
|
|
||||||
*/
|
|
||||||
let load: (<T>(modulePath: string | URL) => Promise<T>) | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This uses a dynamic import to load a module which may be ESM.
|
|
||||||
* CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
|
|
||||||
* will currently, unconditionally downlevel dynamic import into a require call.
|
|
||||||
* require calls cannot load ESM code and will result in a runtime error. To workaround
|
|
||||||
* this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
|
|
||||||
* Once TypeScript provides support for keeping the dynamic import this workaround can
|
|
||||||
* be dropped.
|
|
||||||
*
|
|
||||||
* @param modulePath The path of the module to load.
|
|
||||||
* @returns A Promise that resolves to the dynamically imported module.
|
|
||||||
*/
|
|
||||||
export function loadEsmModule<T>(modulePath: string | URL): Promise<T> {
|
|
||||||
load ??= new Function('modulePath', `return import(modulePath);`) as Exclude<
|
|
||||||
typeof load,
|
|
||||||
undefined
|
|
||||||
>;
|
|
||||||
|
|
||||||
return load(modulePath);
|
|
||||||
}
|
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/core": "0.0.0-PLACEHOLDER",
|
"@angular-devkit/core": "0.0.0-PLACEHOLDER",
|
||||||
"@angular-devkit/schematics": "0.0.0-PLACEHOLDER",
|
"@angular-devkit/schematics": "0.0.0-PLACEHOLDER",
|
||||||
|
"@inquirer/prompts": "5.0.5",
|
||||||
"ansi-colors": "4.1.3",
|
"ansi-colors": "4.1.3",
|
||||||
"inquirer": "9.2.23",
|
|
||||||
"symbol-observable": "4.0.0",
|
"symbol-observable": "4.0.0",
|
||||||
"yargs-parser": "21.1.1"
|
"yargs-parser": "21.1.1"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user