From 834c796011df09b4ac0a11e79644315f23f7e7da Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 20 Dec 2022 12:51:17 +0100 Subject: [PATCH 1/6] add missing-babelrc automigration --- code/lib/cli/src/automigrate/fixes/index.ts | 2 + .../automigrate/fixes/missing-babelrc.test.ts | 91 +++++++++++++++++++ .../src/automigrate/fixes/missing-babelrc.ts | 91 +++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts create mode 100644 code/lib/cli/src/automigrate/fixes/missing-babelrc.ts diff --git a/code/lib/cli/src/automigrate/fixes/index.ts b/code/lib/cli/src/automigrate/fixes/index.ts index 924c558d3314..1fd9ba4c55e4 100644 --- a/code/lib/cli/src/automigrate/fixes/index.ts +++ b/code/lib/cli/src/automigrate/fixes/index.ts @@ -17,6 +17,7 @@ import { autodocsTrue } from './autodocs-true'; import { sveltekitFramework } from './sveltekit-framework'; import { addReact } from './add-react'; import { nodeJsRequirement } from './nodejs-requirement'; +import { missingBabelRc } from './missing-babelrc'; export * from '../types'; @@ -38,4 +39,5 @@ export const fixes: Fix[] = [ mdx1to2, autodocsTrue, addReact, + missingBabelRc, ]; diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts new file mode 100644 index 000000000000..9aa0d770e4c2 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts @@ -0,0 +1,91 @@ +/* eslint-disable no-underscore-dangle */ +/// ; + +import path from 'path'; +import type { JsPackageManager } from '../../js-package-manager'; +import { missingBabelRc } from './missing-babelrc'; + +// eslint-disable-next-line global-require, jest/no-mocks-import +jest.mock('fs-extra', () => require('../../../../../__mocks__/fs-extra')); + +const babelContent = JSON.stringify({ + sourceType: 'unambiguous', + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: 100, + }, + }, + ], + '@babel/preset-typescript', + '@babel/preset-react', + ], + plugins: [], +}); + +const check = async ({ packageJson = {}, main = {}, extraFiles }: any) => { + if (extraFiles) { + // eslint-disable-next-line global-require + require('fs-extra').__setMockFiles({ + [path.join('.storybook', 'main.js')]: `module.exports = ${JSON.stringify(main)};`, + ...extraFiles, + }); + } + const packageManager = { + retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), + } as JsPackageManager; + return missingBabelRc.check({ packageManager }); +}; + +describe('missing-babelrc fix', () => { + it('skips when babelrc config is present', async () => { + const packageJson = { + devDependencies: { + '@storybook/react': '^7.0.0', + '@storybook/react-webpack5': '^7.0.0', + }, + }; + + // different babel extensions + await expect( + check({ extraFiles: { '.babelrc': babelContent }, packageJson }) + ).resolves.toBeNull(); + await expect( + check({ extraFiles: { '.babelrc.json': babelContent }, packageJson }) + ).resolves.toBeNull(); + await expect( + check({ extraFiles: { 'babel.config.json': babelContent }, packageJson }) + ).resolves.toBeNull(); + + // babel field in package.json + await expect( + check({ packageJson: { ...packageJson, babel: babelContent } }) + ).resolves.toBeNull(); + }); + + it('skips when using a framework that provides babel config', async () => { + const packageJson = { + devDependencies: { + '@storybook/react': '^7.0.0', + '@storybook/nextjs': '^7.0.0', + }, + }; + + await expect(check({ packageJson })).resolves.toBeNull(); + }); + + it('prompts when babelrc file is missing and framework does not provide babel config', async () => { + const packageJson = { + devDependencies: { + '@storybook/react': '^7.0.0', + '@storybook/react-webpack5': '^7.0.0', + }, + }; + + await expect(check({ packageJson })).resolves.toBe({ + needsBabelRc: true, + }); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts new file mode 100644 index 000000000000..10b64d67858b --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts @@ -0,0 +1,91 @@ +import chalk from 'chalk'; +import dedent from 'ts-dedent'; +import semver from 'semver'; +import { getStorybookInfo } from '@storybook/core-common'; +import { loadPartialConfigAsync } from '@babel/core'; +import { readConfig } from '@storybook/csf-tools'; +import type { Fix } from '../types'; + +interface MissingBabelRcOptions { + needsBabelRc: boolean; +} + +const logger = console; + +const frameworksThatNeedBabelConfig = [ + '@storybook/angular', + '@storybook/react-webpack5', + '@storybook/vue-webpack5', + '@storybook/vue3-webpack5', + '@storybook/preact-webpack5', + '@storybook/html-webpack5', + '@storybook/react-vite', + '@storybook/vue-vite', + '@storybook/vue3-vite', + '@storybook/preact-vite', + '@storybook/html-vite', +]; + +export const missingBabelRc: Fix = { + id: 'missing-babelrc', + promptOnly: true, + + async check({ packageManager }) { + const packageJson = packageManager.retrievePackageJson(); + const { mainConfig, version: storybookVersion } = getStorybookInfo(packageJson); + + const storybookCoerced = storybookVersion && semver.coerce(storybookVersion)?.version; + if (!storybookCoerced) { + throw new Error(dedent` + ❌ Unable to determine storybook version. + 🤔 Are you running automigrate from your project directory? + `); + } + + if (!semver.gte(storybookCoerced, '7.0.0')) { + return null; + } + + if (!mainConfig) { + logger.warn('Unable to find storybook main.js config, skipping'); + return null; + } + + const main = await readConfig(mainConfig); + + const frameworkField = main.getFieldValue(['framework']); + const frameworkPackage = + typeof frameworkField === 'string' ? frameworkField : frameworkField?.name; + + if (frameworksThatNeedBabelConfig.includes(frameworkPackage)) { + const config = await loadPartialConfigAsync(); + if (!config.config && !packageJson.babel) { + return { needsBabelRc: true }; + } + } + + return null; + }, + prompt() { + return dedent` + ${chalk.bold( + chalk.red('Attention') + )}: We could not automatically make this change. You'll need to do it manually. + + Storybook now uses Babel mode v7 exclusively. In 6.x, Storybook provided its own babel settings out of the box. Now, Storybook's uses your project's babel settings (.babelrc, babel.config.js, etc.) instead. + + In the new mode, Storybook expects you to provide a configuration file. If you want a configuration file that's equivalent to the 6.x default, you can run the following command in your project directory: + + ${chalk.blue('npx sb@next babelrc')} + + This will create a ${chalk.blue( + '.babelrc.json' + )} file with some basic configuration and add new package devDependencies accordingly. + + Please see the migration guide for more information: + ${chalk.yellow( + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#babel-mode-v7-exclusively' + )} + `; + }, +}; From c3d76f9d1c4db6a83fa28b95d96ac1d9929139f3 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 3 Jan 2023 11:57:26 +0100 Subject: [PATCH 2/6] improve message --- code/lib/cli/src/automigrate/fixes/missing-babelrc.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts index 10b64d67858b..6a2224874f45 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts @@ -72,9 +72,9 @@ export const missingBabelRc: Fix = { chalk.red('Attention') )}: We could not automatically make this change. You'll need to do it manually. - Storybook now uses Babel mode v7 exclusively. In 6.x, Storybook provided its own babel settings out of the box. Now, Storybook's uses your project's babel settings (.babelrc, babel.config.js, etc.) instead. + In Storybook 6, it provided its own babel settings out of the box. Now, Storybook's uses your project's babel settings (.babelrc, babel.config.js, etc.) instead. - In the new mode, Storybook expects you to provide a configuration file. If you want a configuration file that's equivalent to the 6.x default, you can run the following command in your project directory: + Storybook now expects you to provide a babel configuration file yourself. If you want a configuration file that's equivalent to the 6.x default, you can run the following command in your project directory: ${chalk.blue('npx sb@next babelrc')} From 6c1d4823e1158aebc72bd8ccfdf55a183faa3bef Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 5 Jan 2023 16:17:09 +0100 Subject: [PATCH 3/6] fix config check --- .../automigrate/fixes/missing-babelrc.test.ts | 17 +++++++++-------- .../src/automigrate/fixes/missing-babelrc.ts | 7 +++++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts index 9aa0d770e4c2..6c7b548235f2 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts @@ -26,13 +26,12 @@ const babelContent = JSON.stringify({ }); const check = async ({ packageJson = {}, main = {}, extraFiles }: any) => { - if (extraFiles) { - // eslint-disable-next-line global-require - require('fs-extra').__setMockFiles({ - [path.join('.storybook', 'main.js')]: `module.exports = ${JSON.stringify(main)};`, - ...extraFiles, - }); - } + // eslint-disable-next-line global-require + require('fs-extra').__setMockFiles({ + [path.join('.storybook', 'main.js')]: `module.exports = ${JSON.stringify(main)};`, + ...(extraFiles || {}), + }); + const packageManager = { retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), } as JsPackageManager; @@ -84,7 +83,9 @@ describe('missing-babelrc fix', () => { }, }; - await expect(check({ packageJson })).resolves.toBe({ + await expect( + check({ main: { framework: '@storybook/react-webpack5' }, packageJson }) + ).resolves.toEqual({ needsBabelRc: true, }); }); diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts index 6a2224874f45..dd576c1229fe 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts @@ -58,8 +58,11 @@ export const missingBabelRc: Fix = { typeof frameworkField === 'string' ? frameworkField : frameworkField?.name; if (frameworksThatNeedBabelConfig.includes(frameworkPackage)) { - const config = await loadPartialConfigAsync(); - if (!config.config && !packageJson.babel) { + const config = await loadPartialConfigAsync({ + babelrc: true, + }); + + if (!config.config && !config.babelrc && !packageJson.babel) { return { needsBabelRc: true }; } } From 8ff119904e9b028b2c11792bcd45769a8799008c Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 6 Jan 2023 10:35:30 +0100 Subject: [PATCH 4/6] rephrase babelrc automigration prompt --- code/lib/cli/src/automigrate/fixes/missing-babelrc.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts index dd576c1229fe..7fda44e0a0cb 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts @@ -75,15 +75,17 @@ export const missingBabelRc: Fix = { chalk.red('Attention') )}: We could not automatically make this change. You'll need to do it manually. - In Storybook 6, it provided its own babel settings out of the box. Now, Storybook's uses your project's babel settings (.babelrc, babel.config.js, etc.) instead. + We detected that your project does not have a babel configuration (.babelrc, babel.config.js, etc.). - Storybook now expects you to provide a babel configuration file yourself. If you want a configuration file that's equivalent to the 6.x default, you can run the following command in your project directory: + In version 6.x, Storybook provided its own babel settings out of the box. Now, Storybook re-uses your project's babel configuration, with small, incremental updates from Storybook addons. - ${chalk.blue('npx sb@next babelrc')} + If your project does not have a babel configuration file, you can generate one that's equivalent to the 6.x defaults with the following command in your project directory: + + ${chalk.blue('npx storybook@next babelrc')} This will create a ${chalk.blue( '.babelrc.json' - )} file with some basic configuration and add new package devDependencies accordingly. + )} file with some basic configuration and add any necessary package devDependencies. Please see the migration guide for more information: ${chalk.yellow( From 9bf5da4911259608902784bc1d2c52510dd1a225 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 6 Jan 2023 10:49:36 +0100 Subject: [PATCH 5/6] account for CRA preset --- .../src/automigrate/fixes/missing-babelrc.test.ts | 13 +++++++++++++ .../cli/src/automigrate/fixes/missing-babelrc.ts | 9 ++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts index 6c7b548235f2..1f896ed5e8e8 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts @@ -75,6 +75,19 @@ describe('missing-babelrc fix', () => { await expect(check({ packageJson })).resolves.toBeNull(); }); + it('skips when using CRA preset', async () => { + const packageJson = { + devDependencies: { + '@storybook/react': '^7.0.0', + '@storybook/react-webpack5': '^7.0.0', + }, + }; + + await expect( + check({ packageJson, main: { addons: ['@storybook/preset-create-react-app'] } }) + ).resolves.toBeNull(); + }); + it('prompts when babelrc file is missing and framework does not provide babel config', async () => { const packageJson = { devDependencies: { diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts index 7fda44e0a0cb..6ef7fa56202f 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts @@ -57,7 +57,14 @@ export const missingBabelRc: Fix = { const frameworkPackage = typeof frameworkField === 'string' ? frameworkField : frameworkField?.name; - if (frameworksThatNeedBabelConfig.includes(frameworkPackage)) { + const addons: any[] = main.getFieldValue(['addons']) || []; + + const hasCraPreset = addons.find((addon) => { + const name = typeof addon === 'string' ? addon : addon.name; + return name === '@storybook/preset-create-react-app'; + }); + + if (frameworksThatNeedBabelConfig.includes(frameworkPackage) && !hasCraPreset) { const config = await loadPartialConfigAsync({ babelrc: true, }); From 68c2009440c7317f707c42df6b5949f4eccece06 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 6 Jan 2023 11:49:10 +0100 Subject: [PATCH 6/6] remove a few frameworks in the babel check --- code/lib/cli/src/automigrate/fixes/missing-babelrc.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts index 6ef7fa56202f..fd65f84faf3e 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts @@ -13,17 +13,11 @@ interface MissingBabelRcOptions { const logger = console; const frameworksThatNeedBabelConfig = [ - '@storybook/angular', '@storybook/react-webpack5', '@storybook/vue-webpack5', '@storybook/vue3-webpack5', '@storybook/preact-webpack5', '@storybook/html-webpack5', - '@storybook/react-vite', - '@storybook/vue-vite', - '@storybook/vue3-vite', - '@storybook/preact-vite', - '@storybook/html-vite', ]; export const missingBabelRc: Fix = {