From 59fd21441fa4e25c630527486032f4fa6144df5a Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Fri, 3 Jul 2020 06:13:34 +0800 Subject: [PATCH 1/2] CLI: Add upgrade utility --- lib/cli/src/generate.ts | 10 ++++ lib/cli/src/upgrade.test.ts | 36 +++++++++++++ lib/cli/src/upgrade.ts | 105 ++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 lib/cli/src/upgrade.test.ts create mode 100644 lib/cli/src/upgrade.ts diff --git a/lib/cli/src/generate.ts b/lib/cli/src/generate.ts index ad7457026091..33d19a55976a 100644 --- a/lib/cli/src/generate.ts +++ b/lib/cli/src/generate.ts @@ -7,6 +7,7 @@ import initiate from './initiate'; import { add } from './add'; import { migrate } from './migrate'; import { extract } from './extract'; +import { upgrade } from './upgrade'; // Cannot be `import` as it's not under TS root dir const pkg = require('../package.json'); @@ -32,6 +33,15 @@ program .option('-s --skip-postinstall', 'Skip package specific postinstall config modifications') .action((addonName, options) => add(addonName, options)); +program + .command('upgrade') + .description('Upgrade your storybook packages to the latest') + .option('-N --use-npm', 'Use NPM to build the Storybook server') + .option('-n --dry-run', 'Only check for upgrades, do not install') + .option('-p --prerelease', 'Upgrade to the pre-release packages') + .option('-s --skip-check', 'Skip postinstall version consistency checks') + .action((options) => upgrade(options)); + program .command('info') .description('Prints debugging information about the local environment') diff --git a/lib/cli/src/upgrade.test.ts b/lib/cli/src/upgrade.test.ts new file mode 100644 index 000000000000..3bf3c3058551 --- /dev/null +++ b/lib/cli/src/upgrade.test.ts @@ -0,0 +1,36 @@ +import { getStorybookVersion, isCorePackage } from './upgrade'; + +describe.each([ + ['│ │ │ ├── @babel/code-frame@7.10.3 deduped', null], + [ + '│ ├── @storybook/theming@6.0.0-beta.37 extraneous', + { package: '@storybook/theming', version: '6.0.0-beta.37' }, + ], + [ + '├─┬ @storybook/preset-create-react-app@3.1.2', + { package: '@storybook/preset-create-react-app', version: '3.1.2' }, + ], + ['│ ├─┬ @storybook/node-logger@5.3.19', { package: '@storybook/node-logger', version: '5.3.19' }], + [ + 'npm ERR! peer dep missing: @storybook/react@>=5.2, required by @storybook/preset-create-react-app@3.1.2', + null, + ], +])('getStorybookVersion', (input, output) => { + it(input, () => { + expect(getStorybookVersion(input)).toEqual(output); + }); +}); + +describe.each([ + ['@storybook/react', true], + ['@storybook/node-logger', true], + ['@storybook/addon-info', true], + ['@storybook/something-random', true], + ['@storybook/preset-create-react-app', false], + ['@storybook/linter-config', false], + ['@storybook/design-system', false], +])('isCorePackage', (input, output) => { + it(input, () => { + expect(isCorePackage(input)).toEqual(output); + }); +}); diff --git a/lib/cli/src/upgrade.ts b/lib/cli/src/upgrade.ts new file mode 100644 index 000000000000..9142729399a7 --- /dev/null +++ b/lib/cli/src/upgrade.ts @@ -0,0 +1,105 @@ +import { sync as spawnSync } from 'cross-spawn'; +import semver from '@storybook/semver'; +import { logger } from '@storybook/node-logger'; +import { JsPackageManagerFactory } from './js-package-manager'; +import { commandLog } from './helpers'; + +type Package = { + package: string; + version: string; +}; + +const versionRegex = /(@storybook\/[^@]+)@(\S+)/; +export const getStorybookVersion = (line: string) => { + if (line.startsWith('npm ')) return null; + const match = versionRegex.exec(line); + if (!match || !semver.clean(match[2])) return null; + return { + package: match[1], + version: match[2], + }; +}; + +const excludeList = [ + '@storybook/linter-config', + '@storybook/design-system', + '@storybook/ember-cli-storybook', + '@storybook/semver', + '@storybook/eslint-config-storybook', + '@storybook/addon-console', + '@storybook/csf', +]; +export const isCorePackage = (pkg: string) => + pkg.startsWith('@storybook/') && + !pkg.startsWith('@storybook/preset-') && + !excludeList.includes(pkg); + +const deprecatedList = [ + '@storybook/addon-notes', + '@storybook/addon-info', + '@storybook/addon-contexts', + '@storybook/addon-options', + '@storybook/addon-centered', +]; +export const isDeprecatedPackage = (pkg: string) => deprecatedList.includes(pkg); + +const formatPackage = (pkg: Package) => `${pkg.package}@${pkg.version}`; + +const warnPackages = (pkgs: Package[]) => + pkgs.forEach((pkg) => logger.warn(`- ${formatPackage(pkg)}`)); + +export const checkVersionConsistency = () => { + const lines = spawnSync('npm', ['ls'], { stdio: 'pipe' }).output.toString().split('\n'); + const storybookPackages = lines + .map(getStorybookVersion) + .filter(Boolean) + .filter((pkg) => isCorePackage(pkg.package)); + if (!storybookPackages.length) { + throw new Error('No storybook core packages found!'); + } + storybookPackages.sort((a, b) => semver.rcompare(a.version, b.version)); + const latestVersion = storybookPackages[0].version; + const outdated = storybookPackages.filter((pkg) => pkg.version !== latestVersion); + if (outdated.length > 0) { + logger.warn( + `Found ${outdated.length} outdated packages (relative to '${formatPackage( + storybookPackages[0] + )}')` + ); + logger.warn('Please make sure your packages are updated to ensure a consistent experience.'); + warnPackages(outdated); + } + + if (semver.gt(latestVersion, '5.4.0')) { + const deprecated = storybookPackages.filter((pkg) => isDeprecatedPackage(pkg.package)); + if (deprecated.length > 0) { + logger.warn(`Found ${deprecated.length} deprecated packages`); + logger.warn( + 'See https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#60-deprecations' + ); + warnPackages(deprecated); + } + } +}; + +type Options = { prerelease: boolean; skipCheck: boolean; useNpm: boolean; dryRun: boolean }; +export const upgrade = async ({ prerelease, skipCheck, useNpm, dryRun }: Options) => { + const packageManager = JsPackageManagerFactory.getPackageManager(useNpm); + + commandLog(`Checking for latest versions of '@storybook/*' packages`); + + const flags = []; + if (!dryRun) flags.push('--upgrade'); + if (prerelease) flags.push('--newest'); + const check = spawnSync('npx', ['npm-check-updates', '/storybook/', ...flags], { + stdio: 'pipe', + }).output.toString(); + logger.info(check); + + if (!dryRun) { + commandLog(`Installing upgrades`); + packageManager.installDependencies(); + } + + if (!skipCheck) checkVersionConsistency(); +}; From 8440db83ef619deb6c32da1755a05f77ee621f02 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Fri, 3 Jul 2020 15:17:23 +0800 Subject: [PATCH 2/2] CLI: Generalize upgrade deprecation scheme --- lib/cli/src/upgrade.ts | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/cli/src/upgrade.ts b/lib/cli/src/upgrade.ts index 9142729399a7..990c5d18fde5 100644 --- a/lib/cli/src/upgrade.ts +++ b/lib/cli/src/upgrade.ts @@ -34,14 +34,19 @@ export const isCorePackage = (pkg: string) => !pkg.startsWith('@storybook/preset-') && !excludeList.includes(pkg); -const deprecatedList = [ - '@storybook/addon-notes', - '@storybook/addon-info', - '@storybook/addon-contexts', - '@storybook/addon-options', - '@storybook/addon-centered', +const deprecatedPackages = [ + { + minVersion: '6.0.0-alpha.0', + url: 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#60-deprecations', + deprecations: [ + '@storybook/addon-notes', + '@storybook/addon-info', + '@storybook/addon-contexts', + '@storybook/addon-options', + '@storybook/addon-centered', + ], + }, ]; -export const isDeprecatedPackage = (pkg: string) => deprecatedList.includes(pkg); const formatPackage = (pkg: Package) => `${pkg.package}@${pkg.version}`; @@ -70,16 +75,16 @@ export const checkVersionConsistency = () => { warnPackages(outdated); } - if (semver.gt(latestVersion, '5.4.0')) { - const deprecated = storybookPackages.filter((pkg) => isDeprecatedPackage(pkg.package)); - if (deprecated.length > 0) { - logger.warn(`Found ${deprecated.length} deprecated packages`); - logger.warn( - 'See https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#60-deprecations' - ); - warnPackages(deprecated); + deprecatedPackages.forEach(({ minVersion, url, deprecations }) => { + if (semver.gte(latestVersion, minVersion)) { + const deprecated = storybookPackages.filter((pkg) => deprecations.includes(pkg.package)); + if (deprecated.length > 0) { + logger.warn(`Found ${deprecated.length} deprecated packages since ${minVersion}`); + logger.warn(`See ${url}`); + warnPackages(deprecated); + } } - } + }); }; type Options = { prerelease: boolean; skipCheck: boolean; useNpm: boolean; dryRun: boolean };