From 7f6237795c40825d9052e2970e995ab1b0f307ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Sat, 17 Dec 2022 12:04:30 +0100 Subject: [PATCH] fix(angular): handle not provided path when generating a component without the project option --- .../__snapshots__/component.spec.ts.snap | 14 ++++ .../angular-v14/lib/normalize-options.ts | 22 +++++- .../generators/component/component.spec.ts | 79 +++++++++++++++++++ .../component/lib/normalize-options.ts | 22 +++++- 4 files changed, 133 insertions(+), 4 deletions(-) diff --git a/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap b/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap index a8bb0263a0ac6..127b5a4e5e399 100644 --- a/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap +++ b/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap @@ -42,6 +42,20 @@ export class ExampleComponent { " `; +exports[`component Generator --path should infer project from path and generate component correctly 1`] = ` +"import { Component } from '@angular/core'; + +@Component({ + selector: 'example', + templateUrl: './example.component.html', + styleUrls: ['./example.component.css'] +}) +export class ExampleComponent { + +} +" +`; + exports[`component Generator secondary entry points should create the component correctly and export it in the entry point 1`] = ` "import { Component } from '@angular/core'; diff --git a/packages/angular/src/generators/component/angular-v14/lib/normalize-options.ts b/packages/angular/src/generators/component/angular-v14/lib/normalize-options.ts index 5aa0ced4cd27e..52e2c2259b1d4 100644 --- a/packages/angular/src/generators/component/angular-v14/lib/normalize-options.ts +++ b/packages/angular/src/generators/component/angular-v14/lib/normalize-options.ts @@ -2,7 +2,6 @@ import type { Tree } from '@nrwl/devkit'; import { createProjectGraphAsync, joinPathFragments, - readCachedProjectGraph, readProjectConfiguration, readWorkspaceConfiguration, } from '@nrwl/devkit'; @@ -15,7 +14,10 @@ import { async function findProjectFromOptions(options: Schema) { const projectGraph = await createProjectGraphAsync(); const projectRootMappings = createProjectRootMappings(projectGraph.nodes); - return findProjectForPath(options.path, projectRootMappings); + + // path can be undefined when running on the root of the workspace, we default to the root + // to handle standalone layouts + return findProjectForPath(options.path || '', projectRootMappings); } export async function normalizeOptions( @@ -26,6 +28,22 @@ export async function normalizeOptions( options.project ?? (await findProjectFromOptions(options)) ?? readWorkspaceConfiguration(tree).defaultProject; + + if (!project) { + // path is hidden, so if not provided we don't suggest setting it + if (!options.path) { + throw new Error( + 'No "project" was specified and "defaultProject" is not set in the workspace configuration. Please provide the "project" option and try again.' + ); + } + + // path was provided, so it's wrong and we should mention it + throw new Error( + 'The provided "path" is wrong and no "project" was specified and "defaultProject" is not set in the workspace configuration. ' + + 'Please provide a correct "path" or provide the "project" option instead and try again.' + ); + } + const { projectType, root, sourceRoot } = readProjectConfiguration( tree, project diff --git a/packages/angular/src/generators/component/component.spec.ts b/packages/angular/src/generators/component/component.spec.ts index 06e86e4bd8698..7c76194c46706 100644 --- a/packages/angular/src/generators/component/component.spec.ts +++ b/packages/angular/src/generators/component/component.spec.ts @@ -1,7 +1,16 @@ +import type { ProjectGraph } from '@nrwl/devkit'; import { addProjectConfiguration, writeJson } from '@nrwl/devkit'; import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import componentGenerator from './component'; +let projectGraph: ProjectGraph; +jest.mock('@nrwl/devkit', () => { + return { + ...jest.requireActual('@nrwl/devkit'), + createProjectGraphAsync: jest.fn().mockImplementation(() => projectGraph), + }; +}); + describe('component Generator', () => { it('should create the component correctly and export it in the entry point when "export=true"', async () => { // ARRANGE @@ -406,6 +415,46 @@ describe('component Generator', () => { ); }); + it('should infer project from path and generate component correctly', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + addProjectConfiguration(tree, 'lib1', { + projectType: 'library', + sourceRoot: 'libs/lib1/src', + root: 'libs/lib1', + }); + tree.write( + 'libs/lib1/src/lib/lib.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class LibModule {}` + ); + projectGraph = { + nodes: { + lib1: { name: 'lib1', type: 'lib', data: { root: 'libs/lib1' } }, + }, + dependencies: {}, + }; + + // ACT + await componentGenerator(tree, { + name: 'example', + path: 'libs/lib1/src/lib/mycomp', + }); + + // ASSERT + const componentSource = tree.read( + 'libs/lib1/src/lib/mycomp/example/example.component.ts', + 'utf-8' + ); + expect(componentSource).toMatchSnapshot(); + }); + it('should throw if the path specified is not under the project root', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); @@ -437,6 +486,36 @@ describe('component Generator', () => { }) ).rejects.toThrow(); }); + + it('should throw when path and projects are not provided and defaultProject is not set', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + addProjectConfiguration(tree, 'lib1', { + projectType: 'library', + sourceRoot: 'libs/lib1/src', + root: 'libs/lib1', + }); + tree.write( + 'libs/lib1/src/lib/lib.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class LibModule {}` + ); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); + + // ACT & ASSERT + await expect( + componentGenerator(tree, { + name: 'example', + export: false, + }) + ).rejects.toThrow(); + }); }); describe('--module', () => { diff --git a/packages/angular/src/generators/component/lib/normalize-options.ts b/packages/angular/src/generators/component/lib/normalize-options.ts index 5aa0ced4cd27e..52e2c2259b1d4 100644 --- a/packages/angular/src/generators/component/lib/normalize-options.ts +++ b/packages/angular/src/generators/component/lib/normalize-options.ts @@ -2,7 +2,6 @@ import type { Tree } from '@nrwl/devkit'; import { createProjectGraphAsync, joinPathFragments, - readCachedProjectGraph, readProjectConfiguration, readWorkspaceConfiguration, } from '@nrwl/devkit'; @@ -15,7 +14,10 @@ import { async function findProjectFromOptions(options: Schema) { const projectGraph = await createProjectGraphAsync(); const projectRootMappings = createProjectRootMappings(projectGraph.nodes); - return findProjectForPath(options.path, projectRootMappings); + + // path can be undefined when running on the root of the workspace, we default to the root + // to handle standalone layouts + return findProjectForPath(options.path || '', projectRootMappings); } export async function normalizeOptions( @@ -26,6 +28,22 @@ export async function normalizeOptions( options.project ?? (await findProjectFromOptions(options)) ?? readWorkspaceConfiguration(tree).defaultProject; + + if (!project) { + // path is hidden, so if not provided we don't suggest setting it + if (!options.path) { + throw new Error( + 'No "project" was specified and "defaultProject" is not set in the workspace configuration. Please provide the "project" option and try again.' + ); + } + + // path was provided, so it's wrong and we should mention it + throw new Error( + 'The provided "path" is wrong and no "project" was specified and "defaultProject" is not set in the workspace configuration. ' + + 'Please provide a correct "path" or provide the "project" option instead and try again.' + ); + } + const { projectType, root, sourceRoot } = readProjectConfiguration( tree, project