diff --git a/code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts b/code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts index 3c6795937847..122cb7f7e4f7 100644 --- a/code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts +++ b/code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts @@ -4,6 +4,9 @@ import type { StorybookConfig } from '@storybook/types'; import type { PackageJson } from '../../js-package-manager'; import { makePackageManager, mockStorybookData } from '../helpers/testing-helpers'; import { incompatibleAddons } from './incompatible-addons'; +import * as packageVersions from '../helpers/getActualPackageVersions'; + +jest.mock('../helpers/getActualPackageVersions'); const check = async ({ packageJson, @@ -25,16 +28,20 @@ const check = async ({ describe('incompatible-addons fix', () => { afterEach(jest.restoreAllMocks); - it('no-op when there are known addons', async () => { - const packageJson = { - dependencies: { '@storybook/addon-essentials': '^7.0.0' }, - }; - await expect( - check({ packageJson, main: { addons: ['@storybook/essentials'] } }) - ).resolves.toBeNull(); - }); - it('should show incompatible addons', async () => { + jest.spyOn(packageVersions, 'getActualPackageVersions').mockReturnValueOnce( + Promise.resolve([ + { + name: '@storybook/addon-essentials', + version: '7.0.0', + }, + { + name: '@storybook/addon-info', + version: '5.3.21', + }, + ]) + ); + const packageJson = { dependencies: { '@storybook/addon-essentials': '^7.0.0', @@ -44,7 +51,30 @@ describe('incompatible-addons fix', () => { await expect( check({ packageJson, main: { addons: ['@storybook/essentials', '@storybook/addon-info'] } }) ).resolves.toEqual({ - incompatibleAddonList: ['@storybook/addon-info'], + incompatibleAddonList: [ + { + name: '@storybook/addon-info', + version: '5.3.21', + }, + ], }); }); + + it('no-op when there are no incompatible addons', async () => { + jest.spyOn(packageVersions, 'getActualPackageVersions').mockReturnValueOnce( + Promise.resolve([ + { + name: '@storybook/addon-essentials', + version: '7.0.0', + }, + ]) + ); + + const packageJson = { + dependencies: { '@storybook/addon-essentials': '^7.0.0' }, + }; + await expect( + check({ packageJson, main: { addons: ['@storybook/essentials'] } }) + ).resolves.toBeNull(); + }); }); diff --git a/code/lib/cli/src/automigrate/fixes/incompatible-addons.ts b/code/lib/cli/src/automigrate/fixes/incompatible-addons.ts index df39bedd1cd1..5620da7d45e1 100644 --- a/code/lib/cli/src/automigrate/fixes/incompatible-addons.ts +++ b/code/lib/cli/src/automigrate/fixes/incompatible-addons.ts @@ -1,10 +1,11 @@ import chalk from 'chalk'; import dedent from 'ts-dedent'; import type { Fix } from '../types'; -import { getStorybookData, getIncompatibleAddons } from '../helpers/mainConfigFile'; +import { getStorybookData } from '../helpers/mainConfigFile'; +import { getIncompatibleAddons } from '../helpers/getIncompatibleAddons'; interface IncompatibleAddonsOptions { - incompatibleAddonList: string[]; + incompatibleAddonList: { name: string; version: string }[]; } export const incompatibleAddons: Fix = { @@ -17,7 +18,7 @@ export const incompatibleAddons: Fix = { configDir, }); - const incompatibleAddonList = getIncompatibleAddons(mainConfig); + const incompatibleAddonList = await getIncompatibleAddons(mainConfig); return incompatibleAddonList.length > 0 ? { incompatibleAddonList } : null; }, @@ -25,11 +26,13 @@ export const incompatibleAddons: Fix = { return dedent` ${chalk.bold( chalk.red('Attention') - )}: We've detected that you're using the following addons which are known to be incompatible with Storybook 7: + )}: We've detected that you're using the following addons in versions which are known to be incompatible with Storybook 7: - ${incompatibleAddonList.map((addon) => `- ${chalk.cyan(addon)}`).join('\n')} + ${incompatibleAddonList + .map(({ name, version }) => `- ${chalk.cyan(`${name}@${version}`)}`) + .join('\n')} - Please upgrade at your own risk, and check the following Github issue for more information: + Please be aware they might not work in Storybook 7. Reach out to their maintainers for updates and check the following Github issue for more information: ${chalk.yellow('https://github.com/storybookjs/storybook/issues/20529')} `; }, diff --git a/code/lib/cli/src/automigrate/helpers/getActualPackageVersions.ts b/code/lib/cli/src/automigrate/helpers/getActualPackageVersions.ts new file mode 100644 index 000000000000..77f741def3d6 --- /dev/null +++ b/code/lib/cli/src/automigrate/helpers/getActualPackageVersions.ts @@ -0,0 +1,26 @@ +import * as fs from 'fs-extra'; +import path from 'path'; + +export const getActualPackageVersions = async (packages: string[]) => { + return Promise.all(packages.map(getActualPackageVersion)); +}; + +export const getActualPackageVersion = async (packageName: string) => { + try { + const packageJson = await getActualPackageJson(packageName); + return { + name: packageName, + version: packageJson.version, + }; + } catch (err) { + return { name: packageName, version: null }; + } +}; + +export const getActualPackageJson = async (packageName: string) => { + const resolvedPackageJson = require.resolve(path.join(packageName, 'package.json'), { + paths: [process.cwd()], + }); + const packageJson = await fs.readJson(resolvedPackageJson); + return packageJson; +}; diff --git a/code/lib/cli/src/automigrate/helpers/getIncompatibleAddons.ts b/code/lib/cli/src/automigrate/helpers/getIncompatibleAddons.ts new file mode 100644 index 000000000000..7e0f393798fa --- /dev/null +++ b/code/lib/cli/src/automigrate/helpers/getIncompatibleAddons.ts @@ -0,0 +1,66 @@ +import type { StorybookConfig } from '@storybook/types'; +import semver from 'semver'; +import { getActualPackageVersions } from './getActualPackageVersions'; +import { getAddonNames } from './mainConfigFile'; + +export const getIncompatibleAddons = async (mainConfig: StorybookConfig) => { + // TODO: Keep this up to date with https://github.com/storybookjs/storybook/issues/20529 in case more addons get added + const incompatibleList = { + '@storybook/addon-knobs': '6.4.0', + '@storybook/addon-postcss': '2.0.0', + 'storybook-addon-next-router': '4.0.2', + 'storybook-addon-outline': '1.4.2', // (deprecated) + '@storybook/addon-info': '5.3.21', + 'storybook-addon-designs': '6.3.1', + 'storybook-addon-next': '1.7.0', // (deprecated)' + 'storybook-docs-toc': '1.7.0', + '@storybook/addon-google-analytics': '6.2.9', + 'storybook-addon-pseudo-states': '1.15.5', + 'storybook-dark-mode': '2.1.1', + 'storybook-addon-gatsby': '0.0.5', + '@etchteam/storybook-addon-css-variables-theme': '1.4.0', + '@storybook/addon-cssresources': '6.2.9', + 'storybook-addon-grid': '0.3.1', + 'storybook-multilevel-sort': '1.2.0', + 'storybook-addon-i18next': '1.3.0', + 'storybook-source-link': '2.0.8', + 'babel-plugin-storybook-csf-title': '2.1.0', + '@urql/storybook-addon': '2.0.1', + 'storybook-addon-intl': '2.4.1', + 'storybook-addon-mock': '3.2.0', + '@chakra-ui/storybook-addon': '4.0.16', + 'storybook-mobile-addon': '1.0.2', + }; + + const addons = getAddonNames(mainConfig).filter((addon) => addon in incompatibleList); + + if (addons.length === 0) { + return []; + } + + const addonVersions = await getActualPackageVersions(addons); + + const incompatibleAddons: { name: string; version: string }[] = []; + addonVersions.forEach( + ({ + name, + version: installedVersion, + }: { + name: keyof typeof incompatibleList; + version: string; + }) => { + if (installedVersion === null) return; + + const addonVersion = incompatibleList[name]; + try { + if (semver.lte(semver.coerce(installedVersion), semver.coerce(addonVersion))) { + incompatibleAddons.push({ name, version: installedVersion }); + } + } catch (err) { + // we tried our best but if we can't compare, we just no-op for that addon + } + } + ); + + return incompatibleAddons; +}; diff --git a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts index 79c3b2df0079..c16b97afebd6 100644 --- a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts +++ b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts @@ -90,48 +90,24 @@ export const updateMainConfig = async ( export const getAddonNames = (mainConfig: StorybookConfig): string[] => { const addons = mainConfig.addons || []; const addonList = addons.map((addon) => { + let name = ''; if (typeof addon === 'string') { - return addon; + name = addon; + } else if (typeof addon === 'object') { + name = addon.name; } - if (typeof addon === 'object') { - return addon.name; + + if (name.startsWith('.')) { + return undefined; } - return undefined; + return name + .replace(/\/dist\/.*/, '') + .replace(/\.[mc]?[tj]?s[x]?$/, '') + .replace(/\/register$/, '') + .replace(/\/manager$/, '') + .replace(/\/preset$/, ''); }); return addonList.filter(Boolean); }; - -export const getIncompatibleAddons = (mainConfig: StorybookConfig) => { - // TODO: Keep this up to date with https://github.com/storybookjs/storybook/issues/20529 - - const incompatibleList = [ - '@storybook/addon-knobs', - '@storybook/addon-postcss', - 'storybook-addon-next-router', - 'storybook-addon-outline', - '@storybook/addon-info', - 'storybook-addon-next', - 'storybook-docs-toc', - '@storybook/addon-google-analytics', - 'storybook-addon-pseudo-states', - 'storybook-dark-mode', - 'storybook-addon-gatsby', - '@etchteam/storybook-addon-css-variables-theme', - '@storybook/addon-cssresources', - 'storybook-addon-grid', - 'storybook-multilevel-sort', - 'storybook-addon-i18next', - 'storybook-source-link', - 'babel-plugin-storybook-csf-title', - '@urql/storybook-addon', - 'storybook-addon-intl', - 'storybook-addon-mock', - '@chakra-ui/storybook-addon', - 'storybook-mobile-addon', - ]; - - const addons = getAddonNames(mainConfig); - return addons.filter((addon) => incompatibleList.includes(addon)); -}; diff --git a/code/lib/core-server/src/build-dev.ts b/code/lib/core-server/src/build-dev.ts index 829a8752bf55..09612746dbfe 100644 --- a/code/lib/core-server/src/build-dev.ts +++ b/code/lib/core-server/src/build-dev.ts @@ -67,7 +67,8 @@ export async function buildDevStandalone( options.serverChannelUrl = getServerChannelUrl(port, options); /* eslint-enable no-param-reassign */ - const { framework, addons } = await loadMainConfig(options); + const config = await loadMainConfig(options); + const { framework } = config; const corePresets = []; const frameworkName = typeof framework === 'string' ? framework : framework?.name; @@ -75,7 +76,7 @@ export async function buildDevStandalone( corePresets.push(join(frameworkName, 'preset')); - warnOnIncompatibleAddons(addons); + await warnOnIncompatibleAddons(config); // Load first pass: We need to determine the builder // We need to do this because builders might introduce 'overridePresets' which we need to take into account diff --git a/code/lib/core-server/src/build-static.ts b/code/lib/core-server/src/build-static.ts index e4c792e39a88..5cb860409bae 100644 --- a/code/lib/core-server/src/build-static.ts +++ b/code/lib/core-server/src/build-static.ts @@ -66,7 +66,8 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption await emptyDir(options.outputDir); await ensureDir(options.outputDir); - const { framework, addons } = await loadMainConfig(options); + const config = await loadMainConfig(options); + const { framework } = config; const corePresets = []; const frameworkName = typeof framework === 'string' ? framework : framework?.name; @@ -76,7 +77,7 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption logger.warn(`you have not specified a framework in your ${options.configDir}/main.js`); } - warnOnIncompatibleAddons(addons); + await warnOnIncompatibleAddons(config); logger.info('=> Loading presets'); let presets = await loadAllPresets({ diff --git a/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts b/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts index a637c41ba841..a850d49809ad 100644 --- a/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts +++ b/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts @@ -1,61 +1,25 @@ -import type { Preset } from '@storybook/types'; +import type { StorybookConfig } from '@storybook/types'; import { logger } from '@storybook/client-logger'; import chalk from 'chalk'; import dedent from 'ts-dedent'; -export const warnOnIncompatibleAddons = (addons: Preset[]) => { - const addonList = addons.map((addon) => { - if (typeof addon === 'string') { - return addon; - } - if (typeof addon === 'object') { - return addon.name; - } +import { getIncompatibleAddons } from '../../../cli/src/automigrate/helpers/getIncompatibleAddons'; - return undefined; - }); - - const addonNames = addonList.filter(Boolean); - - // TODO: Keep this up to date with https://github.com/storybookjs/storybook/issues/20529 - const incompatibleList = [ - '@storybook/addon-knobs', - '@storybook/addon-postcss', - 'storybook-addon-next-router', - 'storybook-addon-outline', - '@storybook/addon-info', - 'storybook-addon-next', - 'storybook-docs-toc', - '@storybook/addon-google-analytics', - 'storybook-addon-pseudo-states', - 'storybook-dark-mode', - 'storybook-addon-gatsby', - '@etchteam/storybook-addon-css-variables-theme', - '@storybook/addon-cssresources', - 'storybook-addon-grid', - 'storybook-multilevel-sort', - 'storybook-addon-i18next', - 'storybook-source-link', - 'babel-plugin-storybook-csf-title', - '@urql/storybook-addon', - 'storybook-addon-intl', - 'storybook-addon-mock', - '@chakra-ui/storybook-addon', - 'storybook-mobile-addon', - ]; - - const incompatibleAddons = addonNames.filter((addon) => incompatibleList.includes(addon)); +export const warnOnIncompatibleAddons = async (config: StorybookConfig) => { + const incompatibleAddons = await getIncompatibleAddons(config); if (incompatibleAddons.length > 0) { logger.warn(dedent` ${chalk.bold( chalk.red('Attention') - )}: We've detected that you're using the following addons which are known to be incompatible with Storybook 7: + )}: We've detected that you're using the following addons in versions which are known to be incompatible with Storybook 7: - ${incompatibleAddons.map((addon) => `- ${chalk.cyan(addon)}`).join('\n')} + ${incompatibleAddons + .map(({ name, version }) => `- ${chalk.cyan(`${name}@${version}`)}`) + .join('\n')} - Please upgrade at your own risk, and check the following Github issue for more information: - ${chalk.yellow('https://github.com/storybookjs/storybook/issues/20529')}\n + Please be aware they might not work in Storybook 7. Reach out to their maintainers for updates and check the following Github issue for more information: + ${chalk.yellow('https://github.com/storybookjs/storybook/issues/20529')} `); } };