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: Automigration for upgrading storybook related dependencies #26377

Merged
Merged
Show file tree
Hide file tree
Changes from 9 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 @@ -26,6 +26,7 @@ import { removeJestTestingLibrary } from './remove-jest-testing-library';
import { addonsAPI } from './addons-api';
import { mdx1to3 } from './mdx-1-to-3';
import { addonPostCSS } from './addon-postcss';
import { upgradeStorybookRelatedDependencies } from './upgrade-storybook-related-dependencies';

export * from '../types';

Expand Down Expand Up @@ -56,6 +57,7 @@ export const allFixes: Fix[] = [
removeLegacyMDX1,
webpack5CompilerSetup,
mdx1to3,
upgradeStorybookRelatedDependencies,
];

export const initFixes: Fix[] = [eslintPlugin];
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { dedent } from 'ts-dedent';
import type { Fix } from '../types';
import { cyan, underline, yellow } from 'chalk';
import { getIncompatibleStorybookPackages } from '../../doctor/getIncompatibleStorybookPackages';
import { valid, coerce } from 'semver';

interface Options {
upgradable: { packageName: string; version: string }[];
problematicPackages: { packageName: string; version: string }[];
}

/**
* Is the user upgrading to the `latest` version of Storybook?
* Let's try to pull along some of the storybook related dependencies to `latest` as well!
*
* We communicate clearly that this migration is a helping hand, but not a complete solution.
* The user should still manually check for other dependencies that might be incompatible.
*
* see: https://github.com/storybookjs/storybook/issues/25731#issuecomment-1977346398
*/
export const upgradeStorybookRelatedDependencies = {
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
id: 'upgradeStorybookRelatedDependencies',
versionRange: ['*.*.*', '*.*.*'],
promptType: 'auto',
promptDefaultValue: false,

async check({ packageManager, storybookVersion }) {
const packageJson = await packageManager.readPackageJson();
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
const analyzed = await getIncompatibleStorybookPackages({
currentStorybookVersion: storybookVersion,
packageManager,
skipErrors: true,
});

const all = await packageManager.getAllDependencies();

const associated = Object.keys(all).filter((dep) => dep.includes('storybook'));
const detected = analyzed
.filter((m) => m.hasIncompatibleDependencies)
.map((m) => m.packageName);

const list = await Promise.all(
Array.from(new Set([...associated, ...detected])).map(async (packageName) => {
return {
packageName,
version: await packageManager.latestVersion(packageName).catch(() => null),
};
})
);

const data = list.reduce<Options>(
(acc, k) => {
if (k.version !== null) {
const { packageName, version } = k;
const upgradable = !(
!valid(k.version) ||
k.version === coerce(packageJson?.dependencies?.[k.packageName])?.toString() ||
k.version === coerce(packageJson?.devDependencies?.[k.packageName])?.toString() ||
k.version === coerce(packageJson?.peerDependencies?.[k.packageName])?.toString()
);

if (upgradable) {
acc.upgradable.push({ packageName, version });
} else {
acc.problematicPackages.push({ packageName, version });
}
}

return acc;
},
{ upgradable: [], problematicPackages: [] }
);

if (data.upgradable.length > 0) {
return data;
}

return null;
},

prompt({ upgradable: list }) {
return dedent`
You're upgrading to the latest version of Storybook. We recommend upgrading the following packages:
${list
.map(({ packageName, version }) => `- ${cyan(packageName)}@${cyan(version)}`)
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
.join('\n')}

We detected those packages are incompatible with the latest version of Storybook.
${underline(
`The list might be incomplete, so it's advised to upgrade dependencies manually, but this automigration can help you get started.`
)}

After upgrading, we will run the dedupe command, which could possibly have effects on dependencies that are not storybook related.
see: https://docs.npmjs.com/cli/commands/npm-dedupe

Do you want to proceed (upgrade the detected packages)?
`;
},

async run({ result: { upgradable, problematicPackages }, packageManager, dryRun }) {
if (dryRun) {
console.log(dedent`
We would have upgrade the following:
${upgradable.map(({ packageName, version }) => `${packageName}@${version}`).join('\n')}
`);
return;
}

if (upgradable.length > 0) {
const packageJson = await packageManager.readPackageJson();

upgradable.forEach((item) => {
if (!item) {
return;
}

const { packageName, version } = item;
const prefixed = `^${version}`;

if (packageJson.dependencies?.[packageName]) {
packageJson.dependencies[packageName] = prefixed;
}
if (packageJson.devDependencies?.[packageName]) {
packageJson.devDependencies[packageName] = prefixed;
}
if (packageJson.peerDependencies?.[packageName]) {
packageJson.peerDependencies[packageName] = prefixed;
}
});

await packageManager.writePackageJson(packageJson);
await packageManager.installDependencies();

await packageManager
.executeCommand({ command: 'dedupe', args: [], stdio: 'ignore' })
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
.catch(() => {});

console.log();
console.log(dedent`
We upgraded ${yellow(upgradable.length)} packages:
${upgradable
.map(({ packageName, version }) => `- ${cyan(packageName)}@${cyan(version)}`)
.join('\n')}
`);
}

if (problematicPackages.length) {
console.log();
console.log(dedent`
The following packages, could not be upgraded,
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
likely because there's no update available compatible with the latest version of Storybook:
${problematicPackages.map(({ packageName }) => `- ${cyan(packageName)}`).join('\n')}

We suggest your reach out to the authors of these packages to get them updated.
But before reporting, please check if there is already an open issue or PR for this.
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
`);
}
console.log();
},
} satisfies Fix<Options>;
42 changes: 34 additions & 8 deletions code/lib/cli/src/automigrate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import { getMigrationSummary } from './helpers/getMigrationSummary';
import { getStorybookData } from './helpers/mainConfigFile';
import { doctor } from '../doctor';

import { upgradeStorybookRelatedDependencies } from './fixes/upgrade-storybook-related-dependencies';
import dedent from 'ts-dedent';

const logger = console;
const LOG_FILE_NAME = 'migration-storybook.log';
const LOG_FILE_PATH = join(process.cwd(), LOG_FILE_NAME);
Expand Down Expand Up @@ -56,8 +59,16 @@ const cleanup = () => {
};

const logAvailableMigrations = () => {
const availableFixes = allFixes.map((f) => chalk.yellow(f.id)).join(', ');
logger.info(`\nThe following migrations are available: ${availableFixes}`);
const availableFixes = allFixes
.map((f) => chalk.yellow(f.id))
.map((x) => `- ${x}`)
.join('\n');

console.log();
logger.info(dedent`
The following migrations are available:
${availableFixes}
`);
};

export const doAutomigrate = async (options: AutofixOptionsFromCLI) => {
Expand All @@ -84,7 +95,7 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => {
throw new Error('Could not determine main config path');
}

await automigrate({
const outcome = await automigrate({
...options,
packageManager,
storybookVersion,
Expand All @@ -94,7 +105,9 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => {
isUpgrade: false,
});

await doctor({ configDir, packageManager: options.packageManager });
if (outcome) {
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
await doctor({ configDir, packageManager: options.packageManager });
}
};

export const automigrate = async ({
Expand All @@ -121,8 +134,21 @@ export const automigrate = async ({
return null;
}

const selectedFixes = inputFixes || allFixes;
const fixes = fixId ? selectedFixes.filter((f) => f.id === fixId) : selectedFixes;
const selectedFixes: Fix[] =
shilman marked this conversation as resolved.
Show resolved Hide resolved
inputFixes ||
allFixes.filter((fix) => {
// we only allow this automigration when the user explicitly asks for it, or they are upgrading to the latest version of storybook
if (
fix.id === upgradeStorybookRelatedDependencies.id &&
isUpgrade !== 'latest' &&
fixId !== upgradeStorybookRelatedDependencies.id
) {
return false;
}

return true;
});
const fixes: Fix[] = fixId ? selectedFixes.filter((f) => f.id === fixId) : selectedFixes;

if (fixId && fixes.length === 0) {
logger.info(`📭 No migrations found for ${chalk.magenta(fixId)}.`);
Expand All @@ -143,7 +169,7 @@ export const automigrate = async ({
mainConfigPath,
storybookVersion,
beforeVersion,
isUpgrade,
isUpgrade: !!isUpgrade,
dryRun,
yes,
});
Expand Down Expand Up @@ -314,7 +340,7 @@ export async function runFixes({
type: 'confirm',
name: 'fix',
message: `Do you want to run the '${chalk.cyan(f.id)}' migration on your project?`,
initial: true,
initial: f.promptDefaultValue ?? true,
},
{
onCancel: () => {
Expand Down
3 changes: 2 additions & 1 deletion code/lib/cli/src/automigrate/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type BaseFix<ResultType = any> = {
versionRange: [from: string, to: string];
check: (options: CheckOptions) => Promise<ResultType | null>;
prompt: (result: ResultType) => string;
promptDefaultValue?: boolean;
};

type PromptType<ResultType = any, T = Prompt> =
Expand Down Expand Up @@ -74,7 +75,7 @@ export interface AutofixOptions extends Omit<AutofixOptionsFromCLI, 'packageMana
/**
* Whether the migration is part of an upgrade.
*/
isUpgrade: boolean;
isUpgrade: false | true | 'latest';
}
export interface AutofixOptionsFromCLI {
fixId?: FixId;
Expand Down
2 changes: 1 addition & 1 deletion code/lib/cli/src/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export const doUpgrade = async ({
mainConfigPath,
beforeVersion,
storybookVersion: currentVersion,
isUpgrade: true,
isUpgrade: isOutdated ? true : 'latest',
});
}

Expand Down
Loading