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

Automigration: Run the mdx-to-csf codemod during automigration #26201

Merged
merged 16 commits into from
Feb 27, 2024
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
118 changes: 0 additions & 118 deletions code/lib/cli/src/automigrate/fixes/bare-mdx-stories-glob.tsx

This file was deleted.

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 { angularBuilders } from './angular-builders';
import { angularBuildersMultiproject } from './angular-builders-multiproject';
import { wrapRequire } from './wrap-require';
import { reactDocgen } from './react-docgen';
import { mdxToCSF } from './mdx-to-csf';
import { removeReactDependency } from './prompt-remove-react';
import { storyshotsMigration } from './storyshots-migration';
import { removeArgtypesRegex } from './remove-argtypes-regex';
Expand All @@ -40,6 +41,7 @@ export const allFixes: Fix[] = [
removeJestTestingLibrary,
removedGlobalClientAPIs,
mdxgfm,
mdxToCSF,
autodocsTrue,
angularBuildersMultiproject,
angularBuilders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { StorybookConfigRaw } from '@storybook/types';
import type { PackageJson } from '@storybook/core-common';
import { ansiRegex } from '../helpers/cleanLog';
import { makePackageManager } from '../helpers/testing-helpers';
import type { BareMdxStoriesGlobRunOptions } from './bare-mdx-stories-glob';
import { bareMdxStoriesGlob } from './bare-mdx-stories-glob';
import type { BareMdxStoriesGlobRunOptions } from './mdx-to-csf';
import { mdxToCSF } from './mdx-to-csf';

const checkBareMdxStoriesGlob = async ({
packageJson,
Expand All @@ -16,7 +16,7 @@ const checkBareMdxStoriesGlob = async ({
main?: Partial<StorybookConfigRaw> & Record<string, unknown>;
storybookVersion?: string;
}) => {
return bareMdxStoriesGlob.check({
return mdxToCSF.check({
mainConfig: mainConfig as StorybookConfigRaw,
packageManager: makePackageManager(packageJson),
storybookVersion,
Expand Down Expand Up @@ -131,7 +131,7 @@ describe('bare-mdx fix', () => {
);

it('prompts', () => {
const result = bareMdxStoriesGlob.prompt({
const result = mdxToCSF.prompt({
existingStoriesEntries: [
'../src/**/*.stories.@(js|jsx|mdx|ts|tsx)',
{ directory: '../src/**', files: '*.stories.mdx' },
Expand All @@ -151,8 +151,10 @@ describe('bare-mdx fix', () => {
"files": "*.stories.mdx"
}

In Storybook 7, we have deprecated defining stories in MDX files, and consequently have changed the suffix to simply .mdx.
In Storybook 7, we have deprecated defining stories in MDX files, and consequently have changed the suffix to simply .mdx. Since Storybook 8, we have removed the support of story definition in MDX files entirely. Therefore '.stories.mdx' files aren't supported anymore.

Now, since Storybook 8.0, we have removed support for .stories.mdx files.

We can automatically migrate your 'stories' config to include any .mdx file instead of just .stories.mdx.
That would result in the following 'stories' config:
"../src/**/*.mdx"
Expand All @@ -161,7 +163,10 @@ describe('bare-mdx fix', () => {
"directory": "../src/**",
"files": "*.mdx"
}
To learn more about this change, see: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mdx-docs-files"

Additionally, we will run the 'mdx-to-csf' codemod for you, which tries to transform '*.stories.mdx' files to '*.stories.js' and '*.mdx' files.

To learn more about this change, see: https://storybook.js.org/docs/migration-guide#storiesmdx-to-mdxcsf"
`);
});
});
Expand Down
164 changes: 164 additions & 0 deletions code/lib/cli/src/automigrate/fixes/mdx-to-csf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import chalk from 'chalk';
import dedent from 'ts-dedent';
import type { StoriesEntry } from '@storybook/types';
import { updateMainConfig } from '../helpers/mainConfigFile';
import type { Fix } from '../types';
import { runCodemod } from '@storybook/codemod';
import { prompt } from 'prompts';
import { glob } from 'glob';

const logger = console;

export interface BareMdxStoriesGlobRunOptions {
existingStoriesEntries: StoriesEntry[];
nextStoriesEntries: StoriesEntry[];
files: string[];
}

const getNextGlob = (globString: string) => {
// '../src/**/*.stories.@(mdx|js|jsx|ts|tsx)' -> '../src/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'
const extGlobsRegex = new RegExp(/(.*\.)(stories\.@.*)(\|mdx|mdx\|)(.*)$/i);
if (globString.match(extGlobsRegex)) {
return globString.replace(extGlobsRegex, '$1@(mdx|$2$4)');
}

// '../src/**/*.stories.*' -> '../src/**/*.@(mdx|stories.*)'
const allStoriesExtensionsRegex = new RegExp(/(.*\.)(stories\.\*)$/i);
if (globString.match(allStoriesExtensionsRegex)) {
return globString.replace(allStoriesExtensionsRegex, '$1@(mdx|$2)');
}

// '../src/**/*.stories.mdx' -> '../src/**/*.mdx'
return globString.replaceAll('.stories.mdx', '.mdx');
};

export const mdxToCSF: Fix<BareMdxStoriesGlobRunOptions> = {
id: 'mdx-to-csf',
versionRange: ['<7', '>=7'],
async check({ mainConfig }) {
const existingStoriesEntries = mainConfig.stories as StoriesEntry[];

if (!existingStoriesEntries) {
throw new Error(dedent`
❌ Unable to determine Storybook stories globs in ${chalk.blue(
mainConfig
)}, skipping ${chalk.cyan(this.id)} fix.

In Storybook 7, we have deprecated defining stories in MDX files, and consequently have changed the suffix to simply .mdx.

Now, since Storybook 8.0, we have removed support for .stories.mdx files.

We were unable to automatically migrate your 'stories' config to include any .mdx file instead of just .stories.mdx.
We suggest you make this change manually.
To learn more about this change, see: ${chalk.yellow(
'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mdx-docs-files'
)}
`);
}

const files: string[] = [];

const nextStoriesEntries = await Promise.all(
existingStoriesEntries.map(async (entry) => {
const isSpecifier = typeof entry !== 'string';
const globString = isSpecifier ? entry.files : entry;

if (!globString) {
// storySpecifier without the 'files' property. Just add the existing to the next list
return entry;
}

files.push(...(await glob(globString)).filter((file) => file.endsWith('.stories.mdx')));

const nextGlob = getNextGlob(globString);
return isSpecifier ? { ...entry, files: nextGlob } : nextGlob;
})
);

const resultFromMainConfig = checkMainConfigStories(existingStoriesEntries, nextStoriesEntries);

if ((nextStoriesEntries && resultFromMainConfig) || files.length > 0) {
return { existingStoriesEntries, nextStoriesEntries, files };
}

// bails if there are no changes, no files to migrate, or if the nextStoriesEntries is empty
return null;
},

prompt({ existingStoriesEntries, nextStoriesEntries }) {
const prettyExistingStoriesEntries = existingStoriesEntries
.map((entry) => JSON.stringify(entry, null, 2))
.join('\n');
const prettyNextStoriesEntries = nextStoriesEntries
.map((entry) => JSON.stringify(entry, null, 2))
.join('\n');
return dedent`
We've detected your project has one or more globs in your 'stories' config that matches .stories.mdx files:
${chalk.cyan(prettyExistingStoriesEntries)}

In Storybook 7, we have deprecated defining stories in MDX files, and consequently have changed the suffix to simply .mdx. Since Storybook 8, we have removed the support of story definition in MDX files entirely. Therefore '.stories.mdx' files aren't supported anymore.

Now, since Storybook 8.0, we have removed support for .stories.mdx files.

We can automatically migrate your 'stories' config to include any .mdx file instead of just .stories.mdx.
That would result in the following 'stories' config:
${chalk.cyan(prettyNextStoriesEntries)}

Additionally, we will run the 'mdx-to-csf' codemod for you, which tries to transform '*.stories.mdx' files to '*.stories.js' and '*.mdx' files.

To learn more about this change, see: ${chalk.yellow(
'https://storybook.js.org/docs/migration-guide#storiesmdx-to-mdxcsf'
)}
`;
},

async run({ dryRun, mainConfigPath, result: { nextStoriesEntries } }) {
logger.info(dedent`✅ Setting 'stories' config:
${JSON.stringify(nextStoriesEntries, null, 2)}`);

if (!dryRun) {
const { glob: globString } = await prompt({
type: 'text',
name: 'glob',
message: 'Please enter the glob for your MDX stories',
initial: './src/**/*.stories.mdx',
});

if (globString) {
await runCodemod('mdx-to-csf', { glob: globString, dryRun, logger });
}

await updateMainConfig({ mainConfigPath, dryRun: !!dryRun }, async (main) => {
main.setFieldValue(['stories'], nextStoriesEntries);
});

logger.info(dedent`
The migration successfully updated your 'stories' config to include any .mdx file instead of just .stories.mdx.

It also ran the 'mdx-to-csf' codemod to convert your MDX stories to CSF format.
This codemod is not perfect however, so you may need to manually fix any issues it couldn't handle.
`);
}
},
};
function checkMainConfigStories(
existingStoriesEntries: StoriesEntry[],
nextStoriesEntries: StoriesEntry[]
) {
if (
existingStoriesEntries.length === nextStoriesEntries.length &&
existingStoriesEntries.every((entry, index) => {
const nextEntry = nextStoriesEntries[index];
if (typeof entry === 'string') {
return entry === nextEntry;
}
if (typeof nextEntry === 'string') {
return false;
}
return entry.files === nextEntry.files;
})
) {
return null;
}
return true;
}
5 changes: 3 additions & 2 deletions code/lib/cli/src/migrate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { listCodemods, runCodemod } from '@storybook/codemod';
import { runFixes } from './automigrate';
import { bareMdxStoriesGlob } from './automigrate/fixes/bare-mdx-stories-glob';
import { mdxToCSF } from './automigrate/fixes/mdx-to-csf';
import {
JsPackageManagerFactory,
getStorybookInfo,
Expand Down Expand Up @@ -36,7 +36,7 @@ export async function migrate(migration: any, { glob, dryRun, list, rename, pars
}

await runFixes({
fixes: [bareMdxStoriesGlob],
fixes: [mdxToCSF],
configDir,
mainConfigPath,
packageManager,
Expand All @@ -46,6 +46,7 @@ export async function migrate(migration: any, { glob, dryRun, list, rename, pars
});
await addStorybookBlocksPackage();
}

await runCodemod(migration, { glob, dryRun, logger, rename, parser });
} else {
throw new Error('Migrate: please specify a migration name or --list');
Expand Down
11 changes: 10 additions & 1 deletion code/lib/codemod/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ async function renameFile(file: any, from: any, to: any, { logger }: any) {
return renameAsync(file, newFile);
}

export async function runCodemod(codemod: any, { glob, logger, dryRun, rename, parser }: any) {
export async function runCodemod(
codemod: any,
{
glob,
logger,
dryRun,
rename,
parser,
}: { glob: any; logger: any; dryRun?: any; rename?: any; parser?: any }
) {
const codemods = listCodemods();
if (!codemods.includes(codemod)) {
throw new Error(`Unknown codemod ${codemod}. Run --list for options.`);
Expand Down
Loading