diff --git a/CHANGELOG.md b/CHANGELOG.md index b1bffa805860..5143f97018fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 8.2.3 + +- Bug: Fix invalid docs links in Configure.mdx template page - [#28560](https://github.com/storybookjs/storybook/pull/28560), thanks @kylegach! +- CLI: Add "missing-storybook-dependencies" automigration - [#28579](https://github.com/storybookjs/storybook/pull/28579), thanks @yannbf! +- CPC: Add `theming/create` aliases in docs preset - [#28570](https://github.com/storybookjs/storybook/pull/28570), thanks @ndelangen! +- CPC: Fix incorrect re-export in `core-events` - [#28573](https://github.com/storybookjs/storybook/pull/28573), thanks @ndelangen! +- CPC: Fix Vite builder had wrong conditions - [#28581](https://github.com/storybookjs/storybook/pull/28581), thanks @ndelangen! +- CSF: Fix small typing issue - [#28587](https://github.com/storybookjs/storybook/pull/28587), thanks @valentinpalkovic! +- Portable stories: Remove unused types - [#28548](https://github.com/storybookjs/storybook/pull/28548), thanks @kasperpeulen! +- Webpack: Fix sourceMap generation in csf-tools - [#28585](https://github.com/storybookjs/storybook/pull/28585), thanks @valentinpalkovic! + ## 8.2.2 - CPC: Add `ESM` export to `docs-tools` & `node-logger` packages - [#28539](https://github.com/storybookjs/storybook/pull/28539), thanks @ndelangen! diff --git a/MIGRATION.md b/MIGRATION.md index 33dfe17f0532..3dd1311dd620 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,7 @@

Migration

- [From version 8.1.x to 8.2.x](#from-version-81x-to-82x) + - [Failed to resolve import "@storybook/X" error](#failed-to-resolve-import-storybookx-error) - [Preview.js globals renamed to initialGlobals](#previewjs-globals-renamed-to-initialglobals) - [From version 8.0.x to 8.1.x](#from-version-80x-to-81x) - [Portable stories](#portable-stories) @@ -415,6 +416,26 @@ ## From version 8.1.x to 8.2.x +### Failed to resolve import "@storybook/X" error + +Storybook's package structure changed in 8.2. It is a non-breaking change, but can expose missing project dependencies. + +This happens when `@storybook/X` is missing in your `package.json`, but your project references `@storybook/X` in your source code (typically in a story file or in a `.storybook` config file). This is a problem with your project, and if it worked in earlier versions of Storybook, it was purely accidental. + +Now in Storybook 8.2, that incorrect project configuration no longer works. The solution is to install `@storybook/X` as a dev dependency and re-run. + +Example errors: + +```sh +Cannot find module @storybook/preview-api or its corresponding type declarations +``` + +```sh +Internal server error: Failed to resolve import "@storybook/theming/create" from ".storybook/theme.ts". Does the file exist? +``` + +To protect your project from missing dependencies, try the `no-extraneous-dependencies` rule in [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import). + ### Preview.js globals renamed to initialGlobals Starting in 8.2 `preview.js` `globals` are deprecated and have been renamed to `initialGlobals`. We will remove `preview.js` `globals` in 9.0. diff --git a/code/addons/docs/src/preset.ts b/code/addons/docs/src/preset.ts index 9f50f7d9a254..0ad19b34bd58 100644 --- a/code/addons/docs/src/preset.ts +++ b/code/addons/docs/src/preset.ts @@ -70,6 +70,8 @@ async function webpack( */ const cliPath = require.resolve('storybook/package.json'); const themingPath = join(cliPath, '..', 'core', 'theming', 'index.js'); + const themingCreatePath = join(cliPath, 'core', 'theming', 'create.js'); + const componentsPath = join(cliPath, '..', 'core', 'components', 'index.js'); const blocksPath = dirname(require.resolve('@storybook/blocks/package.json')); if (Array.isArray(webpackConfig.resolve?.alias)) { @@ -87,6 +89,10 @@ async function webpack( name: '@mdx-js/react', alias: mdx, }, + { + name: '@storybook/theming/create', + alias: themingCreatePath, + }, { name: '@storybook/theming', alias: themingPath, @@ -104,6 +110,7 @@ async function webpack( alias = { ...webpackConfig.resolve?.alias, react, + '@storybook/theming/create': themingCreatePath, '@storybook/theming': themingPath, '@storybook/components': componentsPath, '@storybook/blocks': blocksPath, @@ -168,6 +175,7 @@ export const viteFinal = async (config: any, options: Options) => { const cliPath = dirname(require.resolve('storybook/package.json')); const themingPath = join(cliPath, 'core', 'theming', 'index.js'); + const themingCreatePath = join(cliPath, 'core', 'theming', 'create.js'); const componentsPath = join(cliPath, 'core', 'components', 'index.js'); const blocksPath = dirname(require.resolve('@storybook/blocks/package.json')); @@ -187,6 +195,7 @@ export const viteFinal = async (config: any, options: Options) => { * * In the future the `@storybook/theming` and `@storybook/components` can be removed, as they should be singletons in the future due to the peerDependency on `storybook` package. */ + '@storybook/theming/create': themingCreatePath, '@storybook/theming': themingPath, '@storybook/components': componentsPath, '@storybook/blocks': blocksPath, diff --git a/code/builders/builder-vite/src/vite-config.ts b/code/builders/builder-vite/src/vite-config.ts index dd06ce60155a..2a965dbacffc 100644 --- a/code/builders/builder-vite/src/vite-config.ts +++ b/code/builders/builder-vite/src/vite-config.ts @@ -65,7 +65,7 @@ export async function commonConfig( base: './', plugins: await pluginConfig(options), resolve: { - conditions: ['storybook', 'stories', 'test', 'browser', 'import', 'module', 'default'], + conditions: ['storybook', 'stories', 'test'], preserveSymlinks: isPreservingSymlinks(), alias: { assert: require.resolve('browser-assert'), diff --git a/code/core/src/common/js-package-manager/JsPackageManager.ts b/code/core/src/common/js-package-manager/JsPackageManager.ts index 66ac85860e72..1000cc7bf174 100644 --- a/code/core/src/common/js-package-manager/JsPackageManager.ts +++ b/code/core/src/common/js-package-manager/JsPackageManager.ts @@ -500,6 +500,10 @@ export abstract class JsPackageManager { stdio?: 'inherit' | 'pipe' ): string; public abstract findInstallations(pattern?: string[]): Promise; + public abstract findInstallations( + pattern?: string[], + options?: { depth: number } + ): Promise; public abstract parseErrorFromLogs(logs?: string): string; public executeCommandSync({ diff --git a/code/core/src/common/js-package-manager/NPMProxy.ts b/code/core/src/common/js-package-manager/NPMProxy.ts index 09379285d276..ff77aedfa95a 100644 --- a/code/core/src/common/js-package-manager/NPMProxy.ts +++ b/code/core/src/common/js-package-manager/NPMProxy.ts @@ -132,12 +132,12 @@ export class NPMProxy extends JsPackageManager { }); } - public async findInstallations(pattern: string[]) { - const exec = async ({ depth }: { depth: number }) => { + public async findInstallations(pattern: string[], { depth = 99 }: { depth?: number } = {}) { + const exec = async ({ packageDepth }: { packageDepth: number }) => { const pipeToNull = platform() === 'win32' ? '2>NUL' : '2>/dev/null'; return this.executeCommand({ command: 'npm', - args: ['ls', '--json', `--depth=${depth}`, pipeToNull], + args: ['ls', '--json', `--depth=${packageDepth}`, pipeToNull], env: { FORCE_COLOR: 'false', }, @@ -145,7 +145,7 @@ export class NPMProxy extends JsPackageManager { }; try { - const commandResult = await exec({ depth: 99 }); + const commandResult = await exec({ packageDepth: depth }); const parsedOutput = JSON.parse(commandResult); return this.mapDependencies(parsedOutput, pattern); @@ -153,7 +153,7 @@ export class NPMProxy extends JsPackageManager { // when --depth is higher than 0, npm can return a non-zero exit code // in case the user's project has peer dependency issues. So we try again with no depth try { - const commandResult = await exec({ depth: 0 }); + const commandResult = await exec({ packageDepth: 0 }); const parsedOutput = JSON.parse(commandResult); return this.mapDependencies(parsedOutput, pattern); diff --git a/code/core/src/common/js-package-manager/PNPMProxy.ts b/code/core/src/common/js-package-manager/PNPMProxy.ts index c44172aebb86..41c2858763c8 100644 --- a/code/core/src/common/js-package-manager/PNPMProxy.ts +++ b/code/core/src/common/js-package-manager/PNPMProxy.ts @@ -98,16 +98,16 @@ export class PNPMProxy extends JsPackageManager { }); } - public async findInstallations(pattern: string[]) { - const commandResult = await this.executeCommand({ - command: 'pnpm', - args: ['list', pattern.map((p) => `"${p}"`).join(' '), '--json', '--depth=99'], - env: { - FORCE_COLOR: 'false', - }, - }); - + public async findInstallations(pattern: string[], { depth = 99 }: { depth?: number } = {}) { try { + const commandResult = await this.executeCommand({ + command: 'pnpm', + args: ['list', pattern.map((p) => `"${p}"`).join(' '), '--json', `--depth=${depth}`], + env: { + FORCE_COLOR: 'false', + }, + }); + const parsedOutput = JSON.parse(commandResult); return this.mapDependencies(parsedOutput, pattern); } catch (e) { diff --git a/code/core/src/common/js-package-manager/Yarn1Proxy.ts b/code/core/src/common/js-package-manager/Yarn1Proxy.ts index 9924afd0fb91..b193d4db4f15 100644 --- a/code/core/src/common/js-package-manager/Yarn1Proxy.ts +++ b/code/core/src/common/js-package-manager/Yarn1Proxy.ts @@ -83,16 +83,22 @@ export class Yarn1Proxy extends JsPackageManager { return JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as Record; } - public async findInstallations(pattern: string[]) { - const commandResult = await this.executeCommand({ - command: 'yarn', - args: ['list', '--pattern', pattern.map((p) => `"${p}"`).join(' '), '--recursive', '--json'], - env: { - FORCE_COLOR: 'false', - }, - }); + public async findInstallations(pattern: string[], { depth = 99 }: { depth?: number } = {}) { + const yarnArgs = ['list', '--pattern', pattern.map((p) => `"${p}"`).join(' '), '--json']; + + if (depth !== 0) { + yarnArgs.push('--recursive'); + } try { + const commandResult = await this.executeCommand({ + command: 'yarn', + args: yarnArgs.concat(pattern), + env: { + FORCE_COLOR: 'false', + }, + }); + const parsedOutput = JSON.parse(commandResult); return this.mapDependencies(parsedOutput, pattern); } catch (e) { diff --git a/code/core/src/common/js-package-manager/Yarn2Proxy.ts b/code/core/src/common/js-package-manager/Yarn2Proxy.ts index acd1b9bbfcdf..b8ea0f40a66f 100644 --- a/code/core/src/common/js-package-manager/Yarn2Proxy.ts +++ b/code/core/src/common/js-package-manager/Yarn2Proxy.ts @@ -121,16 +121,22 @@ export class Yarn2Proxy extends JsPackageManager { return this.executeCommand({ command: 'yarn', args: [command, ...args], cwd }); } - public async findInstallations(pattern: string[]) { - const commandResult = await this.executeCommand({ - command: 'yarn', - args: ['info', '--name-only', '--recursive', ...pattern], - env: { - FORCE_COLOR: 'false', - }, - }); + public async findInstallations(pattern: string[], { depth = 99 }: { depth?: number } = {}) { + const yarnArgs = ['info', '--name-only']; + + if (depth !== 0) { + yarnArgs.push('--recursive'); + } try { + const commandResult = await this.executeCommand({ + command: 'yarn', + args: yarnArgs.concat(pattern), + env: { + FORCE_COLOR: 'false', + }, + }); + return this.mapDependencies(commandResult, pattern); } catch (e) { return undefined; diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index f748d1d2d4ba..2ab3bfa27408 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -3,7 +3,7 @@ import { readFile, writeFile } from 'node:fs/promises'; import { dedent } from 'ts-dedent'; import * as t from '@babel/types'; -import bg from '@babel/generator'; +import bg, { type GeneratorOptions } from '@babel/generator'; import bt from '@babel/traverse'; import * as recast from 'recast'; @@ -599,15 +599,9 @@ export const loadCsf = (code: string, options: CsfOptions) => { return new CsfFile(ast, options); }; -interface FormatOptions { - sourceMaps?: boolean; - preserveStyle?: boolean; - inputSourceMap?: any; -} - export const formatCsf = ( csf: CsfFile, - options: FormatOptions = { sourceMaps: false }, + options: GeneratorOptions & { inputSourceMap?: any } = { sourceMaps: false }, code?: string ) => { const result = generate(csf._ast, options, code); diff --git a/code/core/src/preview-api/modules/store/csf/portable-stories.ts b/code/core/src/preview-api/modules/store/csf/portable-stories.ts index 647fa492a0e1..37893aa4c035 100644 --- a/code/core/src/preview-api/modules/store/csf/portable-stories.ts +++ b/code/core/src/preview-api/modules/store/csf/portable-stories.ts @@ -263,10 +263,7 @@ type UnwrappedJSXStoryRef = { __pw_type: 'jsx'; type: UnwrappedImportStoryRef; }; -type UnwrappedImportStoryRef = ComposedStoryFn & { - playPromise?: Promise; - renderingEnded?: PromiseWithResolvers; -}; +type UnwrappedImportStoryRef = ComposedStoryFn; declare global { function __pwUnwrapObject( diff --git a/code/deprecated/core-events/shim.js b/code/deprecated/core-events/shim.js index 4c4b44a5716d..217389a630ed 100644 --- a/code/deprecated/core-events/shim.js +++ b/code/deprecated/core-events/shim.js @@ -1 +1 @@ -module.exports = require('storybook/internal/core-errors'); +module.exports = require('storybook/internal/core-events'); diff --git a/code/frameworks/nextjs/template/cli/js/Configure.mdx b/code/frameworks/nextjs/template/cli/js/Configure.mdx index 055a3c564efc..cc3292373f73 100644 --- a/code/frameworks/nextjs/template/cli/js/Configure.mdx +++ b/code/frameworks/nextjs/template/cli/js/Configure.mdx @@ -52,7 +52,7 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -67,7 +67,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -85,7 +85,7 @@ export const RightArrow = () => Learn more @@ -113,7 +113,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -128,7 +128,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -144,7 +144,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -160,7 +160,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -175,7 +175,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -190,7 +190,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -202,7 +202,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons diff --git a/code/frameworks/nextjs/template/cli/ts-3-8/Configure.mdx b/code/frameworks/nextjs/template/cli/ts-3-8/Configure.mdx index 055a3c564efc..cc3292373f73 100644 --- a/code/frameworks/nextjs/template/cli/ts-3-8/Configure.mdx +++ b/code/frameworks/nextjs/template/cli/ts-3-8/Configure.mdx @@ -52,7 +52,7 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -67,7 +67,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -85,7 +85,7 @@ export const RightArrow = () => Learn more @@ -113,7 +113,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -128,7 +128,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -144,7 +144,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -160,7 +160,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -175,7 +175,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -190,7 +190,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -202,7 +202,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons diff --git a/code/frameworks/nextjs/template/cli/ts-4-9/Configure.mdx b/code/frameworks/nextjs/template/cli/ts-4-9/Configure.mdx index 055a3c564efc..cc3292373f73 100644 --- a/code/frameworks/nextjs/template/cli/ts-4-9/Configure.mdx +++ b/code/frameworks/nextjs/template/cli/ts-4-9/Configure.mdx @@ -52,7 +52,7 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -67,7 +67,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -85,7 +85,7 @@ export const RightArrow = () => Learn more @@ -113,7 +113,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -128,7 +128,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -144,7 +144,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -160,7 +160,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -175,7 +175,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -190,7 +190,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -202,7 +202,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons diff --git a/code/lib/cli/rendererAssets/common/Configure.mdx b/code/lib/cli/rendererAssets/common/Configure.mdx index a3d3c80985fb..54813ea1f8c3 100644 --- a/code/lib/cli/rendererAssets/common/Configure.mdx +++ b/code/lib/cli/rendererAssets/common/Configure.mdx @@ -48,7 +48,7 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -60,7 +60,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -72,7 +72,7 @@ export const RightArrow = () => Learn more @@ -94,7 +94,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -103,7 +103,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -113,7 +113,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -123,7 +123,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -132,7 +132,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -141,7 +141,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -153,7 +153,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons diff --git a/code/lib/cli/src/automigrate/fixes/index.ts b/code/lib/cli/src/automigrate/fixes/index.ts index 3d68c5eec7c7..531606a6d095 100644 --- a/code/lib/cli/src/automigrate/fixes/index.ts +++ b/code/lib/cli/src/automigrate/fixes/index.ts @@ -30,10 +30,12 @@ import { vta } from './vta'; import { upgradeStorybookRelatedDependencies } from './upgrade-storybook-related-dependencies'; import { autodocsTags } from './autodocs-tags'; import { initialGlobals } from './initial-globals'; +import { missingStorybookDependencies } from './missing-storybook-dependencies'; export * from '../types'; export const allFixes: Fix[] = [ + missingStorybookDependencies, addonsAPI, newFrameworks, cra5, diff --git a/code/lib/cli/src/automigrate/fixes/missing-storybook-dependencies.test.ts b/code/lib/cli/src/automigrate/fixes/missing-storybook-dependencies.test.ts new file mode 100644 index 000000000000..52f0e42d8ff0 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/missing-storybook-dependencies.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, vi, it, beforeEach } from 'vitest'; +import type { JsPackageManager } from '@storybook/core/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', () => ({ + __esModule: true, + 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/core': '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, + packageManager: mockPackageManager as JsPackageManager, + mainConfigPath: 'path/to/main-config.js', + }); + + expect(mockPackageManager.addDependencies).toHaveBeenNthCalledWith( + 1, + { installAsDevDependencies: true }, + ['@storybook/preview-api@8.1.10', '@storybook/manager-api@8.1.10'] + ); + expect(mockPackageManager.addDependencies).toHaveBeenNthCalledWith( + 2, + { installAsDevDependencies: true, skipInstall: true, packageJson: expect.anything() }, + ['@storybook/preview-api@^8.1.10', '@storybook/manager-api@^8.1.10'] + ); + }); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/missing-storybook-dependencies.ts b/code/lib/cli/src/automigrate/fixes/missing-storybook-dependencies.ts new file mode 100644 index 000000000000..cdc9bf0fe505 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/missing-storybook-dependencies.ts @@ -0,0 +1,157 @@ +import chalk from 'chalk'; +import { readFile } from 'node:fs/promises'; +import { dedent } from 'ts-dedent'; + +import type { Fix } from '../types'; +import { getStorybookVersionSpecifier } from '../../helpers'; +import type { InstallationMetadata, JsPackageManager } from '@storybook/core/common'; + +const logger = console; + +type PackageUsage = Record; + +interface MissingStorybookDependenciesOptions { + packageUsage: PackageUsage; +} + +const consolidatedPackages = [ + '@storybook/channels', + '@storybook/client-logger', + '@storybook/core-common', + '@storybook/core-events', + '@storybook/csf-tools', + '@storybook/docs-tools', + '@storybook/node-logger', + '@storybook/preview-api', + '@storybook/router', + '@storybook/telemetry', + '@storybook/theming', + '@storybook/types', + '@storybook/manager-api', + '@storybook/manager', + '@storybook/preview', + '@storybook/core-server', + '@storybook/builder-manager', + '@storybook/components', +]; + +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', '>=8.2'], + + async check({ packageManager }) { + // Dynamically import globby because it is a pure ESM module + const { globby } = await import('globby'); + + const result = await checkInstallations(packageManager, consolidatedPackages); + if (!result) { + return null; + } + + const installedDependencies = Object.keys(result).sort(); + const dependenciesToCheck = 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]) => + `- ${chalk.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) { + 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}`) + ); + } + }, +}; diff --git a/code/lib/cli/src/automigrate/index.test.ts b/code/lib/cli/src/automigrate/index.test.ts index b7fc079655cd..7e5c7dacfb94 100644 --- a/code/lib/cli/src/automigrate/index.test.ts +++ b/code/lib/cli/src/automigrate/index.test.ts @@ -1,8 +1,7 @@ -import { vi, it, expect, describe, beforeEach } from 'vitest'; +import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest'; import { runFixes } from './index'; import type { Fix } from './types'; import type { JsPackageManager, PackageJsonWithDepsAndDevDeps } from '@storybook/core/common'; -import { afterEach } from 'node:test'; const check1 = vi.fn(); const run1 = vi.fn(); diff --git a/code/lib/cli/src/helpers.ts b/code/lib/cli/src/helpers.ts index cffea359a871..f2256e11c2b8 100644 --- a/code/lib/cli/src/helpers.ts +++ b/code/lib/cli/src/helpers.ts @@ -221,7 +221,9 @@ export async function copyTemplateFiles({ await fse.copy(await templatePath(), destinationPath, { overwrite: true }); if (includeCommonAssets) { - const rendererType = frameworkToRenderer[renderer] || 'react'; + let rendererType = frameworkToRenderer[renderer] || 'react'; + // This is only used for docs links and the docs site uses `vue` for both `vue` & `vue3` renderers + if (rendererType === 'vue3') rendererType = 'vue'; await adjustTemplate(join(destinationPath, 'Configure.mdx'), { renderer: rendererType }); } } diff --git a/code/lib/csf-plugin/src/webpack-loader.ts b/code/lib/csf-plugin/src/webpack-loader.ts index a4c7aeb825eb..1d27053918b1 100644 --- a/code/lib/csf-plugin/src/webpack-loader.ts +++ b/code/lib/csf-plugin/src/webpack-loader.ts @@ -20,7 +20,11 @@ async function loader(this: LoaderContext, content: string, map: any) { const csf = loadCsf(content, { makeTitle }).parse(); const csfSource = loadCsf(sourceCode, { makeTitle }).parse(); enrichCsf(csf, csfSource, options); - const formattedCsf = formatCsf(csf, { sourceMaps: true, inputSourceMap: map }, content); + const formattedCsf = formatCsf( + csf, + { sourceMaps: true, inputSourceMap: map, sourceFileName: id }, + content + ); if (typeof formattedCsf === 'string') { return callback(null, formattedCsf, map); diff --git a/code/package.json b/code/package.json index 7417103da866..885718e459eb 100644 --- a/code/package.json +++ b/code/package.json @@ -278,5 +278,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "8.2.3" } diff --git a/docs/_snippets/button-story-baseline.md b/docs/_snippets/button-story-baseline.md index b26bdca7ea47..e6862198b815 100644 --- a/docs/_snippets/button-story-baseline.md +++ b/docs/_snippets/button-story-baseline.md @@ -10,7 +10,7 @@ const meta: Meta