From c777f8b63eab7b5f5fa22194a5d12871caebd997 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 6 Mar 2023 08:24:35 +0100 Subject: [PATCH] Add Angular Builder Codemods --- code/frameworks/angular/README.md | 176 ++++++++++++++++-- code/jest.init.base.ts | 4 +- .../angular-builders.test.ts.snap | 6 + .../angular-builders-multiproject.test.ts | 132 +++++++++++++ .../fixes/angular-builders-multiproject.ts | 66 +++++++ .../fixes/angular-builders.test.ts | 135 ++++++++++++++ .../src/automigrate/fixes/angular-builders.ts | 106 +++++++++++ code/lib/cli/src/automigrate/fixes/index.ts | 4 + code/lib/cli/src/detect.test.ts | 1 + code/lib/cli/src/detect.ts | 6 +- .../lib/cli/src/generators/ANGULAR/helpers.ts | 18 ++ code/lib/cli/src/helpers.ts | 4 + 12 files changed, 637 insertions(+), 21 deletions(-) create mode 100644 code/lib/cli/src/automigrate/fixes/__snapshots__/angular-builders.test.ts.snap create mode 100644 code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.test.ts create mode 100644 code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts create mode 100644 code/lib/cli/src/automigrate/fixes/angular-builders.test.ts create mode 100644 code/lib/cli/src/automigrate/fixes/angular-builders.ts diff --git a/code/frameworks/angular/README.md b/code/frameworks/angular/README.md index 8ef91c279af6..356d7c66e694 100644 --- a/code/frameworks/angular/README.md +++ b/code/frameworks/angular/README.md @@ -2,9 +2,16 @@ - [Storybook for Angular](#storybook-for-angular) - [Getting Started](#getting-started) - - [Setup Compodoc](#setup-compodoc) - - [Support for multi-project workspace](#support-for-multi-project-workspace) + - [Setup Storybook for your Angular projects](#setup-storybook-for-your-angular-projects) - [Run Storybook](#run-storybook) + - [Setup Compodoc](#setup-compodoc) + - [Automatic setup](#automatic-setup) + - [Manual setup](#manual-setup) + - [FAQ](#faq) + - [How do I migrate to a Angular Storybook builder?](#how-do-i-migrate-to-a-angular-storybook-builder) + - [Do you have only one Angular project in your workspace?](#do-you-have-only-one-angular-project-in-your-workspace) + - [Adjust your `package.json`](#adjust-your-packagejson) + - [I have multiple projects in my Angular workspace](#i-have-multiple-projects-in-my-angular-workspace) Storybook for Angular is a UI development environment for your Angular components. With it, you can visualize different states of your UI components and develop them interactively. @@ -21,15 +28,9 @@ cd my-angular-app npx storybook init ``` -### Setup Compodoc +## Setup Storybook for your Angular projects -When installing, you will be given the option to set up Compodoc, which is a tool for creating documentation for Angular projects. - -You can include JSDoc comments above components, directives, and other parts of your Angular code to include documentation for those elements. Compodoc uses these comments to generate documentation for your application. In Storybook, it is useful to add explanatory comments above @Inputs and @Outputs, since these are the main elements that Storybook displays in its user interface. The @Inputs and @Outputs are the elements that you can interact with in Storybook, such as controls. - -## Support for multi-project workspace - -Storybook supports Angular multi-project workspace. You can setup Storybook for each project in the workspace. When running `npx storybook init` you will be asked for which project Storybook should be set up. Essentially, during initialization, the `angular.json` will be edited to add the Storybook configuration for the selected project. The configuration looks approximately like this: +Storybook supports Angular multi-project workspace. You can setup Storybook for each project in the workspace. When running `npx storybook init` you will be asked for which project Storybook should be set up. Essentially, during initialization, the `.storybook` folder will be created and the `angular.json` will be edited to add the Storybook configuration for the selected project. The configuration looks approximately like this: ```json // angular.json @@ -44,10 +45,14 @@ Storybook supports Angular multi-project workspace. You can setup Storybook for "storybook": { "builder": "@storybook/angular:start-storybook", "options": { + // the path to the storybook config directory "configDir": ".storybook", + // the build target of your project "browserTarget": "your-project:build", - "compodoc": false, + // the port you want to start Storybook on "port": 6006 + // further options are available and can be found in + // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/start-storybook/schema.json } }, "build-storybook": { @@ -55,8 +60,9 @@ Storybook supports Angular multi-project workspace. You can setup Storybook for "options": { "configDir": ".storybook", "browserTarget": "your-project:build", - "compodoc": false, "outputDir": "dist/storybook/your-project" + // further options are available and can be found in + // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/build-storybook/schema.json } } } @@ -70,19 +76,161 @@ Storybook supports Angular multi-project workspace. You can setup Storybook for To run Storybook for a particular project, please run: ```sh -ng run your-project:storybook +ng run :storybook ``` To build Storybook, run: ```sh -ng run your-project:build-storybook +ng run :build-storybook ``` You will find the output in `dist/storybook/your-project`. For more information visit: [storybook.js.org](https://storybook.js.org) +## Setup Compodoc + +You can include JSDoc comments above components, directives, and other parts of your Angular code to include documentation for those elements. Compodoc uses these comments to generate documentation for your application. In Storybook, it is useful to add explanatory comments above @Inputs and @Outputs, since these are the main elements that Storybook displays in its user interface. The @Inputs and @Outputs are the elements that you can interact with in Storybook, such as controls. + +### Automatic setup + +When installing Storybook via `sb init`, you will be given the option to set up Compodoc automatically. + +### Manual setup + +If you have already installed Storybook, you can set up Compodoc manually. + +Install the following dependencies: + +```sh +npm i -D @compodoc/compodoc +``` + +Add the following option to your to the Storybook Builder: + +```json +{ + ... + "projects": { + ... + "your-project": { + ... + "architect": { + ... + "storybook": { + "builder": "@storybook/angular:start-storybook", + "options": { + ... + "compodoc": true, + "compodocArgs": [ + "-e", + "json", + "-d", + // Where to store the generated documentation. It's usually the root of your Angular project. It's not necessarily the root of your Angular Workspace! + "." + ], + } + }, + "build-storybook": { + "builder": "@storybook/angular:build-storybook", + "options": { + ... + "compodoc": true, + "compodocArgs": [ + "-e", + "json", + "-d", + "." + ], + } + } + } + } + } +} +``` + +Go to your `.storybook/preview.js` and add the following: + +```js +import { setCompodocJson } from '@storybook/addon-docs/angular'; +import docJson from '../documentation.json'; +setCompodocJson(docJson); + +const preview: Preview = { + ... +}; + +export default preview; +``` + +## FAQ + +### How do I migrate to a Angular Storybook builder? + +The Storybook [Angular builder](https://angular.io/guide/glossary#builder) is a new way to run Storybook in an Angular workspace. It is a drop-in replacement for running `storybook dev` and `storybook build` directly. + +#### Do you have only one Angular project in your workspace? + +In this case go to your `angular.json` and add `storybook` and `build-storybook` entries in `architect` section of your project like shown above. + +##### Adjust your `package.json` + +Go to your `package.json` and adjust your script section. Usually, it will look like this: + +```json +{ + "scripts": { + "storybook": "start-storybook -p 6006", // or `storybook dev -p 6006` + "build-storybook": "build-storybook" // or `storybook build` + } +} +``` + +Now, you can run Storybook with `ng run :storybook` and build it with `ng run :build-storybook`. Adjust the scripts in your `package.json` accordingly. + +```json +{ + "scripts": { + "storybook": "ng run :storybook", // or `storybook dev -p 6006` + "build-storybook": "ng run :build-storybook" // or `storybook build` + } +} +``` + +Also remove the compodoc part in your script section if you have set it up previously. +It is now built-in in `@storybook/angular` and you don't have to call it explicitly: + +```json +{ + "scripts": { + "docs:json": "compodoc -p tsconfig.json -e json -d ./documentation", + "storybook": "npm run docs:json && start-storybook -p 6006", + "build-storybook": "npm run docs:json && build-storybook" + } +} +``` + +Change it to: + +```json +{ + "scripts": { + "storybook": "ng run :storybook", + "build-storybook": "ng run :build-storybook" + } +} +``` + +#### I have multiple projects in my Angular workspace + +In this case you have to adjust your `angular.json` and `package.json` as described above for each project in which you want to use Storybook. Please note, that each project should have a dedicated `.storybook` folder, which should be placed in the root of the project. + +You can run `npx sb init` sequentially for each project to setup Storybook for each of them to automatically create the `.storybook` folder and create the necessary configuration in your `angular.json`. + +You can then use [Storybook composition](https://storybook.js.org/docs/angular/sharing/storybook-composition) to composite multiple Storybooks into one. + --- Storybook also comes with a lot of [addons](https://storybook.js.org/addons) and a great API to customize as you wish. diff --git a/code/jest.init.base.ts b/code/jest.init.base.ts index 682b72bb0397..dc2d2b9c500b 100644 --- a/code/jest.init.base.ts +++ b/code/jest.init.base.ts @@ -17,8 +17,8 @@ const localStorageMock = { setItem: jest.fn().mockName('setItem'), clear: jest.fn().mockName('clear'), }; -// @ts-expect-error (Converted from ts-ignore) -global.localStorage = localStorageMock; + +Object.defineProperty(global, 'localStorage', { value: localStorageMock, writable: true }); /* Fail tests on PropType warnings This allows us to throw an error in tests environments when there are prop-type warnings. diff --git a/code/lib/cli/src/automigrate/fixes/__snapshots__/angular-builders.test.ts.snap b/code/lib/cli/src/automigrate/fixes/__snapshots__/angular-builders.test.ts.snap new file mode 100644 index 000000000000..d24d6968b01d --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/__snapshots__/angular-builders.test.ts.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`is not Nx project angular builders Angular < 14.0.0 should throw an Error 1`] = ` +"❌ Your project uses Angular < 14.0.0. Storybook 7.0 for Angular requires Angular 14.0.0 or higher. +Please upgrade your Angular version to at least version 14.0.0 to use Storybook 7.0 in your project." +`; diff --git a/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.test.ts b/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.test.ts new file mode 100644 index 000000000000..b376cca15a30 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.test.ts @@ -0,0 +1,132 @@ +import type { StorybookConfig } from '@storybook/types'; +import type { PackageJson } from '../../js-package-manager'; +import { makePackageManager, mockStorybookData } from '../helpers/testing-helpers'; +import { angularBuildersMultiproject } from './angular-builders-multiproject'; +import * as helpers from '../../helpers'; +import * as angularHelpers from '../../generators/ANGULAR/helpers'; + +const checkAngularBuilders = async ({ + packageJson, + main: mainConfig = {}, + storybookVersion = '7.0.0', +}: { + packageJson: PackageJson; + main?: Partial & Record; + storybookVersion?: string; +}) => { + mockStorybookData({ mainConfig, storybookVersion }); + + // mock file system (look at eslint plugin test) + + return angularBuildersMultiproject.check({ + packageManager: makePackageManager(packageJson), + }); +}; + +jest.mock('../../helpers', () => ({ + ...jest.requireActual('../../helpers'), + isNxProject: jest.fn(), +})); + +jest.mock('../../generators/ANGULAR/helpers', () => ({ + ...jest.requireActual('../../generators/ANGULAR/helpers'), + AngularJSON: jest.fn(), +})); + +describe('is Nx project', () => { + beforeEach(() => { + (helpers.isNxProject as any as jest.SpyInstance).mockReturnValue(true); + }); + + it('should return null', async () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0' }, + }; + + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); +}); + +describe('is not Nx project', () => { + beforeEach(() => { + (helpers.isNxProject as any as jest.SpyInstance).mockReturnValue(false); + }); + + describe('angular builders', () => { + afterEach(jest.restoreAllMocks); + + describe('Angular not found', () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0' }, + }; + + it('should return null', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); + }); + + describe('Angular < 14.0.0', () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0', '@angular/core': '^12.0.0' }, + }; + + it('should return null', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); + }); + + describe('Angular >= 14.0.0', () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0', '@angular/core': '^15.0.0' }, + }; + + describe('has one Storybook builder defined', () => { + beforeEach(() => { + // Mock AngularJSON.constructor + (angularHelpers.AngularJSON as jest.Mock).mockImplementation(() => ({ + hasStorybookBuilder: true, + })); + }); + + it('should return null', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); + }); + + describe('has one project', () => { + beforeEach(() => { + // Mock AngularJSON.constructor + (angularHelpers.AngularJSON as jest.Mock).mockImplementation(() => ({ + hasStorybookBuilder: false, + projects: { + project1: { root: 'project1', architect: {} }, + }, + rootProject: 'project1', + })); + }); + + it('should return null', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); + }); + + describe('has multiple projects without root project defined', () => { + beforeEach(() => { + // Mock AngularJSON.constructor + (angularHelpers.AngularJSON as jest.Mock).mockImplementation(() => ({ + hasStorybookBuilder: false, + projects: { + project1: { root: 'project1', architect: {} }, + project2: { root: 'project2', architect: {} }, + }, + rootProject: null, + })); + }); + + it('should return an empty object', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toMatchObject({}); + }); + }); + }); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts b/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts new file mode 100644 index 000000000000..14d767e1ca59 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts @@ -0,0 +1,66 @@ +import { dedent } from 'ts-dedent'; +import semver from 'semver'; +import chalk from 'chalk'; +import type { Fix } from '../types'; +import { isNxProject } from '../../helpers'; +import { AngularJSON } from '../../generators/ANGULAR/helpers'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface AngularBuildersMultiprojectRunOptions {} + +export const angularBuildersMultiproject: Fix = { + id: 'angular-builders-multiproject', + promptOnly: true, + + async check({ packageManager, configDir }) { + const packageJSON = packageManager.retrievePackageJson(); + + // Skip in case of NX + if (isNxProject(packageJSON)) { + return null; + } + const allDependencies = packageManager.getAllDependencies(); + + const angularVersion = allDependencies['@angular/core']; + const angularCoerced = semver.coerce(angularVersion)?.version; + + // skip non-angular projects + if (!angularCoerced) { + return null; + } + + // Is Angular version lower than 14? -> throw an error (only supports ng 14) + if (semver.lt(angularCoerced, '14.0.0')) { + return null; + } + + const angularJSON = new AngularJSON(); + + const { hasStorybookBuilder } = angularJSON; + + // skip if workspace has already one or more Storybook builder + if (hasStorybookBuilder) { + return null; + } + + if (angularJSON.rootProject || Object.keys(angularJSON.projects).length === 1) { + return null; + } + + return {}; + }, + + prompt() { + return dedent` + In Storybook 6.4 we have deprecated calling Storybook directly (npm run storybook) for Angular. In Storybook 7.0, we've removed it entirely. Instead you have to set up the Storybook builder in your ${chalk.yellow( + 'angular.json' + )} and execute ${chalk.yellow('ng run :storybook')} to start Storybook. + + ❌ Your Angular workspace uses multiple projects defined in the ${chalk.yellow( + 'angular.json' + )} file and we were not able to detect a root project. Therefore we are not able to automigrate to use Angular Storybook builder. Instead, please visit ${chalk.yellow( + 'https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular' + )} to do the migration manually. + `; + }, +}; diff --git a/code/lib/cli/src/automigrate/fixes/angular-builders.test.ts b/code/lib/cli/src/automigrate/fixes/angular-builders.test.ts new file mode 100644 index 000000000000..d7505da9e567 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/angular-builders.test.ts @@ -0,0 +1,135 @@ +import type { StorybookConfig } from '@storybook/types'; +import type { PackageJson } from '../../js-package-manager'; +import { makePackageManager, mockStorybookData } from '../helpers/testing-helpers'; +import { angularBuilders } from './angular-builders'; +import * as helpers from '../../helpers'; +import * as angularHelpers from '../../generators/ANGULAR/helpers'; + +const checkAngularBuilders = async ({ + packageJson, + main: mainConfig = {}, + storybookVersion = '7.0.0', +}: { + packageJson: PackageJson; + main?: Partial & Record; + storybookVersion?: string; +}) => { + mockStorybookData({ mainConfig, storybookVersion }); + + // mock file system (look at eslint plugin test) + + return angularBuilders.check({ + packageManager: makePackageManager(packageJson), + }); +}; + +jest.mock('../../helpers', () => ({ + ...jest.requireActual('../../helpers'), + isNxProject: jest.fn(), +})); + +jest.mock('../../generators/ANGULAR/helpers', () => ({ + ...jest.requireActual('../../generators/ANGULAR/helpers'), + AngularJSON: jest.fn(), +})); + +describe('is Nx project', () => { + beforeEach(() => { + (helpers.isNxProject as any as jest.SpyInstance).mockReturnValue(true); + }); + + it('should return null', async () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0' }, + }; + + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); +}); + +describe('is not Nx project', () => { + beforeEach(() => { + (helpers.isNxProject as any as jest.SpyInstance).mockReturnValue(false); + }); + + describe('angular builders', () => { + afterEach(jest.restoreAllMocks); + + describe('Angular not found', () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0' }, + }; + + it('should return null', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); + }); + + describe('Angular < 14.0.0', () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0', '@angular/core': '^12.0.0' }, + }; + + it('should throw an Error', async () => { + await expect(checkAngularBuilders({ packageJson })).rejects.toThrowErrorMatchingSnapshot(); + }); + }); + + describe('Angular >= 14.0.0', () => { + const packageJson = { + dependencies: { '@storybook/angular': '^7.0.0-alpha.0', '@angular/core': '^15.0.0' }, + }; + + describe('has one Storybook builder defined', () => { + beforeEach(() => { + // Mock AngularJSON.constructor + (angularHelpers.AngularJSON as jest.Mock).mockImplementation(() => ({ + hasStorybookBuilder: true, + })); + }); + + it('should return null', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); + }); + + describe('has multiple projects without root project defined', () => { + beforeEach(() => { + // Mock AngularJSON.constructor + (angularHelpers.AngularJSON as jest.Mock).mockImplementation(() => ({ + hasStorybookBuilder: false, + projects: { + project1: { root: 'project1', architect: {} }, + project2: { root: 'project2', architect: {} }, + }, + rootProject: null, + })); + }); + + it('should return null', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toBeNull(); + }); + }); + + describe('has one project', () => { + beforeEach(() => { + // Mock AngularJSON.constructor + (angularHelpers.AngularJSON as jest.Mock).mockImplementation(() => ({ + hasStorybookBuilder: false, + projects: { + project1: { root: 'project1', architect: {} }, + }, + rootProject: 'project1', + })); + }); + + it('should proceed and return data', async () => { + await expect(checkAngularBuilders({ packageJson })).resolves.toMatchObject({ + mainConfig: {}, + packageManager: {}, + }); + }); + }); + }); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/angular-builders.ts b/code/lib/cli/src/automigrate/fixes/angular-builders.ts new file mode 100644 index 000000000000..c50f3968c93f --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/angular-builders.ts @@ -0,0 +1,106 @@ +import { dedent } from 'ts-dedent'; +import semver from 'semver'; +import type { StorybookConfig } from '@storybook/types'; +import chalk from 'chalk'; +import prompts from 'prompts'; +import type { Fix } from '../types'; +import { getStorybookData } from '../helpers/mainConfigFile'; +import { isNxProject } from '../../helpers'; +import { AngularJSON } from '../../generators/ANGULAR/helpers'; +import type { JsPackageManager } from '../../js-package-manager'; + +interface AngularBuildersRunOptions { + mainConfig: StorybookConfig; + packageManager: JsPackageManager; +} + +export const angularBuilders: Fix = { + id: 'angular-builders', + + async check({ packageManager, configDir }) { + const packageJSON = packageManager.retrievePackageJson(); + + // Skip in case of NX + if (isNxProject(packageJSON)) { + return null; + } + const allDependencies = packageManager.getAllDependencies(); + + const angularVersion = allDependencies['@angular/core']; + const angularCoerced = semver.coerce(angularVersion)?.version; + + // skip non-angular projects + if (!angularCoerced) { + return null; + } + + // Is Angular version lower than 14? -> throw an error (only supports ng 14) + if (semver.lt(angularCoerced, '14.0.0')) { + throw new Error(dedent` + ❌ Your project uses Angular < 14.0.0. Storybook 7.0 for Angular requires Angular 14.0.0 or higher. + Please upgrade your Angular version to at least version 14.0.0 to use Storybook 7.0 in your project. + `); + } + + const angularJSON = new AngularJSON(); + + const { hasStorybookBuilder } = angularJSON; + + // skip if workspace has already one or more Storybook builder + if (hasStorybookBuilder) { + return null; + } + + if (!angularJSON.rootProject && Object.keys(angularJSON.projects).length > 1) { + return null; + } + + const { mainConfig } = await getStorybookData({ configDir, packageManager }); + + return { + mainConfig, + packageManager, + }; + }, + + prompt() { + return dedent` + We have detected that your project does not use the Storybook Angular builder yet. In Storybook 6.4 we have deprecated calling Storybook directly (npm run storybook) for Angular. In Storybook 7.0, we've removed it entirely. + + In order to use the Storybook Angular builder, we need to add a few entries to your angular.json file. Additionally, we will add the @compodoc/compodoc package to your devDependencies if you want and we will add a few scripts to your package.json file. + + Also feel free to remove the Compodoc script from your package.json file if you don't use it apart from Storybook anymore. Storybook uses Compodoc internally and you don't have to call in separately anymore. + + Read more about the Angular builder here: ${chalk.yellow( + 'https://storybook.js.org/docs/angular/configure/storybook-builders' + )} + `; + }, + + async run({ result }) { + const angularJSON = new AngularJSON(); + const { packageManager } = result; + + const { useCompoDoc } = await prompts({ + type: 'confirm', + name: 'useCompoDoc', + message: 'Have you set up compodoc in Storybook previously?', + }); + + const angularProjectName = await angularJSON.getProjectName(); + + angularJSON.addStorybookEntries({ + angularProjectName, + storybookFolder: '.storybook', + useCompodoc: useCompoDoc, + root: '.', + }); + + angularJSON.write(); + + packageManager.addScripts({ + storybook: `ng run ${angularProjectName}:storybook`, + 'build-storybook': `ng run ${angularProjectName}:build-storybook`, + }); + }, +}; diff --git a/code/lib/cli/src/automigrate/fixes/index.ts b/code/lib/cli/src/automigrate/fixes/index.ts index 7bf5b0e4c121..3379f7f18e07 100644 --- a/code/lib/cli/src/automigrate/fixes/index.ts +++ b/code/lib/cli/src/automigrate/fixes/index.ts @@ -17,6 +17,8 @@ import { addReact } from './add-react'; import { nodeJsRequirement } from './nodejs-requirement'; import { missingBabelRc } from './missing-babelrc'; import { bareMdxStoriesGlob } from './bare-mdx-stories-glob'; +import { angularBuilders } from './angular-builders'; +import { angularBuildersMultiproject } from './angular-builders-multiproject'; export * from '../types'; @@ -38,6 +40,8 @@ export const allFixes: Fix[] = [ autodocsTrue, addReact, missingBabelRc, + angularBuildersMultiproject, + angularBuilders, ]; export const initFixes: Fix[] = [missingBabelRc, eslintPlugin]; diff --git a/code/lib/cli/src/detect.test.ts b/code/lib/cli/src/detect.test.ts index 203cfa21c287..9f898a377381 100644 --- a/code/lib/cli/src/detect.test.ts +++ b/code/lib/cli/src/detect.test.ts @@ -6,6 +6,7 @@ import { ProjectType, SUPPORTED_RENDERERS, SupportedLanguage } from './project_t import type { PackageJsonWithMaybeDeps } from './js-package-manager'; jest.mock('./helpers', () => ({ + isNxProject: jest.fn(), getBowerJson: jest.fn(), })); diff --git a/code/lib/cli/src/detect.ts b/code/lib/cli/src/detect.ts index c21c0481263f..74db230922ad 100644 --- a/code/lib/cli/src/detect.ts +++ b/code/lib/cli/src/detect.ts @@ -14,7 +14,7 @@ import { unsupportedTemplate, CoreBuilder, } from './project_types'; -import { getBowerJson, paddedLog } from './helpers'; +import { getBowerJson, isNxProject, paddedLog } from './helpers'; import type { JsPackageManager, PackageJson, PackageJsonWithMaybeDeps } from './js-package-manager'; import { detectWebpack } from './detect-webpack'; @@ -228,7 +228,3 @@ export function detect( return detectFrameworkPreset(packageJson || bowerJson); } - -function isNxProject(packageJSON: PackageJson) { - return !!packageJSON.devDependencies?.nx || fs.existsSync('nx.json'); -} diff --git a/code/lib/cli/src/generators/ANGULAR/helpers.ts b/code/lib/cli/src/generators/ANGULAR/helpers.ts index 967272d2b439..f4fbf6c965d2 100644 --- a/code/lib/cli/src/generators/ANGULAR/helpers.ts +++ b/code/lib/cli/src/generators/ANGULAR/helpers.ts @@ -52,6 +52,24 @@ export class AngularJSON { }); } + get hasStorybookBuilder() { + return Object.keys(this.projects).some((projectName) => { + const { architect } = this.projects[projectName]; + return Object.keys(architect).some((key) => { + return architect[key].builder === '@storybook/angular:start-storybook'; + }); + }); + } + + get rootProject() { + const rootProjectName = Object.keys(this.projects).find((projectName) => { + const { root } = this.projects[projectName]; + return root === '' || root === '.'; + }); + + return rootProjectName ? this.projects[rootProjectName] : null; + } + getProjectSettingsByName(projectName: string) { return this.projects[projectName]; } diff --git a/code/lib/cli/src/helpers.ts b/code/lib/cli/src/helpers.ts index 004e25f4e619..ec4c07b7999c 100644 --- a/code/lib/cli/src/helpers.ts +++ b/code/lib/cli/src/helpers.ts @@ -265,3 +265,7 @@ export function getStorybookVersionSpecifier(packageJson: PackageJsonWithDepsAnd return allDeps[storybookPackage]; } + +export function isNxProject(packageJSON: PackageJson) { + return !!packageJSON.devDependencies?.nx || fs.existsSync('nx.json'); +}