UNPKG

14.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.ArchitectCommand = 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 architect_1 = require("@angular-devkit/architect");
12const node_1 = require("@angular-devkit/architect/node");
13const core_1 = require("@angular-devkit/core");
14const node_2 = require("@angular-devkit/core/node");
15const bep_1 = require("../utilities/bep");
16const json_schema_1 = require("../utilities/json-schema");
17const analytics_1 = require("./analytics");
18const command_1 = require("./command");
19const parser_1 = require("./parser");
20class ArchitectCommand extends command_1.Command {
21 constructor() {
22 super(...arguments);
23 // If this command supports running multiple targets.
24 this.multiTarget = false;
25 }
26 async initialize(options) {
27 await super.initialize(options);
28 this._registry = new core_1.json.schema.CoreSchemaRegistry();
29 this._registry.addPostTransform(core_1.json.schema.transforms.addUndefinedDefaults);
30 this._registry.useXDeprecatedProvider(msg => this.logger.warn(msg));
31 const { workspace } = await core_1.workspaces.readWorkspace(this.workspace.root, core_1.workspaces.createWorkspaceHost(new node_2.NodeJsSyncHost()));
32 this._workspace = workspace;
33 this._architectHost = new node_1.WorkspaceNodeModulesArchitectHost(workspace, this.workspace.root);
34 this._architect = new architect_1.Architect(this._architectHost, this._registry);
35 if (!this.target) {
36 if (options.help) {
37 // This is a special case where we just return.
38 return;
39 }
40 const specifier = this._makeTargetSpecifier(options);
41 if (!specifier.project || !specifier.target) {
42 throw new Error('Cannot determine project or target for command.');
43 }
44 return;
45 }
46 let projectName = options.project;
47 if (projectName && !this._workspace.projects.has(projectName)) {
48 throw new Error(`Project '${projectName}' does not exist.`);
49 }
50 const commandLeftovers = options['--'];
51 const targetProjectNames = [];
52 for (const [name, project] of this._workspace.projects) {
53 if (project.targets.has(this.target)) {
54 targetProjectNames.push(name);
55 }
56 }
57 if (targetProjectNames.length === 0) {
58 throw new Error(this.missingTargetError || `No projects support the '${this.target}' target.`);
59 }
60 if (projectName && !targetProjectNames.includes(projectName)) {
61 throw new Error(this.missingTargetError ||
62 `Project '${projectName}' does not support the '${this.target}' target.`);
63 }
64 if (!projectName && commandLeftovers && commandLeftovers.length > 0) {
65 const builderNames = new Set();
66 const leftoverMap = new Map();
67 let potentialProjectNames = new Set(targetProjectNames);
68 for (const name of targetProjectNames) {
69 const builderName = await this._architectHost.getBuilderNameForTarget({
70 project: name,
71 target: this.target,
72 });
73 if (this.multiTarget) {
74 builderNames.add(builderName);
75 }
76 const builderDesc = await this._architectHost.resolveBuilder(builderName);
77 const optionDefs = await json_schema_1.parseJsonSchemaToOptions(this._registry, builderDesc.optionSchema);
78 const parsedOptions = parser_1.parseArguments([...commandLeftovers], optionDefs);
79 const builderLeftovers = parsedOptions['--'] || [];
80 leftoverMap.set(name, { optionDefs, parsedOptions });
81 potentialProjectNames = new Set(builderLeftovers.filter(x => potentialProjectNames.has(x)));
82 }
83 if (potentialProjectNames.size === 1) {
84 projectName = [...potentialProjectNames][0];
85 // remove the project name from the leftovers
86 const optionInfo = leftoverMap.get(projectName);
87 if (optionInfo) {
88 const locations = [];
89 let i = 0;
90 while (i < commandLeftovers.length) {
91 i = commandLeftovers.indexOf(projectName, i + 1);
92 if (i === -1) {
93 break;
94 }
95 locations.push(i);
96 }
97 delete optionInfo.parsedOptions['--'];
98 for (const location of locations) {
99 const tempLeftovers = [...commandLeftovers];
100 tempLeftovers.splice(location, 1);
101 const tempArgs = parser_1.parseArguments([...tempLeftovers], optionInfo.optionDefs);
102 delete tempArgs['--'];
103 if (JSON.stringify(optionInfo.parsedOptions) === JSON.stringify(tempArgs)) {
104 options['--'] = tempLeftovers;
105 break;
106 }
107 }
108 }
109 }
110 if (!projectName && this.multiTarget && builderNames.size > 1) {
111 throw new Error(core_1.tags.oneLine `
112 Architect commands with command line overrides cannot target different builders. The
113 '${this.target}' target would run on projects ${targetProjectNames.join()} which have the
114 following builders: ${'\n ' + [...builderNames].join('\n ')}
115 `);
116 }
117 }
118 if (!projectName && !this.multiTarget) {
119 const defaultProjectName = this._workspace.extensions['defaultProject'];
120 if (targetProjectNames.length === 1) {
121 projectName = targetProjectNames[0];
122 }
123 else if (defaultProjectName && targetProjectNames.includes(defaultProjectName)) {
124 projectName = defaultProjectName;
125 }
126 else if (options.help) {
127 // This is a special case where we just return.
128 return;
129 }
130 else {
131 throw new Error(this.missingTargetError || 'Cannot determine project or target for command.');
132 }
133 }
134 options.project = projectName;
135 const builderConf = await this._architectHost.getBuilderNameForTarget({
136 project: projectName || (targetProjectNames.length > 0 ? targetProjectNames[0] : ''),
137 target: this.target,
138 });
139 const builderDesc = await this._architectHost.resolveBuilder(builderConf);
140 this.description.options.push(...(await json_schema_1.parseJsonSchemaToOptions(this._registry, builderDesc.optionSchema)));
141 // Update options to remove analytics from options if the builder isn't safelisted.
142 for (const o of this.description.options) {
143 if (o.userAnalytics && !analytics_1.isPackageNameSafeForAnalytics(builderConf)) {
144 o.userAnalytics = undefined;
145 }
146 }
147 }
148 async run(options) {
149 return await this.runArchitectTarget(options);
150 }
151 async runBepTarget(command, configuration, overrides, buildEventLog) {
152 const bep = new bep_1.BepJsonWriter(buildEventLog);
153 // Send start
154 bep.writeBuildStarted(command);
155 let last = 1;
156 let rebuild = false;
157 const run = await this._architect.scheduleTarget(configuration, overrides, {
158 logger: this.logger,
159 });
160 await run.output.forEach(event => {
161 last = event.success ? 0 : 1;
162 if (rebuild) {
163 // NOTE: This will have an incorrect timestamp but this cannot be fixed
164 // until builders report additional status events
165 bep.writeBuildStarted(command);
166 }
167 else {
168 rebuild = true;
169 }
170 bep.writeBuildFinished(last);
171 });
172 await run.stop();
173 return last;
174 }
175 async runSingleTarget(target, targetOptions, commandOptions) {
176 // We need to build the builderSpec twice because architect does not understand
177 // overrides separately (getting the configuration builds the whole project, including
178 // overrides).
179 const builderConf = await this._architectHost.getBuilderNameForTarget(target);
180 const builderDesc = await this._architectHost.resolveBuilder(builderConf);
181 const targetOptionArray = await json_schema_1.parseJsonSchemaToOptions(this._registry, builderDesc.optionSchema);
182 const overrides = parser_1.parseArguments(targetOptions, targetOptionArray, this.logger);
183 const allowAdditionalProperties = typeof builderDesc.optionSchema === 'object' && builderDesc.optionSchema.additionalProperties;
184 if (overrides['--'] && !allowAdditionalProperties) {
185 (overrides['--'] || []).forEach(additional => {
186 this.logger.fatal(`Unknown option: '${additional.split(/=/)[0]}'`);
187 });
188 return 1;
189 }
190 if (commandOptions.buildEventLog && ['build', 'serve'].includes(this.description.name)) {
191 // The build/serve commands supports BEP messaging
192 this.logger.warn('BEP support is experimental and subject to change.');
193 return this.runBepTarget(this.description.name, target, overrides, commandOptions.buildEventLog);
194 }
195 else {
196 const run = await this._architect.scheduleTarget(target, overrides, {
197 logger: this.logger,
198 analytics: analytics_1.isPackageNameSafeForAnalytics(builderConf) ? this.analytics : undefined,
199 });
200 const { error, success } = await run.output.toPromise();
201 await run.stop();
202 if (error) {
203 this.logger.error(error);
204 }
205 return success ? 0 : 1;
206 }
207 }
208 async runArchitectTarget(options) {
209 const extra = options['--'] || [];
210 try {
211 const targetSpec = this._makeTargetSpecifier(options);
212 if (!targetSpec.project && this.target) {
213 // This runs each target sequentially.
214 // Running them in parallel would jumble the log messages.
215 let result = 0;
216 for (const project of this.getProjectNamesByTarget(this.target)) {
217 result |= await this.runSingleTarget({ ...targetSpec, project }, extra, options);
218 }
219 return result;
220 }
221 else {
222 return await this.runSingleTarget(targetSpec, extra, options);
223 }
224 }
225 catch (e) {
226 if (e instanceof core_1.schema.SchemaValidationException) {
227 const newErrors = [];
228 for (const schemaError of e.errors) {
229 if (schemaError.keyword === 'additionalProperties') {
230 const unknownProperty = schemaError.params.additionalProperty;
231 if (unknownProperty in options) {
232 const dashes = unknownProperty.length === 1 ? '-' : '--';
233 this.logger.fatal(`Unknown option: '${dashes}${unknownProperty}'`);
234 continue;
235 }
236 }
237 newErrors.push(schemaError);
238 }
239 if (newErrors.length > 0) {
240 this.logger.error(new core_1.schema.SchemaValidationException(newErrors).message);
241 }
242 return 1;
243 }
244 else {
245 throw e;
246 }
247 }
248 }
249 getProjectNamesByTarget(targetName) {
250 const allProjectsForTargetName = [];
251 for (const [name, project] of this._workspace.projects) {
252 if (project.targets.has(targetName)) {
253 allProjectsForTargetName.push(name);
254 }
255 }
256 if (this.multiTarget) {
257 // For multi target commands, we always list all projects that have the target.
258 return allProjectsForTargetName;
259 }
260 else {
261 // For single target commands, we try the default project first,
262 // then the full list if it has a single project, then error out.
263 const maybeDefaultProject = this._workspace.extensions['defaultProject'];
264 if (maybeDefaultProject && allProjectsForTargetName.includes(maybeDefaultProject)) {
265 return [maybeDefaultProject];
266 }
267 if (allProjectsForTargetName.length === 1) {
268 return allProjectsForTargetName;
269 }
270 throw new Error(`Could not determine a single project for the '${targetName}' target.`);
271 }
272 }
273 _makeTargetSpecifier(commandOptions) {
274 let project, target, configuration;
275 if (commandOptions.target) {
276 [project, target, configuration] = commandOptions.target.split(':');
277 if (commandOptions.configuration) {
278 configuration = commandOptions.configuration;
279 }
280 }
281 else {
282 project = commandOptions.project;
283 target = this.target;
284 if (commandOptions.prod) {
285 // The --prod flag will always be the first configuration, available to be overwritten
286 // by following configurations.
287 configuration = 'production';
288 }
289 if (commandOptions.configuration) {
290 configuration =
291 `${configuration ? `${configuration},` : ''}${commandOptions.configuration}`;
292 }
293 }
294 if (!project) {
295 project = '';
296 }
297 if (!target) {
298 target = '';
299 }
300 return {
301 project,
302 configuration: configuration || '',
303 target,
304 };
305 }
306}
307exports.ArchitectCommand = ArchitectCommand;