in packages/angular/cli/models/schematic-command.ts [405:548]
protected async runSchematic(options: RunSchematicOptions) {
const { schematicOptions, debug, dryRun } = options;
let { collectionName, schematicName } = options;
let nothingDone = true;
let loggingQueue: string[] = [];
let error = false;
const workflow = this._workflow;
const workingDir = normalize(systemPath.relative(this.context.root, process.cwd()));
// Get the option object from the schematic schema.
const schematic = this.getSchematic(
this.getCollection(collectionName),
schematicName,
this.allowPrivateSchematics,
);
// Update the schematic and collection name in case they're not the same as the ones we
// received in our options, e.g. after alias resolution or extension.
collectionName = schematic.collection.description.name;
schematicName = schematic.description.name;
// Set the options of format "path".
let o: Option[] | null = null;
let args: Arguments;
if (!schematic.description.schemaJson) {
args = await this.parseFreeFormArguments(schematicOptions || []);
} else {
o = await parseJsonSchemaToOptions(workflow.registry, schematic.description.schemaJson);
args = await this.parseArguments(schematicOptions || [], o);
}
const allowAdditionalProperties =
typeof schematic.description.schemaJson === 'object' &&
schematic.description.schemaJson.additionalProperties;
if (args['--'] && !allowAdditionalProperties) {
args['--'].forEach((additional) => {
this.logger.fatal(`Unknown option: '${additional.split(/=/)[0]}'`);
});
return 1;
}
const pathOptions = o ? this.setPathOptions(o, workingDir) : {};
const input = {
...pathOptions,
...args,
...options.additionalOptions,
};
workflow.reporter.subscribe((event: DryRunEvent) => {
nothingDone = false;
// Strip leading slash to prevent confusion.
const eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path;
switch (event.kind) {
case 'error':
error = true;
const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist.';
this.logger.warn(`ERROR! ${eventPath} ${desc}.`);
break;
case 'update':
loggingQueue.push(tags.oneLine`
${colors.cyan('UPDATE')} ${eventPath} (${event.content.length} bytes)
`);
break;
case 'create':
loggingQueue.push(tags.oneLine`
${colors.green('CREATE')} ${eventPath} (${event.content.length} bytes)
`);
break;
case 'delete':
loggingQueue.push(`${colors.yellow('DELETE')} ${eventPath}`);
break;
case 'rename':
const eventToPath = event.to.startsWith('/') ? event.to.substr(1) : event.to;
loggingQueue.push(`${colors.blue('RENAME')} ${eventPath} => ${eventToPath}`);
break;
}
});
workflow.lifeCycle.subscribe((event) => {
if (event.kind == 'end' || event.kind == 'post-tasks-start') {
if (!error) {
// Output the logging queue, no error happened.
loggingQueue.forEach((log) => this.logger.info(log));
}
loggingQueue = [];
error = false;
}
});
// Temporary compatibility check for NPM 7
if (collectionName === '@schematics/angular' && schematicName === 'ng-new') {
if (
!input.skipInstall &&
(input.packageManager === undefined || input.packageManager === 'npm')
) {
await ensureCompatibleNpm(this.context.root);
}
}
return new Promise<number | void>((resolve) => {
workflow
.execute({
collection: collectionName,
schematic: schematicName,
options: input,
debug: debug,
logger: this.logger,
allowPrivate: this.allowPrivateSchematics,
})
.subscribe({
error: (err: Error) => {
// In case the workflow was not successful, show an appropriate error message.
if (err instanceof UnsuccessfulWorkflowExecution) {
// "See above" because we already printed the error.
this.logger.fatal('The Schematic workflow failed. See above.');
} else if (debug) {
this.logger.fatal(`An error occurred:\n${err.message}\n${err.stack}`);
} else {
this.logger.fatal(err.message);
}
resolve(1);
},
complete: () => {
const showNothingDone = !(options.showNothingDone === false);
if (nothingDone && showNothingDone) {
this.logger.info('Nothing to be done.');
}
if (dryRun) {
this.logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
}
resolve();
},
});
});
}