in packages/angular/cli/models/schematic-command.ts [220:373]
protected async createWorkflow(options: BaseSchematicSchema): Promise<workflow.BaseWorkflow> {
if (this._workflow) {
return this._workflow;
}
const { force, dryRun } = options;
const root = this.context.root;
const workflow = new NodeWorkflow(root, {
force,
dryRun,
packageManager: await getPackageManager(root),
packageRegistry: options.packageRegistry,
// A schema registry is required to allow customizing addUndefinedDefaults
registry: new schema.CoreSchemaRegistry(formats.standardFormats),
resolvePaths: this.workspace
? // Workspace
this.collectionName === this.defaultCollectionName
? // Favor __dirname for @schematics/angular to use the build-in version
[__dirname, process.cwd(), root]
: [process.cwd(), root, __dirname]
: // Global
[__dirname, process.cwd()],
schemaValidation: true,
optionTransforms: [
// Add configuration file defaults
async (schematic, current) => {
const projectName =
typeof (current as Record<string, unknown>).project === 'string'
? ((current as Record<string, unknown>).project as string)
: getProjectName();
return {
...(await getSchematicDefaults(schematic.collection.name, schematic.name, projectName)),
...current,
};
},
],
engineHostCreator: (options) => new SchematicEngineHost(options.resolvePaths),
});
const getProjectName = () => {
if (this.workspace) {
const projectNames = getProjectsByPath(
this.workspace,
process.cwd(),
this.workspace.basePath,
);
if (projectNames.length === 1) {
return projectNames[0];
} else {
if (projectNames.length > 1) {
this.logger.warn(tags.oneLine`
Two or more projects are using identical roots.
Unable to determine project using current working directory.
Using default workspace project instead.
`);
}
const defaultProjectName = this.workspace.extensions['defaultProject'];
if (typeof defaultProjectName === 'string' && defaultProjectName) {
return defaultProjectName;
}
}
}
return undefined;
};
workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults);
workflow.registry.addSmartDefaultProvider('projectName', getProjectName);
workflow.registry.useXDeprecatedProvider((msg) => this.logger.warn(msg));
let shouldReportAnalytics = true;
workflow.engineHost.registerOptionsTransform(async (_, options) => {
if (shouldReportAnalytics) {
shouldReportAnalytics = false;
await this.reportAnalytics([this.description.name], options as Arguments);
}
return options;
});
if (options.interactive !== false && isTTY()) {
workflow.registry.usePromptProvider((definitions: Array<schema.PromptDefinition>) => {
const questions: inquirer.QuestionCollection = definitions
.filter((definition) => !options.defaults || definition.default === undefined)
.map((definition) => {
const question: inquirer.Question = {
name: definition.id,
message: definition.message,
default: definition.default,
};
const validator = definition.validator;
if (validator) {
question.validate = (input) => validator(input);
// 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) {
case 'confirmation':
question.type = 'confirm';
break;
case 'list':
question.type = definition.multiselect ? 'checkbox' : 'list';
(question as inquirer.CheckboxQuestion).choices = definition.items?.map((item) => {
return typeof item == 'string'
? item
: {
name: item.label,
value: item.value,
};
});
break;
default:
question.type = definition.type;
break;
}
return question;
});
return inquirer.prompt(questions);
});
}
return (this._workflow = workflow);
}