UNPKG

8.29 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.runCommand = void 0;
4/**
5 * @license
6 * Copyright Google Inc. All Rights Reserved.
7 *
8 * Use of this source code is governed by an MIT-style license that can be
9 * found in the LICENSE file at https://angular.io/license
10 */
11const core_1 = require("@angular-devkit/core");
12const fs_1 = require("fs");
13const path_1 = require("path");
14const json_schema_1 = require("../utilities/json-schema");
15const analytics_1 = require("./analytics");
16const command_1 = require("./command");
17const parser = require("./parser");
18// NOTE: Update commands.json if changing this. It's still deep imported in one CI validation
19const standardCommands = {
20 'add': '../commands/add.json',
21 'analytics': '../commands/analytics.json',
22 'build': '../commands/build.json',
23 'deploy': '../commands/deploy.json',
24 'config': '../commands/config.json',
25 'doc': '../commands/doc.json',
26 'e2e': '../commands/e2e.json',
27 'make-this-awesome': '../commands/easter-egg.json',
28 'generate': '../commands/generate.json',
29 'help': '../commands/help.json',
30 'lint': '../commands/lint.json',
31 'new': '../commands/new.json',
32 'run': '../commands/run.json',
33 'serve': '../commands/serve.json',
34 'test': '../commands/test.json',
35 'update': '../commands/update.json',
36 'version': '../commands/version.json',
37 'xi18n': '../commands/xi18n.json',
38};
39/**
40 * Create the analytics instance.
41 * @private
42 */
43async function _createAnalytics(workspace, skipPrompt = false) {
44 let config = await analytics_1.getGlobalAnalytics();
45 // If in workspace and global analytics is enabled, defer to workspace level
46 if (workspace && config) {
47 const skipAnalytics = skipPrompt ||
48 (process.env['NG_CLI_ANALYTICS'] &&
49 (process.env['NG_CLI_ANALYTICS'].toLowerCase() === 'false' ||
50 process.env['NG_CLI_ANALYTICS'] === '0'));
51 // TODO: This should honor the `no-interactive` option.
52 // It is currently not an `ng` option but rather only an option for specific commands.
53 // The concept of `ng`-wide options are needed to cleanly handle this.
54 if (!skipAnalytics && !(await analytics_1.hasWorkspaceAnalyticsConfiguration())) {
55 await analytics_1.promptProjectAnalytics();
56 }
57 config = await analytics_1.getWorkspaceAnalytics();
58 }
59 const maybeSharedAnalytics = await analytics_1.getSharedAnalytics();
60 if (config && maybeSharedAnalytics) {
61 return new core_1.analytics.MultiAnalytics([config, maybeSharedAnalytics]);
62 }
63 else if (config) {
64 return config;
65 }
66 else if (maybeSharedAnalytics) {
67 return maybeSharedAnalytics;
68 }
69 else {
70 return new core_1.analytics.NoopAnalytics();
71 }
72}
73async function loadCommandDescription(name, path, registry) {
74 const schemaPath = path_1.resolve(__dirname, path);
75 const schemaContent = fs_1.readFileSync(schemaPath, 'utf-8');
76 const schema = core_1.json.parseJson(schemaContent, core_1.JsonParseMode.Loose, { path: schemaPath });
77 if (!core_1.isJsonObject(schema)) {
78 throw new Error('Invalid command JSON loaded from ' + JSON.stringify(schemaPath));
79 }
80 return json_schema_1.parseJsonSchemaToCommandDescription(name, schemaPath, registry, schema);
81}
82/**
83 * Run a command.
84 * @param args Raw unparsed arguments.
85 * @param logger The logger to use.
86 * @param workspace Workspace information.
87 * @param commands The map of supported commands.
88 * @param options Additional options.
89 */
90async function runCommand(args, logger, workspace, commands = standardCommands, options = {}) {
91 // This registry is exclusively used for flattening schemas, and not for validating.
92 const registry = new core_1.schema.CoreSchemaRegistry([]);
93 registry.registerUriHandler((uri) => {
94 if (uri.startsWith('ng-cli://')) {
95 const content = fs_1.readFileSync(path_1.join(__dirname, '..', uri.substr('ng-cli://'.length)), 'utf-8');
96 return Promise.resolve(JSON.parse(content));
97 }
98 else {
99 return null;
100 }
101 });
102 let commandName = undefined;
103 for (let i = 0; i < args.length; i++) {
104 const arg = args[i];
105 if (!arg.startsWith('-')) {
106 commandName = arg;
107 args.splice(i, 1);
108 break;
109 }
110 }
111 let description = null;
112 // if no commands were found, use `help`.
113 if (!commandName) {
114 if (args.length === 1 && args[0] === '--version') {
115 commandName = 'version';
116 }
117 else {
118 commandName = 'help';
119 }
120 if (!(commandName in commands)) {
121 logger.error(core_1.tags.stripIndent `
122 The "${commandName}" command seems to be disabled.
123 This is an issue with the CLI itself. If you see this comment, please report it and
124 provide your repository.
125 `);
126 return 1;
127 }
128 }
129 if (commandName in commands) {
130 description = await loadCommandDescription(commandName, commands[commandName], registry);
131 }
132 else {
133 const commandNames = Object.keys(commands);
134 // Optimize loading for common aliases
135 if (commandName.length === 1) {
136 commandNames.sort((a, b) => {
137 const aMatch = a[0] === commandName;
138 const bMatch = b[0] === commandName;
139 if (aMatch && !bMatch) {
140 return -1;
141 }
142 else if (!aMatch && bMatch) {
143 return 1;
144 }
145 else {
146 return 0;
147 }
148 });
149 }
150 for (const name of commandNames) {
151 const aliasDesc = await loadCommandDescription(name, commands[name], registry);
152 const aliases = aliasDesc.aliases;
153 if (aliases && aliases.some(alias => alias === commandName)) {
154 commandName = name;
155 description = aliasDesc;
156 break;
157 }
158 }
159 }
160 if (!description) {
161 const commandsDistance = {};
162 const name = commandName;
163 const allCommands = Object.keys(commands).sort((a, b) => {
164 if (!(a in commandsDistance)) {
165 commandsDistance[a] = core_1.strings.levenshtein(a, name);
166 }
167 if (!(b in commandsDistance)) {
168 commandsDistance[b] = core_1.strings.levenshtein(b, name);
169 }
170 return commandsDistance[a] - commandsDistance[b];
171 });
172 logger.error(core_1.tags.stripIndent `
173 The specified command ("${commandName}") is invalid. For a list of available options,
174 run "ng help".
175
176 Did you mean "${allCommands[0]}"?
177 `);
178 return 1;
179 }
180 try {
181 const parsedOptions = parser.parseArguments(args, description.options, logger);
182 command_1.Command.setCommandMap(async () => {
183 const map = {};
184 for (const [name, path] of Object.entries(commands)) {
185 map[name] = await loadCommandDescription(name, path, registry);
186 }
187 return map;
188 });
189 const analytics = options.analytics ||
190 (await _createAnalytics(!!workspace.configFile, description.name === 'update'));
191 const context = { workspace, analytics };
192 const command = new description.impl(context, description, logger);
193 // Flush on an interval (if the event loop is waiting).
194 let analyticsFlushPromise = Promise.resolve();
195 setInterval(() => {
196 analyticsFlushPromise = analyticsFlushPromise.then(() => analytics.flush());
197 }, 1000);
198 const result = await command.validateAndRun(parsedOptions);
199 // Flush one last time.
200 await analyticsFlushPromise.then(() => analytics.flush());
201 return result;
202 }
203 catch (e) {
204 if (e instanceof parser.ParseArgumentException) {
205 logger.fatal('Cannot parse arguments. See below for the reasons.');
206 logger.fatal(' ' + e.comments.join('\n '));
207 return 1;
208 }
209 else {
210 throw e;
211 }
212 }
213}
214exports.runCommand = runCommand;