UNPKG

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