diff --git a/packages/angular/cli/src/command-builder/architect-command-module.ts b/packages/angular/cli/src/command-builder/architect-command-module.ts index a58edcccc06d..abd3d2c41fb4 100644 --- a/packages/angular/cli/src/command-builder/architect-command-module.ts +++ b/packages/angular/cli/src/command-builder/architect-command-module.ts @@ -94,15 +94,16 @@ export abstract class ArchitectCommandModule } private getArchitectProject(): string | undefined { - const workspace = this.context.workspace; - if (!workspace) { - return undefined; - } - - const [, projectName] = this.context.args.positional; + const { options, positional } = this.context.args; + const [, projectName] = positional; if (projectName) { - return workspace.projects.has(projectName) ? projectName : undefined; + return projectName; + } + + // Yargs allows positional args to be used as flags. + if (typeof options['project'] === 'string') { + return options['project']; } const target = this.getArchitectTarget(); @@ -114,8 +115,8 @@ export abstract class ArchitectCommandModule @memoize private getProjectNamesByTarget(target: string): string[] | undefined { const workspace = this.getWorkspaceOrThrow(); - const allProjectsForTargetName: string[] = []; + for (const [name, project] of workspace.projects) { if (project.targets.has(target)) { allProjectsForTargetName.push(name); @@ -135,8 +136,17 @@ export abstract class ArchitectCommandModule } const maybeProject = getProjectByCwd(workspace); - if (maybeProject && allProjectsForTargetName.includes(maybeProject)) { - return [maybeProject]; + if (maybeProject) { + return allProjectsForTargetName.includes(maybeProject) ? [maybeProject] : undefined; + } + + const { getYargsCompletions, help } = this.context.args.options; + if (!getYargsCompletions && !help) { + // Only issue the below error when not in help / completion mode. + throw new CommandModuleError( + 'Cannot determine project for command. ' + + 'Pass the project name as a command line argument or change the current working directory to a project directory.', + ); } } diff --git a/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts b/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts new file mode 100644 index 000000000000..e75e869fcb75 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts @@ -0,0 +1,40 @@ +import { join } from 'path'; +import { expectFileNotToExist, expectFileToExist } from '../../utils/fs'; +import { execAndWaitForOutputToMatch, ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; + +export default async function () { + const errorMessage = + 'Cannot determine project for command. ' + + 'Pass the project name as a command line argument or change the current working directory to a project directory'; + + // Delete root project + await updateJsonFile('angular.json', (workspaceJson) => { + delete workspaceJson.projects['test-project']; + }); + + await ng('generate', 'app', 'second-app', '--skip-install'); + await ng('generate', 'app', 'third-app', '--skip-install'); + + const startCwd = process.cwd(); + + try { + const { message } = await expectToFail(() => ng('build')); + if (!message.includes(errorMessage)) { + throw new Error(`Expected build to fail with: '${errorMessage}'.`); + } + + // Help should still work + execAndWaitForOutputToMatch('ng', ['build', '--help'], /--configuration/); + + // Yargs allows positional args to be passed as flags. Verify that in this case the project can be determined. + await ng('build', '--project=third-app', '--configuration=development'); + + process.chdir(join(startCwd, 'projects/second-app')); + await ng('build', '--configuration=development'); + } finally { + // Restore path + process.chdir(startCwd); + } +}