| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- /**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
- const core_1 = require("@angular-devkit/core");
- const node_1 = require("@angular-devkit/core/node");
- const schematics_1 = require("@angular-devkit/schematics");
- const tools_1 = require("@angular-devkit/schematics/tools");
- const inquirer = require("inquirer");
- const systemPath = require("path");
- const color_1 = require("../utilities/color");
- const config_1 = require("../utilities/config");
- const json_schema_1 = require("../utilities/json-schema");
- const package_manager_1 = require("../utilities/package-manager");
- const tty_1 = require("../utilities/tty");
- const analytics_1 = require("./analytics");
- const command_1 = require("./command");
- const parser_1 = require("./parser");
- class UnknownCollectionError extends Error {
- constructor(collectionName) {
- super(`Invalid collection (${collectionName}).`);
- }
- }
- exports.UnknownCollectionError = UnknownCollectionError;
- class SchematicCommand extends command_1.Command {
- constructor(context, description, logger) {
- super(context, description, logger);
- this.allowPrivateSchematics = false;
- this.allowAdditionalArgs = false;
- this._host = new node_1.NodeJsSyncHost();
- this.defaultCollectionName = '@schematics/angular';
- this.collectionName = this.defaultCollectionName;
- }
- async initialize(options) {
- await this._loadWorkspace();
- this.createWorkflow(options);
- if (this.schematicName) {
- // Set the options.
- const collection = this.getCollection(this.collectionName);
- const schematic = this.getSchematic(collection, this.schematicName, true);
- const options = await json_schema_1.parseJsonSchemaToOptions(this._workflow.registry, schematic.description.schemaJson || {});
- this.description.options.push(...options.filter(x => !x.hidden));
- // Remove any user analytics from schematics that are NOT part of our safelist.
- for (const o of this.description.options) {
- if (o.userAnalytics) {
- if (!analytics_1.isPackageNameSafeForAnalytics(this.collectionName)) {
- o.userAnalytics = undefined;
- }
- }
- }
- }
- }
- async printHelp(options) {
- await super.printHelp(options);
- this.logger.info('');
- const subCommandOption = this.description.options.filter(x => x.subcommands)[0];
- if (!subCommandOption || !subCommandOption.subcommands) {
- return 0;
- }
- const schematicNames = Object.keys(subCommandOption.subcommands);
- if (schematicNames.length > 1) {
- this.logger.info('Available Schematics:');
- const namesPerCollection = {};
- schematicNames.forEach(name => {
- let [collectionName, schematicName] = name.split(/:/, 2);
- if (!schematicName) {
- schematicName = collectionName;
- collectionName = this.collectionName;
- }
- if (!namesPerCollection[collectionName]) {
- namesPerCollection[collectionName] = [];
- }
- namesPerCollection[collectionName].push(schematicName);
- });
- const defaultCollection = this.getDefaultSchematicCollection();
- Object.keys(namesPerCollection).forEach(collectionName => {
- const isDefault = defaultCollection == collectionName;
- this.logger.info(` Collection "${collectionName}"${isDefault ? ' (default)' : ''}:`);
- namesPerCollection[collectionName].forEach(schematicName => {
- this.logger.info(` ${schematicName}`);
- });
- });
- }
- else if (schematicNames.length == 1) {
- this.logger.info('Help for schematic ' + schematicNames[0]);
- await this.printHelpSubcommand(subCommandOption.subcommands[schematicNames[0]]);
- }
- return 0;
- }
- async printHelpUsage() {
- const subCommandOption = this.description.options.filter(x => x.subcommands)[0];
- if (!subCommandOption || !subCommandOption.subcommands) {
- return;
- }
- const schematicNames = Object.keys(subCommandOption.subcommands);
- if (schematicNames.length == 1) {
- this.logger.info(this.description.description);
- const opts = this.description.options.filter(x => x.positional === undefined);
- const [collectionName, schematicName] = schematicNames[0].split(/:/)[0];
- // Display <collectionName:schematicName> if this is not the default collectionName,
- // otherwise just show the schematicName.
- const displayName = collectionName == this.getDefaultSchematicCollection() ? schematicName : schematicNames[0];
- const schematicOptions = subCommandOption.subcommands[schematicNames[0]].options;
- const schematicArgs = schematicOptions.filter(x => x.positional !== undefined);
- const argDisplay = schematicArgs.length > 0
- ? ' ' + schematicArgs.map(a => `<${core_1.strings.dasherize(a.name)}>`).join(' ')
- : '';
- this.logger.info(core_1.tags.oneLine `
- usage: ng ${this.description.name} ${displayName}${argDisplay}
- ${opts.length > 0 ? `[options]` : ``}
- `);
- this.logger.info('');
- }
- else {
- await super.printHelpUsage();
- }
- }
- getEngine() {
- return this._workflow.engine;
- }
- getCollection(collectionName) {
- const engine = this.getEngine();
- const collection = engine.createCollection(collectionName);
- if (collection === null) {
- throw new UnknownCollectionError(collectionName);
- }
- return collection;
- }
- getSchematic(collection, schematicName, allowPrivate) {
- return collection.createSchematic(schematicName, allowPrivate);
- }
- setPathOptions(options, workingDir) {
- if (workingDir === '') {
- return {};
- }
- return options
- .filter(o => o.format === 'path')
- .map(o => o.name)
- .reduce((acc, curr) => {
- acc[curr] = workingDir;
- return acc;
- }, {});
- }
- /*
- * Runtime hook to allow specifying customized workflow
- */
- createWorkflow(options) {
- if (this._workflow) {
- return this._workflow;
- }
- const { force, dryRun } = options;
- const fsHost = new core_1.virtualFs.ScopedHost(new node_1.NodeJsSyncHost(), core_1.normalize(this.workspace.root));
- const workflow = new tools_1.NodeWorkflow(fsHost, {
- force,
- dryRun,
- packageManager: package_manager_1.getPackageManager(this.workspace.root),
- root: core_1.normalize(this.workspace.root),
- registry: new core_1.schema.CoreSchemaRegistry(schematics_1.formats.standardFormats),
- });
- workflow.engineHost.registerContextTransform(context => {
- // This is run by ALL schematics, so if someone uses `externalSchematics(...)` which
- // is safelisted, it would move to the right analytics (even if their own isn't).
- const collectionName = context.schematic.collection.description.name;
- if (analytics_1.isPackageNameSafeForAnalytics(collectionName)) {
- return {
- ...context,
- analytics: this.analytics,
- };
- }
- else {
- return context;
- }
- });
- const getProjectName = () => {
- if (this._workspace) {
- const projectNames = getProjectsByPath(this._workspace, process.cwd(), this.workspace.root);
- if (projectNames.length === 1) {
- return projectNames[0];
- }
- else {
- if (projectNames.length > 1) {
- this.logger.warn(core_1.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.engineHost.registerOptionsTransform((schematic, current) => ({
- ...config_1.getSchematicDefaults(schematic.collection.name, schematic.name, getProjectName()),
- ...current,
- }));
- if (options.defaults) {
- workflow.registry.addPreTransform(core_1.schema.transforms.addUndefinedDefaults);
- }
- else {
- workflow.registry.addPostTransform(core_1.schema.transforms.addUndefinedDefaults);
- }
- workflow.engineHost.registerOptionsTransform(tools_1.validateOptionsWithSchema(workflow.registry));
- workflow.registry.addSmartDefaultProvider('projectName', getProjectName);
- if (options.interactive !== false && tty_1.isTTY()) {
- workflow.registry.usePromptProvider((definitions) => {
- const questions = definitions.map(definition => {
- const question = {
- name: definition.id,
- message: definition.message,
- default: definition.default,
- };
- const validator = definition.validator;
- if (validator) {
- question.validate = input => validator(input);
- }
- switch (definition.type) {
- case 'confirmation':
- question.type = 'confirm';
- break;
- case 'list':
- question.type = !!definition.multiselect ? 'checkbox' : 'list';
- question.choices =
- definition.items &&
- definition.items.map(item => {
- if (typeof item == 'string') {
- return item;
- }
- else {
- return {
- name: item.label,
- value: item.value,
- };
- }
- });
- break;
- default:
- question.type = definition.type;
- break;
- }
- return question;
- });
- return inquirer.prompt(questions);
- });
- }
- return (this._workflow = workflow);
- }
- getDefaultSchematicCollection() {
- let workspace = config_1.getWorkspace('local');
- if (workspace) {
- const project = config_1.getProjectByCwd(workspace);
- if (project && workspace.getProjectCli(project)) {
- const value = workspace.getProjectCli(project)['defaultCollection'];
- if (typeof value == 'string') {
- return value;
- }
- }
- if (workspace.getCli()) {
- const value = workspace.getCli()['defaultCollection'];
- if (typeof value == 'string') {
- return value;
- }
- }
- }
- workspace = config_1.getWorkspace('global');
- if (workspace && workspace.getCli()) {
- const value = workspace.getCli()['defaultCollection'];
- if (typeof value == 'string') {
- return value;
- }
- }
- return this.defaultCollectionName;
- }
- async runSchematic(options) {
- const { schematicOptions, debug, dryRun } = options;
- let { collectionName, schematicName } = options;
- let nothingDone = true;
- let loggingQueue = [];
- let error = false;
- const workflow = this._workflow;
- const workingDir = core_1.normalize(systemPath.relative(this.workspace.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;
- // TODO: Remove warning check when 'targets' is default
- if (collectionName !== this.defaultCollectionName) {
- const [ast, configPath] = config_1.getWorkspaceRaw('local');
- if (ast) {
- const projectsKeyValue = ast.properties.find(p => p.key.value === 'projects');
- if (!projectsKeyValue || projectsKeyValue.value.kind !== 'object') {
- return;
- }
- const positions = [];
- for (const projectKeyValue of projectsKeyValue.value.properties) {
- const projectNode = projectKeyValue.value;
- if (projectNode.kind !== 'object') {
- continue;
- }
- const targetsKeyValue = projectNode.properties.find(p => p.key.value === 'targets');
- if (targetsKeyValue) {
- positions.push(targetsKeyValue.start);
- }
- }
- if (positions.length > 0) {
- const warning = core_1.tags.oneLine `
- WARNING: This command may not execute successfully.
- The package/collection may not support the 'targets' field within '${configPath}'.
- This can be corrected by renaming the following 'targets' fields to 'architect':
- `;
- const locations = positions
- .map((p, i) => `${i + 1}) Line: ${p.line + 1}; Column: ${p.character + 1}`)
- .join('\n');
- this.logger.warn(warning + '\n' + locations + '\n');
- }
- }
- }
- // Set the options of format "path".
- let o = null;
- let args;
- if (!schematic.description.schemaJson) {
- args = await this.parseFreeFormArguments(schematicOptions || []);
- }
- else {
- o = await json_schema_1.parseJsonSchemaToOptions(workflow.registry, schematic.description.schemaJson);
- args = await this.parseArguments(schematicOptions || [], o);
- }
- // ng-add is special because we don't know all possible options at this point
- if (args['--'] && !this.allowAdditionalArgs) {
- args['--'].forEach(additional => {
- this.logger.fatal(`Unknown option: '${additional.split(/=/)[0]}'`);
- });
- return 1;
- }
- const pathOptions = o ? this.setPathOptions(o, workingDir) : {};
- let input = { ...pathOptions, ...args };
- // Read the default values from the workspace.
- const projectName = input.project !== undefined ? '' + input.project : null;
- const defaults = config_1.getSchematicDefaults(collectionName, schematicName, projectName);
- input = {
- ...defaults,
- ...input,
- ...options.additionalOptions,
- };
- workflow.reporter.subscribe((event) => {
- 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(core_1.tags.oneLine `
- ${color_1.colors.white('UPDATE')} ${eventPath} (${event.content.length} bytes)
- `);
- break;
- case 'create':
- loggingQueue.push(core_1.tags.oneLine `
- ${color_1.colors.green('CREATE')} ${eventPath} (${event.content.length} bytes)
- `);
- break;
- case 'delete':
- loggingQueue.push(`${color_1.colors.yellow('DELETE')} ${eventPath}`);
- break;
- case 'rename':
- loggingQueue.push(`${color_1.colors.blue('RENAME')} ${eventPath} => ${event.to}`);
- 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;
- }
- });
- return new Promise(resolve => {
- workflow
- .execute({
- collection: collectionName,
- schematic: schematicName,
- options: input,
- debug: debug,
- logger: this.logger,
- allowPrivate: this.allowPrivateSchematics,
- })
- .subscribe({
- error: (err) => {
- // In case the workflow was not successful, show an appropriate error message.
- if (err instanceof schematics_1.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 occured:\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();
- },
- });
- });
- }
- async parseFreeFormArguments(schematicOptions) {
- return parser_1.parseFreeFormArguments(schematicOptions);
- }
- async parseArguments(schematicOptions, options) {
- return parser_1.parseArguments(schematicOptions, options, this.logger);
- }
- async _loadWorkspace() {
- if (this._workspace) {
- return;
- }
- try {
- const { workspace } = await core_1.workspaces.readWorkspace(this.workspace.root, core_1.workspaces.createWorkspaceHost(this._host));
- this._workspace = workspace;
- }
- catch (err) {
- if (!this.allowMissingWorkspace) {
- // Ignore missing workspace
- throw err;
- }
- }
- }
- }
- exports.SchematicCommand = SchematicCommand;
- function getProjectsByPath(workspace, path, root) {
- if (workspace.projects.size === 1) {
- return Array.from(workspace.projects.keys());
- }
- const isInside = (base, potential) => {
- const absoluteBase = systemPath.resolve(root, base);
- const absolutePotential = systemPath.resolve(root, potential);
- const relativePotential = systemPath.relative(absoluteBase, absolutePotential);
- if (!relativePotential.startsWith('..') && !systemPath.isAbsolute(relativePotential)) {
- return true;
- }
- return false;
- };
- const projects = Array.from(workspace.projects.entries())
- .map(([name, project]) => [systemPath.resolve(root, project.root), name])
- .filter(tuple => isInside(tuple[0], path))
- // Sort tuples by depth, with the deeper ones first. Since the first member is a path and
- // we filtered all invalid paths, the longest will be the deepest (and in case of equality
- // the sort is stable and the first declared project will win).
- .sort((a, b) => b[0].length - a[0].length);
- if (projects.length === 1) {
- return [projects[0][1]];
- }
- else if (projects.length > 1) {
- const firstPath = projects[0][0];
- return projects.filter(v => v[0] === firstPath).map(v => v[1]);
- }
- return [];
- }
|