refactor all webpack5 related migrations

This commit is contained in:
Yann Braga 2023-02-20 18:19:14 +01:00
parent b5778a0a76
commit 1f27f57472
9 changed files with 159 additions and 169 deletions

View File

@ -1,46 +1,44 @@
/* eslint-disable no-underscore-dangle */
import * as path from 'path';
import type { StorybookConfig } from '@storybook/types';
import type { JsPackageManager, PackageJson } from '../../js-package-manager';
import type { PackageJson } from '../../js-package-manager';
import { makePackageManager, mockStorybookData } from '../helpers/testing-helpers';
import { angular12 } from './angular12';
// eslint-disable-next-line global-require, jest/no-mocks-import
jest.mock('fs-extra', () => require('../../../../../__mocks__/fs-extra'));
const checkAngular12 = async ({
packageJson,
main,
main: mainConfig = {},
storybookVersion = '7.0.0',
}: {
packageJson: PackageJson;
main: Partial<StorybookConfig>;
main?: Partial<StorybookConfig> & Record<string, unknown>;
storybookVersion?: string;
}) => {
// eslint-disable-next-line global-require
require('fs-extra').__setMockFiles({
[path.join('.storybook', 'main.js')]: `module.exports = ${JSON.stringify(main)};`,
mockStorybookData({ mainConfig, storybookVersion });
return angular12.check({
packageManager: makePackageManager(packageJson),
configDir: '',
});
const packageManager = {
retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }),
} as JsPackageManager;
return angular12.check({ packageManager });
};
describe('angular12 fix', () => {
afterEach(jest.restoreAllMocks);
describe('sb < 6.3', () => {
describe('angular12 dependency', () => {
const packageJson = {
dependencies: { '@storybook/react': '^6.2.0', '@angular/core': '^12.0.0' },
dependencies: { '@storybook/angular': '^6.2.0', '@angular/core': '^12.0.0' },
};
it('should fail', async () => {
await expect(
checkAngular12({
packageJson,
main: {},
storybookVersion: '6.2.0',
})
).rejects.toThrow();
});
});
describe('no angular dependency', () => {
const packageJson = { dependencies: { '@storybook/react': '^6.2.0' } };
const packageJson = { dependencies: { '@storybook/angular': '^6.2.0' } };
it('should no-op', async () => {
await expect(
checkAngular12({
@ -54,7 +52,7 @@ describe('angular12 fix', () => {
describe('sb 6.3 - 7.0', () => {
describe('angular12 dependency', () => {
const packageJson = {
dependencies: { '@storybook/react': '^6.3.0', '@angular/core': '^12.0.0' },
dependencies: { '@storybook/angular': '^6.3.0', '@angular/core': '^12.0.0' },
};
describe('webpack5 builder', () => {
it('should no-op', async () => {
@ -62,6 +60,7 @@ describe('angular12 fix', () => {
checkAngular12({
packageJson,
main: { core: { builder: 'webpack5' } },
storybookVersion: '6.3.0',
})
).resolves.toBeFalsy();
});
@ -82,10 +81,11 @@ describe('angular12 fix', () => {
checkAngular12({
packageJson,
main: { core: { builder: 'webpack4' } },
storybookVersion: '6.3.0',
})
).resolves.toMatchObject({
angularVersion: '^12.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});
@ -94,11 +94,11 @@ describe('angular12 fix', () => {
await expect(
checkAngular12({
packageJson,
main: {},
storybookVersion: '6.3.0',
})
).resolves.toMatchObject({
angularVersion: '^12.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});
@ -108,7 +108,6 @@ describe('angular12 fix', () => {
await expect(
checkAngular12({
packageJson: {},
main: {},
})
).resolves.toBeFalsy();
});
@ -117,13 +116,12 @@ describe('angular12 fix', () => {
describe('sb 7.0+', () => {
describe('angular12 dependency', () => {
const packageJson = {
dependencies: { '@storybook/react': '^7.0.0-alpha.0', '@angular/core': '^12.0.0' },
dependencies: { '@storybook/angular': '^7.0.0-alpha.0', '@angular/core': '^12.0.0' },
};
it('should no-op', async () => {
await expect(
checkAngular12({
packageJson,
main: {},
})
).resolves.toBeFalsy();
});

View File

@ -1,15 +1,14 @@
import chalk from 'chalk';
import { dedent } from 'ts-dedent';
import semver from 'semver';
import type { ConfigFile } from '@storybook/csf-tools';
import type { Fix } from '../types';
import { webpack5 } from './webpack5';
import { checkWebpack5Builder } from '../helpers/checkWebpack5Builder';
interface Angular12RunOptions {
angularVersion: string;
// FIXME angularPresetVersion: string;
storybookVersion: string;
main: ConfigFile;
}
/**
@ -21,17 +20,16 @@ interface Angular12RunOptions {
export const angular12: Fix<Angular12RunOptions> = {
id: 'angular12',
async check({ packageManager }) {
const packageJson = packageManager.retrievePackageJson();
const { dependencies, devDependencies } = packageJson;
const angularVersion = dependencies['@angular/core'] || devDependencies['@angular/core'];
async check({ packageManager, configDir }) {
const allDependencies = packageManager.getAllDependencies();
const angularVersion = allDependencies['@angular/core'];
const angularCoerced = semver.coerce(angularVersion)?.version;
if (!angularCoerced || semver.lt(angularCoerced, '12.0.0')) {
return null;
}
const builderInfo = await webpack5.checkWebpack5Builder(packageJson);
const builderInfo = await checkWebpack5Builder({ packageManager, configDir });
return builderInfo ? { angularVersion, ...builderInfo } : null;
},

View File

@ -1,30 +1,27 @@
/* eslint-disable no-underscore-dangle */
import * as path from 'path';
import type { StorybookConfig } from '@storybook/types';
import type { JsPackageManager, PackageJson } from '../../js-package-manager';
import type { PackageJson } from '../../js-package-manager';
import { cra5 } from './cra5';
// eslint-disable-next-line global-require, jest/no-mocks-import
jest.mock('fs-extra', () => require('../../../../../__mocks__/fs-extra'));
import { makePackageManager, mockStorybookData } from '../helpers/testing-helpers';
const checkCra5 = async ({
packageJson,
main,
main: mainConfig,
storybookVersion = '7.0.0',
}: {
packageJson: PackageJson;
main: Partial<StorybookConfig>;
main?: Partial<StorybookConfig> & Record<string, unknown>;
storybookVersion?: string;
}) => {
// eslint-disable-next-line global-require
require('fs-extra').__setMockFiles({
[path.join('.storybook', 'main.js')]: `module.exports = ${JSON.stringify(main)};`,
mockStorybookData({ mainConfig, storybookVersion });
return cra5.check({
packageManager: makePackageManager(packageJson),
});
const packageManager = {
retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }),
} as JsPackageManager;
return cra5.check({ packageManager });
};
describe('cra5 fix', () => {
afterEach(jest.restoreAllMocks);
describe('sb < 6.3', () => {
describe('cra5 dependency', () => {
const packageJson = {
@ -34,7 +31,7 @@ describe('cra5 fix', () => {
await expect(
checkCra5({
packageJson,
main: {},
storybookVersion: '6.2.0',
})
).rejects.toThrow();
});
@ -82,10 +79,11 @@ describe('cra5 fix', () => {
checkCra5({
packageJson,
main: { core: { builder: 'webpack4' } },
storybookVersion: '6.3.0',
})
).resolves.toMatchObject({
craVersion: '^5.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});
@ -95,10 +93,11 @@ describe('cra5 fix', () => {
checkCra5({
packageJson,
main: {},
storybookVersion: '6.3.0',
})
).resolves.toMatchObject({
craVersion: '^5.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});

View File

@ -1,15 +1,14 @@
import chalk from 'chalk';
import { dedent } from 'ts-dedent';
import semver from 'semver';
import type { ConfigFile } from '@storybook/csf-tools';
import type { Fix } from '../types';
import { webpack5 } from './webpack5';
import { checkWebpack5Builder } from '../helpers/checkWebpack5Builder';
interface CRA5RunOptions {
craVersion: string;
// FIXME craPresetVersion: string;
storybookVersion: string;
main: ConfigFile;
}
/**
@ -21,25 +20,22 @@ interface CRA5RunOptions {
export const cra5: Fix<CRA5RunOptions> = {
id: 'cra5',
async check({ packageManager }) {
const packageJson = packageManager.retrievePackageJson();
const { dependencies, devDependencies } = packageJson;
const craVersion = dependencies['react-scripts'] || devDependencies['react-scripts'];
async check({ packageManager, configDir }) {
const allDependencies = packageManager.getAllDependencies();
const craVersion = allDependencies['react-scripts'];
const craCoerced = semver.coerce(craVersion)?.version;
if (!craCoerced || semver.lt(craCoerced, '5.0.0')) {
return null;
}
const builderInfo = await webpack5.checkWebpack5Builder(packageJson);
const builderInfo = await checkWebpack5Builder({ configDir, packageManager });
return builderInfo ? { craVersion, ...builderInfo } : null;
},
prompt({ craVersion, ...rest }) {
prompt({ craVersion }) {
const craFormatted = chalk.cyan(`Create React App (CRA) ${craVersion}`);
console.log({ ...rest });
return dedent`
We've detected you are running ${craFormatted} which is powered by webpack5.
Your Storybook's main.js files specifies webpack4, which is incompatible.

View File

@ -1,23 +1,27 @@
/* eslint-disable no-underscore-dangle */
import * as path from 'path';
import type { JsPackageManager, PackageJson } from '../../js-package-manager';
import type { StorybookConfig } from '@storybook/types';
import type { PackageJson } from '../../js-package-manager';
import { vue3 } from './vue3';
import { makePackageManager, mockStorybookData } from '../helpers/testing-helpers';
// eslint-disable-next-line global-require, jest/no-mocks-import
jest.mock('fs-extra', () => require('../../../../../__mocks__/fs-extra'));
const checkVue3 = async ({
packageJson,
main: mainConfig = {},
storybookVersion = '7.0.0',
}: {
packageJson: PackageJson;
main?: Partial<StorybookConfig> & Record<string, unknown>;
storybookVersion?: string;
}) => {
mockStorybookData({ mainConfig, storybookVersion });
const checkVue3 = async ({ packageJson, main }: { packageJson: PackageJson; main: unknown }) => {
// eslint-disable-next-line global-require
require('fs-extra').__setMockFiles({
[path.join('.storybook', 'main.js')]: `module.exports = ${JSON.stringify(main)};`,
return vue3.check({
packageManager: makePackageManager(packageJson),
});
const packageManager = {
retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }),
} as JsPackageManager;
return vue3.check({ packageManager });
};
describe('vue3 fix', () => {
afterEach(jest.restoreAllMocks);
describe('sb < 6.3', () => {
describe('vue3 dependency', () => {
const packageJson = {
@ -27,7 +31,7 @@ describe('vue3 fix', () => {
await expect(
checkVue3({
packageJson,
main: {},
storybookVersion: '6.2.0',
})
).rejects.toThrow();
});
@ -38,7 +42,7 @@ describe('vue3 fix', () => {
await expect(
checkVue3({
packageJson,
main: {},
storybookVersion: '6.2.0',
})
).resolves.toBeFalsy();
});
@ -75,10 +79,11 @@ describe('vue3 fix', () => {
checkVue3({
packageJson,
main: { core: { builder: 'webpack4' } },
storybookVersion: '6.3.0',
})
).resolves.toMatchObject({
vueVersion: '^3.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});
@ -88,10 +93,11 @@ describe('vue3 fix', () => {
checkVue3({
packageJson,
main: {},
storybookVersion: '6.3.0',
})
).resolves.toMatchObject({
vueVersion: '^3.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});

View File

@ -1,14 +1,13 @@
import chalk from 'chalk';
import { dedent } from 'ts-dedent';
import semver from 'semver';
import type { ConfigFile } from '@storybook/csf-tools';
import type { Fix } from '../types';
import { webpack5 } from './webpack5';
import { checkWebpack5Builder } from '../helpers/checkWebpack5Builder';
interface Vue3RunOptions {
vueVersion: string;
storybookVersion: string;
main: ConfigFile;
}
/**
@ -20,17 +19,16 @@ interface Vue3RunOptions {
export const vue3: Fix<Vue3RunOptions> = {
id: 'vue3',
async check({ packageManager }) {
const packageJson = packageManager.retrievePackageJson();
const { dependencies, devDependencies } = packageJson;
const vueVersion = dependencies.vue || devDependencies.vue;
async check({ configDir, packageManager }) {
const allDependencies = packageManager.getAllDependencies();
const vueVersion = allDependencies.vue;
const vueCoerced = semver.coerce(vueVersion)?.version;
if (!vueCoerced || semver.lt(vueCoerced, '3.0.0')) {
return null;
}
const builderInfo = await webpack5.checkWebpack5Builder(packageJson);
const builderInfo = await checkWebpack5Builder({ configDir, packageManager });
return builderInfo ? { vueVersion, ...builderInfo } : null;
},

View File

@ -1,30 +1,28 @@
/* eslint-disable no-underscore-dangle */
import * as path from 'path';
import type { StorybookConfig } from '@storybook/types';
import type { JsPackageManager, PackageJson } from '../../js-package-manager';
import type { PackageJson } from '../../js-package-manager';
import { webpack5 } from './webpack5';
// eslint-disable-next-line global-require, jest/no-mocks-import
jest.mock('fs-extra', () => require('../../../../../__mocks__/fs-extra'));
import { makePackageManager, mockStorybookData } from '../helpers/testing-helpers';
const checkWebpack5 = async ({
packageJson,
main,
main: mainConfig,
storybookVersion = '6.3.0',
}: {
packageJson: PackageJson;
main: Partial<StorybookConfig>;
main?: Partial<StorybookConfig> & Record<string, unknown>;
storybookVersion?: string;
}) => {
// eslint-disable-next-line global-require
require('fs-extra').__setMockFiles({
[path.join('.storybook', 'main.js')]: `module.exports = ${JSON.stringify(main)};`,
mockStorybookData({ mainConfig, storybookVersion });
return webpack5.check({
packageManager: makePackageManager(packageJson),
configDir: '',
});
const packageManager = {
retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }),
} as JsPackageManager;
return webpack5.check({ packageManager });
};
describe('webpack5 fix', () => {
afterEach(jest.restoreAllMocks);
describe('sb < 6.3', () => {
describe('webpack5 dependency', () => {
const packageJson = { dependencies: { '@storybook/react': '^6.2.0', webpack: '^5.0.0' } };
@ -32,7 +30,7 @@ describe('webpack5 fix', () => {
await expect(
checkWebpack5({
packageJson,
main: {},
storybookVersion: '6.2.0',
})
).rejects.toThrow();
});
@ -43,7 +41,7 @@ describe('webpack5 fix', () => {
await expect(
checkWebpack5({
packageJson,
main: {},
storybookVersion: '6.2.0',
})
).resolves.toBeFalsy();
});
@ -81,7 +79,7 @@ describe('webpack5 fix', () => {
})
).resolves.toMatchObject({
webpackVersion: '^5.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});
@ -94,7 +92,7 @@ describe('webpack5 fix', () => {
})
).resolves.toMatchObject({
webpackVersion: '^5.0.0',
storybookVersion: '^6.3.0',
storybookVersion: '6.3.0',
});
});
});
@ -104,7 +102,6 @@ describe('webpack5 fix', () => {
await expect(
checkWebpack5({
packageJson: {},
main: {},
})
).resolves.toBeFalsy();
});
@ -118,7 +115,6 @@ describe('webpack5 fix', () => {
webpack: '4',
},
},
main: {},
})
).resolves.toBeFalsy();
});
@ -134,6 +130,7 @@ describe('webpack5 fix', () => {
checkWebpack5({
packageJson,
main: {},
storybookVersion: '7.0.0',
})
).resolves.toBeFalsy();
});

View File

@ -1,24 +1,15 @@
import chalk from 'chalk';
import { dedent } from 'ts-dedent';
import semver from 'semver';
import type { ConfigFile } from '@storybook/csf-tools';
import { readConfig, writeConfig } from '@storybook/csf-tools';
import { getStorybookInfo } from '@storybook/core-common';
import type { Fix } from '../types';
import type { PackageJsonWithDepsAndDevDeps } from '../../js-package-manager';
import { checkWebpack5Builder } from '../helpers/checkWebpack5Builder';
import { updateMainConfig } from '../helpers/mainConfigFile';
const logger = console;
interface Webpack5RunOptions {
webpackVersion: string;
storybookVersion: string;
main: ConfigFile;
}
interface CheckBuilder {
checkWebpack5Builder: (
packageJson: PackageJsonWithDepsAndDevDeps
) => Promise<{ storybookVersion: string; main: ConfigFile }>;
}
/**
@ -31,58 +22,13 @@ interface CheckBuilder {
* - Add core.builder = 'webpack5' to main.js
* - Add 'webpack5' as a project dependency
*/
export const webpack5: Fix<Webpack5RunOptions> & CheckBuilder = {
export const webpack5: Fix<Webpack5RunOptions> = {
id: 'webpack5',
async checkWebpack5Builder(packageJson: PackageJsonWithDepsAndDevDeps) {
const { mainConfig, version: storybookVersion } = getStorybookInfo(packageJson);
async check({ configDir, packageManager }) {
const allDependencies = packageManager.retrievePackageJson().dependencies;
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? Please specify your Storybook config directory with the --config-dir flag.
`);
}
if (semver.lt(storybookCoerced, '6.3.0')) {
logger.warn(
dedent`
Detected SB 6.3 or below, please upgrade storybook to use webpack5.
To upgrade to the latest stable release, run this from your project directory:
${chalk.cyan('npx storybook upgrade')}
Add the ${chalk.cyan('--prerelease')} flag to get the latest prerelease.
`.trim()
);
return null;
}
if (semver.gte(storybookCoerced, '7.0.0')) {
return null;
}
if (!mainConfig) {
logger.warn('Unable to find storybook main.js config');
return null;
}
const main = await readConfig(mainConfig);
const builder = main.getFieldValue(['core', 'builder']);
if (builder && builder !== 'webpack4') {
logger.info(`Found builder ${builder}, skipping`);
return null;
}
return { storybookVersion, main };
},
async check({ packageManager }) {
const packageJson = packageManager.retrievePackageJson();
const { dependencies, devDependencies } = packageJson;
const webpackVersion = dependencies.webpack || devDependencies.webpack;
const webpackVersion = allDependencies.webpack;
const webpackCoerced = semver.coerce(webpackVersion)?.version;
if (
@ -92,7 +38,7 @@ export const webpack5: Fix<Webpack5RunOptions> & CheckBuilder = {
)
return null;
const builderInfo = await this.checkWebpack5Builder(packageJson);
const builderInfo = await checkWebpack5Builder({ configDir, packageManager });
return builderInfo ? { webpackVersion, ...builderInfo } : null;
},
@ -113,7 +59,12 @@ export const webpack5: Fix<Webpack5RunOptions> & CheckBuilder = {
`;
},
async run({ result: { main, storybookVersion, webpackVersion }, packageManager, dryRun }) {
async run({
result: { storybookVersion, webpackVersion },
packageManager,
dryRun,
mainConfigPath,
}) {
const deps = [`@storybook/builder-webpack5@${storybookVersion}`];
// this also gets called by 'cra5' fix so we need to add
// webpack5 at the project root so that it gets hoisted
@ -125,8 +76,9 @@ export const webpack5: Fix<Webpack5RunOptions> & CheckBuilder = {
logger.info('✅ Setting `core.builder` to `@storybook/builder-webpack5` in main.js');
if (!dryRun) {
main.setFieldValue(['core', 'builder'], '@storybook/builder-webpack5');
await writeConfig(main);
await updateMainConfig({ mainConfigPath, dryRun }, async (main) => {
main.setFieldValue(['core', 'builder'], '@storybook/builder-webpack5');
});
}
},
};

View File

@ -0,0 +1,46 @@
import chalk from 'chalk';
import semver from 'semver';
import dedent from 'ts-dedent';
import type { GetStorybookData } from './mainConfigFile';
import { getStorybookData } from './mainConfigFile';
const logger = console;
export const checkWebpack5Builder = async ({
configDir,
packageManager,
}: Parameters<GetStorybookData>[0]) => {
const { mainConfig, storybookVersion } = await getStorybookData({ configDir, packageManager });
if (semver.lt(storybookVersion, '6.3.0')) {
logger.warn(
dedent`
Detected SB 6.3 or below, please upgrade storybook to use webpack5.
To upgrade to the latest stable release, run this from your project directory:
${chalk.cyan('npx storybook upgrade')}
Add the ${chalk.cyan('--prerelease')} flag to get the latest prerelease.
`.trim()
);
return null;
}
if (semver.gte(storybookVersion, '7.0.0')) {
return null;
}
if (!mainConfig) {
logger.warn('Unable to find storybook main.js config');
return null;
}
const builder = mainConfig.core?.builder;
if (builder && builder !== 'webpack4') {
logger.info(`Found builder ${builder}, skipping`);
return null;
}
return { storybookVersion };
};