diff --git a/code/lib/cli-storybook/src/automigrate/fixes/index.ts b/code/lib/cli-storybook/src/automigrate/fixes/index.ts index c5953c59605..87f11b33690 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/index.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/index.ts @@ -16,7 +16,6 @@ import { initialGlobals } from './initial-globals'; import { mdx1to3 } from './mdx-1-to-3'; import { mdxgfm } from './mdx-gfm'; import { mdxToCSF } from './mdx-to-csf'; -import { missingStorybookDependencies } from './missing-storybook-dependencies'; import { newFrameworks } from './new-frameworks'; import { removeReactDependency } from './prompt-remove-react'; import { reactDocgen } from './react-docgen'; @@ -37,7 +36,6 @@ import { wrapRequire } from './wrap-require'; export * from '../types'; export const allFixes: Fix[] = [ - missingStorybookDependencies, addonsAPI, newFrameworks, cra5, diff --git a/code/lib/cli-storybook/src/automigrate/fixes/missing-storybook-dependencies.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/missing-storybook-dependencies.test.ts deleted file mode 100644 index 3663cf3f0bd..00000000000 --- a/code/lib/cli-storybook/src/automigrate/fixes/missing-storybook-dependencies.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; - -import type { JsPackageManager } from 'storybook/internal/common'; - -import stripAnsi from 'strip-ansi'; - -import { missingStorybookDependencies } from './missing-storybook-dependencies'; - -vi.mock('globby', () => ({ - __esModule: true, - globby: vi.fn().mockResolvedValue(['.storybook/manager.ts', 'path/to/file.stories.tsx']), -})); - -vi.mock('node:fs/promises', async (importOriginal) => { - const original = (await importOriginal()) as typeof import('node:fs/promises'); - return { - ...original, - readFile: vi.fn().mockResolvedValue(` - // these are NOT installed, will be reported - import { someFunction } from '@storybook/preview-api'; - import { anotherFunction } from '@storybook/manager-api'; - import { SomeError } from '@storybook/core-events/server-errors'; - // this IS installed, will not be reported - import { yetAnotherFunction } from '@storybook/theming'; - `), - }; -}); - -vi.mock('../../helpers', () => ({ - getStorybookVersionSpecifier: vi.fn().mockReturnValue('^8.1.10'), -})); - -const check = async ({ - packageManager, - storybookVersion = '8.1.10', -}: { - packageManager: JsPackageManager; - storybookVersion?: string; -}) => { - return missingStorybookDependencies.check({ - packageManager, - mainConfig: {} as any, - storybookVersion, - }); -}; - -describe('missingStorybookDependencies', () => { - const mockPackageManager = { - findInstallations: vi.fn().mockResolvedValue({ - dependencies: { - '@storybook/react': '8.1.0', - '@storybook/theming': '8.1.0', - }, - }), - retrievePackageJson: vi.fn().mockResolvedValue({ - dependencies: { - storybook: '8.1.0', - }, - }), - addDependencies: vi.fn().mockResolvedValue(undefined), - } as Partial; - - describe('check function', () => { - it('should identify missing dependencies', async () => { - const result = await check({ - packageManager: mockPackageManager as JsPackageManager, - }); - - expect(Object.keys(result!.packageUsage)).not.includes('@storybook/theming'); - expect(result).toEqual({ - packageUsage: { - '@storybook/preview-api': ['.storybook/manager.ts', 'path/to/file.stories.tsx'], - '@storybook/manager-api': ['.storybook/manager.ts', 'path/to/file.stories.tsx'], - '@storybook/core-events': ['.storybook/manager.ts', 'path/to/file.stories.tsx'], - }, - }); - }); - }); - - describe('prompt function', () => { - it('should provide a proper message with the missing dependencies', () => { - const packageUsage = { - '@storybook/preview-api': ['.storybook/manager.ts'], - '@storybook/manager-api': ['path/to/file.stories.tsx'], - }; - - const message = missingStorybookDependencies.prompt({ packageUsage }); - - expect(stripAnsi(message)).toMatchInlineSnapshot(` - "Found the following Storybook packages used in your project, but they are missing from your project dependencies: - - @storybook/manager-api: (1 file) - - @storybook/preview-api: (1 file) - - Referencing missing packages can cause your project to crash. We can automatically add them to your dependencies. - - More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#failed-to-resolve-import-storybookx-error" - `); - }); - }); - - describe('run function', () => { - it('should add missing dependencies', async () => { - const dryRun = false; - const packageUsage = { - '@storybook/preview-api': ['.storybook/manager.ts'], - '@storybook/manager-api': ['path/to/file.stories.tsx'], - }; - - await missingStorybookDependencies.run!({ - result: { packageUsage }, - dryRun, - packageJson: {}, - mainConfig: { stories: [] }, - packageManager: mockPackageManager as JsPackageManager, - mainConfigPath: 'path/to/main-config.js', - }); - - expect(mockPackageManager.addDependencies).toHaveBeenNthCalledWith( - 1, - { installAsDevDependencies: true }, - ['@storybook/preview-api@8.1.0', '@storybook/manager-api@8.1.0'] - ); - expect(mockPackageManager.addDependencies).toHaveBeenNthCalledWith( - 2, - { installAsDevDependencies: true, skipInstall: true, packageJson: expect.anything() }, - ['@storybook/preview-api@8.1.0', '@storybook/manager-api@8.1.0'] - ); - }); - }); -}); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/missing-storybook-dependencies.ts b/code/lib/cli-storybook/src/automigrate/fixes/missing-storybook-dependencies.ts deleted file mode 100644 index 4834cacce5d..00000000000 --- a/code/lib/cli-storybook/src/automigrate/fixes/missing-storybook-dependencies.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { readFile } from 'node:fs/promises'; - -import { getStorybookVersionSpecifier } from 'storybook/internal/cli'; -import type { InstallationMetadata, JsPackageManager } from 'storybook/internal/common'; - -import picocolors from 'picocolors'; -import { dedent } from 'ts-dedent'; - -import { consolidatedPackages } from '../helpers/consolidated-packages'; -import type { Fix } from '../types'; - -const logger = console; - -type PackageUsage = Record; - -interface MissingStorybookDependenciesOptions { - packageUsage: PackageUsage; -} - -async function checkInstallations( - packageManager: JsPackageManager, - packages: string[] -): Promise { - let result: Record = {}; - - // go through each package and get installation info at depth 0 to make sure - // the dependency is directly installed, else they could come from other dependencies - const promises = packages.map((pkg) => packageManager.findInstallations([pkg], { depth: 0 })); - - const analyses = await Promise.all(promises); - - analyses.forEach((analysis) => { - if (analysis?.dependencies) { - result = { - ...result, - ...analysis.dependencies, - }; - } - }); - - return result; -} - -/** Find usage of Storybook packages in the project files which are not present in the dependencies. */ -export const missingStorybookDependencies: Fix = { - id: 'missingStorybookDependencies', - promptType: 'auto', - versionRange: ['<8.2', '<9.0'], - - async check({ packageManager }) { - // Dynamically import globby because it is a pure ESM module - // eslint-disable-next-line depend/ban-dependencies - const { globby } = await import('globby'); - - const result = await checkInstallations(packageManager, Object.keys(consolidatedPackages)); - if (!result) { - return null; - } - - const installedDependencies = Object.keys(result).sort(); - const dependenciesToCheck = Object.keys(consolidatedPackages).filter( - (pkg) => !installedDependencies.includes(pkg) - ); - - const patterns = ['**/.storybook/*', '**/*.stories.*', '**/*.story.*']; - - const files = await globby(patterns, { - ignore: ['**/node_modules/**'], - }); - const packageUsage: PackageUsage = {}; - - for (const file of files) { - const content = await readFile(file, 'utf-8'); - dependenciesToCheck.forEach((pkg) => { - // match imports like @storybook/theming or @storybook/theming/create - const regex = new RegExp(`['"]${pkg}(/[^'"]*)?['"]`); - if (regex.test(content)) { - if (!packageUsage[pkg]) { - packageUsage[pkg] = []; - } - packageUsage[pkg].push(file); - } - }); - } - - return Object.keys(packageUsage).length > 0 ? { packageUsage } : null; - }, - - prompt({ packageUsage }) { - return dedent` - Found the following Storybook packages used in your project, but they are missing from your project dependencies: - ${Object.entries(packageUsage) - .map( - ([pkg, files]) => - `- ${picocolors.cyan(pkg)}: (${files.length} ${files.length === 1 ? 'file' : 'files'})` - ) - .sort() - .join('\n')} - - Referencing missing packages can cause your project to crash. We can automatically add them to your dependencies. - - More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#failed-to-resolve-import-storybookx-error - `; - }, - - async run({ result: { packageUsage }, dryRun, packageManager }) { - logger.info( - `✅ Installing the following packages as devDependencies: ${Object.keys(packageUsage)}` - ); - if (!dryRun) { - console.log(packageManager.retrievePackageJson()); - const dependenciesToInstall = Object.keys(packageUsage); - const versionToInstall = getStorybookVersionSpecifier( - await packageManager.retrievePackageJson() - ); - - const versionToInstallWithoutModifiers = versionToInstall?.replace(/[\^~]/, ''); - - /** - * WORKAROUND: necessary for the following scenario: Storybook latest is currently at 8.2.2 - * User has all Storybook deps at ^8.2.1 We run e.g. npm install with the dependency@^8.2.1 - * The package.json will have ^8.2.1 but install 8.2.2 So we first install the exact version, - * then run code again to write to package.json to add the caret back, but without running - * install - */ - await packageManager.addDependencies( - { installAsDevDependencies: true }, - dependenciesToInstall.map((pkg) => `${pkg}@${versionToInstallWithoutModifiers}`) - ); - const packageJson = await packageManager.retrievePackageJson(); - await packageManager.addDependencies( - { installAsDevDependencies: true, skipInstall: true, packageJson }, - dependenciesToInstall.map((pkg) => `${pkg}@${versionToInstall}`) - ); - } - }, -};