Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: Improve incompatible addons logic #21883

Merged
merged 3 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 40 additions & 10 deletions code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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',
Expand All @@ -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();
});
});
15 changes: 9 additions & 6 deletions code/lib/cli/src/automigrate/fixes/incompatible-addons.ts
Original file line number Diff line number Diff line change
@@ -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<IncompatibleAddonsOptions> = {
Expand All @@ -17,19 +18,21 @@ export const incompatibleAddons: Fix<IncompatibleAddonsOptions> = {
configDir,
});

const incompatibleAddonList = getIncompatibleAddons(mainConfig);
const incompatibleAddonList = await getIncompatibleAddons(mainConfig);

return incompatibleAddonList.length > 0 ? { incompatibleAddonList } : null;
},
prompt({ incompatibleAddonList }) {
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')}
`;
},
Expand Down
26 changes: 26 additions & 0 deletions code/lib/cli/src/automigrate/helpers/getActualPackageVersions.ts
Original file line number Diff line number Diff line change
@@ -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;
};
66 changes: 66 additions & 0 deletions code/lib/cli/src/automigrate/helpers/getIncompatibleAddons.ts
Original file line number Diff line number Diff line change
@@ -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)'
shilman marked this conversation as resolved.
Show resolved Hide resolved
'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;
};
50 changes: 13 additions & 37 deletions code/lib/cli/src/automigrate/helpers/mainConfigFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
};
5 changes: 3 additions & 2 deletions code/lib/core-server/src/build-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ 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;
validateFrameworkName(frameworkName);

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
Expand Down
5 changes: 3 additions & 2 deletions code/lib/core-server/src/build-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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({
Expand Down
56 changes: 10 additions & 46 deletions code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts
Original file line number Diff line number Diff line change
@@ -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')}
`);
}
};