UNPKG

@nx/storybook

Version:

The Nx Plugin for Storybook contains executors and generators for allowing your workspace to use the powerful Storybook integration testing & documenting capabilities.

446 lines (445 loc) 22.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.onlyShowGuide = onlyShowGuide; exports.writeFile = writeFile; exports.removePathResolvesFromNextConfig = removePathResolvesFromNextConfig; exports.removeViteTsConfigPathsPlugin = removeViteTsConfigPathsPlugin; exports.addViteConfigFilePathInFrameworkOptions = addViteConfigFilePathInFrameworkOptions; exports.normalizeViteConfigFilePathWithTree = normalizeViteConfigFilePathWithTree; exports.removeTypecastFromMainTs = removeTypecastFromMainTs; exports.removeUiFrameworkFromProjectJson = removeUiFrameworkFromProjectJson; exports.changeCoreCommonImportToFramework = changeCoreCommonImportToFramework; exports.getAllStorybookInfo = getAllStorybookInfo; exports.prepareFiles = prepareFiles; exports.handleMigrationResult = handleMigrationResult; exports.checkStorybookInstalled = checkStorybookInstalled; exports.checkWebComponentsInstalled = checkWebComponentsInstalled; exports.afterMigration = afterMigration; exports.logResult = logResult; const devkit_1 = require("@nx/devkit"); const executor_options_utils_1 = require("@nx/devkit/src/generators/executor-options-utils"); const tsquery_1 = require("@phenomnomnominal/tsquery"); const fs = require("fs"); const fileutils_1 = require("nx/src/utils/fileutils"); const fs_1 = require("fs"); const path_1 = require("path"); function onlyShowGuide(storybookProjects) { const pm = (0, devkit_1.getPackageManagerCommand)(); devkit_1.output.log({ title: 'Storybook 7 Migration Guide', bodyLines: [ `You can run the following commands manually to upgrade your Storybook projects to Storybook 7:`, ``, `1. Call the Storybook upgrade script:`, `${pm.exec} storybook@latest upgrade`, ``, `2. Call the Nx generator to prepare your files for migration:`, `nx g @nx/storybook:migrate-7 --onlyPrepare`, ``, `3. Call the Storybook automigrate scripts:`, `Run the following commands for each Storybook project:`, ...Object.entries(storybookProjects).map(([_projectName, storybookProjectInfo]) => { return `${pm.exec} storybook@latest automigrate --config-dir ${storybookProjectInfo.configDir} --renderer ${storybookProjectInfo.uiFramework}`; }), ``, `4. Call the Nx generator to finish the migration:`, `nx g @nx/storybook:migrate-7 --afterMigration`, ], }); } function writeFile(file) { if (file?.path && file?.content) { fs.writeFileSync(file.path, file.content); } } function removePathResolvesFromNextConfig(tree, mainJsTsPath) { let mainJsTs = tree.read(mainJsTsPath, 'utf-8'); const hasNextConfig = tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="nextConfigPath"]))`); const nextConfigPathAssignment = hasNextConfig?.find((propertyAssignment) => { return propertyAssignment.getText().startsWith('nextConfigPath'); }); if (!nextConfigPathAssignment) { // No nextConfigPath found, nothing to do return; } const pathResolve = tsquery_1.tsquery.query(nextConfigPathAssignment, `CallExpression:has(PropertyAccessExpression:has([name="path"]):has([name="resolve"]))`)?.[0]; if (pathResolve) { const getStringLiteral = tsquery_1.tsquery.query(pathResolve, 'StringLiteral')?.[0]; if (getStringLiteral) { mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [ { type: devkit_1.ChangeType.Delete, start: pathResolve.getStart(), length: pathResolve.getText().length, }, { type: devkit_1.ChangeType.Insert, index: pathResolve.getStart(), text: getStringLiteral.getText(), }, ]); return { path: mainJsTsPath, content: mainJsTs, }; } } } function removeViteTsConfigPathsPlugin(tree, mainJsTsPath) { let mainJsTs = tree.read(mainJsTsPath, 'utf-8'); const { vitePluginVariableName, importExpression } = getViteTsConfigPathsNameAndImport(mainJsTs); const viteTsConfigPathsPluginParent = tsquery_1.tsquery.query(mainJsTs, `CallExpression:has(Identifier[name="${vitePluginVariableName}"])`); const viteTsConfigPathsPlugin = viteTsConfigPathsPluginParent?.find((callExpression) => { return callExpression.getText().startsWith(vitePluginVariableName); }); if (viteTsConfigPathsPlugin && importExpression) { mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [ { type: devkit_1.ChangeType.Delete, start: viteTsConfigPathsPlugin.getStart(), length: mainJsTs[viteTsConfigPathsPlugin.getEnd()] === ',' ? viteTsConfigPathsPlugin.getText().length + 1 : viteTsConfigPathsPlugin.getText().length, }, { type: devkit_1.ChangeType.Delete, start: importExpression.getStart(), length: importExpression.getText().length, }, ]); tree.write(mainJsTsPath, mainJsTs); removePluginsArrayIfEmpty(tree, mainJsTsPath); } } function getViteTsConfigPathsNameAndImport(mainJsTs) { const requireVariableStatement = tsquery_1.tsquery.query(mainJsTs, `VariableStatement:has(CallExpression:has(Identifier[name="require"]))`); let vitePluginVariableName; let importExpression; if (requireVariableStatement?.length) { importExpression = requireVariableStatement.find((statement) => { const requireCallExpression = tsquery_1.tsquery.query(statement, 'CallExpression:has(Identifier[name="require"])'); return requireCallExpression?.[0] ?.getText() ?.includes('vite-tsconfig-paths'); }); if (importExpression) { vitePluginVariableName = tsquery_1.tsquery .query(importExpression, 'Identifier')?.[0] ?.getText(); } } else { const importDeclarations = tsquery_1.tsquery.query(mainJsTs, 'ImportDeclaration'); importExpression = importDeclarations?.find((statement) => { const stringLiteral = tsquery_1.tsquery.query(statement, 'StringLiteral'); return stringLiteral?.[0]?.getText()?.includes('vite-tsconfig-paths'); }); if (importExpression) { vitePluginVariableName = tsquery_1.tsquery .query(importExpression, 'Identifier')?.[0] ?.getText(); } } return { vitePluginVariableName, importExpression, }; } function removePluginsArrayIfEmpty(tree, mainJsTsPath) { let mainJsTs = tree.read(mainJsTsPath, 'utf-8'); const viteFinalMethodDeclaration = tsquery_1.tsquery.query(mainJsTs, `MethodDeclaration:has([name="viteFinal"])`)?.[0]; if (!viteFinalMethodDeclaration) { return; } const pluginsPropertyAssignment = tsquery_1.tsquery.query(viteFinalMethodDeclaration, `PropertyAssignment:has(Identifier:has([name="plugins"]))`)?.[0]; if (!pluginsPropertyAssignment) { return; } const pluginsArrayLiteralExpression = tsquery_1.tsquery.query(pluginsPropertyAssignment, `ArrayLiteralExpression`)?.[0]; if (pluginsArrayLiteralExpression?.getText()?.replace(/\s/g, '') === '[]') { mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [ { type: devkit_1.ChangeType.Delete, start: pluginsPropertyAssignment.getStart(), length: mainJsTs[pluginsPropertyAssignment.getEnd()] === ',' ? pluginsPropertyAssignment.getText().length + 1 : pluginsPropertyAssignment.getText().length, }, ]); tree.write(mainJsTsPath, mainJsTs); } } function addViteConfigFilePathInFrameworkOptions(tree, mainJsTsPath, viteConfigPath) { let mainJsTs = tree.read(mainJsTsPath, 'utf-8'); const viteFramework = tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/react-vite"]))`)?.[0] ?? tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/web-components-vite"]))`)?.[0] ?? tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/svelte-vite"]))`)?.[0] ?? tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/vue-vite"]))`)?.[0] ?? tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/vue3-vite"]))`)?.[0]; if (viteFramework) { const optionsPropertyAssignments = tsquery_1.tsquery.query(viteFramework, `PropertyAssignment:has(Identifier:has([name="options"]))`); const frameworkOptionsPropertyAssignment = optionsPropertyAssignments?.find((expression) => { return expression.getText().startsWith('options'); }); if (frameworkOptionsPropertyAssignment) { const objectLiteralExpression = tsquery_1.tsquery.query(frameworkOptionsPropertyAssignment, `ObjectLiteralExpression`)?.[0]; mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [ { type: devkit_1.ChangeType.Insert, index: objectLiteralExpression.getStart() + 1, text: ` builder: { viteConfigPath: '${viteConfigPath}', }, `, }, ]); tree.write(mainJsTsPath, mainJsTs); } else { const objectLiteralExpression = tsquery_1.tsquery.query(viteFramework, `ObjectLiteralExpression`)?.[0]; if (!objectLiteralExpression) { return; } mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [ { type: devkit_1.ChangeType.Insert, index: objectLiteralExpression.getStart() + 1, text: ` options: { builder: { viteConfigPath: '${viteConfigPath}', }, }, `, }, ]); tree.write(mainJsTsPath, mainJsTs); } } } function normalizeViteConfigFilePathWithTree(tree, projectRoot, configFile) { return configFile && tree.exists(configFile) ? configFile : tree.exists((0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.ts`)) ? (0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.ts`) : tree.exists((0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.js`)) ? (0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.js`) : undefined; } function removeTypecastFromMainTs(tree, mainTsPath) { let mainTs = tree.read(mainTsPath, 'utf-8'); mainTs = mainTs.replace(/as StorybookConfig/g, ''); return { path: mainTsPath, content: mainTs, }; } function removeUiFrameworkFromProjectJson(tree) { (0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/storybook:build', (options, projectName, targetName) => { if (projectName && options?.['uiFramework']) { const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, projectName); delete projectConfiguration.targets[targetName].options['uiFramework']; (0, devkit_1.updateProjectConfiguration)(tree, projectName, projectConfiguration); } }); (0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/storybook:storybook', (options, projectName, targetName) => { if (projectName && options?.['uiFramework']) { const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, projectName); delete projectConfiguration.targets[targetName].options['uiFramework']; (0, devkit_1.updateProjectConfiguration)(tree, projectName, projectConfiguration); } }); } function changeCoreCommonImportToFramework(tree, mainTsPath) { let mainTs = tree.read(mainTsPath, 'utf-8'); const importDeclarations = tsquery_1.tsquery.query(mainTs, 'ImportDeclaration:has(ImportSpecifier:has([text="StorybookConfig"]))')?.[0]; if (!importDeclarations) { return; } const storybookConfigImportPackage = tsquery_1.tsquery.query(importDeclarations, 'StringLiteral')?.[0]; if (storybookConfigImportPackage?.getText() === `'@storybook/core-common'`) { const frameworkPropertyAssignment = tsquery_1.tsquery.query(mainTs, `PropertyAssignment:has(Identifier:has([text="framework"]))`)?.[0]; if (!frameworkPropertyAssignment) { return; } const propertyAssignments = tsquery_1.tsquery.query(frameworkPropertyAssignment, `PropertyAssignment:has(Identifier:has([text="name"]))`); const namePropertyAssignment = propertyAssignments?.find((expression) => { return expression.getText().startsWith('name'); }); if (!namePropertyAssignment) { return; } const frameworkName = tsquery_1.tsquery.query(namePropertyAssignment, `StringLiteral`)?.[0]; if (frameworkName) { mainTs = (0, devkit_1.applyChangesToString)(mainTs, [ { type: devkit_1.ChangeType.Delete, start: storybookConfigImportPackage.getStart(), length: storybookConfigImportPackage.getWidth(), }, { type: devkit_1.ChangeType.Insert, index: storybookConfigImportPackage.getStart(), text: frameworkName.getText(), }, ]); tree.write(mainTsPath, mainTs); } } } function getAllStorybookInfo(tree) { const allStorybookDirs = {}; (0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/storybook:build', (options, projectName) => { if (projectName && options?.['configDir']) { const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, projectName); allStorybookDirs[projectName] = { configDir: options?.['configDir'], uiFramework: options?.['uiFramework'], viteConfigFilePath: normalizeViteConfigFilePathWithTree(tree, projectConfiguration.root, projectConfiguration.targets?.build?.options?.configFile), }; } }); (0, executor_options_utils_1.forEachExecutorOptions)(tree, '@storybook/angular:build-storybook', (options, projectName) => { if (projectName && options?.['configDir']) { allStorybookDirs[projectName] = { configDir: options?.['configDir'], uiFramework: '@storybook/angular', }; } }); return allStorybookDirs; } function prepareFiles(tree, allStorybookProjects) { devkit_1.output.log({ title: `Preparing Storybook files.`, bodyLines: [ `Nx will make some adjustments to the Storybook configuration files of your workspace`, `so that the Storybook automigration scripts can run successfully.`, `The adjustments are:`, ` - Remove the "as StorybookConfig" typecast from the main.ts files, if any`, ` - Remove the "path.resolve" calls from the Next.js Storybook configuration, if any`, ], color: 'blue', }); Object.entries(allStorybookProjects).forEach(([projectName, storybookProjectInfo]) => { const mainJsTsPath = tree.exists(`${storybookProjectInfo.configDir}/main.js`) ? `${storybookProjectInfo.configDir}/main.js` : tree.exists(`${storybookProjectInfo.configDir}/main.ts`) ? `${storybookProjectInfo.configDir}/main.ts` : undefined; if (!mainJsTsPath) { devkit_1.output.error({ title: `Failed to prepare Storybook files for ${projectName}.`, bodyLines: [ `Could not find main.js or main.ts in ${storybookProjectInfo.configDir}`, `Skipping project ${projectName}.`, ], }); } if (mainJsTsPath.endsWith('.ts')) { writeFile(removeTypecastFromMainTs(tree, mainJsTsPath)); } writeFile(removePathResolvesFromNextConfig(tree, mainJsTsPath)); }); devkit_1.output.log({ title: `Files prepared successfully!`, bodyLines: [`Nx prepared your files successfully.`], color: 'green', }); } function handleMigrationResult(migrateResult, allStorybookProjects) { if ((0, fileutils_1.fileExists)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log')) && Object.keys(migrateResult.successfulProjects)?.length) { const sbLogFile = (0, fs_1.readFileSync)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log'), 'utf-8'); Object.keys(migrateResult.successfulProjects).forEach((projectName) => { if (sbLogFile.includes(`The migration failed to update your ${allStorybookProjects[projectName].configDir}`)) { migrateResult.failedProjects[projectName] = migrateResult.successfulProjects[projectName]; delete migrateResult.successfulProjects[projectName]; } }); } if (Object.keys(allStorybookProjects)?.length === Object.keys(migrateResult.successfulProjects)?.length || Object.keys(migrateResult.failedProjects)?.length === 0) { devkit_1.output.log({ title: `Storybook configuration migrated.`, bodyLines: [ `☑️ The automigrate command was successful.`, `All your projects were migrated successfully.`, ], color: 'green', }); } else { if (Object.keys(migrateResult.failedProjects).length) { if (Object.keys(migrateResult.failedProjects).length) { devkit_1.output.log({ title: `Storybook configuration migrated.`, bodyLines: [ `☑️ The automigrate command was successful.`, `The following projects were migrated successfully:`, ...Object.keys(migrateResult.successfulProjects).map((project) => ` - ${project}`), ], color: 'green', }); } devkit_1.output.log({ title: `Failed migrations.`, bodyLines: [ `There were some projects that were not migrated successfully.`, `⚠️ The following projects were not migrated successfully:`, ...Object.keys(migrateResult.failedProjects).map((project) => ` - ${project}`), `You can run the following commands to migrate them manually:`, ...Object.entries(migrateResult.failedProjects).map(([_project, command]) => `- ${command}`), ], color: 'red', }); } } return migrateResult; } function checkStorybookInstalled(packageJson) { return ((packageJson.dependencies['@storybook/core-server'] || packageJson.devDependencies['@storybook/core-server']) && (packageJson.dependencies['@nx/storybook'] || packageJson.devDependencies['@nx/storybook'])); } function checkWebComponentsInstalled(packageJson) { return (packageJson.dependencies['@storybook/web-components'] || packageJson.devDependencies['@storybook/web-components-vite'] || packageJson.dependencies['@storybook/web-components-vite'] || packageJson.devDependencies['@storybook/web-components-webpack5'] || packageJson.dependencies['@storybook/web-components-webpack5']); } function afterMigration(tree, allStorybookProjects) { Object.entries(allStorybookProjects).forEach(async ([_projectName, storybookProjectInfo]) => { const mainJsTsPath = tree.exists(`${storybookProjectInfo.configDir}/main.js`) ? `${storybookProjectInfo.configDir}/main.js` : tree.exists(`${storybookProjectInfo.configDir}/main.ts`) ? `${storybookProjectInfo.configDir}/main.ts` : undefined; removeViteTsConfigPathsPlugin(tree, mainJsTsPath); if (storybookProjectInfo.viteConfigFilePath) { addViteConfigFilePathInFrameworkOptions(tree, mainJsTsPath, storybookProjectInfo.viteConfigFilePath); } changeCoreCommonImportToFramework(tree, mainJsTsPath); }); } function logResult(tree, migrationSummary) { devkit_1.output.log({ title: `Migration complete!`, bodyLines: [ `🎉 Your Storybook configuration has been migrated to Storybook 7.0.0!`, `📖 You can see a summary of the tasks that were performed in the storybook-migration-summary.md file in the root of your workspace.`, ], color: 'green', }); (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files'), '.', { tmpl: '', successfulProjects: Object.entries(migrationSummary?.successfulProjects)?.map(([_projectName, command]) => command), failedProjects: Object.entries(migrationSummary?.failedProjects)?.map(([_projectName, command]) => command), hasFailedProjects: Object.keys(migrationSummary?.failedProjects)?.length > 0, hasSuccessfulProjects: Object.keys(migrationSummary?.successfulProjects)?.length > 0, }); }