mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-16 02:24:10 +08:00
fix(@angular/cli): improve architect command project parsing
This commit is contained in:
parent
c631c1852f
commit
5e7f995001
@ -7,14 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
Architect,
|
Architect,
|
||||||
BuilderConfiguration,
|
|
||||||
TargetSpecifier,
|
TargetSpecifier,
|
||||||
} from '@angular-devkit/architect';
|
} from '@angular-devkit/architect';
|
||||||
import { experimental, json, schema, tags } from '@angular-devkit/core';
|
import { experimental, json, schema, tags } from '@angular-devkit/core';
|
||||||
import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node';
|
import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node';
|
||||||
import { parseJsonSchemaToOptions } from '../utilities/json-schema';
|
import { parseJsonSchemaToOptions } from '../utilities/json-schema';
|
||||||
import { BaseCommandOptions, Command } from './command';
|
import { BaseCommandOptions, Command } from './command';
|
||||||
import { Arguments } from './interface';
|
import { Arguments, Option } from './interface';
|
||||||
import { parseArguments } from './parser';
|
import { parseArguments } from './parser';
|
||||||
import { WorkspaceLoader } from './workspace-loader';
|
import { WorkspaceLoader } from './workspace-loader';
|
||||||
|
|
||||||
@ -46,84 +45,123 @@ export abstract class ArchitectCommand<
|
|||||||
|
|
||||||
await this._loadWorkspaceAndArchitect();
|
await this._loadWorkspaceAndArchitect();
|
||||||
|
|
||||||
if (!options.project && this.target) {
|
if (!this.target) {
|
||||||
const projectNames = this.getProjectNamesByTarget(this.target);
|
|
||||||
const leftovers = options['--'];
|
|
||||||
if (projectNames.length > 1 && leftovers && leftovers.length > 0) {
|
|
||||||
// Verify that all builders are the same, otherwise error out (since the meaning of an
|
|
||||||
// option could vary from builder to builder).
|
|
||||||
|
|
||||||
const builders: string[] = [];
|
|
||||||
for (const projectName of projectNames) {
|
|
||||||
const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options);
|
|
||||||
const targetDesc = this._architect.getBuilderConfiguration({
|
|
||||||
project: projectName,
|
|
||||||
target: targetSpec.target,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (builders.indexOf(targetDesc.builder) == -1) {
|
|
||||||
builders.push(targetDesc.builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builders.length > 1) {
|
|
||||||
throw new Error(tags.oneLine`
|
|
||||||
Architect commands with command line overrides cannot target different builders. The
|
|
||||||
'${this.target}' target would run on projects ${projectNames.join()} which have the
|
|
||||||
following builders: ${'\n ' + builders.join('\n ')}
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options);
|
|
||||||
|
|
||||||
if (this.target && !targetSpec.project) {
|
|
||||||
const projects = this.getProjectNamesByTarget(this.target);
|
|
||||||
|
|
||||||
if (projects.length === 1) {
|
|
||||||
// If there is a single target, use it to parse overrides.
|
|
||||||
targetSpec.project = projects[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!targetSpec.project || !targetSpec.target) && !this.multiTarget) {
|
|
||||||
if (options.help) {
|
if (options.help) {
|
||||||
// This is a special case where we just return.
|
// This is a special case where we just return.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Cannot determine project or target for Architect command.');
|
const specifier = this._makeTargetSpecifier(options);
|
||||||
|
if (!specifier.project || !specifier.target) {
|
||||||
|
throw new Error('Cannot determine project or target for command.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.target) {
|
const commandLeftovers = options['--'];
|
||||||
// Add options IF there's only one builder of this kind.
|
let projectName = options.project;
|
||||||
const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options);
|
const targetProjectNames: string[] = [];
|
||||||
const projectNames = targetSpec.project
|
for (const name of this._workspace.listProjectNames()) {
|
||||||
? [targetSpec.project]
|
if (this._architect.listProjectTargets(name).includes(this.target)) {
|
||||||
: this.getProjectNamesByTarget(this.target);
|
targetProjectNames.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const builderConfigurations: BuilderConfiguration[] = [];
|
if (targetProjectNames.length === 0) {
|
||||||
for (const projectName of projectNames) {
|
throw new Error(`No projects support the '${this.target}' target.`);
|
||||||
const targetDesc = this._architect.getBuilderConfiguration({
|
}
|
||||||
project: projectName,
|
|
||||||
target: targetSpec.target,
|
if (projectName && !targetProjectNames.includes(projectName)) {
|
||||||
|
throw new Error(`Project '${projectName}' does not support the '${this.target}' target.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!projectName && commandLeftovers && commandLeftovers.length > 0) {
|
||||||
|
const builderNames = new Set<string>();
|
||||||
|
const leftoverMap = new Map<string, { optionDefs: Option[], parsedOptions: Arguments }>();
|
||||||
|
let potentialProjectNames = new Set<string>(targetProjectNames);
|
||||||
|
for (const name of targetProjectNames) {
|
||||||
|
const builderConfig = this._architect.getBuilderConfiguration({
|
||||||
|
project: name,
|
||||||
|
target: this.target,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!builderConfigurations.find(b => b.builder === targetDesc.builder)) {
|
if (this.multiTarget) {
|
||||||
builderConfigurations.push(targetDesc);
|
builderNames.add(builderConfig.builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
const builderDesc = await this._architect.getBuilderDescription(builderConfig).toPromise();
|
||||||
|
const optionDefs = await parseJsonSchemaToOptions(this._registry, builderDesc.schema);
|
||||||
|
const parsedOptions = parseArguments([...commandLeftovers], optionDefs);
|
||||||
|
const builderLeftovers = parsedOptions['--'] || [];
|
||||||
|
leftoverMap.set(name, { optionDefs, parsedOptions });
|
||||||
|
|
||||||
|
potentialProjectNames = new Set(builderLeftovers.filter(x => potentialProjectNames.has(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialProjectNames.size === 1) {
|
||||||
|
projectName = [...potentialProjectNames][0];
|
||||||
|
|
||||||
|
// remove the project name from the leftovers
|
||||||
|
const optionInfo = leftoverMap.get(projectName);
|
||||||
|
if (optionInfo) {
|
||||||
|
const locations = [];
|
||||||
|
let i = 0;
|
||||||
|
while (i < commandLeftovers.length) {
|
||||||
|
i = commandLeftovers.indexOf(projectName, i + 1);
|
||||||
|
if (i === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
locations.push(i);
|
||||||
|
}
|
||||||
|
delete optionInfo.parsedOptions['--'];
|
||||||
|
for (const location of locations) {
|
||||||
|
const tempLeftovers = [...commandLeftovers];
|
||||||
|
tempLeftovers.splice(location, 1);
|
||||||
|
const tempArgs = parseArguments([...tempLeftovers], optionInfo.optionDefs);
|
||||||
|
delete tempArgs['--'];
|
||||||
|
if (JSON.stringify(optionInfo.parsedOptions) === JSON.stringify(tempArgs)) {
|
||||||
|
options['--'] = tempLeftovers;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (builderConfigurations.length == 1) {
|
if (!projectName && this.multiTarget && builderNames.size > 1) {
|
||||||
const builderConf = builderConfigurations[0];
|
throw new Error(tags.oneLine`
|
||||||
const builderDesc = await this._architect.getBuilderDescription(builderConf).toPromise();
|
Architect commands with command line overrides cannot target different builders. The
|
||||||
|
'${this.target}' target would run on projects ${targetProjectNames.join()} which have the
|
||||||
this.description.options.push(...(
|
following builders: ${'\n ' + [...builderNames].join('\n ')}
|
||||||
await parseJsonSchemaToOptions(this._registry, builderDesc.schema)
|
`);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!projectName && !this.multiTarget) {
|
||||||
|
const defaultProjectName = this._workspace.getDefaultProjectName();
|
||||||
|
if (targetProjectNames.length === 1) {
|
||||||
|
projectName = targetProjectNames[0];
|
||||||
|
} else if (defaultProjectName && targetProjectNames.includes(defaultProjectName)) {
|
||||||
|
projectName = defaultProjectName;
|
||||||
|
} else if (options.help) {
|
||||||
|
// This is a special case where we just return.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error('Cannot determine project or target for command.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.project = projectName;
|
||||||
|
|
||||||
|
const builderConf = this._architect.getBuilderConfiguration({
|
||||||
|
project: projectName || (targetProjectNames.length > 0 ? targetProjectNames[0] : ''),
|
||||||
|
target: this.target,
|
||||||
|
});
|
||||||
|
const builderDesc = await this._architect.getBuilderDescription(builderConf).toPromise();
|
||||||
|
|
||||||
|
this.description.options.push(...(
|
||||||
|
await parseJsonSchemaToOptions(this._registry, builderDesc.schema)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(options: ArchitectCommandOptions & Arguments) {
|
async run(options: ArchitectCommandOptions & Arguments) {
|
||||||
|
@ -7,10 +7,15 @@ export default async function() {
|
|||||||
await ng('build');
|
await ng('build');
|
||||||
await expectFileToMatch('dist/test-project/index.html', 'main.js');
|
await expectFileToMatch('dist/test-project/index.html', 'main.js');
|
||||||
|
|
||||||
|
// Named Development build
|
||||||
|
await ng('build', 'test-project');
|
||||||
|
await ng('build', 'test-project', '--no-progress');
|
||||||
|
await ng('build', '--no-progress', 'test-project');
|
||||||
|
|
||||||
// Production build
|
// Production build
|
||||||
await ng('build', '--prod');
|
await ng('build', '--prod');
|
||||||
await expectFileToMatch('dist/test-project/index.html', /main\.[a-zA-Z0-9]{20}\.js/);
|
await expectFileToMatch('dist/test-project/index.html', /main\.[a-zA-Z0-9]{20}\.js/);
|
||||||
|
await ng('build', '--prod', '--no-progress', 'test-project');
|
||||||
|
|
||||||
// Store the production build for artifact storage on CircleCI
|
// Store the production build for artifact storage on CircleCI
|
||||||
if (process.env['CIRCLECI']) {
|
if (process.env['CIRCLECI']) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user