|
7 | 7 | */
|
8 | 8 | import {
|
9 | 9 | Architect,
|
10 |
| - BuilderConfiguration, |
11 | 10 | TargetSpecifier,
|
12 | 11 | } from '@angular-devkit/architect';
|
13 | 12 | import { experimental, json, schema, tags } from '@angular-devkit/core';
|
14 | 13 | import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node';
|
15 | 14 | import { parseJsonSchemaToOptions } from '../utilities/json-schema';
|
16 | 15 | import { BaseCommandOptions, Command } from './command';
|
17 |
| -import { Arguments } from './interface'; |
| 16 | +import { Arguments, Option } from './interface'; |
18 | 17 | import { parseArguments } from './parser';
|
19 | 18 | import { WorkspaceLoader } from './workspace-loader';
|
20 | 19 |
|
@@ -46,84 +45,123 @@ export abstract class ArchitectCommand<
|
46 | 45 |
|
47 | 46 | await this._loadWorkspaceAndArchitect();
|
48 | 47 |
|
49 |
| - if (!options.project && this.target) { |
50 |
| - const projectNames = this.getProjectNamesByTarget(this.target); |
51 |
| - const leftovers = options['--']; |
52 |
| - if (projectNames.length > 1 && leftovers && leftovers.length > 0) { |
53 |
| - // Verify that all builders are the same, otherwise error out (since the meaning of an |
54 |
| - // option could vary from builder to builder). |
55 |
| - |
56 |
| - const builders: string[] = []; |
57 |
| - for (const projectName of projectNames) { |
58 |
| - const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options); |
59 |
| - const targetDesc = this._architect.getBuilderConfiguration({ |
60 |
| - project: projectName, |
61 |
| - target: targetSpec.target, |
62 |
| - }); |
63 |
| - |
64 |
| - if (builders.indexOf(targetDesc.builder) == -1) { |
65 |
| - builders.push(targetDesc.builder); |
66 |
| - } |
67 |
| - } |
68 |
| - |
69 |
| - if (builders.length > 1) { |
70 |
| - throw new Error(tags.oneLine` |
71 |
| - Architect commands with command line overrides cannot target different builders. The |
72 |
| - '${this.target}' target would run on projects ${projectNames.join()} which have the |
73 |
| - following builders: ${'\n ' + builders.join('\n ')} |
74 |
| - `); |
75 |
| - } |
| 48 | + if (!this.target) { |
| 49 | + if (options.help) { |
| 50 | + // This is a special case where we just return. |
| 51 | + return; |
76 | 52 | }
|
77 |
| - } |
78 | 53 |
|
79 |
| - const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options); |
| 54 | + const specifier = this._makeTargetSpecifier(options); |
| 55 | + if (!specifier.project || !specifier.target) { |
| 56 | + throw new Error('Cannot determine project or target for command.'); |
| 57 | + } |
80 | 58 |
|
81 |
| - if (this.target && !targetSpec.project) { |
82 |
| - const projects = this.getProjectNamesByTarget(this.target); |
| 59 | + return; |
| 60 | + } |
83 | 61 |
|
84 |
| - if (projects.length === 1) { |
85 |
| - // If there is a single target, use it to parse overrides. |
86 |
| - targetSpec.project = projects[0]; |
| 62 | + const commandLeftovers = options['--']; |
| 63 | + let projectName = options.project; |
| 64 | + const targetProjectNames: string[] = []; |
| 65 | + for (const name of this._workspace.listProjectNames()) { |
| 66 | + if (this._architect.listProjectTargets(name).includes(this.target)) { |
| 67 | + targetProjectNames.push(name); |
87 | 68 | }
|
88 | 69 | }
|
89 | 70 |
|
90 |
| - if ((!targetSpec.project || !targetSpec.target) && !this.multiTarget) { |
91 |
| - if (options.help) { |
92 |
| - // This is a special case where we just return. |
93 |
| - return; |
94 |
| - } |
| 71 | + if (targetProjectNames.length === 0) { |
| 72 | + throw new Error(`No projects support the '${this.target}' target.`); |
| 73 | + } |
95 | 74 |
|
96 |
| - throw new Error('Cannot determine project or target for Architect command.'); |
| 75 | + if (projectName && !targetProjectNames.includes(projectName)) { |
| 76 | + throw new Error(`Project '${projectName}' does not support the '${this.target}' target.`); |
97 | 77 | }
|
98 | 78 |
|
99 |
| - if (this.target) { |
100 |
| - // Add options IF there's only one builder of this kind. |
101 |
| - const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options); |
102 |
| - const projectNames = targetSpec.project |
103 |
| - ? [targetSpec.project] |
104 |
| - : this.getProjectNamesByTarget(this.target); |
105 |
| - |
106 |
| - const builderConfigurations: BuilderConfiguration[] = []; |
107 |
| - for (const projectName of projectNames) { |
108 |
| - const targetDesc = this._architect.getBuilderConfiguration({ |
109 |
| - project: projectName, |
110 |
| - target: targetSpec.target, |
| 79 | + if (!projectName && commandLeftovers && commandLeftovers.length > 0) { |
| 80 | + const builderNames = new Set<string>(); |
| 81 | + const leftoverMap = new Map<string, { optionDefs: Option[], parsedOptions: Arguments }>(); |
| 82 | + let potentialProjectNames = new Set<string>(targetProjectNames); |
| 83 | + for (const name of targetProjectNames) { |
| 84 | + const builderConfig = this._architect.getBuilderConfiguration({ |
| 85 | + project: name, |
| 86 | + target: this.target, |
111 | 87 | });
|
112 | 88 |
|
113 |
| - if (!builderConfigurations.find(b => b.builder === targetDesc.builder)) { |
114 |
| - builderConfigurations.push(targetDesc); |
| 89 | + if (this.multiTarget) { |
| 90 | + builderNames.add(builderConfig.builder); |
115 | 91 | }
|
| 92 | + |
| 93 | + const builderDesc = await this._architect.getBuilderDescription(builderConfig).toPromise(); |
| 94 | + const optionDefs = await parseJsonSchemaToOptions(this._registry, builderDesc.schema); |
| 95 | + const parsedOptions = parseArguments([...commandLeftovers], optionDefs); |
| 96 | + const builderLeftovers = parsedOptions['--'] || []; |
| 97 | + leftoverMap.set(name, { optionDefs, parsedOptions }); |
| 98 | + |
| 99 | + potentialProjectNames = new Set(builderLeftovers.filter(x => potentialProjectNames.has(x))); |
116 | 100 | }
|
117 | 101 |
|
118 |
| - if (builderConfigurations.length == 1) { |
119 |
| - const builderConf = builderConfigurations[0]; |
120 |
| - const builderDesc = await this._architect.getBuilderDescription(builderConf).toPromise(); |
| 102 | + if (potentialProjectNames.size === 1) { |
| 103 | + projectName = [...potentialProjectNames][0]; |
| 104 | + |
| 105 | + // remove the project name from the leftovers |
| 106 | + const optionInfo = leftoverMap.get(projectName); |
| 107 | + if (optionInfo) { |
| 108 | + const locations = []; |
| 109 | + let i = 0; |
| 110 | + while (i < commandLeftovers.length) { |
| 111 | + i = commandLeftovers.indexOf(projectName, i + 1); |
| 112 | + if (i === -1) { |
| 113 | + break; |
| 114 | + } |
| 115 | + locations.push(i); |
| 116 | + } |
| 117 | + delete optionInfo.parsedOptions['--']; |
| 118 | + for (const location of locations) { |
| 119 | + const tempLeftovers = [...commandLeftovers]; |
| 120 | + tempLeftovers.splice(location, 1); |
| 121 | + const tempArgs = parseArguments([...tempLeftovers], optionInfo.optionDefs); |
| 122 | + delete tempArgs['--']; |
| 123 | + if (JSON.stringify(optionInfo.parsedOptions) === JSON.stringify(tempArgs)) { |
| 124 | + options['--'] = tempLeftovers; |
| 125 | + break; |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + if (!projectName && this.multiTarget && builderNames.size > 1) { |
| 132 | + throw new Error(tags.oneLine` |
| 133 | + Architect commands with command line overrides cannot target different builders. The |
| 134 | + '${this.target}' target would run on projects ${targetProjectNames.join()} which have the |
| 135 | + following builders: ${'\n ' + [...builderNames].join('\n ')} |
| 136 | + `); |
| 137 | + } |
| 138 | + } |
121 | 139 |
|
122 |
| - this.description.options.push(...( |
123 |
| - await parseJsonSchemaToOptions(this._registry, builderDesc.schema) |
124 |
| - )); |
| 140 | + if (!projectName && !this.multiTarget) { |
| 141 | + const defaultProjectName = this._workspace.getDefaultProjectName(); |
| 142 | + if (targetProjectNames.length === 1) { |
| 143 | + projectName = targetProjectNames[0]; |
| 144 | + } else if (defaultProjectName && targetProjectNames.includes(defaultProjectName)) { |
| 145 | + projectName = defaultProjectName; |
| 146 | + } else if (options.help) { |
| 147 | + // This is a special case where we just return. |
| 148 | + return; |
| 149 | + } else { |
| 150 | + throw new Error('Cannot determine project or target for command.'); |
125 | 151 | }
|
126 | 152 | }
|
| 153 | + |
| 154 | + options.project = projectName; |
| 155 | + |
| 156 | + const builderConf = this._architect.getBuilderConfiguration({ |
| 157 | + project: projectName || (targetProjectNames.length > 0 ? targetProjectNames[0] : ''), |
| 158 | + target: this.target, |
| 159 | + }); |
| 160 | + const builderDesc = await this._architect.getBuilderDescription(builderConf).toPromise(); |
| 161 | + |
| 162 | + this.description.options.push(...( |
| 163 | + await parseJsonSchemaToOptions(this._registry, builderDesc.schema) |
| 164 | + )); |
127 | 165 | }
|
128 | 166 |
|
129 | 167 | async run(options: ArchitectCommandOptions & Arguments) {
|
|
0 commit comments