From c1fcb3c3a110bdff4caf6d979de35818ccb8ba73 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 7 Jun 2022 15:17:10 +0000 Subject: [PATCH 1/2] fix(@angular/cli): provide actionable error when project cannot be determined When the workspace has multiple projects and we the project to use cannot be determined from the current working directory, we now issue an actionable error message. --- .../architect-command-module.ts | 16 ++++++--- .../project-cannot-be-determined-by-cwd.ts | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts 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..9f508688a5ec 100644 --- a/packages/angular/cli/src/command-builder/architect-command-module.ts +++ b/packages/angular/cli/src/command-builder/architect-command-module.ts @@ -104,7 +104,6 @@ export abstract class ArchitectCommandModule if (projectName) { return workspace.projects.has(projectName) ? projectName : undefined; } - const target = this.getArchitectTarget(); const projectFromTarget = this.getProjectNamesByTarget(target); @@ -114,8 +113,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 +134,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..938a00105c64 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts @@ -0,0 +1,36 @@ +import { join } from 'path'; +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/); + + process.chdir(join(startCwd, 'projects/second-app')); + await ng('build', '--configuration=development'); + } finally { + // Restore path + process.chdir(startCwd); + } +} From b14a187399e57fe52230d8e2f6ca512030f11a19 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 8 Jun 2022 08:10:15 +0000 Subject: [PATCH 2/2] fix(@angular/cli): handle project being passed as a flag Yargs allows passing using positional arguments as flags. This we should handle this when retrieving the project. Closes #23291 --- .../command-builder/architect-command-module.ts | 16 +++++++++------- .../project-cannot-be-determined-by-cwd.ts | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) 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 9f508688a5ec..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,16 +94,18 @@ 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(); const projectFromTarget = this.getProjectNamesByTarget(target); 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 index 938a00105c64..e75e869fcb75 100644 --- 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 @@ -1,4 +1,5 @@ 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'; @@ -27,6 +28,9 @@ export default async function () { // 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 {