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: add "missing-babelrc" automigration #20341

Merged
merged 6 commits into from
Jan 13, 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
2 changes: 2 additions & 0 deletions code/lib/cli/src/automigrate/fixes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -38,4 +39,5 @@ export const fixes: Fix[] = [
mdx1to2,
autodocsTrue,
addReact,
missingBabelRc,
];
105 changes: 105 additions & 0 deletions code/lib/cli/src/automigrate/fixes/missing-babelrc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* eslint-disable no-underscore-dangle */
/// <reference types="@types/jest" />;

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) => {
// 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();

yannbf marked this conversation as resolved.
Show resolved Hide resolved
// 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('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: {
'@storybook/react': '^7.0.0',
'@storybook/react-webpack5': '^7.0.0',
},
};

await expect(
check({ main: { framework: '@storybook/react-webpack5' }, packageJson })
).resolves.toEqual({
needsBabelRc: true,
});
});
});
97 changes: 97 additions & 0 deletions code/lib/cli/src/automigrate/fixes/missing-babelrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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/react-webpack5',
'@storybook/vue-webpack5',
'@storybook/vue3-webpack5',
'@storybook/preact-webpack5',
'@storybook/html-webpack5',
];

export const missingBabelRc: Fix<MissingBabelRcOptions> = {
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;

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,
});

if (!config.config && !config.babelrc && !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.

We detected that your project does not have a babel configuration (.babelrc, babel.config.js, etc.).

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.

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 any necessary package devDependencies.

Please see the migration guide for more information:
${chalk.yellow(
'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#babel-mode-v7-exclusively'
)}
`;
},
};