diff --git a/packages/angular/src/schematics/component-cypress-spec/component-cypress-spec.ts b/packages/angular/src/schematics/component-cypress-spec/component-cypress-spec.ts index 93d16545b4..350910df37 100644 --- a/packages/angular/src/schematics/component-cypress-spec/component-cypress-spec.ts +++ b/packages/angular/src/schematics/component-cypress-spec/component-cypress-spec.ts @@ -22,6 +22,7 @@ import { getKnobType, } from '../component-story/component-story'; import { applyWithSkipExisting } from '@nrwl/workspace/src/utils/ast-utils'; +import { join, normalize } from '@angular-devkit/core'; export default function (schema: CreateComponentSpecFileSchema): Rule { return chain([createComponentSpecFile(schema)]); @@ -45,8 +46,11 @@ export function createComponentSpecFile({ return (tree: Tree, context: SchematicContext): Rule => { const e2eLibIntegrationFolderPath = getProjectConfig(tree, projectName + '-e2e').sourceRoot + '/integration'; - const fullComponentPath = - libPath + '/' + componentPath + '/' + componentFileName + '.ts'; + const fullComponentPath = join( + normalize(libPath), + componentPath, + `${componentFileName}.ts` + ); const props = getInputPropertyDeclarations(tree, fullComponentPath).map( (node) => { const decoratorContent = findNodes( diff --git a/packages/angular/src/schematics/component-story/component-story.ts b/packages/angular/src/schematics/component-story/component-story.ts index 855b7d54b6..8ab86fc2f0 100644 --- a/packages/angular/src/schematics/component-story/component-story.ts +++ b/packages/angular/src/schematics/component-story/component-story.ts @@ -14,6 +14,7 @@ import { getSourceNodes, applyWithSkipExisting, } from '@nrwl/workspace/src/utils/ast-utils'; +import { join, normalize } from '@angular-devkit/core'; export interface CreateComponentStoriesFileSchema { libPath: string; @@ -35,7 +36,7 @@ export function createComponentStoriesFile({ return (tree: Tree, context: SchematicContext): Rule => { const props = getInputDescriptors( tree, - libPath + '/' + componentPath + '/' + componentFileName + '.ts' + join(normalize(libPath), componentPath, `${componentFileName}.ts`) ); return applyWithSkipExisting(url('./files'), [ template({ diff --git a/packages/angular/src/schematics/stories/stories.spec.ts b/packages/angular/src/schematics/stories/stories.spec.ts index ecd7be1491..416e08c9b2 100644 --- a/packages/angular/src/schematics/stories/stories.spec.ts +++ b/packages/angular/src/schematics/stories/stories.spec.ts @@ -156,29 +156,6 @@ export async function createTestUILib(libName: string): Promise { }), appTree ); - const modulePath = `libs/${libName}/src/lib/${libName}.module.ts`; - appTree.overwrite( - modulePath, - `import * as ButtonExports from './test-button/test-button.component'; - ${appTree.read(modulePath)}` - ); - appTree = await callRule( - externalSchematic('@schematics/angular', 'module', { - name: 'nested', - project: libName, - path: `libs/${libName}/src/lib`, - }), - appTree - ); - appTree = await callRule( - externalSchematic('@schematics/angular', 'component', { - name: 'nested-button', - project: libName, - module: 'nested', - path: `libs/${libName}/src/lib/nested`, - }), - appTree - ); appTree.overwrite( `libs/${libName}/src/lib/test-button/test-button.component.ts`, ` @@ -209,6 +186,69 @@ export class TestButtonComponent implements OnInit { `libs/${libName}/src/lib/test-button/test-button.component.html`, `` ); + + const modulePath = `libs/${libName}/src/lib/${libName}.module.ts`; + appTree.overwrite( + modulePath, + `import * as ButtonExports from './test-button/test-button.component'; + ${appTree.read(modulePath)}` + ); + + // create a module with component that gets exported in a barrel file + appTree = await callRule( + externalSchematic('@schematics/angular', 'module', { + name: 'barrel', + project: libName, + }), + appTree + ); + + appTree = await callRule( + externalSchematic('@schematics/angular', 'component', { + name: 'barrel-button', + project: libName, + path: `libs/${libName}/src/lib/barrel`, + module: 'barrel', + }), + appTree + ); + appTree.create( + `libs/${libName}/src/lib/barrel/barrel-button/index.ts`, + `export * from './barrel-button.component';` + ); + + appTree.overwrite( + `libs/${libName}/src/lib/barrel/barrel.module.ts`, + `import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { BarrelButtonComponent } from './barrel-button'; + +@NgModule({ + imports: [CommonModule], + declarations: [BarrelButtonComponent], +}) +export class BarrelModule {}` + ); + + // create another button in a nested subpath + appTree = await callRule( + externalSchematic('@schematics/angular', 'module', { + name: 'nested', + project: libName, + path: `libs/${libName}/src/lib`, + }), + appTree + ); + appTree = await callRule( + externalSchematic('@schematics/angular', 'component', { + name: 'nested-button', + project: libName, + module: 'nested', + path: `libs/${libName}/src/lib/nested`, + }), + appTree + ); + appTree = await callRule( externalSchematic('@schematics/angular', 'component', { name: 'test-other', @@ -216,6 +256,7 @@ export class TestButtonComponent implements OnInit { }), appTree ); + return appTree; } diff --git a/packages/angular/src/schematics/stories/stories.ts b/packages/angular/src/schematics/stories/stories.ts index 510b23d509..7b0f074e62 100644 --- a/packages/angular/src/schematics/stories/stories.ts +++ b/packages/angular/src/schematics/stories/stories.ts @@ -13,6 +13,8 @@ import { getTsSourceFile, getDecoratorMetadata } from '../../utils/ast-utils'; import { CreateComponentSpecFileSchema } from '../component-cypress-spec/component-cypress-spec'; import { CreateComponentStoriesFileSchema } from '../component-story/component-story'; import { stripIndents } from '@angular-devkit/core/src/utils/literals'; +import { directoryExists } from '@nrwl/workspace/src/utils/fileutils'; +import { join, normalize } from '@angular-devkit/core'; export interface StorybookStoriesSchema { name: string; @@ -39,8 +41,8 @@ export function createAllStories( moduleFilePaths.push(filePath); }); return chain( - moduleFilePaths.map((filePath) => { - const file = getTsSourceFile(tree, filePath); + moduleFilePaths.map((moduleFilePath) => { + const file = getTsSourceFile(tree, moduleFilePath); const ngModuleDecorators = getDecoratorMetadata( file, @@ -49,7 +51,7 @@ export function createAllStories( ); if (ngModuleDecorators.length === 0) { throw new SchematicsException( - `No @NgModule decorator in ${filePath}` + `No @NgModule decorator in ${moduleFilePath}` ); } const ngModuleDecorator = ngModuleDecorators[0]; @@ -66,7 +68,7 @@ export function createAllStories( }); if (!declarationsPropertyAssignment) { context.logger.warn( - stripIndents`No stories generated because there were no components declared in ${filePath}. Hint: you can always generate stories later with the 'nx generate @nrwl/angular:stories --name=${projectName}' command` + stripIndents`No stories generated because there were no components declared in ${moduleFilePath}. Hint: you can always generate stories later with the 'nx generate @nrwl/angular:stories --name=${projectName}' command` ); return noop(); } @@ -83,6 +85,12 @@ export function createAllStories( const imports = file.statements.filter( (statement) => statement.kind === SyntaxKind.ImportDeclaration ); + + const modulePath = moduleFilePath.substr( + 0, + moduleFilePath.lastIndexOf('/') + ); + const componentInfo = declaredComponents.map((componentName) => { try { const importStatement = imports.find((statement) => { @@ -106,11 +114,61 @@ export function createAllStories( .find((node) => node.kind === SyntaxKind.StringLiteral) .getText() .slice(1, -1); - const path = fullPath.slice(0, fullPath.lastIndexOf('/')); - const componentFileName = fullPath.slice( - fullPath.lastIndexOf('/') + 1 + + // if it is a directory, search recursively for the component + let fullCmpImportPath = moduleFilePath.slice( + 0, + moduleFilePath.lastIndexOf('/') ); - return { name: componentName, path, componentFileName }; + if (fullCmpImportPath.startsWith('/')) { + fullCmpImportPath = fullCmpImportPath.slice( + 1, + fullCmpImportPath.length + ); + } + + const componentImportPath = join( + normalize(fullCmpImportPath), + fullPath + ); + + const dir = tree.getDir(componentImportPath); + if (dir && dir.subfiles.length > 0) { + let path = null; + let componentFileName = null; + // search the fullPath for component declarations + tree.getDir(componentImportPath).visit((componentFilePath) => { + if (componentFilePath.endsWith('.ts')) { + const content = tree + .read(componentFilePath) + .toString('utf-8'); + if (content.indexOf(`class ${componentName}`) > -1) { + path = componentFilePath + .slice(0, componentFilePath.lastIndexOf('/')) + .replace(modulePath, '.'); + componentFileName = componentFilePath.slice( + componentFilePath.lastIndexOf('/') + 1, + componentFilePath.lastIndexOf('.') + ); + return; + } + } + }); + + if (path === null) { + throw new SchematicsException( + `Path to component ${componentName} couldn't be found. Please open an issue on https://github.com/nrwl/nx/issues.` + ); + } + + return { name: componentName, path, componentFileName }; + } else { + const path = fullPath.slice(0, fullPath.lastIndexOf('/')); + const componentFileName = fullPath.slice( + fullPath.lastIndexOf('/') + 1 + ); + return { name: componentName, path, componentFileName }; + } } catch (ex) { context.logger.warn( `Could not generate a story for ${componentName}. Error: ${ex}` @@ -119,7 +177,6 @@ export function createAllStories( } }); - const modulePath = filePath.substr(0, filePath.lastIndexOf('/')); return chain( componentInfo .filter((info) => info !== undefined) diff --git a/packages/angular/src/schematics/storybook-configuration/__snapshots__/configuration.spec.ts.snap b/packages/angular/src/schematics/storybook-configuration/__snapshots__/configuration.spec.ts.snap index 8f05944076..1c23638c73 100644 --- a/packages/angular/src/schematics/storybook-configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/angular/src/schematics/storybook-configuration/__snapshots__/configuration.spec.ts.snap @@ -22,6 +22,13 @@ Array [ "/libs/test-ui-lib/src/lib/test-button/test-button.component.spec.ts", "/libs/test-ui-lib/src/lib/test-button/test-button.component.ts", "/libs/test-ui-lib/src/lib/test-button/test-button.component.stories.ts", + "/libs/test-ui-lib/src/lib/barrel/barrel.module.ts", + "/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.css", + "/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.html", + "/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.spec.ts", + "/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.ts", + "/libs/test-ui-lib/src/lib/barrel/barrel-button/index.ts", + "/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.stories.ts", "/libs/test-ui-lib/src/lib/nested/nested.module.ts", "/libs/test-ui-lib/src/lib/nested/nested-button/nested-button.component.css", "/libs/test-ui-lib/src/lib/nested/nested-button/nested-button.component.html", @@ -50,6 +57,7 @@ Array [ "/apps/test-ui-lib-e2e/src/support/index.ts", "/apps/test-ui-lib-e2e/src/integration/test-button/test-button.component.spec.ts", "/apps/test-ui-lib-e2e/src/integration/test-other/test-other.component.spec.ts", + "/apps/test-ui-lib-e2e/src/integration/barrel-button/barrel-button.component.spec.ts", "/apps/test-ui-lib-e2e/src/integration/nested-button/nested-button.component.spec.ts", ] `;