import { addDependenciesToPackageJson, formatFiles, GeneratorCallback, logger, readNxJson, readProjectConfiguration, runTasksInSerial, Tree, } from '@nx/devkit'; import { initGenerator as jsInitGenerator } from '@nx/js'; import { cypressProjectGenerator } from '../cypress-project/cypress-project'; import { StorybookConfigureSchema } from './schema'; import { initGenerator } from '../init/init'; import { addAngularStorybookTarget, addBuildStorybookToCacheableOperations, addStaticTarget, addStorybookTarget, addStorybookToNamedInputs, addStorybookToTargetDefaults, configureTsProjectConfig, configureTsSolutionConfig, createProjectStorybookDir, createStorybookTsconfigFile, editTsconfigBaseJson, findMetroConfig, findNextConfig, findViteConfig, getE2EProjectName, projectIsRootProjectInStandaloneWorkspace, updateLintConfig, } from './lib/util-functions'; import { Linter } from '@nx/eslint'; import { findStorybookAndBuildTargetsAndCompiler, pleaseUpgrade, storybookMajorVersion, } from '../../utils/utilities'; import { coreJsVersion, nxVersion, storybookVersion, tsLibVersion, tsNodeVersion, } from '../../utils/versions'; import { interactionTestsDependencies } from './lib/interaction-testing.utils'; import { ensureDependencies } from './lib/ensure-dependencies'; import { editRootTsConfig } from './lib/edit-root-tsconfig'; export function configurationGenerator( tree: Tree, schema: StorybookConfigureSchema ) { return configurationGeneratorInternal(tree, { addPlugin: false, ...schema }); } export async function configurationGeneratorInternal( tree: Tree, rawSchema: StorybookConfigureSchema ) { if (storybookMajorVersion() === 6) { throw new Error(pleaseUpgrade()); } const schema = normalizeSchema(tree, rawSchema); const tasks: GeneratorCallback[] = []; const { projectType, targets, root } = readProjectConfiguration( tree, schema.project ); const { compiler } = findStorybookAndBuildTargetsAndCompiler(targets); const viteConfig = findViteConfig(tree, root); const viteConfigFilePath = viteConfig?.fullConfigPath; const viteConfigFileName = viteConfig?.viteConfigFileName; const nextConfigFilePath = findNextConfig(tree, root); const metroConfigFilePath = findMetroConfig(tree, root); if (viteConfigFilePath) { if (schema.uiFramework === '@storybook/react-webpack5') { logger.info( `Your project ${schema.project} uses Vite as a bundler. Nx will configure Storybook for this project to use Vite as well.` ); schema.uiFramework = '@storybook/react-vite'; } if (schema.uiFramework === '@storybook/web-components-webpack5') { logger.info( `Your project ${schema.project} uses Vite as a bundler. Nx will configure Storybook for this project to use Vite as well.` ); schema.uiFramework = '@storybook/web-components-vite'; } } if (nextConfigFilePath) { schema.uiFramework = '@storybook/nextjs'; } const jsInitTask = await jsInitGenerator(tree, { ...schema, skipFormat: true, }); tasks.push(jsInitTask); const initTask = await initGenerator(tree, { skipFormat: true, addPlugin: schema.addPlugin, }); tasks.push(initTask); tasks.push(ensureDependencies(tree, { uiFramework: schema.uiFramework })); editRootTsConfig(tree); const nxJson = readNxJson(tree); const hasPlugin = nxJson.plugins?.some((p) => typeof p === 'string' ? p === '@nx/storybook/plugin' : p.plugin === '@nx/storybook/plugin' ); const mainDir = !!nextConfigFilePath && projectType === 'application' ? 'components' : 'src'; const usesVite = !!viteConfigFilePath || schema.uiFramework?.endsWith('-vite'); const useReactNative = !!metroConfigFilePath; createProjectStorybookDir( tree, schema.project, schema.uiFramework, schema.js, schema.tsConfiguration, root, projectType, projectIsRootProjectInStandaloneWorkspace(root), schema.interactionTests, mainDir, !!nextConfigFilePath, compiler === 'swc', usesVite, viteConfigFilePath, hasPlugin, viteConfigFileName, useReactNative ); if (schema.uiFramework !== '@storybook/angular') { createStorybookTsconfigFile( tree, root, schema.uiFramework, projectIsRootProjectInStandaloneWorkspace(root), mainDir ); } configureTsProjectConfig(tree, schema); editTsconfigBaseJson(tree); configureTsSolutionConfig(tree, schema); updateLintConfig(tree, schema); addBuildStorybookToCacheableOperations(tree); addStorybookToNamedInputs(tree); if (!hasPlugin) { addStorybookToTargetDefaults(tree); } let devDeps = {}; if (!hasPlugin || schema.addExplicitTargets) { if (schema.uiFramework === '@storybook/angular') { addAngularStorybookTarget(tree, schema.project, schema.interactionTests); } else { addStorybookTarget( tree, schema.project, schema.uiFramework, schema.interactionTests ); } if (schema.configureStaticServe) { addStaticTarget(tree, schema); } } else { devDeps['storybook'] = storybookVersion; } // TODO(katerina): Nx 19 -> remove Cypress if (schema.configureCypress) { const e2eProject = await getE2EProjectName(tree, schema.project); if (!e2eProject) { const cypressTask = await cypressProjectGenerator(tree, { name: schema.project, js: schema.js, linter: schema.linter, directory: schema.cypressDirectory, standaloneConfig: schema.standaloneConfig, ciTargetName: schema.configureStaticServe ? 'static-storybook' : undefined, skipFormat: true, }); tasks.push(cypressTask); } else { logger.warn( `There is already an e2e project setup for ${schema.project}, called ${e2eProject}.` ); } } if (schema.tsConfiguration) { devDeps['ts-node'] = tsNodeVersion; } if (usesVite && !viteConfigFilePath) { devDeps['tslib'] = tsLibVersion; } if (schema.interactionTests) { devDeps = { ...devDeps, ...interactionTestsDependencies(), }; } if (schema.configureStaticServe) { devDeps['@nx/web'] = nxVersion; } if ( projectType !== 'application' && schema.uiFramework === '@storybook/react-webpack5' ) { devDeps['core-js'] = coreJsVersion; } if (schema.uiFramework?.endsWith('-vite') && !viteConfigFilePath) { // This means that the user has selected a Vite framework // but the project does not have Vite configuration. // We need to install the @nx/vite plugin in order to be able to use // the nxViteTsPaths plugin to register the tsconfig paths in Vite. devDeps['@nx/vite'] = nxVersion; } tasks.push(addDependenciesToPackageJson(tree, {}, devDeps)); if (!schema.skipFormat) { await formatFiles(tree); } return runTasksInSerial(...tasks); } function normalizeSchema( tree: Tree, schema: StorybookConfigureSchema ): StorybookConfigureSchema { const nxJson = readNxJson(tree); const addPlugin = process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; const defaults = { interactionTests: true, linter: Linter.EsLint, js: false, tsConfiguration: true, addPlugin, }; return { ...defaults, ...schema, }; } export default configurationGenerator;